瞭解棧溢出攻擊與保護

棧溢出攻擊是個老話題了,本質上就是通過合法的方式輸入不符合規的數據來破壞棧上的數據,從而執行惡意代碼

以下內容以x86程序來說明,x64大同小異。

0x01 棧的內存佈局

要了解如何攻擊,就要先掌握一個函數的棧空間裏是如何擺放數據的

void test()
{
	int a = 0x11111111;
	int b = 0x22222222;
}

彙編代碼如下

push ebp
mov ebp,esp
sub esp,48
push ebx
push esi
push edi
mov dword ptr ss:[ebp-4],11111111
mov dword ptr ss:[ebp-8],22222222
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 

建議通過像od或x64dbg這樣的調試器來查看比較方便

當我們執行到ret時,棧空間如下

010FFDC8  22222222  // var b
010FFDCC  11111111  // var a
010FFDD0  010FFE24  // ebp
010FFDD4  00D9107E  // 返回地址

棧空間排放順從棧頂到棧底

1.局部變量

2.子過程入口壓入的ebp值

3.子過程返回地址

攻擊者的工作就是找出軟件中進行了數據輸入且有內存操作的地方,比較典型的就是strcpy、memcpy等函數。

來看看一個問題代碼

void test()
{
	char s[8];
	strcpy(s, "11112222");
}

子過程返回時棧的情況

0053FD38  31313131  // 1111
0053FD3C  32323232  // 2222
0053FD40  0053FD00  // ebp
0053FD44  0089107E  // 返回地址

目前因爲輸入的數據長度沒有超過變量允許的長度。但如果我輸入的數據超過8個字節將會覆蓋掉ebp和返回地址

void test()
{
	char s[8];
	strcpy(s, "1111222233334444");
}

棧空間

012FFA74  31313131  
012FFA78  32323232  
012FFA7C  33333333  // 原本ebp值被覆蓋
012FFA80  34343434  // 原本函數返回地址被覆蓋

如果攻擊者提前在內存裏寫入了一段ShellCode代碼,然後在這裏覆蓋掉返回地址爲ShellCode的入口,情況可想而知。。。

所以黑客要成功攻擊程序的話,有個前提條件就是開發者的“配合”:用了不安全的函數或直接對指針操作而不檢查數據最大合法長度。

上面的子過程會執行ret 0x34343434,而這個地址不存在,引發了一個內存錯誤

0x02 微軟的愛,緩衝區溢出檢查

爲了減輕開發者的負擔,VC編譯器增加了一個棧保護功能:/GS 開關

這個選項默認是開啓的,我們看看函數變成什麼樣子了

push ebp
mov ebp,esp
sub esp,4C
mov eax,dword ptr ds:[<___security_cookie>] // 取了一個全局變量cookie
xor eax,ebp
mov dword ptr ss:[ebp-4],eax // cookie入棧
push ebx
push esi
push edi
push dddd.ED2088
lea eax,dword ptr ss:[ebp-C]
push eax
call <dddd._strcpy>
add esp,8
pop edi
pop esi
pop ebx
mov ecx,dword ptr ss:[ebp-4]
xor ecx,ebp
call <dddd.@__security_check_cookie@4> // 檢查cookie是否被修改
mov esp,ebp
pop ebp
ret 

手段也很簡單,/gs打開後,c runtime會在程序啓動時產生一個隨機數,稱爲cookie,然後編譯器會在每個子過程中開頭將這個cookie壓入棧中,讓它處於ebp和返回地址的中間,如果攻擊者還通過第一節中的方法來覆蓋返回地址,勢必會將這個cookie覆蓋掉,而過程最後會通過一個嵌入的__security_check_cookie函數來檢查cookie,如果被覆蓋了就報錯。

這個__security_cookie是導出的,我們可以獲得它

extern UINT_PTR __security_cookie;

int main()
{
	printf("__security_cookie = 0x%x\n", __security_cookie);
	getchar();
	return 0;
}

它是怎麼初始化的呢,可以看下這篇文章:http://blog.sina.com.cn/s/blog_4e0987310101ie77.html

關於/GS開關:https://docs.microsoft.com/en-us/cpp/build/reference/gs-buffer-security-check

關於Cookie初始化函數:https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/security-init-cookie

0x03 更安全的函數

對於像strcpy、memcpy等危險的函數,在標準上已經有安全的版本了,比如strcpy_s、memcpy_s等,更多相關函數可以看手冊:https://docs.microsoft.com/en-us/cpp/c-runtime-library/security-enhanced-versions-of-crt-functions

0x04 JMP ESP

通過第一節已經瞭解了棧溢出攻擊的原理,但是有個問題,胡亂覆蓋數據只會導致程序崩潰而已,似乎並沒有太多作用。

攻擊的目的一般來說是要執行ShellCode代碼,如何能把ShellCode輸入進去並且執行呢?這就是JMP ESP解決的問題。

所謂JMP ESP就是將返回地址指向含有"jmp esp"彙編語句的地址

0x11111110 ret 0x88888888 // 跳向jmp esp語句的地方
0x11111114 ...  // ShellCode代碼
...
0x88888888 jmp esp // 此時esp正好等於0x11111114

因爲ret執行後esp正好+4指向了後面的指令,然後jmp esp跳轉到ShellCode開始執行。

不過可惜的是,這個技術已經很古老了,因爲微軟後來引入了ASLR和DEP技術,在棧上執行代碼這條路基本走不通了。

簡單地說:

ASLR(隨機基址):開啓後,PE鏡像每次載入內存後的地址是不固定的,攻擊者無法對固定的地址攻擊。

DEP(數據執行保護):開啓後,堆棧區域將無法執行代碼,這樣就杜絕了ShellCode。

當然有些大牛提供了繞過的思路,網上可查,這裏不展開。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章