关于Windows漏洞利用堆风水的思考

 

Windows有关堆的代码是操作系统中最重要的一部分代码,在Window7中,它用LFH(Low Fragmentation Heap)取代了之前Windows XP版本中的lookaside。这篇文章中,不会谈论有关LFH的内部实现机理。主要是实现的部分太过于复杂,并且还经常改变,只有编写代码尝试才能了解具体的分配规则。目前已经有了很多关于LFH的研究资料,尤其是Chris Valasek进行了很多的研究。

http://illmatics.com/Understanding_the_LFH.pdf

https://media.blackhat.com/bh-us-12/Briefings/Valasek/BH_US_12_Valasek_Windows_8_Heap_Internals_Slides.pdf

https://www.blackhat.com/docs/us-16/materials/us-16-Yason-Windows-10-Segment-Heap-Internals.pdf

这篇文章将介绍存在于WIndows8之后Windows中的一个已知堆分配问题。

堆分配时候的随机化出现是为了缓解系统中UAF漏洞的利用。已知存在这种UAF漏洞的时候,通常进行malloc() -> free() -> malloc()的操作,让两次申请的堆块都在同一个地址。

分配出的堆形状如下。


这种利用方式在现在的操作系统中已经行不通了,因为LFH机制会让堆在申请的时候返回的堆块地址是随机的。当然,就算加入了这种漏洞利用缓解方式,依然会出现相应的利用方式,比如用一些目标对象喷满整个userblocks,然后修改他们的metadata,总会有办法做到代码执行。

但是在这里我们提出了一种攻击随机化本身的方式。

 

随机化的实现

RtlpInitializeLfhRandomDataArray 会用0x100字节的随机数填充 RtlpLowFragHeapRandomData 数组

然后这个随机数组将和其他一些值一起计算出某个位置。然后再从这个位置出发,找到userblock当中的第一个空闲堆块给用户。

 

攻击步骤

进行如下的堆块分配的话,会返回两个不同地址的堆块给你。

但是我们也可以进行如下的操作

最后一次 HeapAlloc 会在userblock对应的bitmap中寻找一个空的chunk,因为所有的bitmap都已经遍历过一遍了,所以我们会找到第一次分配的堆块地址。

同样这种技巧也可以用于分配出两个连续的堆块。

上图的chunk和tmp_chunk在内存中将会是连续的

最后将附上对应的代码

 

Windows 10 Build 16179

但是Windows 10 Build 16179之后的版本上,这个方法不再适用

我们使用bindiff比较了ntdll库的不同,发现随机数组产生的机制没有出现变化,没有多写入额外的 RtlpLowFragHeapRandomData

继续查看代码,发现从随机数据数组中选取新值的逻辑改变了。之前选择数据的方式只是每次递增1,最多到0x100,然后重复。但是在16179之后的版本中,他们增加了一个对 RtlpHeapGenerateRandomValue32 函数的调用,会修改下一次获取的index的值。

build 14393

build 16179

我对不同操作系统中的这两个代码进行调试,附上了对应的调试片段,可以看到新版本中每次分配堆块的机制都不同。

