前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >how2heap总结-上

how2heap总结-上

作者头像
信安之路
发布2018-08-08 14:17:13
5540
发布2018-08-08 14:17:13
举报
文章被收录于专栏:信安之路

0x00 前言

"how2heap"是shellphish团队在Github上开源的堆漏洞系列教程. 我这段时间一直在学习堆漏洞利用方面的知识,看了这些利用技巧以后感觉受益匪浅. 这篇文章是我学习这个系列教程后的总结,在此和大家分享.我会尽量翻译原版教程的内容,方便英语不太好的同学学习。

源码部分我的可能和原版教程不一样,改动的地方我是为了方便自己理解,所以还是建议大家看这篇总结之前去看原版教程。

不过在学习这些技巧之前,建议大家去看一看华庭写的"Glibc内存管理-Ptmalloc2源码分析"

http://paper.seebug.org/papers/Archive/refs/heap/glibc%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86ptmalloc%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90.pdf

在此也给出原版教程链接:

https://github.com/shellphish/how2heap

0x01 测试环境

Ubuntu 16.04.3 LTS x64 GLIBC 2.23

0x02 目录

1. firtst_fit

2. fastbin_dup

3. fsatbin_dup_into_stack

4. unsafe_unlink

0x03 first_fit

源码:

输出:

翻译: 这个程序并不展示如何攻击,而是展示glibc的一种分配规则. glibc使用一种first-fit算法去选择一个free-chunk. 如果存在一个free-chunk并且足够大的话,malloc会优先选取这个chunk. 这种机制就可以在被利用于use after free(简称uaf)的情形中. 先分配两个buffer,可以分配大一点,是不是fastbin也无所谓. 1st malloc(512): 0x662420 2nd malloc(256): 0x662630 我们也可以继续分配... 为了方便展示如何利用这个机制,我们在这里放置一个字符串 "this is A!" 我们使第一个分配的内存空间的地址 0x662420 指向这个字符串"this is A!". 然后free掉这块内存... 我们也不需要free其他内存块了.之后只要我们用malloc申请的内存大小小于第一块的512字节,都会给我们返回第一个内存块开始的地址 0x662420. ok,我们现在开始用malloc申请500个字节试试. 3rd malloc(500): 0x662420 然后我们在这个地方放置一个字符串 "this is C!" 第三个返回的内存块的地址 0x662420 指向了这个字符串 "this is C!". 第一个返回的内存块的地址也指向这个字符串!


关于first-fit没太多可讲的,这里也解释得很清楚是咋回事.不过这里提到了uaf,网上关于uaf的文章也很多,我就不多说了.在这里推荐一篇吧.

http://bobao.360.cn/learning/detail/3379.html

0x04 fastbin_dup

源码:

输出:

翻译: 这个程序展示了一个利用fastbin进行的double-free攻击.

攻击比较简单.

先分配三块内存. 1st malloc(8): 0x1f89420 2nd malloc(8): 0x1f89440 3rd malloc(8): 0x1f89460 free掉第一块内存... 如果我们再free 0x1f89420 的话,程序就会崩溃,然后报错.因为这个时候这块内存刚好在对应free-list的顶部,再次free这块内存的时候就会被检查到. 所以我们另外free一个,我们free第二块内存 0x1f89440. 现在我们再次free第一块内存,因为它已经不在链表顶部了. 现在我们的free-list有这三块内存[ 0x1f89420, 0x1f89440, 0x1f89420 ]. 如果我们malloc三次的话,我们就会得到0x1f89420两次! 1st malloc(8): 0x1f89420 2nd malloc(8): 0x1f89440 3rd malloc(8): 0x1f89420


这里展示了一个简单的double-free,因为 free() 的过程中只是检查fastbin顶部的chunk是否和当前要free的chunk一样。(至于为什么不检查后面的,我猜可能是因为效率问题。 = =)

关于double-free更具体的利用在下面介绍.

0x05 fastbin_dup_into_stack

源码:

输出:

翻译:

这个程序更具体地展示了上一个程序所介绍的技巧,通过欺骗malloc来返回一个我们可控的区域的指针(在这个例子中,我们可以返回一个栈指针) 我们想要malloc返回的地址是这个 0x7ffef0f6a078. 首先分配三块内存: 1st malloc(8): 0x220f420 2nd malloc(8): 0x220f440 3rd malloc(8): 0x220f460 free掉第一块内存... 和上一个程序一样,我们再free第一块内存是不行的,所以我们free第二块. free 0x220f440 现在我们可以free第一块了. 当前的free-list是这样的 [ 0x220f420, 0x220f440, 0x220f420 ] 我们将通过在第一块内存 0x220f420 上构造数据来进行攻击. 先把链表上前两个地址malloc出来. 1st malloc(8): 0x220f420 2nd malloc(8): 0x220f440 现在的free-list上面就只剩下了[ 0x220f420 ] 尽管现在0x220f420仍然在链表上,但我们还是可以访问它. 然后我们现在写一个假的chunk-size(在这里我们写入0x20)到栈上.(相当于在栈上伪造一块已经free的内存块) 之后malloc就会认为存在这么一个free-chunk,并在之后的内存申请中返回这个地址. 现在,我们再修改 0x220f420 的前8个字节为刚才写下chunk-size的那个栈单元的前一个栈单元的地址. 3rd malloc(8): 0x220f420,将栈地址放到free-list上. 4th malloc(8): 0x7ffef0f6a078 成功返回栈地址.


