Inline Hook
@author: dlive
IAT Hook時如果要鉤取的API不在IAT中(LoadLibrary后調用),則無法使用該技術。而Inline Hook不存在這個限制。
0x01 Inline Hook原理
原理比較簡單,將API代碼的前5個字節修改為JMP xxxxxx 指令來鉤取API。調用執行被鉤取的API時,JMP XXXXXX指令被執行,轉而跳轉至Hook函數。
0x02 相關API
用戶模式下檢測進程的相關API通常分為如下兩類:
CreateToolhelp32Snapshot() 和 EnumProcess()
這兩個API均在其內部調用了ntdll.ZwQuerySystemInformation
ZwQuerySystemInformation()
該API可獲得運行中的所有進程信息(結構體),形成一個鏈表,操作該鏈表從鏈表中刪除相關進程即可達到隱藏的目的。
0x03 隱藏進程時可能遇到的問題
以上就是全局鉤取的概念
注意:鑒于系統安全性考慮,系統進程禁止進行注入操作
0x04 代碼分析
1. HideProc.cpp
InjectAllProcess
向所有進程注入/卸載DLL
BOOL InjectAllProcess(int nMode, LPCTSTR szDllPath) {DWORD dwPID = 0;HANDLE hSnapShot = INVALID_HANDLE_VALUE;PROCESSENTRY32 pe;// Get the snapshot of the systempe.dwSize = sizeof( PROCESSENTRY32 );hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL );// find processProcess32First(hSnapShot, &pe);do{dwPID = pe.th32ProcessID;//鑒于系統安全性考慮,對于PID小于100的系統進程,不執行DLL諸如操作if( dwPID < 100 )continue;if( nMode == INJECTION_MODE )InjectDll(dwPID, szDllPath);elseEjectDll(dwPID, szDllPath);}while( Process32Next(hSnapShot, &pe) );CloseHandle(hSnapShot);return TRUE; }2. stealth.cpp
實際API的鉤取由stealth.dll負責
2.1 SetProcName
// global variable (in sharing memory) #pragma comment(linker, "/SECTION:.SHARE,RWS") #pragma data_seg(".SHARE")TCHAR g_szProcName[MAX_PATH] = {0,}; #pragma data_seg()#pragma data_seg(".SHARE")一般用于DLL中,在DLL中定義一個共享的,有名字的節區。最關鍵的是:這個節區中的全局變量可以被多個進程共享。?這里將建立的節區命名為.SHARE。您可以將這段命名為任何一個您喜歡的名字。在這里的#pragma敘述之后的所有初始化了的變量都放在.SHARE節區中。?
#pragma data_seg()敘述標示段的結束。對變量進行專門的初始化是很重要的,否則編譯器將把它們放在普通的未初始化數據段(.bss)中而不是放在shared中。?
連結器必須知道有一個「shared」共享節區:?#pragma comment(linker,"/SECTION:shared,RWS") 字母RWS表示節區具有讀、寫和共享屬性。?
// ------------------------------ 我是一個題外話 -------------------------------------- //說個題外話,通過共享節區變量可以做到防多開(騰訊游戲安全競賽的時候曾經用過這個方法) //DLL加載時count++, 卸載時count-- //demo代碼如下,只是demo #pragma data_seg("flag_data") int count=0; #pragma data_seg() #pragma comment(linker,"/SECTION:flag_data,RWS") if(count>1) { MessageBox("已經啟動了一個應用程序","Warning",MB_OK); return FLASE; } count++; // ------------------------------ 我是一個題外話 -------------------------------------- #ifdef __cplusplus extern "C" { #endif __declspec(dllexport) void SetProcName(LPCTSTR szProcName) {//將steach.dll字符串存放在內存共享節區_tcscpy_s(g_szProcName, szProcName); } #ifdef __cplusplus } #endif2.2 DllMain
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {char szCurProc[MAX_PATH] = {0,};char *p = NULL;// #1. 判斷進程名稱,不向HideProc.exe注入dllGetModuleFileNameA(NULL, szCurProc, MAX_PATH);p = strrchr(szCurProc, '\\');if( (p != NULL) && !_stricmp(p+1, "HideProc.exe") )return TRUE;switch( fdwReason ){// #2. API Hookingcase DLL_PROCESS_ATTACH : hook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION, (PROC)NewZwQuerySystemInformation, g_pOrgBytes);break;// #3. API Unhooking case DLL_PROCESS_DETACH :unhook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION, g_pOrgBytes);break;}return TRUE; }2.3 hook_by_code
BOOL hook_by_code(LPCSTR szDllName, LPCSTR szFuncName, PROC pfnNew, PBYTE pOrgBytes) {FARPROC pfnOrg;DWORD dwOldProtect, dwAddress;BYTE pBuf[5] = {0xE9, 0, };PBYTE pByte;// 獲取要鉤取的API地址pfnOrg = (FARPROC)GetProcAddress(GetModuleHandleA(szDllName), szFuncName);pByte = (PBYTE)pfnOrg;// 若已經被鉤取,則返回Falseif( pByte[0] == 0xE9 )return FALSE;// 修改內存屬性VirtualProtect((LPVOID)pfnOrg, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);// 備份原有的5字節memcpy(pOrgBytes, pfnOrg, 5);// JMP (E9 XXXX)// => XXXX = pfnNew - pfnOrg - 5dwAddress = (DWORD)pfnNew - (DWORD)pfnOrg - 5;memcpy(&pBuf[1], &dwAddress, 4);// Hook - 5 byte (JMP XXXX)memcpy(pfnOrg, pBuf, 5);// 恢復內存屬性VirtualProtect((LPVOID)pfnOrg, 5, dwOldProtect, &dwOldProtect);return TRUE; }JMP目的地址的計算,xxxxxxxx為相對地址
長跳轉 E9 xxxxxxxx xxxxxxxx = 要跳轉的絕對地址 - 當前指令絕對地址 - 當前指令長度(5)還有另外的一些跳轉指令
短跳轉 short jmp EB xx (指令長度為兩字節, xx為相對地址)跳轉到絕對地址 push xxxxxxxx; ret 68 xxxxxxxx c3跳轉到絕對地址 move eax, xxxxxxxx jmp eax B8 xxxxxxxx FFE02.4 unhook_by_code
BOOL unhook_by_code(LPCSTR szDllName, LPCSTR szFuncName, PBYTE pOrgBytes) {FARPROC pFunc;DWORD dwOldProtect;PBYTE pByte;// 獲取API地址pFunc = GetProcAddress(GetModuleHandleA(szDllName), szFuncName);pByte = (PBYTE)pFunc;// 判斷API 代碼是否被修改過if( pByte[0] != 0xE9 )return FALSE;// 將API開始5Byte內存屬性修改為RWXVirtualProtect((LPVOID)pFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);// Unhook,將原始的5Byte數據修改回來memcpy(pFunc, pOrgBytes, 5);// 恢復內存屬性VirtualProtect((LPVOID)pFunc, 5, dwOldProtect, &dwOldProtect);return TRUE; }2.5 NewZwQuerySystemInformation
Zw開頭的函數和Nt開頭的函數的區別:
在RING3下,應用程序調用NT或者ZW效果是一樣的, 在dll中執行的是同一段代碼。
在RING0下,調用NT函數跟ZW函數就不一樣了,ZW開頭的函數是通過eax中系統服務號去SSDT中查找相應的系統服務,然后調用之。
若在驅動中直接調用NT開頭的函數是不會經過SSDT的 也不會被SSDT HOOK攔截的。
即:在R0下通過調用NT系列函數可以繞過SSDT HOOK 。微軟推薦使用Zw開頭的函數
ZwQuerySystemInformation微軟官方手冊(對各個字段解釋不完整)
https://msdn.microsoft.com/en-us/library/windows/desktop/ms725506(v=vs.85).aspx
#define STATUS_SUCCESS (0x00000000L) typedef LONG NTSTATUS;//將SYSTEM_INFORMATION_CLASS設置為SystemProcessInformation(5)后調用ZwQuerySystemInformation API typedef enum _SYSTEM_INFORMATION_CLASS {SystemBasicInformation = 0,SystemPerformanceInformation = 2,SystemTimeOfDayInformation = 3,SystemProcessInformation = 5,SystemProcessorPerformanceInformation = 8,SystemInterruptInformation = 23,SystemExceptionInformation = 33,SystemRegistryQuotaInformation = 37,SystemLookasideInformation = 45 } SYSTEM_INFORMATION_CLASS;//API獲得的信息為SYSTEM_PROCESS_INFORMATION結構體組成的單向鏈表的起始地址,該結構體重存儲著運行中所有進程的信息 typedef struct _SYSTEM_PROCESS_INFORMATION {ULONG NextEntryOffset; //鏈表中下一個結構體相對于當前結構體的偏移量ULONG NumberOfThreads; //線程數目;BYTE Reserved1[48];PVOID Reserved2[3]; //Reserved2[1]為進程名稱,進程名稱為Unicode字符串HANDLE UniqueProcessId;PVOID Reserved3;ULONG HandleCount;BYTE Reserved4[4];PVOID Reserved5[11];SIZE_T PeakPagefileUsage;SIZE_T PrivatePageCount;LARGE_INTEGER Reserved6[6]; } SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION; typedef NTSTATUS (WINAPI *PFZWQUERYSYSTEMINFORMATION)(SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, //參數單向鏈表的起始地址ULONG SystemInformationLength, PULONG ReturnLength);#define DEF_NTDLL ("ntdll.dll") #define DEF_ZWQUERYSYSTEMINFORMATION ("ZwQuerySystemInformation") //NTSTATUS 是被定義為32位的無符號長整型。在驅動程序開發中,人們習慣用 NTSTATUS 返回狀態。 NTSTATUS WINAPI NewZwQuerySystemInformation(SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength) {NTSTATUS status;FARPROC pFunc;PSYSTEM_PROCESS_INFORMATION pCur, pPrev;char szProcName[MAX_PATH] = {0,};// 首先脫鉤unhook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION, g_pOrgBytes);// 調用原始APIpFunc = GetProcAddress(GetModuleHandleA(DEF_NTDLL), DEF_ZWQUERYSYSTEMINFORMATION);status = ((PFZWQUERYSYSTEMINFORMATION)pFunc)(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);if( status != STATUS_SUCCESS )goto __NTQUERYSYSTEMINFORMATION_END;// 判斷調用API時是否為要鉤取的調用方式(SystemProcessInformation(5))if( SystemInformationClass == SystemProcessInformation ){// pCur 存儲單鏈表的首地址pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;while(TRUE){// 比較進程名稱// g_szProcName為要隱藏的進程if(pCur->Reserved2[1] != NULL){if(!_tcsicmp((PWSTR)pCur->Reserved2[1], g_szProcName)){// 刪除鏈表節點if(pCur->NextEntryOffset == 0)pPrev->NextEntryOffset = 0;elsepPrev->NextEntryOffset += pCur->NextEntryOffset;}else pPrev = pCur;}//遍歷結束if(pCur->NextEntryOffset == 0)break;// 相當于p=p->nextpCur = (PSYSTEM_PROCESS_INFORMATION)((ULONG)pCur + pCur->NextEntryOffset);}}__NTQUERYSYSTEMINFORMATION_END:// 重新Hook APIhook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION, (PROC)NewZwQuerySystemInformation, g_pOrgBytes);return status; }0x05 全局API鉤取
全局API鉤取技術針對的進程:
前面的示例程序并不是全局API鉤取的例子,因為它并不滿足全局API鉤取定義中的第二個條件
1.相關API
//Kernel32.CreateProcess BOOL WINAPI CreateProcess(_In_opt_ LPCTSTR lpApplicationName,_Inout_opt_ LPTSTR lpCommandLine,_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,_In_ BOOL bInheritHandles,_In_ DWORD dwCreationFlags,_In_opt_ LPVOID lpEnvironment,_In_opt_ LPCTSTR lpCurrentDirectory,_In_ LPSTARTUPINFO lpStartupInfo,_Out_ LPPROCESS_INFORMATION lpProcessInformation );該API用于創建新進程,其他啟動運行進程的API(WinExec, ShellExecute, system)在其內部調用的都是該函數
WinExec:
https://msdn.microsoft.com/en-us/library/ms687393(v=vs.85).aspx
可以直接鉤取父進程(通常是explorer.exe)的CreateProcess API來監控子進程創建,從而達到鉤取子進程的目的。
但是這種方法存在一些問題,且不說需要同時鉤取CreateProcessA, CreateProcessW,CreateProcessInternalA, CreateProcessInternalW, 還存在在NewCreateProcess(攻擊者自定義的Hook函數)調用CreateProcess創建子進程時,極短時間內,子進程可能在未鉤取的狀態下運行。
//Ntdll.ZwResumeThread ZwResumeThread(IN HANDLE ThreadHandle,OUT PULONG SuspendCount OPTIONAL )該API也是一個未公開API,它是比CreateProcess更低級的API,它在進程創建后,主線程運行前被調用執行。所以只要鉤取這個函數即可在不運行子進程代碼的情況下鉤取API。
但由于其為未公開API,隨著OS的升級,API可能失效。
2.代碼分析
stealth2.cpp中增加了對CreateProcessA和CreateProcessW的Hook操作,并對新創建的進程進行dll注入d
有NewCreateProcessA為例:
BOOL WINAPI NewCreateProcessA(LPCTSTR lpApplicationName,LPTSTR lpCommandLine,LPSECURITY_ATTRIBUTES lpProcessAttributes,LPSECURITY_ATTRIBUTES lpThreadAttributes,BOOL bInheritHandles,DWORD dwCreationFlags,LPVOID lpEnvironment,LPCTSTR lpCurrentDirectory,LPSTARTUPINFO lpStartupInfo,LPPROCESS_INFORMATION lpProcessInformation ) {BOOL bRet;FARPROC pFunc;// unhookunhook_by_code("kernel32.dll", "CreateProcessA", g_pOrgCPA);pFunc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateProcessA");bRet = ((PFCREATEPROCESSA)pFunc)(lpApplicationName,lpCommandLine,lpProcessAttributes,lpThreadAttributes,bInheritHandles,dwCreationFlags,lpEnvironment,lpCurrentDirectory,lpStartupInfo,lpProcessInformation);// 如果新進程創建成果,將stealth.dll注入到新進程中if( bRet )InjectDll2(lpProcessInformation->hProcess, STR_MODULE_NAME);// hookhook_by_code("kernel32.dll", "CreateProcessA", (PROC)NewCreateProcessA, g_pOrgCPA);return bRet; }0x06 利用熱補丁技術鉤取API
前面demo程序中通過代碼修改技術進行API Hook存在的缺點
1.熱補丁(修改7Byte代碼)
Windows系統庫中的函數,如kernel32.CreateProcessA/W, user32.MessageBoxA,gdi32.TextOutW有一個相似點
微軟做此設計的目的就是方便打熱補丁。
使用熱補丁Hook API的過程如下
2.代碼分析
2.1 DllMain
和之前代碼一樣,在Dll附加進程的時候調用hook方法,在dll卸載時調用unhook方法
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {char szCurProc[MAX_PATH] = {0,};char *p = NULL;// 判斷進程名稱,不向HideProc2.exe注入dllGetModuleFileNameA(NULL, szCurProc, MAX_PATH);p = strrchr(szCurProc, '\\');if( (p != NULL) && !_stricmp(p+1, "HideProc2.exe") )return TRUE;// change privilegeSetPrivilege(SE_DEBUG_NAME, TRUE);switch( fdwReason ){case DLL_PROCESS_ATTACH : // hookhook_by_hotpatch("kernel32.dll", "CreateProcessA", (PROC)NewCreateProcessA);hook_by_hotpatch("kernel32.dll", "CreateProcessW", (PROC)NewCreateProcessW);//可以看到這里沒有對ZwQuerySystemInformation使用熱補丁Hook,具體原因見"3.熱補丁Hook的缺點"hook_by_code("ntdll.dll", "ZwQuerySystemInformation", (PROC)NewZwQuerySystemInformation, g_pOrgZwQSI);break;case DLL_PROCESS_DETACH :// unhookunhook_by_hotpatch("kernel32.dll", "CreateProcessA");unhook_by_hotpatch("kernel32.dll", "CreateProcessW");unhook_by_code("ntdll.dll", "ZwQuerySystemInformation", g_pOrgZwQSI);break;}return TRUE; }2.2 hook_by_hotpatch
修改API中無用的7字節數據,跳轉到用戶自定義Hook函數
BOOL hook_by_hotpatch(LPCSTR szDllName, LPCSTR szFuncName, PROC pfnNew) {FARPROC pFunc;DWORD dwOldProtect, dwAddress;BYTE pBuf[5] = { 0xE9, 0, };BYTE pBuf2[2] = { 0xEB, 0xF9 };PBYTE pByte;pFunc = (FARPROC)GetProcAddress(GetModuleHandleA(szDllName), szFuncName);pByte = (PBYTE)pFunc;if( pByte[0] == 0xEB )return FALSE;VirtualProtect((LPVOID)((DWORD)pFunc - 5), 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);// 1. NOP (0x90)dwAddress = (DWORD)pfnNew - (DWORD)pFunc;memcpy(&pBuf[1], &dwAddress, 4);memcpy((LPVOID)((DWORD)pFunc - 5), pBuf, 5);// 2. MOV EDI, EDI (0x8BFF)memcpy(pFunc, pBuf2, 2);VirtualProtect((LPVOID)((DWORD)pFunc - 5), 7, dwOldProtect, &dwOldProtect);return TRUE; }2.3 unhook_by_hotpatch
將7字節數據修改為原數據
BOOL unhook_by_hotpatch(LPCSTR szDllName, LPCSTR szFuncName) {FARPROC pFunc;DWORD dwOldProtect;PBYTE pByte;BYTE pBuf[5] = { 0x90, 0x90, 0x90, 0x90, 0x90 };BYTE pBuf2[2] = { 0x8B, 0xFF };pFunc = (FARPROC)GetProcAddress(GetModuleHandleA(szDllName), szFuncName);pByte = (PBYTE)pFunc;if( pByte[0] != 0xEB )return FALSE;VirtualProtect((LPVOID)pFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);// 1. NOP (0x90)memcpy((LPVOID)((DWORD)pFunc - 5), pBuf, 5);// 2. MOV EDI, EDI (0x8BFF)memcpy(pFunc, pBuf2, 2);VirtualProtect((LPVOID)pFunc, 5, dwOldProtect, &dwOldProtect);return TRUE; }2.4 NewCreateProcess
這里需要注意一點,在使用之前的hook方法進行API鉤取時,在用戶自定義NewCreateProcess函數開頭需要調用unhook方法(防止進入鉤取的死循環),函數結尾需要調用hook方法進行重新鉤取。
但是使用熱補丁Hook時不需要這樣反復脫鉤,掛鉤,只需在調用原始API時,使用address of API + 2的地址調用API即可
BOOL WINAPI NewCreateProcessA(LPCTSTR lpApplicationName,LPTSTR lpCommandLine,LPSECURITY_ATTRIBUTES lpProcessAttributes,LPSECURITY_ATTRIBUTES lpThreadAttributes,BOOL bInheritHandles,DWORD dwCreationFlags,LPVOID lpEnvironment,LPCTSTR lpCurrentDirectory,LPSTARTUPINFO lpStartupInfo,LPPROCESS_INFORMATION lpProcessInformation ) {BOOL bRet;FARPROC pFunc;pFunc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "CreateProcessA");//以API地址+2為函數地址調用CreateProcessA函數pFunc = (FARPROC)((DWORD)pFunc + 2);bRet = ((PFCREATEPROCESSA)pFunc)(lpApplicationName,lpCommandLine,lpProcessAttributes,lpThreadAttributes,bInheritHandles,dwCreationFlags,lpEnvironment,lpCurrentDirectory,lpStartupInfo,lpProcessInformation);// 注入steach3.dllif( bRet )InjectDll2(lpProcessInformation->hProcess, STR_MODULE_NAME);return bRet; }3.使用熱補丁進行Hook的缺點
熱補丁鉤取技術有個明顯的缺點,即不符合鉤取條件(7字節無用代碼)的API無法使用熱補丁鉤取
這樣的API有,ntdll.dll提供的API 和kernel32.GetStartInfoA() 等
并非所有API都能使用熱補丁鉤取,所以使用前需要先確認要鉤取的API是否支持,若不支持則需使用前面的5字節代碼修改技術
0x07 Ntdll.dll API鉤取技巧
Ntdll.dll中提供的API代碼都較短,鉤取這些API時有一種非常好的方法,使用這種方法時先將原API備份到用戶內存區域,然后使用5字節代碼修改技術修改原API的起始部分。在用戶鉤取函數內部調用API時,只需調用備份的API即可,這樣實現的API鉤取既簡單又穩定。由于Ntdll.all API代碼較短,且代碼內部地址無依賴性,所以它們非常適合用該技術鉤取。
0x08 一些問題
Dll在內存中只會加載一次,那么在Hook時對Dll進行修改后豈不是會影響所有使用該dll的進程?
主要是Copy-On-Write機制,a.dll在內存中只有一份拷貝,增加的是dll的引用計數,當dll的引用計數為0時,系統會回收對應內存和刪除對應的映射。
如果有一個程序對dll的內存進行了相應的修改,比如說修改內存加載鉤子,那么系統會將對應頁面復制一份出來給程序,此時,兩個程序指向的dll的對應內存就不同了。
http://bbs.csdn.net/topics/330026043
使用HideProc注入時注入失敗
Windows 7/Vista中使用了會話隔離技術,可能導致Dll注入失敗。出現這個問題時,不要使用kernel32.CreateRemoteThread而使用ntdll.NtCreateThreadEx就可以了。
此外,嘗試向PE32+格式的進程注入PE32格式的DLL時也會失敗(反之亦然)。注入時必須保證注入的DLL文件和目標進程PE格式一致。
轉載于:https://www.cnblogs.com/dliv3/p/6398967.html
總結
以上是生活随笔為你收集整理的Inline Hook的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VUE2.0开发环境安装
- 下一篇: Visual studio C++ MF