Trail of Bits released a number of CTF challeleges on Github.
This post is about the
easy32 binary exploitation challenge.
easy32 challenge is found in the
ctf/exploits/binary1_workshop/easy/ directory. It contains a binary (
easy32), source code (
EasyServer.c) and a makefile.
From reading the source we can quickly see that it will create a socket and listen on port 12346 on all interfaces. When a client connects they are presented with a menu with 3 choices. All 3 choices lead to similar functions which each read 40 bytes from the socket into a 32 byte stack buffer. The objective of this challenge seems to be to exploit this buffer overflow to alter other variables on the stack which will cause the code to enter a conditional block it would not otherwise execute and set a global variable. When all 3 global varibales are set the key is written out to the socket and the challenge is complete.
Here is the beginning of the
Before each 32 byte stack buffer is a 4 byte integer. Because of the way the stack grows downwards towards lower addresses, writing to byte 33 of of the buffer (
buf + 32) will write into the 4 byte variable before it on the stack.
+-----------------------------+ | sym.pokemans stack frame | +-----------------------------+ | 0x9000 (ebp) | ... | | ... | ... | | 0x8FF7 | pikachy + 3 | | 0x8FF6 | pikachy + 2 | | 0x8FF5 | pikachy + 1 | | 0x8FF4 | pikachy + 0 | | 0x8FF3 | buf + 31 | | 0x8FF2 | buf + 30 | | 0x8FF1 | buf + 29 | | 0x8FF0 | buf + 28 | | 0x8FEF | buf + 27 | | 0x8FEE | buf + 26 | | 0x8FED | buf + 25 | | 0x8FEC | buf + 24 | | 0x8FEB | buf + 23 | | 0x8FEA | buf + 22 | | 0x8FE9 | buf + 21 | | 0x8FE8 | buf + 20 | | 0x8FE7 | buf + 19 | | 0x8FE6 | buf + 18 | | 0x8FE5 | buf + 17 | | 0x8FE4 | buf + 16 | | 0x8FE3 | buf + 15 | | 0x8FE2 | buf + 14 | | 0x8FE1 | buf + 13 | | 0x8FE0 | buf + 12 | | 0x8FDF | buf + 11 | | 0x8FDE | buf + 10 | | 0x8FDD | buf + 09 | | 0x8FDC | buf + 08 | | 0x8FDB | buf + 07 | | 0x8FDA | buf + 06 | | 0x8FD9 | buf + 05 | | 0x8FD8 | buf + 04 | | 0x8FD7 | buf + 03 | | 0x8FD6 | buf + 02 | | 0x8FD5 | buf + 01 | | 0x8FD4 (esp) | buf + 00 | +-----------------------------+
The conditional block we want to enter checks the value of these variables against a hardcoded value.
We simply need to ensure that bytes 33-36 of the data our client sends to the server through the socket correspond to the hardcoded values (
Sounds good in theory, but if things don’t work out right away we may quickly end up trying different variations of the input and generally floundering towards a solution which is not a productive strategy for more complex projects. Even though this project is trivial, lets use a debugger to help us understand what happens at runtime and guide us towards a solution in a decisive manner. Specifically we want r2 to breakpoint before the conditional block and print out the value of the local variable we are trying to overflow (
pikachy). That way we can get immediate feedback on how our client input affects the server.
I’m going to write this script in Ruby using the
First we need to load the binary into r2 and then reopen it in debug mode (
The easy32 server works by listening for a new connection and then spawning a new thread. The new thread is the one which will read data from the client so we need to set our first breakpoint at a point on the main thread after the child thread has been created. The
waitpid function is a good candidate.
Next we need to find the pid of the child thread and attach the debugger to it. This may not be the most elegant code for achieving this.
Now we need to set a breakpoint on one of the compare instructions. We’ll choose number 1, the
pokemans function. It would be simpler to manually disassemble the function with r2 and then copy and paste the correct address into our script. However, this is a good opportunity to demonstrate how to use the r2 search functionality.
Finally we can print out the value of our (hopefully) overflowed local variable which has been stored in
Here is an example session with netcat.
† nc 127.0.0.1 12346 Do you want to be a? 1.) Pokemon Master 2.) Elite Hacker 3.) The Batman 1 So you want to be the best there ever was? aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcde Well then go away
And the output of out debugger script.
† ruby easy32_dbg.rb [+] Waiting for connection on port 12346 [+] Attaching to child 3870 [+] Setting breakpoint on compare instruction at 0x80489d4 [+] Continuing child 3870 [+] Hit breakpoint! eax = 0x65646362 [+] Continuing child 3870
We can see that Endieness causes our input 0x62,0x63,0x64,0x65 to become 0x65646362 when interpreted as a 4 byte integer. From reading the server source code we can see that:
To write our exploit script we will drive an external netcat process using Ruby’s built in
expect functionality (note that mixing
r2 pipe in the same script won’t work because they will fight for control over the target process’s IO.
From here we can get data out of netcat by calling
expect on the
master object and give data to netcat by calling
write on the
And so on for the other 2 functions. Here is example output:
† ruby easy32_exp.rb [+] Waiting for menu... ...ok [+] selecting 1.) Pokemon Master [+] Waiting for menu... ...ok [+] selecting 2.) Elite Hacker [+] Waiting for menu... ...ok [+] selecting 3.) The Batman [+] Trying to read key from socket... Congratulations on your memory coruptions. It only gets harder from here. [+] kthxbai
Here is the full exploit script.
Here is the full r2 debugger script.