Writeups by team perfect blue
In this challenge, we are given an online code challenge website where we can compile and run code, and we get the output of how many test cases were passed.
The obvious things such as open/execve and other risky commands were filtered, however we could use syscall
. Using this, we can basically do a open/read to read the flag file.
Since we don't get direct output, but only a boolean array of which testcases passed, we can use this information to extract the flag 3 bits at a time.
The character variables were uninitialized, so you could create the secret toon, then delete it, and the next one would re-use the previous skill.
In this challenge, there is a file called "dataset.txt", which contains 26 lines of tuple pairs, where the first number in each pair is an integer, and the second a float. Going through each line, sorting the tuples by the first number and plotting it, shows that every graph is increasing along the Y-axis, but with tiny glitches/perturbations.
Each graph can very accurately be approximated by a straight line, which is hinted to in the challenge text. We do some quick linear regression of order 1, and recover the slope of each line.
We were given a binary which implemented a custom data exchange protocol. We were also given a pcap which contained a capture of two such transfers.
First it generated around 64 bytes of random data by generating the md5 hash of a random output from rand()
. However, the RNG was seeded using the current time.
First we used hashcat to recover the 4 bytes which were hashed into the random data.
Once we have this, we can use a tool like untwister
to find which seed generated these two random numbers consecutively.
Now that we have the seed and the secret random data, we can just write a script to generate the AES key and decrypt the flag.
Running this, we get the flag:
The object members are still uninitialized. However, the objects are within shared pointers. Creating, choosing, then deleting an object will cause a double free. Using this double free, we can re-use the previous skill by overlapping an object that is allowed skills over the special character object.
The binary uses tar archives to backup and restore backups.
Upload a tar file that creates a symlink to ../lib, then a second one that writes a file in there to overwrite the libutil.so in ../lib/ and get code exec.
There was a SQL injection in the sortName
field. We can use SQLMap to automatically dump this data.
The flag is stored in one of the tables.
We found that it was possible to make a small amount of money using small decimal values during exchanges. Repeating this, it is possible to earn enough money to buy the flag.
You can easily overwrite the return address with values in a 6x6 board. Also there are some values of a function that calls execve("/bin/sh")
.
The function ends with B5A
, so what we need to do is 1. put enough values on the buffer to overwrite ret, 2. put 5A
and XB
to jump to the function.
The flag is SCTF{ch4LL3N63_pwn3d!_Y0u'r3_br347h74K1N6!}
.
When saving notes the was a stack overflow if the username + note was larger than 100 characters, allowing ROP to be used. Since it was running with qemu, the base address of libc was static so a simple system("/bin/sh") gadget chain could be built and run.
This was a misc/coding challenge that we got first blood on.
We are given a python script with many coordinates of "stars," and it creates a picture with these stars. I noticed that the stars are given in 3d dimensions, but are only displayed in X/Y coordinates, and the Z coordinate is unused. This made me guess that all the points made a flag in 3d, but since we were only given a 2d projection we could not see the flag.
There was also this interesting area near the middle of the image:
I ended up plotting all the points in 3d using Matthew's Laboratory (MATLAB) and just dragging the camera around. However, this was not helpful. Then, I remembered the strange points (with radius 0) near the middle of the circle, so I decided to plot only those.
This was very helpful, as after rotating for a little bit I was able to see this:
This is obviously a QR code! With a bit more zooming and rotation, I was able to rotate to an even better angle:
This still didn't scan though, so I changed the points to black squares:
Then filled the non-square area with black and flipped the image horizontally:
This scanned properly from my phone, and we got the flag.
SCTF{MiB_sh0u1d_t@k3_c4re_0f_my_CAt}
This was a forensics challenge that we got first blood on.
We were given an image of a mars rover.
The first thing I tried was opening it in zsteg. This was not very useful. The next thing I tried was opening it in tweakpng, to see the data chunks:
There are so many IDATs, all with different sizes! This is very suspicious. My initial thought was that the flag was encoded in the length fields, but then I noticed something even more interesting: most of the LSBs in the CRC are the same! The ones that are different, are all printable ASCII too:
I quickly wrote a script to get this data:
This prints out the flag.
SCTF{M4rti@n_wi11_b3_bACk_aT_anY_t1M3}
This was a forensics challenge that we got first blood on.
We were given a VirtualBox saved state file (you can check by opening the file, the first bytes are VirtualBox SavedState V2.0
).
I did a quick google search and saw there were past CTF challenges using this format, and found this writeup: https://ox002147.gitlab.io/writeup-bitsctf-for60.html
I also noticed at the end there was a .png file, used for the screen capture to show on virtualbox. After extracting it, we saw that it showed a shell, with ./sav and "Wanna flag?"
Using the tool linked in the writeup (https://www.dropbox.com/sh/vtsk0ji7pqhje42/AABY57lRqinlwZpo8t9zzGYka), I extracted the data from .sav, and searched the largest file for "Wanna flag?", and found a .elf binary.
This was a simple binary that would XOR the flag string with SCTF{
. Scrolling a little bit up in the extracted data from the .sav, we found this string:
After xoring with repeated "SCTF{", it gave the flag.
SCTF{m3m0ry_15_7h3_k3y_n07_70_7h3_p457_bu7_70_7h3_ch4ll3n63!}
The given circuit checks 8 bytes of the input. The checking logic is like:
ror(inp[2], 5) ^ 0x34 == 0x98
inp[3] == 0x67
inp[7] >> 3 == 0xd
, bitcount(inp[7]) == 5
inp[4] - 0xa3 == 0xbe
, inp[4] + 0xa3 == 0x04
~(-inp[6] ^ 0x3) == 0x36
(inp[5] ^ inp[0]) + (inp[0] ^ inp[1]) == 0x6c
, (inp[5] ^ inp[0]) - (inp[0] ^ inp[1]) == 0x0c
RNG(seed=42)[inp[5]] == 0x01
The other part of the circuit is about doing RC4 to print the flag. Because of ambiguity of the condition 3, there are three possible keys:
The right key was 59 69 65 67 61 65 33 6e
, and the flag is SCTF{lOgic_e1em3nt5_maTteRs}
.
We are given a command to run in adb: adb connect adbaby.sstf.site:6666
Upon connecting, we try many commands that normally would work in adb, such as adb shell
and adb pull
. We can see that most adb commands are blocked, and in adb pull
there is a list of blocked words from the path, including ./
, ../
, flag
, data
, and tmp
. This seems to be very locked down, but one of our team members realized that /proc/
was not blocked. Using this, we could access /proc/self/exe
to retrieve the binary on the Android device that was interfacing with our adb.
Inspecting the binary in IDA, we notice a few interesting parts:
It seems that, if we send a command to adb that starts with flag:
and then send a value that fits with the criteria, we will get the flag back. The criteria in particular was sending a password whose MD5 hash began with 0123456 (in its hex digest). Since such custom commands are hard to send in normal adb, we used a python implementation adbutils and modified some commands.
A function was added:
And pull was rewritten:
Then, in python we simply connect to the device, and send any pull request, which gets translated to the get flag. This gives us the flag.
SCTF{Do_U_th1nk_th1s_1s_adb}
The included exe is an installer application, which installs SCTF2021FlagPass.exe
and some required libraries.
Entering the given keys into the application, gives us the response "Key expired". Patching out the logic for the date check, or turning back the time on the current computer, makes the keys valid again, but the flag is not correct:
When reversing the application, we recover the logic for how the keys are verified:
The signature is checked in this code, which uses ECDSA to verify the signature against a hard-coded public key from the NIST P-192 curve. Only the first 6 bytes are actually hashed and signed.
Looking closely at this function, three numbers are hardcoded. X, Y and ??.
And it turns out that the last number is R from the (R,S) pair used in the signature. This means that the keys have been generated using the same nonce, and now it suddenly makes sense why we were given two keys in the challenge text. When given two signatures using the same nonce, but different data has been signed, we can re-arrange the equations a bit and recover the nonce, and thus the private key.
The private key turns out to be 1325031087835349138965290766193329882829064869944584756462
. Now we can sign our own keys, but there's more to be learned from the application.
Another part of the key verification, is to check that
msg[0] ^ msg[7] ^ msg[28] == 0 and msg[1] ^ msg[3] ^ msg[12] == 0
in addition the signature matching. When the key is accepted, the program checks the current time of your computer, and compares it to the epoch stored inside the key. If the current time is greater, it prints "Key expired", but otherwise it pulls the string TSRFHR6JXKTXUL4T4WY4FLPIAEHSXZC7T3FKRGVEVPEVGWBQ6KKQ====
from the process metadata. This string is decoded as base32, and XORed with the sha256-hash of the key (in its base32-decoded form). The result is shown as a message box, and all the keys we generated gave us random garbage. Though, we had generated keys for a few days into the future, or for the maximum epoch etc. Since the XOR-key-verification part includes bytes in the signature, it becomes a 48-bit brute force (with some heavy calculations!) to go through all the possible times, and this is too much.
When looking closely at the epoch time in the keys we already got, they show
Tuesday, May 11, 2021 6:00:00
and Tuesday, June 8, 2021 6:00:00
. These are pretty recent keys, and both of them were at the exact same time of the day. With this information, we can reduce the brute-force a bit by only checking day by day, but it's still a 16-bit brute force per day to find all the matching verification bytes. We now guess that the correct key must be set to expire during (or right after) the CTF, and start brute-forcing.
after a few minutes, this prints the flag:
This challenge is an aarc64 binary implementing a fragmented echoserver. Messages can be sent in fragments up to a size of 0x200. The bug in this challenge is that the size filed for memcpy can be set to a negative number. By experimenting with this, we found that memcpy actually writes out of bounds BEFORE the dest pointer when size is a very large integer (unsigned -0x10 for example). Using this write, we can overwrite the size metadata of the echo data struct on the stack. This lets the server read out of bounds on the stack, giving us the stack canary. However, it seems that aarch64 stores the return pointer on the top of the stack, meaning that we didn't really need the stack cookie leak and the memcpy buffer underflow allows us to overwrite the return address directly. Instead, we can use the infoleak to obtain PIE leak. It turns out the server is also using qemu-aarch64, so PIE addresses are constant, so we don't really need a second stage buffer underflow. The exploit is shown below:
This was a pwn+crypto challenge. Our ultimate code was to generate a auth code which started with "root" and we also had to leak the address of system in libc so that we could get a shell.
The binary also puts the address of the system in the .data section but we can't access it directly.
There are two bugs we exploit:
Since the program also allows us to control the amount of bytes used for the RC4 key schedule, we can implement a byte-by-byte bruteforce to recover the system ptr.
Then we can use the name overflow to replace the RC4 key with a known key so that we can make the auth_code be whatever we want.
Once that is done, we can get shell.
If you see the key share of ClientHello
, it's just the generator point of secp256r1
. Therefore, the pre-master secret will be the X coordinate of the key share of ServerHello
.
Also, we read one of RFC documentations and found this:
Now we can calculate secret values.
To decrypt the stream, we used WireShark. You can set secret values on TLS by using Preferences > Protocols > TLS > (Pre)-Master-Secret log filename
.
To calculate the secrets, we used tls1.3 library.
The log file is here:
After setting up, you can now decrypt packets. If you see the first HTTP packet, there's the flag:
The format of the flag is SCTF{012012012_012012012_012012012}
, which represents a 27-character ternary value. It's converted to long
, and used as a key of this scheme:
The goal is to find a proper key that encrypts "\x7b\xd8\x4f\x97\x31\x75\x68\x8c"
to #!defuse
. So we wrote the solver like this:
We ran the program with a 120-core CPU. The right key is 0x2986e6fbe96
, and the flag is SCTF{101002210_221000220_020220121}
.