这个程序和上个程序差不多,区别在于,这个程序在double-free之后多伪造了一个chunk在链表上,进行了第四次malloc,将我们可以控制的一个地址malloc了出来. 当然,这个地址也可以是堆地址,只要可控(因为我们至少要伪造好size字段来逃过检查). 在伪造好的堆块被放到链表之前,free-list是这样的(图中的地址的值和上面程序直接输出的不一样,是因为我的系统开了ASLR.)

通过double-free后的第三次malloc将伪造的堆块地址放在了free-list,效果如下

也许有人会有疑问,为什么链表上还会多出来一个地址? 那是因为我们伪造的堆块的fd指针位置刚好是这个地址的值.可以查看一下内存:

当然这不是我们刻意设置的. 不过这可能会给我们后面的malloc带来一定影响,所以,我们可以在malloc出我们的伪堆块之前确保fd字段为0.

0x06 unsafe_unlink

源码:

输出:

翻译: 前两句忽略 (- , -) 这个技术可以被用于当你在一个已知区域内(比如bss段)有一个指针,并且在这个区域内可以调用unlink的时候. 最常见的情况就是存在一个可以溢出的带有全局指针的缓冲区. 这个例程的关键在于利用free()来改写全局指针chunk0_ptr以达到任意地址写入的目的. 这个全局指针 chunk0_ptr 在0x602060,指向 0x1a35420. 而我们要去改造的victim chunk 是 0x1a354b0. 我们开始在chunk0内部伪造一个chunk. 先设置一个fd指针,使得p->fd->bk == p('p'在这里指的是chunk0) 再设置一个bk指针,使得p->bk->fd == p. 经过这些设置之后,就可以pass掉

"(P->fd->bk != P || P->bk->fd != P) == False"

这个校验了. Fake chunk fd: 0x602048 Fake chunk bk: 0x602050

我们还需要确保,fake chunk的size字段和下一个堆块的presize字段(fd->presize)的值是一样的. 经过了这个设置,就可以pass掉

"(chunksize(P) != prev_size (next_chunk(P)) == False"

的校验了. 因此,我们设置fake chunk的size字段为chunk0_[-3]:0x00000000(关于这里可能有人看不明白,我在后面细讲.) ... 我们假设我们在chunk0处有一个溢出,让我们去修改chunk1的头部的信息. 我们缩小chunk1的presize(表示的是chunk0的size),好让free认为chunk0是从我们伪造的堆块开始的. 这里比较关键的是已知的指针正确地指向fake chunk的开头,以及我们相应地缩小了chunk. 如果我们正常地free掉了chunk0的话,chunk1的presize应该是0x90,但是这里被我们修改为了0x80. 我们通过设置"previous_in_use"的值为False,将chunk0标记为了free(尽管它并没有被free) 现在我们free掉chunk1,好让consolidate backward去unlink我们的fake chunk,然后修改chunk_ptr. ... 现在,我们就可以利用chunk_ptr去修改他自己的值,来使它指向任意地址. ok,现在chunk0_ptr指向了我们指定的地址,我们用它来修改victim string Original value: Hello!~ New Value: BBBBAAAA


在这里,我们通过构造一个假的chunk来欺骗free去调用unlink,然后通过unlink来修改内存.以达到任意地址读写的目的. 关键点就在于信息的伪造,如下为刚开始申请的两块chunk的metadata的情况:

构造好了数据之后的metadata:

之后的free就可以调用unlink去修改内存了. 前面翻译部分我说过有个地方可能让人不太搞得明白怎么回事,也就是这里:

我们还需要确保,fake chunk的size字段和下一个堆块的presize字段(fd->presize)的值是一样的. 经过了这个设置,就可以过掉"(chunksize(P) != prevsize (next_chunk(P)) == False"的校验了. 因此,我们设置fake chunk的size字段为chunk0[-3]:0x00000000

不是说要确保chunk1的presize和fake chunk的size是一样的才能通过检查吗?为什么这里明显不一样也能通过?(fake-chunk->size==0 ; chunk1->presize == 0x80) 而且和chunk_0[-3]有啥关系?(这个我是真不知道有啥关系. - -!) 我们先忽略chunk_0[-3]. 其实我试验过,在不改动其他数据的情况下将fake chunk的size字段改为0x80或者0都可以通过检查,其他的就会报错. 这里就需要知道

(chunksize(P) != prevsize (next_chunk(P)) == False

这个检查是怎么进行的. 就这里的fake chunk来说,先获取fake chunk的size值,然后通过这个size值加上fakechunk的地址再减去chunk头部大小去获取下一个chunk的presize值,然后对比size和presize是否相等. 但是这里fake chunk的size是0,也就是说在去找找一个chunk的presize的时候,实际上找到的是fake chunk的presize,两个都是0,自然就是相等的. 而我们将fake chunk的size设置为0x80也能过检查的原因是,这时候获取下一个chunk的presize是正常获取的,而下一个chunk就是chunk1,chunk1的presize已经被设置为了0x80,两个值也是刚好相等. 你们可以自己去验证一下. 成功修改后的chunk0_ptr如下所示:

修改为了chunk0_ptr所在位置往后数第三个单元的值.(一个指针大小为一个单元)

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-08-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 信安之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x02 目录
  • 0x03 first_fit
  • 0x05 fastbin_dup_into_stack
  • 0x06 unsafe_unlink
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档