0×00 序言
2015年12月28号,Adobe发布安全公告,一口气修补了19个漏洞。在致谢部分提到了CVE-2015-8651这个漏洞是由华为的安全研究部门提交的.不过很快这部分就被删除。国内安全厂商微步在线发布了<<境外“暗黑客栈”组织对国内企业高管发起APT攻击>>的分析报告。安天追影小组根据微步在线提供的样本哈希找到了相关样本,对该整数溢出漏洞的原理进行了分析.
0×01 相关知识
2012年的时候Adobe在Adobe Flash player中引入了domain memory的特性,该特性允许快速的访问内存。domain memory相关的函数被定义在package avm2.intrinsics.memory中,如下:
package avm2.intrinsics.memory { public function li8(addr:int): int; // Load Int 8-bit public function li16(addr:int): int; // Load Int 16-bit public function li32(addr:int): int; // Load Int 32-bit public function lf32(addr:int): Number; // Load Float 32-bit (a.k.a. “float”) public function lf64(addr:int): Number; // Load Float 64-bit (a.k.a. “double”) public function si8(value:int, addr:int): void; // Store Int 8-bit public function si16(value:int, addr:int): void; // Store Int 16-bit public function si32(value:int, addr:int): void; // Store Int 32-bit public function sf32(value:Number, addr:int): void; // Store Float 32-bit (a.k.a. “float”) public function sf64(value:Number, addr:int): void; // Store Float 64-bit (a.k.a. “double”) public function sxi1(value:int): int; // Sign eXtend 1-bit integer to 32 bits public function sxi8(value:int): int; // Sign eXtend 8-bit integer to 32 bits public function sxi16(value:int): int; // Sign eXtend 16-bit integer to 32 bits }
示例代码如下:
var domainMemory:ByteArray = new ByteArray(); var BYTE_ARRAY_SIZE:Number = 0x10000000; domainMemory.length = BYTE_ARRAY_SIZE; ApplicationDomain.currentDomain.domainMemory = domainMemory; var index:* = 0; var val:* = 0x200; for(i=0; i< BYTE_ARRAY_SIZE; i++) { si8(val, i); }
根据官方给出的测试报告显示domain memory访问内存的速度相当快。这里ApplicationDomain.currentDomain.domainMemory是一个全局变量,li*/si*函数会直接访问这个变量。该特性引入后被发现存在不少的问题,比较典型漏洞如编号为CVE-2013-5330和CVE-2014-0497等漏洞. 它们均是domain memory相关的opcode对边界检校不严格导致的。
0×02 漏洞成因分析
使用JPEXS Free Flash Decompiler 打开样本,能看到相应的脚本如下:
经过分析找到溢出的代码位于Bymitis类中,Bymitis在初始化的时候会判断当前是不是在IE浏览器中运行,如果不是IE就退出。接着会判断Flash player的版本,然后根据版本执行相应的代码。本次分析使用的Flash Player的版本是13.0.0.128,因此会进入函数cidkedie中执行,部分代码如下:
发现JPEXS Free Flash Decompiler反编译的结果相比动态调试的结果少了一些内容,经过分析比较发现如下的AS反汇编代码存在可疑的情况:
这段代码可以概括为:
op_si32(0x3FFFFFFF,loc_2+0x7FFFFFFC)
loc_2相关的代码为:
这个里可以认为loc_2=0×80001004,至此可以理解为将值0x3FFFFFFF写入到domain memory的0×80001004+0x7FFFFFFC位置处。从cidkedie的代码中可以看到domain memory指向的是一个长度为0×1000的ByteArray数组,它的名字是fastmemory.这个数组的前几个字节被填充为m3mory,后面跟随一个计数,记录该函数被调用的次数。因为使用了堆喷射,所以该函数会被反复被调用,以保证能够访问到特定的内存。
上面的AS脚本生成的JIT代码如下:
0612B480 B8 C6352F09 mov eax,0x92F35C6 0612B485 35 39CAD036 xor eax,0x36D0CA39 0612B48A 8B75 90 mov esi,dword ptr ss:[ebp-0x70] 0612B48D 8B5F 14 mov ebx,dword ptr ds:[edi+0x14] 0612B490 8B4F 18 mov ecx,dword ptr ds:[edi+0x18] 0612B493 8DBE 00F0FF7F lea edi,dword ptr ds:[esi+0x7FFFF000] 0612B499 83E9 04 sub ecx,0x4 0612B49C 3BF9 cmp edi,ecx 0612B49E 0F87 230D0000 ja 0612C1C7 0612B4A4 03DE add ebx,esi 0612B4A6 B9 FCFFFF7F mov ecx,0x7FFFFFFC 0612B4AB 89040B mov dword ptr ds:[ebx+ecx],eax 0612B4AE B8 00F0FF7F mov eax,0x7FFFF000 0612B4B3 8B1C03 mov ebx,dword ptr ds:[ebx+eax] 0612B4B6 B8 419CE424 mov eax,0x24E49C41 0612B4BB 35 39CAD036 xor eax,0x36D0CA39 0612B4C0 3BD8 cmp ebx,eax 0612B4C2 75 00 jnz X0612B4C4
关键部分已经被标红,相关指令解释如下:
(1)
0612B480 B8 C6352F09 mov eax,0x92F35C6 0612B485 35 39CAD036 xor eax,0x36D0CA39
这两条指令执行完后eax=0x3FFFFFFF
(2)
mov esi,dword ptr ss:[ebp-0x70]
这条指令执行完后ESi=0×80001004.
(3)
0612B48D 8B5F 14 mov ebx,dword ptr ds:[edi+0x14] 0612B490 8B4F 18 mov ecx,dword ptr ds:[edi+0x18]
这两条指令执行完后:ebx为ApplicationDomain.currentDomain.domainMemory指向的内存地址,ecx为ApplicationDomain.currentDomain.domainMemory的大小即0×1000.
(4)
0612B4A4 03DE add ebx,esi 0612B4A6 B9 FCFFFF7F mov ecx,0x7FFFFFFC 0612B4AB 89040B mov dword ptr ds:[ebx+ecx],eax
这段代码可以描述为:
*((DWORD*)(domainMemory+0x80001004+0x7FFFFFFC))= 0x3FFFFFFF
很显然0×80001004+0x7FFFFFFC整数溢出为0×1000.这个值就写入到了domainMemory的内存区之外。接下来就是flash漏洞的常用技巧了,相关代码如下:
_loc7_ = 0; while(_loc7_ < 1280) { vc[_loc7_] = new <uint>[305419896]; vc[_loc7_].length = 1022; _loc7_++; }
这里创建1280个uint对象,然后将它们的长度改为1022.每个uint对象的内存第一个DWORD的值为1022,如果uint对象的内存刚好位于domainMemory内存的后面就可以修改这个值,然后就可以访问任意内存,接着可以在可访问的内存范围内创建object对象,找到object的虚函数表,修改函数指针,执行shellcode.
在本次分析的机器上,cidkedie函数被调用两次后就会发现domainMemory的后面紧跟着一个uint对象。domainMemory的内存如下:
0x9c28000向下0×1000处的内存如下:
第一个值为0x03FE(1022),第三个值为0×12345678(305419896),显然这是上面创建的uint对象。漏洞触发后的内存如下:
前后对比后就可以发现uint对象的长度被替换为0x3FFFFFFF,这时候uint对象的内存变得很大,基本上可以访问任意内存了。
0×03总结
CVE-2015-8651是对domain memory执行相关操作的opcode在执行过程中没有很好的对访问地址的范围进行判断从而导致整数溢出漏洞,它的漏洞形成原理和CVE-2013-5330以及CVE-2014-0497都是相似的。最后感谢微步在线威胁情报给出的样本哈希,使安天追影分析小组能够完成这份分析报告。
0×4参考
[2] 深入剖析某国外组织针对中国企业的APT攻击(CVE-2015-8651)
UBIQUITOUS FLASH, UBIQUITOUS EXPLOITS, UBIQUITOUS MITIGATION
* 作者:安天追影(企业账号),转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)