Source room:
https://tryhackme.com/room/bof1
In this task, our objective is to overwrite the normal flow of the program so that it jumps to the special() function. This function is otherwise unreachable by any program logic.
After we SSH into the machine and CD to the “overflow-2” folder, we find the func-pointer binary and the C source code file. Note that there is a second function, other(), that is also unreachable.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
void special()
{
printf("this is the special function\\n");
printf("you did this, friend!\\n");
}
void normal()
{
printf("this is the normal function\\n");
}
void other()
{
printf("why is this here?");
}
int main(int argc, char **argv)
{
volatile int (*new_ptr) () = normal;
char buffer[14];
gets(buffer);
new_ptr();
}
This was my first time using gdb so there was a bit of a learning curve. Thanks to bobloblaw321 and l1ge for their writeups that helped me figure this out. I ran gdb func-pointer
to load into the debugger. Because of the order in which Unix-based systems allocate memory, we need to unset some environment variables in order to use the same memory addresses inside and outside gdb. We need to unset env LINES
and unset env COLUMNS
within gdb with this command:
set exec-wrapper env -u LINES -u COLUMNS
Now that gdb is prepped, we can start trying to crash the program. Entering run
, we run the binary, enter some basic input into the program and hit enter. The program acknowledges it and exits, as we expect. The length of the “qwertyuiop” input we entered isn’t long enough to overflow the memory allocated to it so we need to try again.
According to the source code, the length of the buffer[] array is 14 so we know how many characters we should be able to input before causing a fault. Let’s try sending 13 A characters to the program.
Just what we expected. Adding one more A to make 14 characters results in this:
What happened? I thought the array was supposed to have a length of 14? As it turns out, in C, a string is terminated with a null character. This makes the string AAAAAAAAAAAAAA\0, or 15 actual characters that the program sees, so it crashes.
We try 15 A characters and see that our input has overflowed into the memory address:
A is \x41 in hex so we see that beginning with that 15th A, we can start controlling what’s written to that memory address. Let’s see how far we can go:
With 20 A characters, we fill up the entire memory space. Going to the 21 characters results in the program redirecting to another set of instructions instead. By subtracting the lower limit from the upper limit, we find how many bytes we have to work with:
20 – 14 = 6 bytes
Let’s explore some more to see if that’s enough to accomplish what we need. Next we need to find the location where the special() function is loaded into memory. If the address is 6 bytes long or less, we can plug that address into our overflow and call the function!
Since we have the source code, we can tell gdb to disassemble the function directly without having to search for it:
disassemble special
We see it starts at a memory address of 400567 – 6 bytes long! Now I had to figure out how to inject the hex code into the gdb prompt. This part took the longest to research. I found suggestions to use an ASCII table to convert the hex value into the corresponding ASCII character. This would have worked fine for the six bytes I needed, but for values much longer, it was not a feasible solution. After some more research, I came across a lengthy StackExchange post (oh the irony!) about how to convert hex input into ASCII to allow it to be sent to a program. The simplest method in this case was to redirect Python output into the gdb run command. You may have noticed how I went back and retook some screenshots above to highlight this method because of how easy it is to follow what’s happening:
To break it down, we run Python with a command to print 14 each of the A character, save it all to a variable, and redirect that variable into the program input.
Now that we know where to overflow, how much room we have, what to send, and how to send it, we can finally put together the exploit!
To craft the overflow, we need to start with padding to fill up the space in front. I’ll use the A character like we’ve been using. That gives us:
run <<< $(python -c "print 'A'*14")
Now we need to add the address: 400567. But x86-64 processors are little endian, so we need to reverse the byte order when we write it. Thus, the byte order becomes \x67\x05\x40. Appending that to our Python string, we get the following result:
run <<< $(python -c "print 'A'*14+'\\x67\\x05\\x40\\x00\\x00\\x00'")
It worked! We executed the special() function!
Bonus Round
Remember that other() function we noticed in the code above? Let’s see if we can execute that, too. Using what we learned above, we can use the same overflow and just replace the target address with the address that will call other(). I ran disassemble other
to identify the address, reversed the byte order (little endian, remember!), and updated the Python run string with the new address.
And it worked! I was able to execute other() as well.
References:
https://bobloblaw321.wixsite.com/website/post/tryhackme-buffer-overflows