Nostalgia
hackthebox
Task: GBA ROM expects a cheat code (button sequence) to display the flag as a bitmap image. Solution: reverse ARM Thumb code to find 8-button sum=243 validation, then decode PCX-style RLE-compressed image data at structure+0x80 offset to extract the flag.
$ ls tags/ techniques/
Nostalgia — HackTheBox
Description
It's late at night and your room's a mess, you stumble upon an dusty old looking box and you decide to go through it, you start unveiling hidden childhood memories and you find a mesmerising gamebody advanced flash card labeled 'Nostalgia', you pop the card in and a logo welcomes you, this strange game expects you to input a cheatcode. Can you figure it out?
Instructions: Open the rom in a GBA emulator of your choice. Select is to clear the input on the screen and start is to submit it, if the cheatcode is wrong, nothing will happen.
English summary: A Game Boy Advance ROM (Nostalgia.gba, 72812 bytes) presents a cheat code input screen. The player must enter the correct button sequence and press Start to submit. If correct, the flag is displayed on screen as a bitmap image. The goal is to reverse engineer the validation logic and extract the flag.
Analysis
Initial Reconnaissance
file Nostalgia.gba→ Game Boy Advance ROM image- No plaintext flag strings found via
strings— no "HTB", "flag", "correct", or "wrong" strings - GBA ROM uses ARM7TDMI processor (ARM + Thumb mode), ROM base address 0x08000000
- The game expects a button sequence (cheat code) as input; Select clears, Start submits
Button Input Handling
Disassembled with radare2 (r2 -a arm -b 16 -m 0x08000000). The button input loop at 0x080015F4:
- Reads GBA key register at
0x04000130 - Iterates through 10 button masks from a lookup table at
0x0200B03C - Detects newly pressed buttons via
tst r0, r2(current & ~previous)
Standard GBA button masks: A=0x01, B=0x02, Select=0x04, Start=0x08, Right=0x10, Left=0x20, Up=0x40, Down=0x80, L=0x200, R=0x100.
Accumulator Validation Logic
Each button press adds a different value to a running sum stored at [fp+4]:
| Button | Mask | Value Added | Address |
|---|---|---|---|
| A | 0x01 | 3 (0x03) | 0x08001760 |
| B | 0x02 | 14 (0x0E) | 0x08001730 |
| Right | 0x10 | 58 (0x3A) | 0x0800173C |
| Left | 0x20 | 110 (0x6E) | 0x080016E4 |
| Up | 0x40 | 40 (0x28) | 0x08001772 |
| Down | 0x80 | 12 (0x0C) | 0x0800174E |
A counter at [fp] increments with each button press.
Validation Check (Start Button)
When Start is pressed, two checks at 0x08001630–0x08001666:
cmp r2, 7at 0x08001666 — counter must be > 7 (exactly 8 presses)cmp r3, 0xf3at 0x08001636 — sum must equal 243 (0xF3)
Two valid 8-button combinations summing to 243:
- Up, Down, Left, Right, B, A, A, A → 40+12+110+58+14+3+3+3 = 243
- A, B, Left, Up, Up, Down, Down, Down → 3+14+110+40+40+12+12+12 = 243
Flag Storage — RLE-Compressed Bitmap
The flag is NOT stored as ASCII text. The success path at 0x0800163A:
- Writes
0x0404to DISPCNT register (0x04000000) — switches to GBA Mode 4 (8bpp bitmap) with BG2 enabled - Loads structure pointer from
[0x080017B8]=0x02008AAC(EWRAM) - Calls render function at
0x08000DD0with r0=structure, r1=0x06000000 (VRAM), r2=fp+8 - Calls palette function at
0x08000354
Critical insight: The render function reads image data from structure+0x80, NOT from the pointer at structure+0. The pointer field at +0 was 0x0801050A (all zeros in ROM — a red herring). The actual image data is embedded inline within the structure at offset 0x80, at ROM offset 0x8B2C (= 0x8AAC + 0x80).
The image uses PCX-style RLE compression:
- If
byte & 0xC0 == 0xC0: run-length marker, count =byte & 0x3F, value = next byte - Otherwise: literal pixel byte
Decoded to 240×160 pixels with two palette indices:
- Index 46: text pixels and border
- Index 225: background within text area
Solution
Step 1: Reverse the Validation Logic
Using radare2, identified the accumulator-based cheat code check: 8 button presses must sum to 243.
Step 2: Locate the Flag Image Data
Traced the success path to find the render function reads from structure+0x80 at ROM offset 0x8B2C.
Step 3: Decode RLE and Render the Bitmap
#!/usr/bin/env python3 """Decode the RLE-compressed flag image from Nostalgia.gba""" from PIL import Image data = open("Nostalgia.gba", "rb").read() # Flag image structure at ROM offset 0x8AAC # Image data starts at structure + 0x80 = offset 0x8B2C offset = 0x8B2C chunk = data[offset:offset + 5000] # PCX-style RLE decode decoded = [] i = 0 while i < len(chunk) and len(decoded) < 240 * 160: b = chunk[i] if (b & 0xC0) == 0xC0: # Run-length marker count = b & 0x3F value = chunk[i + 1] decoded.extend([value] * count) i += 2 else: # Literal byte decoded.append(b) i += 1 # Render: palette index 225 = white (background), 46 = black (text) img = Image.new("L", (240, 160)) for y in range(160): for x in range(240): idx = y * 240 + x img.putpixel((x, y), 255 if decoded[idx] == 225 else 0) img.save("flag_screen.png") print("Flag: HTB{GBA_RuLeZ_DudE}")
The rendered image clearly shows the flag text in a pixel font on a dark background.
What Failed
- Full Unicorn Engine emulation — GBA startup too complex without BIOS; the ROM requires proper hardware initialization that Unicorn can't provide out of the box
- XOR brute-force search — flag is not XOR-encrypted; it's stored as a bitmap image
- Looking for flag as ASCII text in ROM — it's rendered as pixel graphics, not stored as a string
- Assuming the structure's pointer field (+0) pointed to the image data — the actual data was at structure+0x80 (inline within the structure)
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar
Similar writeups
- [reverse][Pro]Танчики (Tanks)— duckerz
- [reverse][free]Indirect Memory Access— b01lersc
- [reverse][Pro]Nava - ASIS CTF Reverse Challenge— ASIS CTF
- [reverse][Pro]Pac-Man— volgactf
- [hardware][free]Nishcheta— alfactf