Pwn with File结构体(三)
前言
本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274
前面介绍了几种 File 结构体的攻击方式,其中包括修改 vtable的攻击,以及在最新版本 libc 中 通过 修改 File 结构体中的一些缓冲区的指针来进行攻击的例子。
本文以 hitcon 2017 的 ghost_in_the_heap 为例子,介绍一下在实际中的利用方式。
不过我觉得这个题的精华不仅仅是在最后利用 File 结构体 getshell 那块, 前面的通过堆布局,off-by-null 进行堆布局的部分更是精华中的精华,通过这道题可以对 ptmalloc 的内存分配机制有一个更加深入的了解。
分析的 idb 文件,题目,exp:
https://gitee.com/hac425/blog_data/tree/master/pwn_file
正文
拿到一道题,首先看看保护措施,这里是全开。然后看看所给的各个功能的作用。
new_heap, 最多分配 3个0xb0大小的chunk (malloc(0xA8))然后可以输入0xa8个字符,注意调用的_isoc99_scanf("%168s", heap_table[i]);会在输入串的末尾 添\x00, 可以off-by-one.delete_heapfree掉指定的 heapadd_ghost最多分配一个0x60的 chunk (malloc(0x50)), 随后调用read获取输入,末尾没有增加\x00,可以leakwatch_ghost调用printf打印ghost的内容remove_ghostfree掉ghost指针
总结一下, 我们可以 最多分配 3个 0xb0 大小的 chunk, 以及 一个 0x60 的 chunk ,然后 在 分配 heap 有 off-by-one 可以修改下一块的 size 位(细节后面说), 分配 ghost 时,在输入数据后没有在数据末尾添 \x00 ,同时有一个可以获取 ghost 的函数,可以 leak 数据。
有一个细节需要提一下:
在程序中 new_heap 时是通过 malloc(0xa8), 这样系统会分配 0xb0 字节的 chunk, 原因是对齐导致的, 剩下需要的那8个字节由下一个堆块的 pre_size 提供。

0x5555557571c0 是一个 heap 所在 chunk 的基地址, 他分配了 0xb0 字节,位于 0x555555757270 的 8 字节也是给他用的。
信息泄露绕过 aslr && 获得 heap 和 libc 的地址
先放一张信息泄露的草图压压惊

在堆中进行信息泄露我们可以充分利用堆的分配机制,在堆的分配释放过程中会用到双向链表,这些链表就是通过 chunk 中的指针链接起来的。如果是 bin 的第一个块里面的指针就全是 libc 中的地址,如果 chunk 所属的 bin 有多个 chunk 那么 chunk 中的指针就会指向 heap 中的地址。 利用这两个 tips , 加上上面所说的 , watch_ghost可以 leak 内存中的数据,再通过精心的堆布局,我们就可以拿到 libc 和 heap 的基地址
回到这个题目来看,我们条件其实是比较苛刻的,我们只有 ghost 的内存是能够读取的。而 分配 ghost 所得到的 chunk 的大小是 0x60 字节的,这是在 fastbin 的大小范围的, 所以我们释放后,他会进入 fastbin ,由于该chunk是其 所属 fastbin 的第一项, 此时 chunk->fd 会被设置为 0, chunk->bk 内容不变。
测试一下即可
add_ghost(12345, "s"*0x20)
new_heap("s")
remove_ghost()

所以单单靠 ghost 是不能实现信息泄露的。
下面看看正确的思路。
leak libc
首先
add_ghost(12345, "ssssssss")
new_heap("b") # heap 0
new_heap("b") # heap 1
new_heap("b") # heap 2
# ghost ---> fastbin (0x60)
remove_ghost()
del_heap(0)

然后
del_heap(2) #触发 malloc cosolidate , 清理 fastbin --> unsorted, 此时 ghost + heap 0 合并

可以看到 fastbin 和 unsorted bin 合并了,具体原因在 _int_free 函数的代码里面。

FASTBIN_CONSOLIDATION_THRESHOLD 的值为 0x10000 ,当 free掉 heap2 后,会和 top chunnk 合并,此时的 size 明显大于 0x10000, 所以会进入 malloc_consolidate 清理 fastbin ,所以会和unsorted bin 合并形成了大的 unsorted bin.
然后
new_heap("b") # heap 0, 切割上一步生成的 大的 unsorted bin, 剩下 0x60 , 其中包含 main_arean 的指针
add_ghost(12345, "ssssssss") # 填满 fd 的 8 个字节, 调用 printf 时就会打印 main_arean 地址

