The challenge
The prompt opens with "Unwind obfuscation, native indirection, runtime decryption, and integrity checks", and asks for proof of the same backend effect.
| answer_id | Question | Format |
|---|---|---|
answer |
For case MOB-30-FFC25D0C0D in package com.: the prompt's task, then "Submit only the exact backend answer code returned for this case." |
pattern: ANS- followed by exactly 16 uppercase hex characters; exact |
The Time Trial asked the same for its own case; the Mastery asked it once per delivery (14 deliveries, 14 answers).
Reading the prompt: what it said we'd need
| Clue | What it pointed to | What I did about it |
|---|---|---|
| "Unwind obfuscation" | Peel off encodings one at a time | The v1 asset's data is three layers deep: Base64, a position-keyed XOR, then a byte reversal |
| "runtime decryption" | A key applied while the app runs | The asset carries an integer key (12); the XOR mask for byte i is key + i |
| "native indirection" | Answer logic in a native library | Not in the Single. It mattered in the Mastery: set 8 was a Day 16 JNI control whose native code has branches |
| "integrity checks" | Guards before the real computation | The set-8 native function checks its arguments (cmp / jne) before the arithmetic; the old evaluator stopped there |
| "Layered Protections" (title) | Several protections stacked | Confirmed twice: stacked encodings in the Single, mixed earlier controls in the Mastery |
Safety
Static analysis only. Nothing was installed or executed. The APKs were opened as ZIP files, the puzzle assets were read as JSON, the DEX was read with baksmali and string-table reads, and the set-8 native function was evaluated by an instruction-level model in Python, not run. Nothing went near an emulator or the app's loopback evidence URL. The APKs stayed on our Kali VM.
What was in the files
- Single Solve:
layered_protections.apk, 12,704 bytes.assets/huntress_puzzle.jsonis the old v1 schema withcodec: "layered", a Base64datastring (20 bytes once decoded), and an integerkeyof 12. The DEX has three classes.LayeredProtectionsholds onlyCODEC = "layered",CONTROL = "layered_protections",DAY = 30, anddescribe(): no decode method, so the layers had to be read from the data.ChallengeActivityonly forwards the answer and case id to a local evidence URL. - Time Trial:
layered_protections_time_trial.apk, 12,704 bytes, notlayered. It's a v2analysispuzzle (assetday: 30) whose answer is computed by DEX code (classesLayerKit,PeelStack,Mix,Release,Util). It went through the Day 25 static interpreter's path. - Mastery: 14 deliveries, all v2
analysis, each an earlier day's control (from each asset'sday):
| Set | Day | Artifact |
|---|---|---|
| 1–7 | 1, 3, 5, 8, 9, 13, 14 | ., 12,704 |
| 8 | 16 (JNI) | ., 12,772 (+ lib/) |
| 9 | 20 (loaded later) | ., 12,768 (+ assets/) |
| 10, 11 | 22, 24 | ., 12,704 |
| 12 | 26 (split) | ., 21,535 (base + feature) |
| 13 | 27 | ., 12,704 |
| 14 | 30 | ., 12,704 |
Same pattern as Days 24–29: a v1 codec for the Single, v2 code-reading puzzles for the timed modes.
Single Solve
The automated solver: stopped, nothing sent
started_at 09:01:10.993 ET (started by the solver)
deadline none (Single Solve)
result STOP: unknown puzzle codec 'layered' (day 30): fallback found 0 ANS readings; not trusted; not submitting
timed modes not started
Finding the layers (Claude helper agent)
The DEX had no decoder, so the helper worked from the data and the known answer shape. The key move was lining the data's last 4 bytes up with -SNA, the ANS- prefix reversed: XOR-ing them gave 28, 29, 30, 31, which is 12 + i for positions 16–19. So, outermost first:
- Base64-decode the
datatext. - XOR byte i with
(key + step × i) mod 256, with step 1 on the Single. - Reverse the byte order.
The solver
Today's solver builds on the Day 29 solver and adds layered. It tries a small fixed grid of layer orders (Base64 or hex text; XOR or subtract; reverse before or after the mask), derives the step from the ANS- prefix (byte 0 must give key exactly, bytes 1–3 must agree on one step), and accepts only when exactly one distinct reading fits ANS- + 16 uppercase hex. Anything else stops cleanly. Every other codec and schema goes to the Day 29 path unchanged.
- Decoded answer:
ANS-3F02…43CA(20 characters, fits the format). - Its tests: 11 of 11 OK, including the saved Single, an encoder round trip, other keys, steps and orders, a
delivery 2:question, every stop case (wrong key, broken prefix, bad data, lowercase), and Days 28 and 29 unchanged. - A regression check against every answer the Game Master had already accepted: 574 Android answers reproduced, 0 mismatched, 0 stopped.
reconnected to the solver's attempt 09:04:41 ET
answers 1 ANS-3F02…43CA
start → submit 1.19 s (submit +211.7 s after the solver's started_at)
result CORRECT flag a00dbfe8…bb4a (09:04:41.935 ET)
Time Trial
CORRECT on the first attempt, ranked (variant_fresh: true). The v2 puzzle went through the existing interpreter with no new code.
started_at 2026-10-30 09:05:05.896 ET
deadline 2026-10-30 09:05:55.896 ET (50 s)
answers 1
start 234 ms (after started_at)
artifact 560 ms
solve 565 ms
submit 1770 ms
result CORRECT flag 917a64e9…b9ed (09:05:06.572 ET)
Mastery
CORRECT, ranked, in one attempt, but not in one run.
First run (ranked, started 09:05:11.968 ET, 7 min 20 s clock): 13 of 14 accepted, set 8 left out. Set 8 is the Day 16 JNI control. Its native function guards the arithmetic with argument checks (cmp ecx, 0xffff / jle / movzx / cmp / jne), and Day 24's straight-line evaluator stopped on the first cmp ("unsupported instruction"). The run sent the 13 it had: accepted, remaining 1, nothing wrong sent.
Fix: a branch-aware evaluator we had already built and staged after a Day 24 Mastery hit the same Day 16 variant. The helper added it to today's solver. It is Day 24's evaluator plus four things: it follows the program counter by address, cmp and test set flags (any other flag-writing instruction makes them undefined, and a jump on undefined flags stops), conditional and plain jumps inside the function, and movzx/movsx. Straight-line code runs exactly as before. Offline: set 8 decoded, the other 13 matched the accepted ones, tests still 11 of 11.
Resuming: rerunning on the same open attempt resumed it inside its clock; it was not a retry.
same ranked attempt, resumed 09:06:18 ET
started_at 2026-10-30 09:05:11.968 ET
deadline 2026-10-30 09:12:31.968 ET (440 s)
answers 14
artifact +67.1 s after started_at
solve +67.8 s
submit +69.2 s
result CORRECT flag 9b286d1c…74c6 (09:06:19.906 ET), ranked
Afterwards
- After the three CORRECTs, today's solver became our Android solver for later days. The regression check against accepted answers then stood at 589 reproduced, 0 mismatched, 0 stopped.
- The native-branch fix lives in the Day 30 solver only. It was not added to the Day 24 solver itself.
Time to solve
All three modes were solved by 09:06:19.9 ET, 6 min 19.9 s after release and 5 min 8.9 s after the solver's first start; the Single came 3 min 30.9 s after that start. The Time Trial went through on the first submit, and the Mastery's first run left one set open and was resumed inside the same ranked clock, so both timed modes were ranked.
Takeaways
- A known prefix is a key-schedule oracle. With no decoder in the DEX, lining up
ANS-(reversed, at the end) against the data gave the key and the step in one step. - Accept exactly one reading. The grid of layer orders can produce more than one plausible decode; the solver sends only when one distinct answer fits the format, and stops otherwise.
- Staged fixes pay off later. The branch-aware evaluator was written two days earlier for another day's Mastery and unblocked this one in about a minute.
- An open ranked attempt resumes; it doesn't retry. Sending the 13 sure answers first and the fixed 14th later kept the Mastery ranked.
- The prompt listed every protection, but the Single used only encodings. The "native indirection" and "integrity checks" showed up in the Mastery's mixed controls, not in today's own asset.