zwesty
Write-ups / Day 22 / Android
Day 22 · Thu, Oct 22 · Android

Root of Trust

Prey slaughtered

APK asset analysis, puzzle codec routing, root-detection concepts (read, not run)

STTMClaude Homebase Both

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

Challenge ID
mobile-android-day-22-root-of-trust (+ /time_trial, /mastery)
Solved
2026-10-22: all three modes CORRECT on the first submit, by 09:01:26 ET, 22 s after the solver's first start request (09:01:04 ET) and 1 min 26 s after the 9 AM release. Both timed modes were ranked runs. The scheduled run started on time (08:58:01); Metadata and Warmups ran first
Player
Zwesty (solo); solved by our automated morning solver (a scheduled script on our Kali VM), built with Claude Code (Opus 5.5), with no manual work needed

The challenge

"Model and bypass multiple synthetic root signals without globally patching success."

answer_id Question Format
answer "For case MOB-22-… in package com.huntress.mobile.d22.…: Model and bypass multiple synthetic root signals without globally patching success. Submit only the exact backend answer code returned for this case." pattern: "ANS- followed by exactly 16 uppercase hexadecimal characters; no spaces or explanatory text"; exact

Mastery asked the same question for six APKs (set1.answer … set6.answer), each with its own case and package.

Reading the prompt: what it said we'd need

Clue What it pointed to What I did about it
"multiple synthetic root signals" Root-detection checks: the app looks for signs of a rooted device and changes its behaviour Nothing to defeat: the answer sits in the puzzle asset, so the app never had to run
"Model and bypass … without globally patching success" In a real app: model each check and neutralise them one by one, rather than forcing the final "passed" result to true Read the flow's end point statically instead (the challenge activity and its loopback evidence URL) and didn't contact it
"Root of Trust" (title) Root detection; the class is RootOfTrust with a root_detection control Confirmed the day's mechanic from the DEX strings
"backend answer code" An ANS- token, as on every Android day The chain requires exactly one ANS- plus 16 hex reading

Safety

APKs are hostile: static analysis only, on our Kali VM. No APK was installed or run, and nothing was patched, hooked, or debugged. The solver reads zip members and the puzzle JSON as bytes. ChallengeActivity builds a loopback "evidence" URL (127.0.0.1:19090) from the answer and case ID; it was not contacted (not needed, and not in scope). The DEX also carries a fixture client-secret string; it wasn't used and isn't quoted here.

What was in the files

  • Single Solve: root_of_trust.apk, 12,704 bytes:
  • assets/huntress_puzzle.json (102 bytes): schema huntress.mobile.puzzle.v1, day 22, codec base64, and a 28-character data string that decodes to the answer.
  • classes.dex (2,576 bytes): MainActivity, ChallengeActivity, and RootOfTrust, which has CODEC, CONTROL, and a describe() string DAY-22:root_detection:synthetic root detection.
  • a binary AndroidManifest.xml (3,476 bytes): meta-data huntress.mechanic = "synthetic root detection", a launcher activity, and a deep link with scheme huntress22 and host root-of-trust into ChallengeActivity.
  • v1 signing files (META-INF/MANIFEST.MF, FIXTURE-.SF, FIXTURE-.RSA).
  • Time Trial: root_of_trust_time_trial.apk, 12,704 bytes, the same template (day 22, base64) with its own case and answer.
  • Mastery: six APKs, 12,704 bytes each, one per earlier mechanic: day 1 (plain), day 6 (hex), day 12 (hex), day 13 (base64), day 17 (indexed, with an order permutation of the 20 data characters), and day 22 (base64).

So the timed variants used the same template with different values, and the Mastery mixed today's APK with older ones.

Single Solve

The solver solved it with no stop, and the codec was known. It used our Android solver from Day 20, which builds on the Day 19 and Day 17 versions and the Day 15 base. The Day 17 layer found base64 in its known-codec table and handed the APK to the older decoder, which has read base64 since Day 3 (Intent Injection).

The solver's report has no fallback or unknown-codec line. The Day 17 layer prints a fallback … codec … line when it has to guess; that line is absent. The one extra line, indexed (Day 17): read as scatter, came from the Mastery's day-17 APK: the Day 17 reader tried both ways of applying the order field and only the scatter reading gave a valid ANS- code. No new solver, tests, or rehearsal were written today.

started_at  2026-10-22T13:01:10.852Z
deadline    none (Single Solve)
answers     1   ANS-7259…611F
start       6265 ms   (the start request went at 13:01:04; the reply came back at 13:01:10)
artifact     288 ms
solve          1 ms
submit       631 ms
TOTAL       7184 ms   (submit +1.06 s after started_at)
result      CORRECT   flag 156936ad…262d

The 6.3 s start was the Game Master's reply time, not the solver's; the timed starts that followed took under 1 s.

Time Trial

CORRECT on the first attempt, ranked. The start reply showed unique_attempts_remaining: 31.

started_at  2026-10-22T13:01:17.582Z
deadline    2026-10-22T13:02:07.582Z   (50 s)
answers     1   ANS-A347…4D5B
start        702 ms
artifact     768 ms
solve          1 ms
submit      1040 ms
TOTAL       2511 ms   (submit +1.89 s after started_at)
result      CORRECT   flag 457659f8…3269

Mastery

CORRECT on the first attempt, ranked (6 APKs, 200 s clock; Day 21 had 5 APKs and 170 s).

started_at  2026-10-22T13:01:25.263Z
deadline    2026-10-22T13:04:45.263Z   (200 s)
answers     6   (codecs plain, hex, hex, base64, indexed, base64)
start        846 ms
artifact     667 ms
solve          3 ms
submit       757 ms
TOTAL       2273 ms   (submit +1.53 s after started_at)
result      CORRECT   flag 242e1ef3…f09c

Time to solve

All three modes were solved by 09:01:26 ET, 22 s after the solver's first start request at 09:01:04 ET and 1 min 26 s after release; the Metadata and Warmups lanes ran first. Each mode went through on the first submit, and both timed modes were ranked.

Takeaways

  • A new mechanic still doesn't mean a new codec. The prompt was about root detection, but the puzzle asset used base64, known since Day 3, so the known-codec table took it with no fallback.
  • Read the report's extra lines before calling them fallbacks. indexed (Day 17): read as scatter is the Day 17 reader's normal choice for an order field, not an unknown codec.
  • The slowest step was the Game Master. The Single Solve's start reply took 6.3 s; the solve itself took 1 ms.