|
一、Detours庫的來歷及下載:
Detours庫類似于WTL的來歷,是由Galen Huntand Doug Brubacher自己開發出來,于99年7月發表在一篇名為《Detours: Binary Interception of Win32 Functions.》的論文中。基本原理是改寫函數的頭5個字節(因為一般函數開頭都是保存堆棧環境的三條指令共5個字節:8B FF 55 8B EC)為一條跳轉指令,直接跳轉到自己的函數開頭,從而實現API攔截的。后來得到MS的支持并在其網站上提供下載空間:
http://research.microsoft.com/re ... 03713d/Details.aspx
目前最新的版本是:Detours Express 2.1。
二、Detours的使用準備:
Detours庫是以源碼形式提供的,這給我們的使用帶來極大的方便。你可以選擇把它編譯成庫、也可以直接把源碼加入工程……形式使用。農夫采取的方法是編譯成庫后使用的。編譯庫的方法很簡單,下載包中已經制作好了makefile,我們只須直接用vc下的nmake工具直接編譯即可。鑒于前段時間在網上看見有部分朋友對此也存疑惑,農夫在此“浪費”一下空間,詳細解說一下編譯的過程(括號中為本人的例子):
1、運行你下載的安裝包,把文件解壓到磁盤上
此處建議您把解壓后的src文件夾拷貝到VC的安裝目錄的VC98子目錄下(D:/SDK/6.0/VC98)。對于像我一樣使用庫的方式會有好處,稍后即講:)。
2、編譯并設置開發環境
在你剛才拷貝過去的src文件夾下建立一個*.bat文件,里面填上“../bin/nmake”內容后保存即可。運行該批處理文件,恭喜您:庫已經編譯完成,唯一要做的是要把../bin/Detoured.dll拷貝到您的系統目錄下。現在您的工程里面包含如下文件即可運用Detours庫來進行開發了:
#include <detours.h>
#pragma comment(lib, "detours.lib")
#pragma comment(lib, "detoured.lib")
對于沒有把src文件拷貝過來的朋友,也不用著急。bat文件中把路徑用全路徑即可進行編譯。不過你除了拷貝.dll文件外,還得要把.lib、.h文件拷貝到VC目錄下,或者在你的VC環境中設置這些文件的包含路徑,才可正常使用VC進行開發。看,是不是要麻煩些?
另外的建議:在Detours.h文件中定義一個函數指針類型,到用的時候您就知道會很方便了:
typedef LONG (WINAPI* Detour)(PVOID*, PVOID);
三、幾個重要的函數:
1、DetourAttach & DetourDetach
這兩個函數就是實際實現API掛鉤的(改寫頭5個字節為一個跳轉指令),前一個實現API攔截,后一個為在不需要的時候恢復原來的API。
第一個參數為自己定義的一個用于保存原來系統API的函數,該函數就相當于您沒掛鉤時的API。農夫習慣于在原有API的基礎上加以“Sys”前綴來命名;
第二個參數為自己攔截API的功能函數,您想干什么“壞事”都是在這個函數中實現的。農夫習慣于在原有API的基礎上加以“Hook”前綴來命名。
2、DetourCreateProcessWithDll
該函數是在以DLL注入方式攔截API時使用的,它其實就是封裝“CreateProcess”,以“CREATE_SUSPEND”方式創建進程,然后修改IAT,把Detoured.dll和您的*.dll插入到它的導入表,然后再啟動進程。所以它的參數就是在普通的CreateProcess基礎上增加了兩個DLL的路徑參數,最后一個參數為創建進程的函數指針,默認為CreateProcessA!這點要特別注意:如果您的程序攔截了該函數,而在HookCreateProcessA中又調用DetourCreateProcessWithDll函數的話,一定要在此傳入SysCreateProcessA,否則您的程序就會遞歸調用了,當心您的程序崩潰!至于為何要這么調用?呵呵,問我干嘛?:)
當然其它的API也很有用,不過對于開發者來說,這三個最重要!
四、開發實例:
隨源碼有一個幫助文檔,上面列舉了很多例子:包含普通的API、類成員函數、COM接口、DLL方式注入……。包含了我們的大多應用領域。
下面舉個攔截CreateFileA函數的例子。該例子將所有創建的文件移動到指定目錄下(暈,第一次使用,怎么不能插入C++代碼?):
1、定義保存系統原來函數的函數SysCreateProcessA:(聽起來有點拗口)
staticHANDLE (WINAPI* SysCreateFileA)( LPCTSTR lpFileName, // pointer to name of the file
DWORD dwDesiredAccess, // access (read-write) mode
DWORD dwShareMode, // share mode
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // pointer to security attributes
DWORD dwCreationDisposition, // how to create
DWORD dwFlagsAndAttributes, // file attributes
HANDLE hTemplateFile // handle to file with attributes to copy
) = CreateFileA;
2、編寫自己的功能函數:
HANDLE WINAPI HookCreateFileA( LPCTSTR lpFileName, // pointer to name of the file
DWORD dwDesiredAccess, // access (read-write) mode
DWORD dwShareMode, // share mode
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // pointer to security attributes
DWORD dwCreationDisposition, // how to create
DWORD dwFlagsAndAttributes, // file attributes
HANDLE hTemplateFile // handle to file with attributes to copy
)
{
char chDestFile[256];
strcpy(chDestFile, lpFileName);
if( strstr(lpFileName, "////.//") != NULL ) // 放過設備
{
// 創建的普通文件全部轉到D盤去,這里沒考慮是“讀”訪問
char *p = strrchr(lpFileName, '//');
if( p++ == NULL )
{
p = lpFileName;
}
sprintf(chDestFile, "D://%s", p);
}
// 創建文件,注意這里不可以再調用CreateFileA,否則您就遞歸調用了!取而代之的應該是SysCreateFileA!!!!
return SysCreateFileA(chDestFile, .....); //后面的參數照搬下來就可以了
}
3、掛鉤,用自己的函數替換系統API:
DetourAttach(&(PVOID&)SysCreateFileA, HookCreateFileA);
恢復時用DetourDetach即可。
這下運行您的程序,發現所有用CreateFileA創建的新文件都被轉移到D盤下了。
五、幾個問題:
1、字節編碼:
很多Win32函數都有多字節A版和寬字符W版,之所以平時沒有附加A或W后綴,那是因為編譯器已經幫我們做了這個工作。但在匯編惜字節如金的條件下,如果有兩種版本,請務必明確指出,不要用CreateFile這種函數;
2、應用對象:
該庫適合于初學者在RING3下對大多數API的攔截。對于那些逆向高手來說,這個簡直不值一提;
3、發布:
由于該庫并沒有包含在MS的SDK中,所以要隨程序一塊打包Detoured.dll。當然如果您是直接用源碼加入工程編譯的則可免去這個文件;
4、攔截DLL參數設置:
拿上面CreateFile函數來說,假如我是想在文件名稱符合特定條件下才將其轉移到D盤上,比如當文件類型為TXT文件時才轉移。我們可以把盤符“D”及文件類型“TXT”直接寫死在程序里面。這樣的話,假如有其它程序是把“PDF”文件放在F盤不是又要重寫一個?
不要以為加一個接口就可以解決。如是,祝賀您步入了農夫當初的歧途:)!其實要傳這個參數進去的實質是進程間通信問題。本人采取的是FileMapping,改寫了一下Detours的源碼。當然其它進程間通信方式也是可以的。
5、攔截DLL必須導出一個函數
一般搞個空函數就可以了。如果沒有任何導出函數,您辛苦編寫的DLL將無法工作,還可能為此花上一大堆時間去排查。像如下聲明一個就行了:
////////////////////////////////////////////////////////////////////////////////
// Must at least ONE export function:
__declspec(dllexport) void ExportFunc(void)
{
}
六、后記
鄉村野夫,恍惚于世。本應扶犁,貿入IT。
三十而立,一事無成。慨殤歲逝,輾轉反側。
寫文靜心,閑以思遠。悠悠我祖,自愛陶潛。
1 介紹
Api hook包括兩部分:api調用的截取和api函數的重定向。通過api hook可以修改函數的參數和返回值。關于原理的詳細內容參見《windows核心編程》第19章和第22章。
2 Detours API hook
"Detours is a library for intercepting arbitrary Win32 binary functions on x86 machines. Interception code is applied dynamically at runtime. Detours replaces the first few instructions of the target function with an unconditional jump to the user-provided detour function. Instructions from the target function are placed in a trampoline. The address of the trampoline is placed in a target pointer. The detour function can either replace the target function, or extend its semantics by invoking the target function as a subroutine through the target pointer to the trampoline."
在Detours庫中,驅動detours執行的是函數 DetourAttach(…).
LONG DetourAttach(
PVOID * ppPointer,
PVOID pDetour
);
這個函數的職責是掛接目標API,函數的第一個參數是一個指向將要被掛接函數地址的函數指針,第二個參數是指向實際運行的函數的指針,一般來說是我們定義的替代函數的地址。但是,在掛接開始之前,還有以下幾件事需要完成:
需要對detours進行初始化.
需要更新進行detours的線程.
這些可以調用以下函數很容的做到:
DetourTransactionBegin()
DetourUpdateThread(GetCurrentThread())
在這兩件事做完以后,detour函數才是真正地附著到目標函數上。在此之后,調用DetourTransactionCommit()是detour函數起作用并檢查函數的返回值判斷是正確還是錯誤。
2.1 hook DLL 中的函數
在這個例子中,將要hook winsock中的函數 send(…)和recv(…).在這些函數中,我將會在真正調用send或者recv函數前,把真正說要發送或者接收的消息寫到一個日志文件中去。注意:我們自定義的替代函式一定要與被hook的函數具有相同的參數和返回值。例如,send函數的定義是這樣的:
int send(
__in SOCKET s,
__in const char *buf,
__in int len,
__in int flags
);
因此,指向這個函數的指針看起來應該是這樣的:
int (WINAPI *pSend)(SOCKET, const char*, int, int) = send;
把函數指針初始化成真正的函數地址是ok的;另外還有一種方式是把函數指針初始化為NULL,然后用函數DetourFindFunction(…)指向真正的函式地址.把send(…) 和 recv(…)初始化:
int (WINAPI *pSend)(SOCKET s, const char* buf, int len, int flags) = send;
int WINAPI MySend(SOCKET s, const char* buf, int len, int flags);
int (WINAPI *pRecv)(SOCKET s, char* buf, int len, int flags) = recv;
int WINAPI MyRecv(SOCKET s, char* buf, int len, int flags);
現在,需要hook的函數和重定向到的函數已經定義好了。這里使用 WINAPI 是因為這些函數是用 __stdcall 返回值的導出函數,現在開始hook:
view sourceprint?INTAPIENTRY DllMain(HMODULEhDLL, DWORDReason, LPVOIDReserved)
{
switch(Reason)
{
caseDLL_PROCESS_ATTACH:
DisableThreadLibraryCalls(hDLL);
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)pSend, MySend);
if(DetourTransactionCommit() == NO_ERROR)
OutputDebugString("send() detoured successfully");
break;
.
}
}
它基本上是用上面介紹的步驟開始和結束 —— 初始化,更新detours線程,用DetourAttach(…)開始hook函數,最后調用 DetourTransactionCommit() 函數, 當調用成功時返回 NO_ERROR, 失敗是返回一些錯誤碼.下面是我們的函數的實現,我發送和接收的信息寫入到一個日志文件中:
view sourceprint?intWINAPI MySend(SOCKET s, constchar* buf, intlen, intflags)
{
fopen_s(&pSendLogFile, "C:\\SendLog.txt", "a+");
fprintf(pSendLogFile, "%s\n", buf);
fclose(pSendLogFile);
returnpSend(s, buf, len, flags);
}
intWINAPI MyRecv(SOCKET s, char* buf, intlen, intflags)
{
fopen_s(&pRecvLogFile, "C:\\RecvLog.txt", "a+");
fprintf(pRecvLogFile, "%s\n", buf);
fclose(pRecvLogFile);
returnpRecv(s, buf, len, flags);
}
2.2hook自定義c 函數
舉例來說明,假如有一個函數,其原型為
int RunCmd(const char* cmd);
如果要hook這個函數,可以按照以下幾步來做:
a) include 聲明這個函數的頭文件
b) 定義指向這個函數的函數指針,int (* RealRunCmd)(const char*) = RunCmd;
c) 定義detour函數,例如: int DetourRunCmd(const char*);
d) 實現detour函數,如:
Int DetourRunCmd(const char* cmd)
{
//extend the function,add what you want :)
Return RealRunCme(cmd);
}
這樣就完成了hook RunCmd函數的定義,所需要的就是調用DetourAttack
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)RealRunCmd, DetourRunCmd);
if(DetourTransactionCommit() == NO_ERROR)
{
//error
}
2.3 hook類成員函數
Hook類成員函數通過在static函數指針來實現
還是舉例說明,假如有個類定義如下:
class CData
{
public:
CData(void);
virtual ~CData(void);
int Run(const char* cmd);
};
現在需要hook int CData::Run(const char*) 這個函數,可以按照以下幾步:
a) 聲明用于hook的類
class CDataHook
{
public:
int DetourRun(const char* cmd);
static int (CDataHook::* RealRun)(const char* cmd);
};
b) 初始化類中的static函數指針
int (CDataHook::* CDataHook::RealRun)(const char* cmd) = (int (CDataHook::*)(const char*))&CData::Run;
c) 定義detour函數
int CDataHook::DetourRun(const char* cmd)
{
//添加任意你想添加的代碼
int iRet = (this->*RealRun)(cmd);
return iRet;
}
e) 調用detourAttach函數
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)CDataHook::RealRun, (PVOID)(&(PVOID&)CDataHook::DetourRun));
if(DetourTransactionCommit() == NO_ERROR)
{
//error
}
2.4 DetourCreateProcessWithDll
使用這個函數相當于用CREATE_SUSPENDED 標志調用函數CreateProcess. 新進程的主線程處于暫停狀態,因此DLL能在函數被運行錢被注入。注意:被注入的DLL最少要有一個導出函數. 如用testdll.dll注入到notepad.exe中:
view sourceprint?#undef UNICODE
#include <cstdio>
#include <windows.h>
#include <detours\detours.h>
intmain()
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(STARTUPINFO));
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
si.cb = sizeof(STARTUPINFO);
char* DirPath = newchar[MAX_PATH];
char* DLLPath = newchar[MAX_PATH]; //testdll.dll
char* DetourPath = newchar[MAX_PATH]; //detoured.dll
GetCurrentDirectory(MAX_PATH, DirPath);
sprintf_s(DLLPath, MAX_PATH, "%s\\testdll.dll", DirPath);
sprintf_s(DetourPath, MAX_PATH, "%s\\detoured.dll", DirPath);
DetourCreateProcessWithDll(NULL, "C:\\windows\\notepad.exe",
NULL,NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL,&si, &pi, DetourPath, DLLPath, NULL);
delete[] DirPath;
delete[] DLLPath;
delete[] DetourPath;
return0;
}
2.5 Detouring by Address
假如出現這種情況怎么辦?我們需要hook的函數既不是一個標準的WIN32 API,也不是導出函數。這時我們需要吧我們的程序和被所要注入的程序同事編譯,或者,把函數的地址硬編碼:
view sourceprint?#undef UNICODE
#include <cstdio>
#include <windows.h>
#include <detours\detours.h>
typedefvoid(WINAPI *pFunc)(DWORD);
voidWINAPI MyFunc(DWORD);
pFunc FuncToDetour = (pFunc)(0x0100347C); //Set it at address to detour in
//the process
INTAPIENTRY DllMain(HMODULEhDLL, DWORDReason, LPVOIDReserved)
{
switch(Reason)
{
caseDLL_PROCESS_ATTACH:
{
DisableThreadLibraryCalls(hDLL);
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)FuncToDetour, MyFunc);
DetourTransactionCommit();
}
break;
caseDLL_PROCESS_DETACH:
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)FuncToDetour, MyFunc);
DetourTransactionCommit();
break;
caseDLL_THREAD_ATTACH:
caseDLL_THREAD_DETACH:
break;
}
returnTRUE;
}
voidWINAPI MyFunc(DWORDsomeParameter)
{
//Some magic can happen here
}
例子:
// HOOK.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
#include <ShellAPI.h>
#include "detours.h"
#pragma comment(lib, "detours.lib")
// 注意 A 和 W 類的參數類型是不一樣的
int (WINAPI *SysMessageBoxA)(HWND hWnd,LPCSTR lpText, LPCSTR lpCaption, UINT uType) = MessageBoxA;
int (WINAPI *SysMessageBoxW)(HWND hWnd,LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) = MessageBoxW;
// 這個是接替API
int WINAPI MyMessageBox(HWND hWnd,LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
// 這里不應該調用 MessageBoxA 不然就是遞歸調用了 因為我們HOOK了
SysMessageBoxA(hWnd,"Hook成功!", "嘎嘎~~~",0);
return SysMessageBoxA(hWnd, lpText, lpCaption, uType); // 原請求反回
}
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
// 在這兩件事做完以后,detour函數才是真正地附著到目標函數上
DetourTransactionBegin(); // 對detours進行初始化.
DetourUpdateThread(GetCurrentThread()); // 更新進行detours的線程
// 參數原有的API ,接管的API
DetourAttach(&(PVOID&)SysMessageBoxA, MyMessageBox); // 掛鉤API HOOK 列表
DetourAttach(&(PVOID&)SysMessageBoxW, MyMessageBox);
if(DetourTransactionCommit() != NO_ERROR) // 啟用并檢查啟用是否成功
OutputDebugString("detoured unsuccessfully!");
break;
case DLL_PROCESS_DETACH:
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)SysMessageBoxA, MyMessageBox);
DetourAttach(&(PVOID&)SysMessageBoxW, MyMessageBox);
DetourTransactionCommit();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
// 一般搞個空函數就可以了。如果沒有任何導出函數,您辛苦編寫的DLL將無法工作,還可能為此花上一大堆時間去排查。像如下聲明一個就行了:
__declspec(dllexport) int ExportFunc(VOID)
{
return 5;
}
|
|