首页
社区
课程
招聘
[原创]glibc2.29下的off-by-null
发表于: 2020-3-1 20:44 22514

[原创]glibc2.29下的off-by-null

2020-3-1 20:44
22514

在看雪发的第二篇帖子,回归老本行pwn。
在经典的2.23版本的glibc下,off-by-null算得上是堆利用的一种中等难度的利用技巧,简单来说,这种漏洞发生在读入数据的过程,存在多输入一个字节并且该字节固定为\x00的问题,利用这一个看似不可控却很有作用的字节,攻击者就可以精心构造出堆上的布局,进行任意地址的读写,构造的过程也可以说十分巧妙。
不过慢慢在各种CTF赛题的狂轰乱炸之下,off-by-null也算形成了比较固定的公式,开始被玩坏了。
想到目前已经2020年了,希望出题人也好,选手也好,不要再把思路局限于2.23了,甚至于2.27也算是一个相对过时的版本了,因为除了多了一个tcache机制,其实等于裸奔的fastbins,把tcache塞满之后,其余的区别并不大。所以在这里,我会详细解析一下在各项保护更加完善的2.29下,是如何进行off-by-null的,希望可以起到一些抛砖引玉的作用。

先从构造起来最简单最基本的2.23说起,其实也是一个自己复习知识的过程,由简入繁,才能更好地理解问题。
这里我画了一个简单的2.23下off-by-null的构造流程图,如果你对这个过程足够熟练,看着这个图就可以撸出2.23下off-by-null的exp了:

这里说前向合并或者后向合并容易产生歧义,所以接下来,我会用高低地址来表示。先简述一下流程:

这里主要利用到了free时的两个机制,一个是不满足fastbin的时候,会判断是否向低地值合并:

p就是chunk2,因为此时chunk2的P被off-by-null盖成了0,所以开始向低地值合并,通过prevsize找到了需要合并的chunk,这里也就是chunk0,然后触发第二个机制,也就是unlink。

unlink的作用就是在bin双向链表上把其中的一个chunk卸载下来,曾经稚嫩的unlink还是个宏,后来地位上升了,现在早已经变成函数了。部分能用得到的宏代码:

这里的P已经就是图中的chunk0了。有一段该死的检测:

主要是在检测P是否在一个真正的双向链之中。这也正是off-by-null构造的巧妙之处,chunk0被我们提前丢进了unsortedbin之中,所以当对其检测时,就利用了系统已经帮我们构造好了的链表绕过了检测。(这里的chunk0的fd和bk里面的值相同,都是0x7f....这种,这就是unsortedbin这条双向链的链表头,该链因为只插进了chunk0这一个元素,所以链表头里的fd和bk也会指向chunk0)

别急,接下来还有一步要处理,低地址检测之后还会检测是否要和高地址合并:

这里也就是检测下一个chunk的下一个chunk的P位,因为当前chunk是否被使用是标记在比它地址更高的下一个堆块的P位上的。这也是图里步骤2要构造的东西,为了不让系统执行向高地址合并,用正常的chunk,或者用自己伪造的chunk都是可以的。

之后chunk0直到chunk2,同时连带着中间的这么一大块空间,就都被送进了unsortedbin,当我们再次从unsortedbin切割的时候,就会分配出中间的各种地址,达成堆块复用的效果了。

2.27我就不介绍了,把tcache填满了,其他的跟2.23没什么区别。
2.29的代码进行了更加完善的保护,在向低地值合并的时候,加了一行代码:

检测了chunksize(p) != prevsize,以图为例,就是检测chunk0的size和chunk2的prevsize是否相等。仔细想想,这让我们之前的那种利用方式就变成了不可能。chunk2的prevsize是我们为了索引到chunk0而构造的,而chunk0和chunk2中间还存在准备被复用的chunk,所以这种构造方式,这个保护是绕不过去的。

2.23的链表构造,是系统帮助我们构造出来的。2.29也同样要想办法让系统帮我们构造出来。这里的手法的核心思想就是利用系统的残留数据。

首先根据上述的知识,要达成的目的就是伪造出一个chunk0,使其能够绕过这两个检测。

一个是向低地值合并的检测:

另一个就是unlink时候的检测

