天天看點

Detours 源碼閱讀筆記

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 ;
}