首發於Enaium的個人部落格
測試用例
首先我使用CLion
寫了一個簡單的程式,這個程式會載入一個dinput8.dll
,然後呼叫一個函式顯示一段文字,然後等待使用者按下任意鍵。這個程式的程式碼如下:
#include<windows.h>
#include<iostream>
int display(const char *text) {
std::cout << text << std::endl;
std::cout << "Press any key to continue..." << std::endl;
std::cin.ignore();
return 0;
}
int main() {
LoadLibrary("dinput8.dll");
printf("Address: %p\n", display);
display("Hello World!");
}
我們所做的就是 Hook 這個display
函式,然後在這個函式被呼叫時,將這個文字改為另一個文字。之後將專案進行編譯,別忘了在CMakeLists.txt
中新增set(CMAKE_EXE_LINKER_FLAGS "-static")
,這樣我們就可以得到一個靜態連結的可執行檔案。之後我們就可以開始 Hook 這個函式了。
DLL 劫持
首先我們需要了解一下 DLL 的載入機制,Windows 系統在載入 DLL 時,會按照一定的順序搜尋 DLL 檔案,如果找到了就載入,如果沒有找到就會報錯。這個順序是怎麼樣的呢?我們可以透過檢視官方文件來了解。簡單來說就是首先搜尋應用程式目錄,然後搜尋系統目錄,最後搜尋環境變數中指定的路徑。所以我們可以使用這個機制來劫持 DLL。
包裝 DLL
上面我們知道了 DLL 的載入機制,那麼我們就可以知道如何讓程式載入我們自己的 DLL,載入我們的 DLL 後,我們需要讓這個 DLL 也擁有原 DLL 的功能,這裡我們需要對原 DLL 進行包裝。我們可以使用wrap_dll這個工具來包裝 DLL。這是個Python
指令碼,所以你必須安裝了Python
,之後這個指令碼還需要dumpbin.exe
和undname.exe
這兩個工具,這兩個工具是 Visual Studio 自帶的,所以你需要安裝了Visual Studio
,並且需要將C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.40.33807\bin\Hostx64\x64
設定到環境變數中。之後你就可以使用這個指令碼了。
python .\wrap_dll.py "C:\Windows\System32\dinput8.dll"
執行完成這個條命令後,你會得到一個dinput8
專案,這個就是我們包裝的 DLL。我們可以先把專案中的empty.h
和hook_macro.h
給刪掉,這兩個檔案對我們沒有用。
MinHook
MinHook 是一個輕量級的鉤子庫,可以用來 Hook 函式。我們可以在minhook下載到這個庫。下載完成後我們在 dinput8 這個專案中新建一個資料夾MinHook
,然後將 MinHook 的include
資料夾和src
資料夾複製到這個資料夾中。之後我們在CMakeLists.txt
中新增這個庫。
ADD_LIBRARY(
dinput8
SHARED
dinput8_asm.asm
dinput8.cpp
dinput8.def
MinHook/include/MinHook.h
MinHook/src/hde/hde32.c
MinHook/src/hde/hde32.h
MinHook/src/hde/hde64.c
MinHook/src/hde/hde64.h
MinHook/src/hde/pstdint.h
MinHook/src/hde/table32.h
MinHook/src/hde/table64.h
MinHook/src/buffer.c
MinHook/src/buffer.h
MinHook/src/hook.c
MinHook/src/trampoline.c
MinHook/src/trampoline.h
)
之後我們就可以開始 Hook 這個函式了。
Hook 函式
首先我們需要再專案中執行cmake .
,之後會生成一個Visual Studio
的專案,我們使用Visual Studio
開啟這個專案,這裡需要注意一下,需要透過開啟解決方案的方式開啟這個專案,不要直接開啟這個專案資料夾。之後我們開啟dinput8.cpp
,刪掉_hook_setup
這個函式,接著我們需要將載入庫的地址改為系統的LoadLibrary
函式,這樣我們就可以載入原 DLL 了。之後我們就可以開始 Hook 這個函式了。
#include <windows.h>
#include <stdio.h>
HINSTANCE mHinst = 0, mHinstDLL = 0;
extern "C" UINT_PTR mProcs[6] = {0};
LPCSTR mImportNames[] = {
"DirectInput8Create",
"DllCanUnloadNow",
"DllGetClassObject",
"DllRegisterServer",
"DllUnregisterServer",
"GetdfDIJoystick",
};
#ifndef _DEBUG
inline void log_info(const char* info) {
}
#else
FILE* debug;
inline void log_info(const char* info) {
fprintf(debug, "%s\n", info);
fflush(debug);
}
#endif
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
mHinst = hinstDLL;
if (fdwReason == DLL_PROCESS_ATTACH) {
mHinstDLL = LoadLibrary("C:/Windows/System32/dinput8.dll");
if (!mHinstDLL) {
return FALSE;
}
for (int i = 0; i < 6; ++i) {
mProcs[i] = (UINT_PTR)GetProcAddress(mHinstDLL, mImportNames[i]);
}
#ifdef _DEBUG
debug = fopen("./debug.log", "a");
#endif
} else if (fdwReason == DLL_PROCESS_DETACH) {
#ifdef _DEBUG
fclose(debug);
#endif
FreeLibrary(mHinstDLL);
}
return TRUE;
}
extern "C" void DirectInput8Create_wrapper();
extern "C" void DllCanUnloadNow_wrapper();
extern "C" void DllGetClassObject_wrapper();
extern "C" void DllRegisterServer_wrapper();
extern "C" void DllUnregisterServer_wrapper();
extern "C" void GetdfDIJoystick_wrapper();
接著我們可以在當fdwReason
等於DLL_PROCESS_ATTACH
時,也就是當 DLL 被載入時,我們進行 Hook。
首先我們需要編寫一個原函式的函式指標,這個函式指標的型別需要和原函式的型別一樣,這裡我們使用typedef
來定義這個函式指標。
typedef int (*display_t)(const char*);
之後我們需要定義一個函式指標,這個函式指標用來指向我們 Hook 後的函式。
display_t display = nullptr;
之後我們建立一個Hook函式,這個函式的引數和返回值都需要和原函式一樣。
int display_hook(const char *text) {
return display("Hello MinHook!");
}
最後我們使用MinHook
來Hook
這個函式。
首先我們需要初始化MinHook
,之後使用MH_CreateHook
來建立一個 Hook,傳入原函式的地址,Hook 函式的地址,和一個函式指標的指標,之後我們就可以使用MH_EnableHook
來啟用這個 Hook 了。
MH_Initialize();
MH_CreateHook((LPVOID)0x00007ff62fb51634, &display_hook, reinterpret_cast<LPVOID*>(&display));
MH_EnableHook(nullptr);
這裡的0x00007ff62fb51634
是我們執行這個測試程式後得到的display
函式的地址,你可以透過列印這個函式的地址來得到這個地址。除了這個方法我們還可以使用x64dbg
這個工具來得到這個地址。
我們開啟x64dbg
透過符號切換到這個程式,之後使用字串搜尋Hello World!
,之後我們找到call
這個指令,然後我們就可以得到這個函式的地址了。
之後編譯我們的專案,然後將生成的 DLL 放到我們的測試程式的目錄下,之後我們就可以執行這個程式了。
專案地址: dll-hijack-minhook