Overview
Over the last two weeks, I participated in Advent of Code 2025 — a series of daily programming puzzles that get progressively harder. This year, the event consists of 12 challenges in total, each challenge has two parts (except the very last one) where the second part is more advanced than the previous one.
Usually, I spend my time reversing x86 binaries for CTFs. For Advent of Code 2025, I decided to write them instead. I challenged myself to solve all problems entirely in x86 Assembly, without the help of LLMs.
I will provide my thought process during the challenges, and explain how I constructed the algorithms using x86 Assembly in the sections below.
Disclaimer: Due to the large amount of challenges within one post, I will try my best to only briefly explain each x86 Assembly snippet, not in detail. Methodology of constructing the solutions will be more focused than the execution.
All of the solutions can be found here.
(link to the event: https://adventofcode.com/2025/)
Preparation
To better address the problems, here are some x86 Assembly snippets that can be reused in multiple challenges. Personally, I am more familiar with Intel syntax compared to AT&T syntax, hence I chose to program the solutions in Intel syntax and used NASM to compile.
Input reading
To get the input for the problem, we utilize chain of syscalls to read the input file, store the input into a null-initialized buffer and close the file.
Each line of input will be separated by an endline \n, and passed as argument to the solver function.
Some variables are also introduced in .bss section, in which input is for storing the challenge input, result is for storing the challenge result, and output is used for integer printing that will be explained in the next part.
section .data
filename db "../problem/input.txt", 0
section .bss
input resb 100000
output resb 20
result resq 1
section .text
global _start
_start:
; Open input file
mov rax, 2
mov rdi, filename
xor rsi, rsi
xor rdx, rdx
syscall
mov rbx, rax
; Read input file
xor rax, rax
mov rdi, rbx
mov rsi, input
mov rdx, 100000
syscall
; Close the file
mov rax, 3
mov rdi, rbx
syscall
; Split the ranges and process them one by one
mov r8, input
mov r9, input
mov qword [result], 0
reader:
movzx rax, byte [r9]
test rax, rax
je process_last_line
cmp rax, 10
je process_line
inc r9
jmp reader
process_line:
mov rdi, r8
call solver
inc r9
mov r8, r9
jmp reader
process_last_line:
cmp r8, r9
je print_output
mov rdi, r8
call solver
print_output:
; Print result to stdout
mov rdi, qword [result]
call print_int
; Exit the program
mov rax, 60
xor rdi, rdi
syscallInteger printing
We generally can not directly print an integer from a register or a memory region. To resolve this, a helper is used to convert the integer to string and then print it to the screen. The default length of converted string is 20.
; ----------------------------------------------------------------------
; Component: Print Integer
; Args: rdi
print_int:
mov rax, rdi
mov rcx, output
add rcx, 19
mov byte [rcx], 10
mov rbx, 10
.loop:
dec rcx
xor rdx, rdx
div rbx
add dl, '0'
mov [rcx], dl
test rax, rax
jnz .loop
; Calculate length
mov rdx, output
add rdx, 20
sub rdx, rcx
; Print
mov rax, 1
mov rdi, 1
mov rsi, rcx
syscall
retASCII to Integer (Atoi)
Another helper to convert strings of digits into integers is also being used a lot to solve the problems. Just a clarification, the function is terminated right away when any byte that is not in range [0-9] is detected.
; ----------------------------------------------------------------------
; Component: Atoi (String to Int) conversion
; Args: rdi
; Ret: rax = atoi(rdi)
; Terminate if any byte is not in range [0-9]
atoi:
xor rax, rax
.convert:
movzx rsi, byte [rdi]
test rsi, rsi
je .atoi_done
cmp rsi, 10
je .atoi_done
cmp rsi, '0'
jl .atoi_done
cmp rsi, '9'
jg .atoi_done
sub rsi, '0'
imul rax, 10
add rax, rsi
inc rdi
jmp .convert
.atoi_done:
retChallenges

Day 1
Secret Entrance

Day 2
Gift Shop

Day 3
Lobby

Day 4
Printing Department

Day 5
Cafeteria

Day 6
Trash Compactor

Day 7
Laboratories

Day 8
Playground

Day 9
Movie Theater

Day 10
Factory

Day 11
Reactor

Day 12
Christmas Tree Farm
(yes I vibe-designed all of these thumbnails with Gemini…)
In a nutshell

The hardest challenge in my opinion is Day 10: Factory, Part 2. Along with that, parsing grids and graphs were also a nightmare using x86 Assembly.
Nonetheless, I really enjoyed solving all the challenges this year with x86 Assembly.