Skip to content

SECURE_CONNECTION//PRESS[CTRL+J]FOR ROOT ACCESS

BACK TO INTEL
AndroidEasy

Freeda — Simple Hook

CTF writeup for Freeda — Simple Hook from heroCTF

//Freeda — Simple Hook

Challenge summary

  • Category: Android / Java (easy)

  • Goal: find the password to open the vault (flag format: ^Hero{\S+}$).

  • Files provided: app-release.apk (decompiled as apktool_out/ in the workspace).

Short summary of approach

  • This challenge kept its vault logic in Java (smali) under com.heroctf.freeda1.utils.Vault.

  • Instead of heavy static reverse engineering, I quickly read the smali, re-implemented the algorithm in Python and ran it to get the flag.

What I looked for first

  • I searched for obvious get_flag / flag / CheckFlag strings, and found com/heroctf/freeda1/utils/Vault.smali which has get_flag().

  • CheckFlag.checkFlag reflects/forwards to Vault.get_flag() — so recovering Vault.get_flag() is sufficient.

Static analysis notes (high level)

  • Vault contains a static integer array (byte source) and code that:

  - computes a 32-bit seed() from two strings

  - builds an indices array and does a Fisher–Yates-like shuffle driven by an xorshift PRNG

  - transforms bytes by subtracting the index, rotating, and XORing with seed bytes

  - finally converts those bytes to a string with charset UTF-8

  • The algorithm is straightforward to reimplement in Python; no native code or obfuscation to defeat.

Local reproduction (commands)

Use apktool to decode the APK:

bash

apktool d -f app-release.apk -o apktool_out

Look at the relevant file:

bash

less apktool_out/smali/com/heroctf/freeda1/utils/Vault.smali

Solver code (Python) — this is what I used locally

python

# solver_simple_hook.py

MASK = 0xFFFFFFFF

  

def to_uint32(x):

    return x & MASK

  

def java_hash(s):

    h = 0

    for ch in s:

        h = (31 * h + ord(ch)) & MASK

    return h

  

def rotate_left(val, dist):

    dist &= 31

    val &= MASK

    return ((val << dist) | ((val & MASK) >> (32 - dist))) & MASK

  

# Re-implemented seed() seen in smali

def seed():

    s1 = "com.heroctf.freeda1.MainActivity"

    s2 = "com.heroctf.freeda1.utils.CheckFlag"

    v0 = java_hash(s1)

    v0 ^= to_uint32(-0x3f0011be)

    v0 &= MASK

    v1 = java_hash(s2)

    v0 ^= v1

    v0 &= MASK

    v1 = rotate_left(v0, 7)

    v1 = (v1 * to_uint32(-0x61c88647)) & MASK

    v0 ^= v1

    v0 &= MASK

    return v0

  

vault_a = [

    0x34,0x58,0x1b,0x20,0x1b,0xba,0x60,0x6d,0x2d,0xca,

    0x2a,0x7d,0x19,0x86,0x9f,0x45,0x2f,0x8e,0xc0,0xb8,

    0x0d,0x13,0x8b,0xad,0x3b,0x81,0x00,0x9e,0xa5,0xbc,

    0x0d,0x3e,0x4a,0xb8,0x3a,0x4b,0xac,0xca,0x42

]

  

assert len(vault_a)==0x27

  

# get_flag reimplementation

def get_flag():

    v0 = seed()

    n = 0x27

    indices = list(range(n))

  

    # The xorshift (rot-l/XOR/shift pattern) from smali

    v4 = to_uint32(-0x5a5a5a5b) ^ v0

    v4 &= MASK

    for i in range(n-1, -1, -1):

        v4 ^= (v4 << 13) & MASK

        v4 &= MASK

        v4 ^= ((v4 & MASK) >> 17)

        v4 &= MASK

        v4 ^= (v4 << 5) & MASK

        v4 &= MASK

        j = (v4 & MASK) % (i + 1)

        indices[i], indices[j] = indices[j], indices[i]

  

    out = []

    rot = ((v0 & MASK) >> 27) & 0x7

    for i in range(n):

        val = vault_a[indices[i]]

        val = (val - i) & 0xFF

        if rot:

            val = (((val << (8 - rot)) & 0xFF) | (val >> rot)) & 0xFF

        shift = (i & 3) * 8

        val ^= ((v0 >> shift) & 0xFF)

        out.append(val)

    return bytes(out).decode('utf-8')

  

if __name__ == '__main__':

    print(get_flag())

Local success

  • Running the script in the same folder produced the flag:
Hero{1_H0P3_Y0U_D1DN'T_S7A71C_4N4LYZ3D}

Notes / tips

  • Always search for get_flag, CheckFlag, Vault, and Hero{ first — many CTF Androids put the core logic in Java and not in native code.

  • Reimplement exactly what the smali shows; be careful about Java int semantics (32-bit wrap, signedness) when translating to Python.