-
-
[原创]一道简单但不简约的堆题——CISCN2026初赛堆题robo分析
-
发表于: 4天前 1678
-

CISCN2026线上初赛的题目,线上只有一解,查了下发现过去五个月了还没有人写过WP,就尝试了一下,结果发现不是很难,可能是限时+看到cpp逆向大家都不愿意动手写吧(附件在结尾)

Glibc版本2.42,算很新的版本了,高版本Glibc的ptmalloc机制更新可以移步我的上一篇文章[原创]从2026CCB决赛堆题CreditMarket学习Glibc2.39后ptmalloc的机制更新,以及熟悉的保护全开

一打开IDA就是一股浓厚的cpp味,不知道大佬们cpp逆向一般怎么做比较快,我只会土办法,一边看伪代码一边还原cpp的逻辑,还原出的cpp逻辑如下
可以发现,这道题设计了一个堆管理器,维护了一个结构体,并且通过对应的结构体来确定堆块是否使用、大小,并且以初始化时的一个堆作为基地址,返回一个offset,并且查找堆块的逻辑是通过offset而非index,除此之外,add使用了calloc(Glibc2.41之后calloc也会优先使用tcache),但是add的size输入没有限制,add、edit、show、delete的次数均无限制。
如果看代码还是比较难理解这个堆管理器到底干了啥的,其实这道题我最开始理解这道题的逻辑是直接通过调试来理解的,分享一下我调试的过程。
申请了一个0x400的堆块,发现除了我们申请的之外,程序还自己申请了一个0x30的堆块,我们可以看一下内容
到这里还看不是很太懂+0x0到+0x18的对应值,但是+0x20和+0x28的值很明显存放了我们所申请堆块的用户指针和用户大小,那就只能再申请一个堆块看结果了
可以看到,确实是每申请一个新堆块就会伴随这个0x30的小块,而且也确定+0x20以及+0x28处存放的一定是堆块的用户指针和用户大小
接下来我们尝试一下free,可以发现,需要我们传入的是一个“address”,但是这里很坑,最开始试着传了0x300和768,结果发现都不能正常free,最后发现要传300。。
发现堆块也确实被正常free了(pwndbg对高版本Glibc的支持有点奇怪,对进入tcache的块判定有点问题,但是通过看堆块被写入的next指针和key可以判断其已经进入了tcache),同时,伴随堆块也同时被free了
edit和show的逻辑就一起展示了,这个传入数据要求hex,show的输出格式也需要单独写
测了一下发现没有UAF,也没有堆溢出,虽然没直接找到漏洞,但这短短几分钟的调试能帮我们少分析很大一部分代码

这个add模块对应我们手写的这段代码
不知道大家有没有看出这段代码的漏洞,其实应该要先注意到这个突兀的if,它仅仅做了两个简单的赋值,而这两个值本身都存在于栈上,再将赋完的值传给register_chunk函数进行堆注册,聪明的你肯定想到了题目描述“真的有点粗心呢~”,所以这段代码本来希望实现的逻辑应该是
是为了防止calloc申请失败而做的判定,却意外将register_chunk放到了判定外,导致calloc是否成功都会执行register_chunk函数,而chunk_pointer_1和size_2是存在栈上的变量,在循环当中,如果不对他们进行新的赋值,他们就会沿用之前的值,而这就实现了这道题真正的利用点——UAF。
我们来聊一聊这个UAF的逻辑
我们先看,如果某次calloc(1,size)申请成功, 我们给这个chunk取个名字叫chunk A,满足if (chunk != nullptr)那么紧接着进行变量赋值,chunk_pointer_1 = chunk、size_2 = size,并且将堆指针和大小作为参数传给register_chunk进行堆注册。注意,此时栈上chunk_pointer_1这个变量就是chunk A的指针。
倘若我们此时将chunk A给释放,此时程序会进行unregister_chunk,在堆管理器中删除对应的节点,然后对这个堆块实施free操作。而此时我们如果对这个堆块尝试进行write或read操作是会被ban的,因为此时这个堆块并未在堆管理器中注册
[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。