← Back to Writeups
CUSTOMMediumReversing

Танчики (Tanks)

XESXOR9/2/20266 min read
#reversing#custom#medium#arm_thumb_emulation#function_hooking#gba_rom_reversing#tea_cipher_identification#unicorn_engine_emulation#linux

Танчики (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
$ 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: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

ZoneConditionAction
1Outer circle"no FLAG here, sorry\n P.S. Flag is inside that box" — decoy
2Second circle"Meaw meaw" — cat easter egg
3Third circle"DUCKERZ pobeda" — victory message
4Fourth circle"Orange are bad, green is good, get the flag!" — hint
5Inner circleCalls flag decryption function — this is the "box"

The fifth zone (innermost) is checked like this:

asm
; 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):

  1. Loading ciphertext: 96 bytes of encrypted data from address 0x0800a808
  2. TEA-like cipher: uses characteristic constant 0x9e3779b9 (golden ratio — TEA/XTEA signature)
  3. Secondary key material: constant 0x27d4eb4f
  4. 64-bit multiplication: via helper function fcn.08003bc4
  5. Three lookup tables: 39 bytes each, at offsets 0x12848, 0x12870, 0x12898
  6. Multi-round cipher: XOR, shifts, multiplications through several rounds
  7. 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
0x08000648: 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
#!/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

  1. Identified file type — GBA ROM, ARM7TDMI, Thumb mode
  2. Found hint strings — flag is "inside that box", need to find a zone on the map
  3. Reversed the position check function — 5 nested circles, innermost triggers decryption
  4. Determined "box" coordinates — center (263, 230), radius sqrt(300)
  5. Identified TEA-like cipher by constant 0x9e3779b9
  6. Emulated decryption function via Unicorn Engine with correct coordinates
  7. Intercepted decrypted flag via hook on the output function ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Alternative Approaches

  1. Run in GBA emulator (mGBA, VisualBoyAdvance): play the tank game and drive to coordinates (263, 230) on the map
  2. ROM patching: replace coordinate check with unconditional jump to decryption function
  3. Full crypto algorithm reverse: reconstruct the TEA-like cipher and decrypt manually
  4. 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 ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