AxDSan's Lair

Trellix - Google AndroidCTF Team Challenge 2025

2025/10/22
loading

Introduction

Hello and welcome to this post, I will discuss my adventures on the Google AndroidCTF Team Challenge 2025, which I tackled as part of the process for a Trellix Android Reverse Engineer position! This challenge was really profound and insightful, and as you may know already, Android reversing can be quite a ride.

Initial Analysis & Deobfuscation

So, I downloaded the APK and quickly went into my good ol’ bud JADX-GUI, which is the one I commonly use for my Android RE projects… Started analyzing it and… Wait what? The package was heavily obfuscated, so a little deobfuscation would come in handy. After cleaning it up, I checked the AndroidManifest and MainActivity, but they were pretty bland.

I started digging deeper into the Dashboard and Home components, but the real fun started when I hit the Notifications section. I started seeing a bounch of weird things, eg. imports for ByteArrays, IO Stream and Cipher. So that was quite interesting to go through.

I kept digging further and lo’ and behold there it was!

I noticed a native function declaration Fragment(String str) and some asset loading logic. It seemed like it was loading an asset into a byte array and doing some XOR operations with decimal key 69.

Native Library Analysis

So as I hit a wall with just Java analysis, I noticed the APK had a native library embedded called libfragment.so. Since Fragment("...") was being called to prepare keys, I called in IDA, and started working on it.

So once I started working on it, I found the Java_com_google_ctf_ui_notifications_NotificationsFragment_Fragment function. This caught my attention since it would basically be the call that was going on in the Java function.

So I love the renaming feature of IDA so why not use it? 😜

I noticed that the Java side was loading google.png, chopping off 71612 bytes, and treating the rest as an encrypted payload. It was using AES/ECB/PKCS5Padding. But the key? It was passing “15539863” to that native function. If I just used that string, it would be junk. The native library was actually doing the heavy lifting to generate the real key.

Key Generation & Decryption

So this Challenge, (skipping ahead some prior reversing) was relatively simpe once I looked inside the native code. Not minding any of the junk stuff, I just had to focus on this part where the key was generated.

This is where the meat is, the native function takes the input string and XORs it byte-by-byte with a secondary key string “FragmentsControl”.

and it’s as simple as, getting that derived key, and using it to decrypt the payload inside google.png, viola!

so in python the key generation would be someting like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def generate_key(input_str):
strFragmentCtrl = "FragmentsControl"
strLen = len(input_str)

# First byte logic
prepped_key = [ord(input_str[0]) ^ 0x46]

# Loop for the rest
for i in range(15):
val = ord(input_str[(i + 1) % strLen]) ^ ord(strFragmentCtrl[i + 1])
prepped_key.append(val)

return bytes(prepped_key).hex().upper()

print(generate_key("15539863"))
# Output: 77475454545D584742765A5D4D4A595F

Conclusion & Flag


Once I had the key, I decrypted the payload and found a hidden DEX file. I dropped that back into JADX and found a method called TheFlagReal containing:

Android_CTF{Peel1ng_L4y3rs_0ne_by_0ne}

This was a really a really cool excercise!

So, first, I’m sorry if I didn’t provided every single step of the dynamic reflection analysis, but as the title suggested it was just a write-up, and the challenge encouraged strict static analysis anyway.

Second, shout out and thanks to the Trellix team for the opportunity.

And that’s it, I’m out!

CATALOG
  1. 1. Introduction
  2. 2. Initial Analysis & Deobfuscation
  3. 3. Native Library Analysis
  4. 4. Key Generation & Decryption
  5. 5. Conclusion & Flag