freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

天融信关于VLC Media Player 2.2.8 Use After Free漏洞分析
2018-08-08 08:30:09

*本文中涉及到的相关漏洞已报送厂商并得到修复,本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担。

一、背景介绍

VLC 是一款自由、开源的跨平台多媒体播放器及框架。它支持众多音频与视频解码器及文件格式和各类流式协议,也能作为流式服务器在 IPv4 或 IPv6 的高速网络连接下使用。

本文主要分析 VLC Media Player 2.2.8 版本的 MKV 格式视音频分离器 (demuxer) 中存在的一个 UAF 漏洞,实验环境为最新版本的 Win10 64 位, 主要从漏洞细节和漏洞的利用两大部分对漏洞进行分析。

在漏洞细节部分,主要分为以下三个方面:

1. 如何找到漏洞触发点?

2. 如何确定 UAF 对象?

3. 分析漏洞成因。

在漏洞利用部分,下面这些问题都将得到说明:

1. 如何通过精确的 Heap Spary 布置 shellcode?

2. 如何重新利用 UAF 对象?

3. 如何 bypass DEP&ASLR?

MKV

MKV 格式属于 Matroska 开源多媒体容器标准中的一种。它是建立在 EBML 语言的基础上,这是一种类似于 XML 格式的可扩展二进制元语言,使用可变长度的整数存储,以节省空间。EBML 元素都有自己的级别,每一个高一级的元素由若干次一级的元素组成。整个 MKV 文件 从整体看可分为 2 大部分:EBMLHeader 和 Segment。前者包含了文件的版本、文档类型等相关信息;后者则保存了媒体文件的视频和音频的实际数据,又可以再分为 SeekHead、Chapters、Attachments、Cluster 等若干子元素。

1.1 漏洞描述

天融信阿尔法实验室经过深入分析关于该漏洞主要是源于 VLC 播放器用于处理 MKV 格式的视音频分离器模块——libmkv_plugin.dll 中, 该模块的一个 matroska_segment_c 对象在释放后被重引用。可以通过构造大量和该对象大小相同的数据块,最终使这块内存重新被分配,后面再引用该对象时,便可控制程序指针,最后通过精确的 Heap Spray 布置 shellcode、ROP 绕过 DEP,造成任意代码执行。

1.2 受影响的系统版本

VLC media player <= 2.2.8

1.3 漏洞编号

CVE-2018-11529

二、漏洞细节

由于 VLC Media Player 的 release 版本没有调试信息,所以我们可以在官网下载源码自行编译带有调试信息的程序。另外要注意的的一点是,官方推荐使用 MinGW 在 Linux 环境下交叉编译 VLCMedia Player。而 MinGW 使用的是 DWARF 格式调试信息而非 PDB 格式,所以在 Windbg 中调试时,依然无法看到符号信息,但这依然方便了我们使用 IDA 静态分析。

2.1 漏洞触发点

首先我们需要找到漏洞触发的位置,使用 Windbg 自带工具 gflag 开启页堆:

将 vlc.exe 附加到 Windbg 上,打开 poc.mkv,在此处触发了异常,可以确定漏洞存在于 libmkv_plugin.dll 模块,再结合 IDA 和源码分析,得知具体位置是在函数 int Control(demux_t_0 *p_demux, int i_query, va_list args) 中

现在关闭页堆,重新打开 vlc.exe,并在刚才异常的位置下断点,再次打开 poc.mkv,可以看到 [ebp+28h] 处的值为 0x22000020,

查看 ebp 处的内容,发现正是 PoC 中构造的 UAF_object 的内容,因此可以基本确定,ebp 中保存的是指向 UAF 对象指针。

当然这里并不是漏洞的触发点,UAF 漏洞的触发一般都是要再次引用该对象的某个方法。所以我们可以在 [ebp+28h] 处设置一个内存访问断点(也就是上图中 0x22000020 的位置):ba r4 0f4069e8

继续运行程序,断在了函数 matroska_segment_c::UnSelect() 中:

继续往后执行几步便会触发漏洞,弹出计算器。