接下来的操作全程关闭aslr,为了保证100%的成功率。

这里的思路是利用largebin残留的fd_nextsize和bk_nextsize两个指针,smallbin残留的bk指针,以及fastbin的fd指针。构造出一个如下的布局:

2.29下的off-by-null的题有2019-TCTF-FINAL-babyheap,不过这道题因为有off-by-null发生在edit功能中而不是add中,所以也很简单。
所以接下来,我会以2019-BALSN-CTF-plaintext为例,来详细介绍off-by-null的流程。
当然,因为原来的题目文件在我本地有点问题,所以我重新编译了一份文件,源代码和文件我会放到附件里。(我这个文件也有点问题,因为我是在2.27环境编译,在2.29环境运行的,不过关注点还是主要放在利用手法上嘛hh)

1.首先做一些提前的布置。

用到的最关键的堆块主要是15,0-7的目的都是为了填充堆空间,来调整所需堆块的偏移,这里不完全相同,根据情况自行调整。8-14正好7个chunk是为了对tcache进行操作。16是防止15和topchunk合并。这样布局之后,delete 15 会将其放入unsortedbin:

可以看到此时chunk15堆地址的第二字节(因为是小端序)是\x00,这就是我们前面填充的原因,因为off-by-null不可避免的要在输入后补入\x00,堆地址如果是这个样子,那么我们覆盖第一个字节后,第二个字节被补\x00后就相当于不会产生变化。
而chunk15的size也要尽量的大,因为后续会对他各种切割,并且要保证切割后的大小移然超过tcache的限制,方便我们后续直接让他切割后的部分作为被off-by-null的目标而不用处理tcache。

2.对fake chunk的fd和bk进行布局。

当malloc的请求大于当前unsortedbin的size时,会触发malloc源码中的双循环机制,将unsortedbin里的chunk按bk顺序依次压入对应的bin中,然后再从topchunk切割出对应size的空间。
所以上一步的chunk15就被放进了largebin,同时此时因为它所在的largebin中只有它一个chunk,那么此时的fd_nextsize与bk_nextsize会指向其本身,关于largebin的规则这里就不展开说了,有些复杂和乱,如果有时间我会再详细介绍一下largebin规则的。

这里的chunk17的请求,因为size小于largebin里的size,所以会在largebin里切下来一小块,然后剩余部分放到unsortedbin里面。关于chunk17里所填入的值都是根据后续产生的指针不断调整出来的,这里暂时是无法观察出这几个值都是怎么来的。不过其作用是伪造出一个fake chunk:

3.构造出fake chunk的fd,使fd->bk = fake chunk,满足条件

前面的18-21还是从刚才进到unsortedbin的chunk15中切出来的,之后用到第1步准备的7个tcache填满对应的他tcache_entry链,之后20和18就会被放入fastbin中:

清空tcache防止后续被干扰,之后申请一个超过对应fastbin的请求,就会触发机制把当前fastbin里的压入到smallbin中,当然用不了0x400那么大,不过写的时候没注意,之后不想改了hh。

注意fastbin取链是遍历fd,而smallbin或者其他任何双向链的bin取链都是遍历bk,当然largebin有所不同,所以此时申请0x28,就会从smallbin的bk取对应的chunk分配给我们,而这个chunk的bk如图所示,残留着堆指针可以被我们操作:

smallbin申请之后,会有一个机制将对应smallbin链中余下的chunk放入对应的tcache里,图中可以看到。然后可以看到刚才取出来的chunk的bk被我们覆盖成了fake chunk的地址,这也就是第2步里为什么覆盖成\x40的原因

4.使fake chunk的bk->fd = fake chunk,满足条件。

chunk 17就是fake chunk,fake chunk的bk是之前通过largebin残留的bk_nextsize,这里指向包裹它的正常堆块,所以这里要改变其fd,要用到fastbin残留的fd指针。不用tcache的原因,是因为tcache在2.29下多了一个key的机制,也就是在bk所对应的位置会加一个指向其tcache的指针,这里不细说了。反正会破坏掉我们对fake chunk伪造的size区域。
进入fastbin:

分配之后:

5.触发off-by-null。