先分配 heap 得到 heap_0, 此时原来的 unsorted bin 被切割, 剩下一个小的的 unsorted bin, 其中有指针 fd, bk 都是指向 main_arean, 然后我们在 分配一个 ghost ,填满 fd 的 8 个字节, 然后调用 printf 时就会打印 main_arean 地址。
调试看看。

0x00005555557570c0 是 add_ghost 返回的地址,然后使用 watch_ghost 就能 leak libc 的地址了。具体可以看文末的 exp
leak heap
如果要 leak heap 的地址,我们需要使某一个 bin中有两个 chunk, 这里选择构造两个 unsorted bin.
new_heap("b") # heap 2
remove_ghost()
del_heap(0)
del_heap(2) # malloc cosolidate , 清理 fastbin --> unsorted, 此时 ghost + heap 0 合并

new_heap("b") # heap 0
new_heap("b") # heap 2
# |unsorted bin 0xb0|heap 1|unsorted bin 0x60|heap 2|top chunk|
# 两个 unsorted bin 使用双向链表,链接到一起
del_heap(1)
new_heap("b") # heap 1
del_heap(0)

构造了两个 unsorted bin, 当 add_ghost 时就会拿到 下面那个 unsorted bin, 它的 bk 时指向 上面那个 unsorted bin 的,这样就可以 leak heap 了,具体看代码(这一步还有个小 tips, 代码里有)。
我们来谈谈 第一步到第二步为啥会出现 smallbin ,内存分配时,首先会去 fastbin , smallbin 中分配内存,不能分配就会 遍历· unsorted bin, 然后再去 smallbin 找。
具体流程如下( 来源 ):
逐个迭代
unsorted bin中的块,如果发现chunk的大小正好是需要的大小,则迭代过程中止,直接返回此块;否则将此块放入到对应的small bin或者large bin中,这也是整个heap管理中唯一会将chunk放入small bin与large bin中的代码。迭代过程直到
unsorted bin中没有chunk或超过最大迭代次数(10000)为止。随后开始在
small bins与large bins中寻找best-fit,即满足需求大小的最小块,如果能够找到,则分裂后将前一块返回给用户,剩下的块放入unsorted bin中。如果没能找到,则回到开头,继续迭代过程,直到
unsorted bin空为止
所以在第一次 new heap 时 ,unsorted bin进入 smallbin ,然后 被切割,剩下一个 0x60 的unsorted bin , 再次 new heap ,unsorted bin进入 smallbin,然后在分配 new heap 需要的内存 0xb0 , 然后会从 top chunk 分配,于是 出现了 smallbin。
下面继续
构造exploit之 off-by-one
经过上一步我们已经 拿到了 libc 和 heap 的地址。下面讲讲怎么 getshell
首先清理一下 heap
remove_ghost()
del_heap(1)
del_heap(2)
然后初始化一下堆状态
add_ghost(12345, "ssssssss")
new_heap("b") # heap 0
new_heap("b") # heap 1
new_heap("b") # heap 2
现在的 heap 是这样的

然后构建一个较大的 unsorted bin
remove_ghost()
del_heap(0)
del_heap(2)
new_heap("s")
new_heap("s")
log.info("create unsorted bin: |heap 0|unsorted_bin(0x60)|heap 1|heap 2|top chunk|")
# pause()
del_heap(0)
del_heap(1)

下面使用 off-by-null 进行攻击,先说说这种攻击为啥可以实现,文章开头就说, new_heap 时获取输入,最多可以读取 0xa8 个字节的数据,最后会在末尾添加 0x00 ,所以实际上是 0xa9 字节 , 因为 0xa8 字节 时已经用完了 下一个 chunk 的 presize 区域 , 第0xa9字节就会覆盖 下一个 chunk 的 size 位, 这就是 off-by-null , 具体细节比较复杂,下面一一道来。
首先触发 off-by-one
new_heap("a"*0xa8)

可以看到,在调用 malloc 分配内存后, heap_0 在 heap 的开头分配,然后在 偏移 0xb0 位置处有一个 0x110 大小的 unsorted bin, 此时 heap_2 的 pre_size 为 0x110, pre_inuse 为 0。所以通过 heap_2 找到的 pre chunk 为 0xb0 处开始的 0x110 大小的 chunk.
然后 off-by-null 后, unsorted bin 的 size 域 变成了 0x100 这就造成了 0x10 大小的 hole.

0x5555557571b0 就是 hole.
此时 heap_2 的 pre_size 与 pre_inuse没变化。
在清理下
new_heap("s")
del_heap(0)
del_heap(1)

