Flagdroid - Hack.lu 2020 Writeups

4 minute read Published:

Writeup for flagdroid challenge of Hack.lu CTF 2020



This app won’t let me in without a secret message. Can you do me a favor and find out what it is?


For this challenge we get an APK file. Fortunately APK files are fairly well reversible (as is most Java based bytecode). In this case we just used an online service to “decompile” the APK for us, but there are plenty of tools you can use to do this locally if you want.

After looking at the code briefly we quickly found the place where the input is checked and it seems it expects the flag as input. From the string splitting routine we can figure out that there’s four parts to the flag, separated by underscores, so we will be looking for something in the format of flag{part1_part2_part3_part4}.

Part 1

Effective part of the code to check the correctness of this part boils down to Base64.decode(getResources().getString(R.string.encoded), 0), "UTF-8". There’s some boilerplate around it, but it’s not important.

The APK decompiler also gives us all the “resources” files, which seems to be where the base64 encoded first part of the flag is. After a little searching through the files for the “encoded” string resource we find it: <string name="encoded">dEg0VA==</string>. This decodes to tH4T so our flag so far is: flag{tH4T_?_?_?}

Part 2

The important part of this section seems to be based on XOR encoding of two a static strings, but there is a little math done to prevent us from just directly XOR-ing the two strings.

String str2 = "\u001fTT:\u001f5ñHG";
        try {
            char[] charArray = str.toCharArray();
            byte[] bytes = "hack.lu20".getBytes("UTF-8");
            if (charArray.length != 9) {
                return false;
            for (int i = 0; i < 9; i++) {
                charArray[i] = (char) (charArray[i] + i);
                charArray[i] = (char) (charArray[i] ^ bytes[i]);
            return String.valueOf(charArray).equals(str2);
        } catch (UnsupportedEncodingException unused) {
            return false;

We figured the easiest way to solve this would be to just modify the Java code some to create the reverse algorithm;

class Test2 {
  public static void main(String[] args) {
    String str2 = "\u001fTT:\u001f5ñHG";

    try {
      char[] charArray = str2.toCharArray();
      byte[] bytes = "hack.lu20".getBytes("UTF-8");

      for (int i = 0; i < 9; i++) {
        charArray[i] = (char) (charArray[i] ^ bytes[i]);
        charArray[i] = (char) (charArray[i] - i);

    } catch (Exception unused) {
      System.out.println("Some weirdness in encoding happened.");

For convenience we wrapped it in a standalone Java class so we can directly compile and run the code, but essentially it’s exactly the same but with charArray[i] = (char) (charArray[i] - i); after the XOR instead of charArray[i] = (char) (charArray[i] + i); before the XOR.

Our output is w45N-T~so and we now have flag{tH4T_w45N-T~so_?_?}.

Part 3

String lowerCase = str.toLowerCase();
if (lowerCase.length() == 8 && lowerCase.substring(0, 4).equals("h4rd")) {
    return md5(lowerCase).equals("6d90ca30c5de200fe9f671abb2dd704e");

It seems we need 8 chars for this part, and are given the first 4 as well as the md5 hash of the whole part. Four characters is easily brute forcable in a reasonable time so let’s break out python pwntools' mbruteforce and quickly get this done.

from pwn import *
res = pwnlib.util.iters.mbruteforce(lambda x: pwnlib.util.hashes.md5sumhex(('h4rd' + x).encode('utf-8')) == '6d90ca30c5de200fe9f671abb2dd704e', string.printable, length=4)

print('h4rd' + res)
$ python test3.py
[+] MBruteforcing: Found key: "~huh"

Easy! We now have flag{tH4T_w45N-T~so_h4rd~huh_?}.

Part 4

The last part confused me for a little while, it only checked this part was equal to stringFromJNI(). After a little searching we learned this method can be overwritten by a custom library in the android application and sure enough one was loaded at the start of the class; System.loadLibrary("native-lib");

We can pull the library “.so” files from our decompiled APK too and throw them in Ghidra to see what they do. We quickly find the function override. In this case the assembly code is actually a lot easier to figure out than Ghidra’s disassembled code. All we need to do is make sure a few values are decoded as a char or char array rather than integers and we get the following disassembly:

        00010556 c6 40 08 74     MOV        byte ptr [EAX + 0x8],'t'
        0001055a c7 40 09        MOV        dword ptr [EAX + 0x9],"?8)\x00"
                 3f 38 29 00
        00010561 c7 40 04        MOV        dword ptr [EAX + 0x4],"4S-1"
                 34 53 2d 31
        00010568 c7 00 30        MOV        dword ptr [EAX],"0r~w"
                 72 7e 77

Even if you never saw x86 disassembled code before you can probably see where this is going. We just need to put the fragments of the string in the right order and we have our 4th part, and a complete flag! flag{tH4T_w45N-T~so_h4rd~huh_0r~w4S-1t?8)}.

Challenge zip file(s) available here