首先还是清空tcache防止被干扰,然后分配一个chunk23用于接下来对chunk24进行off-by-null。chunk25则是防止进行off-by-null的chunk24被合并掉。这里的0x5f8也是通过上述的那些size分配调整出来的,可以正好分配出剩余的unsortedbin,而且对应的size为0x601。这样的size有两个好处,一是足够大,这样就不用考虑还要先填满tcache,第二就是进入就是off-by-null之后,我们就不需要再对nextchunk进行伪造了,借助系统帮我们完成

对chunk24进行修改后:

可以看到通过prevsize可以找到我们的fake chunk,同时也是第2步里,fake chunk的size选择为0x521的理由。

关键位置我都框出来了,这时可以来人肉判断一下是否能通过两个检测:

向低地值合并的检测。

unlink时候的检测。

很明显全部满足!bingo!最后free chunk24构造off-by-null成功:

6.泄露libc和getshell。

最后有了堆块复用就随便玩了,后续步骤就不说了。

2.29下的off-by-null到这里就算分析完了,思路是来自于EX师傅的博客,我只是详细地对其中的关键点解释了一下。
当然2.29下还有一些其他的很有意思的新思路和利用手法,这里因为我在过几天的某比赛的出题里用到了这种手法,所以我会在比赛结束后再分享给大家。


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 9
支持
分享
最新回复 (11)
雪    币: 26245
活跃值: (63297)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
2
感谢分享!
2020-3-3 14:01
0
雪    币: 475
活跃值: (401)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
3
学长tql,pwn巨佬
2020-3-3 21:05
0
雪    币: 1120
活跃值: (347)
能力值: ( LV8,RANK:148 )
在线值:
发帖
回帖
粉丝
4
时间的伤 学长tql,pwn巨佬
就尬吹
2020-3-4 11:42
0
雪    币: 1120
活跃值: (347)
能力值: ( LV8,RANK:148 )
在线值:
发帖
回帖
粉丝
5
Editor 感谢分享!
本来还想混个优秀贴的
2020-3-4 11:43
0
雪    币: 61
活跃值: (2390)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
6
目前知道的过几天的比赛有
2020-3-4 16:05
0
雪    币: 1120
活跃值: (347)
能力值: ( LV8,RANK:148 )
在线值:
发帖
回帖
粉丝
7
iddm 目前知道的过几天的比赛有[em_48]
没错,就是它!尽请期待
2020-3-4 21:22
0
雪    币: 2510
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
感谢分享
2020-3-11 12:52
0
雪    币: 219
活跃值: (38)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
9
师傅您好  我想问一下你这个插件是?  就是peda上的heapinfo命令?
2020-8-8 15:47
0
雪    币: 296
活跃值: (236)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
风水大师,在下甘拜下风
2021-6-16 21:09
0
雪    币: 296
活跃值: (236)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
大师,不对啊, 在第 "2.对fake chunk的fd和bk进行布局。" 中, 程序有00截断,large bin的fd_nextsize不是0x555555760040,而是0x555555768840咋办???很大概率是不00啊!!!
2021-6-16 21:45
0
雪    币: 1
活跃值: (188)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12

捉虫:清空tcache防止后续被干扰,之后申请一个超过对应fastbin的请求,就会触发机制把当前fastbin里的压入到smallbin中,当然用不了0x400那么大
翻阅了一下 这个还必须是largebin的大小(最小为0x400) 小了还不行 不然fastbin进不了smallbin (如果fastbin进不了smallbin不会造成利用失败当我没说)
原因:这里的fastbin chunk 进 smallbin chunk的时候是
  else
    {
      idx = largebin_index (nb);
      if (atomic_load_relaxed (&av->have_fastchunks))
        malloc_consolidate (av);
    }
这里的malloc_consolidate做的 如果nb(实际chunk大小)不是largebin 大小触发不了malloc_consolidate
另外一个malloc_consolidate 触发的位置是use_top后面的位置,但是之前就已经从top里面分配chunk返回了binmap从大块里面切割小块(最开始分配的largrebin上切割下来的,也可以看到这里的切割因为nb不是smallbin所以没有更新last_reminder对应了原图

最后于 2021-7-2 19:15 被骨灰小旺编辑 ,原因:
2021-7-2 18:04
0
游客
登录 | 注册 方可回帖
返回
//