Challenge: Baby Android · CTF: DalCTF 2026 · Category: Reversing / Mobile · Difficulty: Easy
TL;DR
The challenge hands you a single BabyAndroid.apk. The running app only shows
some sarcastic “nothing to see here” text — the real flag is split into three
pieces scattered across three different locations inside the APK:
- A hardcoded field in
MainActivity - A string resource in
res/values/strings.xml - A property getter in the Compose theme package (
ColorKt)
Decompile, grep, concatenate:
| |
Recon
We start with exactly one file:
| |
An APK is just a ZIP, so the first thing to do is look at what’s inside it. The package metadata tells us this is a stock Jetpack Compose app:
| |
Listing the archive shows a lot of dex (Compose pulls in a huge runtime), a
couple of native .so files for androidx.graphics.path, and nothing obviously
custom:
| |
The tiny classes4.dex (20 KB) is the giveaway — almost everything else is the
Compose/AndroidX framework. The application’s own code is small.
Decompiling
For Android, the go-to tool is jadx, which turns the dex bytecode back into readable Java:
| |
⚠️ Gotcha: jadx resolves a relative APK path against its own install directory (
/usr/share/jadx/bin/), so it’ll complainFile not found. Pass an absolute path to the APK and output dir and it works fine.
After decompilation, the app’s own package is refreshingly small:
| |
Seven files. This is a “Baby” challenge — the flag isn’t going to be behind a custom VM or an obfuscator. It’s going to be sitting in plain sight if we know where to look.
Piece 1 — MainActivity
Opening MainActivity.java immediately pays off:
| |
There it is: flag1 = "dalctf{4ndr0id". The variable is literally named
flag1, which tells us there are more parts (flag2, flag3, …) to find.
Note that flag1 is never actually used or displayed anywhere — it’s a dead
field. The UI is rendered entirely from the Compose lambdas, which (as we’ll
see) only contain decoy text. This is the whole point of the challenge: the flag
lives in the binary, not on the screen.
The red herrings (what the app actually shows)
If you’d installed and run the app instead of reverse engineering it, you’d have
seen this — and nothing else. The MainScreen composable (jadx struggles to
decompile the full Compose state machine, so we re-run with --show-bad-code)
contains only taunts:
| |
“have you checked under the hood?” is the hint — stop poking at the UI and read the bytecode. This matches the challenge’s theme: Android Debugging.
Piece 2 — the string resources
The flag1 naming convention says to hunt for siblings. A quick grep across
both the decompiled sources and the resources is the fastest way:
| |
Two more pieces fall out at once. flag2 is interesting because it’s wired into
the AndroidManifest as the activity’s android:description attribute —
a perfectly legitimate place to stash a string that never shows up in the UI:
| |
And in res/values/strings.xml:
| |
So piece 2 = _d3bugg1ng_.
Piece 3 — hiding in the theme
The grep also caught flag3 living in, of all places, the Compose color
definitions (ui/theme/ColorKt.java) — right next to Purple80, Pink40,
and friends:
| |
Piece 3 = _1s_e4sy}.
Assembling the flag
Three pieces, three locations:
| Piece | Where it lives | Value |
|---|---|---|
flag1 | MainActivity field (classes4.dex) | dalctf{4ndr0id |
flag2 | res/values/strings.xml (manifest description) | _d3bugg1ng_ |
flag3 | ColorKt.getFlag3() (theme package) | _1s_e4sy} |
Concatenating in order:
| |
reads as “android debugging is easy”, giving the flag:
| |
💡 A note on the underscores. If you concatenate the three stored strings byte-for-byte,
flag2ends with_andflag3begins with_, which produces a double underscore:dalctf{4ndr0id_d3bugg1ng__1s_e4sy}. The intended, human-readable flag uses a single underscore (..._d3bugg1ng_1s_e4sy}). If one is rejected by the scoreboard, submit the other — but the single-underscore version is the natural reading and the one that matches the “debugging is easy” sentence.
Flag
| |
Takeaways
- An APK is a ZIP. The flag doesn’t have to be in code —
strings.xml, the manifest, assets, and resources are all fair game. Always grep the decompiled resources, not just the Java. - Don’t trust the UI. What the app shows you (“nothing to see here”) is often a deliberate distraction. The data is in the binary whether it’s drawn on screen or not.
- Follow the naming. Finding a field called
flag1is an explicit invitation to go look forflag2andflag3. A singlegrep -rn flagover the jadx output solved 2 of the 3 pieces in one shot. - Tooling tip: give jadx absolute paths, and use
--show-bad-codewhen a Compose composable refuses to decompile cleanly.
A fitting “Baby” RE challenge: no obfuscation, no anti-debug, no native crypto — just a lesson in where Android stashes strings and a reminder to check under the hood.