这里的 call 指令调用了一个位于 vlc.exe 模块中的地址,结合 PoC 来看也是 ROP 链第一个 gadget 的地址。

到这里已经可以初步推测漏洞成因,在对象 matroska_segment_c 被释放后,接着申请与其大小相同内存去「占坑」,待后面重引用该对象中某个函数或成员时,就可以控制程序执行流程。

2.2 释放重引用的位置

接下来的问题是——matroska_segment_c 对象是在哪里被释放,又是在哪里被重新分配呢?尝试使用!heap -p -a 查看分配此内存时的栈回溯信息,发现无法却显示。

这是因为这里编译器采用了一种称为 FPO(Frame Pointer Omission)的优化方式,它压缩或者省略了在栈上为函数创建栈帧指针的过程。这样可以加速函数调用,但是在这种情况下,调试器可能不能基于 EBP 展开栈帧(除非它有启用了 FPO 模块相匹配的符号文件,而前面提到过,我们没有办法生成用于 Windbg 调试的 PDB 文件)。

在哪里分配?

又因为程序每次加载时堆的地址都是不固定的,也无法靠下断点来进行分析,那么我们只能结合 IDA 和源码来分析了。通过交叉引用找到调用 matroska_segment_c::UnSelect() 的 Close() 函数,观察 matroska_segment_c *是从哪取出的,过程整理如下:

①  将 p_this 强制转换为 demux_t*型并赋值给 p_demux

②  从 p_demux 偏移 0x48 处取出 demux_sys_t*型的结构赋值给 p_sys

③  再从 p_sys 偏移 0x6C 处取出 virtual_segment_c *型结构赋值给 p_vsegment

④  调用 virtual_segment_c 的 CurrentSegment() 方法得到一个 matroska_segment_c *型指针

⑤  最后调用 matroska_segment_c 的 Unselect() 方法触发漏洞

再来看看定义该函数的 vlc/modules/demux/mkv/mkv.cpp 文件

文件中定义了 Open()、Close() 和 Control() 等函数,通过函数名和注释可以推测,程序在处理 mkv 文件时,首先执行 Open() 函数。而 Open() 和 Close() 都有相同的参数——p_this,在上面的分析中,UAF 对象的指针正是从该结构体中取出的。刚进入 Open() 函数时,对象的初始化应该都没有完成,我们只需监视 p_demux 结构在上述内存偏移中的变化,就能找到分配 UAF 对象的地方。

在 IDA 找到 Open() 函数起始地址,在 Windbg 中下断点,然后找到该函数参数 p_this 这个结构的位置,在其相应偏移处设置内存访问断点,观察这些位置内存的变化。

中断到这里时,UAF 对象处于已经被创建了。

在 IDA 中查看该地址,定位在 demux_sys_t::PreloadLinked(demux_sys_t*const this) 函数中,而这个函数又只在函数 Open(vlc_object_t *) 中被引用,最后很快就能在源码中找到分配该内存的位置:

在哪里释放?

描述一个堆块的数据结构是 HEAP_ENTRY,它位于一个堆块起始处前 8 字节,而一个堆块的状态由占用状态变为空闲状态时,对于已释放的堆块,堆管理器定义了 HEAP_FREE_ENTRY 结构来描述,相比 HEAP_ENTRY,它多了 8 个字节的 LIST_ENTRY 结构用于存放空闲链表(Free List)的节点。

那么要定位到该堆块释放的位置就是十分容易了,我们只需要在 HEAP_ENTRY 处设置内存访问断点,当堆块释放时 HEAP_ENTRY 中某些标志位发生改变就能立刻断下。

按 F5 继续运行,不出所料的断在了 ntdll!RtFreeHeap 中,只需要 Step Out 几次,就能来到 libmkv_plugin.dll 模块中释放这块内存地方了。

此时,堆块状态已经由 busy 变为了 free:

同样的,结合 IDA 在源码中找到释放堆块的位置。

在哪里重用?

继续追踪堆块的变化,和上面的方法类似,就不再赘述了。

