Forge
Forge [RE] - Detailed Writeup#
Challenge#
- Name:
Forge - Category: Reverse Engineering
- Given file:
forge - Prompt hint: only a trinket/firmware can open the workshop
Expected flag format on platform: apoorvctf{...}.
1) Fast Triage#
I started with standard binary triage.
file forge
strings -n 6 forge
readelf -h forge
readelf -S forgebashKey observations:
forgeis a stripped 64-bit PIE ELF.- Imports include OpenSSL primitives:
EVP_sha256,EVP_aes_256_gcm,RAND_bytes, etc. ptrace,fork,waitpid,prctlare present (anti-debug + sandbox-like behavior).- A suspicious encoded string appears in
.rodatathat decodes topayload.bin.
Running once:
./forge
echo $?bashIt exits silently with code 1.
2) Why It Exits Immediately (Anti-Debug)#
I traced execution:
strace -o /tmp/forge.strace ./forgebashImportant line in trace:
ptrace(PTRACE_TRACEME) = -1 EPERMtextSo the binary intentionally fails when it detects tracing/debug context.
In assembly, main starts with:
call ptrace@plt- compare result with
-1 - jump to an exit routine on failure
This explains the immediate exit during tracing.
3) Static RE of Main Logic#
With objdump -d -Mintel forge, the core flow is:
- Anti-debug check (
ptrace). mmapexecutable + data regions.- Build and solve a 56x56 system over a custom finite-field multiply table.
- Derive a 56-byte value from that solve.
- Hash/derive keys with SHA-256.
- Perform AES-256-GCM operations over 56-byte blocks.
- Decode a filename by XORing bytes with
0x5a.
That filename is recovered from .rodata bytes at 0x2020:
2a 3b 23 36 35 3b 3e 64 38 33 34textXOR with 0x5a gives:
payload.bintextThe program tries to open/read this file and then uses seccomp-like prctl setup and child execution flow. That path is noisy and not needed to get the flag.
4) The Important Part: Embedded Math System#
Main solver uses three fixed .rodata objects:
bvector at virtual0x2040, length56Amatrix at virtual0x2080, size56*56- GF(256)-style multiply table at virtual
0x2cc0, size256*256
In file offsets (because .rodata starts at file 0x2000):
bat0x2040(+0x40in.rodata)Aat0x2080(+0x80)mulat0x2cc0(+0xcc0)
The routine performs Gaussian elimination in that algebra:
- pivot search for non-zero byte
- row swaps
- multiplicative inverse lookup using
mul[(pivot<<8)+x] == 1 - row normalization and elimination with table multiply and XOR
So this is a pure static solve: no need to emulate runtime anti-debug path.
5) Deterministic Solver Script#
I wrote a reproducible extractor:
- Script:
forge-solve.py - It reads
forgedirectly and reconstructs the same augmented matrix logic.
Run:
python3 forge-solve.pybashOutput:
APOORVCTF{Y0u_4ctually_brOught_Y0ur_owN_Firmw4re????!!!}text6) Final Flag#
Raw recovered string:
APOORVCTF{Y0u_4ctually_brOught_Y0ur_owN_Firmw4re????!!!}
If platform enforces lowercase prefix format, submit:
apoorvctf{Y0u_4ctually_brOught_Y0ur_owN_Firmw4re????!!!}
Why This Works (Short Reasoning)#
- The binary contains all cryptographic/math material in
.rodata. - Anti-debug and payload execution are defensive layers, not required for extraction.
- The core secret is produced by a deterministic linear solve over the embedded GF table.
- Re-implementing that solver statically reproduces the exact 56-byte flag string.