zwesty
Write-ups / Day 2 / Binary
Day 2 · Fri, Oct 2 · Binary

Mangled

Prey slaughtered

Reading ELF headers, sections, and symbol tables · Itanium C++ name mangling, vtables, and RTTI · finding an embedded config record · MinGW PE files and their COFF symbol table · finding which field a checker rejected

STTMClaude Homebase Both

Flags are shortened to their first 8 and last 4 characters because they're unique per team.

Challenge ID
binary-reverse-engineering-day-02-cpp-and-pe-orientation (+ /time_trial, /mastery)
Solved
2026-10-07 to 2026-10-09, all three modes (Mastery through untimed practice). Single Solve start 2026-10-07 14:23:00 → Mastery CORRECT 2026-10-09 17:31:36 UTC (2 d 3 h 9 min)
Player
Zwesty (solo), with Claude Code (Opus 5.5: C and C++ symbol fields, Time Trial, Mastery) and the Homebase agent (GPT-6 Luna: the six config fields and cpp_format)

The challenge

C++ on Windows. The symbols are mangled, the classes hide behind vtables, and RTTI is your Rosetta Stone. Demangle it all with c++filt, reconstruct the classes, and don't forget the PE imports.

17 questions over one evidence.zip (password infected) holding two files: day01-c.elf (the Day 1 C program) and day02-cpp.elf (the C++ program). Mangled is the only binary challenge with its own short answer ids. Days 3–9 use dNN_analysis_<field>; here the C file's fields are c_*, the C++ file's are cpp_*, and the symbol and config fields have bare names. Every question has the same shape: "What is the exact <field> value for field <field> in the delivered evidence set?" Normalisation is exact everywhere.

answer_id From Format
c_sha256, cpp_sha256 Each file's SHA-256 64 hex characters
c_marker The C ELF's .huntress string Exact text
c_has_symtab The C ELF true / false
c_recover_code The C ELF Base-10 integer
c_machine The C ELF header Exact text
cpp_format The C++ file Exact text
cpp_xor_key, cpp_config_crc32 The C++ file's config record Lowercase hex with 0x
campaign, domain, url, mutex The C++ file's config record Exact text
has_cpp_vtable, has_cpp_typeinfo The C++ file's symbols true / false
mangled_score_symbol, vtable_symbol The C++ file's symbols Exact text

Exact text adds: "use the literal none only when the requested field is absent." Mastery is two sets of the same 17 (34 answers), with ids prefixed set1. / set2..

Reading the prompt: what it said we'd need

Clue What it pointed to What I did about it
"C++ on Windows" A PE file built by a Windows C++ toolchain The Single Solve and Time Trial delivered a Linux ELF anyway. Kept in mind; Mastery set 1 then delivered the C++ program as a MinGW PE
"The symbols are mangled" Itanium C++ ABI names (_Z…) Read the symbol table and kept the mangled spelling: the field is literally mangled_score_symbol
"the classes hide behind vtables" A _ZTV… vtable symbol has_cpp_vtable and vtable_symbol
"RTTI is your Rosetta Stone" _ZTI… typeinfo symbols has_cpp_typeinfo
"Demangle it all with c++filt" Turn _Z names into readable C++ Read the names as C++ (one class, DiscoveryEngine, with a const score(const char*) method). The answers keep the mangled form
"reconstruct the classes" Map methods to classes Only one non-library class, so the "score" method and the vtable were unambiguous
"don't forget the PE imports" Windows import table No question asked for imports. The PE mattered for a different reason: its symbol fields read differently (see Mastery)
c_* ids The bundled Day 1 binary Applied the rules confirmed in ELF Aware (marker, .symtab, a function named recover_code, x86-64)
cpp_xor_key, cpp_config_crc32, campaign, domain, url, mutex An encoded configuration inside the C++ file The binary-lane config record (HCFG, see below)
cpp_format, "exact artifact-derived value" A file-format word with no stated vocabulary Expected a wording test. It became the longest-open field in the lane

Safety

Static analysis only. No binary was ever executed, ELF or PE. The evidence zip was opened inside our Kali VM, and the files were read with readelf and, later, our own pure-Python readers, which only parse headers and tables. The artifacts stayed in the VM. The config values look like attacker infrastructure; any domain or URL in these notes is defanged (*.example.invalid).

What was in the files

