Unearthing Secrets in Memory — A Full Write-Up of “Now You See Me” (Shakti CTF 2025)

Unearthing Secrets in Memory — A Full Write-Up of “Now You See Me” (Shakti CTF 2025)

What’s in the ZIP?

Press enter or click to view image in full size
unzip nowyouseeme.zip -d chall && cd chall && ls

Stage 1 — Decompile the APK

Press enter or click to view image in full size
apktool d nowyouseeme.apk -o now_you_see_me
now_you_see_me/
├─ AndroidManifest.xml     ← readable XML
├─ assets/                 ← extra files bundled by devs
├─ smali*/                 ← Dalvik byte-code in text form
└─ lib/<abi>/              ← four tiny native libs (.so ≈ 5 kB each)

Stage 2 — Recovering the real key from a hidden DEX

Press enter or click to view image in full size
grep -R "getKey" now_you_see_me/smali* | head
const-string v0, "resources.dat"
invoke-virtual {v1, v0}, Landroid/content/res/AssetManager;->open(...)
# ↓ later ↓
invoke-virtual {zipFile}, Ljava/util/zip/ZipFile;->entries() ...
Press enter or click to view image in full size
# -p streams the file to stdout; '*.dex' keeps only that entry.
unzip -p now_you_see_me/assets/resources.dat '*.dex' > secret.dex
Press enter or click to view image in full size
d2j-dex2jar secret.dex -o secret.jar
Press enter or click to view image in full size
public static String getKey() {
    return "Sh@ktiCtf_1337";
}

Key found: Sh@ktiCtf_1337
Keep it safe; we’ll need it for the native routine.

Stage 3 — Inspecting the native library

now_you_see_me/lib/x86_64/libnowyouseeme.so   (≈ 5 662 bytes)
Java_com_shaktictf_nowyouseeme_FlagEngine_getFlag
// 1) decoy check, never triggered in legit flow
if (strcmp(user, "mysecretkey") != 0) { error(); }
// 2) CONSTANTS
size_t N = 0x38;                          // 56 bytes
uint8_t *T = &rodata[0x6B0];              // table to be decoded
// 3) CORE LOOP
for (int i = 0; i < N; ++i)
    dest[i] = T[i] ^ user[i % strlen(user)];
// 4) JNI return
dest[N] = '\0';
return (*env)->NewStringUTF(env, (char*)dest);
0x950  lea rax, [rip - 0x2A7]    ; rip = 0x957 → target = 0x6B0

Stage 4 — Dumping the table

r2 -q -c 'px 0x38 @ 0x6b0' now_you_see_me/lib/x86_64/libnowyouseeme.so
0x000006b0  0000 2100 0000 0020 2024 4803 4668 3558  ..!....  $H.Fh5X                                          
0x000006c0  3505 1036 2e47 393d 025b 5a59 3737 3403  5..6.G9=.[ZY774.
0x000006d0  4736 7218 0a2a 425a 0359 0c58 2634 1759  G6r..*BZ.Y.X&4.Y
0x000006e0  2747 396a 0106 034a                      'G9j...J

Stage 5 — One-file Python solver

#!/usr/bin/env python3
from pathlib import Path

LIB  = "now_you_see_me/lib/x86_64/libnowyouseeme.so"
KEY  = b"Sh@ktiCtf_1337"     # from hidden DEX
OFF  = 0x6B0                 # table location
SIZE = 0x38                  # 56 bytes

table = Path(LIB).read_bytes()[OFF:OFF+SIZE]
flag  = bytes(t ^ KEY[i % len(KEY)] for i, t in enumerate(table))

print(flag.decode())         # human-readable!
Press enter or click to view image in full size
$ python solve.py
ShaktiCTF{y0u_f0und_m3_b3hind_th3_1llusi0n_0f_c0d3_5050}
ShaktiCTF{y0u_f0und_m3_b3hind_th3_1llusi0n_0f_c0d3_5050}

Conclusion