Windows MVSC编译器实现Xtended Flow Guard(XFG)保护机制的原理分析

admin 2021年4月2日12:05:50评论95 views字数 46562阅读155分12秒阅读模式

Windows MVSC编译器实现Xtended Flow Guard(XFG)保护机制的原理分析

Windows MVSC编译器实现Xtended Flow Guard(XFG)保护机制的原理分析
一、前言

近期,微软正在开发Xtended Flow Guard(XFG),这是Control Flow Guard(控制流防护,CFG)的演进版本,作为其自身的控制流完整性实现。XFG通过不同类型函数原型的哈希值,限制间接控制流的转移。在这篇文章中,深入讨论了MSVC编译器是如何生成XFG函数的原型哈希值。

Windows MVSC编译器实现Xtended Flow Guard(XFG)保护机制的原理分析
二、概述

2014年,微软推出了名为“Control Flow Integrity”(控制流防护,CFG)的控制流完整性(CFI)解决方案。在此前,已经有很多研究人员对CFG展开了广泛的研究。随着时间的推移,接连发现了许多绕过CFG的方法。其中一些绕过依赖于实现上的问题(例如JIT编译器的集成,或可以滥用敏感的API),但最终都得到了解决。但是,有一个设计上的问题始终存在——CFG没有提供有效调用目标的任何粒度。任何受保护的间接调用都被允许去调用任何有效的调用目标。在体积较大的二进制文件中,有效的调用目标可能会达到上千个,这就让攻击者拥有了足够的灵活性,可以通过串联有效的C++虚拟函数来绕过CFG(例如:伪造的面向对象编程设计,COOP)。

我们把时间线快进几年。微软一直在开发CFG的改进版本,被称为“Xtended Flow Guard”(XFG)。XFG通过类型签名检查限制间接调用或跳转,从而提供了更细粒度的CFI。XFG背后的一个关键概念是,在编译时将基于类型签名的哈希分配给那些可以作为间接调用/跳转目标的函数。然后,在XFG指示的间接调用点上,进行哈希检查,仅允许具有预期签名哈希的函数。

几周前,研究员Connor McGarr发表了一篇文章,名为《漏洞利用开发:在岩石和XFG之间》,文章说明了XFG的工作方式及其潜在弱点。这篇文章激发了我的好奇心,因此我希望使用IDA Pro和Windbg,以了解XFG哈希是如何生成的。

在撰写本文时,Windows 10 Insider Preview(开发者)版本中已经应用了XFG。如果想要编译支持XFG的程序,需要使用Visual Studio 2019预览版。

本文的分析基于Visual Studio 2019 16.8.0版本 Preview 2.1的二进制文件:

· c1.dll version 19.28.29213.0

· c2.dll version 19.28.29213.0

这篇文章重点介绍如何针对C语言源代码生成XFG哈希。尽管初步看起来C++代码的哈希算法看起来非常相似,但是我们尚未研究其具体细节。由于这篇文章篇幅较长,所以分为了几个部分。首先,从XFG哈希的快速入门开始。然后,分析如何对函数进行哈希处理,详细介绍如何对不同的C类型进行哈希处理。最后,我们检查应用于计算哈希的一些最终转换,并通过尝试计算哈希来得出结论。

Windows MVSC编译器实现Xtended Flow Guard(XFG)保护机制的原理分析
三、XFG哈希快速入门

我们先从一个非常简单的C语言程序开始,定义一个名为FPTR ([1])的函数指针类型,该函数声明一个带有两个float参数并返回另一个float的函数。函数main声明一个类型为FPTR、名为fptr的函数指针变量,该变量设置为函数foo ([2])的地址,该函数的原型与FPTR类型匹配。最后,在[3]的位置,调用fptr指向的函数,并将值1.00001和2.00002作为参数传递。

    #include  < stdio.h > 

[1] typedef float (* FPTR)(float, float);

    float foo(float val1, float val2){

        printf("I received float values %f and %fn", val1, val2);

        return (val2 - val1);

    }

    int main(int argc, char **argv){

[2]     FPTR fptr = foo;

        printf("Calling function pointer...n");

[3]     fptr(1.00001, 2.00002);

        return 0;

    }

我们使用以下命令行,用VS 2019 Preview的x64本地工具命令提示符中编译了上述源代码。这里使用到了/guard:xfg标志,以启用XFG。

>  cl /Zi /guard:xfg example1.c

反汇编后的主要函数如下所示:

main      ; int __cdecl main(int argc, const char **argv, const char **envp)

main

main      var_18          = qword ptr -18h

main      var_10          = qword ptr -10h

main      arg_0           = dword ptr  8

main      arg_8           = qword ptr  10h

main

main          mov     [rsp+arg_8], rdx

main+5        mov     [rsp+arg_0], ecx

main+9        sub     rsp, 38h

main+D        lea     rax, foo

main+14       mov     [rsp+38h+var_18], rax

main+19       lea     rcx, aCallingFunctio ; "Calling function pointer...n"

main+20       call    printf

main+25       mov     rax, [rsp+38h+var_18]

main+2A       mov     [rsp+38h+var_10], rax

main+2F       mov     r10, 99743F3270D52870h

main+39       movss   xmm1, cs:__real@40000054

main+41       movss   xmm0, cs:__real@3f800054

main+49       mov     rax, [rsp+38h+var_10]

main+4E       call    cs:__guard_xfg_dispatch_icall_fptr

main+54       xor     eax, eax

main+56       add     rsp, 38h

main+5A       retn

main+5A   main            endp

我们可以在main+0x2F处看到,对于在main + 0x4E处之后的函数指针调用,R10寄存器被设置为预期的基于类型的哈希(0x99743F3270D52870)。通过函数指针调用的函数是foo,我们可以验证其原型哈希(由函数开头的前8个字节表示)是否与预期的哈希匹配。这意味着,函数foo是main+0x4E上间接调用的有效目标。准确的说,原型哈希位于foo函数(0x99743F3270D52871)之前的8个字节,与我们在R10寄存器(0x99743F3270D52870)中看到的预期哈希相匹配,除了第0位之外。

.text:0000000140001008                 dq 99743F3270D52871h

foo

foo      ; =============== S U B R O U T I N E ================================

foo      ; float __fastcall foo(float val1, float val2)

foo      foo             proc near               ; DATA XREF: main+D

foo

foo      arg_0           = dword ptr  8

foo      arg_8           = dword ptr  10h

foo

foo          movss   [rsp+arg_8], xmm1

foo+6        movss   [rsp+arg_0], xmm0

foo+C        sub     rsp, 28h

foo+10       cvtss2sd xmm0, [rsp+28h+arg_8]

foo+16       cvtss2sd xmm1, [rsp+28h+arg_0]

foo+1C       movaps  xmm2, xmm0

foo+1F       movq    r8, xmm2

foo+24       movq    rdx, xmm1

foo+29       lea     rcx, _Format    ; "I received float values %f and %fn"

foo+30       call    printf

foo+35       movss   xmm0, [rsp+28h+arg_8]

foo+3B       subss   xmm0, [rsp+28h+arg_0]

foo+41       add     rsp, 28h

foo+45       retn

foo+45   foo             endp

但是无需担心这里的差异,因为在XFG调度函数(ntdll!LdrpDispatchUserCallTargetXFG)的开始处,R10的第0位被设置了,导致预期哈希值和函数哈希值在第0位上的差异没有意义。

LdrpDispatchUserCallTargetXFG      LdrpDispatchUserCallTargetXFG proc near

