首页
社区
课程
招聘
[原创]从2026CCB决赛堆题CreditMarket学习Glibc2.39后ptmalloc的机制更新
发表于: 11小时前 246

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

11小时前
246

每一次复现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的堆溢出


传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 8小时前 被F0xm1ao编辑 ,原因:
上传的附件:
收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回