Advent of Pwn is a daily-December CTF: one challenge behind each door, Santa all the way down. I solve them and write up the ones worth writing up here — prompts in the open, full solves folded, so you can try the door before you peek.
The calendar#
| day | category | status |
|---|---|---|
| 01 | reversing | solved |
| 02 | sysadmin | pending |
Day 1 — check-list#
Every year, Santa maintains the legendary Naughty-or-Nice list, and despite the rumors, there's no magic behind it at all — it's pure, meticulous byte-level bookkeeping. Apply every tiny change exactly and confirm the final list matches perfectly. Check it once, check it twice; Santa does not tolerate even a single incorrect byte.
- arch
- x86-64
- input
- read(0, [rbp-0x400], 0x400)
- scheme
- per-byte add/sub, then a wall of cmp
The whole binary is one straight-line function: read 0x400 bytes onto the
stack, mutate them in place with a long run of add/sub, then gate the flag
behind an equally long run of cmp … / jne fail. No loops, no key, no entropy —
just bookkeeping.
|
No tricks, no anti-debug — the entire challenge is in that listing. The only thing to get right is the arithmetic.
Day 1 — full solve
For each offset i, the input byte is transformed by its add/sub lines and
then must equal that offset's cmp target. Everything is a byte, so it all
wraps at 8 bits:
The wrap is the one thing worth checking — 0xff + 2 == 1, 1 - 2 == 0xff —
and Python does it with a single & 0xff:
My first instinct was two tables: run the add/sub deltas on a zero-filled
delta array, stash the cmp targets in a goal array, then recover the
input with a wrapping subtraction.
Then I noticed I was overcomplicating it. A cmp BYTE [rbp-off], imm has the
exact same shape as a transform — [rbp-off], imm — so all three opcodes can
fold into one accumulator and I never build a second array:
sub off, v→ add+vadd off, v→ add-vcmp off, g→ add+g
Sum those per offset and the accumulator is the input (mod 256), since
goal - Σadd + Σsub is exactly the recovery formula above.
The second half is free indexing. The offsets are negative (-0x400 … -0x1),
and Python's negative indexing into a 0x400-long list maps -0x400 → 0,
-0x3ff → 1, … -0x1 → 0x3ff — straight into read order. No manual offset math.
So the solve is just: disassemble .text, regex every
add|sub|cmp [rbp-off], imm, accumulate, mask, send.
|
process() does the rest — send the reconstructed list, scrape the flag off
the tail. One pass, no brute force.
Day 2 — claus(7)#
No binary listing this time — the whole prompt is a man page:
|
Santa hid the hint sheet in plain sight: SIGTSTP is blocked, forcing a quit
"dumps coal," and the SEE ALSO line points straight at signal(7), core(5)
and elf(5).
Day 2 — solve pending
Writeup in progress. I'll fold the full solve in here once it reads clean —
the SEE ALSO list is the entire hint sheet.