← Back to all writeups
Rev

cf madness

194 points • Stripped ELF reversing • Solved during pingCTF 2026

This challenge is a stripped Linux ELF that looks awful in a decompiler, but the validator is much simpler than it first appears. The key is to notice that the program compares one generated table against a constant table, and that every other entry in that table depends only on the flag length.

Challenge file

The download is a single stripped 64-bit ELF. The import table is tiny: printf, scanf, strlen, and puts. That already suggests the whole check is self-contained.

Running strings does not leak much beyond the obvious user-facing text:

Wrong flag!
Correct flag!
Enter the flag:

On macOS I could not execute it directly, so I used static analysis in radare2 and only verified the recovered flag later inside an amd64 Linux container.

Finding the real check

The first important function is the final comparator at 0x403544. It loops over a generated table at 0x41a480 and compares it against a constant table at 0x419460:

generated = *(0x41a480 + index * 4)
expected  = *(0x419460 + index * 4)

if (generated != expected) {
    puts("Wrong flag!")
    crash()
}

puts("Correct flag!")
crash()

The crash is deliberate. The binary prints the verdict and then immediately hits a divide-by-zero, which is why it helps to run it unbuffered when verifying a candidate flag.

The helper table

Near the end of main there is a 128-entry function pointer table at 0x419060. For normal ASCII input, each character is used as an index into that table and the pointed-to helper gets called.

Each helper is almost identical. The only real difference is one immediate value, which is why the compiled main explodes into a huge wall of nearly identical blocks. That was enough to show the generated words were not a cryptographic hash or anything similarly annoying: they were just structured 32-bit values built from constants, the current position, and a tiny bit of state.

The easiest way to solve the challenge is not to perfectly model that whole state machine. It is to dump the final table that reaches the comparator and recover the effective recurrence from there.

The table is interleaved

At first I tried to decode the constant table as if every word came from those helpers. That fails immediately. The reason is that main also writes its own words into the same output buffer between helper calls.

The expected table alternates between two generators: even indices are length-only values written by main, odd indices are character-dependent values written by the helper functions.

The even entries follow a very simple pattern:

expected[2 * i] = 0xDEADBEEF + 726 * (length + 1 - i)   # 0 <= i < length

So the very first entry gives the flag length immediately:

length = ((expected[0] - 0xDEADBEEF) / 726) - 1

For this binary that works out to 51 characters.

Recovering the flag

Once you ignore the even indices, the odd indices fall out directly from the final generator state that reaches the comparator. For the i-th character:

state = 0x539 if i == 0 else i - 1

flag[i] = expected[2 * i + 1]
          ^ 0xDEADBEEF
          ^ (i * 0x1337)
          ^ (state * 0xABCD)

Running that over the first 51 odd entries decodes clean ASCII and immediately produces the whole flag. So even though the control flow is intentionally ugly, the finished comparison table is straightforward to invert.

Minimal solver

The challenge-specific solver I saved with the writeup just reads the constant table from the ELF and applies those two observations:

length = ((table[0] - 0xDEADBEEF) // 726) - 1

prev = 0x539
flag = []

for i in range(length):
    word = table[2 * i + 1]
    ch = word ^ 0xDEADBEEF ^ (i * 0x1337) ^ (prev * 0xABCD)
    flag.append(chr(ch))
    prev = ch

print("".join(flag))

Verification

I verified the decoded flag by running the binary in a Linux container with stdout unbuffered:

printf '%s\n' 'ping{n0_c0mp1l3r_w45_hur7_dur1ng_m4k1ng_7h15_ch4ll}' \
  | stdbuf -o0 ./chall

Enter the flag: Correct flag!

After printing that line the program crashes on purpose with a floating point exception, which matches the control flow in the verdict function.

Downloads

recover_flag.py

Standalone script that extracts the comparison table from the provided ELF, checks the length-only words, and decodes the final odd-slot recurrence.

Download solver

Flag

ping{n0_c0mp1l3r_w45_hur7_dur1ng_m4k1ng_7h15_ch4ll}