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

原文发布于微信公众号 - 信安之路(xazlsec)

原文发表时间:2017-08-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码生

shell 语法 干货

942
来自专栏互联网杂技

丁点而内存知识

在C和C++语言开发中,指针、内存一直是学习的重点。因为C语言作为一种偏底层的中低级语言,提供了大量的内存直接操作的方法,这一方面使程序的灵活度最大化,同时也为...

3354
来自专栏抠抠空间

python常见模块之序列化(json与pickle以及shelve)

什么是序列化? 我们把对象(变量)从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,在其他语言中也被称之为serializatio...

4786
来自专栏用户2442861的专栏

网易面试杂谈

http://blog.csdn.net/silangquan/article/details/18013967

852
来自专栏老马说编程

(67) 线程的基本协作机制 (上) / 计算机程序的思维逻辑

上节介绍了多线程之间竞争访问同一个资源的问题及解决方案synchronized,我们提到,多线程之间除了竞争,还经常需要相互协作,本节就来介绍Java中多线程协...

2126
来自专栏企鹅号快讯

《数据库系统概念》12-文件的组织

一个数据库被映射到多个不同的文件,这些文件由底层的操作系统来维护。每个文件分成定长的存储单元,称为块(bolck),块是存储分配和数据传输的基本单元。数据库默认...

2829
来自专栏Java 技术分享

Struts2 配置文件小结

34310
来自专栏技术专栏

Scala入门与进阶(一)- 初始Scala

1182
来自专栏北京马哥教育

Python 函数库 APIs 编写指南

3194
来自专栏我就是马云飞

设计模式二十四章经之状态模式

981

扫码关注云+社区

领取腾讯云代金券