zwesty
Write-ups / Day 14 / Binary
Day 14 · Wed, Oct 14 · Binary

Selective Memory

Prey slaughtered

ELF triage of an Objective-C (GNUstep) build, HCFG config extraction from its own .hcfg section, compounding field sets

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-14-objective-c-persistence-descriptor (+ /time_trial, /mastery)
Solved
2026-10-14, all three modes; 24 s from first start to last CORRECT (09:00:42–09:01:06 ET)
Player
Zwesty (solo), with Claude Code (Opus 5.5): solved by our automated morning solver (a scheduled script on our Kali VM) with our shared binary solver

The challenge

Objective-C never forgets: every class, every selector, every string is registered and waiting. Read the persistence descriptor and learn what this binary remembers.

140 questions in the Single Solve: 10 per day for Days 1 to 14. Earlier days' questions are the same as yesterday's. The 10 new d14_* fields:

answer_id Asks for Format
d14_artifact_sha256_1 SHA-256 of the Day 14 file exact text
d14_analysis_architecture processor architecture from the header pattern (name, tool label, or machine code)
d14_analysis_format container format exact text
d14_analysis_machine the header's machine field pattern (label or decimal/hex code)
d14_analysis_config_campaign config campaign exact text
d14_analysis_config_domain config domain exact text
d14_analysis_config_mutex config mutex exact text
d14_analysis_config_url config url exact text
d14_analysis_config_crc32_hex the config record's CRC-32 0x + lowercase hex
d14_analysis_has_hcfg_section is there a section named .hcfg? enum true / false

Answer kinds across all 140: 72 exact text, 49 pattern, 9 integer, 5 JSON, 5 enum.

Reading the prompt: what it said we'd need

Clue What it pointed to What I did about it
"Objective-C" a binary built from Objective-C; on Linux that means the GNUstep runtime (libobjc, __objc_* sections) Nothing new. Our binary solver reads ELF structure and the embedded config, not the source language
"every class, every selector, every string is registered" Objective-C keeps class names, selector names, and constant strings as readable metadata Checked statically for this write-up (class TriggerDescriptor, selectors initWithTrigger:, triggerName, eventCountForModel:). No question asked about them, so the solver didn't need them
"Read the persistence descriptor" the embedded HCFG config (its service, windows_path, and linux_path keys read like a persistence plan) The solver's HCFG reader found the record; campaign, domain, mutex, url, and CRC-32 came from it
"what this binary remembers" where the config is stored The record sits in its own .hcfg section, which the Day 10 has_hcfg_section rule already answers

Safety

Static only. The evidence zips (≈32 MB each, AES-encrypted with the usual lab password) were read in memory on our Kali VM with python3 -I. No binary was executed. For this write-up I re-read the saved zips the same way (section table, symbol names, .rodata strings, the HCFG record) and made no Game Master calls.

What was in the files

One evidence.zip with 15 members, one program per day (d03 also has its runtimeconfig.json). The Day 1 to 13 files were byte-identical to Day 13's: all 130 earlier answers today equal yesterday's.

The new file, d14--variant-000.elf (17,304 bytes):

  • 64-bit x86-64 ELF, linked with gold 1.16. It needs libobjc.so.4.6 and libgnustep-base.so.1.27: a GNUstep Objective-C build.
  • Sections include .hcfg and the GNUstep v2 runtime sections __objc_selectors, __objc_classes, __objc_class_refs, __objc_constant_string, and others.
  • One class, TriggerDescriptor (subclass of NSObject), with an ivar _triggerName, a read-only triggerName property, and the methods initWithTrigger: and eventCountForModel:. One constant string: UserLogin.
  • A .rodata trace template that describes itself as harmless. Defanged excerpt: "family":"non_mutating_trigger_plan","effects":"synthetic_trigger_plan_only",...,"network":"none".
  • The HCFG record: raw (not Base64) at file offset 4000, which is the start of .hcfg. Version 1, XOR key 0xa1, 367 bytes, CRC-32 0x79f491da. Keys: campaign BR14-000, domain, ipv4 (a documentation-range address), url, mutex, windows_path, linux_path, service, host_model. All hosts are under .invalid.

The timed variants used the same template with different values. Every Day 14 file in every zip is 17,304 bytes with the record at offset 4000; only the variant number (and so the config values and CRC) changes:

Run Day 14 file Other files
Single Solve d14--variant-000.elf (BR14-000) all variant-000
Time Trial d14--variant-002.elf (BR14-002) mixed variants, all ELF/JAR except d03's .NET DLL
Mastery set 1 d14--variant-002.elf the same variant numbers as the Time Trial zip
Mastery set 2 d14--variant-007.elf the PE set: d02, d04, and d10 are .exe builds
Mastery set 3 d14--variant-004.elf all ELF/JAR plus d03's DLL

Single Solve (solver, 3 batches)

