patch1 (task1.elf)
patch1 (task1.elf)
Platform: CUSTOM | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: decimal_to_hex_to_ascii, elf_table_extraction, offline_state_simulation, reverse_and_add_simulation
Summary
Walkthrough of the CUSTOM Reversing patch1 (task1.elf). Task: stripped x86-64 ELF that sleeps for hours, builds inputs from base strings + elapsed seconds, runs a reverse-and-add palindrome gate, then renders passing numbers via decimal->hex->ASCII. Soluti
Description
task1.elf —
https://rev-kids20.forkbomb.ru/files/rev/re7/task1.elf
A stripped 64-bit Linux ELF (x86-64, dynamically linked, 13360 bytes,
BuildID 8088f52df79142c83d3c30224edf2535066a9768). On launch it prints
this may take too long time ... :), spawns a cosmetic pthread that draws an
ASCII "go" animation, and then proceeds to sleep for what adds up to over an
hour while occasionally printing characters. The goal is to recover the flag
without actually waiting hours.
Analysis
The real logic lives in main() at 0x40107d. The animation thread is purely
cosmetic and can be ignored.
Data tables (in .data/.rodata)
| Address | Type | Contents |
|---|---|---|
0x602540 | qword ptr table, NULL-terminated, 143 entries | pointers to base number-strings in .rodata |
0x602300 | 143 × int | counts[i] — max iterations for the palindrome test |
0x6029c0 | 143 × int | sleeps[i] — seconds to sleep at step i |
Distinct base strings observed:
"109597","3000","300","808","369","16476000","1647600","164760","644","6500","650","669","6370","89256000","8925600","892560","315","1".
Main loop (per index i)
t0 = time(0)
sleep(sleeps[i])
elapsed = time(0) - t0 // == sleeps[i] (deterministic!)
sprintf(elapsed_str, "%d", elapsed)
input = strcpy(base[i]) + strcat(elapsed_str) // base[i] ++ str(sleeps[i])
ret = proc(input, counts[i]) // 0x400d65
if (ret == 0) printf(transform(input)) // 0x400ddd
The crucial observation: elapsed is exactly sleeps[i] — the sleep is the
only thing that consumes the time it measures. So the entire input string is
deterministic and can be reproduced offline. Patching out the sleep() would
change elapsed to ~0 and corrupt the inputs, so the sleeps must be
simulated (elapsed = sleeps[i]), not removed.
proc() @ 0x400d65 — reverse-and-add palindrome test (196 / Lychrel)
Classic Lychrel algorithm. For up to count iterations:
reverse()@0x400b74— stores the reversed digit string.- palindrome check @
0x400bd8— comparess[i]vss[len-1-i]. add_strings()@0x400c49— decimal digit-wise addition with carry of the number and its reverse (string arithmetic; may grow by a leading'1'on the final carry).
Returns 0 (success → print) the moment the current string is a palindrome,
otherwise keeps doing s = add(s, reverse(s)). Returns 1 if no palindrome is
found within count steps.
transform() @ 0x400ddd — decimal → hex → ASCII
num = atol(input)
sprintf(buf, "%x", num) // hex representation
for each pair of hex digits:
byte = (hi_nibble << 4) | lo_nibble
emit byte // ASCII fragment
So each passing number is printed as hex, and that hex string is read back as a sequence of ASCII byte values — yielding a flag fragment.
Solution
- Pull the three tables out of the ELF with
pyelftools: read the NULL-terminated qword table at0x602540, dereference each pointer to its.rodataC string (base[]), and read the 143-int arrays at0x602300(counts[]) and0x6029c0(sleeps[]). - Build each input as
base[i] ++ str(sleeps[i])— usingsleeps[i]directly as the measuredelapsed, so no waiting is needed. - Reimplement
proc()(reverse-and-add palindrome gate) andtransform()(decimal→hex→ASCII) in Python. - Concatenate the
transform()output of every input that passes the gate, in index order.
Only 12 of the 143 entries pass the palindrome gate:
| i | input | fragment |
|---|---|---|
| 16 | 1095977299 | ASIS |
| 37 | 8087089 | {f1 |
| 47 | 3695152 | 8b0 |
| 60 | 1647601201 | b4f1 |
| 68 | 6447926 | bc6 |
| 82 | 6502497 | c8a |
| 86 | 6697521 | f21 |
| 95 | 6370401 | a4a |
| 107 | 892560742 | 53ef |
| 112 | 3158066 | 002 |
| 127 | 6699361 | f9a |
| 136 | 12925 | 2} |
Concatenated → ASIS{REDACTED}.
Reproduction script
python read-only • copy allowedimport json d = json.load(open('extracted.json')) # base[], counts[], sleeps[] from ELF tables base, counts, sleeps = d['base'], d['counts'], d['sleeps'] def rev(s): return s[::-1] def is_pal(s): return s == s[::-1] def add_strings(a, b): la, lb = len(a), len(b) m = max(la, lb) res = [0] * m c = 0 for j in range(m): d1 = ord(a[la-1-j]) - 0x30 if j < la else 0 d2 = ord(b[lb-1-j]) - 0x30 if j < lb else 0 s = d1 + d2 + c if s > 9: c, s = 1, s - 10 else: c = 0 res[m-1-j] = s + 0x30 return ('1' + bytes(res).decode()) if c else bytes(res).decode() def proc(inp, n): s = inp for _ in range(n): if is_pal(s): return 0 s = add_strings(s, rev(s)) return 1 def transform(num): hx = '%x' % num out = b'' for j in range(0, len(hx) - 1, 2): out += bytes([int(hx[j:j+2], 16)]) return out flag = b'' for i in range(len(base)): inp = base[i] + str(sleeps[i]) if proc(inp, counts[i]) == 0: flag += transform(int(inp)) print(flag.decode()) # ASIS{REDACTED}
Key Takeaways / Lessons
- Mastering decimal_to_hex_to_ascii, elf_table_extraction, offline_state_simulation, reverse_and_add_simulation was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-06-25 • CUSTOM • patch1 (task1.elf) • Difficulty: Medium