在WMCTF 2023官方wp中,看到了一个十分新奇的手法house of blindness. 其无需leak地址却仍然能够hijack程序流,这里做以学习研究。本文例题使用WMCTF2023 blindness
前置
首先补充一个关于malloc的前置知识
- 如果申请的chunk大小非常大,那么便不会使用ptmalloc来进行chunk的分配,而是调用
mmap()
- 另外,这样申请出来的chunk位置与libc和ld的偏移保持一致
如图,我们申请出来的chunk位置如下
可以看到,其地址与libc和ld连在一起
exit
首先我们需要关注一下.dynamic节
在gdb中我们可以使用dyn
命令查看.dynamic节内容。其中的内容氛围两个部分,一个是tag用于表示名称,一个是value.
这个结构由于是静态的,所以同样可以在IDA中看到
其中我们需要关注下DT_FINI
这个tag,也就是gdb中显示的FINI
,其代表了fini
函数到程序加载的虚拟地址的offset,即real_fini_addr = code_base + DT_FINI
,以本题为例我们可以得到
在exit()
函数的源码中,我们可以看到如下部分:
1 | /* Is there a destructor function? */ |
其中最下方的DL_CALL_DT_FINI(l, l->l_addr + l->l_info[DT_FINI]->d_un.d_ptr);
便是调用_fini
函数的部分,宏定义如下
1 |
而l_addr
和l_info[DT_FINI]
正对应这我们前面谈到的codebase和.dynamic节上的偏移,l为在ld中存放的link_map结构
以本题为例,我们用gdb跟踪exit流。可以发现,在调用完__do_global_dtors_aux
之后,程序流便会通过r15,来调用fini
,如图
其中mov rax,QWORD PTR [r15+0xa8]
,就是对应l_info[DT_FINI]
,即指向.dynmic中DT_FINI条目的指针
而add rax,QWORD PTR [r15]
,对应l_addr
,也就是codebase
house of blindness
明白了调用fini
的原理后,how do we exploit this?
可以想象,如果我们能够更改link_map的内容,比如存放codebase和指向DT_FINI条目的指针。那么在调用fini时,我们就可以控制两者之和为system
这样的函数,从而拿到shell(这里我们先不考虑rdi参数如何控制)。这也是所谓house of blindness的核心思想,不过我们还是来看看完整的流程。
首先考虑,若我们想让两者之和为system这样的libc函数,那么一定要是一个libc的地址+一个偏移的形式,因而原有的codebase+offset的形式是不满足要求的。由于我们没有leak的能力,在不考虑爆破的情况下只能覆盖写最低字节,显然无法让一个text段地址调整至libc。
原作者给出的解法是利用DT_DEBUG
条目。DT_DEBUG中存放了指向_r_debug
函数的指针,且_r_debug
是一个ld部分的地址,则我们只需要需要将l_addr劫持为_r_debug
和system
函数的差,l_info[DT_FINI]劫持为DT_DEBUG条目,就可以执行system。
此时rdi参数恰好也是ld中的一个指针,另外还需要劫持fini_array
为空,防止由于指针更改出现crash
原作者给出了exp的大致形式如下:
1 | # disable fini array from exec |
总结一下利用条件:
- malloc的size不限制大小,比如可以
malloc(0x100000)
- 可以在malloc出的chunk附近实现越界的任意写
作者给出的利用条件的形式如下
1 | // get size from user |
WMCTF blindness
WMCTF的这道例题也使用了类似的思想,不过手法并不完全相同,并非利用DT_DEBUG
. 我们可以再看一眼本题的.dynamic标签的地址分布
可以注意到DT_FINI
的地址为0x3DB8,DT_DEBUG
的地址为0x3E58,即如果我们考虑原作者的思路。则需要覆盖2个byte,即仍需1/16的爆破。但是我们注意到,本题是存在后门backdoor
的,那么我们可以控制l_info[DT_FINI]
为一个text段的函数,然后利用l_addr偏移至后门即可。可以看到.dynamic上面的一个标签__frame_dummy_init_array_entry
且恰好后门函数和该函数只有0x9的偏移,如图。那么我们就可以覆盖l_info[DT_FINI]的最低字节从0xB8到0x80,然后控制l_addr偏移为9,从而执行后门函数.
官方exp如下,估计出题人套了自己的板子,因而注释与代码并不匹配,这里并非利用DT_DEBUG。且劫持参数为/bin/sh
也是无必要的
1 | #!/usr/bin/env python |