是的,您并没有看错:这是一篇关于如何保护漏洞利用代码本身的文章,而不是介绍如何保护您不受漏洞利用代码攻击的文章。
我一直有这样的想法,即如何将漏洞利用代码提升到一个新的水平,这不仅仅指漏洞利用过程,而且还包括漏洞利用之前和之后。这一次,我将编写一个文章系列,详细介绍如何保护漏洞利用代码。危险无处不在,包括黑客在内,因为他们已经失去了很多漏洞利用代码。
在这个文章系列中,将探讨不同的方法来检测受攻击的程序(在这种情况下是浏览器)是否正在被某种工具所分析,以便我们可以中止漏洞利用代码,而不至于发生故障、崩溃和被检测到。
为什么?
漏洞利用代码是有价值的资产,所以你自然希望尽可能长时间地保护和持有它们。此外,大多数时候你也不希望被发现。但是为了实现这一点,你需要想法设法保证漏洞利用代码也不会被发现。
首先,野外的漏洞利用代码之所以被发现,通常是由于以下四个主要原因:
重用了其他漏洞利用代码的部分,因此被检测软件察觉
因为不可靠而崩溃,后来经分析而曝光
由于程序被监视和分析(蜜罐)而导致崩溃
你分享给了朋友,他在使用过程中被检测到
因为我们这篇文章专注于第三个原因,所以我们首先来介绍一下PageHeap。
PageHeap是如何工作的?
PageHeap是SDK/WDK中提供的一个Windows工具,用来尽快检测出进程堆中的内存破坏情况。
为了实现这一点,它用另一个堆分配器替换了原来的那个分配器。这个分配器将通过VirtualAlloc进行所有的内存分配,使其至少返回一个指定大小(大多数系统中为4Kb)的内存页。
除此之外,返回的地址将指向内存页的末尾减去指定的大小。 因此,它能使任何堆缓冲区溢出到内存页的末尾。
这也防止了许多广泛使用的技术,这些技术依赖于特定的堆布局来以特定方式来布置内存的分配。这些技术的名称很多,比如Heap Massaging、堆风水等。
所以,由于PageHeap打破了原来的布局,所以会导致大部分依赖某种堆栈布局的漏洞利用代码崩溃。
如果这是您是初次接触PageHeap的话,不妨在网上多搜索一些相关的资料,因为它的确是一个非常方便的调试工具。
如何检测PageHeap?
受PageHeap影响的程序的行为的主要特点是,无论分配空间的大小是多少,堆分配都会慢很多。记住,堆早就为尽可能快地分配各种不同尺寸的内存对象而进行了相应的优化。
相反,使用PageHeap时,每次分配都要通过VirtualAlloc向内核提出请求,这就涉及上下文切换并在内核中进行相应的处理。因此,使用PageHeap时,与常规情况下分配大内存块的过程相比,它所用的时间会跟分配小的内存块的时间更加接近。
由于window.performance.now()计数器在大多数JavaScript引擎中都支持,因此可以用它来检测这个时间的测量值,并且具有微秒的精度。
因为Chrome和Firefox拥有自己的分配器,所以启用PageHeap不会引起太多的变化(请记住,它会劫持原始的malloc / free函数)。在这篇文章中,我们将重点介绍Windows环境中使用默认分配器的两种浏览器:IE11和Edge。
在寻找分配不同数量字节的函数的过程中,我遇到了Uint8Array,它是一个在低层使用了ArrayBuffer的TypedArray。
它的用法很简单,例如var buf = new Uint8Array(len)。通过跟踪该函数,无论是在IE中还是Edge中,当创建ArrayBuffer时,都会直接使用我们指定的值调用msvcrt!malloc。
对于小大值,这里分别使用0x10和0x1000,注意,大的值会触发对VirtualAlloc的调用。我这里使用的方法是,尝试在20ms内为小型和大型的内存分配任务分配尽可能多的Uin8Array。
好的,下面看看具体代码!
function doFor(fun, time) {
var i = 0;
var store = new Array();
var startTime = performance.now();
do {
for(var j=0; j
store.push(fun());
i++;
} while((performance.now() - startTime)
return i;
}
function allocPageBA() {
return new Uint8Array(0x1000);
}
function allocSmallBA() {
return new Uint8Array(0x10);
}
var bigRet = doFor(allocPageBA, ALLOC_TIME);
var smallRet = doFor(allocSmallBA, ALLOC_TIME);
alert(bigRet);
alert(smallRet);
当然,这段代码不是很完美,因为我们在测量内存分配时引入其他的分配任务,肯定会带来测量误差。
为了解决这个问题,我创建了一个对象,并预先分配了Array,然后将对象存储在数组中,这样就不会引起新的分配任务了。
另外,要防止分配的对象被垃圾回收器回收而释放它们的内存,这会在测量中引入另一个误差。
function NoAllocStore(count) {
this.count = count;
this.array = new Array(count);
for(var i=0; i
this.array[i] = 0x41414141;
}
this.index = 0;
}
NoAllocStore.prototype.store = function(obj) {
if (this.index >= this.count) {
alert("bad");
throw false;
}
this.array[this.index] = obj;
this.index++;
}
最后的代码在这里。为了这项实验,运行了许多次(准确的说是250次),并比较了在两台浏览器在禁用和启用PageHeap情况下的分布情况。
在IE11中测量的分布情况为:
下面看看混合分布详情:
非常明显的是,启用了PageHeap的时候分布更为密集,因此它对分配时间影响更大。 这可以归因于内存分配比堆分配更耗时,使得代码的其他部分的耗时在整体上就不那么显著了。
当然,您可能想要了解每次调用malloc后执行的总指令数(ring0和ring3),这时就需要使用系统仿真器或调试器了,这可以作为一项练习留给读者自己完成。
对于Edge浏览器来说,分布是非常相似的,但你会注意到它们更加分散,3x是一个保守和非常好的阀值:
对于IE和Edge浏览器来说,我们将其阀值分别设置为2x和3x,在此阀值以下被视为启用了PageHeap,否则就可以认为没有启用PageHeap。
您可以使用detect.html来检测自己的IE或Edge浏览器,如果结果大相径庭的话,请通知在下。此外,如果您有兴趣,也可以查阅检索和分析数据的相关代码,其地址位于https://github.com/snf/exploit/tree/master/anomalies/pageheap 。
小结
事实证明,只需要40ms的时间,您就可以迅速确定PageHeap是否存在,从而决定是否继续使用漏洞利用代码了。当然,这只是一个实验,结论未必绝对可靠,同时还需要在不同的cpus和虚拟化技术下做进一步的测试。但是别忘了,这只是ItWorksInMyPC(TM)项目中的一部分,还有更精彩的项目在等着您呢。
|