Double free 漏洞复现与利用

2018体系结构安全大作业
申明:转载请注明出处
选题:2015 年 0ctf 题
题目描述:提供记事本功能的二进制文件,找出其漏洞,get shell

第一章 操作说明

为方便老师审核作业,本报告将操作说明放在第一章。
操作流程如下:
1 、安装 netcat 。
2 、安装 pwntools 库。命令: pip install pwntools (安装过程中,一定要保证网络畅通,曾经因为网不好装这个库装了一个下午。。。)
3 、进入 netcat-0.7.1/src 文件夹,将 freenote 文件(可执行文件)和 zy ( python 编写的 exploit 文件)拷本到 src 文件夹下。
4 、打开一个终端进入 netcat-0.7.1/src 目录,执行命令: ./netcat -e bash -lvp 10003

5 、再打开一个终端,执行命令: python zy

第二章 相关原理简述

本报告只重点讲解本实验需要用的的知识,其他的相关知识请自行查阅。

2.1 堆管理机制

什么是堆

堆是在程序运行时 , 而不是在编译时,请求操作系统动态分配给自己的内存,程序对其访问和对一般内存的访问没有区别。

堆的特点
1 )堆的基本单位是一块块连续的内存空间。
2 )堆在内存空间中不一定连续。
3 )堆可由用户管理。

堆的基本单位 —— chunk

堆的基本单位是 chunk 。在 glibc malloc 中将整个堆内存空间分成了连续的、大小不一的 chunk ,即对于堆内存管理而言 chunk 就是最小操作单位。 Chunk 总共分为 4 类: allocated chunk; free chunk; top chunk; Last remainder chunk 。从本质上来说,所有类型的 chunk 都是内存中一块连续的区域。本报告重点说明 allocated chunk 以及 free chunk ,前者表示已经分配给用户使用的chunk ,后者表示未使用的 chunk。

chunk 的特点

1 )数据区数据和指针是混用的,当本 chunk 是空闲的时候,数据区的前两个内存单元存放指针,与其他 chunk 一起形成双向环链表。

2 ) presize 和 size 不仅仅具有标识前 chunk 和本 chunk 的大小的作用,还具有寻找前一个和后一个chunk 的地址的作用,前一个 chunk 的地址 =chunk 地址 -presize ,后一个 chunk 的地址 =chunk 地址 +size 。

堆的管理

chunk 在申请时,先查找 fast bin 中是否含有符合要求的 chunk 块,如果有则从 fast bin 中获取,如果没有则查找 unsorted bin 。本报告中,漏洞程序中 chunk 的大小都大于 faastbin 的范围,所以在unsorted bin 中获取 chunk 块。chunk 在使用时由使用它的进程管理,属于 allocated chunk 。释放后属于 free chunk ,如果大小是 fast bin 范围,放入 fast bin 中,否则放入 unsorted bin 中。

本报告中,漏洞程序中 chunk 的大小都大于 faastbin 的范围,所以就存放在 unsorted bin 中。

2.2 相关函数

malloc 函数
函数形式: void *malloc(long NumBytes)
功能:该函数分配了 NumBytes 个字节,并返回了指向这块内存的指针。如果分配失败,则返回一个空指针( NULL )。
注意:返回的指针指向数据区,不是指向真正的 chunk 头部。
free 函数
函数形式: void free(void *FirstByte)
功能:将之前用 malloc 分配的空间还给程序或者是操作系统,也就是释放了这块内存,让它重新得到自由。
注意:如果在 free 过程中发现 free 的 chunk 前后有空闲的 chunk ,则会触发 unlink 操作和堆块合并, 合并后再加入 unsorted bin。
关键代码如下:
FD = P->fd
BK = P->bk
FD->bk =BK
BK->fd =FD

2.3 double free 漏洞

见链接:
http://d0m021ng.github.io/2017/02/24/PWN/Linux%E5%A0%86%E6%BC%8F%E6%B4%9E%E4%B9%8BDouble-free/

其实我也有尝试过自己写的,但是发现自己怎么讲也没有人家讲的清楚 QAQ 。。。所以想了想还是直接上链接吧。

2.4 PLT 表和 GOT 表

见链接

https://blog.csdn.net/qq_18661257/article/details/54694748


第三章 代码分析

3.1 poc 动态分析

运行程序,尝试进行二次删除,发现程序崩溃,猜测是 double free 漏洞.

3.2 IDA 逆向静态代码分析

3.2.1 程序内存架构

