在2.27版本中尝试进行堆的简单攻击
tcache bin 基础
tcache机制是glibc 2.26之后引入的一种技术,它提高了堆管理器的性能,但是舍弃了很多的安全检查,所以有多种利用方式,现在的题目一般都基于更新版本的libc,tcache肯定是必不可少的一环,而且是最开始的那一环。
1.每个线程默认使用64个单链表结构的bins,每个bins最多存放7个chunk,64位机器16字节递增,从0x20到0x410,也就是说位于以上大小的chunk释放后都会先行存入到tcache bin中。
2.对于每个tcache bin单链表,它和fast bin一样都是先进后出,而且prev_inuse标记位都不会被清除,所以tcache bin中的chunk不会被合并,即使和Top chunk相邻。
tcache机制出现后,每次产生堆都会先产生一个0x250大小的堆块,该堆块位于堆的开头,用于记录64个bins的地址(这些地址指向用户数据部分)以及每个bins中chunk数量。在这个0x250大小的堆块中,前0x40个字节用于记录每个bins中chunk数量,每个字节对应一条tcache bin链的数量,从0x20开始到0x410结束,刚好64条链,然后剩下的每8字节记录一条tcache bin链的开头地址,也是从0x20开始到0x410结束。还有一点值得注意的是,tcache bin中的fd指针是指向malloc返回的地址,也就是用户数据部分,而不是像fast bin单链表那样fd指针指向chunk头。
tcache_entry
1 | typedef struct tcache_entry |
tcache_entry用于链接空闲的chunk结构体,其中next指针指向下一个大小相同的chunk。(fd指向fd)

tcache_perthread_struct
1 | typedef struct tcache_perthread_struct |
tcache_perthread_struct:管理tcache链表的,位于heap段的起始位置,size大小为0x251
tcache_entry :用单向链表的方式链接了相同大小的处于空闲状态(free 后)的 chunk
counts :记录了 tcache_entry 链上空闲 chunk 的数目,每条链上最多可以有 7 个 chunk
tcache_perthread_struct、tcache_entry和malloc_chunk三者的关系如下