0:001> u RtlpLowFragHeapAllocFromContext+1B1 
ntdll!RtlpLowFragHeapAllocFromContext+0x1b1: 
00007ffb`13768dd1 470fb68c0860091500 movzx r9d,byte ptr [r8+r9+150960h] 
00007ffb`13768dda 418d4001        lea     eax,[r8+1] 
00007ffb`13768dde 664123c4        and     ax,r12w 
00007ffb`13768de2 668981b2170000  mov     word ptr [rcx+17B2h],ax 
00007ffb`13768de9 4d8b4220        mov     r8,qword ptr [r10+20h] 
00007ffb`13768ded 4d8b6228        mov     r12,qword ptr [r10+28h] 
00007ffb`13768df1 4983f840        cmp     r8,40h 
00007ffb`13768df5 0f8250010000    jb      ntdll!RtlpLowFragHeapAllocFromContext+0x32b (00007ffb`13768f4b) 
0:001> bp RtlpLowFragHeapAllocFromContext+1B1 ".printf \"RtlpLowFragHeapRandomData == 0x%p, currIdx == 0x%p\\r\\n\", @r9, @r8;g" 
0:001> g 
... 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x00000000000000ef 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x00000000000000f0 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x00000000000000f1 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x00000000000000f2 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x00000000000000f3 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x00000000000000f4 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x00000000000000f5 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x00000000000000f6 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x00000000000000f7 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x00000000000000f8 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x00000000000000f9 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x00000000000000fa 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x00000000000000fb 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x00000000000000fc 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x00000000000000fd 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x00000000000000fe 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x00000000000000ff 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x0000000000000000 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x0000000000000001 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x0000000000000002 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x0000000000000003 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x0000000000000004 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x0000000000000005 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x0000000000000006 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x0000000000000007 
RtlpLowFragHeapRandomData == 0x00007ffb13730000, currIdx == 0x0000000000000008

build 16179

0:001> u ntdll!RtlpLowFragHeapAllocFromContext+327 
ntdll!RtlpLowFragHeapAllocFromContext+0x327: 
00007ffb`e7e5df27 440fb69c0800691500 movzx r11d,byte ptr [rax+rcx+156900h] 
00007ffb`e7e5df30 4983f940        cmp     r9,40h 
00007ffb`e7e5df34 730e            jae     ntdll!RtlpLowFragHeapAllocFromContext+0x344 (00007ffb`e7e5df44) 
00007ffb`e7e5df36 4d8b4628        mov     r8,qword ptr [r14+28h] 
00007ffb`e7e5df3a 4c3bce          cmp     r9,rsi 
00007ffb`e7e5df3d 7358            jae     ntdll!RtlpLowFragHeapAllocFromContext+0x397 (00007ffb`e7e5df97) 
00007ffb`e7e5df3f 418bf1          mov     esi,r9d 
00007ffb`e7e5df42 eb53            jmp     ntdll!RtlpLowFragHeapAllocFromContext+0x397 (00007ffb`e7e5df97) 
0:001> bp ntdll!RtlpLowFragHeapAllocFromContext+327 ".printf \"RtlpLowFragHeapRandomData == 0x%p, currIdx == 0x%p\\r\\n\", @rcx, @rax;g" 
0:001> g 
... 
RtlpLowFragHeapRandomData == 0x00007ffbe7e30000, currIdx == 0x00000000000000fc 
RtlpLowFragHeapRandomData == 0x00007ffbe7e30000, currIdx == 0x00000000000000fd 
RtlpLowFragHeapRandomData == 0x00007ffbe7e30000, currIdx == 0x00000000000000fe 
RtlpLowFragHeapRandomData == 0x00007ffbe7e30000, currIdx == 0x00000000000000ff 
RtlpLowFragHeapRandomData == 0x00007ffbe7e30000, currIdx == 0x0000000000000000 
RtlpLowFragHeapRandomData == 0x00007ffbe7e30000, currIdx == 0x00000000000000c6    <--- here 
RtlpLowFragHeapRandomData == 0x00007ffbe7e30000, currIdx == 0x00000000000000c7 
RtlpLowFragHeapRandomData == 0x00007ffbe7e30000, currIdx == 0x00000000000000c8 
RtlpLowFragHeapRandomData == 0x00007ffbe7e30000, currIdx == 0x00000000000000c9 
RtlpLowFragHeapRandomData == 0x00007ffbe7e30000, currIdx == 0x00000000000000ca 
RtlpLowFragHeapRandomData == 0x00007ffbe7e30000, currIdx == 0x00000000000000cb 
RtlpLowFragHeapRandomData == 0x00007ffbe7e30000, currIdx == 0x00000000000000cc 
RtlpLowFragHeapRandomData == 0x00007ffbe7e30000, currIdx == 0x00000000000000cd 
RtlpLowFragHeapRandomData == 0x00007ffbe7e30000, currIdx == 0x00000000000000ce 
RtlpLowFragHeapRandomData == 0x00007ffbe7e30000, currIdx == 0x00000000000000cf 
RtlpLowFragHeapRandomData == 0x00007ffbe7e30000, currIdx == 0x00000000000000d0 
RtlpLowFragHeapRandomData == 0x00007ffbe7e30000, currIdx == 0x00000000000000d1 
RtlpLowFragHeapRandomData == 0x00007ffbe7e30000, currIdx == 0x00000000000000d2

 

缓解机制

从上面的代码中我们可以看出,这里并不是随机产生一个数,然后对 0xff 求模获得对应值的。这种方法会让后面的堆块得到更多的使用率,降低了效率。

为了避免这种情况,代码所选取的索引不是在0xff处重置为随机索引,而是只有当当前索引的MSB和LSB相等时才会重置为随机索引(见上面的IDA图片)。这样的话所有分配的堆块就是均匀分布的了,这也给我们的漏洞利用带来难度。

(完)