前言
功能十分强大的一个house of 系列 可以执行onegadget或者是system(“/bin/sh”)来获取shell
原理是通过largebinattack把chunk_addr覆盖程序执行exit时所需要的结构体 进行伪造 这样就可以劫持exit函数
源码分析
我们在之前的栈部分学习中 学过篡改fini_array 来进行一个重复执行main函数
但是其利用需要关闭RELRO保护 然而堆题基本都是保护全开 所以很少用到 今天来学习一下伪造fini_array赋值用到的结构体 从而控制程序exit时的程序执行流
利用源码调试 可以知道 fini_array是在/elf/dl-fini.c的139行被调用的 那么我们跟进一下源码 看一下赋值逻辑
if (l->l_info[DT_FINI_ARRAY] != NULL) |
array = (l->l_addr + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr)
貌似和l这个链表有关 朔源一下l的定义
struct link_map *l = maps[i]
继续朔源一下maps数组的赋值
define GL(name) _rtld_global._# |
maps的赋值和GL(dl_ns)[ns]._ns_loaded这个结构体有关系 所以我们只需要伪造该结构体 就能操控maps的值 从而操控
fini_array的内容
每一次for循环后l的赋值可以看出来其是一个链表 看其他师傅的介绍是必须有四个元素 也就有两种做法 截取第一个节点开始伪造 或者是从第三个节点开始伪造 前者伪造的更加麻烦 这里先来介绍后者的
劫持第三个节点
首先需要获得第三个节点的位置 采用计算和_rtld_global结构体的偏移来得到
distance &_rtld_global &(_rtld_global._dl_ns._ns_loaded->l_next->l_next->l_next) |
接着为了进入if分支 我们还需要通过第一个判断
l == l->l_real |
也就是在fake+0x28处需要写入fake地址
struct link_map *l = maps[i]; |
for循环中还有这样一段 为了使for循环顺利执行 我们同样需要绕过这里的if判断
需要绕过l->l_init_called 不过这里貌似不能是个1就行 需要伪造成其原本的值 和上面的一样 查一下偏移和所需要的值
distance _rtld_global._dl_ns[0]._ns_loaded &(_rtld_global._dl_ns[0]._ns_loaded)->l_init_called |
第二个if用的是或 通过一个就行了 这里选第一个 可以一并绕过下一个if
l->l_info[DT_FINI_ARRAY] != NULL
同时注意一下下面maps赋值的操作
ElfW(Addr) *array = (ElfW(Addr) *) (l->l_addr + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr); |
fini_array的值由l->l_info[DT_FINI_ARRAY]决定
我们还需要控制i的值 i由下面这个式子得到
i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr))) |
distance (_rtld_global._dl_ns[0]._ns_loaded) &((_rtld_global._dl_ns[0]._ns_loaded)->l_info[28]) |
这里的i跟随着系统原本的布局
并且你会发现 i的值应该是存放在对应偏移地址的下一个字长处
同理可得 我们还需要关注一下l->l_info[DT_FINI_ARRAY]需要怎么构造
同样是需要把地址放在下一个字长处 并且需要经过两次跳转
总结一下就是按照下面这样构造 这里的偏移因人而异 我把所有的偏移都减去了0x10 是因为fake是从chunk头开始算的 而edit是从chunk的用户空间开始
payload = p64(0)*3 + p64(fake_addr) |
更加详细的流程可以看这位师傅的博客 我只是做个总结