The challenge
"Separate real instrumentation signals from generated false positives."
| answer_id | Question | Format |
|---|---|---|
answer |
"For case MOB-23-40D151C015 in package com.huntress.mobile.d23.v40d151c0156f: Separate real instrumentation signals from generated false positives. Submit only the exact backend answer code returned for this case." | pattern: "ANS- followed by exactly 16 uppercase hexadecimal characters"; exact |
Time Trial asked the same for its own case; Mastery asked it once per APK (7 APKs, 7 answers).
Reading the prompt: what it said we'd need
| Clue | What it pointed to | What I did about it |
|---|---|---|
| "instrumentation signals" | Frida and hook detection code (method names like scanFridace, sealHook8c) |
Read them statically; nothing was hooked or run |
| "generated false positives" | Decoy methods and constants that look important but aren't used | Followed only the real call path from the answer method; the decoys are never called or read |
| "the exact backend answer code returned" | The code the app would send to its backend | The answer is what Release. computes, not a value stored in the APK |
| "ANS- … 16 uppercase hex" (format) | One 64-bit value printed as hex | The interpreter's result had to match that shape before sending |
Safety
Static analysis only. Nothing was installed or executed. The DEX was read with baksmali and jadx on our Kali VM. Claude's interpreter evaluates only static methods doing int/long arithmetic and a few StringBuilder, String, and Long library calls, with a step budget; anything else (other library calls, instance fields, unknown opcodes) stops it. The app's loopback backend (127.0.0[.]1:19090) was never contacted; nothing serves it anyway. The APKs stayed on our Kali VM.
What was in the files
- Single Solve:
hook_line_and_signal.apk, 12,704 bytes. Itsassets/huntress_puzzle.jsonwas the old v1 schema:{"schema":"huntress.mobile.puzzle.v1","day":23,"codec":"xor","data":"EB8C…","key":81}, the Day 1–9 codec. - Time Trial: an APK of the same size with a new v2 asset:
{"schema":"huntress.mobile.puzzle.v2","day":23,"codec":"analysis","build":"c4c2860ad9c2"}. No data, no key. - What the DEX does (from the static read, short and defanged):
ChallengeActivity: ifRelease.proof(ctx)equals the intent'sanswer, it shows"Evidence released: " + Release.code(ctx)and posts that code to the loopback backend.Release.code()="ANS-"+ 16 hex digits offinSignal47(Mix.f1(token())) ^ K, wheretoken()=mixFrida0c(scanFridace(mixFrida40(seed))). Pure long/int arithmetic;ctxis stored but never read.scanFridacefolds in three "signals" fromProbe.s1/s3: brute-force loops over 0..4095 solving a small modular equation (((i*a+b)^c)&4095 == d, and a rotate form), plusinstrumentationSignals = (a^b^c)&7.- The false positives:
sealFridac7,sealHook8c,sealTrampolinef3and the staticKHook96,KPatcheb,KTrampolinea0are never called or read on the code path. - Mastery: 7 APKs (12,704 to 12,845 bytes), all v2, for packages from Days 2, 4, 10, 12, 17, 22, and 23. So the timed variants used the v2 template with different arithmetic, while the Single Solve kept the old one.
Single Solve
The solver solved it with no stop, using the Day 20 Android solver's v1 XOR reader.
started_at 2026-10-23T13:00:56.109Z
deadline none (Single Solve)
answers 1 ANS-75C47B4B…B1C1
start 332 ms
artifact 264 ms
solve 1 ms
submit 1858 ms
TOTAL 2456 ms (submit +2.21 s after started_at)
result CORRECT flag 82bbd521…4555
Time Trial
Ranked attempt: stopped, nothing sent
started_at 2026-10-23T13:01:03.723Z
deadline 2026-10-23T13:01:53.723Z (50 s)
answers none
result STOP: unexpected puzzle schema huntress.mobile.puzzle.v2; not submitting.
The solver stopped this lane (it doesn't retry on its own); the attempt expired at the deadline.
This stop was correct. The chain only knew v1 assets, and a v2 asset holds no answer to decode, so any value sent would have been a guess.
The v2 rule and the solver
v2 rule: the answer isn't in the asset. It is the return value of the DEX's single (Context)String method that builds "ANS-". Evaluate its static call graph (arithmetic only) and ignore the decoy methods it never calls.
Our solver: the Day 20 Android solver unchanged for v1; for v2 (codec: analysis), a small read-only Dalvik interpreter over the DEX bytes, as described under Safety.
Bug on the way: the invoke branch reused the caller's method-index name, so the decode cache read the callee's code. It raised a TypeError on a wide-value marker: a clean stop, nothing sent. Fixed before any send.
Checks before sending: - 10 tests pass (pytest), including: a hand transcription of the jadx output for the Time Trial APK gives the same value as the interpreter; the Single Solve's v1 answer; a Mastery-shaped test (mixed v1/v2, and one APK streamed); a tampered DEX, an unknown v2 codec, and an exhausted budget all stop. - A regression rehearsal against the previous Android solver: 132 identical, 0 changed. - A regression check against every answer the Game Master had already accepted on the Android lane (284), checked against the request log: all reproduced, 0 mismatches.
Untimed retry: CORRECT
Claude then ran one untimed retry, using the Game Master's retry without asking for a ranked run, so the Time Trial is unranked. Release.code took 115,728 interpreter steps.
started_at 2026-10-23T13:09:04.605Z (retry, untimed)
deadline none
answers 1
retry 70 ms after started_at
artifact 304 ms
solve 65 ms
submit 847 ms
TOTAL 1216 ms from the retry reply (submit +1.29 s after started_at)
result CORRECT flag ec076040…ddda
Mastery
CORRECT on the first attempt, ranked (230 s clock). All seven APKs were v2; they took 400 to 62,503 interpreter steps.
started_at 2026-10-23T13:09:13.523Z
deadline 2026-10-23T13:13:03.523Z (230 s)
answers 7
start 126 ms after started_at
artifact 614 ms
solve 150 ms
submit 1056 ms
TOTAL ~2.7 s (submit +1.95 s after started_at)
result CORRECT flag 8df0a470…dc05
Afterwards a regression rehearsal over 135 saved attempts gave 132 identical, 3 newly solved, 0 changed, and all 292 accepted Android answers reproduced. At 09:10:00 ET the new solver became the one the scheduled job uses.
Time to solve
The Single Solve went through at 09:00:58 ET. The ranked Time Trial stopped on the new v2 schema and expired with nothing sent; after about 8 minutes of static reading, interpreter building, and checks, an untimed (unranked) retry went through at 09:09:05 ET. The ranked Mastery went through on its first submit at 09:09:15 ET, 8 min 20 s after the lane's first start and 9 min 15 s after release.
Takeaways
- Timed sets can change format from the Single Solve. The Single was v1 and every timed APK was v2. A Single CORRECT doesn't prove the timed shape; solvers prepared ahead of the day should be tested on the newest Time Trial shapes too.
- When the answer is computed, evaluate the code, don't run the app. A bounded interpreter for the narrow subset the method uses gave the answer without installing anything.
- Make the interpreter stop on anything it doesn't understand. The one bug hit that stop, and nothing wrong was sent.
- Cross-check one case by hand. The jadx transcription matching the interpreter was the test that made sending safe.
- Decoys are named to be found.
sealHook8candKHook96look central, but only the call graph from the answer method decides what matters.