因为笔记的申请空间最小为( 128+16 ) byte ,所以不会使用到 fastbin 。

由源程序和内存结构可知,在程序的初始化阶段,程序在堆的开头申请了一块内存用于存放一个 table ,这个 table 在堆的最底部,用于记录之后各个笔记堆块的基本信息。值得注意的是,这个 table 本身也是一个 chunk ,所以它的开头也有 presize 和 size 。
这个 table 的构成是:
堆头 + 笔记上限 + 笔记数目 +note 数组
这个 table 的大小为:
16+ ( 8+8+(8+8+8)×256 ) =16+6160=6176byte ,换算成 16 进制为 0x1820 。

3.2.2 漏洞点

1 )漏洞点 1 :笔记内容缺少字符结束符号
新建笔记时,会 malloc() 一块大小为 128-byte 对齐的空间。而输入和存储笔记内容时,结尾不会自动补 '\0' ,并且程序分配内存时也没有多开额外的空间,所以输出笔记时,是选择将所分配的内存信息全部输出,把后面接续的内容全部打印出来。如果巧妙的让某一个非使用中的 chunk 的 fd 位指向另一個chunk ,并且让新建笔记的内容刚好接上,就可以把 chunk 的地址泄漏出来,然后再通过对内存地址的计算,就能找出堆的基址。

2 )漏洞点 2 : free 操作后没有对指针置 NULL ,留下 double free 漏洞。

第四章 堆基址获取 —— ASLR 保护基址绕过

Step1 :

新建 5 个笔记,就会申请五个连续的 chunk ,每个 chunk 的大小都为 128byte+16byte 。

step2 :

释放 chunk0 和 chunk1, 两块 chunk 合并,合并后的 chunk 头部为 chunk0 的头部。释放 chunk3 和chunk4, 两块 chunk 发生合并,合并后的 chunk 头部为 chunk3 的头部。因为 chunk2 没有释放,所以 chunk2 前面和后面的堆块不能发生合并,而是依靠双向链表连接在一起,所以此时, chunk3 的 fd指向 chunk0 的头部, chunk0 的 bk 指向 chunk3 的头部。

释放 chunk2 ,此时所有的笔记都被删除了。

Step3 :
新建一个笔记,笔记的长度要求必须要占到原来 chunk3 的区域,也就是刚刚覆盖了原来 chunk3 的presize 和 size ,但是不能覆盖了 chunk3 的 fd 。
所以长度应该是:( 128+16 ) ×3=432
因为源程序在分配内存时,会用笔记长度除以 128 向上取整数,记取得的整数为 Z ,则分配的内存为128×Z ,也就是说,分配的内存必须为 128 的整数倍,所以实际上分配的内存是大 432byte+16byte的。

Step4 :

输出获得对应的指针。输出笔记的时候,因为缺少结束符号,所以程序会把笔记内存的所有内容全都输出,进而造成了堆地址的泄漏。

Step5 :
由内存格局计算堆基址
由第二章所述的程序内存分布可知,存储笔记的 chunk 是从 “ 堆基址 +table 内存大小 ” 地址处正式开始的,
也就是说 chunk0 的头部地址 = 堆基址 +table 内存大小, chunk0 的地址 = 堆基址 +table 内存大小
+16byte 。
所以,当我们利用漏洞得到 chunk0 的头部地址时,就可以根据它算出堆基址,堆基址 =chunk0 的头部
地址 -0x1820 。

攻击结果:


第五章 利用 double free 漏洞实现任意地址写
                     ——改写 GOT 表(绕过 DEP 保护机制)

堆数据的变化过程如下图所示:

高地址
Step1:
新建四个笔记(申请 4 个 chunk ),构造 chunk0 如图所示,为之后的 double free 任意地址写做准备。chunk1 写入字符串 “ sh” ,为之后系统调用提供参数。另外,再申请两个 chunk ,分别为chunk2 、 chunk3 。

Step2:

释放 chunk2 、 chunk3, 但是由于程序释放堆块后并没有把指针置 NULL ,导致悬空指针存在。

Step3:
此时,再次新建一个笔记 chunk2’ ,笔记长度要求可以覆盖到原来 chunk3 的数据区,这是为了让chunk3 指针能够指到这个新申请的 chunk 块。
对 chunk2‘ 的内容进行精心构造,将其内容伪造成了两个假的 chunk 块,这样就造成了 chunk3 指针指向了一个假的 chunk 块,这里我们称它为 fake chunk 1 。

