Defcon CTF Quals 2015 - WWTW
03 Jun 2015Solving the Game
Wibbly wobbly timey wimey, or “wwtw” was a two point pwnable from Defcon quals this year. I worked on this challenge with my teammate, @MarvelousBreadchris. Running it right away shows us a little game screen:
You(^V<>) must find your way to the TARDIS(T) by
avoiding the angels(A).
Go through the exits(E) to get to the next room
and continue your search.
But, most importantly, don't blink!
012345678901234567890
00
01
02
03 ^ A
04
05
06 A
07 AA
08 E
09
10
11
12
13
14
15
16
17
18 A
19 A
Your move (w,a,s,d,q): Too slow!
It’s actually a pretty easy game with five levels. Based on previous experiences with games in CTF challenges, I figured the real challenge was two-fold: first automate the game, and then exploit the immediately visible vulnerability. I was correct about the first part, but not entirely so about the second.
Getting the Password
Luckily for me, I have a @MarvelousBreadchris on my team, so I didn’t have to write the game solver. As soon as you solve the game and get to the “tardis” though, you get a password prompt. Getting it wrong causes the game to exit. Here’s where it gets a bit tricky: if you use a debugger and step through the password function one instruction at a time, you might find that you end up with the wrong password.
Basically, the function reads the raw bytes of itself, comparing your input to the printable bytes in the function. The problem only appears when you set a breakpoint on the function to extract the password, and try using that password without a debugger attached. The secret is in how debuggers work. On x86/64 architectures, debuggers place an int3
instruction at the target breakpoint by writing a 0xCC byte. This changes the actual byte that ends up appearing in the function, basically working as a “hidden in plain sight” anti-debugging trick. To get the password, I wrote up an idapython one-liner (0xeb8 is the address of the function):
And we have the password: UeSlhCAGEp
Time Traveling
Once in the “tardis,” you’re given a prompt, but trying to do anything useful doesn’t work:
Welcome to the TARDIS!
Your options are:
1. Turn on the console
2. Leave the TARDIS
Selection: 1
Access denied except between
May 17 2015 23:59:40 GMT and May 18 2015 00:00:00 GMT
Clearly we couldn’t travel to a day before the CTF, so we needed to do something else. Doing some more reversing, I spotted this oddity:
Basically, it does something like this:
... inside the main loop after the game ...
tardis_menu(); // print the menu
// user_input is a 12 byte array of 4 bytes
bzero(user_input, 8); // zero out eight bytes (user_input[0] and user_input[1])
read(stdin, user_input, 9); // read nine bytes
It zeroes out eight bytes of user_input
, but reads in nine. This means the ninth byte is never erased via bzero. Now we just need to figure out what the ninth byte of user_input
is used for. Continuing on with more reversing, I spotted a function, time_vortex_thing
, which is set via alarm()
to execute every two seconds. Here’s the interesting bit:
We can see it’s using user_input[2]
as the file descriptor for read
. For the sake of “brevity”, user_input[2]
actually holds the file descriptor for a loopback socket, which we don’t have access to as an outside attacker. This is unfortunate, because in the second block of the last screenshot, we see that the input on this socket is directly written into current_timestamp
- what the “tardis” uses to confirm the current time.
However, using the fact that we can send in nine bytes and overflow into user_input[2]
, we actually control the file descriptor!! Using the overflow, we can redirect this time traveling bit to read from stdin, instead of the loopback socket! We’re time traveling!
Teleporting
After passing in a timestamp within the accepted range, a third option is unlocked:
3. Dematerialize
After reading this function, it’s pretty clear what we need to do.
(You might need to click the picture to zoom in)
The teleport function reads a pair of coordinates and calls atof()
on it twice, once before the comma, and once after it. After it gets the floating point version of your strings, it does some comparisons (the fstp
, fld
, fucomip
). If your coordinates match the “bad” coordinates, it prints some strange error message about time and space. The issue is with how it’s printed: it calls printf()
with your input (string form) as the first parameter!!!
Exploitation
We now have a controlled format string vulnerability that lets us basically read a limited set of memory, and write anywhere. The reading bit is really important here, because the binary is running both with ASLR and PIE - we don’t know where anything is located before hand. By passing in format specifiers, we can read addresses off the stack, disclosing things such as return addresses (which would tell us where the binary is located in memory), stack cookies (which would let us overwrite the return address on the stack without stack smashing protections stopping us), and more importantly, libc addresses on the stack (which gives us access to the entirety of libc).
My initial version of the exploit involved leaking out an address from the binary, using the controlled format string to write an to write a ROP chain to memory, and then using the format string again to replace a function in the GOT with a stack pivot that lands me in my ROP chain. It was a hueg pain in the butthole, so I decided to use the libc leak instead.
Because there is a strchr
called on your input at every iteration, I decided to overwrite its GOT entry with the libc address of system
. To do that, I used the controlled format string to leak a libc address from the stack. I wasn’t sure what it was the address of at first, but comparing against my local copy of libc, I saw it was _IO_2_1_stdout_
. Because I knew how far _IO_2_1_stdout_
was from the base of libc, I was able to calculate the base, and then I was able to calculate where system
was in relation to that. The issue with this is that this only works for my local copy of libc.so.6
. Luckily, having solved another exploitable challenge, I just grabbed the libc.so.6
from that system and used it to calculate the appropriate offsets.
Actual Exploit
Here’s the final exploit code (adjusted for my VM offsets):