Simple x86_64 buffer overflow in gdb
Background
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.
Prerequisites
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:
1 2 3 4 | 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
. 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.1
gcc pwnme.c -o pwnme -fno-stack-protector -ggdb
Exploitation
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:
. You should then
see some version information, and, assuming you compiled in debugging symbols with -ggdb earlier,
you should see:1
gdb ./pwnme
One of the foremost things I usually do, just to get a feel for the code at hand, is enter
(short for disassemble). You can replace main with any function name called from
within the code, including libraries used.1
disas main
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
which should show you
the C source 4 lines before and after line 11; where you want to land, at
1
list 11
. Your gdb session should now look something like this:1
printf("How you do dat?\n");
We’ll now insert a breakpoint at line 10, the conditional check
that we want to
circumvent.1
if (p != 0)
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
, a non-existent memory location. You can check this two ways:1
0x414141414141
Now that the program has been run, crashed, and left some registers behind for gdb to inspect, you
should again run
and this time your memory locations should be prefixed with 0x5555555.1
disas main
You can now run
and you’ll see something like:1
info breakpoints
So now you know that line 11 in the original C program corresponds to memory location
. You can view which exact instructions will be executed at that location too:1
0x000055555555519b
By now you can probably see where this is headed. We’ll want to overwrite the return pointer with
so that we skip past the p conditional.1
0x55555555519b
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
. It’s worth noting that the
leading zeros don’t matter and should be omitted here. Also, if it were required to use 1
0x000055555555519b
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.1
00
So the moment of truth, and to make this work you’ll need to change
to wherever your
compiler assigned the instruction in memory. It’s likely different from where mine did!1
0x55555555519b
Conclusion
Looks like we did it! We finally hit breakpoint #2 and were able to execute the instructions at
, printing “How u do dat?”.1
0x55555555519b
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!
Bitcoin Address:
bc1qclqhff9dlvmmuqgu4907gh6gxy8wy8yqk596yp
Thank you so much and happy hacking!