Windows有关堆的代码是操作系统中最重要的一部分代码,在Window7中,它用LFH(Low Fragmentation Heap)取代了之前Windows XP版本中的lookaside。这篇文章中,不会谈论有关LFH的内部实现机理。主要是实现的部分太过于复杂,并且还经常改变,只有编写代码尝试才能了解具体的分配规则。目前已经有了很多关于LFH的研究资料,尤其是Chris Valasek进行了很多的研究。
http://illmatics.com/Understanding_the_LFH.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图片)。这样的话所有分配的堆块就是均匀分布的了,这也给我们的漏洞利用带来难度。