5.3 KiB
bufoverflow.c demo
This demo corresponds to the Buffer Overflow: Code Execution slide of the Software Security lecture.
The function mystr()
is vulnerable as it has a buffer overflow when calling fread()
with a wrong maximum lenght: only 512B would fit into the buffer but 1024B are passed as maximum length argument.
Attackers can therefore overflow the buffer char mystr[512]
by providing input files larger than 512B.
NOTE: Before starting here, make sure you have your .gdbinit
file configured correctly (see Moodle).
Demo usage
To compile the demo, use an x64 system with gcc
and nasm
installed and invoke make
. Then:
./bufoverflow <filename>
If the file is too large, the program should receive a segmentation fault during execution. Otherwise, the program just exits gracefully.
Exploiting the program WITHIN a debugger
Once we got the program running, we have to write the shellcode. We face two challenges here.
- We have to investigate how many bytes of shellcode we can place and what offset the saved
rip
has. - We have to compute the stack pointer to our shellcode that overwrites the saved
rip
. We devote each a subsection for each challenge.
Determinining the offset
Computing the correct stack pointer
To compute the stack address, simply use gdb
. To this end, we open the program in a debuger:
gdb --args ./bufoverflow <path-to-input-file>
Within gdb
, we then set a breakpoint on the mystr
function like this:
break mystr
You can then execute the program using the gdb command run
. The program will execute until hitting the breakpoint. If you don't see registers or assembly you, simply enter layout asm
and layout regs
. You should then see the program before executing the mystr
function prologue. Now, you can single-step using the gdb
command ni
. Single-step until after the function prologue (i.e., right aftersub rsp,0x200
). You then see the stack frame looking at rbp
and rsp
, where rsp
points to the top of the stack and thus the single local function variable (the 512B buffer) in our function. Note down this value (in my case, 0x7fffffffe090
) and exit the debugger with SIGINT (i.e., CTRL+D) or using exit
(noone does that). Update the last line of the shellcode accordingly (the dq
contains the stack pointer).
Compiling the shellcode
After having adapted the shellcode, compile the shellcode using make
.
To double-check if the shellcode was compiled correctly, you can disassemble it using the command
objdump -D -b binary -M intel -d -m i386:x86-64 ./shellcode | less -S
It should start with a (long) nop
chain, followed by the system call, the shell string (misinterpreted as code) and the shellcode pointer (likewise misinterpreted as code).
Once you're satisfied, invoke
gdb --args ./bufoverflow ./shellcode
and then use the run
command to get your shell.
Exit with SIGINT (CTRL+D); the first SIGINT will exit the shell, the second the debugger.
Shellcode for exploits OUTSIDE OF the debugger
When you now try to exploit the program without using gdb
, you will likely observe a segmentation fault like this:
$ ./bufoverflow ./shellcode
Segmentation fault
This has two reasons, both of which you'll have to address.
Stack randomization
First, by default, Linux randomizes the stack location to make attacks like yours harder.
In gdb
your attack worked as gdb
disables this randomization to make debugging easier.
You can disable this randomization by executing the program like this:
setarch \
uname -m` -R ./bufoverflow ./shellcode`.
Adjusting the stack pointer
Second, the stack pointer you obtained using gdb
is invalid when the program is being run without gdb
.
This is as gdb
places environment variables on the stack that change (decrease!) the stack (i.e., rsp
value).
There are (at least) two solutions to this problem:
Variant A: Guesstimate the right stack address
We can take into account that gdb
places environment variables that change (decrease!) the rsp
value (see here for details and possible workarounds that however only work for programs without arguments).
Thankfully, given that we have sufficient room for a nop
sled in our sled, we can adopt to this and try to guesstimate a valid rsp
value.
Try to increase the value by 256B (0x100).
This address should now point somewhere in the middle of our shellcode, i.e., into our NOP sled.
Then recompile the shellcode and give it a try.
Variant B: Inspecting a core dump.
If you're really after the exact correct rsp
value, you can also record a so-called core
dump of the vulnerable program upon crash, and then inspect the core dump using gdb
.
To enable core dumps, run:
ulimit -S -c unlimited
Then, run the program and let it crash.
setarch
uname -m -R ./bufoverflow ./shellcode
Now you should see a file called core
, which is our core dump. We can inspect this using gdb
:
gdb ./bufoverflow ./core
By inspecting the registers or memory content, you may be able to find out the correct rsp
value. Note however that the core dump was taken after the function's epilogue and hence the rbp
and rsp
values will be clobbered. So it's always a little nasty to find the exact value and there's no systematic answer how to find it.