House Of Banana
House Of Banana 利用背景:
在glibc2.28之后更新了关于unsorted bin的bk指针的检查,导致我们不能在用unsortedbin attack来实现任意地址写一个较大值了
1 | /* remove from unsorted list */ |
此时我们能够利用的还有large bin attack,例如house of storm,但是随着glibc版本的提高,更新了许多对于largebin的检查,导致了之前的一些largebin attack的手法失效了
1 | if (__glibc_unlikely (size <= 2 * SIZE_SZ) |
1 | if ((unsigned long) size |
高版本largebin attack 在高版本只能从下面这个分支利用:
1 | /* maintain large bins in sorted order */ |
这种利用方法要求双链表中至少存在一个 largebin chunk 并且新链入的 largebin chunk 必须比目前在largebin 中最小的 chunk 还小,此时将新链入的 largebin chunk 的 bk_nextsize 指针修改为目标地址就可已实现一次任意地址写堆地址。
在该分支利用largebin attack,只是完成往任意地址写一个堆地址的作用,因为在这里bck->bk才是我们的 large bin,因此我们能够控制的也就是这个分支中的fwd->fd->bk_nextsize,而完成写的操作是在另一个分支的fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; 这句中,即可以往任意地址写上这个unsorted bin chunk堆的地址。而以前旧版large bin attack是可以往任意的两个地址写两个堆地址。
Largebin 堆复习:
有一段时间没有学习堆知识了,这里简单复习一下:
fd** (Forward Pointer):** 指向链表中下一个(比当前 chunk 更靠近头部/Head) 的 chunk。bk** (Backward Pointer):** 指向链表中上一个(比当前 chunk 更靠近尾部/Tail) 的 chunk。fd_nextsize: 指向比当前 size 更小 的下一个 size 组的第一个块。bk_nextsize: 指向比当前 size 更大 的上一个 size 组的第一个块。
这两个指针的指向逻辑与 fd/bk 是相反的(fd 指向大,而 fd_nextsize 指向小)。这是为了形成一个闭环,方便从大向小扫描,也方便从小向大扫描。
Largebin 内部是按 Size 从大到小 降序排列的。
Head -> Largest Chunk -> ... -> Smallest Chunk -> Headfd指向更大的 chunk,bk指向更小的 chunk。
之前提到 Unsorted Bin Attack 在 2.28 后失效了,而 Largebin Attack 成了替代方案:
其原理就在于插入 Largebin 时,如果进入了 bk_nextsize 的处理逻辑,源码中会有这样的操作:
1 | victim->bk_nextsize = fwd->bk_nextsize; |
通过伪造 fwd->bk_nextsize,我们依然可以实现类似 Unsorted Bin Attack 的 “往任意地址写一个堆地址” 的效果。
House Of Banana:
适用版本及其原理:
- glibc2.23——至今
- 程序能实现若干次large bin attack
- 程序能够调用exit结束
源码分析:
程序通过exit退出时,会调用一个名叫 rtld_global 的结构体中的一系列函数来进行诸如恢复寄存器,清除缓冲区等操作:
1 | rtld_global -> _ns_loaded -> link_map -> ((fini_t) array[i]) () |
rtld_global结构体里面的link_map结构体,其中rtld_global中的_ns_loaded指向link_map结构体的头节点。通过劫持link_map结构体就能实现函数调用。
下面来分析一下rtld_global结构体 , rtld_global结构体组成也是非常复杂的,我们直击重点
1 | struct rtld_global |
1 | +-------+----------------------------------+----------------------------------+ |
而这个结构体里面存放的是elf文件各段的符号结构体 _dl_ns,而这个符号结构体里面又套的有结构体,我们关注的是里面的 fini_array 段的动态链接结构体指针,而这个指针又会在 _dl_fini 中被使用。
当调用到 _dl_fini 函数时,会执行每个 so 中注册的 fini 函数:
1 | void |
代码中的l是link_map结构体
link_map结构体是一个在Linux下用于动态链接的数据结构,用于保存共享对象库之间的依赖关系和符号表信息。每个共享对象都有一个link_map结构体,通过这些结构体可以组成一个链表,这个链表描述了所有的共享对象库之间的依赖关系。
link_map结构体定义在/usr/include/link.h头文件中,其主要成员变量包括:
l_next:指向下一个共享对象库的link_map结构体的指针。l_real:指向本身的地址info[x]:它是一个指向ElfW(Dyn)类型的指针,用于保存共享对象库的动态信息
_rtld_global是一个在Linux下用于动态链接的全局变量,它是一个指向struct link_map结构体的指针,代表当前进程的所有共享对象库之间的依赖关系和符号表信息。
该全局变量在动态链接器ld.so中定义,其作用是记录动态链接器在进程启动时加载的所有共享对象库的link_map结构体链表的头指针。
在动态链接过程中,当需要查找符号表信息时,动态链接器可以通过访问_rtld_global指向的link_map链表,遍历所有已加载的共享对象库,以寻找所需的符号表信息,这是动态链接的核心功能之一。
_也就是说我们需要利用large bin attack需改 rtld_global为我们伪造的link_map结构体
构造函数指针入口 (l_info):
link_map 结构体内部有一个 l_info 指针数组,保存着指向 Elf64_Dyn(动态节)的指针。我们需要精心伪造两个关键条目:
l_info[DT_FINI_ARRAY]:指向伪造的动态节条目,该条目进一步指向包含目标恶意函数地址的数组。- 这个恶意地址可以是 one_gadgets,高版本 libc 下也可以是 setcontext
l_info[DT_FINI_ARRAYSZ]:指向指明数组大小的伪造条目(确保循环能够执行)。
3. 触发漏洞执行流
完成堆上数据的布局后,通过输入程序菜单的退出选项 Quit 或触发异常终止程序,引导控制流进入 exit()。
4. 结合 setcontext 进行栈迁移 (SROP)
在高版本 libc 的利用场景中,我们直接跳转的目标通常不是 system("/bin/sh")(沙箱场景),而是 Glibc 中的汇编片段 setcontext+0x3d(或类似偏移)。
- 当
_dl_fini调用伪造的析构函数时,寄存器RDX恰好指向当前的link_map结构体(即我们的伪造堆块)。 - 执行效果:
setcontext代码会从[rdx + offset]处恢复各个通用寄存器(包括 RSP 栈指针)。我们将 RSP 劫持到预先布置了 ROP 链的堆内存上,即可流畅执行任意系统调用。
在有些情况下,rtld_global_ptr与libc_base的偏移在本地与远程并不是固定的,可能会在地址的第2字节处发生变化,因此可以爆破256种可能得到远程环境的精确偏移。
更新: 2026-05-20 15:23:16
原文: https://www.yuque.com/idcm/wnemg9/ekzm2qwvp90s5lcr