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

Keystore Misuse

Prey slaughtered

Static APK inspection, reading a packaged puzzle asset, matching Mastery deliveries to their questions

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-13-keystore-misuse (+ /time_trial, /mastery)
Solved
2026-10-13, all three modes CORRECT on the first submit; 14.4 s from first start to last CORRECT (09:00:21–09:00:35 ET). Both timed runs were unranked on the day; ranked retries followed on 10-13 and 10-15
Player
Zwesty (solo), with Claude Code (Opus 5.5): solved by our automated morning solver (a scheduled script on our Kali VM) using our existing Android solver

The challenge

"Recover data protected by an app-controlled wrapping key despite nominal Keystore use."

answer_id Question Format
answer "For case MOB-13-… in package com.huntress.mobile.d13.…: Recover data protected by an app-controlled wrapping key despite nominal Keystore use. Submit only the exact backend answer code returned for this case." pattern: ANS- followed by exactly 16 uppercase hex characters; normalization exact

Mastery asks the same question once per APK: three on the day (set1.answer to set3.answer), five on the later ranked retries.

Reading the prompt: what it said we'd need

Clue What it pointed to What I did about it
"nominal Keystore use" the app calls Android Keystore but doesn't really rely on it Nothing new. As on every Android day since Day 10, the answer sits in the packaged puzzle asset
"app-controlled wrapping key" the key that protects the data is held by the app itself, so it can be recovered The puzzle asset carries everything needed to decode it (its codec and any key), and our Android solver handles every codec seen so far
"backend answer code returned for this case" a runtime service The app points at a loopback URL nothing serves; the code is the decoded asset (method in the Network Config Split write-up)
ANS- + 16 uppercase hex, exact a strict check The solver checks the decoded value against this format before sending

Safety

Static only. The solver opened each APK as a zip in Python on our Kali VM and read the puzzle asset. Nothing was installed, run, or decompiled. This write-up was built from the solver's report and the saved Game Master replies only. The APKs weren't reopened for it.

What was in the files

Five APKs on the day, each 12,704 bytes, one template:

Mode APK sha256 (first 16) Answer
Single Solve keystore_misuse.apk 80795a1f9f632902 ANS-773762E85797064C
Time Trial keystore_misuse_time_trial.apk 99549e41a1dd1db4 ANS-8D9E9BBB4DA4808E
Mastery 1–3 keystore_misuse_mastery_01.apk … _03.apk (set 3: f39b1a62a000c84a) ANS-9ED753498DB0BCB3, ANS-6EB4390198502B30, ANS-0A00AA01BCA6F42A

Unconfirmed: which codec the asset used today. The solver's report doesn't print it, and the APKs weren't reopened for this write-up. The timed variants differed only in values (case ID, package suffix, answer).

Single Solve

The solver started the untimed Single Solve, fetched the APK, checked its hash, decoded the asset, and sent one answer. No HTTP 429 this morning.

started_at  2026-10-13T13:00:21.073Z   deadline none (untimed)
answer      ANS-773762E85797064C
timings     start 486  artifact 310  solve 0  ready 0  submit 705  TOTAL 1501 ms
vs started_at: start +107, artifact +417, ready +417, submit +1122 ms
result      CORRECT  df71ea7a…f0b3   (solved_by.at 13:00:21.720Z)

Time Trial

The start reply said unique_attempts_remaining: 0 and hall_of_fame_eligible: false: unranked. The clock was 50 s (Day 12's Android routes had 30 s).

started_at  2026-10-13T13:00:27.630Z   deadline 13:01:17.630Z (50 s clock)
answer      ANS-8D9E9BBB4DA4808E
timings     start 486  artifact 371  solve 2  ready 0  submit 831  TOTAL 1690 ms
vs started_at: start +96, artifact +468, ready +469, submit +1301 ms
result      CORRECT  925039e0…4bca   (solved_by.at 13:00:28.415Z)

Mastery

Three APKs; the solver's matcher paired each with its question. One submit. Again unique_attempts_remaining: 0, hall_of_fame_eligible: false. The submit leg took 1.76 s, about twice the others'; the saved files don't say why.

started_at  2026-10-13T13:00:34.552Z   deadline 13:02:24.552Z (110 s clock)
answers     set1 ANS-9ED753498DB0BCB3  set2 ANS-6EB4390198502B30  set3 ANS-0A00AA01BCA6F42A
timings     start 679  artifact 516  solve 1  ready 0  submit 1764  TOTAL 2960 ms
vs started_at: start +105, artifact +621, ready +622, submit +2386 ms
result      CORRECT  c7c2eb18…901b   (solved_by.at 13:00:35.449Z)

Later: ranked retries

On a retry, the submit reply's solved_by.at still shows the first CORRECT (13:00), so the times below are our own timing laps, not the server's.

  • 2026-10-13 ~10:16 ET, Time Trial: CORRECT, ranked (hall_of_fame_eligible: true, 14 ranked attempts left). Submit +3.66 s after started_at: the server was slow right after its 503 outage (the submit leg alone took ~3 s).
  • Same minute, Mastery: retry → HTTP 503 retry_rotation_invalid. Nothing started; not resent. A health check at ~10:50 ET found the server up but slow, so we held the rest for the day.
  • 2026-10-15 13:06 ET, Mastery (now five APKs): CORRECT, ranked, submit +4.41 s (slow server again; the download leg took 2.1 s).
  • 2026-10-15 evening, three more Mastery passes (22:33 ET from the Homebase agent (GPT-6 Luna), submit +3.25 s; 23:32 ET from our Kali VM on a wired link, ready 0.71 s, submit +1.42 s; 23:58 ET from the Homebase, ready 0.58 s). After the 23:30 pass the board showed 6th of 26 at 0.980 s; the 23:58 run was confirmed ranked but the board hadn't moved when last checked.
  • Unconfirmed: the Time Trial's board place was never recorded.

Time to solve

All three modes were solved by 09:00:35 ET, 14.4 s from the first start to the last CORRECT, each on the first submit; the two timed runs were unranked. Ranked retries later the same day and on 10-15 went through at +3.66 s (Time Trial) and, at best, +1.42 s (Mastery).

Takeaways

  • Our Android solver carried over again: no change, all three modes unattended in under 15 s.
  • "Keystore" was a theme, not a lock. The prompt's own words say the app controls the wrapping key; for the puzzle that meant the answer could be recovered from what the APK itself carries.
  • Ranked time depends on the server as much as on us. The same route went from +4.41 s on a slow afternoon to +1.42 s on a healthy evening with a wired link; the solve was ~1 ms every time.