LdrpDispatchUserCallTargetXFG      ; __unwind { // LdrpICallHandler

LdrpDispatchUserCallTargetXFG          or      r10, 1

LdrpDispatchUserCallTargetXFG+4        test    al, 0Fh

LdrpDispatchUserCallTargetXFG+6        jnz     short loc_180094337

LdrpDispatchUserCallTargetXFG+8        test    ax, 0FFFh

LdrpDispatchUserCallTargetXFG+C        jz      short loc_180094337

LdrpDispatchUserCallTargetXFG+E        cmp     r10, [rax-8]

LdrpDispatchUserCallTargetXFG+12       jnz     short loc_180094337

LdrpDispatchUserCallTargetXFG+14       jmp     rax

Windows MVSC编译器实现Xtended Flow Guard(XFG)保护机制的原理分析
四、哈希函数类型

MSVC编译器由两个部分组成——前端和后端。前端是特定于某一语言的,负责读取源代码、词法、解析、进行语义分析和发出IL(中间语言)。后端是特定于某一目标体系结构的,它读取前端生成的IL,进行优化,并为特定体系结构生成代码。

函数原型哈希的生成是由语言前端进行的。这意味着在编译C语言代码时,C语言前端(c1.dll)负责生成原型哈希,而在编译C++代码时,C++前端(c1xx.dll)负责这个任务。

一旦相应的语言前端生成了原型哈希,就会由编译器后端(在这里是x64的后端c2.dll)执行一些最终转换。接下来,我们将详细介绍在编译C代码时创建原型哈希的每个步骤。

在使用/guard:xfg标志编译C语言源代码时,编译器前端会调用c1!XFGHelper__ComputeHash_1函数,以计算要处理的函数的原型哈希。

c1!XFGHelper__ComputeHash_1函数创建一个类型为XFGHelper::XFGHasher的对象,该对象负责收集正在处理的函数的类型信息,并根据收集的类型信息生成原型哈希。XFGHelper::XFGHasher使用std::vector的实例存储所有即将被计算哈希的类型信息,并且提供了在计算哈希的整个过程中调用的多个方法:

    XFGHelper::XFGHasher::add_function_type()

    XFGHelper::XFGHasher::add_type()

    XFGHelper::XFGHasher::get_hash()

    XFGHelper::XFGTypeHasher::compute_hash()

    XFGHelper::XFGTypeHasher::hash_indirection()

    XFGHelper::XFGTypeHasher::hash_tag()

    XFGHelper::XFGTypeHasher::hash_primitive()

在初始化XFGHelper::XFGHasher的实例后,XFGHelper__ComputeHash_1函数调用XFGHelper::XFGHasher::add_function_type(),将XFGHelper::XFGHasher实例和一个Type_t对象作为参数传递,该对象包含有关哈希函数的类型信息。

XFGHelper__ComputeHash_1      XFGHelper__ComputeHash_1 proc near

XFGHelper__ComputeHash_1

XFGHelper__ComputeHash_1      arg_0           = qword ptr  8

XFGHelper__ComputeHash_1      arg_8           = qword ptr  10h

XFGHelper__ComputeHash_1      arg_10          = qword ptr  18h

[...]

XFGHelper__ComputeHash_1+79        xorps   xmm0, xmm0

XFGHelper__ComputeHash_1+7C        movdqu  cs:xfg_hasher, xmm0 ; zero inits xfg_hasher

[...]

XFGHelper__ComputeHash_1+B1        mov     rdx, rbp        ; rdx = Type_t containing function information

XFGHelper__ComputeHash_1+B4        lea     rbp, xfg_hasher

XFGHelper__ComputeHash_1+BB        mov     rcx, rbp

XFGHelper__ComputeHash_1+BE        call    XFGHelper::XFGHasher::add_function_type(Type_t const *,XFGHelper::VirtualInfoFromDeclspec)

XFGHelper__ComputeHash_1+C3        mov     rdx, rsi        ; rdx = function- > return_type (struct Type_t *)

XFGHelper__ComputeHash_1+C6        mov     rcx, rbp        ; this

XFGHelper__ComputeHash_1+C9        call    XFGHelper::XFGHasher::add_type(Type_t const *) ; (step 5)

函数XFGHelper::XFGHasher::add_function_type将检索有关哈希函数的4条信息,从XFGHelper::XFGHasher::add_function_type返回后,通过调用XFGHelper::XFGHasher::add_type可以再添加一条信息,如上面的反汇编列出的XFGHelper__ComputeHash_1+C9所示。这些信息存储在XFGHelper::XFGHasher实例的std::vector中:

(1)4个字节,指示函数的参数数量;

(2)每个函数参数有8个字节,保存其参数的哈希值;

(3)1个字节,指示函数是否是可变参数(是否使用可变数量的参数);

(4)4个字节,指示函数使用的调用约定;

(5)8个字节,存保存函数返回类型的哈希值。

4.1 参数数量

XFGHelper::XFGHasher::add_function_type函数首先将一个DWORD添加到std::vector,以指示该函数的参数数量。这个数字可能会受到可变数量的参数、来自__declspec的虚拟信息的影响(我怀疑这可能是在C++的XFG实现中的一些重用代码)。简而言之,这里我们考虑的参数数量,就是在函数原型中声明的实际参数数量,如果函数用到了可变数量的参数,那么就是-1,如果函数具有来自__declspec的虚拟信息,则为-1。

XFGHelper::XFGHasher::add_function_type+18        mov     rsi, [rdx+10h]  ; rsi = function_info- > FunctionTypeInfo

XFGHelper::XFGHasher::add_function_type+1C        mov     rbx, rcx

XFGHelper::XFGHasher::add_function_type+1F        mov     rcx, rsi        ; this

XFGHelper::XFGHasher::add_function_type+22        movzx   r14d, r8b

XFGHelper::XFGHasher::add_function_type+26        mov     r15, rdx

XFGHelper::XFGHasher::add_function_type+29        call    FunctionTypeInfo_t::RealNumberOfParameters(void)

XFGHelper::XFGHasher::add_function_type+2E        mov     rcx, rsi        ; this

XFGHelper::XFGHasher::add_function_type+31        mov     r9d, eax        ; r9 = real_number_of_params

XFGHelper::XFGHasher::add_function_type+34        call    FunctionTypeInfo_t::IsVarArgsFunction(void)

XFGHelper::XFGHasher::add_function_type+39        mov     rdx, [rbx+8]

XFGHelper::XFGHasher::add_function_type+3D        lea     rbp, [r9-1]     ; rbp = real_number_of_params - 1

XFGHelper::XFGHasher::add_function_type+41        test    al, al          ; is variadic function?

XFGHelper::XFGHasher::add_function_type+43        mov     rcx, rbx

XFGHelper::XFGHasher::add_function_type+46        cmovz   rbp, r9         ; if not variadic, rbp = real_number_of_params

XFGHelper::XFGHasher::add_function_type+4A        test    r8b, r8b        ; does it have virtual info from __declspec?

XFGHelper::XFGHasher::add_function_type+4D        lea     r9, [rsp+48h+arg_14]

XFGHelper::XFGHasher::add_function_type+52        lea     r8, [rsp+48h+arg_10]

XFGHelper::XFGHasher::add_function_type+57        lea     eax, [rbp-1]    ; number of params = rbp - 1

XFGHelper::XFGHasher::add_function_type+5A        cmovz   eax, ebp        ; if no virtual info from __declspec, number of params = rbp

XFGHelper::XFGHasher::add_function_type+5D        mov     [rsp+48h+arg_10], eax ; value to add = number of params (dword)

XFGHelper::XFGHasher::add_function_type+5D                     ; [step 1]

XFGHelper::XFGHasher::add_function_type+61        call    std::vector < uchar > ::_Insert_range < uchar const * > (std::_Vector_const_iterator < std::_Vector_val < std::_Simple_types < uchar >  >  > ,uchar const *,uchar const *,std::forward_iterator_tag)

4.2 每个参数的类型哈希

接下来,XFGHelper::XFGHasher::add_function_type进入一个循环,在该循环中,它计算每个函数参数类型的哈希,然后将每个类型哈希(8个字节)添加到std::vector。

对于集中特殊情况(类型& 0x10f == 0x103、类型& 0x103 == 0x101)有特殊处理,但是对于大多数参数类型,将返回到loc_180105541。在这个位置,如果需要(调用Type_t::clearModifiersAndQualifiers),则会清除表示要处理的参数类型的Type_t对象的限定符(例如const (0x800)和volatile (0x40)),然后清除8个字节的哈希。通过调用XFGHelper::XFGHasher::add_type,将参数类型添加到std::vector,我们可以在XFGHelper::XFGHasher::add_function_type+CC看到。至于XFGHelper::XFGHasher::add_type是如何精确计算给定Type_t的哈希,我们在后续章节进行分析。

最后,如果还有更多参数需要哈希,就会跳转到循环的开始部分。

XFGHelper::XFGHasher::add_function_type+6E   loc_1801054F6:

XFGHelper::XFGHasher::add_function_type+6E        mov     rax, [rsi]      ; rax = &function_info- > params

XFGHelper::XFGHasher::add_function_type+71        mov     rcx, [rax+rdi*8] ; rcx = function_info- > params[i] (Type_t)

XFGHelper::XFGHasher::add_function_type+75        mov     edx, [rcx]      ; edx = params[i].type

XFGHelper::XFGHasher::add_function_type+77        mov     eax, edx

XFGHelper::XFGHasher::add_function_type+79        and     eax, 10Fh

XFGHelper::XFGHasher::add_function_type+7E        cmp     eax, 103h       ; params[i].type & 0x10f == 0x103 ?

XFGHelper::XFGHasher::add_function_type+83        jnz     short loc_18010552C

XFGHelper::XFGHasher::add_function_type+85        cmp     edx, 8103h      ; params[i].type == 0x8103 ?

XFGHelper::XFGHasher::add_function_type+8B        jz      short loc_18010554E

XFGHelper::XFGHasher::add_function_type+8D        mov     r8d, [rcx+4]

XFGHelper::XFGHasher::add_function_type+91        lea     edx, [rax-1]

XFGHelper::XFGHasher::add_function_type+94        mov     rcx, [rcx+8]

XFGHelper::XFGHasher::add_function_type+98        btr     r8d, 1Fh

XFGHelper::XFGHasher::add_function_type+9D        call    Type_t::createType(Type_t const *,uint,mod_t,bool)

XFGHelper::XFGHasher::add_function_type+A2        jmp     short loc_18010554B

XFGHelper::XFGHasher::add_function_type+A4   ; --------------------------------------------------------------

XFGHelper::XFGHasher::add_function_type+A4

XFGHelper::XFGHasher::add_function_type+A4   loc_18010552C:

XFGHelper::XFGHasher::add_function_type+A4        and     edx, 103h

XFGHelper::XFGHasher::add_function_type+AA        cmp     edx, 101h       ; params[i].type & 0x103 == 0x101 ?

XFGHelper::XFGHasher::add_function_type+B0        jnz     short loc_180105541

XFGHelper::XFGHasher::add_function_type+B2        call    Type_t::decayFunctionType(void)

XFGHelper::XFGHasher::add_function_type+B7        jmp     short loc_18010554B

XFGHelper::XFGHasher::add_function_type+B9   ; --------------------------------------------------------------

XFGHelper::XFGHasher::add_function_type+B9

XFGHelper::XFGHasher::add_function_type+B9   loc_180105541:

XFGHelper::XFGHasher::add_function_type+B9        mov     edx, 8C0h       ; discards qualifiers 0x800 (const) | 0x80 | 0x40 (volatile)

XFGHelper::XFGHasher::add_function_type+BE        call    Type_t::clearModifiersAndQualifiers(mod_t)

XFGHelper::XFGHasher::add_function_type+C3

XFGHelper::XFGHasher::add_function_type+C3   loc_18010554B:

XFGHelper::XFGHasher::add_function_type+C3                     ; XFGHelper::XFGHasher::add_function_type+B7↑j

XFGHelper::XFGHasher::add_function_type+C3        mov     rcx, rax

XFGHelper::XFGHasher::add_function_type+C6

XFGHelper::XFGHasher::add_function_type+C6   loc_18010554E:

XFGHelper::XFGHasher::add_function_type+C6        mov     rdx, rcx        ; struct Type_t *

XFGHelper::XFGHasher::add_function_type+C9        mov     rcx, rbx        ; this

XFGHelper::XFGHasher::add_function_type+CC        call    XFGHelper::XFGHasher::add_type(Type_t const *) ; adds hash of params[i] type

XFGHelper::XFGHasher::add_function_type+CC                     ; [step 2]

XFGHelper::XFGHasher::add_function_type+D1        inc     rdi

XFGHelper::XFGHasher::add_function_type+D4        cmp     rdi, rbp        ; counter  <  number_of_params ?

XFGHelper::XFGHasher::add_function_type+D7        jb      short loc_1801054F6 ; if so, loop

4.3 可变参数函数

下一步是向std::vector添加一个字节,指示该函数是否可接受可变数量的参数。在大多数情况下,当函数不包含来自__declspec的虚拟信息时,会采用以下代码路径:

XFGHelper::XFGHasher::add_function_type+D9        mov     rcx, rsi        ; this = functioninfo

XFGHelper::XFGHasher::add_function_type+DC        call    FunctionTypeInfo_t::IsVarArgsFunction(void)

XFGHelper::XFGHasher::add_function_type+E1        mov     r8b, al         ; r8b = is_var_args_function

XFGHelper::XFGHasher::add_function_type+E4        test    r14b, r14b      ; contains virtual info from __declspec?

XFGHelper::XFGHasher::add_function_type+E7        jz      short loc_1801055EB

[...]

XFGHelper::XFGHasher::add_function_type+163  loc_1801055EB:

XFGHelper::XFGHasher::add_function_type+163        mov     rdx, [rbx+8]

XFGHelper::XFGHasher::add_function_type+167        lea     r9, [rsp+48h+arg_10+1]

XFGHelper::XFGHasher::add_function_type+16C        mov     byte ptr [rsp+48h+arg_10], r8b ; value to add = is_var_args_function (byte)

XFGHelper::XFGHasher::add_function_type+16C        ; [step 3]

XFGHelper::XFGHasher::add_function_type+171        mov     rcx, rbx

XFGHelper::XFGHasher::add_function_type+174        lea     r8, [rsp+48h+arg_10]

XFGHelper::XFGHasher::add_function_type+179        call    std::vector < uchar > ::_Insert_range < uchar const * > (std::_Vector_const_iterator < std::_Vector_val < std::_Simple_types < uchar >  >  > ,uchar const *,uchar const *,std::forward_iterator_tag)

4.4 调用约定

最后,XFGHelper::XFGHasher::add_function_type将一个4字节的值添加到std::vector,以指示该函数使用的调用约定。在Intel x64体系结构中没有太多的调用约定,这一点与x86不太一样。默认的x64调用约定在寄存器RCX、RDX、R8和R9中传递整数型参数,而浮点型参数通过XMM0-XMM3传递。该默认调用约定在内部用0x201值来表示,但是由于在将其保存到std::vector之前,使用& 0x0F进行了屏蔽(请参考下面的反汇编),因此我们很可能会看到一个值为0x00000001的DWORD写入std::vector。

下面展示了将调用约定数据添加到std::vector的代码。

XFGHelper::XFGHasher::add_function_type+17E        mov     eax, [r15+4]    ; eax = function_info- > calling_convention

XFGHelper::XFGHasher::add_function_type+182        lea     r9, [rsp+48h+arg_14]

XFGHelper::XFGHasher::add_function_type+187        mov     rdx, [rbx+8]

XFGHelper::XFGHasher::add_function_type+18B        lea     r8, [rsp+48h+arg_10]

XFGHelper::XFGHasher::add_function_type+190        and     eax, 0Fh        ; eax = calling_convention & 0xF

XFGHelper::XFGHasher::add_function_type+193        mov     rcx, rbx

XFGHelper::XFGHasher::add_function_type+196        mov     [rsp+48h+arg_10], eax ; value to add = calling_convention & 0xF (size = dword)

XFGHelper::XFGHasher::add_function_type+196                      ; [step 4]

XFGHelper::XFGHasher::add_function_type+19A        call    std::vector < uchar > ::_Insert_range < uchar const * > (std::_Vector_const_iterator < std::_Vector_val < std::_Simple_types < uchar >  >  > ,uchar const *,uchar const *,std::forward_iterator_tag)

4.5 返回类型的哈希

数据的第五部分,也是最后一个组成部分,用于获取函数原型哈希,它无法在XFGHelper::XFGHasher::add_function_type中检索到,而是在返回后立即添加的。在下面的代码中我们看到,它调用XFGHelper::XFGHasher::add_type,为表示返回类型的Type_t计算8字节的哈希,并将计算出来的8字节哈希值添加到std::vector。

XFGHelper__ComputeHash_1+BE        call    XFGHelper::XFGHasher::add_function_type(Type_t const *,XFGHelper::VirtualInfoFromDeclspec)

XFGHelper__ComputeHash_1+C3        mov     rdx, rsi        ; rdx = function- > return_type (struct Type_t *)

XFGHelper__ComputeHash_1+C6        mov     rcx, rbp        ; this

XFGHelper__ComputeHash_1+C9        call    XFGHelper::XFGHasher::add_type(Type_t const *) ; (step 5)

4.6 最后一步:计算收集的原型数据的哈希值

如果该函数包含来自__declspec的虚拟信息,则会从该信息中生成一个附加的8字节类型的哈希,并将其添加到std::vector。但是,在测试期间,我没能实现这种特殊情况。如前所述,虚拟信息可能不适用于C语言代码。
无论是否存在来自__declspec的虚拟信息,XFGHelper__ComputeHash_1函数都可以通过调用XFGHelper::XFGHasher::get_hash函数来完成:

XFGHelper__ComputeHash_1+CE        test    rbx, rbx        ; contains virtual info from __declspec?

XFGHelper__ComputeHash_1+D1        jz      short loc_1801052EF

[...]

XFGHelper__ComputeHash_1+103  loc_1801052EF:

XFGHelper__ComputeHash_1+103                  mov     rcx, rbp        ; this

XFGHelper__ComputeHash_1+106                  mov     rbx, [rsp+38h+arg_0]

XFGHelper__ComputeHash_1+10B                  mov     rbp, [rsp+38h+arg_8]

XFGHelper__ComputeHash_1+110                  mov     rsi, [rsp+38h+arg_10]

XFGHelper__ComputeHash_1+115                  add     rsp, 30h

XFGHelper__ComputeHash_1+119                  pop     rdi

XFGHelper__ComputeHash_1+11A                  jmp     XFGHelper::XFGHasher::get_hash(void)

XFGHelper__ComputeHash_1+11A  XFGHelper__ComputeHash_1 endp

XFGHelper::XFGHasher::get_hash对在std::vector中收集的类型数据进行哈希处理。我们在XFGHelper::XFGHasher::get_hash+5F中看到,选择的哈希算法是SHA256,仅返回生成的SHA256摘要的前8个字节:

XFGHelper::XFGHasher::get_hash(void)      public: unsigned __int64 XFGHelper::XFGHasher::get_hash(void)const proc near

[...]

XFGHelper::XFGHasher::get_hash(void)+18        mov     dl, 3           ; algorithm_ids[3] == CALG_SHA_256

XFGHelper::XFGHasher::get_hash(void)+1A        lea     rcx, [rsp+58h+hHash] ; phHash

XFGHelper::XFGHasher::get_hash(void)+1F        call    HashAPIWrapper::HashAPIWrapper(uchar)

XFGHelper::XFGHasher::get_hash(void)+24        nop

XFGHelper::XFGHasher::get_hash(void)+25        mov     r8, [rbx+8]

XFGHelper::XFGHasher::get_hash(void)+29        sub     r8, [rbx]       ; dwDataLen

XFGHelper::XFGHasher::get_hash(void)+2C        xor     r9d, r9d        ; dwFlags

XFGHelper::XFGHasher::get_hash(void)+2F        mov     rdx, [rbx]      ; pbData

XFGHelper::XFGHasher::get_hash(void)+32        mov     rcx, [rsp+58h+hHash] ; hHash

XFGHelper::XFGHasher::get_hash(void)+37        call    cs:__imp_CryptHashData

XFGHelper::XFGHasher::get_hash(void)+3D        test    eax, eax

XFGHelper::XFGHasher::get_hash(void)+3F        jnz     short loc_180105822

[...]

XFGHelper::XFGHasher::get_hash(void)+4A   loc_180105822:

XFGHelper::XFGHasher::get_hash(void)+4A        mov     r8d, 20h ; ' '  ; unsigned int

XFGHelper::XFGHasher::get_hash(void)+50        lea     rdx, [rsp+58h+sha256_digest] ; unsigned __int8 *

XFGHelper::XFGHasher::get_hash(void)+55        lea     rcx, [rsp+58h+hHash] ; this

XFGHelper::XFGHasher::get_hash(void)+5A        call    HashAPIWrapper::GetHash(uchar *,ulong)

XFGHelper::XFGHasher::get_hash(void)+5F        mov     rbx, qword ptr [rsp+58h+sha256_digest] ; *** only returns first 8 bytes of SHA256 hash

XFGHelper::XFGHasher::get_hash(void)+64        mov     rcx, [rsp+58h+hHash] ; hHash

XFGHelper::XFGHasher::get_hash(void)+69        call    cs:__imp_CryptDestroyHash

XFGHelper::XFGHasher::get_hash(void)+6F        test    eax, eax

XFGHelper::XFGHasher::get_hash(void)+71        jnz     short loc_180105854

[...]

XFGHelper::XFGHasher::get_hash(void)+7C   loc_180105854:

XFGHelper::XFGHasher::get_hash(void)+7C        mov     rax, rbx

XFGHelper::XFGHasher::get_hash(void)+7F        mov     rcx, [rsp+58h+var_10]

XFGHelper::XFGHasher::get_hash(void)+84        xor     rcx, rsp        ; StackCookie

XFGHelper::XFGHasher::get_hash(void)+87        call    __security_check_cookie

XFGHelper::XFGHasher::get_hash(void)+8C        add     rsp, 50h

XFGHelper::XFGHasher::get_hash(void)+90        pop     rbx

XFGHelper::XFGHasher::get_hash(void)+91        retn

Windows MVSC编译器实现Xtended Flow Guard(XFG)保护机制的原理分析
五、哈希类型

到目前为止,我们知道函数原型哈希是基于五个信息构建的。其中三个是普通值(参数数量、一个布尔值用于标识函数是否参数可变、一个数字值表示正在使用的调用约定),而另外两个是类型哈希(每个函数参数的类型哈希,以及返回类型的哈希)。在这一章中,我们将了解如何对类型(编译器内部使用Type_t对象表示)进行哈希处理。

类型在XFGHelper::XFGHasher::add_type函数中计算哈希。它调用XFGHelper__GetHashForType,随后返回该类型的8字节哈希,然后通过调用std::vector::_Insert_range()将8字节哈希存储在std::vector中。

.text:00000001801056A0 public: void XFGHelper::XFGHasher::add_type(class Type_t const *) proc near

.text:00000001801056A0 arg_0           = qword ptr  8

.text:00000001801056A0 arg_8           = byte ptr  10h

.text:00000001801056A0

.text:00000001801056A0        push    rbx

.text:00000001801056A2        sub     rsp, 30h

.text:00000001801056A6        mov     rbx, rcx

.text:00000001801056A9        mov     rcx, rdx        ; rcx = Type_t

.text:00000001801056AC        call    XFGHelper__GetHashForType

.text:00000001801056B1        mov     rdx, [rbx+8]

.text:00000001801056B5        lea     r9, [rsp+38h+arg_8]

.text:00000001801056BA        lea     r8, [rsp+38h+arg_0]

.text:00000001801056BF        mov     [rsp+38h+arg_0], rax ; value to add = hash (qword)

.text:00000001801056C4        mov     rcx, rbx

.text:00000001801056C7        call    std::vector < uchar > ::_Insert_range < uchar const * > (std::_Vector_const_iterator < std::_Vector_val < std::_Simple_types < uchar >  >  > ,uchar const *,uchar const *,std::forward_iterator_tag)

.text:00000001801056CC        add     rsp, 30h

.text:00000001801056D0        pop     rbx

.text:00000001801056D1        retn

我们来看看XFGHelper__GetHashForType是如何为指定的Type_t生成8字节哈希的。首先,它通过对std:Tree::emplace()的调用,来检查指定类型的哈希是否存在于它保存的缓存中,我们可以在XFGHelper__GetHashForType+AF发现这一点。如果满足条件,则只返回缓存的类型哈希。这样,就可以避免一遍又一遍地计算已知类型的哈希值。

如果在缓存中没有找到类型哈希,则通过调用XFGHelper::XFGTypeHasher::compute_hash从头开始计算哈希,将会使用要计算哈希的类型数据构建std::vector,最后调用XFGHelper::XFGHasher::get_hash,它会生成std::vector中包含的数据的SHA256摘要,并返回这个摘要的前8个字节。

XFGHelper__GetHashForType      XFGHelper__GetHashForType proc near

[...]

XFGHelper__GetHashForType+A3        lea     r9, [rbp+arg_8]

XFGHelper__GetHashForType+A7        lea     r8, [rbp+Type_t]

XFGHelper__GetHashForType+AB        lea     rdx, [rbp+xfg_type_hasher]

XFGHelper__GetHashForType+AF        call    std::_Tree < std::_Tmap_traits < Type_t const *,unsigned __int64,std::less < Type_t const * > ,std::allocator < std::pair < Type_t const * const,unsigned __int64 >  > ,0 >  > ::_Emplace < Type_t const * &,int > (Type_t const * &,int &&)

XFGHelper__GetHashForType+B4        mov     rbx, qword ptr [rbp+xfg_type_hasher]

XFGHelper__GetHashForType+B8        cmp     byte ptr [rbp+xfg_type_hasher+8], 0 ; hash for type was found in cache?

XFGHelper__GetHashForType+BC        jz      short loc_18010544D ; if so, just return the cached hash

XFGHelper__GetHashForType+BE        xor     edi, edi        ; otherwise, compute the hash of the type

XFGHelper__GetHashForType+C0        xorps   xmm0, xmm0

XFGHelper__GetHashForType+C3        movdqu  [rbp+xfg_type_hasher], xmm0

XFGHelper__GetHashForType+C8        and     [rbp+var_10], rdi

XFGHelper__GetHashForType+CC        mov     [rbp+var_8], 1

XFGHelper__GetHashForType+D0        mov     rdx, [rbp+Type_t] ; struct Type_t *

XFGHelper__GetHashForType+D4        lea     rcx, [rbp+xfg_type_hasher] ; this

XFGHelper__GetHashForType+D8        call    XFGHelper::XFGTypeHasher::compute_hash(Type_t const *)

XFGHelper__GetHashForType+DD        nop

XFGHelper__GetHashForType+DE        cmp     [rbp+var_8], dil

XFGHelper__GetHashForType+E2        jz      short loc_180105434

XFGHelper__GetHashForType+E4        lea     rcx, [rbp+xfg_type_hasher] ; this

XFGHelper__GetHashForType+E8        call    XFGHelper::XFGHasher::get_hash(void)

[...]

这些是XFGHelper::XFGTypeHasher::compute_hash收集的特定类型信息:

(1)从类型限定符得到的1个字节(从Type_t对象的偏移量4处获取);

(2)指示类型的1个字节(指针、union/struct/enum、原始类型);

(3)一些特定于类型的数据,具体取决于类型属于(2)中的哪一个类型。

接下来,我们将详细分析这三部分信息。

5.1 类型限定符

第一部分是限定符,作为DWORD存储在Type_t对象的偏移量4的位置。关于const (0x800)和volatile (0x40)限定符的信息将被组合写入到std::vector单字节中。这个新字节的第一位负责指示是否存在const限定符,第二位指示是否存在volatile类型的限定符。

XFGHelper::XFGTypeHasher::compute_hash+1B        call    Type_t::getFirstNonArrayType(void)

XFGHelper::XFGTypeHasher::compute_hash+20        mov     rcx, rdi        ; this

XFGHelper::XFGTypeHasher::compute_hash+23        mov     r8d, [rax+4]    ; r8d = Type_t- > qualifiers

XFGHelper::XFGTypeHasher::compute_hash+27        shr     r8d, 0Bh

XFGHelper::XFGTypeHasher::compute_hash+2B        and     r8b, 1

XFGHelper::XFGTypeHasher::compute_hash+2F        movzx   r9d, r8b        ; r9d = (Type_t- > qualifiers  >  >  0xB) & 1 (has_const_qualifier)

XFGHelper::XFGTypeHasher::compute_hash+33        call    Type_t::getFirstNonArrayType(void)

XFGHelper::XFGTypeHasher::compute_hash+38        lea     r8, [rbp+arg_0]

XFGHelper::XFGTypeHasher::compute_hash+3C        mov     edx, [rax+4]    ; edx = Type_t- > qualifiers

XFGHelper::XFGTypeHasher::compute_hash+3F        mov     al, r9b         ; al = has_const_qualifier

XFGHelper::XFGTypeHasher::compute_hash+42        or      al, 2           ; al = has_const_qualifier | 2

XFGHelper::XFGTypeHasher::compute_hash+44        and     dl, 40h         ; dl = Type_t- > qualifiers & 0x40 (has_volatile_qualifier)

XFGHelper::XFGTypeHasher::compute_hash+47        movzx   ecx, al         ; qualifiers_info = has_const_qualifier | 2

XFGHelper::XFGTypeHasher::compute_hash+4A        mov     rdx, [rbx+8]

XFGHelper::XFGTypeHasher::compute_hash+4E        cmovz   ecx, r9d        ; if it doesn't have volatile qualifier, then

XFGHelper::XFGTypeHasher::compute_hash+4E                     ; qualifiers_info = has_const_qualifier

XFGHelper::XFGTypeHasher::compute_hash+52        lea     r9, [rbp+arg_1]

XFGHelper::XFGTypeHasher::compute_hash+56        mov     [rbp+arg_0], cl ; value to insert (size = byte)

XFGHelper::XFGTypeHasher::compute_hash+59        mov     rcx, rbx

XFGHelper::XFGTypeHasher::compute_hash+5C        call    std::vector < uchar > ::_Insert_range < uchar const * > (std::_Vector_const_iterator < std::_Vector_val < std::_Simple_types < uchar >  >  > ,uchar const *,uchar const *,std::forward_iterator_tag)

5.2 类型组

如果存储在Type_t中的类型值设置为0x100,则它是一个指针。通过将值为3的字节写入到std::vector来发出信号。

XFGHelper::XFGTypeHasher::compute_hash+61        test    dword ptr [rdi], 100h ; *Type_t & 0x100 == 0 ?

XFGHelper::XFGTypeHasher::compute_hash+67        jz      short loc_180105762

XFGHelper::XFGTypeHasher::compute_hash+69        mov     rdx, [rbx+8]    ; if not, it's a pointer

XFGHelper::XFGTypeHasher::compute_hash+6D        lea     r9, [rbp+arg_1]

XFGHelper::XFGTypeHasher::compute_hash+71        lea     r8, [rbp+arg_0]

XFGHelper::XFGTypeHasher::compute_hash+75        mov     [rbp+arg_0], 3  ; value to insert: POINTER_TYPE (3)

XFGHelper::XFGTypeHasher::compute_hash+79        mov     rcx, rbx

XFGHelper::XFGTypeHasher::compute_hash+7C        call    std::vector < uchar > ::_Insert_range < uchar const * > (std::_Vector_const_iterator < std::_Vector_val < std::_Simple_types < uchar >  >  > ,uchar const *,uchar const *,std::forward_iterator_tag)

如果类型不是指针,则会检查存储在Type_t & 0x600的值是否为0,以确认它是union、struct还是enum。请注意,0x600是0x200 | 0x400,其中0x200表示enum类型,0x400表示union和struct。如果满足这个条件,会将值为2的字节写入std::vector。

XFGHelper::XFGTypeHasher::compute_hash+8E   loc_180105762:

XFGHelper::XFGTypeHasher::compute_hash+8E        test    dword ptr [rdi], 600h ; *Type_t & (0x400 | 0x200) == 0 ?

XFGHelper::XFGTypeHasher::compute_hash+94        jz      short loc_180105790

XFGHelper::XFGTypeHasher::compute_hash+96        mov     rdx, [rbx+8]    ; if not, it's a union/struct/enum

XFGHelper::XFGTypeHasher::compute_hash+9A        lea     r9, [rbp+arg_1]

XFGHelper::XFGTypeHasher::compute_hash+9E        lea     r8, [rbp+arg_0]

XFGHelper::XFGTypeHasher::compute_hash+A2        mov     [rbp+arg_0], 2  ; value to insert: UNION_STRUCT_OR_ENUM_TYPE (2)

XFGHelper::XFGTypeHasher::compute_hash+A6        mov     rcx, rbx

XFGHelper::XFGTypeHasher::compute_hash+A9        call    std::vector < uchar > ::_Insert_range < uchar const * > (std::_Vector_const_iterator < std::_Vector_val < std::_Simple_types < uchar >  >  > ,uchar const *,uchar const *,std::forward_iterator_tag)

最后,如果类型既不是指针,也不是union/struct/enum,则采用默认情况。如果类型是泛型的,则不会将任何内容写入到std::vector(但这是一种边界情况,仅影响设置了值0x1000的类型,以及标识为值0x8103的类型)。否则,如果是绝大多数的基本类型,会将值为1的字节添加到std::vector。

XFGHelper::XFGTypeHasher::compute_hash+BC   loc_180105790:

XFGHelper::XFGTypeHasher::compute_hash+BC        mov     rcx, rdi        ; this

XFGHelper::XFGTypeHasher::compute_hash+BF        call    Type_t::isGeneric(void)

XFGHelper::XFGTypeHasher::compute_hash+C4        test    al, al

XFGHelper::XFGTypeHasher::compute_hash+C6        jz      short loc_1801057A2

XFGHelper::XFGTypeHasher::compute_hash+C8        mov     byte ptr [rbx+18h], 0

XFGHelper::XFGTypeHasher::compute_hash+CC        jmp     short epilog

XFGHelper::XFGTypeHasher::compute_hash+CE   loc_1801057A2:

XFGHelper::XFGTypeHasher::compute_hash+CE        mov     rdx, [rbx+8]

XFGHelper::XFGTypeHasher::compute_hash+D2        lea     r9, [rbp+arg_1]

XFGHelper::XFGTypeHasher::compute_hash+D6        lea     r8, [rbp+arg_0]

XFGHelper::XFGTypeHasher::compute_hash+DA        mov     [rbp+arg_0], 1  ; value to insert: PRIMITIVE_TYPE (1)

XFGHelper::XFGTypeHasher::compute_hash+DE        mov     rcx, rbx

XFGHelper::XFGTypeHasher::compute_hash+E1        call    std::vector < uchar > ::_Insert_range < uchar const * > (std::_Vector_const_iterator < std::_Vector_val < std::_Simple_types < uchar >  >  > ,uchar const *,uchar const *,std::forward_iterator_tag)

5.3 特定类型的数据

5.3.1 指针类型的哈希

对于指针类型,在将值为3的字节写入std::vector后,将调用XFGHelper::XFGTypeHasher::hash_indirection函数。这里的指针定义要更为宽泛,因为其中包括所有值为0x100的Type_t对象。除了常规的C指针外,还包括一种内部函数对象(由函数指针引用)和数组。

XFGHelper::XFGTypeHasher::compute_hash+81        mov     rdx, rdi        ; struct Type_t *

XFGHelper::XFGTypeHasher::compute_hash+84        mov     rcx, rbx        ; this

XFGHelper::XFGTypeHasher::compute_hash+87        call    XFGHelper::XFGTypeHasher::hash_indirection

XFGHelper::XFGTypeHasher::compute_hash+8C        jmp     short epilog

顾名思义,函数XFGHelper::XFGTypeHasher::hash_indirection将由指向std::vector的指针引用的类型的哈希值添加。其行为取决于所处理的指针的类型:

(1)如果是函数指针(Type_t值为0x106),或Type_t值为0x102的通用指针,则通过调用XFGHelper::XFGHasher::add_type添加指针引用的Type_t的哈希,再加上值为2的字节。对于函数指针,指针引用的Type_t是一种内部函数对象,Type_t值为0x101,这意味着它也在XFGHelper::XFGTypeHasher::hash_indirection中进行处理。

XFGHelper::XFGTypeHasher::hash_indirection+15        mov     ecx, [rdx]      ; ecx = *Type_t

XFGHelper::XFGTypeHasher::hash_indirection+17        mov     eax, ecx

XFGHelper::XFGTypeHasher::hash_indirection+19        and     eax, 10Fh

[...]

XFGHelper::XFGTypeHasher::hash_indirection+25        sub     eax, 1          ; case 0x102 (general pointer):

XFGHelper::XFGTypeHasher::hash_indirection+28        jz      short loc_1801058E3

[...]

XFGHelper::XFGTypeHasher::hash_indirection+2F        cmp     eax, 3          ; case 0x106 (function pointer):

XFGHelper::XFGTypeHasher::hash_indirection+32        jz      short loc_1801058E3

[...]

XFGHelper::XFGTypeHasher::hash_indirection+6B   loc_1801058E3:

XFGHelper::XFGTypeHasher::hash_indirection+6B        mov     dil, 2          ; will be written to std::vector

XFGHelper::XFGTypeHasher::hash_indirection+6E        jmp     short loc_1801058F6

[...]

XFGHelper::XFGTypeHasher::hash_indirection+7E   loc_1801058F6:

XFGHelper::XFGTypeHasher::hash_indirection+7E        mov     rdx, [rsi+8]    ; rdx = ptr to the Type_t referenced by the pointer

XFGHelper::XFGTypeHasher::hash_indirection+7E                     ; (return type in the case of functions)

XFGHelper::XFGTypeHasher::hash_indirection+82        mov     rcx, rbx        ; this

XFGHelper::XFGTypeHasher::hash_indirection+85        call    XFGHelper::XFGHasher::add_type

XFGHelper::XFGTypeHasher::hash_indirection+8A        mov     rdx, [rbx+8]

XFGHelper::XFGTypeHasher::hash_indirection+8E        lea     r9, [rsp+38h+arg_8+1]

XFGHelper::XFGTypeHasher::hash_indirection+93        lea     r8, [rsp+38h+arg_8]

XFGHelper::XFGTypeHasher::hash_indirection+98        mov     byte ptr [rsp+38h+arg_8], dil ; value to insert (size = byte)

XFGHelper::XFGTypeHasher::hash_indirection+9D        mov     rcx, rbx

XFGHelper::XFGTypeHasher::hash_indirection+A0        call    std::vector < uchar > ::_Insert_range < uchar const * > (std::_Vector_const_iterator < std::_Vector_val < std::_Simple_types < uchar >  >  > ,uchar const *,uchar const *,std::forward_iterator_tag)

如果它是一个函数对象(Type_t值为0x101,通常由Type_t值为0x106的函数指针引用),它通过调用XFGHelper::XFGHasher::add_function_type函数以及函数返回类型的哈希值来添加函数原型的哈希值,再加上一个值为1的字节。

XFGHelper::XFGTypeHasher::hash_indirection+15        mov     ecx, [rdx]      ; ecx = *Type_t

XFGHelper::XFGTypeHasher::hash_indirection+17        mov     eax, ecx

XFGHelper::XFGTypeHasher::hash_indirection+19        and     eax, 10Fh

XFGHelper::XFGTypeHasher::hash_indirection+1E        sub     eax, 101h       ; case 0x101 (function):

XFGHelper::XFGTypeHasher::hash_indirection+23        jz      short loc_1801058E8

[...]

XFGHelper::XFGTypeHasher::hash_indirection+70        xor     r8d, r8d

XFGHelper::XFGTypeHasher::hash_indirection+73        mov     rcx, rbx

XFGHelper::XFGTypeHasher::hash_indirection+76        mov     dil, 1          ; this is written to std::vector at the end of this function

XFGHelper::XFGTypeHasher::hash_indirection+79        call    XFGHelper::XFGHasher::add_function_type(Type_t const *,XFGHelper::VirtualInfoFromDeclspec)

XFGHelper::XFGTypeHasher::hash_indirection+7E

XFGHelper::XFGTypeHasher::hash_indirection+7E   loc_1801058F6:

XFGHelper::XFGTypeHasher::hash_indirection+7E                     ; XFGHelper::XFGTypeHasher::hash_indirection+6E↑j

XFGHelper::XFGTypeHasher::hash_indirection+7E        mov     rdx, [rsi+8]    ; rdx = ptr to the Type_t referenced by the pointer

XFGHelper::XFGTypeHasher::hash_indirection+7E                     ; (return type in the case of functions)

XFGHelper::XFGTypeHasher::hash_indirection+82        mov     rcx, rbx        ; this

XFGHelper::XFGTypeHasher::hash_indirection+85        call    XFGHelper::XFGHasher::add_type

XFGHelper::XFGTypeHasher::hash_indirection+8A        mov     rdx, [rbx+8]

XFGHelper::XFGTypeHasher::hash_indirection+8E        lea     r9, [rsp+38h+arg_8+1]

XFGHelper::XFGTypeHasher::hash_indirection+93        lea     r8, [rsp+38h+arg_8]

XFGHelper::XFGTypeHasher::hash_indirection+98        mov     byte ptr [rsp+38h+arg_8], dil ; value to insert (size = byte)

XFGHelper::XFGTypeHasher::hash_indirection+9D        mov     rcx, rbx

XFGHelper::XFGTypeHasher::hash_indirection+A0        call    std::vector < uchar > ::_Insert_range < uchar const * > (std::_Vector_const_iterator < std::_Vector_val < std::_Simple_types < uchar >  >  > ,uchar const *,uchar const *,std::forward_iterator_tag)

最后,如果它是一个数组(Type_t值为0x103),则会写入一个QWORD,其中包含数组中元素的数量、数组元素类型的哈希、值为6的单字节。

XFGHelper::XFGTypeHasher::hash_indirection+15        mov     ecx, [rdx]      ; ecx = *Type_t

XFGHelper::XFGTypeHasher::hash_indirection+17        mov     eax, ecx

XFGHelper::XFGTypeHasher::hash_indirection+19        and     eax, 10Fh

[...]

XFGHelper::XFGTypeHasher::hash_indirection+2A        sub     eax, 1          ; case 0x103 (array passed by pointer):

XFGHelper::XFGTypeHasher::hash_indirection+2D        jz      short loc_1801058B2

[...]

XFGHelper::XFGTypeHasher::hash_indirection+3A   loc_1801058B2:

XFGHelper::XFGTypeHasher::hash_indirection+3A        lea     eax, [rcx-4103h]

XFGHelper::XFGTypeHasher::hash_indirection+40        mov     dil, 6          ; will be written to std::vector

XFGHelper::XFGTypeHasher::hash_indirection+43        test    eax, 0FFFFBFFFh

XFGHelper::XFGTypeHasher::hash_indirection+48        jz      short loc_1801058AC

XFGHelper::XFGTypeHasher::hash_indirection+4A        mov     rax, [rdx+10h]  ; rax = number of elems in array

XFGHelper::XFGTypeHasher::hash_indirection+4E        lea     r9, [rsp+38h+arg_10]

XFGHelper::XFGTypeHasher::hash_indirection+53        mov     rdx, [rbx+8]

XFGHelper::XFGTypeHasher::hash_indirection+57        lea     r8, [rsp+38h+arg_8]

XFGHelper::XFGTypeHasher::hash_indirection+5C        mov     rcx, rbx

XFGHelper::XFGTypeHasher::hash_indirection+5F        mov     [rsp+38h+arg_8], rax ; value to insert: number of elems in array (size = qword)

XFGHelper::XFGTypeHasher::hash_indirection+64        call    std::vector < uchar > ::_Insert_range < uchar const * > (std::_Vector_const_iterator < std::_Vector_val < std::_Simple_types < uchar >  >  > ,uchar const *,uchar const *,std::forward_iterator_tag)

XFGHelper::XFGTypeHasher::hash_indirection+69        jmp     short loc_1801058F6

[...]

XFGHelper::XFGTypeHasher::hash_indirection+7E   loc_1801058F6

XFGHelper::XFGTypeHasher::hash_indirection+7E        mov     rdx, [rsi+8]    ; rdx = ptr to the Type_t referenced by the pointer

XFGHelper::XFGTypeHasher::hash_indirection+7E                     ; (return type in the case of functions)

XFGHelper::XFGTypeHasher::hash_indirection+82        mov     rcx, rbx        ; this

XFGHelper::XFGTypeHasher::hash_indirection+85        call    XFGHelper::XFGHasher::add_type

XFGHelper::XFGTypeHasher::hash_indirection+8A        mov     rdx, [rbx+8]

XFGHelper::XFGTypeHasher::hash_indirection+8E        lea     r9, [rsp+38h+arg_8+1]

XFGHelper::XFGTypeHasher::hash_indirection+93        lea     r8, [rsp+38h+arg_8]

XFGHelper::XFGTypeHasher::hash_indirection+98        mov     byte ptr [rsp+38h+arg_8], dil ; value to insert (size = byte)

XFGHelper::XFGTypeHasher::hash_indirection+9D        mov     rcx, rbx

XFGHelper::XFGTypeHasher::hash_indirection+A0        call    std::vector < uchar > ::_Insert_range < uchar const * > (std::_Vector_const_iterator < std::_Vector_val < std::_Simple_types < uchar >  >  > ,uchar const *,uchar const *,std::forward_iterator_tag)

5.3.2 union/struct/enum类型的哈希

在处理union/struct/enum时,将值为2的字节写入std::vector后,函数XFGHelper::XFGTypeHasher::compute_hash调用XFGHelper::XFGTypeHasher::hash_tag,在RDX中将指向Symbol_t的指针作为参数传递,其中包括union/struct/enum类型的可读名称的对象。

XFGHelper::XFGTypeHasher::compute_hash+AE        mov     rdx, [rdi+10h]  ; struct Symbol_t *

XFGHelper::XFGTypeHasher::compute_hash+B2        mov     rcx, rbx        ; this

XFGHelper::XFGTypeHasher::compute_hash+B5        call    XFGHelper::XFGTypeHasher::hash_tag(Symbol_t *)

XFGHelper::XFGTypeHasher::hash_tag调用XFGHelper::XFGHasher::add_string,将union/struct/enum的名称添加到std::vector(命名情况下)。如果union/struct/enum是匿名的,则会将字符串“ < unnamed > ”添加到std::vector。

XFGHelper::XFGHasher::add_string      public: void XFGHelper::XFGHasher::add_string(class Symbol_t *) proc near

XFGHelper::XFGHasher::add_string           sub     rsp, 38h

XFGHelper::XFGHasher::add_string+4         cmp     byte ptr [rdx+11h], 4

XFGHelper::XFGHasher::add_string+8         jnz     short loc_18010568B

XFGHelper::XFGHasher::add_string+A         mov     r8, [rdx]

XFGHelper::XFGHasher::add_string+D         mov     eax, [r8+10h]

XFGHelper::XFGHasher::add_string+11        shr     eax, 16h

XFGHelper::XFGHasher::add_string+14        test    al, 1           ; union/struct/enum is named?

XFGHelper::XFGHasher::add_string+16        jz      short loc_180105674

XFGHelper::XFGHasher::add_string+18        lea     r9, aUnnamed+9  ; ""

XFGHelper::XFGHasher::add_string+1F        lea     r8, aUnnamed    ; " < unnamed > "

XFGHelper::XFGHasher::add_string+26

XFGHelper::XFGHasher::add_string+26   loc_180105666:

XFGHelper::XFGHasher::add_string+26        mov     rdx, [rcx+8]

XFGHelper::XFGHasher::add_string+2A        call    std::vector < uchar > ::_Insert_range < uchar const * > (std::_Vector_const_iterator < std::_Vector_val < std::_Simple_types < uchar >  >  > ,uchar const *,uchar const *,std::forward_iterator_tag)

XFGHelper::XFGHasher::add_string+2F        add     rsp, 38h

XFGHelper::XFGHasher::add_string+33        retn

XFGHelper::XFGHasher::add_string+34   ; ---------------------------------------------------------------------------

XFGHelper::XFGHasher::add_string+34

XFGHelper::XFGHasher::add_string+34   loc_180105674:

XFGHelper::XFGHasher::add_string+34        mov     r8, [r8+8]      ; r8 = union/struct/enum name

XFGHelper::XFGHasher::add_string+38        or      r9, 0FFFFFFFFFFFFFFFFh

XFGHelper::XFGHasher::add_string+3C

XFGHelper::XFGHasher::add_string+3C   loc_18010567C:

XFGHelper::XFGHasher::add_string+3C        inc     r9

XFGHelper::XFGHasher::add_string+3F        cmp     byte ptr [r8+r9], 0

XFGHelper::XFGHasher::add_string+44        jnz     short loc_18010567C

XFGHelper::XFGHasher::add_string+46        add     r9, r8          ; r9 points to end of string

XFGHelper::XFGHasher::add_string+49        jmp     short loc_180105666

之后,函数XFGHelper::XFGTypeHasher::hash_tag中有一个代码分支,可以在某些情况下将字符串" < local > "添加到需要计算哈希的数据中。我们对此没有进行太多研究,但它可能处理了本地范围的union/struct/enum的情况。

XFGHelper::XFGTypeHasher::hash_tag+4D        mov     rbx, [rbx+18h]

XFGHelper::XFGTypeHasher::hash_tag+51        test    rbx, rbx

XFGHelper::XFGTypeHasher::hash_tag+54        jnz     short loc_180105A16

XFGHelper::XFGTypeHasher::hash_tag+56        jmp     short loc_180105A76

XFGHelper::XFGTypeHasher::hash_tag+58   ; ---------------------------------------------------------------------------

XFGHelper::XFGTypeHasher::hash_tag+58

XFGHelper::XFGTypeHasher::hash_tag+58   loc_180105A5C:

XFGHelper::XFGTypeHasher::hash_tag+58        mov     rdx, [rdi+8]

XFGHelper::XFGTypeHasher::hash_tag+5C        lea     r9, aLocal+7    ; ""

XFGHelper::XFGTypeHasher::hash_tag+63        lea     r8, aLocal      ; " < local > "

XFGHelper::XFGTypeHasher::hash_tag+6A        mov     rcx, rdi

XFGHelper::XFGTypeHasher::hash_tag+6D        call    std::vector < uchar > ::_Insert_range < uchar const * > (std::_Vector_const_iterator < std::_Vector_val < std::_Simple_types < uchar >  >  > ,uchar const *,uchar const *,std::forward_iterator_tag)

5.3.3 原始类型的哈希

在处理原始类型时(在Type_t值中未设置0x100、0x200或0x400的原始类型),在将值为1的字节写入std::vector后,函数XFGHelper::XFGTypeHasher::compute_hash会调用XFGHelper::XFGTypeHasher::hash_primitive。

XFGHelper::XFGTypeHasher::hash_primitive基本上是一个很大的switch语句,它将Type_t值映射到代表原始类型的一组不同常量。然后,将得到的常数(单个字节)添加到std::vector。例如,对于以Type_t 0x26表示的浮点型,该函数将一个值为0x0B的字节添加到std::vector。

XFGHelper::XFGTypeHasher::hash_primitive      private: void XFGHelper::XFGTypeHasher::hash_primitive(class Type_t const *) proc near

XFGHelper::XFGTypeHasher::hash_primitive           sub     rsp, 38h

XFGHelper::XFGTypeHasher::hash_primitive+4         mov     eax, [rdx]

XFGHelper::XFGTypeHasher::hash_primitive+6         mov     r10, rcx

XFGHelper::XFGTypeHasher::hash_primitive+9         and     eax, 1FFFh

XFGHelper::XFGTypeHasher::hash_primitive+E         cmp     eax, 40h ; '@'

XFGHelper::XFGTypeHasher::hash_primitive+11        ja      loc_1801059D4

XFGHelper::XFGTypeHasher::hash_primitive+17        jz      loc_1801059D0   ; case 0x40:

XFGHelper::XFGTypeHasher::hash_primitive+1D        cmp     eax, 1Ah

XFGHelper::XFGTypeHasher::hash_primitive+20        ja      short loc_18010599E

[...]

XFGHelper::XFGTypeHasher::hash_primitive+6E   loc_18010599E:

XFGHelper::XFGTypeHasher::hash_primitive+6E        sub     eax, 1Bh        ; case 0x1B:

XFGHelper::XFGTypeHasher::hash_primitive+71        jz      short loc_1801059CC

XFGHelper::XFGTypeHasher::hash_primitive+73        sub     eax, 1          ; case 0x1C:

XFGHelper::XFGTypeHasher::hash_primitive+76        jz      short loc_1801059C8

XFGHelper::XFGTypeHasher::hash_primitive+78        sub     eax, 2          ; case 0x1E:

XFGHelper::XFGTypeHasher::hash_primitive+7B        jz      short loc_1801059C4

XFGHelper::XFGTypeHasher::hash_primitive+7D        sub     eax, 8          ; case 0x26 (float):

XFGHelper::XFGTypeHasher::hash_primitive+80        jz      short loc_1801059C0

[...]

XFGHelper::XFGTypeHasher::hash_primitive+90   loc_1801059C0:

XFGHelper::XFGTypeHasher::hash_primitive+90        mov     cl, 0Bh         ; primitive_type = 0xB (float)

XFGHelper::XFGTypeHasher::hash_primitive+92        jmp     short loc_1801059DE

[...]

XFGHelper::XFGTypeHasher::hash_primitive+AE   loc_1801059DE:

XFGHelper::XFGTypeHasher::hash_primitive+AE        mov     rdx, [r10+8]

XFGHelper::XFGTypeHasher::hash_primitive+B2        lea     r9, [rsp+38h+arg_9]

XFGHelper::XFGTypeHasher::hash_primitive+B7        mov     [rsp+38h+arg_8], cl ; value to add: primitive_type

XFGHelper::XFGTypeHasher::hash_primitive+BB        lea     r8, [rsp+38h+arg_8]

XFGHelper::XFGTypeHasher::hash_primitive+C0        mov     rcx, r10

XFGHelper::XFGTypeHasher::hash_primitive+C3        call    std::vector < uchar > ::_Insert_range < uchar const * > (std::_Vector_const_iterator < std::_Vector_val < std::_Simple_types < uchar >  >  > ,uchar const *,uchar const *,std::forward_iterator_tag)

Windows MVSC编译器实现Xtended Flow Guard(XFG)保护机制的原理分析
六、最终转换

到目前为止,我们已经深入描述了C编译器前端如何为XFG机制来计算函数原型的哈希。我们可以用类似Python的伪代码来进行概括,函数的哈希是通过以下方式构建的:

hash =  sha256(number_of_params +

              type_hash(params[0]) +

              type_hash(params[...]) +

              type_hash(params[n]) +

              is_variadic +

              calling_convention +

              type_hash(return_type)

        )[0:8]

XFG函数哈希是SHA256摘要的一部分,仅保留了前8个字节,因此与完整的SHA256哈希相比,它们的抗冲突性有所降低,但是我们可以预期,不同的XFG哈希可以在一定程度上保证哈希的功能。

但是,如果针对特定的二进制文件,检查其XFG哈希(这里选择了ntdll.dll),我们会注意到,它们似乎没有64位上熵:

function 0x180001a30 - >  prototype hash: 0x8d952e0d365aa071

function 0x180001b50 - >  prototype hash: 0xe2198f4a3c515871

function 0x180001dc0 - >  prototype hash: 0xbeac2e06165fc871

function 0x180001de0 - >  prototype hash: 0xfaec0e7f70d92371

function 0x180001fc0 - >  prototype hash: 0xc5d11eb750d75871

function 0x180002030 - >  prototype hash: 0xe8bcaf9a10586871

function 0x180002040 - >  prototype hash: 0xc3110f087e584871

function 0x1800020b0 - >  prototype hash: 0xdbc1261858d2f871

function 0x1800023a0 - >  prototype hash: 0xda690f3e36531a71

其背后的原因是,由编译器前端(c1.dll)生成的SHA256片段,在实际写入到生成的目标文件之前,会由编译器后端(c2.dll)进行最终转换。确切的说,c2.dll中的XfgIlVisitor::visit_I_XFG_HASH函数将两个掩码应用到了截断的SHA256哈希上:

XfgIlVisitor::visit_I_XFG_HASH(tagILMAP *)+5B        mov     rcx, 8000060010500070h

XfgIlVisitor::visit_I_XFG_HASH(tagILMAP *)+65        mov     r13, 0FFFDBFFF7EDFFB70h

[...]

XfgIlVisitor::visit_I_XFG_HASH(tagILMAP *)+E9        mov     rdx, [rax]      ; rdx = 8 bytes of SHA256 hash

XfgIlVisitor::visit_I_XFG_HASH(tagILMAP *)+EC        add     rax, 8

XfgIlVisitor::visit_I_XFG_HASH(tagILMAP *)+F0        and     rdx, r13        ; hash &= 0FFFDBFFF7EDFFB70h

XfgIlVisitor::visit_I_XFG_HASH(tagILMAP *)+F3        mov     [rbx], rax

XfgIlVisitor::visit_I_XFG_HASH(tagILMAP *)+F6        or      rdx, rcx        ; hash |= 8000060010500070h

XfgIlVisitor::visit_I_XFG_HASH(tagILMAP *)+F9        mov     ecx, r9d        ; this

XfgIlVisitor::visit_I_XFG_HASH(tagILMAP *)+FC        call    XFG::TiSetHash(ulong,unsigned __int64,tagMOD *)

这就是之所以XFG哈希基于SHA256,但看起来也不像完全随机的原因。不过,我们不清楚为什么要使用这些掩码。

Windows MVSC编译器实现Xtended Flow Guard(XFG)保护机制的原理分析
七、尝试进行哈希计算

为了确保我们已经正确理解了如何生成XFG哈希,我们尝试进行手动的哈希计算。假设我们使用以下原型计算函数的哈希值:

void *memcpy(

   void *dest,

   const void *src,

   size_t count

);

我们需要找出构成函数原型的五条数据:

(1)参数数量;

(2)为每个参数输入哈希值;

(3)是否具有可变参数函数;

(4)调用约定;

(5)返回类型的哈希值。

其中的1、3、4都很简单:

(1)参数数量 - >  DWORD,值为3;

(3)是否具有可变参数函数 - >  值为0的字节;

(4)调用约定 - >  默认值(值为0x201和0xF == 0x1的DWORD)。

因此,我们来计算更复杂的部分——每个参数的类型哈希,以及返回类型的类型哈希。

7.1 参数1的类型哈希

第一个参数的类型为void *,该类型由以下内容的Type_t表示:

00000102 00000200 [+ pointer to referenced Type_t]

我们需要寻找3个数据,来产生类型哈希:

(1)类型限定符 - >  值为0的字节;

(2)类型组:指针 - >  值为3的字节;

(3)特定类型的数据:这是一个通用指针 - >  引用类型的哈希(在这里有递归)+值为2的字节。

为了递归计算引用类型(void)的哈希,该类型由Type_t表示,其内容如下:

00000040 00000000

我们需要构建如下数据:

(1)类型限定符 - >  值为0的字节;

(2)类型组:原始类型 - >  值为1的字节;

(3)特定类型的数据:对于Type_t 0x40(void),XFGHelper::XFGTypeHasher::hash_primitive写入一个值为0x0E的字节。

7.2 参数2的类型哈希

第二个参数的类型为const void *。该类型由具有以下内容的Type_t表示:

00000102 00000200 [+ pointer to referenced Type_t]

我们需要构建的数据如下:

(1)类型限定符 - >  值为0的字节;

(2)类型组:指针 - >  值为3的字节;

(3)特定类型的数据:这是一个通用指针 - >  引用类型的哈希(在这里有递归)+值为2的字节。

为了递归计算引用类型(void)的哈希,该类型由Type_t表示,其内容如下:

00000040 00000000

我们需要构建如下数据:

(1)类型限定符:具有const限定符 - >  编码为值为1的字节;

(2)类型组:原始类型 - >  值为1的字节;

(3)特定类型的数据:对于Type_t 0x40(void),XFGHelper::XFGTypeHasher::hash_primitive写入一个值为0x0E的字节。

7.3 参数3的类型哈希

第三个参数的类型为size_t。该类型由具有以下内容的Type_t表示:

00004019 00000000

我们需要构建的数据如下:

(1)类型限定符 - >  值为0的字节;

(2)类型组:原始类型 - >  值为1的字节;

(3)特定类型的数据:对于Type_t 0x4019(无符号长长整型),XFGHelper::XFGTypeHasher::hash_primitive写入一个值为0x88的字节。

7.4 返回类型的类型哈希

返回类型为void *,与该函数的第一个参数相同,因此这里只需要重复即可:

(1)类型限定符 - >  值为0的字节;

(2)类型组:指针 - >  值为3的字节;

(3)特定类型的数据:这是一个通用指针 - >  引用类型的哈希(在这里有递归)+值为2的字节。

对于引用类型(void)的哈希进行递归计算:

(1)类型限定符:值为0的字节;

(2)类型组:原始类型 - >  值为1的字节;

(3)特定类型的数据:对于Type_t 0x40(void),XFGHelper::XFGTypeHasher::hash_primitive写入一个值为0x0E的字节。

7.5 组合

我们将所有数据组合到一起:

# Number of params

03 00 00 00

# type hash of param 1 (void *)

SHA256(

    00  #qualifiers

    03  # type group: pointer

    # type hash of referenced type (void)

    SHA256(

        00  # qualifiers

        01  # type group: primitive type

        0E  # hash of primitive type: void - >  0x0E

    )[0:8]

    02  # regular pointer

)[0:8]

# type hash of param 2 (const void *)

SHA256(

    00  # qualifiers

    03  # type group: pointer

    # type hash of referenced type (const void)

    SHA256(

        01  # qualifiers: const

        01  # type group: primitive type

        0E  # hash of primitive type: void - >  0x0E

    )[0:8]

    02  # regular pointer

)[0:8]

# type hash of param 3 (size_t)

SHA256(

    00  # qualifiers

    01  # type group: primitive type

    88  # hash of primitive type: unsigned long long - >  0x88

)[0:8]

# is variadic

00

# calling convention

01 00 00 00

# type hash of return value (void *)

SHA256(

    00  # qualifiers

    03  # type group: pointer

    # type hash of referenced type (void)

    SHA256(

        00  # qualifiers

        01  # type group: primitive type

        0E  # hash of primitive type: void - >  0x0E

    )[0:8]

    02  # regular pointer

)[0:8]

以下Python代码获取该数据的SHA256摘要,并将其截断为前8个字节,以获取与编译器前端发出的哈希相同的哈希值。最后,它将编译器后端的两个掩码进行应用,形成最终形式的XFG哈希。

import struct

import hashlib

def truncated_hash(data):

    return hashlib.sha256(data).digest()[0:8]

def apply_backend_masks(hash):

    hash = hash & 0xFFFDBFFF7EDFFB70

    hash = hash | 0x8000060010500070

    return hash

def main():

    # number of params

    data  = struct.pack(' < L', 3)

    # type hash of first param (void *)

    data += truncated_hash(b'x00x03' + truncated_hash(b'x00x01x0e') + b'x02')

    # type hash of second param (const void *)

    data += truncated_hash(b'x00x03' + truncated_hash(b'x01x01x0e') + b'x02')

    # type hash of third param (size_t)

    data += truncated_hash(b'x00x01x88')

    # is variadic

    data += struct.pack(' < B', 0x0)

    # calling convention (default)

    data += struct.pack(' < L', 0x201 & 0x0F)

    # type hash of return type (void *)

    data += truncated_hash(b'x00x03' + truncated_hash(b'x00x01x0e') + b'x02')

    print(f'Data to be hashed: {data} ({len(data)} bytes)')

    frontend_hash = struct.unpack(' < Q', truncated_hash(data))[0]

    print(f'Hash generated by the frontend: 0x{frontend_hash:x}')

    final_hash = apply_backend_masks(frontend_hash)

    print(f'[*] Final XFG hash: 0x{final_hash:x}')

Python代码的输出结果如下:

>  python test.py

Data to be hashed: b'x03x00x00x00xf5x97x > [J`xb0x17x80xb8xc0[x1bxd0xd8#x14xb4xbax91xc7xf6jx00x01x00x00x00xf5x97x > [J`xb0' (41 bytes)

Hash generated by the frontend: 0x1da7d393d6b63a72

[*] Final XFG hash: 0x9da5979356d63a70

如果我们使用函数指针编译一些代码,以调用其原型与我们在本章中讨论过的原型相匹配的函数,就会看到,我们手工计算的XFG哈希值与MSVC生成的哈希完全匹配(参阅分配的值)。在下面的反汇编中的main+0x8E处注册了R10。

main+1C        lea     rax, my_memcpy

main+23        mov     [rsp+78h+var_50], rax

[...]

main+6A        lea     rcx, aCallingFunctio ; "Calling function pointer...n"

main+71        call    printf

main+76        lea     rcx, Str        ; "a test"

main+7D        call    strlen

main+82        cdqe

main+84        mov     rcx, [rsp+78h+var_50]

main+89        mov     [rsp+78h+var_48], rcx

main+8E        mov     r10, 9DA5979356D63A70h

main+98        mov     r8, rax

main+9B        lea     rdx, aATest_0   ; "a test"

main+A2        lea     rcx, [rsp+78h+var_28]

main+A7        mov     rax, [rsp+78h+var_48]

main+AC        call    cs:__guard_xfg_dispatch_icall_fptr

Windows MVSC编译器实现Xtended Flow Guard(XFG)保护机制的原理分析
八、总结

在这篇文章中,我们分享了MSVC编译器是如何为C语言程序生成XFG哈希的所有详细信息。除了探讨后续的漏洞利用缓解措施细节外,我们还可以深入了解编译器内部原理。

请注意,目前XFG仅存在于Windows Insider Preview版本中,因此在这个CFI解决方案进入到Windows 10正式版本之前,本文所描述的细节可能还会被微软进行调整。

目前暂时不清楚,为什么编译器后端对前端生成的哈希要使用两个位掩码,为什么哈希在函数启动前使用第0位存储,而未设置第0位的就要存储在XFG的调用目标中。

最后,非常有趣的是,我们可以看看C++编译器前端(c1xx.dll)计算XFG哈希值的方式的不同之处。如果迅速浏览这个二进制文件,会发现哈希算法看起来与C语言的算法非常相似,但是考虑到继承、C++类型限定符、修饰符这类独有概念,还是有可能会进行调整的。

参考及来源:https://blog.quarkslab.com/how-the-msvc-compiler-generates-xfg-function-prototype-hashes.html

Windows MVSC编译器实现Xtended Flow Guard(XFG)保护机制的原理分析

Windows MVSC编译器实现Xtended Flow Guard(XFG)保护机制的原理分析

本文始发于微信公众号(嘶吼专业版):Windows MVSC编译器实现Xtended Flow Guard(XFG)保护机制的原理分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年4月2日12:05:50
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Windows MVSC编译器实现Xtended Flow Guard(XFG)保护机制的原理分析https://cn-sec.com/archives/189063.html

发表评论

匿名网友 填写信息