Nostalgia
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
| Port | Service | Version | Notes |
|---|---|---|---|
| <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
- Accumulator_sum_check
- Arm_thumb_disassembly
- Bitmap_rendering
- Pcx_rle_decompression
- 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
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
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:
- 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{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)
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR