DLL劫持並使用MinHook

Enaium發表於2024-11-03

首發於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.exeundname.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.hhook_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!");
}

最後我們使用MinHookHook這個函式。

首先我們需要初始化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這個指令,然後我們就可以得到這個函式的地址了。

20241103121643

20241103121851

20241103122021

之後編譯我們的專案,然後將生成的 DLL 放到我們的測試程式的目錄下,之後我們就可以執行這個程式了。

20241103122428

專案地址: dll-hijack-minhook

相關文章