How to Win A CTF by Overcomplicating Things
My unnecessary long writeups for Petronas Inter-University CTF 2023
Last updated
My unnecessary long writeups for Petronas Inter-University CTF 2023
Last updated
Let them do all the work.
Act busy by going the long way to solve challenges with unintended solutions.
In the first Android Reversing challenge, we were given the file myapps.apk
, which can be decompiled with apktools
or decompiler.com.
To get the flag the easy way, perform a simple recursive grep.
To make my life easier, I decided to install the package on a Pixel 5 emulator using Android Studio to interact with the Android application.
Overall, the application presents an event handler that loads the flag in the background when the button is pressed.
Taking a look at the source code, the flag is used in the displayFlagStatus
method to calculate its length, but was never printed out on the application interface.
A novel method to leak the flag is by patching the application's Dalvik bytecode. The assembly representation of displayFlagStatus
is a relatively big chunk, but our main interest is only .param p1
:
To retrieve the flag during runtime, we'll make use of the PrintStream
object from Java's IO class. The remaining portion of the code can then be discarded safely.
Once the changes have been made, we can recompile the application using APKLab, and re-install it on the emulator with adb.
Up until this point, we're still missing 1 crucial step. Think about it; we are printing the flag to the console, but there's no direct access to the terminal via the application's interface. To overcome this, we can use adb logcat
to dump system logs, which include messages written from our application's code.
This is actually one of the common pitfalls in mobile development dubbed "Insecure Logging", whereby sensitive information logged in a staging environment is shipped to production.
Flag: {PETGRAD2023}_S1mPl3Fl4g
We have yet another Android Reversing challenge with the given file dynamic.apk
. As usual, we'll decompile the application to look at some point of interest.
It appears that the flag is constructed part-by-part with a string builder during runtime, with part 3 derived from unknown native libraries.
Part 1, 2, and 4 of the flag is visible directly from LiveLiterals$MainActivityKt.java
:
Since part 3 is derived from native libraries, a simple strings command on the decompiled library should reveal it. Putting everything together would then give us the complete flag.
After installation, the original APK file appears to be corrupted, which results in runtime error. Not entirely sure if this is by design or accidental, but we'll try to repair the APK so that it actually runs.
After several troubleshooting attempts, the following modifications on AndroidManifest.xml
are required:
android:extractNativeLibs="false"
-> android:extractNativeLibs="true"
android:theme="@style/Theme.DynamicFlagChallenge"
-> android:theme="@style/Theme.AppCompat.Light"
Furthermore, the application seems to be loading the native library via the wrong file name. To fix this, I simply renamed libdynamicflagchallenge.so
to libnativeFlagPart.so
and recompile with APKLab.
After getting the application to run, we are greeted with a similar interface as the previous challenge.
The popup message Nice try! But you'll have to dig deeper
is implemented through the Toast
class whenever the button is pressed. Similar to the previous challenge, we'll tackle this challenge by patching Dalvik bytecode.
This time around, instead of printing the flag to a console terminal, we'll make use of the existing Toast
class for the same purpose. The patched code is going to be slightly lengthy as we need to retrieve all 4 parts of the flags and concatenate them together to form the final flag.
Recompile the application and press the button to get the final flag!
Flag: petgrad2023{Qu@ntum_N3xu$_C0d3br3@k3r}
For this challenge, we were given an Atmel AVR firmware runningoutoftime2.hex
in the Intel Hex format.
The aim is to derive the 5-digit PIN by any means and unlock the physical safe located in the middle of the room.
To emulate the firmware logic, I opted to use the following simavr components:
simduino to boot start the Arduino emulator.
picocom for serial input.
avr-gdb for debugging.
The physical safe itself has a lockout policy to prevent brute forcing during the CTF. However, the firmware binary does not contain this constraint when emulated with simavr. As shown above, simduino exposes /dev/pts/3
for serial input and /dev/pts/4
for serial output. This presents a perfect opportunity for us to brute force the PIN as there are only 99999 combinations to try.
We can automate this whole process using input & output redirection magic in bash.
The AVR firmware provided initially did not contain any symbols, which makes debugging a little difficult. Near the end of the CTF, the author released the original ELF file that contains all the debugging symbols.
Disassembling dbg.main
under Radare2 reveals an interesting loop of comparison statements where our input PIN is checked. Since the correct PIN is loaded into a couple of registers, a simple registers dump should give us the answer.
Enable OPCODE description with
e asm.describe = true
in Radare2.
Using GDB, place a breakpoint on the first brne
instruction and dump all registers value with info registers
. In this case, the correct PIN for each position is stored on the lower byte region; whereas our input is stored on the higher byte region.
PIN: 89076
This challenge is worth 700 points, making it the highest-score challenge (but not necessarily the hardest) throughout the entire CTF. To start off, we were given a relatively small packet capture file blackcube.pcap
, containing only 295 packets to analyze.
Initial analysis reveals several endpoints /admin
, /menu
, /dashboard
made to the web server on port 5000, but nothing too interesting, except for packet 205, which reveals a suspicious base64 encoded string.
Decoding the string gives us petronasgradctf:Whoistheg0dofCtf
that looks like a set of credentials we could use somewhere else.
Looking at the list of network access points, we were able to authenticate to a hidden network using the credentials above.
Later on, I was stuck at this challenge until a dead giveaway hint was released afterward.
The hint combined with a bunch of TCP retransmission confirmed that there's something to do with Port Knocking. Coincidentally, a topic that I've shared previously for a sharing session.
Now we'll have to find out the knock sequence by figuring out the timeline where the web server responded to our request on port 5000 after the TCP retransmission stream ends.
Sending the knock sequence is pretty straightforward. The flag -d 500
is used here to introduce delay between knocks, just in case the server receives them in the wrong order.
Navigating to port 5000, we can now access the web server which wasn't possible before.
Recall that the endpoint upload-page
was found during PCAP analysis, and the subtle hints of TAR file made it obvious that we needed to exploit a TAR Wildcard Injection vulnerability. (From OSCP lol, which I took just a week before the CTF)
Prepare the payload as follows:
Finally, upload the tar file to receive the flag.
Flag: petgrad2023{7h3_B1@ck_cu83}
Position | Input | Correct PIN |
---|---|---|
Pos 1 (r18-r19)
5
8
Pos 2 (r20-r21)
4
9
Pos 3 (r22-r23)
3
0
Pos 4 (r30-r31)
2
7
Pos 5 (r26-r27)
1
6