Танчики (Tanks)
Танчики (Tanks)
Platform: duckerz | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-02-09 | Status: Solved Techniques: arm_thumb_emulation, function_hooking, gba_rom_reversing, tea_cipher_identification, unicorn_engine_emulation
Summary
Task: GBA ROM with tank game hiding encrypted flag in a map zone. Solution: Reverse ARM Thumb code to find trigger coordinates (263, 230), identify TEA cipher by 0x9e3779b9 constant, emulate decryption function with Unicorn Engine.
Танчики (Tanks) — DUCKERZ CTF
Description
"Я написал игру под одну старую консоль, и закрыл флаг в коробочку, попробуй его достать" (I wrote a game for an old console, and locked the flag in a box, try to get it)
One file provided:
tanks.gba(97876 bytes) — Game Boy Advance ROM image
Author: mixinspace
Analysis
Initial Examination
bash read-only • copy allowed$ file tanks.gba tanks.gba: Game Boy Advance ROM image: "game" (000000, Rev.00) $ strings tanks.gba | grep -i "flag\|duckerz\|pobeda\|meaw\|orange" #{es;P} no FLAG here, sorry\n P.S. Flag is inside that box #{es;P} DUCKERZ pobeda #{es;P} Orange are bad, green is good, get the flag! #{es;P} Meaw meaw
Key observations:
- GBA ROM — uses ARM7TDMI processor (ARM + Thumb mode)
- ROM base address:
0x08000000(standard for GBA) - Flag is not in plaintext — encrypted in ROM, decrypted under certain game conditions
- Hints in strings: flag is "inside that box" — need to find a specific zone on the map
Static Analysis (Ghidra / radare2)
Loading in Ghidra with ARM v4T Little Endian profile (corresponds to ARM7TDMI).
Position Check Function — fcn.08000f64
This function checks whether the player's tank position falls within one of 5 circular zones on the game map. Each zone triggers different behavior:
| Zone | Condition | Action |
|---|---|---|
| 1 | Outer circle | "no FLAG here, sorry\n P.S. Flag is inside that box" — decoy |
| 2 | Second circle | "Meaw meaw" — cat easter egg |
| 3 | Third circle | "DUCKERZ pobeda" — victory message |
| 4 | Fourth circle | "Orange are bad, green is good, get the flag!" — hint |
| 5 | Inner circle | Calls flag decryption function — this is the "box" |
The fifth zone (innermost) is checked like this:
asm read-only • copy allowed; Check 5th zone: (x - 263)^2 + (y - 230)^2 < 300 0x08000fde: movs r3, r0 ; r3 = x 0x08000fe0: subs r3, 8 ; r3 = x - 8 0x08000fe2: subs r3, 0xff ; r3 = x - 263 0x08000fe4: movs r2, r3 0x08000fe6: muls r2, r3, r2 ; r2 = (x-263)^2 0x08000fea: movs r2, r1 ; r2 = y 0x08000fec: subs r2, 0xe6 ; r2 = y - 230 0x08000ff0: muls r4, r2, r4 ; r4 = (y-230)^2 0x08000ff2: adds r3, r3, r4 ; r3 = (x-263)^2 + (y-230)^2 0x08000ff4: cmp r3, ip ; compare with 0x12C (300) 0x08000ff6: bge 0x8001032 ; if >= 300, skip 0x08000ff8: bl fcn.0800054c ; if < 300, call flag decryption!
Center of the "box": (263, 230) on the game map.
Flag Decryption Function — fcn.0800054c
Main cryptographic function (1106 bytes):
- Loading ciphertext: 96 bytes of encrypted data from address
0x0800a808 - TEA-like cipher: uses characteristic constant
0x9e3779b9(golden ratio — TEA/XTEA signature) - Secondary key material: constant
0x27d4eb4f - 64-bit multiplication: via helper function
fcn.08003bc4 - Three lookup tables: 39 bytes each, at offsets
0x12848,0x12870,0x12898 - Multi-round cipher: XOR, shifts, multiplications through several rounds
- Result: 39-character decrypted flag string
Important detail — function arguments (r0 = x-coordinate, r1 = y-coordinate) are used in XOR operations with the intermediate hash:
asm read-only • copy allowed0x08000648: ldr r3, [r7, 0x2c] ; arg1 (x) 0x0800064a: lsls r4, r3, 0x10 ; r4 = x << 16 0x0800064c: ldr r3, [r7, 0x28] ; arg2 (y) 0x0800064e: eors r4, r3 ; r4 = (x << 16) ^ y 0x08000650: eors r1, r4 ; hash ^= (x << 16) ^ y
This means that for correct decryption you need the correct coordinates — the center of the 5th zone.
Solution
Approach: ARM Thumb Code Emulation via Unicorn Engine
Instead of fully reversing the crypto algorithm — emulate the decryption function directly:
python read-only • copy allowed#!/usr/bin/env python3 """ Emulate the flag decryption function from the GBA ROM using Unicorn engine. The function at 0x0800054c decrypts and prints the flag. """ from unicorn import * from unicorn.arm_const import * import struct # Read the ROM with open("tanks.gba", "rb") as f: rom_data = f.read() # GBA memory map ROM_BASE = 0x08000000 ROM_SIZE = len(rom_data) RAM_BASE = 0x02000000 # EWRAM (256KB) RAM_SIZE = 0x100000 STACK_BASE = 0x03000000 # IWRAM (32KB) STACK_SIZE = 0x10000 IO_BASE = 0x04000000 # I/O registers IO_SIZE = 0x1000 def try_emulate(arg0, arg1): """Emulate fcn.0800054c with given x,y coordinates.""" mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB) # Map memory regions mu.mem_map(ROM_BASE, (ROM_SIZE + 0xFFF) & ~0xFFF) mu.mem_write(ROM_BASE, rom_data) mu.mem_map(RAM_BASE, RAM_SIZE) mu.mem_map(STACK_BASE, STACK_SIZE) mu.mem_map(IO_BASE, IO_SIZE) # Set up registers sp = STACK_BASE + STACK_SIZE - 0x800 mu.reg_write(UC_ARM_REG_SP, sp) mu.reg_write(UC_ARM_REG_R0, arg0 & 0xFFFFFFFF) mu.reg_write(UC_ARM_REG_R1, arg1 & 0xFFFFFFFF) RETURN_ADDR = 0x08000FFC mu.reg_write(UC_ARM_REG_LR, RETURN_ADDR | 1) # +1 for Thumb result = None def hook_code(uc, address, size, user_data): nonlocal result # Intercept call to print function fcn.080043a4 if address == 0x080043A4: # r0 = format string "#{es;P} %s" # r1 = pointer to decrypted flag string r1_val = uc.reg_read(UC_ARM_REG_R1) try: buf = uc.mem_read(r1_val, 0x28) result = bytes(buf).rstrip(b"\x00") except: pass uc.emu_stop() mu.hook_add(UC_HOOK_CODE, hook_code) try: # Start at function entry (Thumb mode: address | 1) mu.emu_start(0x0800054C | 1, RETURN_ADDR, timeout=5000000, count=10000000) except UcError: pass return result # Emulate with the center of the 5th circle: x=263, y=230 print("Trying x=263 (0x107), y=230 (0xe6)...") result = try_emulate(263, 230) if result: print(f"Flag: {result.decode('ascii', errors='replace')}")
Solution Logic
- Identified file type — GBA ROM, ARM7TDMI, Thumb mode
- Found hint strings — flag is "inside that box", need to find a zone on the map
- Reversed the position check function — 5 nested circles, innermost triggers decryption
- Determined "box" coordinates — center (263, 230), radius sqrt(300)
- Identified TEA-like cipher by constant
0x9e3779b9 - Emulated decryption function via Unicorn Engine with correct coordinates
- Intercepted decrypted flag via hook on the output function
Alternative Approaches
- Run in GBA emulator (mGBA, VisualBoyAdvance): play the tank game and drive to coordinates (263, 230) on the map
- ROM patching: replace coordinate check with unconditional jump to decryption function
- Full crypto algorithm reverse: reconstruct the TEA-like cipher and decrypt manually
- Memory dump: run in emulator with debugging, set breakpoint on output function, read flag from memory
Lessons
- GBA reversing requires knowledge of ARM7TDMI architecture — code is often in Thumb mode (16-bit instructions) to save ROM space
- Unicorn Engine — powerful tool for ARM code emulation without a full GBA emulator; just need to set up memory map and intercept necessary calls
- TEA cipher is easily identified by constant
0x9e3779b9— this is(sqrt(5) - 1) / 2 * 2^32 - Function arguments affect decryption — need to determine correct values from call context (coordinates from zone checks)
- Hint strings in ROM ("Flag is inside that box", "Orange are bad, green is good") — authors intentionally leave hints to guide analysis