深入GS保護機制


大多數溢出漏洞產生的原因是由於數組越界,導致溢出。首先要明白溢出漏洞這個我在很早前就寫過爛大街的文章了

我們知道大部分的溢出攻擊主要是覆蓋程序函數的返回地址那么看完再講講GS的工作流程=檢測某些覆蓋函數的返回地址、異常處理程序地址(SEH)或者類型參數的緩沖區溢出。在執行緩沖區溢出時會有安全檢查GS 緩沖區。 GS 緩沖區可以是下列之一:

+++++++++++++++++++++++++++++

char buffer[20];

int buffer[20];

struct { int a; int b; int c; int d; } myStruct;

struct { int a; char buf[20]; };

但是,以下語句不聲明 GS 緩沖區。 前兩個聲明包含指針類型的元素。 第三個和第四個語句聲明的數組太小。 第五個語句聲明一個結構,其大小在 x86 平台不超過 8 個字節。

+++++++++++++++++++++++++++++

char *pBuf[20];

void *pv[20];

char buf[4];

int buf[2];

struct { int a; int b; };

 

一個數組,大於 4 個字節,超過兩個元素,並且具有不是指針類型的元素類型。一種數據結構,其大小超過 8 個字節,不包含指針。通過使用分配的緩沖區_alloca函數。任何類或結構,其中包含 GS 緩沖區。

例如,以下語句聲明 GS 緩沖區。

從MSDN的官宣可以知道什么情況下可以申明GS,什么情況下是不可以。

GS編譯器選項要求使用 COOKIE 的任何函數運行之前初始化安全 COOKIE。 在進入 EXE 或 DLL,必須立即初始化安全 COOKIE。如果使用默認 VCRuntime 入口點會自動完成: mainCRTStartup,wmainCRTStartup,WinMainCRTStartup,wWinMainCRTStartup,或 _DllMainCRTStartup。如果使用備用的入口點,必須通過調用手動初始化的安全 cookie __security_init_cookie這個就是后話了,當科普。

那么為了進一步了解GS,動手測試一下吧。

新建項目那么編譯之前我們先關閉其他的一些機制,只開啟GS的安全檢查。

 

 

開啟GS

 

 

+++++++++++++++++++++++++++++

void fun(const char * D)

{

    char buf[10];

    strcpy_s(buf, D);

}

int _tmain(int argc, _TCHAR* argv[])

{

    fun("aaaaaaaaaaaaaaa");

    return 0;

}

+++++++++++++++++++++++++++++

運行崩潰,載入Debugger。

查看堆棧空間

 

載入的15個字節的A,但是會發現真正入棧的只有9個A而多余都被舍去。

用可以看到有這么一行。

 

用OD分析Security_Check_Cookie流程

可以看到在執行我們的函數之前先CALL了一個地址。

 

CALL正是Security-cookie,下面分析流程。

00412EA0 >  55              push    ebp

00412EA1    8BEC            mov     ebp,esp

00412EA3    83EC 14         sub     esp,0x14

00412EA6    C745 F4 0000000>mov     dword ptr ss:[ebp-0xC],0x0

00412EAD    C745 F8 0000000>mov     dword ptr ss:[ebp-0x8],0x0

00412EB4    813D 00804100 4>cmp     dword ptr ds:[__security_cookie],0xBB40E64E

00412EBE    74 1F           je      short 00412EDF

00412EC0    A1 00804100     mov     eax,dword ptr ds:[__security_cookie]

00412EC5    25 0000FFFF     and     eax,0xFFFF0000

00412ECA    74 13           je      short 00412EDF

00412ECC    8B0D 00804100   mov     ecx,dword ptr ds:[__security_cookie]

00412ED2    F7D1            not     ecx

00412ED4    890D 04804100   mov     dword ptr ds:[__security_cookie_complement],ecx

00412EDA    E9 9A000000     jmp     00412F79

00412EDF    8D55 F4         lea     edx,dword ptr ss:[ebp-0xC]

00412EE2    52              push    edx

從反匯編窗口可以看出進行了一個比較然后取COOKIE之后存儲然后生成一個新的COOKIE那么要生成新的COOKIE,函數首先會查詢當前系統的時間也就是GetSystemTimeAsFileTime然后XOR生成新的保存至eax這是第一部分要做的事

 

 

第二部分GetCurrentThreadId開始獲取了進程標識符然后XOR作為COOKIE的第二部分。

TEB第一個指向SHE,第二個指向了自身ID。

 

第三部分組成就是GetCurrentProcessId由線程標識符XOR了。

 

第四部分通過GetTickCount獲取執行的毫秒數XOR作為COOKIE的第四部分。

003115D1  |.  8945 FC       mov     [local.1],eax

