Auto Cooker
Auto Cooker
Platform: GPN CTF | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: NA | Author: D3v0o0Nu11 | Date: 2026-06-05 | Status: Solved Techniques: buffer_reversal, constant_extraction, nibble_swap, static_analysis, transform_pipeline_inversion, xor_self_inverse
Summary
Task: intro reverse engineering ELF that runs the input flag through a 5-stage cooking-themed byte transform pipeline and compares against a hardcoded TARGET. Solution: each stage is reversible (XOR 0xAA, nibble swap, buffer reverse, padding mask), so invert the pipeline in reverse order from TARGET to recover the flag.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
GPNCTF 2026| ID:20260605_gpnctf_autocooker - Tags: xor, elf, not_stripped, rev, byte_transform, easy, intro
- Indicators: not-stripped ELF with descriptive function names, salt/fry/trim/mix/taste pipeline, hardcoded TARGET array in .data, XOR key 0xAA (GRAIN_OF_SALT), nibble swap (b<<4)|(b>>4)
- Source:
20260605_gpnctf_autocooker.md
Foothold
Vulnerability / Misconfiguration
- Buffer_reversal
- Constant_extraction
- Nibble_swap
- Static_analysis
- Transform_pipeline_inversion
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- buffer_reversal
- constant_extraction
- nibble_swap
- static_analysis
- transform_pipeline_inversion
- xor_self_inverse
- Tags: xor, elf, not_stripped, rev, byte_transform, easy, intro
Original Writeup
<details><summary>Click to expand original content</summary>Description
I always feel like cooking is such a chore... You have to chop up all your ingredients, cook them for hours and then make the plating look half-decent. But not with this new machine I got! You just have to put in your recipe (weirdly, the interface calls it a flag...) and it will get cooked for you. It's so easy, even someone with no experience in
cookingreverse engineering can do it.
English summary: We are given a 64-bit ELF binary that reads a "recipe" (the flag), runs it through a series of cooking-themed transforms, and checks the result against a hardcoded target. The goal is to find the input flag that "cooks" into the expected output.
Analysis
The handout autocooker.tar.gz contains a single ELF:
ELF 64-bit LSB executable, x86-64, dynamically linked, GNU/Linux 3.2.0, NOT stripped, 16616 bytes.checksec: Partial RELRO, No canary, NX enabled, No PIE (base0x400000), not stripped.
Because the binary is not stripped, every transform function keeps its descriptive name, which essentially documents the whole algorithm:
check_recipe_length, explain_current_food, salt, fry, trim, mix, taste, main
Control flow in main
- Reads the user's recipe (the flag) via
fgetsinto a 64-byte bufferRECIPEat0x4040e0(size0x40). check_recipe_length: ensuresRECIPE[TARGET_LENGTH] == 0andRECIPE[TARGET_LENGTH-1] != 0(length sanity check); otherwise it exits with "Your recipe is too complicated or too simple...".- Copies
RECIPEinto the 64-byteFOODbuffer at0x404120. - Runs a 5-stage pipeline (with
explain_current_foodprinting flavor text between stages), thentastecomparesFOODto a hardcodedTARGETarray and prints "Congratulations, you cooked a delicious plate of food!" on a full match.
The 5 transform stages (forward direction)
Each stage iterates i = 0..63 over the 64-byte FOOD buffer:
| Stage | Operation | Property |
|---|---|---|
salt | FOOD[i] ^= GRAIN_OF_SALT (XOR 0xAA) | self-inverse |
fry | FOOD[i] = ((FOOD[i] << 4) | (FOOD[i] >> 4)) & 0xFF (nibble swap) | self-inverse |
trim | for i in TARGET_LENGTH(61)..63: FOOD[i] &= 0x0F | only touches padding bytes 61-63 |
mix | reverse the entire 64-byte buffer (FOOD = FOOD[::-1]) | self-inverse |
taste | require FOOD[i] == TARGET[i] for all i | comparison / exit |
Constants extracted from the binary
TARGET_LENGTH@0x404060=61GRAIN_OF_SALT@0x404064=0xAATARGET(64 bytes) @0x404080:
0a0a0a0a 7ddfa94c 5f9dfc2c 9db9ec5f d9f9fcee 8fe92e5f 8dff5c5f 8d5ecc5f
3feee9fc 8f5ffe8f bc5ffd5c 3f5ffe1e 3cb95f6c fc99cc5f 3c1dceef 9e4eafde
Solution
Every stage is reversible:
saltis XOR — its own inverse.fryis a nibble swap — its own inverse.mixis a buffer reversal — its own inverse.trimonly masks the 3 trailing padding bytes (indices 61-63). Aftermixreverses the buffer, those bytes land outside the 60-character flag, sotrimis lossy but irrelevant to recovering the flag.
So we invert the whole pipeline by applying the inverse operations in reverse order, starting from TARGET:
- Undo
mix: reverse theTARGETarray - Undo
fry: nibble-swap each byte - Undo
salt: XOR each byte with0xAA
#!/usr/bin/env python3
target = [0x0a,0x0a,0x0a,0x0a,0x7d,0xdf,0xa9,0x4c,0x5f,0x9d,0xfc,0x2c,0x9d,0xb9,0xec,0x5f,
0xd9,0xf9,0xfc,0xee,0x8f,0xe9,0x2e,0x5f,0x8d,0xff,0x5c,0x5f,0x8d,0x5e,0xcc,0x5f,
0x3f,0xee,0xe9,0xfc,0x8f,0x5f,0xfe,0x8f,0xbc,0x5f,0xfd,0x5c,0x3f,0x5f,0xfe,0x1e,
0x3c,0xb9,0x5f,0x6c,0xfc,0x99,0xcc,0x5f,0x3c,0x1d,0xce,0xef,0x9e,0x4e,0xaf,0xde]
GRAIN = 0xAA
nibswap = lambda b: ((b << 4) | (b >> 4)) & 0xFF
food = target[::-1] # undo mix (reverse)
food = [nibswap(b) for b in food] # undo fry (nibble swap)
food = [b ^ GRAIN for b in food] # undo salt (XOR 0xAA)
print(bytes(food))
# b'GPNCTF{REDACTED}\n\n\n\n'
The first 60 bytes are the flag (7 for GPNCTF{ + 52 body + }). The trailing bytes are the fgets newline plus padding.
Forward verification
Feeding the recovered flag (plus a newline, padded to 64 bytes) through salt -> fry -> trim -> mix reproduces TARGET exactly, confirming correctness:
def forward(buf):
buf = [b ^ GRAIN for b in buf] # salt
buf = [nibswap(b) for b in buf] # fry
for i in range(61, 64): # trim
buf[i] &= 0x0F
return buf[::-1] # mix
flag = b"GPNCTF{REDACTED}\n"
buf = list(flag) + [0] * (64 - len(flag))
assert forward(buf) == target
print("verified")
</details>
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR