397 words
2 minutes
ApoorvCTF 2026 - Forge's ....... well forge - Reverse Engineering Writeup

Category: Reverse Engineering Flag: apoorvctf{Y0u_4ctually_brOught_Y0ur_owN_Firmw4re????!!!}

Challenge Description#

Forge has made it so that only his trinket can open his workshop(forge) or so it would seem.

Analysis#

The binary immediately looked like a custom validator/loader rather than a normal crackme, so I started with basic triage to understand what kind of target it was.

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

Since it was stripped PIE, I checked symbols/functions through radare2 and found one big main plus helper routines at 0x1b60, 0x1b70, and 0x1c00, which turned out to be the fail path, SHA-256 helper, and AES-GCM helper.

r2 -A -q -c "afl" ./forge | rg "main|1b60|1b70|1c00|entry0"
0x00001a60    1     37 entry0
0x000011e0   52   2123 main
0x00001b60    1     14 fcn.00001b60
0x00001b70    5    142 fcn.00001b70
0x00001c00   13    313 fcn.00001c00

One important clue in the binary strings was a payload> prefix, and reversing showed the program decodes a runtime filename (payload>bin), reads that file, and executes it from an RWX mmap. That is where the challenge troll happens: the program behaves like only “Forge’s trinket firmware” can satisfy the checks, and if anything is wrong it just dies quietly.

strings ./forge | rg -i "payload>|apoorv|ctf|forge|trinket|workshop|correct|wrong|flag"
payload>

At that point, the practical route was to provide my own firmware blob (payload>bin) and make the parent process accept it. The core issue was that the parent performs post-child digest validation and the binary also cleanses sensitive buffers before exiting, so I patched those cleanse callsites to NOP in a local working copy (forge_noptrace2) to keep the useful state intact while solving.

tableflip

I verified that the expected bytes at those offsets were indeed NOP in the patched file.

python -c "from pathlib import Path; p=Path('forge_noptrace2'); b=bytearray(p.read_bytes());
for off in (0x16ef,0x1701,0x170e):
    print(hex(off), hex(b[off]))"
0x16ef 0x90
0x1701 0x90
0x170e 0x90

Then I built the custom payload file used by the loader:

python build_forge_payload.py
Wrote payload>bin (50 bytes)

Running the patched binary with that payload produced the flag directly on stdout.

./forge_noptrace2
APOORVCTF{Y0u_4ctually_brOught_Y0ur_owN_Firmw4re????!!!}

The challenge prefix is lowercase apoorvctf{...}, so the final normalized flag is:

apoorvctf{Y0u_4ctually_brOught_Y0ur_owN_Firmw4re????!!!}

Solution#

# build_forge_payload.py
from keystone import Ks, KS_ARCH_X86, KS_MODE_64

insns = [
    "cld",
    "lea rsi, [rsp + 0xd78]",
    "mov rdi, 0x400001bc",
    "mov ecx, 0x38",
    "rep movsb",
    "lea rsi, [rsp + 0xd38]",
    "mov rdi, 0x4000013c",
    "mov ecx, 0x38",
    "rep movsb",
    "ret",
]

ks = Ks(KS_ARCH_X86, KS_MODE_64)
encoding, _ = ks.asm("\n".join(insns))
payload = b"\xf3\x0f\x1e\xfa" + bytes(encoding)  # ENDBR64 + shellcode

with open("payload>bin", "wb") as f:
    f.write(payload)

print(f"Wrote payload>bin ({len(payload)} bytes)")
python build_forge_payload.py
./forge_noptrace2
Wrote payload>bin (50 bytes)
APOORVCTF{Y0u_4ctually_brOught_Y0ur_owN_Firmw4re????!!!}
ApoorvCTF 2026 - Forge's ....... well forge - Reverse Engineering Writeup
https://blog.rei.my.id/posts/113/apoorvctf-2026-forge-s-well-forge-reverse-engineering-writeup/
Author
Reidho Satria
Published at
2026-03-10
License
CC BY-NC-SA 4.0