今天集训的第二次比赛出题aFang找的题目,第一次接触到这个难度级别,学到了很多,感觉很爽~
这题主要考察了VTV校验的绕过
可能需要先配置环境将libvtv.so.0复制到/lib/下即可
0x01 源码
1 | /* |
0x02 防护机制
1 | ➜ vtvl_chall pwn checksec vtvl |
这题main函数里没有东西,server
函数设置了__attribute__((constructor(100)));
,而用valloc
函数代替了malloc
,这些都是关键点
首先__attribute__((constructor(100)));
是设置优先级的,一般0-100都是不用的,这里设置为100,可以查vtv的源码 https://github.com/gcc-mirror/gcc/blob/master/libgcc/vtv_end.c
1 |
|
VTV也是设置的100所以server
可能在__VLTprotect
之前执行,从二进制文件的.init_array中也能看到
1 | .init_array:0000000000602C18 _init_array segment para public 'DATA' use64 |
这样就会导致原本一些只读的的页,在这种情况下变成了可写,详细可以看VTV的源码 https://github.com/gcc-mirror/gcc/blob/master/libvtv/vtv_rts.cc
这又有什么用呢?
VTV中有一个hash_map_set,负责管理vtable的,这个set的地址可以通过.vtable_map_vars
节读取到
1 | gdb-peda$ x /10xg 0x604000 |
这里面就存有vtable的地址,最终指向要执行的函数
在VTV中会经常对vtable做检验,校验用的就是这个set,参考https://github.com/gcc-mirror/gcc/blob/da8dff89fa9398f04b107e388cb706517ced9505/libvtv/vtv_set.h
1 | template <typename Key, class HashFcn, class Alloc> |
所以改掉这个set中的地址并且绕过校验,就可以修改vtable了
这里面绕过的技巧就是让capacity=1(默认为4)即可得到固定值0,就能得到一个固定的位置,如下
1 | gdb-peda$ x /30xg 0x00007efe99daa050 |
当然还有一个方法就是各个位置都试一遍,一共也没几个= =,不过需要注意每次地址变化时,位置都不一样,具体怎么变化的算法目前还是不懂。。。太菜了.jpg
那么现在还有个问题就是这个在__VLTprotect
时用到了mprotect
,所以肯定是mmap出来的,那么我们也申请的内存也必须是mmap,在malloc
时大小是要超过MMAP_THRESHOLD bytes才能出发mmap(默认0x20000),而且一般mmap的地址到不了那么高的地址,这又该怎么办呢?
valloc
在这里起作用了,当输入-1,即valloc(0)
的时候如果mmap,就会在距离set固定偏移(不同内核偏移不同)的mmap一段内存,并且还可以进行overflow
但是一般valloc(0)
是不会触发mmap的,不过可以通过读glibc源码
1 | void * |
可以发现当环境变量MALLOC_MMAP_THRESHOLD_=0时,就可以触发mmap,进而就可以完成这一连串的利用
0x03 总结利用步骤
- 通过栈溢出设置环境变量MALLOC_MMAP_THRESHOLD_=0
valloc(0)
触发mmap,利用堆溢出覆盖VTV的hash map set- 修改vtable,接触控制流,构造ROP链(需要栈迁移)get shell
注:构造ROP链的时候需要注意避开一些栈上必要的变量信息,利用pop ret,add rsp等gadget跳过即可构造任意长度ROP链(PS:这地方坑了我半天,写ROP还是太渣了QAQ)
0x04 exp
1 | #!/usr/bin/env python |
0x05 总结
感受到了pwn的博大精深,要不是因为出题,之前根本不会接触这方面的知识,学到了很多,还磨炼了各种调试的技巧和读源码的能力,总之收获很多不过和其他大佬相比还是太菜了QAQ还需要继续努力啊加油!