zwesty
Write-ups / Day 14 / Android
Day 14 · Wed, Oct 14 · Android

Predictable Crypto

Prey slaughtered

Static APK inspection, reading a packaged asset, character-code shift ("caesar") decoding

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-14-predictable-crypto (+ /time_trial, /mastery)
Solved
2026-10-14, all three modes; 15.3 s from first start to last CORRECT (09:00:20–09:00:36 ET)
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

Exploit seeded nonces or weak derivation without bypassing earlier storage gates.

One question, answer: "For case MOB-14-… in package com.huntress.mobile.d14.…: Exploit seeded nonces or weak derivation without bypassing earlier storage gates. Submit only the exact backend answer code returned for this case." Format: ANS- + exactly 16 uppercase hex characters, normalization exact. Mastery asks the same question three times (set1.answer to set3.answer), one per APK.

Reading the prompt: what it said we'd need

Clue What it pointed to What I did about it
"seeded nonces or weak derivation" an app that builds its crypto from a fixed seed or a guessable key, so the output can be recomputed Not needed for the answer. The DEX only carries the label predictable_rng; the answer sits in the packaged puzzle asset
"without bypassing earlier storage gates" the storage and Keystore themes of Days 11 and 13, so don't skip steps by patching the app Nothing was patched or run. The APK was read as a zip
"backend answer code returned for this case" a runtime service As on Day 10: the app points at a loopback URL nothing serves; the code is the decoded assets/huntress_puzzle.json
ANS- + 16 uppercase hex, exact a strict check The solver checks the decoded value against this format before sending

The full method is in the Network Config Split write-up.

Safety

Static only. Each APK was opened as a zip in Python (python3 -I) on our Kali VM. The puzzle asset was read, and printable strings were pulled from classes.dex and the binary manifest. Nothing was installed, run, or decompiled to code. The APKs stayed on the VM.

What was in the files

Five APKs (Single Solve, Time Trial, three Mastery deliveries), each 12,704 bytes, all built from one template: AndroidManifest.xml, classes.dex, assets/huntress_puzzle.json, and a FIXTURE- signature.

The codec today was caesar (as on Day 7): take each character's code and subtract key. For example, the Single Solve data starts JW\6L, and with key 9 that gives ANS-C. Every mode used caesar, with a new key each time:

Mode APK sha256 (first 16) Key Answer
Single Solve predictable_crypto.apk b6d3fff1c44485ea 9 ANS-C539C482FC5EE146
Time Trial predictable_crypto_time_trial.apk ef619068a4795a4a 10 ANS-FFDA049C74988184
Mastery 1 predictable_crypto_mastery_01.apk a684fcd3da1214e1 17 ANS-2C1197C6BA89DA27
Mastery 2 predictable_crypto_mastery_02.apk 1ef24b5759bc5ade 4 ANS-4561FFEA182C4655
Mastery 3 predictable_crypto_mastery_03.apk 0a50cf28cd77204f 7 ANS-7DE42633A78D6CAA

The "predictable crypto" theme was a label, not real crypto. The manifest has meta-data huntress.mechanic = "predictable nonce and derivation". The DEX has a PredictableCrypto class and the strings predictable_rng, caesar, encode, and DAY-14:predictable_rng:predictable nonce and derivation. It also has a fixture client_secret=SECRET-…&proof= string, which differs per APK. No javax/crypto, java/security, or java/util/Random references showed up in the strings. So the only "weak derivation" in play is the one in the asset: a fixed shift with its key stored right beside the data. I can't say more from strings alone. What encode does with the secret was not traced, and it wasn't needed.

As on earlier days, ChallengeActivity (deep link huntress14://predictable-crypto/unlock) reads answer and case_id and loads http://127.0.0.1:19090/mobile/attempt/d14-…/evidence in a WebView. Nothing serves that port.

What the timed variants changed: only the values. Each had a new case ID, package suffix, caesar key, data, client secret, and loopback attempt ID. The template, the codec, and the file size stayed the same.

Single Solve

The solver started the untimed Single Solve, fetched the APK, and checked its hash. It found one caesar asset, decoded it to ANS-C539C482FC5EE146, confirmed it fits the format, and sent it once.

started_at  2026-10-14T13:00:20.803Z   deadline none (untimed)
answer      ANS-C539C482FC5EE146
timings     start 527  artifact 388  solve 1  ready 0  submit 986  TOTAL 1902 ms
vs started_at: start +106, artifact +494, ready +494, submit +1480 ms
result      CORRECT  1f423662…d5e3   (solved_by.at 13:00:21.672Z)

Time Trial

Started about 7 s after the Single Solve, as the solver's pacing allows. The start reply said unique_attempts_remaining: 0 and hall_of_fame_eligible: False, as on Day 13. So this run was unranked. The saved files don't say why; that is how the Game Master answered on the first start.

started_at  2026-10-14T13:00:27.804Z   deadline 13:00:57.804Z (30 s clock)
answer      ANS-FFDA049C74988184
timings     start 567  artifact 457  solve 1  ready 0  submit 870  TOTAL 1895 ms
vs started_at: start +95, artifact +552, ready +552, submit +1423 ms
result      CORRECT  2aaf74e8…3358   (solved_by.at 13:00:28.686Z)

Mastery

Three APKs, named delivery N: predictable_crypto_mastery_0N.apk in the questions. The delivery matcher fixed on Day 10 paired them correctly. All three answers went in one submit. Again unique_attempts_remaining: 0 and hall_of_fame_eligible: False. The start step took 1,161 ms, about twice the others; our request log shows the request at 13:00:34 and the reply at 13:00:35. The cause isn't in the saved files.

started_at  2026-10-14T13:00:35.353Z   deadline 13:01:05.353Z (30 s clock)
answers     set1 ANS-2C1197C6BA89DA27  set2 ANS-4561FFEA182C4655  set3 ANS-7DE42633A78D6CAA
timings     start 1161  artifact 395  solve 1  ready 0  submit 915  TOTAL 2472 ms
vs started_at: start +83, artifact +478, ready +479, submit +1394 ms
result      CORRECT  6df7e208…be27   (solved_by.at 13:00:36.084Z)

No failed attempts, no retries, and no HTTP 429s for this challenge.

Time to solve

All three modes were solved by 09:00:36 ET, 15.3 s from the first start to the last CORRECT, each on the first submit. Both timed runs were unranked.

Takeaways

  • The puzzle-asset solver carried over again: a codec it already knew (caesar, first seen Day 7), no change, all three modes unattended in about 15 s.
  • The theme was a label. "Seeded nonces" and "weak derivation" showed up only as names in the manifest and DEX. The real weak point was a fixed-key shift with the key stored next to the data.
  • Timed runs are still unranked: both timed starts said hall_of_fame_eligible: False, as on Day 13.