-
-
[旧帖] [原创]CFG机制原理 0.00雪花
-
发表于: 2015-5-22 22:24 2681
-
对Control Flow Guard 这个缓解机制比较感兴趣,所以自己调试了一下。
首先CFG只有在Windows8.1 Update3之后才有。我用的是win8.1 x64
CFG主要是在间接调用函数前对要调用的函数到Bitmap中检查,成功调用,失败直接蓝屏。这样可以增加漏洞利用的难度。
首先在下断点。
kd> bp nt!PspPrepareSystemDllInitBlock
断点触发后查看,通过下面的栈回溯可以看出是在创建进程时初始化SystemDllInitBlock这个全局变量的。
kd> k
Child-SP RetAddr Call Site
ffffd000`21855a70 fffff802`a44d957f nt!PspPrepareSystemDllInitBlock+0x36
ffffd000`21855ab0 fffff802`a4415980 nt!PspSetupUserProcessAddressSpace+0x13b
ffffd000`21855b50 fffff802`a44da5cf nt!PspAllocateProcess+0xa80
ffffd000`21855f20 fffff802`a41e34b3 nt!NtCreateUserProcess+0x55b
ffffd000`21856a90 00007ffc`8e8d70fa nt!KiSystemServiceCopyEnd+0x13
000000f0`a2cfdf48 00000000`00000000 ntdll!NtCreateUserProcess+0xa
查看nt!PspPrepareSystemDllInitBlock看看其中的代码是怎么样的。
一开始就有调用了nt!MmGetCfgBitMapInformation
这个函数的作用后面会提到其实就是获取bitmap的长度和地址
同时还调用了nt!ExGenRandom
产生一个随机数 放到LdrSystemDllInitBlock+0x50应该是系统会进行检测可能是为了防止被篡改。
接下来看看上面提到的nt!MmGetCfgBitMapInformation函数。
nt!MmGetCfgBitMapInformation返回的值放在rax中就是bitmap的开始地址nt!MmGetCfgBitMapInformation。第二个参数是一个out参数,放入的是bitmap的长度。放入的Bitmap地址是0000020000000000(Byte)即2TB(在x64环境下是2TB)。
fffff801`3bc10750 48897760 mov qword ptr [rdi+60h],rsi 这里在SystemDllInitBlock+0x60处放入Bitmap地址
fffff801`3bc10754 488b442448 mov rax,qword ptr [rsp+48h]
fffff801`3bc10759 48894768 mov qword ptr [rdi+68h],rax 这里在SystemDllInitBlock+0x68处放入长度
接下来再次下断点
kd> bp ntdll!LdrpCfgProcessLoadConfig
这个函数主要是检查进程是否支持CFG,同时将设置检查函数LdrpValidateUserCallTarget
kd> k
Child-SP RetAddr Call Site
000000fb`76bdf218 00007ffc`8e890463 ntdll!LdrpCfgProcessLoadConfig
000000fb`76bdf220 00007ffc`8e909208 ntdll!LdrpProcessMappedModule+0x1cf
000000fb`76bdf280 00007ffc`8e8ec0e4 ntdll!LdrpInitializeProcess+0x1020
000000fb`76bdf5a0 00007ffc`8e866eda ntdll!_LdrpInitialize+0x851b8
000000fb`76bdf610 00000000`00000000 ntdll!LdrInitializeThunk+0xe
0033:00007ffa`0395782a 41f7809000000000100000 test dword ptr [r8+90h],1000h [r8+90h]是Guard Flag
0033:00007ffa`03957835 7476 je ntdll!LdrpCfgProcessLoadConfig+0xb1 (00007ffa`039578ad)
0033:00007ffa`03957849 488b4930 mov rcx,qword ptr [rcx+30h] rcx指向IMAGE_NT_HEADER rcx+0x30指向ImageBase
0033:00007ffa`0395784d 4c8d4d38 lea r9,[rbp+38h]
0033:00007ffa`03957851 41b80d000000 mov r8d,0Dh
0033:00007ffa`03957857 b201 mov dl,1
0033:00007ffa`03957859 e87202fdff call ntdll!RtlImageDirectoryEntryToData (00007ffa`03927ad0) 这个函数是要获取Delay Import Directory的虚拟地址
0033:00007ffc`0ec1785e 4885c0 test rax,rax
0033:00007ffc`0ec17861 744a je ntdll!LdrpCfgProcessLoadConfig+0xb1 (00007ffc`0ec178ad)
0033:00007ffc`0ec17863 0fb74e14 movzx ecx,word ptr [rsi+14h]
0033:00007ffc`0ec17867 440fb74606 movzx r8d,word ptr [rsi+6]
0033:00007ffc`0ec1786c 488d5618 lea rdx,[rsi+18h]
0033:00007ffc`0ec17870 4803d1 add rdx,rcx
0033:00007ffc`0ec17873 33c9 xor ecx,ecx
0033:00007ffc`0ec17875 4585c0 test r8d,r8d
0033:00007ffc`0ec17878 7433 je ntdll!LdrpCfgProcessLoadConfig+0xb1 (00007ffc`0ec178ad)
0033:00007ffc`0ec1787a 448b480c mov r9d,dword ptr [rax+0Ch]
0033:00007ffc`0ec1787e 418bc1 mov eax,r9d
0033:00007ffc`0ec17881 2b420c sub eax,dword ptr [rdx+0Ch] VirtualAddress
0033:00007ffc`0ec17884 3b4208 cmp eax,dword ptr [rdx+8] VirtualSize
0033:00007ffc`0ec17887 720d jb ntdll!LdrpCfgProcessLoadConfig+0x9a (00007ffc`0ec17896)
0033:00007ffc`0ec17889 ffc1 inc ecx
0033:00007ffc`0ec1788b 4883c228 add rdx,28h 刚好是一个节区头的长度(IMAGE_SECTION_HEADER)这里是循环
0033:00007ffc`0ec1788f 413bc8 cmp ecx,r8d
0033:00007ffc`0ec17892 72ea jb ntdll!LdrpCfgProcessLoadConfig+0x82 (00007ffc`0ec1787e)
cmp qword ptr [ntdll!LdrSystemDllInitBlock+0x60 (00007ffc`0ecf0280)],0 比较地址是否为NULL是则直接结束
0033:00007ffc`0ec178bb b800400000 mov eax,4000h看以前的资料0x4000是保留的可能就是这里是最新的IMAGE_DLLCHARACTERISTICS_GUARD_CF flag
0033:00007ffc`0ec178c0 6685465e test word ptr [rsi+5Eh],ax option header DLLCharacstics
0033:00007ffc`0ec178c4 0f84a2000000 je ntdll!LdrpCfgProcessLoadConfig+0x170 (00007ffc`0ec1796c)
0033:00007ffc`0ec178ca f7839000000000010000 test dword ptr [rbx+90h],100h Guard Flags
0033:00007ffc`0ec178d4 0f8492000000 je ntdll!LdrpCfgProcessLoadConfig+0x170 (00007ffc`0ec1796c)
0033:00007ffc`0ec178da 488b5b70 mov rbx,qword ptr [rbx+70h] Guard CF address of check-function pointer
0033:00007ffc`0ec178de 4885db test rbx,rbx
0033:00007ffc`0ec178e1 0f8485000000 je ntdll!LdrpCfgProcessLoadConfig+0x170 (00007ffc`0ec1796c)
。。。。。。。。。。
0033:00007ffa`039578da 488b5b70 mov rbx,qword ptr [rbx+70h] ds:002b:00007ffa`038f9190={ntdll!_guard_check_icall_fptr (00007ffa`03a301d0)}
这里就是接下来放入LdrpValidateUserCallTarget验证函数的地方
..........................................
0033:00007ffc`0ec1793c 488d3d5da20100 lea rdi,[ntdll!LdrpValidateUserCallTarget (00007ffc`0ec31ba0)]
0033:00007ffc`0ec17943 4c8d45f0 lea r8,[rbp-10h]
0033:00007ffc`0ec17947 48893b mov qword ptr [rbx],rdi将LdrpValidateUserCallTarget放入
最后看看是LdrpValidateUserCallTarget是如何检测的
kd> bp ntdll!LdrpValidateUserCallTarget
kd> k
Child-SP RetAddr Call Site
00000058`856ff108 00007ffc`0ebcdcea ntdll!LdrpValidateUserCallTarget
00000058`856ff110 00007ffc`0ebcd9bb ntdll!LdrpCallInitRoutine+0x42
00000058`856ff170 00007ffc`0ebca73e ntdll!LdrpInitializeThread+0x15b
00000058`856ff250 00007ffc`0ebca65e ntdll!_LdrpInitialize+0x8e
00000058`856ff2c0 00000000`00000000 ntdll!LdrInitializeThunk+0xe
0033:00007ffc`0ec31ba0 488b15d9e60b00 mov rdx,qword ptr[ntdll!LdrSystemDllInitBlock+0x60 (00007ffc`0ecf0280)] ds:002b:00007ffc`0ecf0280=00007df5ff3d0000
0033:00007ffc`0ec31ba7 488bc1 mov rax,rcx
0033:00007ffc`0ec31baa 48c1e809 shr rax,9将传入的地址除以512
0033:00007ffc`0ec31bae 488b14c2 mov rdx,qword ptr [rdx+rax*8]然后以此为偏移量获取相应的8字节
0033:00007ffc`0ec31bb2 488bc1 mov rax,rcx
0033:00007ffc`0ec31bb5 48c1e803 shr rax,3
0033:00007ffc`0ec31bb9 f6c10f test cl,0Fh
0033:00007ffc`0ec31bbc 7507 jne ntdll!LdrpValidateUserCallTarget+0x25 (00007ffc`0ec31bc5)
0033:00007ffc`0ec31bbe 480fa3c2 bt rdx,rax 这里检测相应的bit是否置位
0033:00007ffc`0ec31bc2 730c jae ntdll!LdrpValidateUserCallTarget+0x30 (00007ffc`0ec31bd0)
0033:00007ffc`0ec31bc4 c3 ret
0033:00007ffc`0ec31bc5 4883c801 or rax,1 没有以16对齐的情况按8字节对齐后查找
0033:00007ffc`0ec31bc9 480fa3c2 bt rdx,rax 这里也是检测相应的bit位是否置位
0033:00007ffc`0ec31bcd 7301 jae ntdll!LdrpValidateUserCallTarget+0x30 (00007ffc`0ec31bd0)
0033:00007ffc`0ec31bcf c3 ret
0033:00007ffc`0ec31bd0 488bd1 mov rdx,rcx 如果间接调用的函数被改,直接异常
0033:00007ffc`0ec31bd3 b90a000000 mov ecx,0Ah
0033:00007ffc`0ec31bd8 cd29 int 29h
0033:00007ffc`0ec31bda c3 ret
可以看到开启了CFG的进程虚拟尺寸为2TB
参考资料:http://www.powerofcommunity.net/poc2014/mj0011.pdf
http://blogs.msdn.com/b/vcblog/archive/2014/12/08/visual-studio-2015-preview-work-in-progress-security-feature.aspx
首先CFG只有在Windows8.1 Update3之后才有。我用的是win8.1 x64
CFG主要是在间接调用函数前对要调用的函数到Bitmap中检查,成功调用,失败直接蓝屏。这样可以增加漏洞利用的难度。
首先在下断点。
kd> bp nt!PspPrepareSystemDllInitBlock
断点触发后查看,通过下面的栈回溯可以看出是在创建进程时初始化SystemDllInitBlock这个全局变量的。
kd> k
Child-SP RetAddr Call Site
ffffd000`21855a70 fffff802`a44d957f nt!PspPrepareSystemDllInitBlock+0x36
ffffd000`21855ab0 fffff802`a4415980 nt!PspSetupUserProcessAddressSpace+0x13b
ffffd000`21855b50 fffff802`a44da5cf nt!PspAllocateProcess+0xa80
ffffd000`21855f20 fffff802`a41e34b3 nt!NtCreateUserProcess+0x55b
ffffd000`21856a90 00007ffc`8e8d70fa nt!KiSystemServiceCopyEnd+0x13
000000f0`a2cfdf48 00000000`00000000 ntdll!NtCreateUserProcess+0xa
查看nt!PspPrepareSystemDllInitBlock看看其中的代码是怎么样的。
一开始就有调用了nt!MmGetCfgBitMapInformation
这个函数的作用后面会提到其实就是获取bitmap的长度和地址
同时还调用了nt!ExGenRandom
产生一个随机数 放到LdrSystemDllInitBlock+0x50应该是系统会进行检测可能是为了防止被篡改。
接下来看看上面提到的nt!MmGetCfgBitMapInformation函数。
nt!MmGetCfgBitMapInformation返回的值放在rax中就是bitmap的开始地址nt!MmGetCfgBitMapInformation。第二个参数是一个out参数,放入的是bitmap的长度。放入的Bitmap地址是0000020000000000(Byte)即2TB(在x64环境下是2TB)。
fffff801`3bc10750 48897760 mov qword ptr [rdi+60h],rsi 这里在SystemDllInitBlock+0x60处放入Bitmap地址
fffff801`3bc10754 488b442448 mov rax,qword ptr [rsp+48h]
fffff801`3bc10759 48894768 mov qword ptr [rdi+68h],rax 这里在SystemDllInitBlock+0x68处放入长度
接下来再次下断点
kd> bp ntdll!LdrpCfgProcessLoadConfig
这个函数主要是检查进程是否支持CFG,同时将设置检查函数LdrpValidateUserCallTarget
kd> k
Child-SP RetAddr Call Site
000000fb`76bdf218 00007ffc`8e890463 ntdll!LdrpCfgProcessLoadConfig
000000fb`76bdf220 00007ffc`8e909208 ntdll!LdrpProcessMappedModule+0x1cf
000000fb`76bdf280 00007ffc`8e8ec0e4 ntdll!LdrpInitializeProcess+0x1020
000000fb`76bdf5a0 00007ffc`8e866eda ntdll!_LdrpInitialize+0x851b8
000000fb`76bdf610 00000000`00000000 ntdll!LdrInitializeThunk+0xe
0033:00007ffa`0395782a 41f7809000000000100000 test dword ptr [r8+90h],1000h [r8+90h]是Guard Flag
0033:00007ffa`03957835 7476 je ntdll!LdrpCfgProcessLoadConfig+0xb1 (00007ffa`039578ad)
0033:00007ffa`03957849 488b4930 mov rcx,qword ptr [rcx+30h] rcx指向IMAGE_NT_HEADER rcx+0x30指向ImageBase
0033:00007ffa`0395784d 4c8d4d38 lea r9,[rbp+38h]
0033:00007ffa`03957851 41b80d000000 mov r8d,0Dh
0033:00007ffa`03957857 b201 mov dl,1
0033:00007ffa`03957859 e87202fdff call ntdll!RtlImageDirectoryEntryToData (00007ffa`03927ad0) 这个函数是要获取Delay Import Directory的虚拟地址
0033:00007ffc`0ec1785e 4885c0 test rax,rax
0033:00007ffc`0ec17861 744a je ntdll!LdrpCfgProcessLoadConfig+0xb1 (00007ffc`0ec178ad)
0033:00007ffc`0ec17863 0fb74e14 movzx ecx,word ptr [rsi+14h]
0033:00007ffc`0ec17867 440fb74606 movzx r8d,word ptr [rsi+6]
0033:00007ffc`0ec1786c 488d5618 lea rdx,[rsi+18h]
0033:00007ffc`0ec17870 4803d1 add rdx,rcx
0033:00007ffc`0ec17873 33c9 xor ecx,ecx
0033:00007ffc`0ec17875 4585c0 test r8d,r8d
0033:00007ffc`0ec17878 7433 je ntdll!LdrpCfgProcessLoadConfig+0xb1 (00007ffc`0ec178ad)
0033:00007ffc`0ec1787a 448b480c mov r9d,dword ptr [rax+0Ch]
0033:00007ffc`0ec1787e 418bc1 mov eax,r9d
0033:00007ffc`0ec17881 2b420c sub eax,dword ptr [rdx+0Ch] VirtualAddress
0033:00007ffc`0ec17884 3b4208 cmp eax,dword ptr [rdx+8] VirtualSize
0033:00007ffc`0ec17887 720d jb ntdll!LdrpCfgProcessLoadConfig+0x9a (00007ffc`0ec17896)
0033:00007ffc`0ec17889 ffc1 inc ecx
0033:00007ffc`0ec1788b 4883c228 add rdx,28h 刚好是一个节区头的长度(IMAGE_SECTION_HEADER)这里是循环
0033:00007ffc`0ec1788f 413bc8 cmp ecx,r8d
0033:00007ffc`0ec17892 72ea jb ntdll!LdrpCfgProcessLoadConfig+0x82 (00007ffc`0ec1787e)
cmp qword ptr [ntdll!LdrSystemDllInitBlock+0x60 (00007ffc`0ecf0280)],0 比较地址是否为NULL是则直接结束
0033:00007ffc`0ec178bb b800400000 mov eax,4000h看以前的资料0x4000是保留的可能就是这里是最新的IMAGE_DLLCHARACTERISTICS_GUARD_CF flag
0033:00007ffc`0ec178c0 6685465e test word ptr [rsi+5Eh],ax option header DLLCharacstics
0033:00007ffc`0ec178c4 0f84a2000000 je ntdll!LdrpCfgProcessLoadConfig+0x170 (00007ffc`0ec1796c)
0033:00007ffc`0ec178ca f7839000000000010000 test dword ptr [rbx+90h],100h Guard Flags
0033:00007ffc`0ec178d4 0f8492000000 je ntdll!LdrpCfgProcessLoadConfig+0x170 (00007ffc`0ec1796c)
0033:00007ffc`0ec178da 488b5b70 mov rbx,qword ptr [rbx+70h] Guard CF address of check-function pointer
0033:00007ffc`0ec178de 4885db test rbx,rbx
0033:00007ffc`0ec178e1 0f8485000000 je ntdll!LdrpCfgProcessLoadConfig+0x170 (00007ffc`0ec1796c)
。。。。。。。。。。
0033:00007ffa`039578da 488b5b70 mov rbx,qword ptr [rbx+70h] ds:002b:00007ffa`038f9190={ntdll!_guard_check_icall_fptr (00007ffa`03a301d0)}
这里就是接下来放入LdrpValidateUserCallTarget验证函数的地方
..........................................
0033:00007ffc`0ec1793c 488d3d5da20100 lea rdi,[ntdll!LdrpValidateUserCallTarget (00007ffc`0ec31ba0)]
0033:00007ffc`0ec17943 4c8d45f0 lea r8,[rbp-10h]
0033:00007ffc`0ec17947 48893b mov qword ptr [rbx],rdi将LdrpValidateUserCallTarget放入
最后看看是LdrpValidateUserCallTarget是如何检测的
kd> bp ntdll!LdrpValidateUserCallTarget
kd> k
Child-SP RetAddr Call Site
00000058`856ff108 00007ffc`0ebcdcea ntdll!LdrpValidateUserCallTarget
00000058`856ff110 00007ffc`0ebcd9bb ntdll!LdrpCallInitRoutine+0x42
00000058`856ff170 00007ffc`0ebca73e ntdll!LdrpInitializeThread+0x15b
00000058`856ff250 00007ffc`0ebca65e ntdll!_LdrpInitialize+0x8e
00000058`856ff2c0 00000000`00000000 ntdll!LdrInitializeThunk+0xe
0033:00007ffc`0ec31ba0 488b15d9e60b00 mov rdx,qword ptr[ntdll!LdrSystemDllInitBlock+0x60 (00007ffc`0ecf0280)] ds:002b:00007ffc`0ecf0280=00007df5ff3d0000
0033:00007ffc`0ec31ba7 488bc1 mov rax,rcx
0033:00007ffc`0ec31baa 48c1e809 shr rax,9将传入的地址除以512
0033:00007ffc`0ec31bae 488b14c2 mov rdx,qword ptr [rdx+rax*8]然后以此为偏移量获取相应的8字节
0033:00007ffc`0ec31bb2 488bc1 mov rax,rcx
0033:00007ffc`0ec31bb5 48c1e803 shr rax,3
0033:00007ffc`0ec31bb9 f6c10f test cl,0Fh
0033:00007ffc`0ec31bbc 7507 jne ntdll!LdrpValidateUserCallTarget+0x25 (00007ffc`0ec31bc5)
0033:00007ffc`0ec31bbe 480fa3c2 bt rdx,rax 这里检测相应的bit是否置位
0033:00007ffc`0ec31bc2 730c jae ntdll!LdrpValidateUserCallTarget+0x30 (00007ffc`0ec31bd0)
0033:00007ffc`0ec31bc4 c3 ret
0033:00007ffc`0ec31bc5 4883c801 or rax,1 没有以16对齐的情况按8字节对齐后查找
0033:00007ffc`0ec31bc9 480fa3c2 bt rdx,rax 这里也是检测相应的bit位是否置位
0033:00007ffc`0ec31bcd 7301 jae ntdll!LdrpValidateUserCallTarget+0x30 (00007ffc`0ec31bd0)
0033:00007ffc`0ec31bcf c3 ret
0033:00007ffc`0ec31bd0 488bd1 mov rdx,rcx 如果间接调用的函数被改,直接异常
0033:00007ffc`0ec31bd3 b90a000000 mov ecx,0Ah
0033:00007ffc`0ec31bd8 cd29 int 29h
0033:00007ffc`0ec31bda c3 ret
可以看到开启了CFG的进程虚拟尺寸为2TB
参考资料:http://www.powerofcommunity.net/poc2014/mj0011.pdf
http://blogs.msdn.com/b/vcblog/archive/2014/12/08/visual-studio-2015-preview-work-in-progress-security-feature.aspx
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
看原图
赞赏
雪币:
留言: