I competed in the Hackerone Hacktivity Con CTF this past week, and I really enjoyed my time there. I first have to say thank you to all the challenge devs, and everyone involved in making this event such a success. Even though there were issues with the 3600~ people giving CTFd a hug of death, and the person(s) that decided to be jerks and drop fork bombs on the CTF challenge boxes; they were mitigated extremely well. I absolutely plan on doing this again if it is run next year!
Without further ado, here’s my writeups for all the challenges I found to be interesting!
Ladybug
100
Want to check out the new Ladybug Cartoon? It's still in production, so feel free to send in suggestions!
Taking a look at the website, the links on the right hand have the URL slug match, except for the home
slug, where that should be (in my mind) cartoon
to match the others.
Attempting to fix that, it shows an AssertionError
from the python webserver, conveniently displayed by Werkzeug, which conveniently includes a python shell.
By running a quick test for the flag.txt
file, and reading it via some quick code dumps the flag out.
>>> f = open('flag.txt', 'r')
>>> f.read()
'flag{weurkzerg_the_worst_kind_of_debug}'
flag{weurkzerg_the_worst_kind_of_debug}
Mobile One
50
The one true mobile app.
Download the file below.
Since this was a standard Android app, I did my first quick run of using apktool
to extract the archive into its components.
james@bountyhunting ~/CTF/HacktivityCon2020/mobile_one ls -alh
total 896K
drwxr-xr-x 6 james james 4.0K Jul 29 23:55 .
drwxr-xr-x 14 james james 4.0K Jul 31 14:43 ..
-rw-r--r-- 1 james james 893 Jul 29 17:00 AndroidManifest.xml
-rw-r--r-- 1 james james 1.6K Jul 29 17:00 apktool.yml
-rw-r--r-- 1 james james 862K Jul 29 23:55 app-release.apk
drwxr-xr-x 8 james james 4.0K Jul 29 17:00 kotlin
drwxr-xr-x 3 james james 4.0K Jul 29 17:00 original
drwxr-xr-x 130 james james 4.0K Jul 29 17:00 res
drwxr-xr-x 8 james james 4.0K Jul 29 17:00 smali
From here, I did a quick grep
for the anything matching the flag format, since this was an easy challenge I expected to find it quickly, and I did.
james@bountyhunting ~/CTF/HacktivityCon2020/mobile_one grep 'flag{' -r .
./res/values/strings.xml: <string name="flag">flag{strings_grep_and_more_strings}</string>
flag{strings_grep_and_more_strings}
Pinocchio
100
Pinocchio just made a new app! He says it is very secure... but I think I see his nose growing...
Download the file below.
Knowing this would be a bit more complex than Mobile One, I tossed the APK into Bytecode Viewer, seeing that the FlagActivity
class sent a POST request to http://jh2i.com:50029
with a JSON body of the pin tested. The pin had to be a 4 character number, from 0000 to 9999. 10k possible values meant that I could either make a python script, or do this quickly in Burp.
{"pin": "{PIN NUM}"}
Taking this, and putting it into Burp, I added a list of all the possible values, 0000 to 9999. Realizing this would be a bit excessive, I took a gamble and put only 5000-9999, half the sample size as I was wagering that the number would be in the second half of the valid pins.
Intruder ran for under a minute, and had one successful request, with the pin being 6402
, returning the flag as
flag{lied_about_this_being_secure}
flag{lied_about_this_being_secure}
Just Not Interesting
300
No, seriously, this app is not interesting.
Download the file below.
Starting again with tossing the APK into Bytecode Viewer, I found the function signatures for a Java native function in MainActivity
.
private final native boolean checkPassword(String var1);
private final native boolean checkUsername(String var1);
This means that the actual code was in a native compiled library, not the Java code.
Taking the libnative-lib.so
shared object and putting it into Ghidra showed the two native functions.
The first, checkUsername
was a simple strcmp
of the users input and admin
. Fast enough, no major issue there.
The second, checkPassword
required a bit more work. After cleaning up the decompiled code a bit, I was left with this:
uint Java_com_example_justnotinteresting_MainActivity_checkPassword
(int *param_1,undefined4 param_2,undefined4 param_3)
{
int iVar1;
size_t input_len;
char *__s1;
char *input;
size_t len_check_1;
int len_check_2;
uint len_check_3;
byte *flag_source;
uint i;
int in_GS_OFFSET;
char init_flag [33];
iVar1 = *(int *)(in_GS_OFFSET + 0x14);
memcpy(init_flag,"NOTFLAG(the_fLag_ISN\'T_here!!!!)",0x21);
input_len = __strlen_chk(init_flag,0x21);
__s1 = (char *)calloc(input_len,1);
input = (char *)(**(code **)(*param_1 + 0x2a4))(param_1,param_3,0);
input_len = strlen(input);
len_check_1 = __strlen_chk(init_flag,0x21);
if (input_len == len_check_1) {
len_check_2 = __strlen_chk(init_flag,0x21);
if (len_check_2 != 0) {
i = 0;
flag_source = &DAT_0001084f;
do {
__s1[i] = *flag_source ^ init_flag[i];
i = i + 1;
len_check_3 = __strlen_chk(init_flag,0x21);
flag_source = flag_source + 1;
} while (i < len_check_3);
}
input_len = __strlen_chk(init_flag,0x21);
i = strncmp(__s1,input,input_len);
i = i & 0xffffff00 | (uint)(i == 0);
}
else {
i = 0;
}
if (*(int *)(in_GS_OFFSET + 0x14) == iVar1) {
return i;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
This code takes the string, NOTFLAG(the_fLag_ISN\'T_here!!!!)
and XOR’s each character with the data stored in the DAT_0001084f
global, plus the offset from the beginning of the first string.
Taking both of these, and putting them into some python code, dumps out the flag.
str1 = "NOTFLAG(the_fLag_ISN\'T_here!!!!)"
str2 = "\x28\x23\x35\x21\x37\x2c\x26\x51\x16\x0d\x3a\x3e\x39\x20\x08\x13\x2b\x25\x36\x11\x4e\x3a\x2b\x0d\x17\x17\x16\x55\x48\x4f\x46\x54"
i = 0
for c in str1:
print(chr(ord(c) ^ ord(str2[i])), end='')
i+=1
flag{maybe_a_little_interesting}
flag{maybe_a_little_interesting}
Butterfly
300
I just made an app using a cool mobile development framework. Can you get the flag?
Download the file below.
Quick side tangent, I was the second person to solve this challenge, and I’m really proud of that because of the amount of legwork required to get there. Especially since this challenge only had 7 solves total.
Starting again with tossing the APK into Bytecode Viewer, I quickly realized that this is a Flutter base app. Flutter is a nightmare to reverse, due to the fact that the app logic is not in a JVM based language but instead is compiled inside a native library, and usually encrypted. It’s extracted during runtime, and then run.
Knowing that I would have to load an app into runtime at sometime during this CTF, I had prepared a BlueStacks emulator earlier. After installing the app, I enabled the ADB server for the Bluestacks emulator, allowing me to connect to it from my computer.
PS C:\Users\james\Downloads\platform-tools_r30.0.4-windows\platform-tools> .\adb.exe connect localhost:5555
connected to localhost:5555
PS C:\Users\james\Downloads\platform-tools_r30.0.4-windows\platform-tools> .\adb.exe devices
List of devices attached
localhost:5555 device
PS C:\Users\james\Downloads\platform-tools_r30.0.4-windows\platform-tools> .\adb.exe shell
OnePlus5:/ $
I also set up FRIDA, a reverse engineering toolkit for runtime of apps. By following this guide - https://frida.re/docs/android/ - I found that I needed to be root. Turns out, Bluestacks comes with a handy dandy su
command to elevate to root with no extra rooting needed. This will do for now.
OnePlus5:/ $ id
uid=2000(shell) gid=2000(shell) groups=2000(shell),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc) context=u:r:adbd:s0
OnePlus5:/ $ /system/xbin/bstk/su
OnePlus5:/ # id
uid=0(root) gid=0(root) groups=0(root),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc) context=u:r:adbd:s0
OnePlus5:/ #
With this, I could successfully start the frida-server
for remote memory dumping.
Connecting to the remote device using fridump
, I was able to list the processes running.
$ frida-ps -U
PID Name
---- --------------------------------------
3891 adbd
2265 appstatsd
993 ash
997 ash
1001 ash
1492 audioserver
2037 bstime
1488 bstsvcmgrtest
1493 cameraserver
4175 com.android.chrome
4236 com.android.chrome:privileged_process0
4480 com.android.chrome:sandboxed_process1
4695 com.android.chrome:sandboxed_process5
1852 com.android.inputmethod.latin
1946 com.android.phone
1857 com.android.systemui
4280 com.android.vending
4353 com.android.vending:download_service
1921 com.bluestacks.BstCommandProcessor
2248 com.bluestacks.appguidance
1958 com.bluestacks.appmart
2424 com.bluestacks.home
3464 com.example.butterfly_project
2015 com.google.android.ext.services
2287 com.google.android.gms
1913 com.google.android.gms.persistent
4638 com.google.android.gms.unstable
2217 com.google.process.gapps
1442 debuggerd
1445 debuggerd:signaller
1494 drmserver
4438 frida-server
1503 gatekeeperd
1481 healthd
1 init
1495 installd
1496 keystore
1483 lmkd
3865 logcat
3875 logcat
4440 logcat
1410 logd
1485 logwrapper
2022 logwrapper
2263 logwrapper
1497 media.codec
1499 media.extractor
1498 mediadrmserver
1500 mediaserver
1502 nbinary
1501 netd
1863 sdcard
1484 servicemanager
1489 sh
3897 sh
1486 surfaceflinger
1615 system_server
1005 ueventd
1006 ueventd
1443 vold
1491 zygote
This included the Flutter app: com.example.butterfly_project
Dumping the memory from the app resulted in 464 files, totalling around 500Mb of data.
Running grep with a few flags enabled to search through binary files as normal text, along with some regex matching found the flag.
james@bountyhunting ~/CTF/HacktivityCon2020/butterfly/dump grep -iaoE 'flag{.*' -r .
./0x8bb00000_dump.data:flag{wow_you_really_know_your_apps}
flag{wow_you_really_know_your_apps}