博客 / 詳情

返回

DLL劫持並使用MinHook

首發於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

user avatar nian_5aedc008c1353 頭像
1 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.