The Blue Frost Security Challenge: An Exploitation Journey for Fun and Free DrinksAugust 10, 2017 | Jasiel Spelman
In a departure from the usual ZDI blogs about software actively in use, I'm going to talk about exploiting a fun challenge from Blue Frost Security. On August 2nd, Blue Frost Security released a blog post stating that they were giving away seven tickets to the Ekoparty Security Conference in Argentina. In order to get the tickets (and free whiskey bonuses), you had to complete an exploitation challenge and send them the solution. They provided a 64-bit PE binary with a simple stack-based buffer overflow. The goal is to run 'calc.exe' on Windows 7, Windows 8.1, or Windows 10. Additionally there was an optional goal of having an exploit that functions on all three, as well as another optional goal of achieving continuation and leaving the service in a usable state after exploitation.
I completely missed the announcement, but my coworker Abdul-Aziz Hariri didn't. As always, an offhand comment he made piqued my interest and I had to exploit it. I ended up being the fourth person to submit a successful exploit, though I did so without continuation. Here’s the exploit in action:
Getting there was a fun break from what I usually work on. I used to play wargames and partake in CTFs, but haven’t as much recently, so it was nice to be able to do something reminiscent of that activity. The buffer overflow is pretty evident when you look at the binary, but let's look at snippets of everything we'll need to do to reach it.
After accepting a new connection, up to 4096 bytes are read into a buffer on the stack.
As long as the call to recv didn't return an error, it'll then call a function using the number of bytes received and the buffer as arguments.
Within the function a string comparison is done against the string “Hello” however there's a minor issue here. The length used in the call to strncmp is the length of “Hello” plus the NULL terminator. The number of bytes we sent isn't actually used in the check at all. This means that as long as we send “Hello” followed by a NULL in that initial recv call, that we can place 4090 arbitrary bytes on the stack.
Once we've passed the string comparison check, the code flow returns to the main function before descending into a new function.
This function starts off by reading four bytes from the client. If there's any issue in reading four bytes, or if less than four bytes were sent, an error path will be taken and the function will exit early.
If we've made it this far, then the lower two bytes received will be treated as an unsigned 16-bit integer. This integer will be used to allocate a buffer, read that many bytes into it, memcpy the entire buffer onto the stack, and then free the heap buffer.
If we look at the stack buffer that gets written into, we'll see it is a buffer of 256 bytes.
Although we can write to the stack, there is a stack cookie adjacent to the stack buffer we overflow. We need to investigate further to see how we can get around that protection.
Looking at the next section, we see something rather strange. It takes the upper half of the four bytes received at the beginning of this function, ANDs it with 0xff, then uses it as an index in what appears to be a string consisting solely of the letter 'A'. After this value is saved, much to our benefit, it is called!
So far, we can call 0x4141414141414141, which isn't useful as there doesn't appear to be an exception handler that does something interesting. As such, we'll need to continue further.
If we look into the string, two things stand out. The one you see first will vary based on the disassembler you use, which for me was IDA, and the second thing we'll have to do some work to see. There is a very nice looking pointer to a function IDA has identified as system that is 2048 bytes into the buffer. Unfortunately, this is a red herring. An index of 0xff will only get us 2040 bytes into the buffer so we are still stuck with only being able to call 0x4141414141414141.
In an attempt to be helpful, IDA unintentionally hides part of what we are looking for. There is a NULL terminator on the first part of the ‘A’s at 816 bytes into the string. If we instead remove the NULL from the string and instead treat it as a QWORD containing the adjacent bytes, we see that it's actually a pointer to a function.
At 0x330 bytes into the buffer, this function is well within what we are able to reference. Using an index of 0x66 would result in calling this function with our overflown stack buffer as the sole argument.
There isn't much to the function. It reads a QWORD from the input argument, calls IsBadReadPtr, and returns the result. This is exactly what we want though. The IsBadReadPtr function expects to handle invalid pointers so we can pass it any value to be able to continue execution.
We've now survived the call and are almost to the tail end of the function. We still need to find a way of leaking data back, but thankfully, that is now right in front of us. There is a call to send that sends the contents of the destination stack buffer back to us. It sends back one more byte than the number received – which is important. We can now leak a single byte of the stack.
On the face of it, that may not seem very useful. However, this is a network service, and this service is single-threaded. If we make multiple requests, they'll all occur with the same stack addresses and thus with the same cookie value. In our malicious client, we can send 256 bytes, read the first byte of the stack cookie, append it to our buffer, then send 257 bytes to read the second byte of the stack cookie. We can repeat this process over and over again until we've read as many bytes as we like.
Since we can read values off the stack, we can also grab pointers off the stack and use them to calculate the base of the binary image as well as the value of the stack pointer. We can then use the buffer overflow to modify the saved return pointer without modifying the stack cookie to then execute an arbitrary function instead.
We have a few options as to how we manage to execute calc. We have the base address of the image and can calculate the offset to the system function. We also have the playground of 4090 bytes from earlier that we can use to our advantage. For the exploit I submitted, I ended up using the buffer overflow to land at a ROP gadget that added 0x88 to RSP, then returned into the 4090-byte buffer. From there, I landed in the middle of the vulnerable function, using the dynamic call to my advantage and had it call system for me with a pointer to the string “calc.exe\x00” as the argument.
My boss Brian gave me some grief for _only_ getting code execution, so I did end up getting continuation. To do so, I used the 4090-byte buffer as a place to store a copy of my desired stack layout. With the buffer overflow, I then used a few ROP gadgets to call memcpy. The idea was to copy my desired stack layout to a lower address, and then use a POP RSP gadget to return into it. At that point, execution can proceed as normal. The only extra detail I had to manage was a socket descriptor referenced by the main function that I was trashing. To handle this, I read a few more QWORDs during the information gathering stage and then made sure to write it back as part of the memcpy call.
If you’re interested in checking out the details for the full exploit, I’ve made it available here, and the target application from the contest is found here. Regardless of the promised reward, I found this to be a fun an interesting challenge. It also shows the basic process exploit writers use as they probe an executable. Whether BFS follows through with its whiskey promises remains to be seen, but watching calc pop is always worth raising a glass.