RTL(Return into Libc) – x64
공유 라이브러리 함수 주소를 return address에 넣어서 해당 함수를 호출하는 방법
-RTL 공격으로 NX bit(DEP) 우회 가능
# 호출 규약
System V AMD64 ABI 호출 규약
*Solaris, Linux, FreeBSD, macOS에서 사용
*특징
인자 전달 방식 :
레지스터 RDI, RSI, RDX, RCX, R8, R9는 정수 및 메모리 주소 인수가 전달되고,
레지스터 XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7은 부동 소수점 인수가 전달된다.
인자 전달 순서 : 오른쪽에서 왼쪽 순서로 레지스터 저장
함수의 반환값 : EAX
Stack 정리 : 호출한 함수가 호출된 함수의 stack 공간을 정리한다.
Calling convention example (C language)
int a,b,c,d; int ret;
ret = function(a,b,c,d); |
4개의 인자 전달받고 ret 변수에 저장한다.
Cdecl 형태의 assembly code로 변환하면
Calling convention example (Assembly code)
mov rcx,d mov rdx, c mov rsi, b mov rdi, a call function mov ret, eax |
4개의 인자값을 mov 명령어를 이용해서 레지스터에 저장한다.
함수 호출 후 반환값은 EAX 레지스터에 저장하고, 해당 값을 ret 변수에 저장한다.
$ gcc -o test test.c
$ gdb -q test
Main 함수에서 함수 프롤로그 과정 후, vuln 함수의 인자값을 mov 명령어를 이용해서 ECX, EDX, ESI, EDI 레지스터에 인자 4개를 각각 넣는다. 순서는 오른쪽으로 왼쪽 순이므로 4부터 1까지 넣는다.그리고 vuln 함수를 호출한다.
Vuln 함수 호출 부분에서 break point를 잡아보자.
각 레지스터에 저장된 vuln 함수의 인자값을 확인할 수 있다.
Vuln 함수는 printf 함수에 인자를 전달하기 위해 인자를 재배치한다.
Printf 함수의 첫 번째 인자는 “%d, %d, %d, %d” 인데,
이것으로 인해 각 레지스터의 값이 재배치된다.
함수 프롤로그
인자 넣기 위해 sub 0x10으로 16 바이트만큼 stack 공간 확장
Mov 명령어로 4부터 1까지 ebp-4, ebp-8, ebp-12, ebp-20 위치에 인자 4개 각 레지스터에 넣음
근데 보면, main에서 EDI 레지스터에 넣은 인자값은 0x01이다.
0x01부터 0x04 순서로 인자가 printf 함수에 인자로 전달된다.
그리고 printf 함수 호출 전 0x400804는 printf 함수의 인자 넣는 주소이다.
New arguments
Register |
Value |
Explanation |
RDI |
0x400604 |
"%d, %d, %d, %d" |
RSI |
0x1 |
Arg 1 |
RDX |
0x2 |
Arg 2 |
RCX |
0x3 |
Arg 3 |
R8 |
0x4 |
Arg 4 |
그리고 EDI 레지스터에 함수 인자 주소를 넣어서 RDI 레지스터 확인해보면 주소가 vuln 함수 인자 주소이다.
0x400604 값을 출력해보면 vuln 함수의 인자가 들어있음을 확인할 수 있다.
ret2libc 기법을 사용하기 위해서 각 레지스터에 값을 저장해야 한다.
레지스터에 값을 저장하는 방법=> ROP(Return-oriented programming) 기법
-return address 영역에 “pop rdi, ret” 코드가 저장된 주소값 저장
-return address 다음 영역에 해당 레지스터에 저장할 인자값 저장
(RTL -84에서 알았듯이 return address 다음에는 인자가 오기 때문)
-그 다음 영역에 호출할 함수의 주소를 저장
[RET] [RET+4] [RET+8]
&”pop rdi, ret” 인자 &function_addr
# return address 덮기
Ret2libc.c
<main 함수>
vuln 함수 호출
<vuln 함수>
dlsym 함수로 printf 함수의 주소를 심볼 테이블에서 찾아서 printf_addr 포인터에 저장한다.
read 함수로 사용자로부터 100개의 문자를 입력받아 buf 변수에 저장하는데, buf 배열의 크기는 50 바이트이기 때문에 여기서 stack overflow가 발생하는 취약점이 발생한다.
Breakpoint 설정
Vuln 함수의 첫 번째 명령어 0x400676
Vuln 함수의 read 함수 호출 0x4006e0
Vuln 함수의 ret 명령어 0x4006e7
Return address 확인
Breakpoint 1에서 실행시키면, ESP 레지스터가 가리키고 있는 최상위 stack 주소는 0x7fffffffe578 이다.
0x7fffffffe578 영역에는 return address(0x4006f6)이 저장되어 있다
main에서 확인해보면 vuln 함수 call 이후 주소가 0x4006f6 임을 확인할 수 있고, return address임을 알 수 있다.
Buf 변수 주소 확인
Breakpoint 2, 0x4006e0
여기서 보면, read 함수 call 전에, mov 명령어로 인자들을 넣는다. Buf 변수도 넣는데 RSI 레지스터에 넣는다. 따라서 RSI 레지스터를 출력해보면 buf 주소를 알 수 있을 것이다.
0x7fffffffe530이 buf 변수의 위치이다.
0x7fffffffe578 return address에서 buf 변수 위치 0x7fffffffe420 이 값을 빼면 buf에서 return address 차이를 얻을 수 있다.
Buf와 return address 거리는 72 바이트로, read 함수로 문자를 72 개 이상 입력하면 return address를 덮을 수 있다. 즉, 72 바이트 다음이 return address이다.
0x7fffffffe578 영역에 “0x4a4a4a4a4a4a4a4a”(JJJJJJJJ가 저장되어있는 것을 확인할 수 있다.
# libc에서 system 함수 주소와 “/bin/sh” 주소 찾기
(gdb) info proc map
Libc start address는 0x7ffff7809000이다.
system addr – libc start addr = system function addr offset
printf addr – libc start addr = libc base address offset
Libc base address offset : 0x55800
System function address offset : 0x45390
“/bin/sh” 주소 찾기
“pop rdi, ret” 코드 주소 찾기
“pop rdi, ret” 주소 : 0x400763
# exploit 코드
Payload
Buf + dummy + SFP + RET
buf에서 RET까지가 아까 구해봤을 때 76이었다. “J”*8까지 문자 80개 입력했을 때 “J”*8이 return address를 덮었으니까.
따라서 RET 전까지 “A”*76으로 덮고, return address에는 pop_rdi_ret를 넣는다.
“pop rdi, ret”을 사용하는 이 ROP 기법은 기존의 payload와 다르게 ret 이후에 system 함수 구성이 binsh + sysaddr이다.
[Exploit Tech] ROP (Return Oriented Programming) -x64 (0) | 2019.08.17 |
---|---|
[Exploit Tech] ROP (Return Oriented Programming) -x86 (0) | 2019.08.17 |
Return to Shellcode (0) | 2019.08.10 |
1. RTL (Return into Library) -x84 (0) | 2019.07.25 |
[Protection Tech] 메모리 보호기법 정리 (0) | 2019.07.03 |