← Back to Writeups
CUSTOMMediumReversing

patch1 (task1.elf)

XESXOR9/10/20264 min read
#reversing#custom#medium#decimal_to_hex_to_ascii#elf_table_extraction#offline_state_simulation#reverse_and_add_simulation#linux

patch1 (task1.elf)

Platform: sbpctf | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-06-25 | Status: Solved Techniques: decimal_to_hex_to_ascii, elf_table_extraction, offline_state_simulation, reverse_and_add_simulation

Summary

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. Solution: extract the three ELF data tables with pyelftools and simulate the whole computation offline (elapsed == sleep value, so no waiting), concatenating the 12 passing fragments into the flag.

patch1 (task1.elf) — sbpctf

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)

AddressTypeContents
0x602540qword ptr table, NULL-terminated, 143 entriespointers to base number-strings in .rodata
0x602300143 × intcounts[i] — max iterations for the palindrome test
0x6029c0143 × intsleeps[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 — compares s[i] vs s[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

  1. Pull the three tables out of the ELF with pyelftools: read the NULL-terminated qword table at 0x602540, dereference each pointer to its .rodata C string (base[]), and read the 143-int arrays at 0x602300 (counts[]) and 0x6029c0 (sleeps[]).
  2. Build each input as base[i] ++ str(sleeps[i]) — using sleeps[i] directly as the measured elapsed, so no waiting is needed.
  3. Reimplement proc() (reverse-and-add palindrome gate) and transform() (decimal→hex→ASCII) in Python.
  4. Concatenate the transform() output of every input that passes the gate, in index order. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Only 12 of the 143 entries pass the palindrome gate:

iinputfragment
161095977299ASIS
378087089{f1
4736951528b0
601647601201b4f1
686447926bc6
826502497c8a
866697521f21
956370401a4a
10789256074253ef
1123158066002
1276699361f9a
136129252}

Concatenated → ASIS{REDACTED}.

Reproduction script

python
import 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}

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