这里那两个 unsorted bin 不合并的原因是,系统判定下面那个 unsorted bin ,找到 hole 里面的 第二个 8字节,取它的最低位,为0表示已经释放,为1则未被释放。由于那里值 为 0x3091 (不知道从哪来的),所以系统会认为它还没有被释放。

此时 heap_2 的 pre_size 为 0x110, pre_inuse 为 0。如果我们释放掉 heap2 ,系统根据 pre_size 找到 偏移 0xb0 ,并且会认为 这个块已经释放( pre_inuse 为 0), 然后就会与 heap2 合并,这样就会有 unsorted bin 的交叉情况了。
要能成功 free heap_2 还需要 偏移 0xb0 处伪造一个 free chunk 来过掉 unlink check.
# fake free chunk
add_ghost(12345, p64(heap + 0xb0)*2)
new_heap(p64(0)*8 + p64(0) + p64(0x111) + p64(heap) + p64(heap)) # 0
new_heap("s") #防止和 top chunk 合并
del_heap(2)

首先分配 ghost ,它的 fd 和 bk 域都是 偏移 0xb0 , 然后在 分配 heap ,在 伪造偏移 0xb0 free chunk , 使他的 fd 和 bk 都指向 ghost 所在块的基地址。
这样就能过掉 unlink 的 检查
然后 del_heap(2), 获得一个 0x1c0 的 unsorted bin , 可以看到此时已经有 free chunk 的交叉情况了。
下一步,在交叉区域内构造 unsorted bin, 然后 分配内存,修改其中的 bk 进行 unsorted bin攻击
del_heap(0)
new_heap("s") # 0
new_heap("s") # 2
del_heap(0)
del_heap(2)
首先释放掉 heap0 增加两个heap. ,会出现交叉的。原因有两个 unsorted bin.

然后分别释放 heap 0, heap 2,注意在释放 heap 0 的时候,由于画红圈标注的那个 smallbin 中的 pre_inuse 为 1, 所以 它上面的那个 smallbin 没有和 unsorted bin 合并, 原因在于,上一步 new_heap("s") # 2 时 , 切割完后,剩下 chunk 开头正好是 画红圈标注的那个 smallbin, 就会设置 它的 pre_inuse 为 1。

最后我们有了两个 unsorted bin.再次分配 heap 时,会先分配到位于 0x60,大小为 0xb0 的 unsorted bin,此时我们就可以修改 位于 0xb0 大小为 0x1c0 的 unsorted bin的首部,进而 进行 unsorted bin 攻击。
unsorted bin attack
现在我们已经有了 unsorted bin 攻击的能力了,目前我知道的攻击方式如下。
- 修改
global_max_fast,之后使用fastbin攻击, 条件不满足 (x) - house_of_orange , 新版 libc 校验 (x)
- 修改
stdin->_IO_base_end, 修改malloc_hook. ( ok )
在调用 scanf 获取输入时,首先会把输入的东西复制到 [_IO_base_base, _IO_base_end], 最大大小为 _IO_base_end - _IO_base_base。
修改 unsorted bin 的 bck 为 _IO_base_end-0x10 ,就可以使 _IO_base_end=main_arens+0x88,我们就能修改很多东西了,而且 malloc_hook 就在这里面。
# 修改 unsorted bin
new_heap(p64(0)*8 + p64(0) + p64(0xb1) + p64(0) + p64(buf_end-0x10))
# 触发unsorted bin attack, 然后输入内容,修改 malloc_hook 为 magic
new_heap(("\x00"*5 + p64(lock) + p64(0)*9 + p64(vtable)).ljust(0x1ad,"\x00")+ p64(magic))
注意 unsorted bin 的 size 域 一定要修改为 0xb1, 原因是 分配内存时如果 smallbin, fastbin都不能分配,就会遍历 unsorted bin ,如果找到大小完全匹配的就直接返回,停止遍历,否则会持续性遍历,此时的 bck 已经被修改为 _IO_base_end-0x10, 如果遍历到这个, 会 check ,具体原因可以自行调试看。
我们接下来需要分配heap 大小 为 0xb0, 设置size 域为 0xb1, 会在 unsorted bin 第一次遍历后直接返回。不会报错。此时unsorted bin完成。
magic 可用 one_gadget 查找。
最后 del_heap(2) 触发 malloc。
# 此时 unsorted bin 已经损坏, del heap 2触发
# 堆 unsorted bin的操作
# 触发 malloc_printerr
# malloc_printerr 里面会调用 malloc
del_heap(2)

