“热补丁”(hot patch)是微软提出的一种安全Hook的机制,也是为了方便开发者对某些API函数进行下钩子。这种方法不同于普通的Inline hook更改首部的五个字节,而是更改首部的七个字节。为什么是七个字节呢?下边我们来讲一下这个的原理。
我们可以看到CreateProcessW函数的首字节为 mov edi,edi(88 FF),这句汇编意思就是将edi的值放入edi,实际上并没有什么用,我们还看到在这个API上边有大段无用的字节。这就给了我们一种新的Hook思路,即将前两个字节改为短跳转指令(EB E9),使其跳到函数上边五字节处,然后再将这五个字节改为长跳转指令(E9 xxxxxxxx)。这样,即使Hook失败,也不影响函数的继续执行。
接下来我们用代码来实现这一功能:
// HotPatch.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <Windows.h> typedef BOOL(WINAPI *pfnCreateprocessW)( LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ); BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege); BOOL HOOKByHotpatch(LPWSTR wzDllName, LPCSTR szFuncName, PROC pfnNewFunc); BOOL UnhookByHotpatch(LPWSTR wzDllName, LPCSTR szFuncName); BOOL WINAPI NewCreateProcessW( LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ); int main() { SetPrivilege(SE_DEBUG_NAME, TRUE); //hook HOOKByHotpatch(L"kernel32.dll", "CreateProcessW", (PROC)NewCreateProcessW); Sleep(1000); STARTUPINFO si = { 0 }; si.cb = sizeof(si); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOW; PROCESS_INFORMATION pi; //创建进程 TCHAR cmdLine[MAXBYTE] = L"notepad.exe"; BOOL bOk = CreateProcess(NULL, cmdLine, NULL, NULL, FALSE, NULL, NULL, NULL, &si, &pi); Sleep(1000); UnhookByHotpatch(L"kernel32.dll", "CreateProcessW"); return 0; } //设置权限 BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) { TOKEN_PRIVILEGES TokenPrivileges;//权限令牌 HANDLE TokenHandle = NULL; //权限令牌句柄 LUID Luid; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &TokenHandle)) { printf("OpenProcessToken error: %u ", GetLastError()); return FALSE; } if (!LookupPrivilegeValue(NULL, // lookup privilege on local system lpszPrivilege, // privilege to lookup &Luid)) // receives LUID of privilege { printf("LookupPrivilegeValue error: %u ", GetLastError()); return FALSE; } TokenPrivileges.PrivilegeCount = 1; TokenPrivileges.Privileges[0].Luid = Luid; if (bEnablePrivilege) TokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; else TokenPrivileges.Privileges[0].Attributes = 0; // Enable the privilege or disable all privileges. //调整权限 if (!AdjustTokenPrivileges(TokenHandle, FALSE, &TokenPrivileges, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) { printf("AdjustTokenPrivileges error: %u ", GetLastError()); return FALSE; } if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) { printf("The token does not have the specified privilege. "); return FALSE; } return TRUE; } //Hootpatch,将函数首字节改为 short jmp(EB F9) BOOL HOOKByHotpatch(LPWSTR wzDllName, LPCSTR szFuncName, PROC pfnNewFunc) { FARPROC pOrgFuncAddr = NULL; DWORD dwOldProtect, dwAddress; BYTE pBuf[5] = { 0xE9, 0, }; BYTE pBuf2[2] = { 0xEB, 0xF9 }; PBYTE pByte; pOrgFuncAddr = (FARPROC)GetProcAddress(GetModuleHandle(wzDllName), szFuncName); pByte = (PBYTE)pOrgFuncAddr; //判断是否被勾 if (pByte[0] == 0xEB) return FALSE; //将前五字节代码改为可读可写 VirtualProtect((LPVOID)((DWORD)pOrgFuncAddr - 5), 7, PAGE_EXECUTE_READWRITE, &dwOldProtect); // 1. NOP (0x90) //将前五字节改为E8 xxxxxxxx dwAddress = (DWORD)pfnNewFunc - (DWORD)pOrgFuncAddr; memcpy(&pBuf[1], &dwAddress, 4); memcpy((LPVOID)((DWORD)pOrgFuncAddr - 5), pBuf, 5); // 2. MOV EDI, EDI (0x8BFF) //将函数前两个字节改为EB F9 memcpy(pOrgFuncAddr, pBuf2, 2); VirtualProtect((LPVOID)((DWORD)pOrgFuncAddr - 5), 7, dwOldProtect, &dwOldProtect); return TRUE; } BOOL UnhookByHotpatch(LPWSTR wzDllName, LPCSTR szFuncName) { FARPROC pHookFunc = NULL; DWORD dwOldProtect; PBYTE pByte; BYTE pBuf[5] = { 0x90, 0x90, 0x90, 0x90, 0x90 }; BYTE pBuf2[2] = { 0x8B, 0xFF }; pHookFunc = (FARPROC)GetProcAddress(GetModuleHandle(wzDllName), szFuncName); pByte = (PBYTE)pHookFunc; if (pByte[0] != 0xEB) return FALSE; VirtualProtect((LPVOID)pHookFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect); // 1. NOP (0x90) memcpy((LPVOID)((DWORD)pHookFunc - 5), pBuf, 5); // 2. MOV EDI, EDI (0x8BFF) memcpy(pHookFunc, pBuf2, 2); VirtualProtect((LPVOID)pHookFunc, 5, dwOldProtect, &dwOldProtect); return TRUE; } BOOL WINAPI NewCreateProcessW( LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ) { MessageBox(NULL, L"Hook", L"", 0); return TRUE; }
需要注意的是使用这一方法钩取的适用条件(NOP*5指令+MOV ESI,ESI),使用的时候一定要反汇编看一下目标函数是否满足条件。