2.3 漏洞成因

在 libmkv_plugin.dll 模块的 static intOpen(vlc_object_t * p_this) 函数中,通过调用 ProloadLinked() 函数预加载所有链接段,并创建了一个 virtual_segment_c 对象实例,在该对象的构造函数中,matroska_segment_c 对象被初始化。

但后面的 FreeUnused() 函数中却又释放掉了 matroska_segment_c*指向的内存。

而在 static void Close(vlc_object_t *p_this) 函数中,p_vsegment 调用 CurrentSegment() 方法返回了一个指向 matroska_segment_c 的指针赋值给 p_segment,但该指针指向的对象 matroska_segment_c 已经被释放,p_segment 早已是一个悬空指针。

三、漏洞利用

3.1 Heap Spray部署 shellcode

一个 MKV 文件所有的音视频帧数据都存放在 Cluster 这个结构中。每个 Cluster 内可能有很多个 BlockGroup 组成,BlockGroup 内又由若干个 Block 组成。这些 Block 内就是音视频的帧数据。在 PoC 就是构造了数十个大小为 0xfff000 的 Cluster,并将喷射数据放入每个 Block 中。

当堆块进行大量分配的时候,堆地址的低位不会发生变化,仅有几个高字节是改变的,查看堆喷的内存块,可以看到每次打开 PoC 堆喷的地址总是以「018」结尾的,变化的总是前面的高位地址,所以我们只需以将每个 Block 的大小控制为 0x1000,就能够准确得到喷射的位置。以下图为例,0x22000020 所在堆块的 UserPtr 是 0x211e5020。(0x22000020 - 0x211e5020) / 0x1000 = 0xE1B(3611) 刚好能被整除,也就是 0x22000020 的内容能被我们精确控制,因为它永远是一个 Block 的起始地址。

3.2 构造 UAF 对象

MKV 的 Attachment 部分主要是用于支持在文件中附加任何类型的文件,包括图片、网页、程序等。通过构造 500 个大小与之前被释放的 matroska_segment_c 大小相同的 attachments,使释放掉的内存被重新分配。

结合源码可以看到,最后触发漏洞的函数并不是 UAF 对象的方法,而是从 UAF 对象中又取出的其他,所以在上面堆喷的数据中不仅布置了 shellcode,还有 demux_sys_t、demux_t 和 es_out_t 这些结构。最终触发漏洞的 es_out_Del 函数的指针就定义在 es_out_t 这个结构中,回过头看看堆喷数据中,该指针已被布置为了 ROP chains 的起始地址。

3.3 bypass DEP&ASLR

使用 Immunity debugger 的插件 mona,我们可以查看程序所有运行的模块。这里可以看到主程序 vlc.exe 的 Rebase 是 False, 即使有 ASLR,程序每次打开其加载地址仍然和以前一样的不会改变,所以我们可以在 vlc.exe 模块中寻找可用的 gadget,构造 ROP chains。

完整的 ROP 链如下:

不同于普通的栈溢出,此时我们能控制的只有几个寄存器的值,而当前程序的栈空间是不可控的,所以在 ROP 链的开始利用 Stack Pivot, 将栈指针 esp 指向堆喷的内存中,相当于将堆作为栈来使用。

将 edi 的值放入 edx, 剩下的三条指令没有实际作用,在栈上填充无用的数据来补偿被三个 pop 指令弹出的值。

剩下的几条 gadget 在为后面布置栈的内容做准备。

将上面寄存器中的值压入栈中。

设置 VirtualProtectStub 的参数。

转跳到 VirtualProtectStub 函数中,修改喷射内存的的执行权限。

完成后转跳到 shellcode 继续执行。

要实现漏洞的利用,只需简单的修改 PoC 中的 shellcode 部分就行,下面是经过修改的用于 MSF 的漏洞利用模块。

四、修复建议

目前官方已修复该漏洞,可从官网下载最新版本。

*本文作者:alphalab,转载请注明来自Freebuf.COM

# 漏洞 # VLC # Use After Free
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录