/GS(缓冲区安全检查)浅析

来源:互联网 发布:网络鲜花礼品网 编辑:程序博客网 时间:2024/04/30 14:41

VC.net 增加了/GS缓冲区安全检查编译选项。MSDN是这样说明的:检测某些覆盖函数返回地址、异常处理程序地址或特定类型的参数的缓冲区溢出。导致缓冲区溢出是黑客用于利用不强制缓冲区大小限制的代码的一种技术。

安全检查

对于编译器认为容易出现缓冲区溢出问题的函数,编译器将在堆栈上返回地址之前分配空间。在进入函数时,用安全 Cookie(它在模块加载时计算一次)加载分配的空间。在退出函数时,以及在 64 位操作系统上展开帧的过程中,将调用 helper 函数,以确保 Cookie 值仍保持不变。不同的值指示可能已覆盖堆栈。如果检测到不同的值,则终止该进程。

 

GS 缓冲区生成依赖项只指在解决方案中的项目依赖项。GS 缓冲区可以是下列之一:

1、In earlier versions of $$$$, this level of dependency was sufficient.

2、大小大于 8 字节且不包含指针的数据结构。

3、通过使用 _alloca 函数分配的缓冲区。

4、包含 GS 缓冲区的任何类或结构。

 

以下实战:

开了/GS编译开关,每个入口函数都要进行初始化检测值。

.text:00012606 ; NTSTATUS __stdcall DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
.text:00012606                 public DriverEntry
.text:00012606 DriverEntry     proc near
.text:00012606
.text:00012606 DriverObject    = dword ptr  8
.text:00012606 RegistryPath    = dword ptr  0Ch
.text:00012606
.text:00012606                 mov     edi, edi
.text:00012608                 push    ebp
.text:00012609                 mov     ebp, esp
.text:0001260B                 call    InitCookie      ; 初始化/GS编译开关的堆栈溢出Cookie
.text:00012610                 pop     ebp
.text:00012611                 jmp     sub_12530
.text:00012611 DriverEntry     endp


INIT:0001715F ; 初始化/GS编译开关的堆栈溢出Cookie
INIT:0001715F
INIT:0001715F InitCookie      proc near               ; CODE XREF: DriverEntry+5 p
INIT:0001715F                 mov     eax, dwCheckValue
INIT:00017164                 mov     ecx, 0BB40E64Eh
INIT:00017169                 test    eax, eax
INIT:0001716B                 jz      short loc_17171
INIT:0001716D                 cmp     eax, ecx
INIT:0001716F                 jnz     short loc_1718B
INIT:00017171
INIT:00017171 loc_17171:                              ; CODE XREF: InitCookie+C j
INIT:00017171                 mov     eax, ds:KeTickCount
INIT:00017176                 mov     eax, [eax]
INIT:00017178                 xor     eax, offset dwCheckValue
INIT:0001717D                 mov     dwCheckValue, eax
INIT:00017182                 jnz     short loc_1718B
INIT:00017184                 mov     eax, ecx
INIT:00017186                 mov     dwCheckValue, eax
INIT:0001718B
INIT:0001718B loc_1718B:                              ; CODE XREF: InitCookie+10 j
INIT:0001718B                                         ; InitCookie+23 j
INIT:0001718B                 not     eax
INIT:0001718D                 mov     dwCookie, eax
INIT:00017192                 retn

dwCookie 存放堆栈检测标志数值,dwCheckValue 后面用于测试使用.

