M1n10n'5_53cr37 picoMini by CMU-Africa Solution

Published: April 2, 2026

Description

An Android APK hides the flag in its string resources under a suspicious key. Decompile the app and decode the base32-encoded value.

Download the APK file.

Install apktool: sudo apt install apktool

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1
    Decompile the APK
    Observation
    I noticed the challenge supplied an APK file containing the flag, and APKs store string resources as compiled binary XML that cannot be read with a plain text editor, which suggested using apktool to decode the archive into human-readable XML and smali before searching for any hidden values.
    Use apktool to decode the APK into readable smali bytecode and XML resource files. APK files are ZIP archives containing compiled DEX bytecode.
    bash
    apktool d minion.apk -o minion_decoded
    What didn't work first

    Tried: Rename the APK to .zip and open it directly to browse the contents

    Unzipping the APK gives raw binary XML files (e.g., res/values/strings.arsc) that are not human-readable. Binary XML must be decoded by apktool or aapt to become plain text. The unzip approach works for extracting individual files but not for reading resource values.

    Tried: Use jadx instead of apktool to decompile the APK

    jadx produces readable Java source, which is great for logic analysis, but it does not always accurately reconstruct string resource names and may inline values instead of preserving the strings.xml structure. For finding a resource by key name like 'Banana', apktool's decoded res/values/strings.xml is the more direct and reliable target.

    Learn more

    An APK (Android Package) file is a ZIP archive that contains everything needed to install an Android app: compiled bytecode in classes.dex (Dalvik Executable format), compiled binary XML resources in res/, the app manifest (AndroidManifest.xml), and native libraries in lib/. Because it is a standard ZIP, you can rename it to .zip and browse the contents directly.

    apktool decodes the APK's binary XML back into human-readable XML and disassembles the DEX bytecode into smali - a human-readable assembly-like language for the Dalvik/ART virtual machine. Smali is harder to read than Java source, but it reveals every method, field, and string constant in the app. The -o flag specifies the output directory.

    Android apps compiled from Java or Kotlin can also be decompiled to near-source-level Java using jadx or JADX-GUI, which produce more readable output than smali for most analysis tasks. The choice between apktool (smali) and jadx (Java) depends on the task: apktool is better for re-packaging and patching; jadx is better for reading and understanding logic.

  2. Step 2
    Search string resources for the key
    Observation
    I noticed the challenge description mentioned the flag was hidden under a suspicious key in the app's string resources, which suggested grepping res/values/strings.xml for the key name 'Banana' after apktool produced the decoded output.
    String resources live in res/values/strings.xml. Search for the key 'Banana' - it contains a base32-encoded value.
    bash
    grep -i 'banana' minion_decoded/res/values/strings.xml
    What didn't work first

    Tried: Search the APK's classes.dex or smali files for the secret value instead of strings.xml

    String resources defined in res/values/strings.xml are compiled into resources.arsc, not embedded in DEX bytecode. Grepping smali files for 'Banana' will return nothing because the smali code references the resource by its integer ID (e.g., 0x7f0b0023), not by name. The actual value lives only in strings.xml after apktool decoding.

    Tried: Run grep directly on the original minion.apk instead of the decoded output directory

    The APK contains a compiled binary resources.arsc file where all strings are stored in a binary format, not plain text XML. Grepping the raw APK may occasionally surface some plaintext fragments but will miss most values and produce no reliable output. apktool's decoded output is required first.

    Learn more

    Android string resources are defined in res/values/strings.xml as key-value pairs: <string name="key">value</string>. These are intended for UI text and localization, but developers sometimes store hardcoded credentials, API keys, or other sensitive values here under innocuous-sounding names. After apktool decoding, these files are plain text XML, readable with any text editor or grep.

    Secrets in string resources are a top finding in mobile app security audits. Unlike Java class constants which require decompilation, string resources are trivially accessible to anyone who extracts the APK - no reverse engineering skill is needed. This is documented by OWASP as M1: Improper Credential Usage in the OWASP Mobile Application Security Verification Standard (MASVS).

    A comprehensive mobile app review searches not just strings.xml but also other resource files, raw asset files (assets/), and native libraries for hardcoded secrets. Tools like MobSF (Mobile Security Framework) automate this scan and produce a full security report from an APK file.

  3. Step 3
    Decode the base32 value
    Observation
    I noticed the value stored under the 'Banana' key consisted entirely of uppercase letters and digits 2 through 7 with '=' padding, which is the distinctive character set of base32 encoding and suggested running base32 -d to recover the flag.
    Copy the base32 string from the XML and decode it. The result is the flag.
    bash
    echo '<BASE32_VALUE>' | base32 -d
    python
    python3 -c "import base64; print(base64.b32decode('<BASE32_VALUE>').decode())"
    What didn't work first

    Tried: Pipe the value through base64 -d instead of base32 -d

    Base64 uses a 64-character alphabet (A-Z, a-z, 0-9, +, /) while base32 uses only 32 characters (A-Z and 2-7). A base32 string fed to base64 -d will either produce garbage binary output or an error because the character set does not match. The uppercase-only, digits-2-7 pattern is the fingerprint for base32.

    Tried: Use python3 -c "import base64; print(base64.b64decode(...))" treating the value as base64

    Python's b64decode will silently produce wrong binary output or raise a binascii.Error because base32 and base64 use incompatible block structures: base32 encodes 5 bytes into 8 characters while base64 encodes 3 bytes into 4 characters, so the group sizes and padding are structurally mismatched. Even though the characters in a base32 string (A-Z, 2-7) are mostly valid in base64, the decoded bytes will be meaningless garbage rather than the flag. The correct call is base64.b32decode(), not b64decode().

    Learn more

    Base32 is an encoding scheme that represents binary data using only 32 printable characters: A-Z and 2-7 (RFC 4648). Like base64, it is not encryption - it is purely a representation that avoids characters that might be problematic in certain contexts (no lowercase, no special characters except = padding). Base32 produces longer output than base64 (every 5 bytes become 8 characters vs. base64's 4 per 3 bytes) but is safer for case-insensitive systems.

    The base32 -d command decodes a base32-encoded string from stdin. Python's base64.b32decode() function does the same from within a script. Base32 is used in TOTP authentication codes (Google Authenticator), DNS labels, and some URL schemes. Recognizing it by its uppercase-letter-plus-digits character set and = padding is a useful forensics skill.

    When encountering an encoded string in a CTF, a quick heuristic: base64 uses A-Z, a-z, 0-9, +, /; base32 uses A-Z, 2-7; base16 (hex) uses 0-9, A-F. If the string contains only uppercase letters and digits 2-7, base32 is the first thing to try. The file command on the decoded output tells you what the data actually is.

Interactive tools
  • Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
  • Hex ViewerView text or raw hex bytes as a xxd-style hex dump with byte offset, hex columns, and ASCII sidebar. Highlights printable characters and null bytes.
  • File Magic IdentifierIdentify file types from magic numbers. Paste hex bytes or drop a file to detect PNG, JPEG, ZIP, PDF, ELF, PCAP, SQLite, and dozens of other formats.
Alternate Solution

Once you locate the base32 value in strings.xml, use the Base64 & Base32 Decoder on this site - paste the encoded string and select Base32 to decode it instantly in your browser without any command-line tools.

Flag

Reveal flag

picoCTF{1t_w4sn7_h4rd_unr4v3l1n9_th3_m0b1l3_c0d3}

APK string resources are plaintext XML after decompilation - hiding secrets there provides no protection.

Key takeaway

Android APKs are ZIP archives and the security boundary they provide is effectively zero: anyone can extract, decompile, and read every string constant, resource value, and class in an app without special privilege. Hardcoding secrets in string resources, BuildConfig fields, or class constants is equivalent to publishing them, because decompilation with apktool or jadx takes seconds. The same principle applies to any distributed client binary, including iOS IPAs, desktop Electron apps, and compiled game assets, where secrets intended to stay hidden on the server must never be shipped to the client.

Related reading

Want more picoMini by CMU-Africa writeups?

Useful tools for Reverse Engineering

What to try next