BreizhCTF 2026 - Keys, Keys, Keys

Keys, Keys, Keys
- Difficulty: Easy
- Category: Forensic
- Author: Zlippy
Description
A white living room console from the mid-2000s, famous for its motion-controller, was briefly “borrowed” from its owner. A partial dump of its external storage is provided.
Your mission: identify the name of the game being played, and recover a second flag fragment hidden in the artifacts.
Flag format:
BZHCTF{GameName_artifact}
Files:
image.dd
Solve
Step 1 — Identify the image
file image.dd
# image.dd: DOS/MBR boot sector, code offset 0x58+2, OEM-ID "mkfs.fat", FAT (32 bit)A FAT32 partition image. Mount it to explore its contents:
sudo mount -o loop image.dd /mnt/case/
find /mnt/case/ -type f/mnt/case/19E4-3310/private/uii/title/BZPP/data.binA single file at a very characteristic path: /private/uii/title/BZPP/data.bin.
Step 2 — Identify the console: Nintendo Wii
The path /private/wii/title/<TITLEID>/data.bin is the standard format used by the Nintendo Wii to store save files on SD cards (since firmware 3.0 in 2007). Here, uii replaces wii — a cosmetic obfuscation by the challenge author.
The TITLEID in the path (BZPP) normally identifies the game, but it has been modified to mislead.
Step 3 — Decrypt the data.bin
The Wii data.bin is partially encrypted with AES-128-CBC:
| Section | Encryption | Content |
|---|---|---|
0x00 → 0xF0BF | AES-128-CBC | WIBN banner section (icons, title) |
0xF0C0 → end | Plaintext | Bk section (file table) |
- Key (SD Key):
AB01B9D8E1622B08AFBAD84DBFC2A55D(public key known to the homebrew community) - IV: 16 null bytes
from Crypto.Cipher import AES
SD_KEY = bytes.fromhex("AB01B9D8E1622B08AFBAD84DBFC2A55D")
with open("data.bin", "rb") as f:
data = f.read()
cipher = AES.new(SD_KEY, AES.MODE_CBC, iv=b'\x00' * 16)
decrypted = cipher.decrypt(data)
with open("data_decrypted.bin", "wb") as f:
f.write(decrypted)
print(decrypted[0x20:0x24]) # → b'WIBN'the WIBN magic should appear at offset 0x20.
WIBN confirms the Wii Banner format. The structure after decryption:
| Offset | Size | Content |
|---|---|---|
0x20 | 4 | Magic WIBN |
0x40 | 64 | Game title (UTF-16BE) |
0xC0 | 7 × 0x1200 | 7 icon frames, 48×48 px (RGB5A3) |
0xEAC0 | 0x6000 | Banner, 192×64 px (RGB5A3) |
Step 4 — Extract the banner image
The TITLEID in the path (BZPP) and the one in the metadata section (RSPP, found at offset 0xF122) are both decoys. The real game name is visible in the embedded banner image.
Nintendo Wii textures use the RGB5A3 format: tiles of 4×4 pixels, 2 bytes per pixel, where bit 15 selects between RGB555 and RGB4A3 encoding.
from PIL import Image
import struct
def rgb5a3_to_rgba(val):
if val & 0x8000: # RGB555 mode
r = ((val >> 10) & 0x1F) * 255 // 31
g = ((val >> 5) & 0x1F) * 255 // 31
b = (val & 0x1F) * 255 // 31
a = 255
else: # RGB4A3 mode
a = ((val >> 12) & 0x07) * 255 // 7
r = ((val >> 8) & 0x0F) * 255 // 15
g = ((val >> 4) & 0x0F) * 255 // 15
b = (val & 0x0F) * 255 // 15
return (r, g, b, a)
def decode_rgb5a3(raw, width, height):
img = Image.new('RGBA', (width, height))
pixels = img.load()
offset = 0
for ty in range(0, height, 4):
for tx in range(0, width, 4):
for y in range(4):
for x in range(4):
val = struct.unpack_from('>H', raw, offset)[0]
offset += 2
if tx + x < width and ty + y < height:
pixels[tx + x, ty + y] = rgb5a3_to_rgba(val)
return img
# 7 icon frames (48×48 px)
frames = []
for i in range(7):
off = 0xC0 + i * 0x1200
frame = decode_rgb5a3(decrypted[off:off + 0x1200], 48, 48)
frames.append(frame)
frames[0].save("icon_animated.gif", save_all=True,
append_images=frames[1:], loop=0, duration=150)
# Banner (192×64 px)
banner = decode_rgb5a3(decrypted[0xEAC0:0xEAC0 + 0x6000], 192, 64)
banner.save("banner.png")Icon animation:
![]()
The icon animation clearly shows the game name: BREIZHSports.
Step 5 — Extract the hidden fragment from the title field
The WIBN title field is at offset 0x40 (64 bytes), normally UTF-16BE. The challenge author embedded raw ASCII bytes inside it to hide a message.
title_raw = decrypted[0x40:0x80]
# Filter out null bytes to read the raw ASCII
non_null = bytes(b for b in title_raw if b != 0)
print(non_null)
# b'LastPArt:_W11_1z_fun}'Fragment extracted: _W11_1z_fun}
Step 6 — Build the flag
| Element | Value | Source |
|---|---|---|
| Game name | BREIZHSports | Banner image (RGB5A3) |
| Fragment | _W11_1z_fun} | WIBN title field, offset 0x40 |
Flag : BZHCTF{BREIZHSports_W11_1z_fun}