day01-c.elf is the Day 1 C program (see the ELF Aware write-up) with a new marker:

  • SHA-256 a0d3aedd…37b9b, x86-64 ELF64, a .symtab present.
  • .huntress holds BR01-CATALOG-000.
  • A function named recover_code returns 8100.

day02-cpp.elf (SHA-256 e9ca3054…fd0d7) is a small, unstripped C++ program for x86-64 Linux:

  • One class, DiscoveryEngine, with a vtable (_ZTV15DiscoveryEngine), RTTI (_ZTI… typeinfo symbols), and a const method _ZNK15DiscoveryEngine5scoreEPKc. c++filt reads that as DiscoveryEngine::score(char const*) const: N…E is the nested name, K is const, PKc is a pointer to const char.
  • A config record, the same layout the later binary days use: the magic HCFG, a version byte, a one-byte XOR key, a length, a CRC32 of the plaintext, then JSON XOR'd with that key. The JSON holds the campaign, domain, URL, and mutex. The record only counts if its CRC32 matches.

The Time Trial delivered the same two files (same SHA-256s; evidence.zip 5,483 bytes). Mastery did not: set 1 held day01-c.elf plus day02-cpp.exe, the same C++ program built as a MinGW PE; set 2 held day01-c.elf plus day02-cpp.elf.

Single Solve: CORRECT (10 by Claude, 6 by the Homebase agent, 1 after two days)

Claude, 2026-10-07, by hand with readelf. Two batches of five, both accepted within 43 s of the start:

  • C ELF: c_sha256, c_marker BR01-CATALOG-000, c_has_symtab true, c_recover_code 8100, c_machine x86-64.
  • C++ symbols: cpp_sha256, has_cpp_vtable true, has_cpp_typeinfo true, mangled_score_symbol _ZNK15DiscoveryEngine5scoreEPKc, vtable_symbol _ZTV15DiscoveryEngine.

The six config fields were not Claude's. Claude's safety filter stopped one step of the analysis, and we didn't work around it. The Homebase agent's config analyser answered cpp_xor_key, cpp_config_crc32, campaign, domain, url, and mutex at 17:11 UTC the same day, in one batch of six: all accepted, 16 of 17. The analyser finds the HCFG record, checks its CRC32, and reads the fields from the decoded JSON. The same analyser then filled the Day 2 config fields in Days 3–8, where this file is bundled again.

cpp_format: open for two days

When Who Tried Result
2026-10-07 Claude ELF64, ELF incorrect
2026-10-07 17:18 Homebase agent ELF_CPP incorrect; set aside
2026-10-09 12:50–15:24 Homebase agent 57 single guesses: ELF_EXEC, ELF64_EXEC, ELF_CXX, ELF64_LSB, ELF64_CPP, elf64, HCFG, .elf, C++, MIME types, the full file -b line, and more all incorrect
2026-10-09 15:29:15 Homebase agent elf (lowercase, as objdump -f prints the family) ✅ CORRECT

The answer was the plainest word, in lowercase. Uppercase ELF had been the second guess. The same elf then worked for every ELF format field in Days 2–9, and jar for every JAR. Single Solve flag: 3ba0005c…56d0.

The solver came later. The Single Solve was answered by hand, so on 2026-10-09 Claude added Mangled's ids to our core binary solver, using our ELF and COFF symbol reader. Re-run on the Single Solve material it gave 17/17, every value equal to the accepted one.

Time Trial: CORRECT in 2.1 s (first attempt)

Run by Claude from our Kali VM with our solvers. An earlier run from the Homebase at 16:57 had skipped Mangled because nothing answered its symbol fields yet (coverage 7/17).

started_at 2026-10-09T17:24:42.981Z  deadline 2026-10-09T17:24:47.981Z  (17 answer_ids)
  artifact evidence.zip 5483 bytes (sha256 checked)
  c_marker BR01-CATALOG-000 · c_recover_code 8100 · c_machine x86-64 · cpp_format elf · cpp_xor_key 0x31
  mangled_score_symbol _ZNK15DiscoveryEngine5scoreEPKc · vtable_symbol _ZTV15DiscoveryEngine
  start 666 ms · artifact 370 ms · solve 29 ms · submit 1071 ms · TOTAL 2135 ms
result CORRECT  flag 489d9680…f7b8

A 5-second window, met with about 2.9 s to spare. The solve itself took 29 ms; the network was most of it.

