← Back to Writeups
HTBN/AReversing

Nostalgia

XESXOR8/23/20266 min read
#reversing#htb#n/a

Nostalgia

Platform: HackTheBox | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: NA | Author: D3v0o0Nu11 | Date: 2026-05-21 | Status: Solved Techniques: accumulator_sum_check, arm_thumb_disassembly, bitmap_rendering, pcx_rle_decompression, structure_offset_analysis

Summary

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.

Recon

Port scan

nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
PortServiceVersionNotes
<PORT><SVC><VER><notes>

Enumeration highlights

  • Event: hackthebox | ID: 20260521_hackthebox_nostalgia
  • Tags: bitmap, gba, arm_thumb, rle_compression, cheat_code, button_sequence, game_boy_advance
  • Indicators: GBA ROM file (.gba), ARM7TDMI Thumb mode at 0x08000000, cheat code / button sequence input, flag stored as RLE-compressed bitmap not as plaintext, render function reads from structure+offset not from pointer field
  • Source: 20260521_hackthebox_nostalgia.md

Foothold

Vulnerability / Misconfiguration

  1. Accumulator_sum_check
  2. Arm_thumb_disassembly
  3. Bitmap_rendering
  4. Pcx_rle_decompression
  5. Structure_offset_analysis
<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

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

Key Takeaways / Lessons

  • accumulator_sum_check
  • arm_thumb_disassembly
  • bitmap_rendering
  • pcx_rle_decompression
  • structure_offset_analysis
  • Tags: bitmap, gba, arm_thumb, rle_compression, cheat_code, button_sequence, game_boy_advance

Original Writeup

<details><summary>Click to expand original content</summary>

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{REDACTED}")

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)
</details>

Auto-tracked: saved to WriteUps; run /xesor-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR