首页
社区
课程
招聘
[原创]从2026CCB决赛堆题CreditMarket学习Glibc2.39后ptmalloc的机制更新
发表于: 2026-5-7 05:34 12878

[原创]从2026CCB决赛堆题CreditMarket学习Glibc2.39后ptmalloc的机制更新

2026-5-7 05:34
12878

每一次复现CCB的题总会学习到一些没接触过的知识,这次的题目涉及到的是高版本Glibc在引入了很多安全检查之后的利用手法

这道题所使用的Glibc版本是2.43的(Ubuntu 26.04所使用的Glibc),这个版本也会作为Ubuntu 26.04 LTS的Glibc版本,所以在未来一段时间应该也会是安全研究的一个主要方向,所以在开始复现题目之前要先简要的了解从Ubuntu 24.04 LTS(Glibc-2.39)后引入的一些更新。

在这里特别感谢@jiegec的师傅的许可,glibc 内存分配器 - 杰哥的知识库这篇文章对于ptmalloc的机制实现解释得特别详细,同时对于各版本Glibc更新也总结的十分好,笔者也是一边看这篇博客一边看源码学习的高版本Glibc对于ptmalloc的更改。

我们主要关心Glibc-2.39后的更新,2.39与2.40的malloc.c文件完全一致,而从2.41开始改动就比较大了。

首先,2.41封装了一系列函数用于tcache的判定,这也解决了在之前Glibc版本中散落在不同地方的tcache判定->取块/存块逻辑,可以通过这系列函数直接取/存(但是其中某些函数仅作为过渡,因为在Glibc2.42对tcache又进行了重构)。图1、2:封装的判定逻辑图3:可以看到,原本的tcache判定逻辑是散落 在各个地方的

同时,大概也是因为封装了这个函数后取块变得容易,calloc自2.41后也会默认使用tcache了。同时也是因为封装了tcache的处理逻辑,free的对应逻辑也有修改,将原本的_int_free拆成了多个小函数来实现,执行逻辑和原本并无太大区别,唯一改动的是如今符合smallbin大小的堆块会直接进入smallbin而不是先进入unsortedbin,对于CTF来说,不能简单通过填满tcache来获得unsortedbin了

这个版本在tcache上动了大刀子,原有的counts数组被删除,转而变为num_slot数组,counts数组统计每个size的tcache块,而num_slot数组转而统计每个size的“可放入块”。(同时,tcache被分为tcache_smallbin{与原来的tcache逻辑一致}和tcache_largebin{新增机制},这个我们在后面展开)进一步的,因为tcache_largebin的机制,所以tcache_get和tcache_put不再是简单的头插逻辑了接下来是新增的tcache_largebin机制,它和largebin的机制是比较像的,维护了某个size段的链表,这便与传统的tcache有区别,传统的tcache只维护单个size的链表所以可以用简单的头插法实现,而加入largebin中必须允许向某个链表中段插入堆块,同时,因为next字段是经过safe-linking的,所以添加了一些检查和处理:

同时还由于加强了对于double free的检查,之前检查的是某单个size的链表,而现在会扫描所有size中的链表,意味着传统的free->change size->free将不能够通过检查:同时,将malloc的流程进行拆分,分为了初始化tcache的__libc_malloc和不初始化tcache的__libc_malloc2,检查机制上没有什么独特的地方,反耳呢,值得注意的是,如今改为num_slot计数后 ,如果attack tcache_prethread_struct不需要再伪造counts数组中的计数,而只需要保证entries数组不为0即可图不知道几:可以看到,在Glibc2.41中,tcache_available是检查了counts数组的图不知道几:而在Glibc2.42中,除了常规检查外几乎没有安全检查

这使得我们对tcache_prethread_struct的攻击变得更轻易了。而非常非常非常非常重要的一个更新是,现在补全了unsortedbin进入largebin的双向链表检查,这意味着几乎横跨了一个时代的largebin attack终究落下了帷幕...还补全了针对fastbin的安全检查,但是不介绍了,因为马上它就g了

删除了所有有关于Fastbin的机制,是所有,从此之后再无fastbin。

Glibc2.43中规范了对于mmap_chunk的结构,同时增加了对于huge_page的支持,值得注意的是mmap_chunk的prev_size处存在一个hp_flag(huge_page),和prev_inuse_flag一样,它占用的也是0x1同时因为huge_page的引入,更改了thp模式的一些源码,能看懂的部分就是现在由页向下对齐(扩充size)改为了页向上对齐(缩减size)(似乎不是很重要?不知道与sysmalloc会不会有关系)“TCACHE is never NULL”,Glibc2.43给tcache设置了三个状态inactive->表示还没决定是否分配真正 tcache; disabled->表示 tcache 被禁用; 其他值->表示真正 live tcache所以,这也带来一个巨大的改变,当首次malloc/calloc时不再初始化tcache!!而这对于CTF中堆利用可以说是一个利好,因为tcache_prethread_struct不再默认为slot_0_chunk了,这也就意味着堆溢出等漏洞可以进行tcache_prethread_struct的攻击了而真正的tcache初始化如今交给了free图不知道几:在__libc_free的末尾判断如果tcache处于inactive就进行初始化所以流程就是:第一次 free: num_slots 为 0,放不进去 ->发现 inactive ->初始化真实 tcache ->重新 free

为什么没有在Glibc2.42展开讲tcache_largebin的get呢,因为在Glibc2.43中补充了nb要与chunksize相等的条件:什么意思?举个例子,某个tcache_largebin链表中存放了0x500 -> 0x600 -> 0x800三个堆块,如果此时我malloc一个0x580的chunk

在Glibc2.42的情况下,tcache_location_large找到第一个>= 0x580 的 chunk:发现是0x600,然后会直接返回 0x600这个堆块。

而在Glibc2.43的情况下,tcache_location_large虽然同压根会找到0x600这个chunk,但是在tcache_get_large中不能通过nb != chunksize(te)的检查,因此会返回null。

换而言之,在Glibc2.42中,我们可能是可以拿到比我们所申请的size更大的tcache_largebin堆块的(未实际验证,仅从源码分析233),但是在Glibc2.43中不行,后续验证一下是不是这样的,如果是的话可能是一个出题素材)剩下一些就是细枝末节的优化,对堆分配并无大影响

本来这篇WP是想主要写我在这题调试过程中遇到的一些困难和阻碍的,但在前面对Glibc的更新总结后,发现这些阻碍主要都是因为对更新不了解所导致的...所以学堆看源码还是一个很重要的能力的!!那么接下来就简要写一写这道题的利用链和高版本的特性吧!

其实这道题的逆向工程并不复杂,是一个很经典的增改查删程序

主要漏洞点也并不难找,而且可以说是很好利用的类型,一是edit时能造成0x40的堆溢出


冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

最后于 2026-5-7 08:30 被F0xm1ao编辑 ,原因:
上传的附件:
收藏
点赞 ・10
打赏
分享
最新回复 (11)
雪    币: 1898
活跃值: (3920)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
2 楼


1

最后于 2026-5-9 16:48 被s1nec-1o编辑 ,原因:
2026-5-9 16:47
0
雪    币: 2687
活跃值: (856)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
3 楼
s1nec-1o 1
师傅没来得及看到您回复的内容)是这道题有什么别的思路吗,可以讨论一下!
2026-5-10 21:26
0
雪    币: 2703
活跃值: (4009)
能力值: ( LV12,RANK:286 )
在线值:
发帖
回帖
粉丝
4 楼
我见过pwn最nb的师傅之一
2026-5-12 08:15
0
雪    币: 648
活跃值: (510)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5 楼
最后于 2026-5-14 18:35 被B1t3编辑 ,原因: 发现评论的技术细节有点小问题,修改一下
2026-5-14 15:37
0
雪    币: 648
活跃值: (510)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6 楼
最后于 2026-5-14 20:28 被B1t3编辑 ,原因: 添加了点内容
2026-5-14 20:26
0
雪    币: 2687
活跃值: (856)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
7 楼
2026-5-15 15:15
0
雪    币: 648
活跃值: (510)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8 楼
F0xm1ao B1t3 师傅,我看了一下这题,他的 _exit() 函数不是由系统调用的那个 _exit() 你IDA里面双击的点进去可以看到他是由 exit() 函数封装 ...
但是我的学长就是最后打apple2的啊
2026-5-15 15:26
0
雪    币: 648
活跃值: (510)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9 楼
F0xm1ao B1t3 师傅,我看了一下这题,他的 _exit() 函数不是由系统调用的那个 _exit() 你IDA里面双击的点进去可以看到他是由 exit() 函数封装 ...
我问了一下我学长,他是利用assert_bort触发的
2026-5-16 20:13
0
雪    币: 648
活跃值: (510)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10 楼
F0xm1ao B1t3 师傅,我看了一下这题,他的 _exit() 函数不是由系统调用的那个 _exit() 你IDA里面双击的点进去可以看到他是由 exit() 函数封装 ...
还有这题泄露堆地址不一定要走calloc一个更大的堆块使得Old Top进入Largebin从而泄露堆地址,你在free将一个chunk放入tcache之后这个chunk的fd指针的值为0,但是tcache的fd指针会被 safe-linking 加密,加密方式为    fd = (pos>>12)⊕real_ptr ,也就是说我们利用show 泄露了fd指针的值之后将其进行 <<12 的运算即可还原出一个堆指针,从而即可泄露堆地址
2026-5-16 20:18
0
雪    币: 2687
活跃值: (856)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
11 楼
B1t3 还有这题泄露堆地址不一定要走calloc一个更大的堆块使得Old Top进入Largebin从而泄露堆地址,你在free将一个chunk放入tcache之后这个chunk的fd指针的值为0,但是tca ...
嗯嗯,我最后的exp用的就是用本身的指针来得到的,另外,能问问那位学长是走哪条assert触发的IO刷新吗,似乎新版本的assert只会触发fflush(stderr),是通过打stderr劫持的控制流吗,确实没在高版本打过stderr这个流,想学习一下,感谢!
2026-5-16 23:44
0
雪    币: 648
活跃值: (510)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12 楼
F0xm1ao 嗯嗯,我最后的exp用的就是用本身的指针来得到的,另外,能问问那位学长是走哪条assert触发的IO刷新吗,似乎新版本的assert只会触发fflush(stderr),是通过打stderr劫持的控制 ...
我学长说就是触发io里的一个报错然后就会用err log的形式输出错误信息,应该就是用stderr来触发,打IO的
2026-5-18 13:19
0
游客
登录 | 注册 方可回帖
返回