The challenge
"Defeat app-owned environment checks on the same pinned emulator used by the solve oracle."
| answer_id | Question | Format |
|---|---|---|
answer |
"For case MOB-24-D222D6F58D in package com.huntress.mobile.d24.vd222d6f58d31: Defeat app-owned environment checks on the same pinned emulator used by the solve oracle. 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 (8 APKs, 8 answers).
Reading the prompt: what it said we'd need
| Clue | What it pointed to | What I did about it |
|---|---|---|
| "app-owned environment checks" | Emulator-detection code inside the app (EmulatorAlibi.) |
Read statically: emulatorScore(s1,s2,s3) over Probe loops is pure arithmetic, so the Day 23 interpreter already evaluated it. Nothing had to be "defeated" at run time |
| "the same pinned emulator used by the solve oracle" | The environment is fixed, so the answer is deterministic; and the emulator's ABI is x86_64 | When a set needed a native library, only the lib/ build was modelled |
| "the exact backend answer code returned" | The code the app would post to its backend | The answer is what Release. computes, as on Day 23 |
| "ANS- … 16 uppercase hex" (format) | One 64-bit value printed as hex | Every computed value had to match that shape before sending |
Safety
Static analysis only. Nothing was installed or executed. The DEX files were read with baksmali and jadx on our Kali VM and the native library with objdump. The interpreter evaluates only what it models (int/long arithmetic, a few library calls, the asset and class-loading calls listed below, a register-only subset of x86-64) under a step budget; anything else stops it. The app's loopback backend was never contacted. The APKs stayed on the VM.
What was in the files
- Single Solve:
emulator_alibi.apk, 12,704 bytes, with the old v1 asset:{"schema":"huntress.mobile.puzzle.v1","day":24,"codec":"split",…}, the answer cut into three parts plus an order list. Our Day 20 v1 reader rebuilt it:ANS-70755D5A…1B05. - Time Trial: an APK of the same size with a v2 asset (
"codec":"analysis", no data): the answer is the return value ofRelease.code(), evaluated in 60,199 interpreter steps:ANS-C8BBDD46…38C9. - Ranked Mastery: 8 v2 APKs for packages from Days 2, 6, 8, 11, 14, 18, 20, and 24. Seven were plain arithmetic (425 to 111,647 steps). Set 7 (Day 20,
LoadedLater.CONTROL = "dynamic_dex") was 12,768 bytes and carried an extraassets/payload.dex. - Untimed retry Mastery: a fresh variant, Days 3, 6, 9, 10, 15, 17, 23, and 24. Set 5 (Day 15,
jni_secret) was 12,772 bytes and carriedlib/x86_64/libarena.so.
So the Single kept the v1 template, and the v2 Masteries mix earlier days' controls under today's wrapper. Two of them needed more than arithmetic.
Single Solve
The solver got it with no stop, using our existing Android solver from Day 23.
started_at 2026-10-24T13:00:50.145Z
deadline none (Single Solve)
answers 1 ANS-70755D5A…1B05
start 396 ms
artifact 424 ms
solve 1 ms
submit 760 ms
TOTAL 1580 ms (submit +1.27 s after started_at)
result CORRECT flag a52aac8f…5077
Time Trial
CORRECT on the first attempt, ranked.
started_at 2026-10-24T13:00:56.843Z
deadline 2026-10-24T13:01:46.843Z (50 s)
answers 1 ANS-C8BBDD46…38C9 (Release.code in 60,199 steps)
start 473 ms
artifact 402 ms
solve 30 ms
submit 826 ms
TOTAL 1732 ms (submit +1.35 s after started_at)
result CORRECT flag d48c7214…6940
Mastery
Ranked attempt: 7 of 8, expired
started_at 2026-10-24T13:01:03.703Z
deadline 2026-10-24T13:05:23.703Z (260 s)
answers 7 of 8 (set7 not in the submit)
start 91 ms after started_at
artifact 618 ms after started_at
solve 739 ms after started_at
submit 957 ms after started_at (submit call 218 ms; TOTAL 1422 ms)
result accepted, remaining 1; the solver stopped this lane, so the attempt expired at the deadline
What went wrong. Set 7's token() passes through Core.lateModule14, which reads assets/payload.dex through Context.getAssets().open(…), wraps the bytes in a ByteBuffer, loads them with InMemoryDexClassLoader, and calls com.huntress.arena.payload.Module.share(long) by reflection. The Day 23 interpreter refuses any Context call, so it stopped on that set alone; the per-set solve sent the other seven.
Nothing was burned. The submit in the request log has 7 keys and no set7.
The fix, part 1: the payload DEX (helper)
The helper built a new solver on top of the Day 23 one, keeping its interpreter and only adding:
- const-class, Class[]/Object[] arrays, Long.TYPE/Integer.TYPE;
- a model of the asset path: Context.getAssets → AssetManager.open (the asset read from the APK zip, its MANIFEST.MF SHA-256 checked) → InputStream.read/close → Arrays.copyOf → ByteBuffer.wrap;
- InMemoryDexClassLoader (the second DEX parsed with the same checked parser) → loadClass → getDeclaredMethod (static, primitive parameters only) → Method.invoke, run as a child interpreter on the same step budget;
- Long.valueOf/longValue, String.indexOf/substring. Any other Context call still stops.
Set 7 of the failed Mastery: Release.code in 325 steps, equal to a hand transcription of the jadx output (the payload's Module.share included).
Untimed retry: a second unseen control
We allowed ourselves one untimed retry of a failed first timed run, so the helper used retry in untimed (unranked) mode. The retry's fresh variant had a different mix, and its solve stopped before submitting on set 5: NativeLibrarySecrets.<clinit> calls System.loadLibrary("arena"), and the token passes through static native long nativeShare(long) in lib/x86_64/libarena.so (not stripped). Short, defanged objdump excerpt: mov rax,rdx; imul rax,<const>; rol rax,0x34; add …; ret.
The fix, part 2: the JNI function (helper)
System.loadLibraryloadslib/x86_64/lib<name>.sofrom the APK (manifest digest checked).- A static call to a DEX method with no code is resolved as the JNI symbol
Java_<mangled class>_<method>in the ELF64 symbol tables and evaluated by a small x86-64 evaluator: capstone decoding plus a model of x86-64 register arithmetic only (mov, add/sub/xor/and/or, imul, rotates and shifts, not/neg, bswap,leawithout memory, push/pop, ret). Memory operands, branches, calls, 8/16-bit writes, and any read ofJNIEnv*/jclassstop it. Only the x86_64 build is used (the pinned emulator's ABI). - Set 5: equal to a hand transcription of jadx plus objdump (248 steps).
The attempt was still open and untimed, and nothing had been sent on it, so it was resumed. That is the same retry, not a second one.
Checks before the submit:
- 20 tests pass: the set 7 and set 5 hand transcriptions; the Day 23 solver stops on both; the failed Mastery keeps its 7 accepted answers; one-APK streamed delivery; tampered or missing payload.dex and libarena.so stop; the shared budget; the x86-64 model (zero-extension, memory/branch/call/JNIEnv stops); JNI name mangling.
- A regression check against every answer the Game Master had already accepted: 301 accepted Android answers reproduced, 0 mismatched, 0 stopped.
- A regression rehearsal against the previous solver: 139 attempts, 137 identical, 2 newly solved (both Day 24 Masteries), 0 changed, slowest 0.19 s.
started_at 2026-10-24T13:07:39.705Z (retry, untimed)
deadline none
answers 8
first pass stopped in the solver on set5 before submitting; fixed; same attempt resumed at 13:11:12
start +213.95 s after started_at (resume)
artifact 387 ms
solve 202 ms
submit 766 ms
TOTAL ~1.35 s for the resumed pass (submit +215.30 s after started_at)
result CORRECT flag 5742e932…eaf1
This Mastery has no ranked time. Afterwards this solver became our default Android solver for the following days.
Time to solve
All three modes were solved by 09:11:15 ET, 11 min 15 s after release (10 min 26 s from the first start request on this lane). The Single Solve and the ranked Time Trial went through on the first submit; the ranked Mastery expired at 7 of 8, and the Mastery was then solved on an untimed (unranked) retry.
Takeaways
- v2 Masteries replay older days' controls. Today's control was plain arithmetic, but the Mastery sets came from Days 2–24, including the Day 20 DEX reflection and the Day 15 JNI secret. Pre-build static models for every earlier control and test them inside a v2 wrapper.
- Still not seen in a v2 Mastery: the Day 1, 5, 7, 13, 16, 19, and 21 controls. Those are the next pre-builds.
- Leaving a set out is better than guessing it. The solver sent the 7 it could prove and burned nothing.
- A clean stop inside an untimed retry can still be saved. The retry's solve stopped before submitting, so the same attempt was resumed after the JNI fix instead of using a second retry.
- Model only what the code uses, and stop on the rest. The x86-64 model handles register arithmetic only; that was enough for
nativeShare, and anything wider stops cleanly.