逆向工程核心原理-第33章-隐藏进程
回顾 中间有一周没有看这本书了前面的知识有些忘了, 所以在这里先把前面的东西缕一缕(copy)
调试器钩取API
使用DebugActiveProcess()建立起调试器被调试者的关系
通过DLL共享虚拟内存地址相同的特性获取到目标API的起始地址
修改该API的起始第一个字节为0xCC, 即INT3断点指令机器码
我们在调用该API时, 就会遇上这个断点指令从而将控制权交给了调试器
在调试器中通过ReadProcessMemory()和WriteProcessMemory()实现调试器和被调试者之间的数据交互, 从而达到修改数据的操作
然后脱钩, 即将0xCC改回原来的内容(通过全局变量来实现), 并将EIP减一回到断点前, 如果不脱勾的话, 当控制流从调试器转回被调试者时又会遇到INT3指令再次回到调试器钩取, 陷入死循环.
再根据需要决定是否要继续挂钩
通过DLL修改IAT钩取API 还是要重复: DLL注入之所以能过如此灵活的使用于多个场景, 重要的不是在DllMain中做了什么操作, 而是在于它可以让我们可以轻易进入一个目标的虚拟空间, 并对其进行读写.
首先时Inject程序将我们的DLL注入到目标进程中
DllMain获取了我们目标API的地址(便于后面的IAT替换和恢复), 同时也轻易得到了我们的钩取函数MyFun()的地址
随后进入的IAT修改函数 通过进程句柄的转换得到了进程的基址, 然后通过对PE头的结构体信息利用找到了IDT(导入表, 描述的是导入的全部DLL), 然后通过DLL的名称遍历筛选找到了目标API所在的DLL
同样通过结构体IID的成员信息找到了对应的IAT, 通过函数名称遍历筛选找到了我们的目标API在IAT表中的对应的ITD结构体, 并修改其内容: 将其中描述函数起始地址的成员修改为我们的钩取函数MyFun()的地址
当我们决定脱钩的时候仍然调用的是IAT修改函数 , 经过同样的过程将原来的API地址恢复到IAT中
33.1 技术图表
我们所用到的技术仍然是DLL注入, 其作用在上面的回顾和上一章(好像)都提到过
33.2 API代码修改技术的原理 跟上一章的修改IAT技术实现钩取有相似之处, 所以理解起来会相对的简单, 但是也要注意区分二者的区别
IAT通过修改IAT值来实现API钩取
API代码修改技术则是通过将API前5个字符修改为JMP XXXXX来钩取API(这又有点像是调试钩取API技术了, 只不过调试是只修改一个字节为0xCC), 从而将控制转移给钩取函数.
33.2.1 钩取之前 正常调用API
钩取后
这里看起来可能有点绕, 我们分步分析
00422CF7调用了48C69C地址上的内容 作为调用地址, 48C69C的内容是7C93D92E, 也就是API
控制流来到了7C93D92E, 而API的开头内容就是JMP 10001120, 所以我们不会执行API的内容, 而是跳至目标地址
跳转至10001120 这个地址就是我们的Hooking函数, 在这里我们先使用脱钩函数(这里的目的跟前面调试钩取的脱钩思路是一样的)
在Hooking函数中我们先调用unhook()脱钩, 因为后面还要调用到目标函数, 如果不脱勾, API开始的内容又是JMP变成了类似无穷递归的操作(这跟上面调试脱钩是一样的道理)
然后在对数据进行了一系列我们想要的操作以后, 我们调用原来的API实现其功能
随后调用hook()重新挂钩
最后我们使用show参数取消挂钩, 然后在DllMain中使用另一个标签直接脱钩.
33.3 进程隐藏 进程隐藏最常用的ntdll.ZwQuerySystemInformation()API钩取技术
33.3.1 进程隐藏工作原理 想要隐藏进程首先想到的方法是让目标进程隐身, 但就像是我们现实生活中一样, 隐身是不可能实现的. 所以我们用相反的方法实现隐藏进程, 那就是让其他的进程都变成瞎子, 所有的进程都看不见我们的目标进程, 那它也就相当于隐身了.
33.3.2 相关API 进程间要检测其存在, 需要调用相关API, 这种API相当于其他进程的”眼睛”
1 2 3 4 5 6 7 8 9 10 11 12 HANDLE WINAPI CreateToolhelp32Snapshot ( DWORD dwFlags, DWORD th32ProcessID ) ;BOOL EnumProcesses ( DWORD* pProcessIds, DWORD cb, DWORD* pBytesReturned )
上面的两个APi均在内部调用了ntdll.ZwQuerySystemInformation()API
1 2 3 4 5 6 7 NTSTATUS ZwQuerySystemInformation ( SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength )
ZwQuerySystemInformation()所获取的是进程信息的链表, 所以我们要做的就是从除目标函数意外的所有函数中钩取ZwQuerySystemInformation(), 在获取的链表中删去关于目标函数的节点. 这样我们刺瞎了其他进程的眼睛(当然, 有点不恰当, 因为其他进程只是看不见目标进程, 其他的进程仍然可以看到)
33.3.3 隐藏技术的问题 问题一: 要钩取的进程个数 能看到我们目标进程不只有检索进程的那些查找工具, 任何函数都可以调用ZwQuerySystemInformation()来实现查看进程
问题二: 新创建的进程 如果用户再运行一个任务管理器, 如果没有被钩取的话, 我们隐藏进程的目的仍然没有达到.
解决方法: 全局钩取 为了解决上面的问题, 我们隐藏test.exe进程时需要钩取系统中运行的所有进程的ZwQuerySystemInformation()API, 并且对后面将要启动运行的所有进程进行相同的操作. 这就是全局钩取的概念.
33.4 练习 #1 (HideProc.exe, stealth.dll)
HideProc.exe负责将stealth.dll文件注入到所有运行中的进程.
stealth.dll负责钩取进程的ntdll.ZwQuerySystemInformation()
33.4.1 运行notepad.exe, procexp.exe, taskmgr.exe 首先将HideProc.exe跟stealth.dll放入同一个文件夹
随后打开notepad.exe文件
使用procxp查看进程
此时procxp是可以检测到notepad的
然后我们开始钩取API
再次使用procxp查看进程
此时procxp不能检测到notepad
我们查看有哪些进程是被注入了stealth.dll的
可以看到我们用来检索notepad的procxp被注入了stealth.dll, 所以才没有检索到notepad进程
接下来我们停止隐藏进程
再次使用procxo查看进程
此时notepad再一次出现了
33.5 源代码分析 33.5.1 HideProc.cpp HideProc.exe负责向运行中的所有进程注入/卸载指定DLL文件, 它再原有InjectDll.exe的基础上添加了向所有进程注入DLL的功能, 可以认为是InjectDll.exe程序的加强版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #define _CRT_SECURE_NO_WARNINGS #include <Windows.h> #include <tlhelp32.h> BOOL InjectAllProcess (int nMode, LPCTSTR szDllPath) { DWORD dwPID = 0 ; HANDLE hSnapShot = INVALID_HANDLE_VALUE; PROCESSENTRY32 pe; pe.dwSize = sizeof (PROCESSENTRY32); hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL ); Process32First(hSnapShot, &pe); do { dwPID = pe.th32ProcessID; if (dwPID < 100 ) continue ; if (nMode == INJECTION_MODE) InjectDll(dwPID, szDllPath); else EjectDll(dwPID, szDllPath); } while (Process32Next(hSnapShot, &pe)); CloseHandle(hSnapShot); return TRUE; }
这个函数是对全部(除了特殊的进程)进程注入或者是卸载stealth.dll
我们来到下一个源码分析
33.5.2 stealth.cpp 实际的API钩取操作由Stealth.dll文件负责
SetProcName()导出函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #pragma comment (linker, "/SECTION:.SHARE,RWS" ) #pragma data_seg(".SHARE" ) TCHAR g_szProcName[MAX_PATH] = { 0 }; #pragma data_seg() #ifdef __cplusplus extern "C" {#endif __declspec(dllexport) void SetProcName (LPCTSTR szProcName) { _tcscpy_s(g_szProcName, szProcName); } #ifdef __cplusplus } #endif
下面我们看DllMain函数
DllMain 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 BOOL APIENTRY DllMain ( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { char szCurProc[MAX_PATH] = { 0 , }; char * p = NULL ; GetModuleFileNameA(NULL , szCurProc, MAX_PATH); p = strrchr (szCurProc, '\\' ); if ((p != NULL ) && !_stricmp(p + 1 , "HideProc.exe" )) return TRUE; switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: hook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION, (PROC)NewZwQuerySystemInformation, g_pOrgBytes); break ; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: unhook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION, g_pOrgBytes); break ; } return TRUE; }
可以看到在进入了DllMain后还会通过进程文件名来对进程进行一次筛选, 如果是HideProc.exe注入程序本身就会跳过注入.
ATTCH附加则调用hook_by_code实现API钩取, DETACH脱离则调用unhook_by_code()实现API脱钩
下面是对两个函数进行解析
hook_by_code() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 BOOL hook_by_code (LPCSTR szDllName, LPCSTR szFuncName, PROC pfnNew, PBYTE pOrgBytes) { FARPROC pfnOrg; DWORD dwOldProtect, dwAddress; byte pBuf[5 ] = { 0xE9 , 0 , }; PBYTE pByte; pfnOrg = (FARPROC)GetProcAddress(GetModuleHandleA(szDllName), szFuncName); pByte = (PBYTE)pfnOrg; if (pByte[0 ] = 0xE9 ) return FALSE; VirtualProtect((LPVOID)pfnOrg, 5 , PAGE_EXECUTE_READWRITE, &dwOldProtect); memcpy (pOrgBytes, pfnOrg, 5 ); dwAddress = (DWORD)pfnNew - (DWORD)pfnOrg - 5 ; memcpy (&pBuf[1 ], &dwAddress, 4 ); memcpy (pfnOrg, pBuf, 5 ); VirtualProtect((LPVOID)pfnOrg, 5 , dwOldProtect, &dwOldProtect); return TRUE; }
大致思路
获取目标API首地址
查看是否已经注入
修改内存属性
计算JMP地址
挂钩(将JMP修改到API首地址)
恢复内存属性
unhook_by_code() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 BOOL unhook_by_code (LPCSTR szDllName, LPCSTR szFuncName, PBYTE pOrgBytes) { FARPROC pFunc; DWORD dwOldProtect; PBYTE pByte; pFunc = GetProcAddress(GetModuleHandleA(szDllName), szFuncName); pByte = (PBYTE)pFunc; if (pByte[0 ] != 0xE9 ) return FALSE; VirtualProtect((LPVOID)pFunc, 5 , PAGE_EXECUTE_READWRITE, &dwOldProtect); memcpy (pFunc, pOrgBytes, 5 ); VirtualProtect((LPVOID)pFunc, 5 , dwOldProtect, &dwOldProtect); return TRUE; }
unhook_by_code()跟hook_by_code几乎是一模一样
获取目标API首地址
查看是否有注入
修改内存属性
脱钩
恢复内存属性
最后, 分析钩取函数NewZwQuerySystemInformation(), 在这之前再次看看ntdll.ZwQuerySystemInformation()API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 NTSTATUS WINAPI ZwQuerySystemInformation ( __in SYSTEM_INFORMATION_CLASS SystemInformationClass, __inout PVOID SystemInformation, __in ULONG SystemInformationLength, __out_opt PULONG ReturnLength ) ;typedef struct _SYSTEM_PROCESS_INFORMATION { ULONG NextEntryOffset; ULONG NumberOfThreads; byte Reserved1[48 ]; PVOID Reserved2[3 ]; 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;
下面是我们的NewZwQuerySystemInformation()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 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); pFunc = GetProcAddress(GetModuleHandleA(DEF_NTDLL), DEF_ZWQUERYSYSTEMINFORMATION); status = ((PFZWQUERYSYSTEMINFORMATION)pFunc) (SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength); if (status != STATUS_SUCCESS) goto __NTQUERYSYSTEMINFORMATION_END; if (SystemInformationClass == SystemProcessInformation) { pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation; while (TRUE) { if (pCur->Reserved2[1 ] != NULL ) { if (!_tcsicmp((PWSTR)pCur->Reserved2[1 ], g_szProcName)) { if (pCur->NextEntryOffset == 0 ) pPrev->NextEntryOffset = 0 ; else pPrev->NextEntryOffset += pCur->NextEntryOffset; } else pPrev = pCur; } if (pCur->NextEntryOffset == 0 ) break ; pCur = (PSYSTEM_PROCESS_INFORMATION)((ULONG)pCur + pCur->NextEntryOffset); } } __NTQUERYSYSTEMINFORMATION_END: hook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION, (PROC)NewZwQuerySystemInformation, g_pOrgBytes); return status; }
大致流程:
首先脱钩, 为了后面能正常使用函数, 而不是无限递归
然后通过Loadxxx获取到了隐藏API的地址
通过地址正常 调用了API
获取进程信息链表
通过名称字符串对比找到了隐藏进程所在节点, 并删去, 这时我们的隐藏进程对于当前进程来说就是不存在的了
重新挂钩
我之前对于ZwQuerySystemInformation()的理解有点误区, 那就是我认为进程信息的链表是全局的, 也就是说只用需改一次就可以了, 但是实际上并不是, ZwQuerySystemInformation()的工作原理是获取当前的进程信息组成局部链表, 然后给其他的API使用, 如果下一次没有过去的话, ZwQuerySystemInformation()又会重新获取新的进程信息链表, 这时该链表中又有我们想要隐藏的进程信息了, 所以我们每次调用ZwQuerySystemInformation()时都需要重复钩取.
33.6 全局API钩取 全局钩取的对象有两个:
我们前面只完成了第一个目标, 接下来我们将在此基础上 完成第二个目标.
33.6.1 Kernel32.CreateProcess()API Kernel32.CreateProcess()API用来创建新进程.其他创建进程的API内部都调用了这个API
1 2 3 4 5 6 7 8 9 10 11 12 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 ) ;
因此, 向当前运行的所有进程注入stealth.dll后, 如果在stealth.dll中将CreateProcess()API也一起钩取, 那么以后运行的进程也会自动注入stealth.dll文件.
但是要考虑以下方面:
钩取CreateProcess()API时, 还要分别钩取CreateProcessA(), CreateProcessW()这两个API
CreateProcessA(), CreateProcessW()中又分别调用了CreateProcessInternalA(), CreateProcessInternalW()这两个函数.
钩取函数(NewCreateProcess)要钩取子进程的API. 因此, 极短时间内, 子进程可能在未钩取的状态下运行.
对于上面的问题, 有一个一次性解决的方法, 那就是钩取比CreateProcess()更低级的API
33.6.2 Ntdll.ZwResumeThread()API 1 2 3 4 ZwResumeThread( IN HANDLE ThreadHandle, OUT PULONG SuspendCount OPTIONAL )
ZwResumeThread()是在进程创建后, 主线程运行前被调用执行. 所以只用钩取这个函数, 即可在不运行子进程代码的状态下钩取API.
在读书的时候读到这里会有点晕, 首先对于子进程, 我们的目标是钩取子进程的API, 而钩取API所要做的事就是注入DLL, 而我们想要达成注入DLL到子进程中所要做的事是在父进程中钩取创建子进程的API, 然后在钩取函数中调用Loadxxx. 这样捋下来就是: 我们要在父进程中额外钩取API, 这一个钩取函数的行为是在子进程中再一次注入DLL钩取API, 同时子进程中也会发生同样的操作.
33.7 练习#2 (HideProc2.exe, Stealth2.dll) 33.7.1 复制stealth2.dll文件到%SYSTEM%文件夹中 为了把stealth2.dll文件注入所有运行进程, 首先要把stealth2.dll文件复制到%SYSTEM%文件夹
33.7.2 运行HideProc2.exe -hide
33.7.3 运行ProcExp.exe 这时再运行ProcExp看是否是全局钩取
可以看到即使是钩取后的进程也依然被钩取了
33.7.4 运行HideProc2.exe -show
在取消钩取后又可以看到notepad了
33.8 源代码分析 33.8.1 HideProc2.cpp 和HideProc.cpp相比, HideProc2.cpp只是减少了参数个数, 内容跟HideProc差不多, 所以可以参照上面的内容
33.8.2 stealth2.cpp DllMain.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 BOOL APIENTRY DllMain ( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { char szCurProc[MAX_PATH] = { 0 , }; char * p = NULL ; GetModuleFileNameA(NULL , szCurProc, MAX_PATH); p = strrchr (szCurProc, '\\' ); if ((p != NULL ) && !_stricmp(p + 1 , "HideProc2.exe" )) return TRUE; SetPrivilege(SE_DEBUG_NAME, TURE); switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: hook_by_code("kernel32.dll" , "CreateProcessA" , (PROC)NewCreateProcessA, g_pOrgCPA); hook_by_code("kernel32.dll" , "CreateProcessW" , (PROC)NewCreateProcessW, g_pOrgCPW); hook_by_code("ntdll.dll" , "ZwQuerySystemInformation" , (PROC)NewZwoQuerySystemInformation, g_pOrgZwQSI); break ; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: unhook_by_code("kernel32.dll" , "CreateProcessA" , g_pOrgCPA); unhook_by_code("kernel32.dll" , "CreateProcessW" , g_pOrgCPW); unhook_by_code("ntdll.dll" , "ZwQuerySystemInformation" , g_pOrgZwQSI); break ; } return TRUE; }
NewCreateProcessA() 针对创建进程API的钩取函数的源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 BOOL CreateProcessA ( LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ) { BOOL bRet; FARPROC pFunc; unhook_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); if (bRet) InjectDll2(lpProcessInformation->hProcess, Str_MODULE_NAME); hook_by_code("kernel32.dll" , "CreateProcessA" , (PROC)NewCreateProcessA, g_pOrgCPA); return bRet; }
大致思路:
在当前进程中对创建进程API 脱钩, 防止无限递归
然后调用脱钩后的原API创建子进程
通过PID对该子进程进程DLL注入, 钩取了子进程的检索进程函数 和创建进程函数
在当前进程中对创建进程挂钩
33.9 利用”热补丁”技术钩取API 33.9.1 API代码修改技术 由于线程问题, 在对API进行写的操作的同时, 运行代码会引发错误, 所以需要跟安全的API钩取技术.
33.9.2 “热补丁”(修改7个字节代码) 我们观察API的结构
可以发现又7个字节的空余, 这些专门用于热补丁的
思路(二次跳转)
前面5个字节用于JMP
后面2个字节用于JMP SHORT, 跳转的目的就是前面的5字节JMP
跟代码修改JMP之间的区别在于, 这个API是可以正常运行原有功能的, 因为下面的指令都没有被破坏, 然后如果想要正常调用不需要再脱钩挂钩, 直接起始地址 + 2 就可调用了.
33.11 源代码分析 stealth3.cpp hook_by_hotpatch() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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); dwAddress = (DWORD)pfnNew - (DWORD)pFunc; memcpy (&pBuf[1 ], &dwAddress, 4 ); memcpy ((LPVOID)((DWORD)pFunc - 5 ), pBuf, 5 ); memcpy (pFunc, pBuf2, 2 ); VirtualProtect((LPVOID)((DWORD)pFunc - 5 ), 7 , dwOldProtect, &dwOldProtect); return TRUE; }
跟之前的钩取函数差不多, 只是修改了7个字节
unhook_by_hotpatch() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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); memcpy ((LPVOID)((DWORD)pFunc - 5 ), pBuf, 5 ); memcpy (pFunc, pBuf2, 2 ); VirtualProtect((LPVOID)pFunc, 5 , dwOldProtect, &dwOldProtect); return TRUE; }
修改后的NewCreateProcessA() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 BOOL CreateProcessA ( LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ) { BOOL bRet; FARPROC pFunc; pFunc = GetProcAddress(GetModuleHandleA("kernel32.dll" ), "CreateProcessA" ); pFunc = (FARPROC)((DWORD)pFunc + 2 ); bRet = ((PFCREATEPROCESSA)pFunc)( lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); if (bRet) InjectDll2(lpProcessInformation->hProcess, Str_MODULE_NAME); return bRet; }
跟之前的HOOK函数相比
省去了hook和unhook
多出了一个 起始地址 + 2
的操作
注意使用热补丁之前要看看API是否支持热补丁