总结
这道题非常不错,不仅学到了利用 file 结构体的新型攻击方式,还可以通过这道题深入理解堆分配的流程。
参考
http://brieflyx.me/2016/heap/glibc-heap/
https://github.com/scwuaptx/CTF/tree/master/2017-writeup/hitcon/ghost_in_the_heap
https://tradahacking.vn/hitcon-2017-ghost-in-the-heap-writeup-ee6384cd0b7
Pwn with File结构体(三)的更多相关文章
- Pwn with File结构体(一)
前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 利用 FILE 结构体进行攻击,在现在的 ctf 比赛中也经常出现 ...
- Pwn with File结构体(四)
前言 前面几篇文章说道,glibc 2.24 对 vtable 做了检测,导致我们不能通过伪造 vtable 来执行代码.今天逛 twitter 时看到了一篇通过绕过 对vtable 的检测 来执行代 ...
- Pwn with File结构体(二)
前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 最新版的 libc 中会对 vtable 检查,所以之前的攻击方式 ...
- Pwn with File结构体之利用 vtable 进行 ROP
前言 本文以 0x00 CTF 2017 的 babyheap 为例介绍下通过修改 vtable 进行 rop 的操作 (:-_- 漏洞分析 首先查看一下程序开启的安全措施 18:07 haclh@u ...
- Linux_Struct file()结构体
struct file结构体定义在/linux/include/linux/fs.h(Linux 2.6.11内核)中,其原型是:struct file { /* * f ...
- 2018.5.2 file结构体
f_flags,File Status Flag f_pos,表示当前读写位置 f_count,表示引用计数(Reference Count): dup.fork等系统调用会导致多个文件描述符指向同一 ...
- C语言文件操作 FILE结构体
内存中的数据都是暂时的,当程序结束时,它们都将丢失.为了永久性的保存大量的数据,C语言提供了对文件的操作. 1.文件和流 C将每个文件简单地作为顺序字节流(如下图).每个文件用文件结束符结束,或者在特 ...
- Linux--struct file结构体
struct file(file结构体): struct file结构体定义在include/linux/fs.h中定义.文件结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的 ...
- fd与FILE结构体
文件描述符 fd 概念:文件描述符在形式上是一个非负整数.实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表.当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件 ...
随机推荐
- UiAutomator -- UiObject2 API
1.点击与长按 void click() Clicks on this object. void click(long duration) Performs a click on this objec ...
- 给easyui datebox时间框控件扩展一个清空的实例
给easyui datebox扩展一个清空的实例 步骤一:拓展插件 /** * 给时间框控件扩展一个清除的按钮 */ $.fn.datebox.defaults.cleanText = '清空'; ( ...
- 剑指offer四十七之求1+2+3+...+n
一.题目 求1+2+3+...+n,要求不能使用乘除法.for.while.if.else.switch.case等关键字及条件判断语句(A?B:C). 二.思路 1.需利用逻辑与的短路特性实现递归终 ...
- 剑指offer三十八之二叉树的深度
一.题目 输入一棵二叉树,求该树的深度.从根结点到叶结点依次经过的结点(含根.叶结点)形成树的一条路径,最长路径的长度为树的深度. 二.思路 递归,详见代码. 三.代码 public class So ...
- paddlepaddle使用(一)
paddlepaddle是百度提出来的深度学习的框架,个人感觉其实和tensorflow差不多(语法上面),因为本人也是初学者,也不是很懂tensorflow,所以,这些都是个人观点. 百度的padd ...
- pthon获取word内容之获取表单
需求:把word里面的表单内容获取 按照规则拼成字符串 转换成类似下面的样子 代码如下: from docx import Document import re def parse_docx(f): ...
- 【转】Ext JS 集合1713个icon图标的CSS文件
原文:http://extjs.org.cn/node/715 由于最近在研究Extjs4.1.1,没想到Extjs没有自带的iconCls所使用的图标样式css,就是用那个写那个的,纠结了半天,网上 ...
- Hive文件存储格式和hive数据压缩
一.存储格式行存储和列存储 二.Hive文件存储格式 三.创建语句和压缩 一.存储格式行存储和列存储 行存储可以理解为一条记录存储一行,通过条件能够查询一整行数据. 列存储,以字段聚集存储,可以理解为 ...
- Java中的四种引用
引用定义 实际上,Java中存在四种引用,它们由强到弱依次是:强引用.软引用.弱引用.虚引用.下面我们简单介绍下这四种引用: 强引用(Strong Reference):通常我们通过new来创建一个新 ...
- JavaScript预编译详解
一.js运行三部曲: 1.语法分析(通篇扫描看有没有语法错误) 2.预编译 3.解释执行 二.预编译前奏 1.imply global 暗示全局变量:任何变量如果未经声明就赋值,此变量为全局对象所有 ...