- ASLR이 걸린 환경에서 system 함수를 사용하려면 프로세스에서 libc가 매핑된 주소를 찾고, 그 주소로부터 system 함수의 오프셋을 이용하여 함수의 주소를 계산해야 함
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); //1번 파일 = 표준 출력
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;
}
- system 함수 주소 계산
- system 함수는 libc.so.6에 정의되어 있으며, 해당 라이브러리에는 read, puts, printf도 정의되어 있음
- 바이너리가 system 함수를 직접 호출하지 않아서 system 함수가 GOT에 등록되지 않음
- read, puts, prinf는 GOT에 등록되어 있음
- main 함수에서 반환될 때는 이 함수들을 모두 호출한 이후이므로, 이들의 GOT를 읽을 수 있다면 libc.so.6 라이브러리 파일이 매핑된 영역의 주소를 구할 수 있음
- rop.c에서는 read, puts, printf 함수가 GOT에 등록되어 있으므로, 하나의 함수를 정해서 그 함수의 GOT 값을 읽은 후 그 함수 주소와 system 함수 사이의 거리를 이용해 system 함수의 주소를 구할 수 있음
- ELF파일의 기본 정보가 포함된 헤더에서 오프셋 찾기
$ readelf -s libc.so.6 | grep " read@" -> read 함수 오프셋 0x114980
$ readelf -s libc.so.6 | grep " system@" -> system 함수 오프셋 0x50d60
=> read함수와 system함수의 거리는 0x114980 - 0x50d60 = 0xc3c20
- pwntools ELF symbols
e = ELF("./libc.so.6")
read_offset = e.symbols["read"] // read 함수 offset
binsh = list(e.search("/bin/sh"))[0] // /bin/sh 문자열의 주소
- "/bin/sh"
- 이 바이너리는 데이터 영역에 "/bin/sh/" 문자열이 없음
- 문자열을 임의 버퍼에 직접 주입하여 참조하거나, 다른 파일(libc.so.6을 많이 사용)에 포함된 것을 사용해야 함
- system 함수 주소를 계산할 때처럼 libc 영역의 임의 주소를 구하고, 그 주소로부터 거리를 더하거나 빼서 계산
- GOT Overwrite
- system 함수의 주소를 알았을 때는 이미 ROP 페이로드가 전송된 이후이므로 알아낸 system 함수의 주소를 페이로드에 사용하려면 main 함수로 돌아가서 다시 버퍼 오버플로우를 일으켜야 함; ret2main 공격 패턴
- system 함수의 주소를 어떤 함수의 GOT에 쓰고 그 함수를 재호출하도록 ROP 체인 구성; GOT Overwrite
- ROPgadget으로 가젯 주소 구하기
- 함수 호출 규약에 따라 인자는 rdi, rsi, rdx, rcx ... 순으로 들어가므로 read함수의 rdi, rsi, rdx 에
- 2번째 read로 buf에 넣는 payload
* read, write 함수의 3번째 인자는 rdx에 넣어주는데 이미 충분히 큰 값이 들어가있어 따로 처리하지 않음
-main의 ret 실행 시 rip가 return address(pop rdi;)를 가리키고 rsp는 8 증가하여 1을 가리킴
-프로그램의 실행 흐름이 rip이므로 pop rdi;가 실행되면 rdi에 1 들어가고 rsp 8 증가
-2번째 인자가 들어가는 rsi에 값을 넣어주기 위해 pop rsi; 를 찾았는데 pop rsi; pop r15; ret; 가젯밖에 없어서 저거 사용
-pop rsi; 하면 rsi에 read got 주소 들어가고 rsp 8 증가
-pop r15; 해서 아무거나 넣어주고 rsp 8 증가
-ret; = pop rip, jmp rip 라서 rsp 값이 rip 에 저장, rip가 가리키는 값으로 이동
=> write(1, read_got, ...); got에 저장된 read의 실제 주소를 구해 libc 베이스 찾기
-> read(0, read_got, ...); read의 got를 system의 got로 overwrite하기 위함
-> read("/bin/sh"); 이때 read의 주소는 system으로 overwrite 되었을 테니까 실제 실행되는 함수는 system("/bin/sh")
-> read(0, read_got, ...)에서 입력을 기다리고 있으므로 p.send로 system 주소와 "/bin/sh" 보내주면 쉘 획득
드림핵 워게임 rop
'보안' 카테고리의 다른 글
[시스템해킹] PIE (0) | 2024.04.26 |
---|---|
[시스템해킹] Return Oriented Programming(ROP); ret2main (0) | 2024.04.26 |
[시스템해킹] Return to Library (0) | 2024.04.23 |
[시스템해킹] Background: Library (0) | 2024.04.23 |
[시스템해킹] NX & ASLR (0) | 2024.04.23 |