下面是/GS编译开关的效果函数(中间省略掉一堆无关操作代码)
PAGE:00015C30 ; =============== S U B R O U T I N E =======================================
PAGE:00015C30
PAGE:00015C30 ; Attributes: bp-based frame
PAGE:00015C30
PAGE:00015C30 sub_15C30       proc near               ; CODE XREF: sub_12394+77 p
PAGE:00015C30                                         ; sub_15AA2+22 p
PAGE:00015C30
PAGE:00015C30 var_5C          = dword ptr -5Ch    // 这次变量够多了吧............................
PAGE:00015C30 var_58          = dword ptr -58h
PAGE:00015C30 var_48          = dword ptr -48h
PAGE:00015C30 var_44          = dword ptr -44h
PAGE:00015C30 var_40          = byte ptr -40h
PAGE:00015C30 var_3C          = dword ptr -3Ch
PAGE:00015C30 var_38          = dword ptr -38h
PAGE:00015C30 var_34          = dword ptr -34h
PAGE:00015C30 var_30          = dword ptr -30h
PAGE:00015C30 var_2C          = dword ptr -2Ch
PAGE:00015C30 var_28          = dword ptr -28h
PAGE:00015C30 var_24          = dword ptr -24h
PAGE:00015C30 var_20          = dword ptr -20h
PAGE:00015C30 var_1C          = dword ptr -1Ch
PAGE:00015C30 P               = dword ptr -18h
PAGE:00015C30 var_14          = dword ptr -14h
PAGE:00015C30 var_D           = byte ptr -0Dh
PAGE:00015C30 var_C           = byte ptr -0Ch
PAGE:00015C30 var_B           = byte ptr -0Bh
PAGE:00015C30 var_A           = word ptr -0Ah
PAGE:00015C30 var_8           = word ptr -8
PAGE:00015C30 var_6           = word ptr -6
PAGE:00015C30 var_4           = dword ptr -4    // 堆栈顶部变量值
PAGE:00015C30 arg_0           = dword ptr  8
PAGE:00015C30
PAGE:00015C30                 mov     edi, edi
PAGE:00015C32                 push    ebp
PAGE:00015C33                 mov     ebp, esp
PAGE:00015C35                 sub     esp, 5Ch
PAGE:00015C38                 mov     eax, dwCheckValue
PAGE:00015C3D                 xor     eax, ebp
PAGE:00015C3F                 mov     [ebp+var_4], eax  // 将检测值放入堆栈顶部

// 在中间,函数进行一些操作
.......................................................  // 函数进行一些操作
// 执行到函数的结尾了

PAGE:00015F8C                 mov     ecx, [ebp+var_4]  // 取出本地堆栈顶部
PAGE:00015F8F                 mov     eax, edi
PAGE:00015F91                 pop     edi
PAGE:00015F92                 pop     esi
PAGE:00015F93                 xor     ecx, ebp
PAGE:00015F95                 pop     ebx
PAGE:00015F96                 call    loc_1247D    // 进程检测堆栈是否被覆盖了(检测数值被更改了)
PAGE:00015F9B                 leave
PAGE:00015F9C                 retn    4
PAGE:00015F9C sub_15C30       endp


call loc_1247D 代码如下:
.text:0001247D loc_1247D:                              ; CODE XREF: sub_11352+37E p
.text:0001247D                                         ; sub_15C30+366 p
.text:0001247D                 cmp     ecx, dwCheckValue // 检测堆栈顶部是否相等(也就是没有被覆盖刚才那个var_4栈顶变量)
.text:00012483                 jnz     short loc_12488  // 如果不等,则调用KeBugCheckEx打印出堆栈检测更改数值与原始数值,并蓝屏
.text:00012485                 retn    0
.text:00012488 ; ---------------------------------------------------------------------------
.text:00012488
.text:00012488 loc_12488:                              ; CODE XREF: .text:00012483 j
.text:00012488                 jmp     loc_12492
.text:00012488 ; ---------------------------------------------------------------------------
.text:0001248D                 db 5 dup(0CCh)
.text:00012492 ; ---------------------------------------------------------------------------
.text:00012492
.text:00012492 loc_12492:                              ; CODE XREF: .text:loc_12488 j
.text:00012492                 mov     edi, edi
.text:00012494                 push    ebp
.text:00012495                 mov     ebp, esp
.text:00012497                 push    ecx
.text:00012498                 mov     [ebp-4], ecx
.text:0001249B                 push    0
.text:0001249D                 push    dwCookie
.text:000124A3                 push    dwCheckValue
.text:000124A9                 push    dword ptr [ebp-4]
.text:000124AC                 push    0F7h
.text:000124B1                 call    ds:KeBugCheckEx

 

在所有平台上,/GS 尝试检测进入返回地址的缓冲区溢出。通过调用约定将函数调用的返回地址存储到堆栈上,可以更容易地在平台(如 x86 和 x64)上利用缓冲区溢出。在 x86 上,如果函数使用异常处理程序,则编译器将插入一个安全 Cookie 以保护异常处理程序的地址。在展开帧的过程中会检查该 Cookie。