EXP編寫學習之繞過SafeSEH

SafeSEH對異常處理的保護原理:
編譯選項/SafeSEH啟動,VS2003以后默認啟用。
生成SafeSEH表,放在PE文件中,調用異常處理函數的時候,將地址與SafeSEH表中的地址比較。
檢查異常處理鏈是否位于當前程序的棧中,如果不在棧中,則程序終止異常處理函數的調用。
檢查異常處理函數指針是否在程序的棧中,如果指向當前棧中,則終止異常處理函數的調用。
前面兩項檢查都通過后,程序調用一個全新的函數 RtlIsValidHandler ,對異常處理函數的有效性進行驗證。
RtlIsValidHandler檢測原理:
首先,該函數判斷異常處理函數地址是不是在加載模塊的內存空間,如果屬于加載模塊的內存空間,校驗函數將依次進行如下校驗:
(1)判斷程序是否設置了IMAGE_DLLCHARACTERISTICS_NO_SEH 標識。如果設置了這個標識,這個程序內的異常會被忽略。所以當這個標志被設置時,函數直接返回校驗失敗。
(2)檢測程序是否包含安全S.E.H 表。如果程序包含安全S.E.H 表,則將當前的異常處理函數地址與該表進行匹配,匹配成功則返回校驗成功,匹配失敗則返回校驗失敗。
(3)判斷程序是否設置ILonly 標識。如果設置了這個標識,說明該程序只包含.NET 編譯人中間語言,函數直接返回校驗失敗。
(4)判斷異常處理函數地址是否位于不可執行頁(non-executable page)上。當異常處理函數地址位于不可執行頁上時,校驗函數將檢測DEP 是否開啟,如果系統未開啟DEP 則返回校驗成功,否則程序拋出訪問違例的異常。
如果異常處理函數的地址沒有包含在加載模塊的內存空間,校驗函數將直接進行DEP 相關檢測,函數依次進行如下校驗:
(1)判斷異常處理函數地址是否位于不可執行頁(non-executable page)上。當異常處理函數地址位于不可執行頁上時,校驗函數將檢測DEP 是否開啟,如果系統未開啟DEP 則返回校驗成功,否則程序拋出訪問違例的異常。
(2)判斷系統是否允許跳轉到加載模塊的內存空間外執行,如果允許則返回校驗成功,否則返回校驗失敗。

RtlIsValidHandler允許異常函數執行的情況
1)異常處理函數位于加載模塊內存范圍之外,DEP 關閉。
2)異常處理函數位于加載模塊內存范圍之內,相應模塊未啟用SafeSEH(安全S.E.H 表為空),同時相應模塊不是純IL。
3)異常處理函數位于加載模塊內存范圍之內,相應模塊啟用SafeSEH(安全S.E.H 表不為空),異常處理函數地址包含在安全SEH表中。
分析一下這三種情況的可行性。
(1)現在我們只考慮SafeSEH,不考慮DEP。排除DEP 干擾后,我們只需在加載模塊內存范圍之外找到一個跳板指令就可以轉入shellcode 執行,這點還是比較容易實現的。
(2)在第二種情況中,我們可以利用未啟用SafeSEH 模塊中的指令作為跳板,轉入shellcode執行,這也是為什么我們說SafeSEH 需要操作系統與編譯器的雙重支持。在加載模塊中找到一個未啟用的SafeSEH 模塊也不是一件很困難的事情。
(3)這種情況下我們有兩種思路可以考慮,一是清空安全S.E.H 表,造成該模塊未啟用SafeSEH 的假象;二是將我們的指令注冊到安全S.E.H 表中。由于安全S.E.H 表的信息在內存中是加密存放的,所以突破它的可能性也不大,這條路我們就先放棄吧。
利用SafeSEH的缺陷
利用S.E.H 的終極特權!這種安全校驗存在一個嚴重的缺陷——如果S.E.H 中的異常函數指針指向堆區,即使安全校驗發現了S.E.H 已經不可信,仍然會調用其已被修改過的異常處理函數,因此只要將shellcode 布置到堆區就可以直接跳轉執行!
繞過SafeSEH
1.攻擊返回地址繞過
2.虛函數繞過
3.從堆中繞過 :shellcode布置在堆中 ,SEH處理函數指向這個地址即可
4.利用未啟用SafeSEH模塊繞過 :可以把這個模塊的指令作為跳板,去執行shellcode
5.加載模塊之外的地址繞過 :內存中有一些Map類型的映射文件,在這些文件中找到跳板指令覆蓋SEH處理函數地址即可繞過
6.利用未啟用SafeSEH的控件,且控件包含溢出漏洞可以被觸發(IE瀏覽器控件)
實踐利用加載模塊之外的地址
1.我們使用上一篇中的代碼,稍微修改來測試,關閉GS DEP ASLR, 開啟 SafeSEH ,如果你有VC6 ,最好使用它來編譯。
#include
#include
int zero = 0;
int MyException()
{
printf("Error OverFlow %d", zero);
return 1;
}
void __stdcall test(char* str, char* out)
{
char buf[0x500] = { 0 };
__try
{
strcpy(buf, str);
zero = 1 / zero;
}
__except (MyException())
{
}
}
int main(int arc, char** argv)
{
char buf1[200];
test(argv[1], buf1);
return 0;
}
2.先用IDA查看一下代碼,因為我用VS2019編譯, 編譯器會擴展SEH。

可以看到, 這里使用了第3代的異常處理模型 ,往棧中放入了不少東西,會影響我們的偏移。
用od插件搜索一下,都開啟了SafeSEH保護。

3.調試一下看看,可以看到,輸入0x500個字節的A后, 還差12個字節才可以覆蓋到Handler。

修改參數 ,再次調試查看。

好的,現在可以看到,Handler已經被覆蓋為 C , 那么現在需要找到跳板地址來跳到shellcode。
之前已經看過,所有模塊都啟用了SafeSEH,那么我們需要找到加載模塊之外的跳板地址,內存映射查看 MAP類型的地址。
那么我們需要什么樣的跳板指令呢?

觀察寄存器,發現eax指向我們溢出的緩沖區,那么是否可以利用 jmp eax , call eax,來跳轉到shellcode執行(答案是不行,eax是一個易失寄存器,在轉到異常處理函數的過程中會被修改)。
好的, Next先不管,Handler需要什么樣的跳板指令呢,按照之前利用SEH的總結, 我們需要 pop pop ret指令。
隨便填寫一個地址測試是否成功轉到該地址 ,我們在MAP類型內存映射中,找到了 0x7FFA5BE8地址,7FFA2017 它的指令是 jmp eax。


好的,修改Handler為這個地址, 看一下是否可以轉到這個地址執行 ,答案是可以,但是無法下斷跟蹤(且提示訪問0地址)。
之后我又選擇了一個 pop ret指令的地址, 沒辦法,只能找到這個指令了,推算一下, 也就是 jmp [esp+4]。
根據微軟的解釋 EstablisherFrame 是此函數的固定堆棧分配的基地址 ,也就是我們得到的地址是 系統設置的異常處理函數的ebp(好吧,日后詳細研究一下)。

好的好的,可以看到程序已經轉到棧上執行,如果有合適的跳板指令可以利用(沒辦法了,我使用win10進行測試)。
結語
1.可以看到限制我們進行漏洞利用的因素有很多,我們不得不研究新的手段來對抗微軟的保護機制。
2.經過測試,如果你不是使用加載模塊地址之外的地址,確實會與safeSEH表來進行對比,會提示異常 無效的異常處理程序。
3.經過這次實踐,碰到了各種各樣的問題,此時才能理解前人的智慧,不得不佩服。