CFG防护机制简单实践与介绍

简介

Control Flow Guard(CFG)是较新的windows漏洞利用缓解措施,旨在解决内存损坏漏洞。 针对的是间接跳转的保护,比如call eax,jmp eax等。 CFG扩展了先前的漏洞缓解技术,例如GS,DEP和ASLR。

这个保护措施从Microsoft Visual Studio 2015及以上开始支持。

系统的话是从windows 8.1开始吧

简单来说就是在call eax等间接跳转之前加个验证。

实践

使用的示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef int(*fun_t)(int);

int foo(int a)
{
printf("hellow world %d\n",a);
return a;
}
class CTargetObject
{
public:
fun_t fun;
};
int main()
{
int i = 0;
CTargetObject *o_array = new CTargetObject[5];
for (i = 0; i < 5 ; i++)
o_array[i].fun = foo;
o_array[0].fun(1);
return 0;
}

修改编译选项

上面改了可能报错:

命令行 error D8016: “/ZI”和“/guard:cf”命令行选项不兼容

就是CFG与这个ZI不兼容,所以我们得关闭ZI

编译完成我们可以使用winchecksec工具查看是否开启了CFG

https://github.com/trailofbits/winchecksec

或者使用VS自带的工具dumpbin.exe,命令如下:

1
dumpbin.exe /headers /loadconfig E:\VS2017\learnCFG\Debug\learnCFG.exe

开启了CFG,OPTIONAL HEADER VALUES里面应该有Control Flow Guard

Section contains the following load config那里有“CF Instrumented”和“FID table present”等

我们看看开了CFG和没有开的区别

首先是没开的

接下来是开了的

双击跟过去那个检查函数是没有代码的,这应该是运行程序的时候再填充了

运行时,实际调用的是下面的ntdll!LdrpValidateUserCallTarget

原理分析

首先说说CFGBitmap

pCFG检查基于CFGBitmap,它表示在进程空间内所有函数的起始位置。在进程空间内每8个字节的状态对应CFGBitmap中的一位。如果函数的地址是合法有效的,那么这个函数对应在CFGbitmap的位置会被设置位1,否则是0。 p p一个Bitmap的大小是4字节

下面以地址0x00b01030为例(这是先知参考文章的例子)

具体调试结果跟解析如下了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
ntdll!LdrpValidateUserCallTarget:
77408be0 8b15f8124a77 mov edx,dword ptr [ntdll!LdrSystemDllInitBlock+0xb0 (774a12f8)] ds:002b:774a12f8=00730000
;获取CFGBitmap基址
77408be6 8bc1 mov eax,ecx ;ecx为要检验的函数地址,给到eax (ecx=007113a0)
77408be8 c1e808 shr eax,8 ;取地址的高3个字节 (eax = 00007113)
ntdll!LdrpValidateUserCallTargetBitMapCheck:
77408beb 8b1482 mov edx,dword ptr [edx+eax*4] ds:002b:0074c44c=10100444 ;获取该地址对应的bitmap
77408bee 8bc1 mov eax,ecx
77408bf0 c1e803 shr eax,3 ;舍弃最低3个bit
77408bf3 f6c10f test cl,0Fh ;判断目标地址是否以0x10对齐,跟0xf与运算,判断是否等于 0
77408bf6 7506 jne ntdll!LdrpValidateUserCallTargetBitMapRet+0x1 (77408bfe) ;不等于0则跳转到77408bfe
; bt系列指令它们的结果影响CF标志位
;BT:把指定位传送给CF;
;BTC:把指定位传送给CF后还使该位变反;
;BTR:把指定位传送给CF后还使该位变为0;
;BTS:把指定位传送给CF后还使该位变为1;
77408bf8 0fa3c2 bt edx,eax ;将edx的第eax位,给到CF(其实bt指令最多取eax的低5个bit)
;假如bt的第二个参数很大,32位取低3bit,64位取低5bit,参考http://faydoc.tripod.com/cpu/bt.htm的Description的第二段
;edx为00010000 00010000 00000100 01000100,eax的最低5bit为10100,即20,可以看到索引20位置为1,置CF为1,所以是有效的
77408bfb 730a jae ntdll!LdrpValidateUserCallTargetBitMapRet+0xa (77408c07) ;CF等于0才跳
ntdll!LdrpValidateUserCallTargetBitMapRet:
77408bfd c3 ret ;正常情况下在这就返回执行正常代码了
77408bfe 0fbaf000 btr eax,0 ;跟0x10不对齐跳到这,将eax的最低bit给到CF,之后将最低bit置0
77408c02 0fa3c2 bt edx,eax ;跟上面的一样,取低5bit作为edx的bit的索引,给到CF
77408c05 7309 jae ntdll!LdrpValidateUserCallTargetBitMapRet+0x13 (77408c10) ;这就跳到失败的流程了
77408c07 83c801 or eax,1 ;上面77408bfb判断无效后,来到这将eax最低位置1后再判断
77408c0a 0fa3c2 bt edx,eax
77408c0d 7301 jae ntdll!LdrpValidateUserCallTargetBitMapRet+0x13 (77408c10) ;这就跳到失败的流程了
77408c0f c3 ret
77408c10 51 push ecx
77408c11 8d642480 lea esp,[esp-80h]
77408c15 0f110424 movups xmmword ptr [esp],xmm0
77408c19 0f114c2410 movups xmmword ptr [esp+10h],xmm1
77408c1e 0f11542420 movups xmmword ptr [esp+20h],xmm2
77408c23 0f115c2430 movups xmmword ptr [esp+30h],xmm3
77408c28 0f11642440 movups xmmword ptr [esp+40h],xmm4
77408c2d 0f116c2450 movups xmmword ptr [esp+50h],xmm5
77408c32 0f11742460 movups xmmword ptr [esp+60h],xmm6
77408c37 0f117c2470 movups xmmword ptr [esp+70h],xmm7
77408c3c e8e7460500 call ntdll!RtlpHandleInvalidUserCallTarget (7745d328)
77408c41 0f100424 movups xmm0,xmmword ptr [esp]
77408c45 0f104c2410 movups xmm1,xmmword ptr [esp+10h]
77408c4a 0f10542420 movups xmm2,xmmword ptr [esp+20h]
77408c4f 0f105c2430 movups xmm3,xmmword ptr [esp+30h]
77408c54 0f10642440 movups xmm4,xmmword ptr [esp+40h]
77408c59 0f106c2450 movups xmm5,xmmword ptr [esp+50h]
77408c5e 0f10742460 movups xmm6,xmmword ptr [esp+60h]
77408c63 0f107c2470 movups xmm7,xmmword ptr [esp+70h]
77408c68 8da42480000000 lea esp,[esp+80h]
77408c6f 59 pop ecx
77408c70 c3 ret

那么最终假如校验失败,那就会跳到

1
77408c3c e8e7460500      call    ntdll!RtlpHandleInvalidUserCallTarget (7745d328)

最终int 0x29抛出异常

参考

https://blog.trendmicro.com/trendlabs-security-intelligence/exploring-control-flow-guard-in-windows-10/
https://docs.microsoft.com/en-us/windows/win32/secbp/control-flow-guard
https://xz.aliyun.com/t/2587
https://docs.microsoft.com/en-us/windows/win32/secbp/control-flow-guard#how-do-i-tell-that-a-binary-is-under-control-flow-guard

打赏专区