zwesty
Write-ups / Day 27 / Android
Day 27 · Tue, Oct 27 · Android

Dependency Surface

Prey slaughtered

APK triage (manifest, DEX string table, assets), the v1 puzzle asset and its codecs, mixed-control Masteries

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-27-dependency-surface (+ /time_trial, /mastery)
Solved
2026-10-27: all three modes CORRECT on the first submit, by 09:01:32 ET, 17 s after the first start request on this lane (09:01:16 ET) and 1 min 33 s after the 9 AM release. Both timed modes were ranked runs
Player
Zwesty (solo), with Claude Code (Opus 5.5): our automated morning solver (a scheduled script on our Kali VM) did all three modes, with no hands-on work needed

The challenge

"Prove which generated dependency flaw is reachable from an exported mobile surface."

answer_id Question Format
answer "For case MOB-27-7540D4374B in package com.huntress.mobile.d27.v7540d4374bc6: Prove which generated dependency flaw is reachable from an exported mobile surface. 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

The Time Trial asked the same for its own case; the Mastery asked it once per delivery (11 deliveries, 11 answers).

Reading the prompt: what it said we'd need

Clue What it pointed to What I did about it
"an exported mobile surface" An Android component other apps (or a link) can reach The manifest has an activity with a VIEW/BROWSABLE intent filter for huntress27://dependency-surface/unlock
"generated dependency flaw" A planted, synthetic library weakness The DEX names the control legacy_dependency ("reachable challenge-owned dependency")
"Prove which … is reachable" Trace input from that surface to the dependency The string table has getQueryParameter, getStringExtra, and loadUrl; no question needed the trace, because the v1 asset carried the answer
"the exact backend answer code returned for this case" The code the app would get from its loopback backend The backend was never contacted; the value comes from the APK alone
"Dependency Surface" (title) Attack surface through a dependency Confirmed the reading

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 string tables and the binary manifest's UTF-16 strings were read in Python, and nothing went near an emulator or the app's loopback backend. The APKs stayed on our Kali VM. A fixture client-secret string in the DEX is not reproduced here.

What was in the files

  • Single Solve: dependency_surface.apk, 12,704 bytes: AndroidManifest.xml, classes.dex, assets/huntress_puzzle.json, and the fixture signature files. The asset is the old v1 schema: {"schema":"huntress.mobile.puzzle.v1","day":27,"codec":"base64","data":"QU5TLTgw…"}. Decoding the Base64 gives the code, ANS-802A…2F7E. The DEX has three classes (MainActivity, ChallengeActivity, DependencySurface), the control name legacy_dependency, and the case text.
  • Time Trial: dependency_surface_time_trial.apk, 12,704 bytes, the same v1 base64 template with a different case (MOB-27-A2DADF4499) and code (ANS-2F52…0F0D).
  • Mastery: 11 deliveries, all v1, each with an earlier day's control (class names from the DEX):
Set Day Control class Codec Artifact
1 2 DeepLinkDrift reverse .apk, 12,704
2 4 PendingIntentRelay xor .apk, 12,704
3 6 ProviderPaths hex .apk, 12,704
4 8 CleartextSession reverse .apk, 12,704
5 12 SQLiteAssumptions hex .apk, 12,704
6 14 PredictableCrypto caesar .apk, 12,704
7 16 JNIBoundary native .apk, 12,772 (+ lib/x86_64/libarena.so)
8 20 LoadedLater dynamic .apk, 12,768 (+ assets/payload.dex)
9 21 DebugMeIfYouCan reverse .apk, 12,704
10 26 SplitDecision split .apks, 21,536 (base + feature)
11 27 DependencySurface base64 .apk, 12,704

A change from Days 24–26: those days kept v1 for the Single and used v2 (an answer computed by the app's code) for the timed modes. Today every delivery was v1, so the code-reading path wasn't needed at all.

Single Solve

The solver got it with no stop. Our existing Android solver (from Day 26, unchanged) found the v1 asset and applied its base64 codec.

started_at  2026-10-27 09:01:17.158 ET
deadline    none (Single Solve)
answers     1   ANS-802A…2F7E
start        332 ms
artifact     388 ms   (12,704 bytes)
solve          1 ms
submit       611 ms
TOTAL       1332 ms   (submit +1.10 s after started_at)
result      CORRECT   flag 1194b934…d573   (09:01:17.750)

Time Trial

CORRECT on the first attempt, ranked (unique_attempts_remaining: 31, variant_fresh: true).

started_at  2026-10-27 09:01:23.515 ET
deadline    2026-10-27 09:02:13.515 ET   (50 s)
answers     1   ANS-2F52…0F0D
start        288 ms
artifact     339 ms   (12,704 bytes)
solve          1 ms
submit      1349 ms
TOTAL       1977 ms   (submit +1.79 s after started_at)
result      CORRECT   flag 81f44e70…1628   (09:01:24.079)

Mastery

CORRECT on the first attempt, ranked (11 deliveries, 350 s clock; Day 26 had 10 and 320 s). Each delivery went through its own day's v1 path; the solver's report names the Day 20 one for set 8: dynamic (Day 20): assets/payload.dex [manifest digest ok] dex Lcom/huntress/arena/payload/Module;->ANSWER (the payload DEX checked against MANIFEST.MF, then its ANSWER field read statically). Set 10's .apks bundle went through Day 26's split path.

started_at  2026-10-27 09:01:31.134 ET
deadline    2026-10-27 09:07:21.134 ET   (350 s)
answers     11   (ANS-7AA3…C460 … ANS-1A21…C1FB)
start        907 ms
artifact    1298 ms
solve          7 ms
submit       716 ms
TOTAL       2928 ms   (submit +2.16 s after started_at)
result      CORRECT   flag a8c51622…e5b7   (09:01:32.720)

No code was written for this lane today.

Time to solve

All three modes were solved by 09:01:32.720 ET, 1 min 33 s after release (17 s from the first start request on this lane). The Time Trial and Mastery were both ranked and each went through on the first submit.

Takeaways

  • Every earlier control stayed answered. Eleven deliveries across ten earlier days' controls (native library, dynamic DEX, split bundle among them) went through on the first submit with no code change.
  • The timed modes can get easier, not only harder. Day 26's Single and Time Trial differed in kind (v1 vs v2); today all 13 deliveries were v1. The solver still checks each delivery's own schema rather than assuming one.
  • The prompt promises more than the questions ask. "Prove which … is reachable" sounds like tracing a deep link into a vulnerable library; the asset carried the code, and no field asked for the path.
  • Keep the v2 path ready anyway. The DEX has the pieces of a real reachability trace (an exported browsable activity, query-parameter and extra reads, a URL loader); a v2 variant of this control would need them modelled.