封裝Detours用於Python中x64函式hook

Python成長路發表於2023-12-22

Detours

程式碼倉庫: https://github.com/microsoft/Detours

x64寫一個任意地址hook要比x86麻煩的多,所以這裡直接封裝框架來用於x64的hook。

Detours是微軟釋出的一個API hook框架,同時支援x86和x64,看文件說也支援ARM和ARM64的Windows。

編譯文件

Detours翻了下github,並沒有發現什麼編譯文件,就只有README裡面有這麼一段話:

大概意思是說開啟visual studio的命令列,然後切換到原始碼目錄,執行nmake就能編譯,測試除了一個小問題確實是可以編譯成功。

visual studio的命令列在開始選單裡開啟,上面兩個是x86的,下面兩個是x64的,至於x64和x86_x64有啥區別我就不清楚了,因為測試編譯的時候沒什麼不一樣的,就懶得去搜了。

錯誤

編譯的時候有一個錯誤: 'sn' 不是內部或外部命令,也不是可執行的程式

搜尋發現這是一個秘鑰管理和簽名驗證的工具,而visual studio安裝完後就已經有了,那就是沒加入到環境變數裡,用everything搜尋一下,把這個路徑新增到環境變數。我就臨時用set命令來設定環境變數

開始編譯

如果要編譯64位就開啟64位的命令列,
set path=C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64;%path%

nmake或者nmake all(可以從cmake檔案裡看到,還有nmake cleannmake test等)

這樣就編譯成功了,然後就會在bin.X64和lib.X64生成相應的檔案,需要的是lib.X64下面的lib庫

怎麼編譯Debug版本

這樣編譯出來的版本應該是Release版本,因為沒有pdb檔案,看了下cmake檔案裡的資訊,指定Debug版本的變數是DETOURS_CONFIG, 而指定是x86、x64、arm的變數是DETOURS_TARGET_PROCESSOR

!IF "$(DETOURS_CONFIG)" == "Debug"
DETOURS_DEBUG=1
!ELSE
DETOURS_DEBUG=0
!ENDIF

那麼只需要在編譯之前執行下set DETOURS_CONFIG=Debug就可以編譯成Debug版本的了,會生成一個lib.X64Debug目錄,裡面編譯出來的lib就有了pdb檔案

編譯成dll

Detours的使用很簡單,幾行程式碼就行了,srcFunc是hook的函式指標的指標,注意這是二級指標(PVOID就是void *),具體為什麼要定義二級指標看下面的解釋。newFunc是新函式的函式指標,在c++裡的話,可以直接傳函式名。

#include "detours.h"

DWORD DetourHookFunction(PVOID* srcFunc, PVOID newFunc) {
	DetourTransactionBegin();
	DetourUpdateThread(GetCurrentThread());
	DetourAttach(srcFunc, newFunc);
	DetourTransactionCommit();
	return 0;
}

DWORD DetourUnHookFunction(PVOID* srcFunc, PVOID newFunc) {
	DetourTransactionBegin();
	DetourUpdateThread(GetCurrentThread());
	DetourDetach(srcFunc, newFunc);
	DetourTransactionCommit();
	return 0;
}

為了避免大家還不知道,我說一下怎麼引入標頭檔案和lib庫

引入外部標頭檔案

如果就想Detours就兩個標頭檔案,你可以直接新增到專案裡,如果標頭檔案比較多的話,放到dllmain.cpp目錄下,然後在右鍵屬性裡,配置屬性->C/C++->常規->附加包含目錄新增這個標頭檔案目錄,我這裡用$(ProjectDir)來表示專案的目錄, 你也可以新增絕對路徑。

注意上面配置(C)選所有配置,平臺(P)選所有平臺,避免每個配置都要新增一遍

lib庫則是在連結器->常規->附加庫目錄裡新增

你可以在輸入裡填lib庫的名稱(detours.lib)

不過我更喜歡在程式碼裡用#pragma comment(lib, "detours.lib")來引入lib庫,因為這樣可以根據宏來分別引入Debug還是Release的lib

#ifdef _DEBUG
#pragma comment(lib, "detours.lib")
#else
#pragma comment(lib, "detoursd.lib")
#endif

也可以判斷是x86還是x64

#ifdef _WIN64
#pragma comment(lib, "detours64.lib")
#else
#pragma comment(lib, "detours32.lib")
#endif

也可以兩個宏都判斷一下。

hook函式為啥要定義成二級指標

我為啥要定義成二級指標?因為DetourAttach函式就是傳的二級指標,它的定義如下:

LONG WINAPI DetourAttach(_Inout_ PVOID *ppPointer,
                         _In_ PVOID pDetour);

為啥DetourAttach要把它定義成二級指標? 開始我也不理解這個問題,直到我想在Python裡呼叫原函式,發生了無限遞迴的異常。

為啥C++寫的程式碼不會觸發無限遞迴,它同樣是用的函式指標來呼叫,要想知道原因只能自己使用x64dbg除錯看看。

經過漫長的除錯發現,我傳進去的ppPointer指標指向的值會被修改,它不在指向原函式(被hook函式),而是指向Detours新構建的一個函式指標,這也就能解釋為啥不會無限遞迴了

這裡再接上【Python微信機器人】第六七篇: 封裝32位和64位Python hook框架實戰列印微信日誌這篇文章留的一個坑: 如何在新函式里呼叫原函式。

c_log_addr就是我傳給DetourAttach的第一個引數ppPointer,我透過呼叫Detours修改後的函式指標也就是c_log_addr.value就可以避免無限遞迴的問題

你可以在hook前和hook後列印下c_log_addr.value的值看一下,肯定是不一樣的,

Python使用Detours

這個就看【Python微信機器人】第六七篇: 封裝32位和64位Python hook框架實戰列印微信日誌這篇文章了,裡面說了怎麼用Detours的dll hook日誌

相關文章