Sitemap
Press enter or click to view image in full size

🇧🇩 BdSecCTF 2025 — Hacker App Challenge Write-Up

3 min readJul 24, 2025

--

🏴 Challenge Name: Hacker App

Category: Reverse Engineering / Mobile
Platform: Android (APK)
Author: NomanProdhan
Flag Format: BDSEC{...}

🧩 Challenge Description

You can hack any website with this app !!

🔧 Tools Used

  • ByteCode-Viewer (APK decompiler)
  • Python (for decoding custom encoding)
  • Base64 + logic

🔍 Initial Analysis

After loading the APK into ByteCode-Viewer, we landed on the MainActivity.class file which controls the core functionality of the app. Inside it, we found this suspicious string:

Press enter or click to view image in full size
private static final String zN2LpB = "ioFOE6/xXoxB5M02UsaVQAhuQVC5f8+PMMgwOwGbmE0R7n6qyRQ9qwCzCgDVYWc6";

Also, this line caught my eye:

if ("2025.bdsec-ctf.com".equals(var1)) {
var7.add("[SUCCESS] Root Account Password: " + zN2LpB);
}

🤔 Looks like we’re supposed to input that exact domain to trigger the payload! So this string was definitely the flag or a key.

🔐 The Obfuscation

Looking deeper, we noticed a method encrypting strings like:

Base64.encodeToString(iP7sV3("root:toor123".getBytes(StandardCharsets.UTF_8), mK4pJ1[var3]), 2);

The encoding chain:

  1. Reverse & XOR (oX8jZ6)
  2. Custom TEA encryption (uE9rC5)
  3. Byte transformation with jN5fC2 table (yW0qH1)
  4. Base64 encode

That means zN2LpB was generated using this same method — so we need to reverse all these steps.

🐍 Decoding Script

I wrote a custom Python script that reverses all the transformations above:

import base64
import struct

jN5fC2 = [(i * 73 + 41) & 0xFF for i in range(256)]
inv_jN5fC2 = [0] * 256
for i in range(256):
inv_jN5fC2[jN5fC2[i]] = i

vY7kD3 = [253635900, 1264216440, -2053724232, -908399620]

def reverse_yW0qH1(data):
result = bytearray(len(data))
for i in range(len(data)):
b = data[i]
b = (b >> 5 | (b << 3)) & 0xFF
result[i] = inv_jN5fC2[b]
return result

def reverse_uE9rC5(data):
result = bytearray(len(data))
for i in range(0, len(data), 8):
v0 = struct.unpack('>I', data[i:i+4])[0]
v1 = struct.unpack('>I', data[i+4:i+8])[0]
sum_ = -1640531527 * 16

for _ in range(16):
v1 -= ((v0 << 4 ^ v0 >> 5) + v0) ^ (vY7kD3[(sum_ >> 11) & 3] + sum_)
v1 &= 0xFFFFFFFF
v0 -= ((v1 << 4 ^ v1 >> 5) + v1) ^ (vY7kD3[sum_ & 3] + sum_)
v0 &= 0xFFFFFFFF
sum_ += 1640531527

result[i:i+4] = struct.pack('>I', v0)
result[i+4:i+8] = struct.pack('>I', v1)

# Remove padding
pad = result[-1]
return result[:-pad]

def reverse_oX8jZ6(data, key):
data = bytearray(data)
for i in range(len(data)):
data[i] ^= (key >> (i % 4 * 8)) & 0xFF
data.reverse()
return data

def try_decrypt(base64_string):
decoded = base64.b64decode(base64_string)
rev1 = reverse_yW0qH1(decoded)

for key in [int(k, 16) for k in [
"4AC93E17", "DE9A3214", "D2C3E1F0", "BEEFCAFE",
"C0FFEE01", "BAADF00D", "CAFEBABE", "8BADF00D"
]]:
try:
rev2 = reverse_uE9rC5(rev1)
rev3 = reverse_oX8jZ6(rev2, key)
return rev3.decode('utf-8')
except Exception:
continue
return None

# The encoded string
zN2LpB = "ioFOE6/xXoxB5M02UsaVQAhuQVC5f8+PMMgwOwGbmE0R7n6qyRQ9qwCzCgDVYWc6"

# Try decoding
flag = try_decrypt(zN2LpB)
print("Decrypted string:", flag)

When run, the output was:

Decrypted string: BDSEC{h4cK3r_aPP_t0_HACK_THE_PLANET_bdSECctf}

💥 BOOM! There’s the flag, hidden deep under layers of custom obfuscation and encoding!

🧠 Key Learnings

  • APKs can hide juicy secrets, especially in hardcoded constants.
  • Custom encoding chains are tricky, but with patience you can reverse them.
  • Always look for strings that are Base64-encoded or XOR’d — they’re often the key 🔑.
  • Static analysis using ByteCode-Viewer can go a long way without even needing to run the app.

🏁 Flag

BDSEC{h4cK3r_aPP_t0_HACK_THE_PLANET_bdSECctf}

💬 Final Thoughts

This was a fun reverse-engineering challenge with some clever string obfuscation. It reminded me of real-world malware unpacking. Shoutout to the challenge creator — great job!

--

--