how2heap总结-上

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所在位置往后数第三个单元的值.(一个指针大小为一个单元)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值