Recently, CrowdStrike Intelligence ran a small CTF for about two weeks with twelve challenges spread over a wide selection of categories. I managed to solve all the challenges and got eighth place. The challenges were of very high quality and I thoroughly enjoyed them so I decided to publish my solutions here. This is not a full write-up with a lot of details but more a short summary of my solution to each problem. The challenges were divided into three storylines, “adversaries” with four challenges each and as such I will structure this post in the same way.
Space Jackal
The first adversary we are facing is Space Jackal which seems to really like spaces over tabs (an honourable cause).
The Proclamation
The file is a DOS/MBR boot sector which prints a message when run.
Analyzing the code, we that it xor:s some data in a loop.
Brute forcing the key eventually gives us the flag.
Flag: CS{0rd3r_0f_0x20_b00tl0ad3r}
Matrix
We are given some Python code and an onion address. Visitng the site gives us three ciphertexts all beginning with “259F8D014A44C2BE8F”, i.e. the same 9 bytes.
The code takes a 9 byte key and treats it as a 3x3 matrix. It checks that the matrix is invertible by calculating its determinant and checking that it is 1.
We know from the code that each message is prefixed with “SPACEARMY” before being encrypted. This means that we can set up and solve the matrix equation:
Kenc⋅M=C⇔Kenc=CM−1⇒Kdec=Kenc−1=(CM−1)−1.
Where C and M are created from the ciphertext and plaintext prefixes. Implementing this in Sage and running it on the three ciphertexts gives us the solution:
Here we are given an image of a machine and an address where the same machine is running. In the temp directory we find the file “/tmp/.hax/injector.sh”. The file is an obfuscated shell script which uses ProcFS to resolve a few symbols, insert the resulting addresses into a piece of shellcode and then injects the shellcode into memory. The disassembly of the shellcode looks like this:
This code will hijack free() and if a freed string is on the format cmd{.*}, the contents of it will be passed to system(). We can make a request to the web server on the running machine with our payload in a header which will be executed once the server has finished processing our request:
Flag: CS{fr33_h00k_b4ckd00r}
Tab-Nabbed
We are given an image an address where the image is running. From the image we find that it is running a gitolite git server with one repository: “hashfunctions”. There is also a post-receive hook set up for that repo to run the “detab” program on every modified file. The program converts leading tabs to spaces in files but it has a buffer overflow vulnerability. The program works with a 512 byte buffer which is flushed when it is full. However, the check to determine if it is full only checks for strict equality so by putting a tab in the file when the buffer has for example 510 characters in it the size will jump to 514 and continue overflowing from there. We need to make sure to keep some other local variables valid but other than that there are no protections which means we can directly overwrite the return address with the convenient “print flag” function in the program. To generate the payload file we run the following exploit:
We then check out the repoistory from the server using the key found in the image, add the file, push the changes back to the server and then finally pull the changes back down to bet the output of the post-receive hook to get the flag.
Flag: CS{th3_0ne_4nd_0nly_gith00k}
Protective Pengiun
Adversary number two is the Protective Penguin. Unfortunately, I didn’t really catch the overall theme here.
Portal
We are provided with the code for a web server running a cgi-bin program to authenticate users. The program base64 decodes the username and password into a buffer. The credentials are concatenated with a colon between and compared against entries in a text file. Unfortunately the buffer is too small and we can overflow it. The program has a stack cookie and we have no memory leak but we can overwrite a single pointer pointing to the filename of the users list. Since the file is opened after the credentials are decoded and the buffer overflowed we can replace the path to the user list with a different string. The binary contains the strings “/lib64/ld-linux-x86-64.so.2” and fortunately this file contains strings with a colon in them such as “conflict processing: %s”. The following exploit performs the attack:
Running it gives us the flag: CS{w3b_vPn_h4xx}.
Dactyl’s Tule Box
Here we are given an image and a server where that image is running. We can login in to the server with an SSH key we are given. On the server there is a GTK binary “/usr/local/bin/mapviewer”. We can also find that X forwarding is enabled on the server by looking at the sshd configuration. To be able to run the program at all, we connect to the server with X forwarding on our SSH client:
We are allowed to run this program as root with sudo but for this to work we need to specify the Xauthority:
Every GTK program takes a number of extra command line parameters including one called “–gtk-module” where you can specify extra libraries to load, similar to LD_PRELOAD. We can use this to build a library which will simply open a shell on load and provide it as an argument to the program:
Finally we can check the history to see what the intruder did to access the other server and do the same thing ourselves to get the flag.
Flag: CS{sudo_+_GTK_=_pwn}
Egg Hunt
Again we are given an image and a server running the same image. We can load the snapshot in the image and find that three eBPF programs have been loaded into the kernel. We can dump a disassembly of these programs.
The first programs checks incoming packets to see if they are IP/UDP packets with destination port 1337 and a 34 byte payload
54: (55) if r1 != 0x40 goto pc+236 ; ip version == 4
58: (55) if r1 != 0x11 goto pc+232 ; ip protocol == 0x11 (UDP)
63: (55) if r1 != 0x5 goto pc+227 ; IHL == 5
74: (55) if r1 != 0x3905 goto pc+216 ; port == 1337 (BE)
78: (55) if r1 != 0x2a00 goto pc+212 ; len = 34 (42-8)
It will then check that the packet starts with “fsf”, discard those characters, xor the rest of the payload with 0x66 and prefix them with “$1$”.
92: (55) if r1 != 0x66 goto pc+198
94: (55) if r1 != 0x73 goto pc+196
96: (55) if r1 != 0x66 goto pc+194
This matches the format of a md5_crypt() hash and the other two eBPF programs interact with PAM so we make an educated guess that it replaces the user’s actual password hash with what we provide it in this packet. The following Python code crafts the UDP packet and sends it to the server.
We run it in a loop and try to SSH to the server at the same time giving us the flag.
Flag: CS{ebpf_b4ckd00r_ftw}
Exfiltrat0r
In this challenge we are given a pcap of encrypted traffic and the code of the program used to exfiltrate some files. The program can read the encryption key interactively and has fancy ASCII art letters with ANSI command sequences for color. Each character entereted is reflected back on the network in this ASCII art way which means that each character is represented by a different amount (with a few collisions) of bytes in its artful version. This means that we can use the packet sizes of the responses containing the character echoed back to infer which character was pressed. First we export the packet sizes from the pcap to a text file.
We also export the three encrypted files into separate files. Now we first preprocess the list of sizes to combine any packets that might have been split, then we try character by character and compare against the expected size using the encryption code to see how large of a packet the resulting ASCII art becomes. We try this with different amounts of fixed overhead as well. Eventually this gives us a few number of candidate keys, all variations of the string “my secret key”. Parsing the encrypted data by looking at the code to understand the format we can try each key to try to decrypt the data.
The correct key turns out to be “my_s3cr3t_k3y” and we can decrypt the flag.
Flag: CS{p4ck3t_siz3_sid3_ch4nn3l}
Catapult Spider
The final adversary is Catapult Spider which has a Doge meme theme throughout.
Much Sad
In this challenge we start out with a ransom note pointing to a Doge Coin account.
Apparently there was a much easier way to solve this by instead looking at the email address in the note and going directly to Twitter or Reddit but this is how I solved it.
Flag: CS{shibe_good_boi_doge_to_the_moon}
Very Protocol
In this challenge we are given a binary and a server where it is running. The binary contains NodeJS and some packaged scripts. The script is written using dogescript and implements a server with a custom protocol. By analysing the code we can reverse engineer the protocol and implement our own client in Python:
Running this against the server gives us the flag.
Flag: CS{such_Pr0t0_is_n3tw0RkS_w0W}
Module Wow
This challenge provides us with a program that takes a string as input and uses it as an xor key to decrypt some code and run it. We can solve this similarly to how you would solve regular repeated xor encryption, i.e. by finding patterns in the plaintext. It is a bit more difficult since we are not simply searching for plain English text but x86 machine code which can be a broad range of bytes and still be valid. However, there are some patterns to look for. By guessing that the code starts with a standard function prologue show below and ends with ret we can make some initial progress.
push ebp
mov ebp, esp
sub esp, N
With this we quickly find that the key starts with “CS{cr” (prologue) and that the seventh character must be “p” (ends with ret). Assuming that the key is between 10 and 60 characters we can solve the equation 196≡7(modkeylen) and get 21 and 27 as reasonable candidates. It looks like 27 produces more reasonable output further down the code. From here we continue this iterative process, looking for bytes that results in reasonable looking code until we get the full flag.
Flag: CS{crypt0_an4lys1s_0n_c0d3}
Many Neurotoxin
In this final challenge we are tasked with tricking a neural network to misclassify three cat images as doge images without altering the images too much. I did this by first going through a tutorial in the tensorflow documentation. However this tutorial only concerns getting the classifier to not classify the image as a specific class. I then continued by reading through another article where they talk about how to instead target a specific class. To get the accuracy high enough I had to mix two different loss functions. First I used the softmax_cross_entropy until the accuracy reached 80% and then I switched to using the CategoricalCrossentropy for the last part.
Flag: CS{4tt4cks_0n_n3ur4l_netw0rks}
Conclusion
I had a lot of fun solving the Crowdstrike Adversary Quest challenges and I hope you had some use of this write-up. Thanks again to the Crowdstrike Intelligence team for organizing. If you have any questions or comments, feel free to leave them below or get in touch with me by other means.