有關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處理的變化程序員