Старый боец (Old Fighter)
Старый боец (Old Fighter)
Platform: hackerlab | Category: Forensics | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2025-05-03 | Status: Solved Techniques: wad_directory_parsing, duplicate_lump_name_detection, lump_size_anomaly_detection, doom_patch_format_validation, png_magic_byte_carving, image_upscaling
Summary
Task: a DOOM PWAD file (my.wad, 238 lumps) hides a flag. Solution: parse the WAD directory, spot two duplicate PANBOOK3 lumps with an anomalous size, validate they are not real DOOM patches, carve them by PNG magic bytes, and read the flag rendered as text on a bookshelf image.
Старый боец (Old Fighter) — hackerlab
Description
Куда теперь занесло этого Палача? Хотя в любом случае он справится
English: "Where has the Executioner (Палач) ended up now? He'll manage anyway."
Download: a single old_fighter.zip containing one file, my.wad.
Semantic clues
- "Старый боец" = Old Fighter.
- "Палач" (the Executioner) is the classic Russian nickname for DOOM's protagonist (Doomguy).
Both hints point directly at DOOM. The artifact is a DOOM data file (WAD), so the intended path is WAD-level forensics, not classic image/audio stego.
Analysis
Extracting the zip yields my.wad:
$ file my.wad
my.wad: doom patch PWAD data containing 238 lumps
A PWAD (Patch WAD) is a container with a fixed structure:
- Header (12 bytes): 4-byte magic
PWAD,int32 numlumps,int32 dirofs. - Directory:
numlumpsentries of 16 bytes each —int32 filepos,int32 size,8-byte name(NUL-padded). - Each lump is raw data referenced by
filepos/size.
Parsing the directory listed 238 lumps: a complete MAP01 (THINGS, LINEDEFS, SIDEDEFS,
VERTEXES, SEGS, SSECTORS, NODES, SECTORS, REJECT, BLOCKMAP) plus many texture / flat /
patch graphic lumps.
Red herrings (what did NOT work)
- Rendering MAP01 geometry (VERTEXES + LINEDEFS) with matplotlib produced a normal, complete, playable level — no flag drawn into the walls.
- THINGS positions — normal monster/item placement, not a message.
strings/ grep forCODEBY,flag,hackerlab— no plaintext anywhere.
Key insight — anomalous duplicate lumps
Scanning the directory for anomalies revealed two duplicate lumps both named
PANBOOK3 at directory indices 222 and 223, each with size 12747 bytes.
The legitimate texture PANBOOK3 lives at index 218 with size 17544 bytes.
Duplicate lump names with a different size = strong tampering signal.
Attempting to parse idx 222/223 as a DOOM patch (picture) lump gave absurd header
values — width 20617, height 18254 — proving these are not valid DOOM patches.
(A real patch header is int16 width, int16 height, int16 leftoffset, int16 topoffset,
followed by width int32 column offsets and column "post" data.)
Dumping their raw bytes showed the real format:
$ xxd dup_222.bin | head -1
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR
Magic 89 50 4E 47 0D 0A 1A 0A → these lumps are PNG files (128×128, 8-bit RGBA),
smuggled into the WAD disguised as texture lumps at file offsets 3419459 and
3432206 (size 12747 each).
Solution
- Extract
my.wadfrom the zip. - Parse the PWAD header and 16-byte directory entries.
- Flag the anomaly: two
PANBOOK3lumps (idx 222, 223) with size 12747, distinct from the genuine texture at idx 218 (size 17544). - Confirm they are not DOOM patches (impossible width/height in the patch header).
- Carve them by PNG magic bytes and save as
.png. - Open the PNG — a bookshelf graphic with overlaid text; upscale with NEAREST to read it.
The flag is rendered across three lines:
CODEBY{D00m/REDACTED/}.
python read-only • copy allowed#!/usr/bin/env python3 import struct with open("my.wad", "rb") as f: data = f.read() magic, numlumps, dirofs = struct.unpack_from("<4sii", data, 0) assert magic == b"PWAD", magic print(f"{magic.decode()} lumps={numlumps} dirofs={dirofs}") # Parse directory lumps = [] for i in range(numlumps): off = dirofs + i * 16 filepos, size = struct.unpack_from("<ii", data, off) name = data[off + 8:off + 16].split(b"\x00")[0].decode("latin1") lumps.append((i, name, filepos, size)) # Detect duplicate names with conflicting sizes from collections import defaultdict by_name = defaultdict(list) for i, name, pos, size in lumps: by_name[name].append((i, pos, size)) for name, entries in by_name.items(): if len(entries) > 1: sizes = {s for _, _, s in entries} if len(sizes) > 1: print(f"[!] duplicate '{name}' with differing sizes: {entries}") # Carve any lump that is actually a PNG PNG_MAGIC = b"\x89PNG\r\n\x1a\n" for i, name, pos, size in lumps: blob = data[pos:pos + size] if blob.startswith(PNG_MAGIC): out = f"carved_{i}_{name}.png" with open(out, "wb") as o: o.write(blob) print(f"[+] PNG lump idx={i} name={name} offset={pos} size={size} -> {out}")
Carved PNGs carved_222_PANBOOK3.png / carved_223_PANBOOK3.png contain the flag text
on a bookshelf image.