The solver's core already covered all 10 new fields. No new plug-in was written. The solver picked up the new challenge by itself and used its core; it had last been changed on 2026-10-11.

How the core answered each new field:

  • SHA-256: hash of the member's bytes.
  • format / architecture / machine: the ELF magic gives elf; e_machine 62 gives x86-64 and 62.
  • config fields and CRC-32: the HCFG reader finds the one CRC-checked HCFG record and reads its keys.
  • has_hcfg_section: the Day 10 (Zig Zag) rule: is any ELF or PE section named .hcfg? Here, true.

The solver sent the core's answers first, then the two plug-ins'. Every batch was accepted:

Batch Sent Result Open after
core 125 accepted 15
Day 1 ELF basics plug-in 9 accepted 6
Day 2 config plug-in (HCFG fields + XOR key) 6 CORRECT

The Day 1 ELF batch carried a warning from the solver's answer history: ELF64 had once been rejected on a different question. It is still the right form for d01_analysis_elf_class and was accepted again.

Question families in the 140 (all accepted on the first send):

Family Count Examples
Artifact SHA-256 15 d14_artifact_sha256_1 = d93c0e4d…b617; d03 has a second file
Config campaign / domain / mutex / url / CRC-32 65 (5 × Days 2–14) BR14-000, node-000.day14.example.invalid, Global\HuntressBR14_000, 0x79f491da
Format 13 elf (most days), jar (d06, d07), PE_CLI (d03)
Architecture / machine 18 x86-64, 62; d03's .NET DLL is i386
HCFG version, offset, XOR key, .hcfg section 15 d14_analysis_has_hcfg_section = true
Day 1 ELF basics (besides machine) 8 class, type, endianness, entry point, sections, symbols, disassembly, symtab
Other one-offs 6 d08 elf_class (64), C++ typeinfo/vtable (d02), ZIP compression methods (d06, d07), .NET metadata version (d03)

CORRECT at 13:00:47 UTC, flag 9da1df0f…af4a. The solver records no lap timings for the untimed Single Solve.

Time Trial

started_at   2026-10-14T13:00:54.036Z   deadline 13:01:24.036Z (30 s)
artifact     evidence.zip, 32.2 MB, Day 14 = d14--variant-002.elf
answers      140, one submit
             d14_analysis_config_mutex: Global\HuntressBR14_002
             d14_analysis_config_url: https://node-002.day14.example.invalid/api/BR14-002
             d14_analysis_config_crc32_hex: 0x9fab14e7
             d14_analysis_has_hcfg_section: true
laps         start 548, artifact 776, solve 598, ready 0, submit 957, TOTAL 2881 ms
vs started_at: start +121, artifact +897, solve +1496, ready +1496, submit +2454 ms
result       CORRECT 13:00:55.915Z   flag e6688b5a…f372

Mastery

started_at   2026-10-14T13:01:03.235Z   deadline 13:01:33.235Z (30 s)
artifacts    3 streamed downloads: 01/02/03-evidence.zip, 32.2 / 32.4 / 32.2 MB
             set 2 is the PE/ELF mix (d02, d04, d10 as PE .exe); Day 14 is ELF in all three sets
answers      420 (140 per set), one submit
             e.g. set2.d02_analysis_format: pe, set2.d10_analysis_machine: 34404 (PE x86-64),
                  set1/set3 d10 machine: 62 (ELF), set1-3 d14_analysis_has_hcfg_section: true
laps         start 1185, dl2 1010, dl3 506, dl1 46, artifact 0, solve 672, ready 1, submit 1257, TOTAL 4678 ms
vs started_at: start +232, dl2 +1242, dl3 +1749, dl1 +1795, artifact +1795, solve +2467,
             ready +2468, submit +3725 ms
result       CORRECT 13:01:06.236Z   flag e1c0ac30…7f85

The three downloads ran at the same time and finished out of order (2, 3, then 1). The solve lap ended 672 ms after the last one landed.

Both timed start replies said hall_of_fame_eligible: false and unique_attempts_remaining: 0, as on earlier Binary days. These times aren't ranked and no retry is possible.

Time to solve

All three modes were solved by 09:01:06 ET, 24.1 s from the first start to the last CORRECT; the Single Solve's three batches were all accepted, and the Time Trial and Mastery each went through on the first submit. Both timed runs were unranked.

Takeaways

  • The prompt's language doesn't decide the questions. It talked about classes, selectors, and strings, but the questions were the same field set as every day. The Objective-C metadata was never asked about.
  • Structure-based parsers carried one more language. ELF header, section table, and the CRC-checked HCFG search covered GNUstep Objective-C with no change. That's the payoff of answering by structure and not by language.
  • Old rules pay off later. has_hcfg_section was added on Day 10 for Zig; today's build also kept the record in its own section, and the rule answered it unchanged.
  • Downloads are still most of the Mastery clock: about 1.6 s of 3.7 s went to three 32 MB zips.