我相信很多人了解过pwn2own大赛,心中肯定很膜拜大神,很想深入去了解这到底是什么啊,很高大上啊,怎么做到的啊?但是一看具体的漏洞分析又常常根本看不懂。想必很多人都想找一篇从零基础教学的漏洞分析文章。本文就从零基础去教学,用一个例子解释到底什么是网页木马以及它的具体原理。

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

0×1 漏洞分析

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

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

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

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

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

对应源代码:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

这个断点是在CTreeNode+0x8c地方断下,打印对象指针,对应的CElement指针(CTreeNode+0×0处保存对应CElement对象),以及对应的CElement对象的虚函数表地址。

定好断点,GO!

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

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

我们看00201c30的地方:

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

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

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

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

0×2 漏洞利用

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

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

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

果然,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(分配的内存地址,内存大小,内存种类,内存属性)

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

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

然后我们要想办法把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返回的时候指令

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

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,没有准确的堆喷射,即使有,也看得不明白。后来我突然发现,我们块的起始地址有个共同的特点:

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

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是一个网上大家用来堆喷射的库,某个牛人写的):

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

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

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

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

转载自FreeBuf黑客与极客(FreeBuf.COM)

 

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

扫“安全盒子”二维码,获取最新互联网资讯!