Detour庫
1. 源碼
Detour庫很小,直接編譯成lib比較好,在用到的代碼中做靜态連結。
直接從微軟官網 下載下傳Detours: http://research.microsoft.com/en-us/projects/detours/
目前免費下載下傳的是 Detours Express 3.0。
Detours 3.0加入的新的功能:
支援X64的API Hook,包括 AMD64 和 IA64兩套
支援所有的Windows處理器(包括ARM)
不再依賴detoured.dll
枚舉PE導入表,導出表的API,确定函數指針引用的子產品
源碼結構:
disasm.cpp / detours / detours.h 共同構成了Detours基本功能
modules.cpp 提供了周遊PE檔案的API
image.cpp / creatwth.cpp / uimports.cpp 用于建構新的PE檔案,添加.detours 節
編譯:
直接在VS中建立一個lib工程,然後将源檔案導入即可(uimports.cpp 除外)。
注: uimports.cpp 被 include進了 creatwth.cpp 中了,如果加入到工程,就會提示編譯錯誤。
2. 源碼閱讀摘要:
其實Detours的工作比較簡單,但是要做一個健壯的庫,還是不太容易的。
自己感覺Detours比較困難的幾個點難點:
1. 給出一個位址,判斷能否Hook
不能Hook jmp指令,否則Trampline 代碼會有問題
2. 代碼段的判斷:
需要Trampline 幾個位元組,不能在Hook時将指令截斷
3. Trampline部分 位址重定位的問題
這就需要識别出被搬走代碼中的位址,并進行修改
2和3是一個問題,就是要能夠識别指令,其實就是解析CPU的 OPCode
Detours API的作用,以及内部做的工作:
LONG WINAPI DetourTransactionBegin(VOID);
鎖住detour庫,并将Trampline的所有塊變為可讀可寫可執行,友善後面向Trampline塊中寫入。
LONG WINAPI DetourTransactionAbort(VOID);
恢複所有的 Operation(一次可以有多個Operation),修改Trampline塊為執行屬性,并恢複所有線程執行
LONG WINAPI DetourTransactionCommit(VOID);
首先恢複Hook或插入Hook,對執行到Trampline的線程進行調整,flush icache保證代碼更新到緩存,讓CPU執行新的指令,
修改Trampline塊組執行屬性,恢複所有線程
LONG WINAPI DetourTransactionCommitEx(PVOID **pppFailedPointer);
同上
LONG WINAPI DetourUpdateThread(HANDLE hThread);
挂起指定句柄指定的線程
LONG WINAPI DetourAttach(PVOID *ppPointer, PVOID pDetour);
尋找可以Hook的位址(目前位址可能無法Hook),配置設定Trampline塊,并且确定Trampline中的拷貝的指令位元組數,加入操作(Operation)清單
LONG WINAPI DetourAttachEx(PVOID *ppPointer, PVOID pDetour, PDETOUR_TRAMPOLINE *ppRealTrampoline, PVOID *ppRealTarget, PVOID *ppRealDetour);
同上
LONG WINAPI DetourDetach(PVOID *ppPointer, PVOID pDetour);
與Attach逆向的操作,建立一個Operation,指明為Remove Hook的操作,将要恢複的Hook的Trampline挂入Operation中。
3. Detours需要注意的地方:
1. DetourTransactionBegin()方法
每個Hook如果隻做了一次,在多線程情況下可能出現Hook失敗的情況。
// Only one transaction is allowed at a time.
if (s_nPendingThreadId != 0) {
return ERROR_INVALID_OPERATION;
}
// Make sure only one thread can start a transaction.
if (InterlockedCompareExchange(&s_nPendingThreadId, (LONG)GetCurrentThreadId(), 0) != 0) {
return ERROR_INVALID_OPERATION;
}
如果兩個線程同時執行到了 DetourTransactionBegin的起始位置,同時向下執行,肯定有一個線程會 return ERROR_INVALID_OPERATION; 。
2. DetourUpdateThread() 方法
這個函數是挂起指定句柄的線程,隻留下執行Hook的線程。
是以要周遊目前程序中的所有的線程,逐一執行這個函數。
3. 如果攔截函數在DLL中,那麼絕大多數情況下不能在Unhook之後解除安裝這個DLL,或者解除安裝存在造成崩潰的危險。
因為某些線程的調用堆棧中可能還包含Hook函數,這時解除安裝掉DLL,調用堆棧傳回到Hook函數時記憶體位置已經不是合法的代碼了。
4. 有一些非常短的目标函數無法Hook。
jmp指令需要占用一定空間,有些函數太過短小,甚至不夠jmp指令的長度,沒有辦法Hook,(比如ntdll!DbgBreakPoint函數)
4. 示例:
#include <Windows.h>
#include "detours.h"
#pragma comment(lib, "detours.lib")
static int (WINAPI * OLD_MessageBoxW)(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) = MessageBoxW;
int WINAPI New_MessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCation, UINT uType)
{
int ret = OLD_MessageBoxW( hWnd, L"輸入的參數已修改", L"[測試]", uType);
return ret;
}
void HOOK()
{
DetourRestoreAfterWith();
DetourTransactionBegin();
DetourUpdateThread( GetCurrentThread());
DetourAttach( &(PVOID&)OLD_MessageBoxW, New_MessageBoxW);
DetourTransactionCommit();
}
void UnHook()
{
DetourTransactionBegin();
DetourUpdateThread( GetCurrentThread());
DetourDetach( &(PVOID&)OLD_MessageBoxW, New_MessageBoxW);
DetourTransactionCommit();
}
void main()
{
::MessageBoxW(NULL, L"正常消息框", L"測試", MB_OK);
HOOK();
::MessageBoxW(NULL, L"正常消息框", L"測試", MB_OK);
UnHook();
return ;
}