It’s been a while since my last post. I’d say I was busy, but the truth is that I feel like I made the last long post a few weeks ago. I’ll hopefully re-adjust my perception of time and revive the blog - we’ll see how that goes.
Also, I was asked to be a mentor for this season of UNbreakable! I had the chance to hold a 2h presentation, write learning materials, create challenges, and answer questions from participants. I also had access to the challenges repositories, so for some challenges I’ll just present the author’s solution.
After downloading and analyzing the given binary, we can see that it is a 64-bit linux executable:
I found no way of controlling the message that will be encrypted, so the only way of solving this challenge is through reverse engineering. We are going to use IDA Pro to read the binary’s assembly code. The program wasn’t stripped, so we can see the names of the functions as the challenge author put them:
The main function is not very interesting - it pushes ‘This_is_message_from_space!’ on the stack, uses strlen on it, allocates some space for the encrypted string, calls encrypt_function and then prints the message we saw above. The ‘encrypt_function’, on the other hand, is what we are interested in. Here’s the decompiled assembly code of the function:
The Graph view is, al always, very helpful to understand what encrypt_function does:
The first few lines set up the stack. The execution is then redirected to the beginning of the loop main:
The loop takes each character of the provided string and uses sprintf(“%03o”) to put it into the memory area that holds the encrypted string. In this case, “%03o” is known as a format string - it changes the input in a predictable way. A look on printf’s man page (printf also supports format strings) will reveal that “%03o” converts the character to octal and pads the resulting number with zeroes until the string reaches a length of 3.
The last few lines of the loop move the next byte of the plaintext into eax and test it against 0, which is a NULL byte. In other words, the loop will finish once all the characters of the plaintext were processed.
Since the last lines of the function just set up the stack for returning to the caller, we can determine that the ‘encrypt_function’ just converts the ascii string to octal. The following python script will decode the flag:
In case you’re wondering, here’s the source code of the binary:
Initial analysis of the given file does not seem to reveal its type:
To be honest, I’m not sure if I could have solved this challenge. In order to get the flag, you need to speculate that the original file’s name is utmp - find more about /var/run/utmp here. Having this information and the article, finding the IP address is simple:
Since each line of the output contains an IP address, we can use the ‘cut’ program to get all IP addresses and then print each unique address once. As you can see in the output above, there are only two addresses: 0.0.0.0 and 220.127.116.11. The first one means any interface (it’s like 127.0.0.1 - we can ignore it), so the second one must belong to the attacker.
The given file seems to be an executable that asks for a password:
Let’s open the binary in IDA Pro. Unfortunately, not all functions have a clear name, meaning that the binary might have been stripped of symbols. However, IDA automatically identifies the main function, which looks like this:
The function prints a string, reads the user’s input, calls strlen and two other functions (sub_1375 and sub_1195) to alter it and then compares the result with a hardcoded value, 9094929R948S0N940. Let’s start by analyzing sub_1375:
You should recognize the loop from the reversing challenge explained above. It takes each character of the input string and processes it using the following code:
The code above calls sprintf using the string "%02X" as the ‘format’ parameter. This will take the character that is being processed, convert it to hex, and pad it with zeroes until it reaches a length of 2. Basically, this function takes a string and converts it to hex. Let’s now take a look at the other function, sub_1195.
The function has a lot of branches (which translate to ifs in higher-level languages such as C). In fact, sub_1195 is a simple implementation of the ROT13 encoding algorithm.
Knowing how the input is being transformed before it is compared to the hardcoded string, we can create a solve script that recovers the original password:
Note: I encountered a pitfall while doing this challenge. While rot13(rot13(character)) = character is true for bot lowercase and uppercase characters, it’s not true for digits (the alphabet length is len('0123456789') = 10). This means that the decrypt function needs to substract 13 from all digits instead of adding it, as adding would result in a value different to the initial one.
Running the above script will print the correct input, gaina_zapacita.
Note 2: The printed text is full of references to Counter Strike: Global Offensive, but it’s written in romanian.
For the curious ones, here’s the original source code of the binary:
The provided pcap file contains packets that use the ‘USB’ and ‘USBMS’ packets. This is a ‘standard’ CTF challenge - we need to extract the data that was sent between the two communicating devices.
Packets contain a data section - the first logical step is to find a way to extract the data in an easily-parsable format. We can do this using the following command (taken from the author’s writeup):
The ‘tshark’ pogram extracts the ‘capdata’ field from all the packets loated in the task.pcap file. The output is then piped to ‘grep’, which filters out all empty lines (if a packet doesn’t contain a capdata field, tshark will just print an empty line). Finally, all ‘empty’ values are filtered out and the output is saved to a file called ‘data.txt’. If you’re having a difficult time understanding what a part of the bash one-liner does, I suggest running the commands separately and seeing how the output is affected.
The script below was also taken from the chall author’s writeup. It can also be found here:
The device was an USB keyboard; the script just translates ‘opcodes’ into letters. The output is ‘Yu=6SD6mvD9dU!9B’. Using ‘binwalk’ on the packet capture file reveals an archive that can be extraced by using the string we found earlier as password:
I love downloading huge files from the internet! The provided 1.4G file was a memory dump. As explained in my bootcamp presentation, we can use volatility to parse it. The first step is to determine the profile of the memory dump:
As always, the first action we make is to look for interesting files. We can see a .kdbx file, which normally stores passwords and other secrets:
We can extract the files using the ‘dumpfiles’ command:
The password database file is password-protected, so we must keep searching. Eventually, we’ll stumble upon another interesting file, SuperSecretFile.txt:
We can get the flag by importing the .kdbx file into keepass and providing the newly-found password:
This time we are provided with 2 files: flag.enc and capture.pcapng. Since the ‘.enc’ extension suggests that the former is encrypted (and the file contains non-readable characters), we can safely assume that we need to analyze capture.pcapng first.
After opening the file in WireShark and analyzing it, we find an interesting HTTP request:
The data returned by the server suggests that the file content is not entirely readable. After saving it (‘Show and save data as: Raw’; save to ‘peanutcrypt_raw’), we can run ‘strings’ to get a better idea of what the data is:
As you can see on the last line of output, the file contains a string of value “main.py”. This suggests that peanutcrypt is a python compiled file. Before proceeding further, I need to mention that the magic bytes of a ‘.pyc’ file often change for each version of python. This means that there are two ways of splitting the original ‘peanutcrypt’ binary from the HTTP response: either do it manually or find the python version that has the same ‘.pyc’ file header. I went with the latter and discovered that the binary is a python3.8 .pyc file. The script below should extract the original .pyc WHEN RUN USING PYTHON3.8:
Thankfully, the source code of .pyc files can usually be recovered. I used ‘uncompyle6’ to do that (remember, the program was written in python3.8!):
The ransomware encrypts files using AES. We can also see that the key and iv and sent to the attacker’s server via TCP on port 31337. The data being communicated is XORed with ‘super_secret_encoding_key’, so we can recover it if we find the packets in WireShark:
Only one TCP connection was made to a host’s port 31337, so we can safely assume that it contains the encrypted key and iv. The following python script can recover the flag:
Accessing the provided website returns the following response:
I’ve seen this challenge before, but the fact that you can achieve remote code execution using preg_replace still amazes me. The payload below reads the flag; refer to this article for a more in-depth explaination.
The provided site reads “Pingster - Down just for me?”. Since it asks us to enter a domain, we can just enter one that we control (I used ngrok to ‘borrow’ an URL accesible from anywhere on the internet). Here’s the request:
The “User-Agent” contains “jsdom/16.5.3” - this hints that the backend uses jsdom to make a request. Searching Google for “jsdom escape” returns a link to this GitHub issue, which thankfully provides a PoC.
The final payload, heavily inspired by the creator’s writeup:
The server seems not to allow ‘simple’ page redirection, so an iframe whose source would change needed to be used. Also, the setTimeout function ensures that the data exfiltration is attempted 2 seconds after the page loads.
This was one of my challnenges. Let’s connect to the provided address and test out all options:
It turned out that this challenge was not very easy. The secret to solve this challenge is to notice the formula used to generate the signature:
Some hashing algorithms, including md5, are vulnerable to length extension attacks. The concept is simple: if I know the hash of a string (let’s call the string s), I can compute the hash of p, where p is s + random_looking_data + a_string_that_i_control. In other words, we can build new tickets by using the data provided in the free ticket (by adding, for example, ‘; cat flag.txt’ to s, which is ‘whoami’ in our case). I highly recoomend reading this Wikipedia page before looking at my solve script:
I used HashPump’s python library, haspumpy, to forge a new signature.
After downloading the binary from the challenge age, we cand try to figre out what the binary does by running it:
Of course, merely running the application won’t reveal teh vulnerability (although experienced CTF players might have already spotted it). Since the binary is not stripped, we can open it in IDA Pro. Thankfully, the main function is not very long:
In the code above, v6 is the health that we want to bring down to 0. There’s no way of doing that in 3 hits, so the game should be impossible to win. However, notice that we can use the 3rd option how many times we want. The third option adds 1999999 to the int variable that holds the boss health - if we call it enough times, we might trigger an integer overflow and the value of the variable will turn negative. Let’s try that:
Note: Still don’t understand what we just did? Think of it this way: C and C++, like many other programming languages, have different variable types (int, unisgned int, short int, long long, etc). Each type stores the variable’s value in memory using a known number of bits. This means that an int (or any other variable type that stores a number) has a maximum value that it cannot exceed - for C integers, that value is INT_MAX = 2147483647. If an integer that stores the INT_MAX value is incremented, the resulting value will be read as INT_MIN = -INT_MAX = -2147483647.
This challenge is much easier to solve using Burp suite. The first level has am ‘Order now!’ button, so it would be logical to just select a ‘Floppy Flag’. The resulting page contains the first part of the flag along with a link to the next level:
The checkbox near ‘flag’ is disabled - we need to find a way to order a flag. We can start by inspecting the page source:
We could delete the ‘disabled’ keyword using inspect element or use Burp. If we choose the latter, we need to order something like a calky-coffee and modify the order using Burp before it reaches the server. This is the original request:
We only need to replace ‘chalky-coffee=on’ with ‘flag=on’, so the modified request will be very similar:
After receiving and saving the 2nd part of the flag, we can click on the link that is going to take us to the next level:
The page looks different, but the idea remains the same. We order any dish (I recommend Pensive Profiterol in this case - it sounds tastier than the others) and modify the request in Burp:
Here’s the next level:
However, we cannot directly order any dish - we first need to get a ticket and then get our order using the ticket:
To win this level, we just need to order something, get a valid ticket and modify the ticket to say ‘flag’ instead of the dish we ordered:
The last level is a little bit harder than the previous ones:
The solution requires a little bit of creativity. Since all tickets are signed, we won’t be able to simply modify the items we ordered and get the flag. Also, a flag can’t be ordered by modifying an id from the first page. However, we can observe how the tickets are made: the ‘:’ character acts as a delimiter, the first word is always ‘ticket’, followed by the name we entered, the ordered dishes, and a signature.
A good hacker would now ask himself a question: What if our name contained ‘:’? The ticket encoding algorithm might encode the character, but there are also chances that the developer never thought of checking that since actual names don’t contain ‘:’. Indeed, the name ‘yaku:flag’ would generate the following ticket:
I also needed to order trupple’s famous truffles. After sending the ticket, the last part of the flag is revealed.
This was one of the hardest (and most interesting!) challenges. We are given two files, the binary that is running on a remote server and the libc library that it’s using (the latter is going to come in handy later). Here’s the relevant code that IDA Pro managed to assemble:
The vulnerability lies in the setValue function: after reading the column, line, and value, the program does not check whether the line and column ar less than 10 and greater than or equal to 0. In other words, if we input 0, 100, 1337, the program would run matrix = 1337. This out-of-bounds write can allow us to rewrite any data on the stack, including the return address. This means that we can theoretically redirect the execution flow to any address we want.
A good idea would involve calling libc’s system function. Since we need to simulate a line of code that looks like system("/bin/sh"), there are a few things we need to do:
Align the stack. Sicne we’re overwriting a return address, the stack might not be aligned. This can be solved by just calling a “ret” instruction.
Assign “rdi” to an address that points to “/bin/sh”. In Linux, the arguments of functions are passed via the RDI, RSI, RDX, RCX, R8, and R9 registers. Any additional argument will be pushed onto the stack. Since we want the argument of the system function to be “/bin/sh”, we need to assign RDI to an address that points to “/bin/sh”. Libc contains a few “/bin/sh” strings and pwntools will make it very easy for us to find them.
Call system. We can find the function’s address by adding the base address of libc to the function’s offset in the library. Once it gets called, we should have a shell on the remote server.
Also, PIE is enabled and the executable’s libraries are loaded at a random memory address. This means that we need to find a way of finding libc’s base address. Luckily for us, if we print the matrix before any value is initialized, we’ll see some values that were at some point on the stack (this is why your computer science teacher bugged you not to let variables uninitialised inside functions - they get assigned with values from rbp-offset, which are not always 0). We can use this ‘leak’ to find the address of libc - believe it or not, the fourth line of the matrix contains a libc address.
Here’s the challenge author’s solve script (in wich I added a few comments):
I don’t think the binary can be called ‘source code’, but here’s what IDA Pro managed to recover:
The ‘gets’ function is known to be insecure and cause buffer overflows. Aditionally, the ‘s1’ variable is declared after ‘v1’ (closer to RBP), meaning that we can overwrite it. Its offset is rbp - 0x50 - (rbp - 0x4) = 0x4c. Here’s the solve script:
The provided zip file contains a PNG image that seems to contain random colors. However, analyzing the image with stegsolve reveals that a red plane looks like a readable QR code:
The same goes for green plane 0 and blue plane 0. We can rebuild the original QR code with the following script:
The output image is saved in ‘qr.png’, which looks like this:
Reading the code with any tool gives us the flag.
Note: Apparently runnins strings image.png will reveal a script very similar to mine that can build the QR code. However, I was not able to read the script’s output, so I suppose we still need to use the ‘mask’ used in my original script.
The given address host a website. To solve this challenge, we need to notice to things: the name of the challenge (“yaml” in reverse) and that the form’s action is set to Servlet, which means that our input will be sent to ‘/Servlet’.
The first result of a Google search for “yaml payloads” reveals this repository, which thankfully also contains instructions for exploiting a vulnerable application. The solution involves the following steps:
First, we need clone the repository with git clone https://github.com/artsploit/yaml-payload.git and set our payload in src/artsploit/AwesomeScriptEngineFactory.java:
Notice that I used ngrok to get a public URL. To exploit the application, we just need to host a server on the port ngrok connects to (run python -m http.server PORT in the src directory) and to paste the following payload on the website:
The flag can be found in our web server’s access logs.