执行流程
第一次malloc时,回显malloc一块内存用来存放tcache_perthread_struct,这块内存size一般为0x251。
释放chunk时,如果chunk的size小于small bin size,在进入tcache之前会先放进fastbin或者unsorted bin中。
在放入tcache后:
先放到对应的tcache中,直到tcache被填满(7个)
tcache被填满后,接下来再释放chunk,就会直接放进fastbin或者unsorted bin中
tcache中的chunk不会发生合并,不取消inuse bit
重新申请chunk,并且申请的size符合tcache的范围,则先从tcache中取chunk,直到tcache为空
tcache为空时,如果fastbin、small bin、unsorted bin中有size符合的chunk,会先把fastbin、small bin、unsorted bin中的chunk放到tcache中,直到填满,之后再从tcache中取
需要注意的是,在采用tcache的情况下,只要是bin中存在符合size大小的chunk,那么在重启之前都需要经过tcache一手。并且由于tcache为空时先从其他bin中导入到tcache,所以此时chunk在bin中和在tcache中的顺序会反过来
## 绕过tcache1
2
3
4
5
6
7
8
9
10
11
12
13
14#include <stdio.h>
#include <stdlib.h>
int main()
{
long long *ptr[7];
long long *a = malloc(0x80);
for (int i=0; i<7; i++)
ptr[i] = malloc(0x80);
for (int i=0; i<7; i++)
free(ptr[i]);
free(a);
printf("libc addr is %llx\n", (long long)a[0]);
return 0;
}
然后再free(a);
tcache机制无非就是增加了一层缓存,如果我们还是想使用fast bin/unsorted bin等的性质,那么需要将对应的tcache bin填满,然后再执行相应的操作就可以了。
__malloc_hook以及__free_hoook的作用
在调用malloc或者free的时候,如果 malloc_hook 和free_hook的值存在,则会调用malloc_hook或者free_hook指向的地址,假设在使用one_gadget的时候满足one_gadget的调用条件,当overwrite malloc_hook和free_hook的时候,便可以getshell,执行malloc的时候,其参数是size大小,所以overwrite malloc_hook的时候使用one_gadget的地址可以getshell。执行free的时候,可以将__free_hook的值overwrite为system的地址,通过释放(/bin/sh\x00)的chunk,可以达到system(/bin/sh)来getshell
劫持原理
malloc_hook位于main_arena上方-0x10的位置,可以通过fake chunk来overwrite该值实现getshell
free_hook 位于libc上_free_hook上,可以通过fake chunk来overwrite该值达到劫持程序流的目的
malloc_hook
- malloc_hook的调用
1 | 2907 void *(*hook) (size_t, const void *) |
- 对应的汇编代码为
1 | .text:00000000000730AE push rbp ; Alternative name is '__libc_malloc' |
- 利用思路:在执行malloc时,会检测__malloc_hook的值,如果malloc_hook的值存在,将调用malloc_hook指向的地址call rax,如果我们将该值overite 为one_gadget,当程序执行malloc的时候,便可以getshell
- 实际利用过程
- malloc_hook的存储的位置
1 | gdb-peda$ p &__malloc_hook |
- 在malloc_hook 上方查找满足条件的fastbin 的size
1 | gdb-peda$ x/8gx 0x7ffff7dd5b00-0x20 |
- 查找符合条件的fake_fastbin,pwndbg内置了查找符合条件的fake_fast_bin的命令
1 | gdb-peda$ find_fake_fast 0x7ffff7dd5b10 0x7f |
伪造的fastbin的addr为0x7ffff7dd5aed,当程序可以在该位置申请chunk的时候,便可以达到程序流劫持getshell
free_hook
- free_hook的调用
1 | 2939 void (*hook) (void *, const void *) |
- 对应的汇编代码为
1 | 汇编代码 |
利用思路:
- 通过改写main_arena中的top_chunk的地址,将top_chunk的值改写到free_hook上方指定的位置,通过不断向top_chunk申请chunk,最终可以分配到包含free_hook的区块,从而可以改写__free_hook的值。
- 通过改写global_max_fast的值,可以在free_hook的上方找到一个足够大包含free_hook的块,当改写了global_max_fast后,向heap申请的块都将按照fastbin来处理
- 通过unsorted bin attack 在free_hook上方伪造 0x7f大小的chunk,再通过fastbin attack 来修改free_hoook的值
实际利用过程
修改top_chunk
free_hook的存储的位置,在 free_hook上方 -0xb58 我们可以找到满足top_chunk要求的大小
1
2
3
4
5
6gdb-peda$ p &__free_hook
$5 = (void (**)(void *, const void *)) 0x7ffff7dd77a8 <__free_hook>
gdb-peda$ x/8gx 0x7ffff7dd77a8 -0xb58
0x7ffff7dd6c50 <initial+16>: 0x0000000000000004 0x697b5349788728d7
0x7ffff7dd6c60 <initial+32>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd6c70 <initial+48>: 0x0000000000000000 0x0000000000000000伪造的fastbin的addr为0x7ffff7dd6c50,当程序可以在该位置申请chunk的时候,通过重复向top_chunk申请,最终覆写__free_hook的值为system的值通过free /bin/sh\x00的chunk,达到程序流劫持getshell。
修改global_max_fast,但是这依赖于可以malloc的chunk的大小是可控的
通过fastbin attack 使得可以malloc 指定包含free_hook大小的chunk,比如利用0x7ffff7dd6c58处的值,但是这里要合理去构造偏移
1
2
3
4
5gdb-peda$ x/200gx 0x7ffff7dd77a8-0xC00
0x7ffff7dd6ba8 <lock+8>: 0x0000000000000000 0x0000000000000000
....
0x7ffff7dd6c48 <initial+8>: 0x0000000000000001 0x0000000000000004
0x7ffff7dd6c58 <initial+24>: 0xbf378114d045c300 0x0000000000000000
通过unsorted_bin attack 来写入free_hook上方0x7f的值,可以在free_hook上方伪造出0x7f大小的chunk,再可以通过fastbin attack 来malloc得到包含free_hook的chunk,写入system的地址,通过free(/bin/sh)来实现利用。
注:由于2.27的tcache不对size位进行检查,所以我不需要去找到free_hook去伪造chunk,再通过fastbin attack,同时注意tcache bin也是可以进行doublefree
泄露libc
演示脚本:
1 |
|
编译之后patchelf一个2.27的libc,理论上低版本编译是可以切换高版本的libc,高版本编译切换不了低版本的libc,所以我建议是使用ubuntu16.04去编译这个heap.c源文件,这样一个heap文件可以复现很多堆的漏洞,我觉得这个demo还是很方便的,可以自己复现一些堆的漏洞,更好的总结吧
unsortbin泄露
1 | add(0,0x410) |
由于上述的demo中有uaf漏洞,所以这里可以利用unsortedbin中的特点去泄露libc(但是2.29引入了UsortedBin Attack的缓解机制,这里总结的是2.27堆的简单攻击,所以不再多述)

这里计算出了libc
简单攻击
1 | add(2,0x60) |
这里首先进行doublefree,然后第一次edit3修改fd指针为free_hook

将free_hook申请出来到堆上,然后我们在堆上去修改,将free_hook修改为system,(其实也可以修改成ogg的,但是我没有成功)
最后再次创建一个chunk6,修改chunk6里面的内容为/bin/sh,free掉,即是调用system(/bin/sh)。
exp:
1 | from tools import * |
参考文章
I'm so cute. Please give me money.
- 本文链接: https://www.imalun.com/2025/10/25/在2-27版本中尝试进行堆的简单攻击/
- 版权声明: 本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。