值得注意的是,根据 fake chunk 的 presize 和 size 可知, presize=416 , fake chunk1 的上一个chunk 是 fake chunk0, 并且因为 size 位的最后一位是 0, 所以认为前一个 chunk ,也就是 fake chunk0 是空闲态的。

Step4:

再一次释放 chunk3 块,其实之前已经释放过了,这里我们又构造了一个假的 fake chunk 1 ,让程序二次释放,也就是利用了 double free 漏洞。

Fake chunk 在释放时,由于不能存在连续空闲 chunk 的 规定,会检查前后的 chunk 块是否是空闲态,如果是,就会触发 unlink 拆链操作,将空闲堆块从 unsorted bin 中拆除,与当先释放堆块进行合并,合并完成后再一次放回到 unsorted bin 中。

在释放 fake chunk1 的过程中,触发了对 fake chunk0 的 unlink 操作,由于 fake chunk 0 的内容我们可以自己构造,所以这里就讲 fake chunk 0 的 fd 与 bk 分别设为:

fd=heap_base +0x18 , bk=heap_base + 0x20 。

unlink 操作过程如下:

FD= fake chunk 0->fd= heap_base + 0x18
BK= fake chunk 0->bk= heap_base + 0x20
FD->bk =BK (此时 FD->bk =heap_base + 0x18+0x18=heap_base+0x30 )
BK->fd =FD (此时 BK->fd =heap_base + 0x20+0x10=heap_base+0x30 )
经过 unlink , 地址 heap_base+0x30 所存的值等于 heap_base + 0x18 。

回顾一下程序的内存分布,如图所示:

记 heap_base+0x30=p, 可以发现 p 中存放的是 chunk 0 地址。现在我们通过利用 double free 漏洞,实现了对 p 的写操作,将其改成 了 heap_base + 0x18 ,即 &p=p-0x18=p-24 。由代码分析可知,当程序修改笔记 n 时,首先会查找 table 表格,找到对应的 note[n] ,然后根据note[n] 中所记录的 chunk 的地址来找到存储笔记的 chunk ,进而对其进行改写。
而现在, note[0] 中存放的地址变成了 p-0x18 ,程序执行的修改 chunk0 的操作实际上就变成了修改 p->0x18 处数据的操作,实际上是修改了 table 表的数据。
Step5:
现在我们来实现任意写操作,进入程序修改编号为 0 的笔记,构造笔记内容为:

‘ XX’+’XX’+’XX’+’free_got’

这个时候, note[0] 所记录的地址变为 free_got ( free 函数在 got 表中的表项地址),此时输出笔记,就能得到 free 函数的地址 free_addr , free 函数的地址减去 free 函数的偏移量,就得到了代码链接库的基址 libc_base ,代码基址加上 system 函数的偏移 system_off ,就得到了 system 函数的地址:

system_addr = libc_base + system_off 。

Step6:

再次改写编号为 0 的笔记,将它改写为 system_addr, 因为 note[0] 中记录的地址是 free 函数在 got 中的表项地址,改写笔记就是改写这个表项的内容,将原先 free 函数的地址改为 system 函数的地址,就变成了, free 函数的 got 表项存的是 system 函数的地址。


Step7:

释放编号为 1 的笔记,也就是 chunk1, 因为 chunk1 中存放的笔记是 “ sh” ,又因为 free 函数表项被填入了 system 函数的地址,所以表面上是执行了 free ( chunk1 ),实际上是执行了 ‘ system ( ‘ sh’ ) ’ ,这样就实现了 get shell 。

攻击结果:


第六章 netcat 工具使用

netcat 简介:
netcat( 简写是 nc) 是 linux 上非常有用的网络工具,它能通过 TCP 和 UDP 在网络中读写数据。通过配合使用其他工具和重定向,可以在脚本中以多种方式使用它。 netcat 所做的就是在两台电脑之间建立链接并返回两个数据流,进而让两台主机通过交互完成多项功能。
netcat 在实验中的使用:
在本实验中,一个终端作为服务器监听端口 10003, 另一个终端执行 exploit 向 10003 端口发送消息。

如图所示:


参考文献:

[1]https://wps2015.org/drops/drops/Double%20Free%E6%B5%85%E6%9E%90.html

[2]http://winesap.logdown.com/posts/258859-0ctf-2015-freenote-write-up

[3]https://github.com/ctfs/write-ups-2015/tree/master/0ctf-2015/exploit/freenote



猜你喜欢

转载自blog.csdn.net/vivid_moon/article/details/80497128