Mobile / Sea of Secrets CTF 2026

The Phantom Application

01 Nov 2026 · 1 min read

The APK (com.phantom.navigator) has a vault activity that decrypts an AES ledger once you pass an officer/passcode check. Both the check and the key derivation are in the code, so none…

Mobile Security, Hard, 300 pts

FLAG{ph4nt0m_n4v1g4t0r_cr4ck3d_th3_bl4ck_sp0t_7721}

The APK (com.phantom.navigator) has a vault activity that decrypts an AES ledger once you pass an officer/passcode check. Both the check and the key derivation are in the code, so none of it needs the device.

Decompiling with jadx gives a handful of classes. StorageManager seeds SharedPreferences (navigator_prefs) with the hex ledger and a session nonce, D4RK_T1D3S_7721. It also seeds a few fake flags (sh4r3d_pr3fs_...), and QuartermasterValidator.MASTER_KEY is another one. None of them are the flag.

The passcode is stored in QuartermasterValidator as an int table that is XORed with 42 at check time. Undoing that gives Skul1&Cr0ssB0n3s_1718!, and its SHA-256 matches EXPECTED_PASSCODE_HASH, so it's the real one. The officer string is Captain_Edward_Vane.

SecurityBridge.decryptLedger does the rest:

  • key = SHA-256(passcode + ":" + nonce + ":PHANTOM_FLEET_KEY")
  • AES-256-CBC, PKCS5, fixed IV CaribbeanSea1718

Redoing it outside the app:

python
import hashlib
t = [121,65,95,70,27,12,105,88,26,89,89,104,26,68,25,89,117,27,29,27,18,11]
pw = ''.join(chr(i ^ 42) for i in t)
key = hashlib.sha256(f"{pw}:D4RK_T1D3S_7721:PHANTOM_FLEET_KEY".encode()).hexdigest()
# openssl enc -d -aes-256-cbc -K <key> -iv 43617269626265616e53656131373138

Piping the ledger's bytes (DEFAULT_LEDGER in StorageManager, hex-decoded) through that openssl command prints the flag. I used the default ledger and nonce from the code and did not check them against a live device's prefs.