ipwn
[pwnable] Format String Bug 본문
FSB(Format String Bug)
필요한 친구가 있다고 하여 일단 brop보다 우선적으로 작성하게 됐다.
fsb는 format string을 사용하는 함수 딴에서 인자 전달을 확실히 하지 않았을 경우에 발생하는 취약점이다.
가장 먼저 포맷 스트링이 무엇인지 알아보자.
자세한 설명은 생략하겠다.
하지만 이 표에서도 설명이 되어있지 않은 포맷 스트링이 두 가지 존재한다.
바로 "%n"과 "%hn"이라는 포맷스트링인데, 이 포맷스트링은 현 (esp + 4 + 포맷스트링에 따라 더해질 byte수)가 가리키고 있는 값을 주소로 인식하고 이전에 출력했던 byte수 만큼 그 주소값에 대입을 하겠다는 포맷스트링이다.
이 두 포맷스트링의 차이점은 %n같은 경우에는 4byte 메모리 공간을 가진 주소로 인식해서 4byte만큼의 값을 대입을 할 수 있다는 것이고 %hn은 2byte 메모리 공간을 가진 주소값으로 인식을 해 2byte의 값을 대입을 할 수 있는 것이다.
일단 포맷스트링 사용의 예를 하나 들어보겠다.
#include <stdio.h> int main() { int a = 0x12345678; printf("%x\n", a); } |
이러한 코드가 작성되어있다고 하자.
그렇다면 출력값은 제대로 12345678이라는 값이 출력이 될 것이다.
이렇게 작동되는 원리를 gdb로 찾아보자.
이렇게 esp에 담겨져있는 문자열의 주소를 받아서 포맷스트링을 갖고 있다면 esp+4부터 시작하여 포맷스트링이 표현 가능한 byte 수 만큼 esp+4에서 더해서 그 위치의 값을 맞는 출력 방식에 따라 출력한다는 것을 알 수 있다.
Ex) printf("%d%x%s%c", 12345678, 0x12345678, "asdf", 'c'); 와 같은 코드가 있다면 스택 상태는 아래와 같은 상태가 될 것이다.
esp -> "%d%x%s%c"의 주소
esp + 0x4 -> 0x00bc614e
esp + 0x8 -> 0x12345678
esp + 0xc -> "asdf"의 주소
esp + 0x10 -> 0x78
이제 fsb의 예시를 하나 보자.
#include <stdio.h> #define BUF ".%x.%x.%x.%x\n" int main() { printf(BUF); } |
이러한 코드가 있다고 가정해보자.
BUF에는 ".%x.%x.%x.%x\n"의 문자열을 define해놓았고 printf로 BUF라는 문자열을 넣어주었다.
이러한 경우에는 출력값이 어떻게 될까?
이렇게 문자열이 출력되는 것이 아닌 이상한 쓰레기값들이 출력된다.
gdb로 한 번 확인해보자.
알고보니 출력된 쓰레기 값들은 각각 esp + 0x4, esp + 0x8, esp + 0xc, esp + 0x10의 위치에 존재한다는 것을 알 수 있다.
이게 가장 기초적인 포맷스트링 버그라고 할 수 있다.
#include <stdio.h> int main() { char str[256]; fgets(str, 255, stdin); printf(str); } |
이제 이러한 코드가 작성되어있다고 해보자.
입력값을 서식문자가 아닌 평범한 input을 준다면, input을 그대로 출력해주는 취약점이 전혀 없는 코드임을 알 수 있다.
하지만 입력값을 포맷스트링으로 넘겨주게 된다면 바로 memory leak부터 시작해서 "%n", "%hn"을 이용한 memory overwrite도 가능해진다.
가장 먼저 printf가 발생할 때 input의 값을 "%x"와 같은 값으로 넘겨주면 memory값들이 leak 될 것이다.
위의 글들을 봤으면 알겠지만 %x를 input으로 넘겨줄 경우 esp + 0x4의 값을 출력할 것이고 그 뒤도 계속 같은 방식이 될 것이다.
이렇게 입력값을 AAAA로 넘겨준 상태로 디버깅을 해보겠다.
보면 알겠지만 input의 시작값은 ebp-0x10c 즉 0xffffd4fc인 것을 알 수 있고 printf의 인자로 넘겨져있는 esp의 주소는 0xffffd4e0이다.
즉 input의 값을 우리가 "%x"와 같은 포맷스트링을 통해 어떤값을 넣어주었는지 확인할 수 있다는 말이 된다.
0xffffd4fc - 0xffffd4e0 == 0x1c == 28이므로 "AAAA" + ".%x"*7의 값을 넘겨주게 된다면 input의 시작주소인 ebp-0x10c을 참조하게 될 것이고 그 안에는 "AAAA" 즉 0x41414141의 값이 들어있으므로 쓰레기 값을 6번 출력해주다가 input의 값을 16진수로 출력하게 될 것이다.
이렇게 0x41414141의 값이 잘 출력 된 것을 확인할 수 있다.
하지만 만약 여기서 7번째 포맷스트링을 "%x"와 같은 포맷스트링이 아닌 "%n", 혹은 "%hn"과 같은 포맷스트링을 사용하게 된다면 0x41414141의 위치에 임의의 값을 대입할 수 있게 된다는 말이다.
이게 마냥 0x41414141의 값이라면 아무 문제가 없이 segmentation fault를 띄우고 말 것이지만 만일 변수의 주소, 혹은 함수의 got의 주소같은 경우라면 치명적으로 프로그램의 flow를 조작할 수 있게 된다는 말이다.
예를 들어서 input을 32bit으로 packing한 (ebp + 4)의 값 즉 return address를 가리키고 있는 포인터 값으로 넘겨준 뒤 oneshot gadget의 주소로 바꿔준다면 쉘을 획득할 수 있는 등 여러가지 방면으로 flow를 조작할 수 있다는 말이다.
fsb를 사용한 exploit의 예제는 http://shotgh.tistory.com/77을 참고하시길 바란다.
'Pwnable' 카테고리의 다른 글
[Pwnable] close된 stdin, stdout 열기 (0) | 2018.11.14 |
---|---|
[pwnable] Blind ROP (0) | 2018.07.28 |
[Heap] House of force (0) | 2018.05.14 |
[Heap] Use After Free (4) | 2018.02.18 |