InoolineHook需要讀寫兩次內存(先HOOK,再還原),這種Hook方式,性能比較低,具有局限性。今天所講的HotFixHOOK(熱補丁)是InlineHook的升級版
Win32 API特殊性
Win32API的實現代碼有這樣的特點:起始指令為1個MOV EDI,EDI,上方有多個int3(稱之為內存空白區),這些指令實際沒有任何意義。
接下來我們通過觀察MessageBoxA的反匯編代碼來驗證這個現象
如圖所示,確實存在無意義的指令
HotFix HOOK
我們從上文了解到,Win32 API的實現代碼中起始位置處存在一些無意義的指令,因此我們可以通過修改這些無意義的指令來實現HOOK操作。這種方法可以使得進程處于運行狀態時臨時更改進程內存中的庫文件,因此被稱為打熱補丁,即HotFix HOOK
HOOK原理
如圖是MessageBoxA的匯編代碼實現處,我們借助這個圖方便我們理解HOOK
修改Win32 API實現代碼中的第一行指令為jmp,使之可以跳轉到上方內存空白處。上方內存空白處修改為我們自己的代碼,如執行某API。此時每當程序調用該API時,在執行第一行指令時,都會跳轉到上方內存空白處執行我們自己的代碼,而不會影響原API實際的函數功能實現
HOOK實現
接下來我們創建dll文件,實現HotFix?HOOK
// dllmain.cpp : 定義 DLL 應用程序的入口點。
#include "pch.h"
#include <Windows.h>BOOL Hook(const char * pszModuleName, const char * pszFuncName, PROC pfnHookFunc)//HOOK
{BYTE ShortJmp[2] = { 0xEB,0xF9 };//用于替換函數頭兩個字節mov edi, edi(8B FF)實現短跳上方五個字節BYTE NewCodes[5] = { 0xE9,0, };//替換短跳后的五個字節:一字節指令,四字節函數地址HMODULE hModule = GetModuleHandleA(pszModuleName);//獲取目標HOOK函數所處模塊的句柄FARPROC FunAddress = GetProcAddress(hModule, pszFuncName);//獲取目標HOOK函數地址DWORD dwOldProtect = 0;//保存原有的內存屬性VirtualProtect((LPVOID)((DWORD)FunAddress - 5), 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);//修改五字節空白內存讀寫屬性,注意修改的兩個頭字節一共七個字節DWORD dwFuncAddr = ((DWORD)pfnHookFunc - (DWORD)FunAddress);//計算要跳轉的函數相對地址//注意換算結果:跳轉函數地址 - E8指令地址處 - E8指令長度 == 跳轉函數地址 - (原函數地址處 - E8指令長度) - E8指令長度 == 跳轉函數地址 - 原函數地址處*(DWORD *)(NewCodes + 1) = dwFuncAddr;memcpy((LPVOID)((DWORD)FunAddress - 5), NewCodes, 5);memcpy(FunAddress, ShortJmp, 2);//修改內存VirtualProtect((LPVOID)((DWORD)FunAddress - 5), 7, dwOldProtect, &dwOldProtect);//恢復原有的內存屬性//此時該程序中所有的原函數都被我們修改了,只要調用該函數,都會被HOOKreturn TRUE;
}BOOL UnHook(const char * pszModuleName, const char * pszFuncName)//卸載HOOK
{BYTE ShortJmp[2] = { 0x8B,0xFF };//還原原有字節BYTE NewCodes[5] = { 0x90,0x90,0x90,0x90,0x90 };//恢復空白內存的一種方式:NOPHMODULE hModule = GetModuleHandleA(pszModuleName);FARPROC = GetProcAddress(hModule, pszFuncName);DWORD dwOldProtect = 0;VirtualProtect((LPVOID)((DWORD)FunAddress - 5), 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);memcpy((LPVOID)((DWORD)FunAddress - 5), NewCodes, 5);memcpy(FunAddress, ShortJmp, 2);VirtualProtect((LPVOID)((DWORD)FunAddress - 5), 7, dwOldProtect, &dwOldProtect);//此時原函數恢復原來的代碼,卸載了HOOKreturn TRUE;
}typedef int //聲明函數指針
(WINAPI
* fnMessageBoxA)(_In_opt_ HWND hWnd,_In_opt_ LPCSTR lpText,_In_opt_ LPCSTR lpCaption,_In_ UINT uType);int
WINAPI
MyMessageBoxA(_In_opt_ HWND hWnd,_In_opt_ LPCSTR lpText,_In_opt_ LPCSTR lpCaption,_In_ UINT uType)
{fnMessageBoxA NewFunc = (fnMessageBoxA)((DWORD)MessageBoxA + 2);//這是我們用于HOOK后執行的函數,其指向MessageBoxA并跳過頭兩個字節,防止被短跳而反復HOOK進入死循環。此時我們調用這個函數時,可以執行原來的MessageBoxA的功能 int bRet = NewFunc(hWnd, "hook", "hook", uType);//執行我們定義的函數,實際上就是執行MessageBoxA的有效部分return bRet;
}BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:Hook("user32.dll", "MessageBoxA", (PROC)MyMessageBoxA);break;case DLL_THREAD_ATTACH:break;case DLL_THREAD_DETACH:break;case DLL_PROCESS_DETACH:UnHook("user32.dll", "MessageBoxA");break;}return TRUE;
}
?接下來我們將通過如下的代碼,演示HotFix?HOOK
#include <iostream>
#include <Windows.h>int main()
{MessageBoxA(NULL, "rkvir", "success", MB_OK);system("pause");MessageBoxA(NULL, "rkvir", "success", MB_OK);return 0;
}
首先運行程序,我們發現正常運行程序時,兩次彈窗都是原來的窗口:
接下來我們再次重新運行程序,第一次彈窗是原來的窗口:
此時我們注入上文編寫的dll文件,然后再次彈窗,發現彈窗變了,HOOK成功了: