Simple x86_64 buffer overflow in gdb
We will be debugging a C buffer overflow in gdb to attain higher privileges.
The basic idea behind a C buffer overflow is pretty simple. You have a buffer, a chunk of memory reserved for the purpose of storing data. To the outside of this on the stack (which grows downwards on x86 and x86_64, meaning as it gets larger the memory addresses go down), other pieces of the program are stored and manipulated. Generally, the idea as we are hacking is to redirect program flow as we see fit. Luckily for us, manipulation of the stack (stack “smashing”) can allow us to do this. Usually, you’ll want to gain privileges, usually by execution of shellcode - or whatever your end goal is, but for the purposes of this tutorial, we’ll just be redirecting program flow to code that would otherwise be unreachable to us (in practice, this can be virtually anything; even including the execution of instructions that were not formally there). This is done by writing past the end of the buffer and arbitrarily overwriting the stack.
You’ll need some patience, a C compiler (I’m using gcc, I recommend you use that to follow along), as well as gdb (the debugger, giddabug as I lovingly call it), and a Linux machine or VM, and perl or python (this walkthrough uses perl).
My environment is:
gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Linux jerkon 5.11.0-41-generic #45~20.04.1-Ubuntu SMP Wed Nov 10 10:20:10 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
This is perl 5, version 30, subversion 0 (v5.30.0) built for x86_64-linux-gnu-thread-multi
The Vulnerable Code
This program is vulnerable to a buffer overflow:
Upon reading the code, you’ll notice that we allocate a char array u of 16 bytes, but then we use scanf to bring in user input, without checking the length of the data the user entered. Compile the code using the command
gcc pwnme.c -o pwnme -fno-stack-protector -ggdb. You’ll need the -ggdb to be able to see the C source file in gdb, and -fno-stack-protector so stack smashing protection isn’t compiled into the binary for testing.
Simply run it and press a few (more than 16!) random keys, you’ll then be overwriting the stack. Unless the data entered is carefully picked, this usually just results in a crash, more often what’s known as a segmentation fault.
You can now fire up gdb and pull in our binary using the command:
gdb ./pwnme. You should then see some version information, and, assuming you compiled in debugging symbols with -ggdb earlier, you should see:
One of the foremost things I usually do, just to get a feel for the code at hand, is enter
disas main (short for disassemble). You can replace main with any function name called from within the code, including libraries used.
Right off the bat, you should see a bunch of locations of various instruction sequences in memory.
You can get an idea of where the code you want to land at is by typing
list 11 which should show you the C source 4 lines before and after line 11; where you want to land, at
printf("How you do dat?\n");. Your gdb session should now look something like this:
We’ll now insert a breakpoint at line 10, the conditional check
if (p != 0) that we want to circumvent.
You should also insert a breakpoint at line 11 so it will notify you when you land in the correct spot.
The next part takes a bit of trial and error, you’ll need to figure out how many A’s (hex 0x41) you can insert past the end of the buffer u until you fully overwrite the RIP address (return instruction pointer).
It should look something like this when you’ve found the max overwrite:
As you can see, we hit a segmentation fault and at the time of the fault, the RIP was pointed at
0x414141414141, a non-existent memory location. You can check this two ways:
Now that the program has been run, crashed, and left some registers behind for gdb to inspect, you should again run
disas main and this time your memory locations should be prefixed with 0x5555555.
You can now run
info breakpoints and you’ll see something like:
So now you know that line 11 in the original C program corresponds to memory location
0x000055555555519b. You can view which exact instructions will be executed at that location too:
By now you can probably see where this is headed. We’ll want to overwrite the return pointer with
0x55555555519b so that we skip past the p conditional.
You’ll need to recalculate the number of A’s as padding to use, it’s usually the number you used - 6.
Addresses in memory will be backward because of the endianness, so to illustrate this let us try:
We’re now ready to plug in our memory location
0x000055555555519b. It’s worth noting that the leading zeros don’t matter and should be omitted here. Also, if it were required to use
00 since this translates to NULL, and code execution stops if it hits a NULL character, you would need to find another way around using the existing instructions.
So the moment of truth, and to make this work you’ll need to change
0x55555555519b to wherever your compiler assigned the instruction in memory. It’s likely different from where mine did!
Looks like we did it! We finally hit breakpoint #2 and were able to execute the instructions at
0x55555555519b, printing “How u do dat?”.
This buffer overflow was quite trivial, and most will require quite a bit more work to exploit. You should however now get a general concept, and learn some about gdb in the process.
If you have questions or need help, feel free to leave a comment, or email me!
If you enjoy my work, sponsor or hire me! I work hard keeping oxasploits running!
Thank you so much and happy hacking!