1. 程式人生 > >___security_cookie機制,防止棧溢位

___security_cookie機制,防止棧溢位

有關security cookie在棧保護上的研究

06.10 by flyingkisser

這裡主要討論棧,不是堆。
首先,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處理的變化