author:大宝@360天眼安全实验室
在今年五月份,国外安全专家发现了一个未知的Adobe漏洞在野外被利用。在该漏洞被披露后,Adobe发布了一个升级补丁用于修复此漏洞(APSB16-15),编号为CVE-2016-4117。同时,CVE-2016-4117漏洞被列为高危漏洞,在CVSS Score中被评为10.0,它同时影响到Windows,Mac OS X,Linux和Chrome OS。Adobe在发布的漏洞信息中提到,“Windows、mac、Linux和Chrome OS中的Adobe Flash Player 21.0.0.226和早期版本中存在一个高危漏洞,成功利用该漏洞可能会导致系统崩溃,甚至攻击者可以控制受影响的系统。”
0x0漏洞简介
CVE-2016-4117是出现在ActionScript的com.adobe.tvsdk.mediacore.timeline.operations.DeleteRangeTimelineOperation类中的一个类型混淆漏洞,最终可能导致远程代码执行,在今年五月份的时候首次出现野外样本。在此类中,存在以下两个get,set的接口(图0-0),名称为placement,假如我们以此类作为基类,在子类中创建一个相同名称的object(图0-1),avm虚拟机在解释的过程中会出现错误,从而引起类型混淆。
(图0-0)
(图0-1)
0x1 漏洞相关知识
要深入了解这个漏洞的深入原理,首先我们要了解avm虚拟机解释字节码getproperty(0x66)的逻辑流程。adobe在github上公布过avm虚拟机的源代码,并且有一些参考文档,尽管这是3年前的代码,但是还是可以作为参考。
首先我们可以查看getproperty这个指令的简介(图1-0):
图(1-0)
简单来说就是从一个object中根据后面的index取出某个属性。我们再看源代码的实现:
图(1-1)
getBinding是根据我们的属性名字,最后返回一个bind ID值,然后会根据这个值取出不同的函数或者字段的值。这个ID的值由两部分组成,分别是低三位的bit和其余的bit,低三位的bit保存的是这个property的类型,其余的bit保存这个ID真正的值,枚举值如图:
图(1-2)
随后的bindingKind就是将这个ID值与7相与,也就是取出低三位的bit,然后switch表根据这个值进行不同的操作,这里与漏洞相关的有两个,一个是BKING_VAR(2),一般是object,uint等变量,一个是BKING_GET(5),对应get的接口,首先我们查看BKING_VAR:
图(1-3)
BindingToSlotId是右移三位,得到真正的ID值,然后根据这个ID值取出真正的value,getSlotAtom的逻辑也很简单,比如ID值是0x5,就取出对象偏移0x5*4的value,当然还会进行类型的判断,如这个类型是double型则取出8字节,如果是int型,则取出4字节。对比的汇编指令如下:
图(1-4)
然后我们再看BKING_GET:
图(1-5)
跟进coerceEnter函数:
图(1-6)
这段代码比较复杂,首先解释几个名词:
Vtable:是一个保存as层面(不是native层面)虚函数列表的对象,里面的methods数组保存不同虚函数对象的对应的MethodEnv,一般保存在object的+0x8偏移的位置。
MethodEnv:在其+0x8保存MethodInfo对象。
MethodInfo:在其0x8 保存虚函数将要调用的函数指针。最终会调用这个函数指针从而调用get or set接口。
这段代码首先右移三位,得到真正的ID值,然后以这个ID值作为索引从methods数组中取出对应虚函数的MethodEnv对象,再调用对应的get or set接口。
对应的汇编指令如下:
图(1-7)
另一个需要了解的就是byteArray的数据结构,根据不同版本,在byteArray对象的+0x40 or +0x44 or +0x48会有一个m_buffer,而m_buffer的+0x8保存了真正保存数据的array,+0x10是length。具体如图所示:
图(1-8)
0x2 漏洞触发分析
实验环境:Windows 7 64 bit 企业版cn
Adobe Flash:flashplayer21_0r0_213_win_sa_debug.exe
根据捕获的样本分析得出,漏洞触发的关键代码如下:
图(2-0)
通过flash90.palcement语句从而会在AS虚拟机层面进入getPlacement(该部分没有源代码,暂时命名)函数,是触发漏洞的关键所在,伪代码如下:
图(2-1)
首先会进入Toplevel_getproperty函数(也就是图1-1的函数),该函数没有漏洞,因此所有代码运行正常,最终返回placement在内存中的值0xd。我们知道placement等于true,也就是0x1,但是在as虚拟机保存变量的机制中,末尾三位bit也是保存类型的,官方文档如下:
图(2-2)
因此true的值是1<<3 or 101 =0xd。返回值0xd大于0x4,所以跳过if语句,进入下面的漏洞触发代码部分:
图(2-3)
我们可以看到这里调用了getBinding函数,然后返回0xe2:
图(2-4)
ID值0xE2如果与7相与等于0x2(因为我们已经混淆成object类型,本来是get接口),对应图(1-2),正确的流程应该是进入图(1-3)的代码,取出placement的值0xd。但是这里并没有进行任何判断,就进入图(2-3)下面的代码,我们对比和图(1-7)的代码:
我们发现这两处地方的代码是一样的,这里是进入了case 5和7的处理流程,从Vtable中取出MethodEnv然后取出虚函数地址并且调用。但是我们应该进入case 2的处理环节的,却进入了case5和7的处理流程,我们可以查看修补该漏洞后的代码:
图(2-5)
修补后的代码会对getBinding后的ID值的类型进行判断,如果不是0x5的类型,则进入错误处理流程,从而避免了其他ID值的类型进入了0x5类型的处理流程。下面我们来分析如果我们把这个0x2类型的ID值进入0x5类型的处理流程会产生什么严重的问题。
图(2-6)
将ID值右移三位得到0x1c,该值是placement属性在flash90对象中的偏移值(44+1c*4=b4):
图(2-7)
但是该值被用于从Vtable对象的MethodEnv数组中作为index值取出MethodEnv元素(图2-6中的edx+0x3c是数组的开始位置),查看Vtable对象的内存:
图(2-8)
Vtable对象的大小是0x60,0x1C并不是一个合法的值,经过计算后,最终会越界从下一个相邻的Vtable对象中读出MethodEnv元素。而这个Vtable对象是属于另一个对象类型Data5的,查看Data5的内存:
图(2-9)
图(2-10)
Data5对象的+0x8保存的Vtable对象等于0x39cbf40,与图2-8相符合,第一个属性等于0xab4130等于十进制的11223344,与图2-10相符合,最终判定该对象就是Data5对象。所以最后取出的虚函数指针是Data5对象的f2虚函数指针:
图(2-11)
但是这里的this指针却变成了flash90的指针,根据源码(图2-12),我们查看进入虚函数指针前第三个参数的内存区域:
图(2-12)
图(2-13)
换句话说,在f2函数上所有的一切操作的this指针都不是指向Data5对象的内存区域,而是指向flash90的内存区域,最终引起类型混淆漏洞。由于Data5对象里面有很多属性,占用的内存空间会比flash90对象大很多(图2-14),最终可以对flash90内存区域进行越界读写,只要在flash90对象的下一个相邻位置放入一个byteArray,即可以在f2函数中对这个byteArray的关键数据区域修改,得到一个长度超长的byteArray。
(图2-14)
0x3 漏洞利用分析
漏洞利用的过程中,主要有几个问题:
3.1 怎样令Data5对应的Vtable对象刚好分配在Data4对应的Vtable对象的下一个相邻位置。
第一步:Vtable对象的大小是由一个对象的虚函数数量决定的,虚函数越多,MethodEnv数组越大,Vtable对象也越大。目标Vtable对象是0x60,样本中通过添加5个虚函数,加上本来存在的3个虚函数,最后得到0x3c+8*4=0x5c,再经过内存对齐就是0x60:
图(3-0)
第二步:增加几个继承Data5的类Data6,Data7等等,然后在运行的过程中new出来,最后在内存中即可分配多个同样大小的Vtable对象。
图(3-1)
3.2 怎样令虚函数f2对应的MethodEnv刚好对应0x1c*4+0x3c+Vtable_Add
这个则十分简单,根据在内存中的偏移,在f2虚函数前增加若干个虚函数,样本中是1个。然后f2对应的MethodEnv就会刚好在0x1c*4的位置。
图(3-2)
3.3 如何令flash90后面紧跟一个byteArray
通过heap feng shui技术:
图(3-3)
Data3的构造函数如下图:
图(3-4)
最终内存布局如下图:
图(3-5)
进入f2函数后,首先进行一系列内存数据判断,找出byteArray关键数据区域的偏移值,因为版本和环境的不同,这些都可能不同的,因此为了兼容性,这里有必要进行判断:
图(3-6)
(图解:a48的内存位置计算法方法是flash90_add+0x10(因为Data5在第一个字段前会有0x10的metedata)+48*4=0x66a500=0x123=291,根据上图的内存布局(图3-5),a57就是byteArray的index,a64就是byteArray的this指针,a50是m_buffer的地址。a56和a23只是用于检测是否byteArray对象)
获得与flash90对象相邻的下一个byteArray的引用,将byteArray的m_buffer结构地址修改成我们可以控制的内存区域,把真正m_buffer区域的数据赋值至这个内存区域,然后进入flash20函数。复制利用的技术是把atom伪造成Number类型(图2-2),然后就可以读出指定内存区域的数据。修改前m_buffer地址(这里的版本是0x48偏移)如图所示:
图(3-7)
修改后的地址:
图(3-8)
进入flash20函数后,将会修改m_buffer区域的length字段,最终构造超长数组,实现任意内存读写:
图(3-9)
m_buffer的地址已经修改成我们可以控制的内存区域,因此这里的ba.a0是对应m_buffer的+0x8位置的,从图3-7可以看到,m_buffer的+0x8位置等于0x11223344,而ba.a0的构造函数如图3-4所示。在最新版的flash中,在m_buffer的结构中引入了cookie字段,以防篡改length,例如在+0x20就保存了length的cookie,计算方法是length xor .Data字段的一个cookie值。在修改length的同时也需要把+0x20的length_cookie也要修改,不然在校验的时候就会出错。通过将a1(capacity)与a5(capacity_cookie)异或,可以求出.Data字段中的cookie值,然后将这个cookie值与0xffffffff放进a6(length_cookie),最终完成篡改m_buffer的length字段的所有步骤。至此,我们已经得到了一个超大的byteArray数组,可以进行任意内存读写。样本接下来的利用方法与Hacking Team的CVE-2015-5119基本雷同,公开的分析文章非常多,在这就不再详细叙述:
图(3-10)
查找PE头,然后根据导入表结构查找VirtualProtect地址
图(3-11)
根据functionObject的结构,进行一系列读写操作,最终修改functionObject父对象的虚函数地址至VirtualProtect,在call.apply中会调用这个虚函数,从而调用VirtualProtect绕过DEP。
图(3-12)
读取functionObject的MethodEnv地址,再读取MethodEnv中的MethodInfo地址,再修改MethodInfo的_implGPR变成shellcode地址,从而绕过CFG检测。在functionObject.call的时候,会跳进shellcode执行,最终实现远程代码执行。
0x4 样本行为分析
和众多样本一样,shellcode的作用就是一个网马下载器。在shellcode执行后,样本会从网上下载一个恶意的exe文件然后执行,运行该exe后,受害者主机将会完全被攻击者控制。
发表评论
您还未登录,请先登录。
登录