• CFG——ControlFlowGuard 控制流保护


    CFG防护机制的简要分析 - 先知社区 (aliyun.com)

    Exploring Control Flow Guard in Windows 10 (trendmicro.com)

    本文主要来自上面两篇文章,自己做一个记录罢了。

    CFG 通过在间接跳转(Indirect Call)前插入校验代码(比如 call dword ptr ss:[ebp-8] 等等 ),检查目标地址的有效性,进而可以阻止执行流跳转到预期之外的地点, 最终及时并有效的进行异常处理,避免引发相关的安全问题。

    CFG 的实现需要联合编译器、操作系统用户层库和内核模块,它是一个汇编层面的保护。

    首先,一个正常的函数地址(x86)的前24位会被取出,作为一个偏移值(OFFSET)。CFGBitMapBase + OFFSET*4  地址中存储着一个计算值(每4字节作为) ,_guard_check_icall_fptr 函数会对传入的参数进行计算,然后与存储着的计算值进行比较:

    ① 最后一字节的最后4位全为0,则取最后一字节的前5位作为一个偏移值,若对应的存储值(从0开始数)的对应位是1,则函数地址有效。

    ② (windows 10 pro 2018年)最后一字节的最后4位不全为0,则取最后一字节的前5位作为一个偏移值。若对应的存储值(从0开始数)的对应位是1,则函数地址有效并返回;

    Win 10 1909(OS内部版本18363.1198) 中,最后一字节的最后4位不全为0,则取最后一字节的前5位并将这5位的最后一位置0,再作为一个偏移值。若对应的存储值(从0开始数)的对应位是1,则执行下一步

    ③ 若前两个都不满足,或第二个满足,则将前5位的最后一位置1,作为一个偏移值。若对应的存储值(从0开始数)的对应的位是1,则函数地址有效。否则判定无效,并进行异常处理。

    借助上面提到的文章,自己添加了一些函数进去,对编译的程序进行调试:

    因为是用 C++ 写的,使用的是 __cdecl (C 规范的) 调用约定,参数从右到左入栈,由调用者负责清除栈。

    可以看到 _guard_check_icall_fptr 程序实际调用了 ValidateUserCallTarget 函数

    系统:Windows 10 1909   编译器:VS 2019 win32 release版

    mov
    edx,dword ptr ds:[77D112F8] // 获得 CFGBitMapBase 地址 mov eax,ecx              // ecx = 要调用的函数地址 shr eax,8               // 取函数地址的前24位作为 OFFSET mov edx,dword ptr ds:[edx+eax*4] // 到 CFGBitMapBase+OFFSET*4 取值 mov eax,ecx              shr eax,3               // eax 右移三位 test cl,F               // 函数地址的最后4位是否全为0
    jne ntdll.77C79DBE         // 不全为0则跳转到后面执行 bt edx,eax              // Bit Test 指令,第一个操作数(edx)是寄存器且
                        // eax > 32时,将会对 eax = eax mod 32 (AX 时模数是16)
                        // 然后再取 edx 相对应的位的值放入 CF 中
                        // 因为右移了3位,最后一字节前5位能表示的最大值是31,
                        // 因此其实取余后的值也还是由最后一字节的前5位表示

    jae ntdll.77C79DC7         // 若edx对应的位是0,则跳转,否则正常返回 ret

    77C79DBE
    btr eax,0               // 取 eax 最后一位,放入 CF 并将 eax 最后一位置 0 bt edx,eax              // 取 edx 对应的位放入 CF,与前面原理相同 jae ntdll.77C79DD0         // 若 CF == 0,则进入异常处理
    77C79DC7
    or eax,1               // 将 eax 最后一位置 1 bt edx,eax              // 取对应位 jae ntdll.77C79DD0 ret


    77C79DD0:              // 异常处理 push ecx lea esp,dword ptr ss:[esp-80] movups xmmword ptr ss:[esp],xmm0 movups xmmword ptr ss:[esp+10],xmm1 movups xmmword ptr ss:[esp+20],xmm2 movups xmmword ptr ss:[esp+30],xmm3 movups xmmword ptr ss:[esp+40],xmm4 movups xmmword ptr ss:[esp+50],xmm5 movups xmmword ptr ss:[esp+60],xmm6 movups xmmword ptr ss:[esp+70],xmm7 call <ntdll.@RtlpHandleInvalidUserCallTarget@4> movups xmm0,xmmword ptr ss:[esp] movups xmm1,xmmword ptr ss:[esp+10] movups xmm2,xmmword ptr ss:[esp+20] movups xmm3,xmmword ptr ss:[esp+30] movups xmm4,xmmword ptr ss:[esp+40] movups xmm5,xmmword ptr ss:[esp+50] movups xmm6,xmmword ptr ss:[esp+60] movups xmm7,xmmword ptr ss:[esp+70] lea esp,dword ptr ss:[esp+80] pop ecx ret

    同时,若在编译过程中不开启 ASLR ,则自己编写的函数的 CFGBitMap 值会是 FFFFFFFF,而系统函数的值则不会是

    使用不同的运行库也会导致某些函数的 CFGBitMap 值为 FFFFFFFF,猜测如下:如果代码写死在程序中,不需要调用外部 DLL 库中的文件,则值为 FFFFFFFF,如果需要调用外部 DLL 库中的文件,则值不为全 F。

  • 相关阅读:
    第七周总结
    结对开发nabcd
    第六周总结
    地铁售票设计思想及部分代码
    第二周总结
    进度总结(地铁查询购票)
    第三周总结
    冲刺四
    冲刺三
    冲刺2
  • 原文地址:https://www.cnblogs.com/Rev-omi/p/14027780.html
Copyright © 2020-2023  润新知