2020年5月13日 星期三

[轉載] ___security_cookie機制,防止棧溢出

原文

從研究底層和彙編以來,已經多次接觸到“棧溢出”這個名詞了。

這次在彙編碼中看到了個不明就裡的 ___security_cookie ,查了下,原來是編譯器的安全檢查機制。轉載一篇文章:

首先,security cookie並不是windows系統自帶的保護機制,並不是說一個確實存在溢出漏洞的
程序,放到帶security cookie保護的環境中,就不能正常溢出了。
那麼,到底是什麼是security cookie呢?
我覺得從廣義上講,它應該是一種保護棧的機制,提供這種保護的,是程序本身,編譯進程序本身的代碼提供的,而不是系統中某個運行在黑暗角落中的線程。
所以,既然是程序自身就帶上的,為了不給程序員帶來額外的負擔,這份工作就交給編譯器來完成了。
vc6.0的cl.exe是不帶這個功能的,只有vc.net以後面版本的cl.exe才帶這個功能,就所謂的/GS選項。
即用vc.net的cl編譯器時,/GS選項默認就打開了。
現在,我們知道了這個機制的提供方,那麼,這個機製到底是怎麼一回事呢?
熟悉函數調用及返回前後的彙編指令的人肯定很清楚,在win32平台,對於stdcall類型的函數調用,當call指令運行完畢,當前的堆棧結構基本上是這樣的:



局變2 ebp-8 低地址
局變1 ebp- 4
ebp ebp
返回地址 ebp+4
參數1 ebp+8
參數2 ebp+c
參數3 ebp+10
參數4 ebp+14 高地址

第一列是堆棧中存放的dword的內容,第二列是用ebp作為棧地址的索引時,它對應的應該用ebp表示的值,說得形像一點,ebp中存放著棧的一個地址(棧其實也是一片內存,ebp只是指向其中一個對當前函數內部比較重要的地址,其實是相當重要),棧的其它位置都是通過這個ebp來尋址的,即我們給函數的第一個形參的地址,就是ebp+8,第二個就是ebp+c,我們定義的局部變量的地址,第一個局部變量是ebp-4,第二局部變量的地址就是ebp-8,依此類推。但這也不是一定的,上面說的是理想情況,如果我們在函數里定了一個數組,如 char buf[8];並且是定義的第一個局部變量,那麼它的地址肯定就不是ebp-4,還是ebp-8。所以,數組比較特殊,結構體也比較特殊,其根本原因是棧是從高地址向低地址生長的,而我們的數組,結構體,卻是從低向高地址生長的,兩者矛盾的結果就是尋址上的微妙變化。
當然,這里為了方便說明問題,都默認定義的變量,傳入的參數,都是四字節對齊的,並且一個變量一個雙字。你可以把數組理解一個4字節的char,也就是一個雙字了。
話說回來,當call運行完畢,當前的堆棧結構已經給出,如果在函數里調用strcpy()往局部變量1 裡考入東西,對長度沒有進行檢測,那麼ebp-4,ebp,ebp+4,還有後面的地址,其所在的內容都會被覆蓋掉。這裡溢出就發生了,我們控制住了ret的返回地址,然後...
嗯,為了防止這一切,新的cl編譯器的/GS選項加上入所謂的"security cookie",如何加入的?在哪加入的
呢?
先看看加入"security cookie"後的call指令運行完以後,堆棧的變化。

局變2 ebp-c 低地址
局變1 ebp-8
XXXXX ebp-4
ebp ebp
返回地址 ebp+4
參數1 ebp+8
參數2 ebp+c
參數3 ebp+10
參數4 ebp+14 高地址

變化很明顯,在ebp上面,第一個局部變量的下面,填入的一個新的值,這個值就是所謂的"security cookie".按照前面說的溢出過程,ebp- 4的內容被覆蓋掉,即security cookie的值被修改,在函數返回,即執行ret指令前,會call另一個函數,這個函數就是用來對比 ebp-4的值和當時push到棧中的值是不是一樣,不一樣的話,就說明溢出了,然後進程被終止。
那麼,你大概會產生以下幾個問題:
1. 這個security cookie是如何計算出來的?
security cookie是一個雙字,也可以說是一個int,其本身是保存在全局變量裡的,其創建是編譯器在編譯階段就創建的,然後寫入到.data段裡,即在PE裡就保存了這個值。
但這個值又是變化的,windows裝載器完成必要的前期準備工作後(如創建進程,為棧分配內存,等待)把 EIP設置為PE裡的代碼入口處,第一個執行的指令就是一個call調用,這個call調用就是用來初始化這個cookie值的,當然,這段代碼也是公開的,但沒有關係,這個算法保證這個cookie值是隨機的,hacker也是不能在一個shellcode中可以猜出來的。
具體算法我不打算在此說明,感興趣的讀者可以自己編譯一下再反彙編一下看看。

2.是什麼時候填入到棧裡面的?
我們知道了這個 security cookie的計算和初始化過程,那麼,它必須在函數調用時寫入到ebp-4裡面才有用。
所以,過去的不帶這種保護的代碼,在函數入口處一般是這樣的:
push ebp
mov ebp,esp
sub ebp,n ;這條指令可能不同,不過多數情況下都是這樣來為局部變量分配空間的
然後,後面就開始執行我們的代碼了,加入這種保護後,會在sub ebp,n後面,加入一條像這樣的指令:
mov dword ptr [ebp-4],XXX
XXX就是security cookie的值,這個值保存在全局變量裡,通過RVA+PE頭地址,實際上也可以說成是絕對地址來引用了。
到這裡security cookie的值就寫入棧了,然後在函數返回前檢測一下就行了。

到了這裡,你大概又會產生一個新的問題,
必須為每一個函數調用都寫入security cookie進行保護嗎?
答案是否定的,要不然我們的程序的執行效率會受到一定的影響,並且可能還不小。
那麼,就應該存在一定的規則,什麼時候進行這種保護,什麼時候不需要。
其依據當然也很簡單,有溢出可能的,就加入這種保護,沒有溢出可能的,就不加。
那怎麼樣才算是有溢出可能呢?
這個是編譯器進行判斷的,像函數里定義了char數組,後面又用字符串操作函數進行了一定的操作,就說明可能存在溢出。編譯器在編譯這個函數里的時候就加上security cookie的保護。
當然,這裡還有一些其它的很具體的規則,在msdn裡有更詳細的描述。

還有其它一些問題沒有在這裡說明,可以把這些問題留給大家
1.有對付security cookie的檢測的方法嗎? (答案是有的,但好像都不是很優美)
2.有關security異常的異常處理函數
3./safeSEH對 SEH處理的變化

沒有留言:

張貼留言