I recently worked on a CTF challenge by CyberSpace Kenya which I ended up winning. This is a writeup on how I solved the challenge. We were provided with an android app to reverse engineer and submit 3 flags.
I haven’t done tons of mobile app reverse engineering but as I was learning about the subject in previous challenges I found this 2-step approach to work well:
- Extracting the apk contents with apktool
- Reversing the app code with jadx-gui
Using apktool is pretty simple just run this command on the apk:
> apktool d cyberspace-ctf.apk
This generates files in a folder cyberspace-ctf as below:
. ├── AndroidManifest.xml ├── apktool.yml ├── assets ├── lib ├── original ├── res ├── smali └── unknown
Checking the AndroidManifest.xml and res/values/strings.xml doesn’t reveal any flag. Usually grepping recursively through the apk files for interesting names would hint at something. After a few tries with the words flag, flag1, flag2, flag3 I got some interesting output.
Without much thought I opened jadx to find more mentions of that ValidateFlag3 and found the code.
Reading through the code I was able to figure out that the validate function does the following:
- takes our input
- xor encrypts it with a key(which is generated by getKey() function)
- base64 encodes it
- checks if it is equal to the hardcoded flag3.
From previous experience I know that with xor encryption if you have the key and the encrypted data getting the plain text output is simple. Let me illustrate:
encrypted = key ^ plaintext plaintext = key ^ encrypted
Getting the code to work in java isn’t as easy so I decided to use python and script the solution.
import base64 key1 = "hUZAf9gFIARRFTAvKRDs8A==" key2 = "gp9jPELztMsd51Ih81gO0Q==" flag3 = "RJ9qOOqa7pAinT19vyuQRHOGSi3FnPW5La0BYb4tnw==" data1 = base64.b64decode(key1) data2 = base64.b64decode(key2) key =  for i in range(len(data2)): key.append(data1[i] ^ data2[i]) decodedflag =  input = base64.b64decode(flag3) encryptedFlag = input for i in range(len(encryptedFlag)): decodedflag.append(encryptedFlag[i] ^ key[i % len(key)]) b = [chr(s) for s in decodedflag] print("".join(b))
Since I found flag3 first I must have missed some stuff.
Refering back to my earlier screenshot, after performing the grep there’s some base64 encoded data which I should have been more keen to look at!
Now flag 2 was the last to find. Since most of the clues we have been finding were in that
assets/index.android.bundle file let me have a look at it.
Something I noted was that jadx didn’t find this file, that why the approach to use two tools is good IMO. A friend of mine has pointed out that it was actually there under Resources/assets on the jadx ui, nevertheless using two or more tools for analysis is still a good practice IMO. There is a lot of code in this file that is not easily readable, so I just googled what this file is.
Reading the code in it’s minified form isn’t easy so js-beautifier to the rescue. I then opened the readable version in vs code and searched for the validateflag function again.
I landed here and we can clearly see the function that contained the base64 and the the other one that called flag3, due to their order flag 2 is what is left.
I took the same approach to porting the code to python as it is easier for me to work with. I had to do a bit of googling to understand the js flow but came up with this:
def f(n): t = 2 r = 0 e =  for r in range(len(n)): if 1 & r: t += 13 else: t -= len(n) % 9 u = ord(n[r]) + t e.append(str(u)) return e
At the end, the output of the function is checked if it is equal to that list of numbers which is a hex representation of characters. Instead of trying to undo the transformation that was going on in the function where they add 13… which I tried for a few hours and failed, better to bruteforce all possibilities.
I came up with this script for that:
import string k = [64,80,78,141,124,124,123,151,144,141,134,166,146,158,148,172,158,192,166,197,176,204,190,210,209,201,206,229,204,232,228,246,220,253,234,245,258,268,250,262,282] all = string.printable a = "a"*41 flag = [a[i] for i in range(len(a))] def f(n): t = 2 r = 0 e =  for r in range(len(n)): if 1 & r: t += 13 else: t -= len(n) % 9 u = ord(n[r]) + t e.append(str(u)) return e for i in range(len(flag)): for j in range(len(all)): flag[i] = all[j] out = f(flag) if out[i] == str(k[i]): break print("".join(flag))
This was a good ctf for practising some code review. I also had never played around with an app made with react so that was something I learnt.
I’d like to thank the CyberSpace Kenya team for putting up the challenge as well as ZuluMeats for the prize.
You can find more challenges on their website: https://ctf.cyberspace.co.ke
For any questions you can reach me on Twitter.