Blog

Hacktivity CTF 2020

Aug 3, 2020 | 7 minutes read

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

Challenge

Ladybug
100
Want to check out the new Ladybug Cartoon? It's still in production, so feel free to send in suggestions!

Solution

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. Home

Attempting to fix that, it shows an AssertionError from the python webserver, conveniently displayed by Werkzeug, which conveniently includes a python shell.

AssertError Console

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

flag{weurkzerg_the_worst_kind_of_debug}

Flag

Mobile One

Challenge

Mobile One
50
The one true mobile app.

Download the file below.

Solution

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

flag{strings_grep_and_more_strings}

Pinnochio

Challenge

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.

Solution

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.

Burp 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

flag{lied_about_this_being_secure}

Just Not Interesting

Challenge

Just Not Interesting
300
No, seriously, this app is not interesting.

Download the file below.

Solution

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.

Ghidra-Global

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

flag{maybe_a_little_interesting}

Butterfly

Challenge

Butterfly
300
I just made an app using a cool mobile development framework. Can you get the flag?

Download the file below.

Solution

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

flag{wow_you_really_know_your_apps}