Mastery: CORRECT through practice (fresh run incorrect)

The fresh run failed with no detail. cpp_format for a PE (pe) had been confirmed two minutes earlier (17:27) on Nimble's Mastery, so the solver had an answer for all 34 fields.

started_at 2026-10-09T17:29:13.635Z  (34 answer_ids, 2 sets)
  set 1: day01-c.elf + day02-cpp.exe (MinGW PE)   set 2: day01-c.elf + day02-cpp.elf
  TOTAL 1194 ms
result incorrect   (no per-field detail)
retry 17:30:28  HTTP 409 rotation_exhausted  unique_attempts_remaining 0

A fresh Mastery only says incorrect, so it can't show which field was wrong. With no fresh variant left, op=practice reopened the last delivery untimed and outside the Hall of Fame. Practice accepts partial submits and answers each batch with accepted or incorrect, without ending the attempt. Claude wrote a small script to submit the answers in planned batches and stop at the first rejection.

Practice, from 17:30:29 UTC (the first three replies share one second; shown in the order the remaining counts imply):

Time Sent Result What it taught
17:30:29 29 answers (everything certain) accepted, 5 left The C ELF fields, both SHA-256s, config, and all of set 2 were right
17:30:29 set1.cpp_format = pe accepted, 4 left The PE format word holds here too
17:30:30 set1.has_cpp_typeinfo, set1.has_cpp_vtable = true (from COFF symbols) accepted, 2 left The two left were the PE's symbol names
17:30:30 set1.mangled_score_symbol = _ZNK15DiscoveryEngine5scoreEPKc incorrect The plain mangled name, right for the ELF, is wrong for the PE
17:30:54 set1.vtable_symbol = _ZTV15DiscoveryEngine incorrect Same for the vtable
17:31:00 An already-accepted answer, resent accepted, 2 left The attempt was still live after two rejections
17:31:21 set1.vtable_symbol = none incorrect The symbol counts as present
17:31:29 set1.vtable_symbol = .rdata$_ZTV15DiscoveryEngine ✅ accepted, 1 left The checker takes the first COFF table entry holding the name
17:31:36 set1.mangled_score_symbol = .text$_ZNK15DiscoveryEngine5scoreEPKc ✅ CORRECT The same rule for the method

Why those names. A PE keeps an optional COFF symbol table, and MinGW leaves it in unless stripped. The C++ vtable and the method each sit in their own grouped section, named after the symbol: .rdata$_ZTV15DiscoveryEngine and .text$_ZNK15DiscoveryEngine5scoreEPKc. Each section has a section symbol, and it comes before the plain _ZTV… / _ZNK… symbol in the table. The checker evidently takes the first entry that contains the name, so on a PE it lands on the section symbol. On the ELF, the first match is the plain symbol. The candidates were tried one at a time (none, then the section-symbol form, with the demangled names held in reserve).

Mastery flag: 06456b9a…0483. The solver now applies the rule: on a PE, these two fields take the first COFF entry holding the name.

Time to solve

The Single Solve was completed on 2026-10-09 at 15:29:15 UTC, 2 d 1 h 6 min after it started: ten fields were accepted in the first 43 s, and cpp_format took two days. The Time Trial went through first time in 2.1 s (ranked); the fresh Mastery run was incorrect, and the Mastery was completed through untimed practice at 17:31:36 UTC, so it is unranked.

Takeaways

  • The prompt describes the series, not this file. "C++ on Windows" arrived as a Linux ELF in two modes and as a PE only in Mastery set 1. Check the magic bytes, not the story.
  • The same question can need a different answer per container. A symbol name on the ELF was the plain mangled name; on the PE it was the first COFF table entry, a section symbol. A rule confirmed on one format is only a candidate on another.
  • Practice mode is a debugger for Mastery. A fresh run says only incorrect; practice takes partial batches and reports each one, so the wrong field can be found in minutes.
  • Try the plainest spelling in every case early. About 60 rejections went into cpp_format, and the answer was elf in lowercase. Uppercase ELF had been the second guess. One lowercase try would have closed it on day one.
  • A guessing loop needs a stop rule. The Homebase agent kept going through 57 guesses, and it happened to land; it doesn't stop well once a task turns into guessing.
  • Split the work by who can do it. Claude's safety filter stopped one step of the analysis on the config fields. The Homebase agent's analyser answered them, and it was reused for the same file in six later challenges.