sfl-examples/lecture-demos/buffer-overflow
christian.rossow 58d1785333 fixed typo 2024-11-07 15:25:25 +01:00
..
Makefile added explanation of buffer overflow example 2024-11-07 15:22:04 +01:00
README.md fixed typo 2024-11-07 15:25:25 +01:00
bufoverflow.c added demos 2023-12-14 13:56:57 +01:00
shellcode.asm added explanation of buffer overflow example 2024-11-07 15:22:04 +01:00

README.md

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.

  1. We have to investigate how many bytes of shellcode we can place and what offset the saved rip has.
  2. 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.