在12月Adobe狂补78个漏洞的一周后,国外安全研究人员kafeine(@kafeine)爆出Angler Exploit Kit开始使用这个月修补的CVE-2015-8446漏洞进行攻击。我们第一时间跟进对该漏洞的原理和样本的利用方式进行了分析,在此与大家分享。
该漏洞可以描述为:Flash在解码mp3文件中的”ID3” tag中的编码数据时,没有能够正确检查所需的decode buffer的大小,从而导致了堆溢出。
有趣的是,这个漏洞已经修补过一次,当时的编号是CVE-2015-5560。当时这个漏洞也曾被公开利用:
http://malware.dontneedcoffee.com/2015/08/cve-2015-flash-player-up-to-1800209-and.html
我们先来看一下有问题的解码逻辑:
ID3 tag中可以包含各种tag。有些tag可以包含经过编码的文本: http://id3.org/id3v2.3.0
以TIT tag为例,当Flash处理到TIT tag时,会解码里面的文本。在TIT tag中有一个域指明了编码文本的大小,以下面的mp3文件为例:
该文件包含两个TIT tag,它们包含的编码文本大小分别是4字节和0x15555556字节。当Flash尝试解压这些数据时,会动态分配buffer来存放解压后的数据,分配算法是(根据编码方式不同会有微小的差别):
decode_buffer_size = (encode_data_size – 1) * 6 + 2
因为可以有多个TIT tag,所以可能需要多次解码过程。每次解码时,都会检查当前的buffer长度是否足够用于解码,如果不够,则重新分配合适大小:
int current_buffer_size int decoded_buffer_size if (current_buffer_size < decoded_buffer_size) { // reallocate the decode buffer }
下面我们看这里第一次出的问题,也就是CVE-2015-5560,报告者是google project zero的Natalie Silvanovich (@natashenka):
https://code.google.com/p/google-security-research/issues/detail?id=443&can=1&q=flash&start=100
我们来看一下decode buffer大小的计算公式:
decode_buffer_size = (encode_data_size – 1) * 6 + 2
相关代码如下:
.text:10024F13 loc_10024F13: ; CODE XREF: sub_10024C79+278j .text:10024F13 mov eax, ebx .text:10024F15 imul eax, 6 .text:10024F18 add eax, 2 .text:10024F1B cmp [esi+28h], eax .text:10024F1E mov [ebp+var_20], eax .text:10024F21 jge short loc_10024F4D
显然,当encode_data_size > 0x2aaaaaab时,(encode_data_size –1) * 6 + 2将超出32位整数的范围,而得到一个很小的数值,从而导致分配到的解码数组大小不够而产生溢出。
Adobe试图在18.0.0.232版本中修复这个漏洞,我们看看他的修补逻辑: .text:10024E3E mov eax, [ebp+var_14] .text:10024E41 imul eax, 6 .text:10024E44 inc eax .text:10024E45 inc eax .text:10024E46 cmp eax, [ebp+var_14] .text:10024E49 jbe loc_10024FB8
这个patch对encode_data_size进行了限制,限制条件为:
(encode_data_size * 6 + 2) > encode_data_size
仔细想一下就知道,这个修补并不能解决这个漏洞,甚至连整数溢出的问题都解决不了。/
最简单的bypass补丁的方法是利用下面这个带符号整数比较:
int current_buffer_size int decoded_text_size if (current_buffer_size < decoded_text_size) { // Signed Compare !!! ! // reallocate the decode buffer }
比如当encode_data_size=0x15555580时,
(0x15555580 – 1) * 6 + 2 = 0x800000FC 小于0
于是只要当前buffer size是正数,则buffer不会重新分配,导致最终的溢出。这就是CVE-2015-8446。
我们看一下样本中的恶意mp3文件:
样本使用的是TIT tag,其中恶意的encode_data_size为0x15555556,而
(0x15555556 – 1) * 6 + 2 = 0x80000000,正好小于0,可以触发这个漏洞。
样本mp3包含2个TIT tag,第一个编码数据大小是4,第二个是0x15555556,当处理这两个tag的解码时,decode buffer分配过程如下:
第一个tag,size = 0x4:
decode_buffer_size = (4-1) * 6 + 2 = 0x14, 分配0x14 bytes的buffer
第二个tag,size=0x15555556:
decode_buffer_size = (0x15555556 – 1) * 6 + 2 = 0x80000000, 与现有的buffer size进行带符号比较:
((int)0x80000000 < (int)0x14),
于是buffer不会重新分配,第二次解码数据拷贝到0x14 bytes buffer中导致溢出。
Exploit选择溢出0x14是有意义的,0x14 bytes buffer最后分配到的内存块是0x18 bytes。正好是ByteArray::Buffer的大小:
class Buffer : public FixedHeapRCObject { public: virtual void destroy(); virtual ~Buffer(); uint8_t* array; uint32_t capacity; uint32_t length; };
样本首先通过内存布局,分配一系列的ByteArray对象,然后让解码buffer后面紧跟着ByteArray::Buffer对象:
0x18 bytes 0x18 bytes
——————————————————————–> Overflow
于是溢出后可以覆盖ByteArray::Buffer对象的相关字段。溢出使用的数据如下:
0:007> dd 09b38ec0
09b38ec0 40404040 40404040 40404040 40404040
09b38ed0 40404040 40404040 40404040 40404040
09b38ee0 40404040 ffffffff ffffffff 00000000
注意这里两个0xffffffff,正好覆盖了Buffer.length和Buffer.capacity,于是溢出后可以得到一个长度为0xffffffff的ByteArray,在32进程中可以用来读写整个进程空间。之后的利用就很简单了。
在Adobe对vector长度加入保护机制之后,exploit开始放弃vector而选用ByteArray作为exploit的利用对象,比如之前的CVE-2015-7645和这次的CVE-2015-8446。好消息是,Adobe在12月补丁之后,对ByteArray长度进行了保护,原理和vector长度保护类似。
12月补丁之后,ByteArray::Buffer数据结构进行了扩展,里面保存了3个检验字段:
LengthCookie, CapacityCookie和ArrayCookie,分别对应Buffer里面的length, capacity和array字段。当对ByteArray进行操作时,会检查:
if ( Buffer.length ^ key != LengthCookie || Buffer.capacity ^ key != CapacityCookie || Buffer.array ^ key != ArrayCookie ) { // throw Error }
在ByteArray保护起来之后,下一个被利用的数据结构是什么,让我们拭目以待。
在分析过程中,由于Angler EK的加密机制,以及身处的地域问题(EK对访问的IP回进行判断,有些区域不会触发。感慨现在在天朝求被Exploit都不得),我们遇到了各种各样得到阻碍。在此我们想感谢包括@ kafeinex(和不愿意透露姓名的友商朋友)在内的国外安全研究者的大力帮助,感谢他们的帮助和分享。