reversefreeeasy

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/
bitmap_renderingarm_thumb_disassemblyaccumulator_sum_checkpcx_rle_decompressionstructure_offset_analysis

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:

  1. Reads GBA key register at 0x04000130
  2. Iterates through 10 button masks from a lookup table at 0x0200B03C
  3. 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]:

ButtonMaskValue AddedAddress
A0x013 (0x03)0x08001760
B0x0214 (0x0E)0x08001730
Right0x1058 (0x3A)0x0800173C
Left0x20110 (0x6E)0x080016E4
Up0x4040 (0x28)0x08001772
Down0x8012 (0x0C)0x0800174E

A counter at [fp] increments with each button press.

Validation Check (Start Button)

When Start is pressed, two checks at 0x08001630–0x08001666:

  1. cmp r2, 7 at 0x08001666 — counter must be > 7 (exactly 8 presses)
  2. cmp r3, 0xf3 at 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:

  1. Writes 0x0404 to DISPCNT register (0x04000000) — switches to GBA Mode 4 (8bpp bitmap) with BG2 enabled
  2. Loads structure pointer from [0x080017B8] = 0x02008AAC (EWRAM)
  3. Calls render function at 0x08000DD0 with r0=structure, r1=0x06000000 (VRAM), r2=fp+8
  4. 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