Skip to main content

Command Palette

Search for a command to run...

The Elf's Wager: Reverse Engineering CTF Writeup

Updated
5 min read
The Elf's Wager: Reverse Engineering CTF Writeup
N

Barely Tame CTF Player. Debugging Addict. Worshipper of Wi-Fi Signals. Human? Depends on the Ping.

Challenge Description

Category: Reverse engineering
Author: qvipin

The break room buzzes with energy when you walk in. A crowd of elves has gathered around Jingle McSnark's desk, where a holographic scoreboard floats above a plate of half-eaten gingerbread.

"Ah, the human!" Jingle spins around, candy cane tucked behind one pointed ear. "Perfect timing. We were just discussing how long it would take you to fail today's challenge."

He gestures dramatically at his terminal, where green text scrolls across a black screen.

"Every week, I post a little puzzle for the SOC team. Keeps us sharp, you know? Last week, Snowdrift over there" he points at a sheepish-looking elf "took three days to crack my binary. THREE. DAYS."

Snowdrift mutters something about "unfair obfuscation" into his hot cocoa.

"But you," Jingle continues, leaning forward with a grin that's equal parts challenge and condescension, "you're supposed to be some kind of specialist, right? Santa's new secret weapon against the Krampus Syndicate?"

He slides a USB drive across the desk. It's shaped like a tiny Christmas tree.

"Prove it. My mainframe authentication module. Figure out what gets you in. No debuggers and I've made sure of that. Static analysis only, human."

The elves exchange glances. Someone starts a betting pool on a napkin.

"Oh, and one more thing," Jingle adds, spinning back to his monitors. "The Syndicate's been probing our mainframe access systems all week. If you can't figure out how authentication works at the North Pole... well, let's hope they can't either."

The room falls silent, waiting.


Step-by-Step Analysis

In keeping with the challenge constraints, the analysis is performed strictly through static inspection of the binary, avoiding any form of decompilation.

Understanding the Binary

Initial inspection of the binary involves identifying its format and the security hardening features applied to it.

❯ file day4
day4: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked,
interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b12ceece7b0740e07024986b701c0b5d81aa17f3,
for GNU/Linux 3.2.0, stripped

❯ checksec --file=day4
[*] '/Downloads/AdventOfCTF/Dec04/day4'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    FORTIFY:    Enabled
    SHSTK:      Enabled
    IBT:        Enabled

From this we can conclude that the binary is heavily hardened: full RELRO prevents GOT overwrite attacks, stack canaries block stack‑based exploitation, NX disallows code execution on the stack, PIE introduces ASLR across the entire binary, and additional mitigations like FORTIFY, SHSTK, and IBT further reduce the attack surface. Combined with the binary being stripped and the challenge’s explicit restrictions, these protections make runtime exploitation impractical. As a result, the only viable approach is strict static analysis, examining the instructions and data directly without relying on dynamic debugging or code‑execution tricks.

Running the binary with various inputs immediately reveals one key detail: the program is extremely picky about input length.

❯ ./day4
NPLD Mainframe Authentication
Enter access code:
Jingle laughs. Wrong credential length!

❯ ./day4
NPLD Mainframe Authentication
Enter access code: 1234567890
Jingle laughs. Wrong credential length!

❯ ./day4
NPLD Mainframe Authentication
Enter access code: lajkdsfhsadfuueriggvoaiyugrwetkgbiusfgsafyughp
Jingle laughs. Wrong credential length!

Everything fails with the same message, but the error wording strongly suggests that a very specific length is required.

Before diving into disassembly, we check for readable strings:

❯ strings -n 4 day4
... SNIP ...
Nice try, but Santa sees when youre peeking!
Coal for you! Tampering detected.
NPLD Mainframe Authentication
Enter access code:
Jingle laughs. Wrong credential length!
Welcome to the mainframe, Operative. Jingle owes the elves a round.
Access Denied. Jingle smirks.
... SNIP ...

These strings confirm that there is a success path inside the binary.

Finding the Length Check

We dump the disassembly to inspect the binary statically: objdump -d -M intel day4
Inside the disassembly, we encounter:

    11e3: e8 e8 fe ff ff        call   10d0 <strlen@plt>
    11e8: 48 83 f8 17           cmp    rax,0x17
    11ec: 74 0e                 je     11fc <exit@plt+0xcc>

At address 0x11e8 the program compares the result of strlen in rax against 0x17 (23 in decimal), and at 0x11ec it performs a conditional jump that proceeds only when the input length matches exactly (23 bytes) —anything else is rejected immediately.

Trying a 23‑char string confirms that this passes the length gate, but is still rejected:

❯ ./temp
NPLD Mainframe Authentication
Enter access code: 11112222333344445555666
Access Denied. Jingle smirks.

Thus, there must be a second validation stage, and this is where things get interesting.

Locating the Real Authentication Logic

A deeper look into the disassembly reveals a tight loop implementing per‑byte validation. The core logic looks like this:

1362: xor eax,eax
1368: lea rcx,[rip+0xda1]
136f: movsx edx,BYTE PTR [rdi+rax*1]  ; user[i]
1373: movzx esi,BYTE PTR [rcx+rax*1]  ; secret[i]
1377: xor edx,0x42                    ; user[i] ^ 0x42
137a: cmp edx,esi                     ; == secret[i] ?
137c: jne 138d                        ; fail
137e: inc rax
1381: cmp rax,0x17                    ; 23 bytes
1385: jne 136f                        ; loop

Not hard to translate into logic:

for (i = 0; i < 23; i++) {
    if ((input[i] ^ 0x42) != secret[i])
        return 0;
}
return 1;

Meaning the correct input is obtained by: input[i] = secret[i] ^ 0x42
All we need now is the 23‑byte secret array at address 0x2110

Extracting the Secret Bytes

❯ hexdump -Cv -s 0x2110 -n 23 day4
00002110  21 31 26 39 73 2c 36 72  1d 36 2a 71 1d 2f 76 73  |!1&9s,6r.6*q./vs|
00002120  2c 24 30 76 2f 71 3f                              |,$0v/q?|

The 23 bytes are:

21 31 26 39 73 2c 36 72 1d 36 2a 71 1d 2f 76 73 2c 24 30 76 2f 71 3f

Using a tiny Python script to XOR each byte with 0x42:

hexBytes = "21 31 26 39 73 2c 36 72 1d 36 2a 71 1d 2f 76 73 2c 24 30 76 2f 71 3f"
secret = hexBytes.split(" ")

flag = ''.join(chr(int(b, 16) ^ 0x42) for b in secret)
print(flag)

Running it:

❯ python3 sol.py
csd{1nt0_th3_m41nfr4m3}

And that is the valid access code.


Final Flag

csd{1nt0_th3_m41nfr4m3}

Advent of CTF'25

Part 4 of 15

A structured walkthrough of all the challenges I solved from CyberStudents’ Advent of CTF 2025. This series documents each day’s puzzle with precise methodology, technical breakdowns, and reproducible exploitation steps.

Up next

Kramazon CTF Writeup: Cookie Forgery & IDOR Exploit

Challenge Description Category: Web exploitationAuthor: thee2d Intelligence analysts from the North Pole Logistics Directorate (NPLD) have uncovered a covert online storefront operated by the KRAMPUS Syndicate. Its name? Kramazon. Looks familiar. Wor...

More from this blog

C

CaptureTheFlags

31 posts