堆溢出漏洞
实验目的
根据一个重复释放漏洞利用例子,调试利用程序(exp.py
),理解堆溢出的原理和利用过程中堆的变化。
步骤
程序运行
程序运行后大致显示了如下结果。
静态分析
把程序拖入 IDA 中进行分析,发现主要的逻辑还是比较简单的。
左上图为主要的程序逻辑,用户输入的选择数字对应了不同的子程序,其中输入 1 将进入 sub_400909
,对应了 new 操作,从中可以得到,我们最多可以分配 10 个 chunk,每一块最多 96 字节大小。输入 2 将进入 sub_4009f9
,对应了 write 操作,它也经历了严格的边界检查,利用基本上不可能。输入 3 将进入 sub_400aef
,对应了 delete 操作,其中第 18 行将指针 ptr[v1]
free 掉,在第 19 行把其中内容置空了,却没有把指向它的指针置空,留下了双重释放的漏洞。
事实上,尝试连续释放两次相同的 chunk,的确会引发系统的 core dump。如图。
动态分析 (gdb 调试)
-
当程序执行到第一个断点时,
exp.py
做了这些事情:- 新建了两个 0x60 大小的 “chunk”(恰好为边界值)
- 释放第一个堆块,释放第二个堆块,再释放第一个堆块(触发 Double Free),完成 fastbin attack。此时 heap 和 fastbin 的情况如图所示。 从中可以推断,堆的当前情况是:
- 堆的头部为
0x1a740e0
。 - 有两个大小为 0x71(属于 0x70 类)的 fastbin 已被分配,它们目前处于首尾相连状态。因为从截图可以得知,
0x1a74000
的前向指针指向了0x1a74070
,0x1a74070
的前向指针指向了0x1a74000
,刚好是一个闭环。
- 堆的头部为
-
当程序执行到第二个断点时,
exp.py
做了这些事情:-
分配了第三个堆块,大小为 0x60。
-
向其中写入地址
0x60208d
。- 值得注意的是在每次分配堆块后,有一个全局变量的数字均自增了 1,这个全局变量在每次分配堆块前均和 9 检查,证明一共最多只能进行 10 次分配操作,10 次分配,在
ptmalloc
分配方式中只有 fastbin 中有 10 个堆块满足我们的要求,这也是采用 fastbin attack 的原因。 - 下图显示堆块
0x1a74000
的前向指针fd
被篡改为了我们所写的地址0x60208d
上,而0x1a74010000000
,一个内存地址正在指向它。回环结构已被破坏。
为什么是
0x60208d
?我们需要一个大小属于 0x70 类的空闲块来满足
malloc
安全分配的需求(程序中要求最大分配 0x60 的 chunk),观察内存,可以发现0x60208d
为满足要求的内存块之一。由于小端显示原因,此图显示实际上对应于内存中的顺序为:第二列 - 第一列 - 第四列 - 第三列,与 gdb-peda 的显示不太相同。
- 值得注意的是在每次分配堆块后,有一个全局变量的数字均自增了 1,这个全局变量在每次分配堆块前均和 9 检查,证明一共最多只能进行 10 次分配操作,10 次分配,在
这次分配第三个堆块的意义是将所找到可以利用的内存地址推入 FASTBIN 链中,后续可以方便直接对内存中此处内容进行改写。
-
-
当程序执行到第三个断点时,
exp.py
做了如下事情:- 分配了大小为 0x60 的第四、第五、第六块 chunk。
- 向第五块 chunk 写入字符串
/bin/sh\x00
。 - 向第六块 chunk 写入 got 表中
free
的地址。-
这时 fastbin 中只剩下一个大小为
0x70
的块:0x1a74010000000
,原来的0x1a74000
已经指向了一个内存地址中。 -
查看现在
0x602080
的内容,发现已经被我们所写入的 got 中 free 表地址所覆盖。哦,原来0x00602018
是 got 表中free
的地址啊!
-
-
当程序执行到第四个断点时,
exp.py
做了如下事情:- 向第三块 chunk 中写入 plt 表中
system
的地址(第三块没有被free
过,现在实际上在覆盖写)。 - Bin 和 heap 相较上一步没什么变化。
- 向第三块 chunk 中写入 plt 表中
- 最终,我们释放了含有
/bin/sh
字符串的第五块 chunk,由于free
的 got 表地址已经被改写成了system
的 plt 表地址,再调用free
就相当于调用了system
,而被释放的块中含有/bin/sh
字符串,所以就相当于执行了system("/bin/sh")
,成功获取 shell。
实验难点与收获
堆的结构比较复杂,需要的前置知识非常多,理解起来也会非常难。通过学习这次的利用程序,我掌握了大概的利用思路。
其实应该再加一个断点,来观察一下 free
后 bin 和 heap 的情况,不过多半因为程序控制权转移而看不到这里的现象。
实际上,所有的利用办法,最终都是要想办法执行 system("/bin/sh")
的,但是利用的路线是不相同的。
实验思考
为什么可以在 0x602018
处发现符合要求的地址,大概是因为有一个在 dword_6020f0
的关键全局变量,所以可以推断可供利用的内存地址、全局变量等数据都会在其附近?(然后逐渐缩小查找范围……)