003115D4  |.  FF15 04203100 call    dword ptr ds:[<&KERNEL32.GetCurrentThreadId>]        ; [GetCurrentThreadId

003115DA  |.  3145 FC       xor     [local.1],eax

003115DD  |.  FF15 08203100 call    dword ptr ds:[<&KERNEL32.GetCurrentProcessId>]       ; [GetCurrentProcessId

003115E3  |.  3145 FC       xor     [local.1],eax

003115E6  |.  8D45 EC       lea     eax,[local.5]

003115E9  |.  50            push    eax                                                  ; /pPerformanceCount

003115EA  |.  FF15 0C203100 call    dword ptr ds:[<&KERNEL32.QueryPerformanceCounter>]   ; \QueryPerformanceCounter

最后通過QueryPerformanceCounter獲取性能計算器值異或XOR為第五部分的COOKIE。

 

當strcpy函數跑完后會進行一個COOKIE值比較,不相等就跳到了IsProcessorFeaturePresent。

跟完后發現他在函數執行之前就把COOKIE取出來PUSH到了堆棧當中,用於函數執行后的比較。

 

基本上門清了一下流程,當然GS不是萬能的在某種情況下是不受保護的。官方也說明了:

什么是不受保護

/GS編譯器選項不能防止所有緩沖區溢出安全攻擊。 例如,如果在對象中有一個緩沖區和 vtable,緩沖區溢出可能會損壞 vtable。即使使用 /GS,始終應該嘗試編寫安全代碼沒有緩沖區溢出。

MSDN已經告訴我們了,一個虛表函數就可以繞過GS.

從現存的資料有這么幾種方式:

1、攻擊S.E.H

1、通過猜測cookies值繞過

2、通過覆蓋虛函數指針繞過

3、等

這里我先拿簡單的通過覆蓋虛函數指針來舉例,剩下的幾種方法我會從下篇文章中一一列舉。

那么就定義一個虛表函數來嘗試繞過GS保護機制

// GS.cpp : 定義控制台應用程序的入口點。

//

 

#include "stdafx.h"

#include <string.h>

#include <windows.h>

 

class TestGS

{

public:

    void  test(char* src)

    {

       char buf[8];

       strcpy(buf, src);

       test2(); //調用

    }

    void  test2()//虛函數

    {

    }

};

int main()

{

    TestGS test;

    test.test("AAAAAAAAAAAAAAAAAAAA");

    return 0;

}

shellcode

/*LPVOID Memory = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

memcpy(Memory, buf, sizeof(buf));

((void(*)())Memory)();*/

//unsigned char buf[] =

//"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"

//"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"

//"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"

//"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"

//"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"

//"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"

//"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"

//"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"

//"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"

//"\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f"

//"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5"

//"\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"

//"\x00\x53\xff\xd5\x6e\x6f\x74\x65\x70\x61\x64\x2e\x65\x78\x65"

//"\x00";

編譯后運行崩潰,用ICE進行調試。

 

 

第一個CALL 生成Security-cookie

跟第二個CALL進去,從中會遇到很多函數別去管他找到入口。

 

 

再跟進到Strcpy函數。

 

 

使用JE指令來依次壓棧,這里插入個題外話可以去跟一下各種復制字符串到地址空間的匯編代碼,收獲會有很多。這里發一下我自己跟的。

strcpy

00401000  /$  55            push    ebp

00401001  |.  8BEC          mov     ebp,esp

00401003  |.  83EC 08       sub     esp,0x8

00401006  |.  33C0          xor     eax,eax

00401008  |.  EB 06         jmp     short 00401010

0040100A  |   8D9B 00000000 lea     ebx,dword ptr ds:[ebx]

00401010  |>  8A88 20214000 /mov     cl,byte ptr ds:[eax+0x402120]

00401016  |.  8D40 01       |lea     eax,dword ptr ds:[eax+0x1]

00401019  |.  84C9          |test    cl,cl

0040101B  |.^ 75 F3         \jnz     short 00401010

0040101D  |.  8BE5          mov     esp,ebp

0040101F  |.  5D            pop     ebp

00401020  \.  C2 0400       retn    0x4

 

scanf

706AEA32 >  55              push    ebp

706AEA33    8BEC            mov     ebp, esp

706AEA35    8B4D 08         mov     ecx, dword ptr [ebp+8]

706AEA38    FF49 04         dec     dword ptr [ecx+4]

706AEA3B    78 0C           js      short 706AEA49

706AEA3D    8B01            mov     eax, dword ptr [ecx]

706AEA3F    0FB610          movzx   edx, byte ptr [eax]

706AEA42    40              inc     eax

706AEA43    8901            mov     dword ptr [ecx], eax

706AEA45    8BC2            mov     eax, edx

還有像fopen等等。

當我們進入到Strcpy函數后單步走的注意力看着堆棧的變化

 

然后當我們Strcpy函數執行完后可以看到我們的虛函數TEST2的CALL

當mov edx,dword ptr ds:[eax] 獲取到EAX的地址后我們就可以覆蓋到虛函數指針的地址然后JMP我們的shellcode。具體計算我們可以使用:

虛表指針地址-字符串堆棧地址=要覆蓋的字數

從中的COOKIE在虛表函數后再進行校驗,而我們的SHELLCODE早就執行完了。

但這種方式在實際過程中對使用者技術難度相對要求稍微高一點。就到這吧動手的活留給大家琢磨了,關於shellcode的生成搞web就不陌生了,可以使用自己寫的代碼然后取機器碼無非就是自己動手累一點了,我比較喜歡用現成的metasploit了。

 

剩下的幾種方式會在下節文章中列舉,Thanks。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM