NOVA Blogs Logo

Ham44 - Android CTF Challenge Writeup


Ham44 — Android CTF Challenge Writeup

image

Author: gr00t


Introduction

Recently, I got the opportunity to build an Android challenge for a CTF organized by my Team NOVA. This was actually my first time designing a medium-level Android challenge, so it was both exciting and a bit challenging to balance the difficulty.

The challenge was called Ham44 — yes, the name comes from Sir Lewis Hamilton, one of my favorite Formula 1 drivers! 🏎️

The challenge included several Android security concepts:

  • A hidden activity
  • An encrypted flag retrieval from a gRPC server

Interestingly, the challenge ended up with 0 official solves, but a team from Amrita Vishwa Vidyapeetham, Coimbatore managed to solve it in the intended way. Unfortunately, they ran out of time before submitting. Still, huge congratulations to them — that was impressive work.

In this blog, I'll walk through the intended solution path for the challenge.


Challenge Description

Ham44 As part of a campus promotion for F1 by KICTF, a small Android app began circulating among students. It appears to be nothing more than a simple media demo that plays a familiar song on launch. Just simple right? Author: gr00t Flag Format: KICTF{...} APK: Download Here

The challenge description does not reveal much. The only hint given is: "Just simple right?" This is meant to subtly indicate that the application is not as simple as it appears.


Step 1 — Installing the APK

First, we need to install the APK on a device or emulator.

adb install Ham44.apk

When we launch the application, we immediately notice something unusual. The app only plays a Lewis Hamilton edit video when opened.

image

There are:

  • No buttons
  • No menus
  • No visible features

The user can only open the app and close it. At first glance, it looks like a simple video app. But since this is a CTF challenge, we know that the real functionality must be hidden somewhere beneath the surface.


Step 2 — Inspecting the APK

Since the UI gives us nothing to work with, the next logical step is static analysis. An Android APK is essentially a ZIP archive. We can unpack it using apktool to inspect the files:

apktool -d Ham44.apk

image

Understanding Android APK Structure

A typical Android APK contains several important components:

  • AndroidManifest.xml: This acts as the configuration file for the entire app. It defines Activities, Permissions, Application components, and Intent filters.
  • classes.dex: This contains the compiled Java/Kotlin bytecode.
  • res/: This folder contains layout files, images, and UI resources.
  • assets/: An optional directory containing extra files used by the application.

Step 3 — Inspecting the Manifest & Decompiling

While exploring the extracted files, we notice something interesting: the Manifest file and class names appear obfuscated, which makes manual analysis a bit harder.

To better understand the application logic, we can decompile the APK using JADX. JADX converts the compiled bytecode back into readable Java code.

jadx-gui Ham44.apk

image

Step 4 — Discovering the Hidden Activity

Inside the AndroidManifest.xml, we find a major clue. There is another activity defined in the application:

<activity  
    android:name="com.example.challx.FlagActivity"  
    android:permission="com.example.ctfapp.PERMISSON_FLAG"  
    android:exported="true">
    <intent-filter>  
        <action android:name="com.example.ctfapp.OPEN_FLAG"/>  
        <category android:name="android.intent.category.DEFAULT"/>  
    </intent-filter>  
</activity>

The main thing standing out here is that the Activity is Exported (android:exported="true").

This means the activity can be launched by external applications. Even if the UI does not expose it, we can still trigger it manually!


Step 5 — Launching the Hidden Activity

Since the activity is exported, we can start it manually using ADB. (Note: I am using a rooted Android emulator for this step).

adb root
adb shell am start -n com.example.challx/.FlagActivity

image

Success! Suddenly, a new screen appears in the application. This screen contains a single Download button.


Step 6 — Retrieving the Flag

Clicking the Download button triggers a request to the backend gRPC server and returns a value. At first glance, it looks promising, but after examining the result, we realize this is not the actual flag.

image

Instead, we receive an encrypted hex string: 38590f1b3d20235c3352151c3448523d2e6c2132345823050a58340e0803313126564956

Now, the challenge pivots to cryptanalysis.


Step 7 — Identifying the Encryption

From the challenge description, we already know something important—the flag format is: KICTF{...}

This gives us a known plaintext, allowing us to attempt a known-plaintext attack. A common encryption used in CTF challenges is repeating XOR.

The rule for XOR encryption is: Ciphertext ⊕ Plaintext = Key

If we know part of the plaintext, we can potentially recover the key.


Step 8 — Discovering the Base64 Layer

Initially, XORing the ciphertext directly with KICTF{ produces non-printable characters. This suggests that another encoding layer was applied before XOR encryption.

A common candidate in such challenges is Base64. Encoding KICTF{ in Base64 gives: S0lDVEZ7

Now, if we XOR the first 8 bytes of our hex ciphertext with this Base64 string, it reveals a repeating pattern: kic_keyk

This strongly suggests that the repeating XOR key is: kic_key


Step 9 — Decrypting the Flag

Now that we have all the pieces of the puzzle:

  1. Ciphertext (hex)
  2. Encryption method: Repeating XOR
  3. XOR key: kic_key
  4. Additional encoding: Base64

We can fully reverse the encryption! Below is the Python script used to decrypt the flag:

import base64

# Ciphertext shown in the app
encrypted_hex = "38590f1b3d20235c3352151c3448523d2e6c2132345823050a58340e0803313126564956"

# Recovered repeating XOR key
xor_key = "kic_key"

# Step 1: Convert hex string to bytes
xored_bytes = bytes.fromhex(encrypted_hex)

# Step 2: XOR-decrypt using repeating key
key_bytes = xor_key.encode("utf-8")
b64_bytes = bytes(b ^ key_bytes[i % len(key_bytes)] for i, b in enumerate(xored_bytes))

# Step 3: Decode Base64
flag = base64.b64decode(b64_bytes).decode("utf-8")

print(f"Congratulations! The flag is: {flag}")

Running the script produces our final target!

Final Flag

KICTF{gRpC_S3rV3r_St0r4g3}


Final Thoughts

This challenge combined multiple Android security concepts into one neat package:

  • Hidden Activities
  • Exported Components
  • Backend Communication via gRPC
  • Cryptographic Reversal

Even though the app appeared extremely simple at first glance, deeper inspection revealed multiple layers of logic. That is often the key lesson in mobile security challenges: What you see in the UI is rarely the whole story. There is almost always something hidden beneath.

This was my first time building an Android CTF challenge, and it was a great learning experience designing the vulnerability chain.

Hopefully, you enjoyed this walkthrough!

- gr00t