|
一、Detours庫的來歷及下載:
Detours庫類似于WTL的來歷,是由Galen Huntand Doug Brubacher自己開發(fā)出來,于99年7月發(fā)表在一篇名為《Detours: Binary Interception of Win32 Functions.》的論文中。基本原理是改寫函數(shù)的頭5個(gè)字節(jié)(因?yàn)橐话愫瘮?shù)開頭都是保存堆棧環(huán)境的三條指令共5個(gè)字節(jié):8B FF 55 8B EC)為一條跳轉(zhuǎn)指令,直接跳轉(zhuǎn)到自己的函數(shù)開頭,從而實(shí)現(xiàn)API攔截的。后來得到MS的支持并在其網(wǎng)站上提供下載空間:
http://research.microsoft.com/re ... 03713d/Details.aspx
目前最新的版本是:Detours Express 2.1。
二、Detours的使用準(zhǔn)備:
Detours庫是以源碼形式提供的,這給我們的使用帶來極大的方便。你可以選擇把它編譯成庫、也可以直接把源碼加入工程……形式使用。農(nóng)夫采取的方法是編譯成庫后使用的。編譯庫的方法很簡(jiǎn)單,下載包中已經(jīng)制作好了makefile,我們只須直接用vc下的nmake工具直接編譯即可。鑒于前段時(shí)間在網(wǎng)上看見有部分朋友對(duì)此也存疑惑,農(nóng)夫在此“浪費(fèi)”一下空間,詳細(xì)解說一下編譯的過程(括號(hào)中為本人的例子):
1、運(yùn)行你下載的安裝包,把文件解壓到磁盤上
此處建議您把解壓后的src文件夾拷貝到VC的安裝目錄的VC98子目錄下(D:/SDK/6.0/VC98)。對(duì)于像我一樣使用庫的方式會(huì)有好處,稍后即講:)。
2、編譯并設(shè)置開發(fā)環(huán)境
在你剛才拷貝過去的src文件夾下建立一個(gè)*.bat文件,里面填上“../bin/nmake”內(nèi)容后保存即可。運(yùn)行該批處理文件,恭喜您:庫已經(jīng)編譯完成,唯一要做的是要把../bin/Detoured.dll拷貝到您的系統(tǒng)目錄下。現(xiàn)在您的工程里面包含如下文件即可運(yùn)用Detours庫來進(jìn)行開發(fā)了:
#include <detours.h>
#pragma comment(lib, "detours.lib")
#pragma comment(lib, "detoured.lib")
對(duì)于沒有把src文件拷貝過來的朋友,也不用著急。bat文件中把路徑用全路徑即可進(jìn)行編譯。不過你除了拷貝.dll文件外,還得要把.lib、.h文件拷貝到VC目錄下,或者在你的VC環(huán)境中設(shè)置這些文件的包含路徑,才可正常使用VC進(jìn)行開發(fā)。看,是不是要麻煩些?
另外的建議:在Detours.h文件中定義一個(gè)函數(shù)指針類型,到用的時(shí)候您就知道會(huì)很方便了:
typedef LONG (WINAPI* Detour)(PVOID*, PVOID);
三、幾個(gè)重要的函數(shù):
1、DetourAttach & DetourDetach
這兩個(gè)函數(shù)就是實(shí)際實(shí)現(xiàn)API掛鉤的(改寫頭5個(gè)字節(jié)為一個(gè)跳轉(zhuǎn)指令),前一個(gè)實(shí)現(xiàn)API攔截,后一個(gè)為在不需要的時(shí)候恢復(fù)原來的API。
第一個(gè)參數(shù)為自己定義的一個(gè)用于保存原來系統(tǒng)API的函數(shù),該函數(shù)就相當(dāng)于您沒掛鉤時(shí)的API。農(nóng)夫習(xí)慣于在原有API的基礎(chǔ)上加以“Sys”前綴來命名;
第二個(gè)參數(shù)為自己攔截API的功能函數(shù),您想干什么“壞事”都是在這個(gè)函數(shù)中實(shí)現(xiàn)的。農(nóng)夫習(xí)慣于在原有API的基礎(chǔ)上加以“Hook”前綴來命名。
2、DetourCreateProcessWithDll
該函數(shù)是在以DLL注入方式攔截API時(shí)使用的,它其實(shí)就是封裝“CreateProcess”,以“CREATE_SUSPEND”方式創(chuàng)建進(jìn)程,然后修改IAT,把Detoured.dll和您的*.dll插入到它的導(dǎo)入表,然后再啟動(dòng)進(jìn)程。所以它的參數(shù)就是在普通的CreateProcess基礎(chǔ)上增加了兩個(gè)DLL的路徑參數(shù),最后一個(gè)參數(shù)為創(chuàng)建進(jìn)程的函數(shù)指針,默認(rèn)為CreateProcessA!這點(diǎn)要特別注意:如果您的程序攔截了該函數(shù),而在HookCreateProcessA中又調(diào)用DetourCreateProcessWithDll函數(shù)的話,一定要在此傳入SysCreateProcessA,否則您的程序就會(huì)遞歸調(diào)用了,當(dāng)心您的程序崩潰!至于為何要這么調(diào)用?呵呵,問我干嘛?:)
當(dāng)然其它的API也很有用,不過對(duì)于開發(fā)者來說,這三個(gè)最重要!
四、開發(fā)實(shí)例:
隨源碼有一個(gè)幫助文檔,上面列舉了很多例子:包含普通的API、類成員函數(shù)、COM接口、DLL方式注入……。包含了我們的大多應(yīng)用領(lǐng)域。
下面舉個(gè)攔截CreateFileA函數(shù)的例子。該例子將所有創(chuàng)建的文件移動(dòng)到指定目錄下(暈,第一次使用,怎么不能插入C++代碼?):
1、定義保存系統(tǒng)原來函數(shù)的函數(shù)SysCreateProcessA:(聽起來有點(diǎn)拗口)
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、編寫自己的功能函數(shù):
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 ) // 放過設(shè)備
{
// 創(chuàng)建的普通文件全部轉(zhuǎn)到D盤去,這里沒考慮是“讀”訪問
char *p = strrchr(lpFileName, '//');
if( p++ == NULL )
{
p = lpFileName;
}
sprintf(chDestFile, "D://%s", p);
}
// 創(chuàng)建文件,注意這里不可以再調(diào)用CreateFileA,否則您就遞歸調(diào)用了!取而代之的應(yīng)該是SysCreateFileA!!!!
return SysCreateFileA(chDestFile, .....); //后面的參數(shù)照搬下來就可以了
}
3、掛鉤,用自己的函數(shù)替換系統(tǒng)API:
DetourAttach(&(PVOID&)SysCreateFileA, HookCreateFileA);
恢復(fù)時(shí)用DetourDetach即可。
這下運(yùn)行您的程序,發(fā)現(xiàn)所有用CreateFileA創(chuàng)建的新文件都被轉(zhuǎn)移到D盤下了。
五、幾個(gè)問題:
1、字節(jié)編碼:
很多Win32函數(shù)都有多字節(jié)A版和寬字符W版,之所以平時(shí)沒有附加A或W后綴,那是因?yàn)榫幾g器已經(jīng)幫我們做了這個(gè)工作。但在匯編惜字節(jié)如金的條件下,如果有兩種版本,請(qǐng)務(wù)必明確指出,不要用CreateFile這種函數(shù);
2、應(yīng)用對(duì)象:
該庫適合于初學(xué)者在RING3下對(duì)大多數(shù)API的攔截。對(duì)于那些逆向高手來說,這個(gè)簡(jiǎn)直不值一提;
3、發(fā)布:
由于該庫并沒有包含在MS的SDK中,所以要隨程序一塊打包Detoured.dll。當(dāng)然如果您是直接用源碼加入工程編譯的則可免去這個(gè)文件;
4、攔截DLL參數(shù)設(shè)置:
拿上面CreateFile函數(shù)來說,假如我是想在文件名稱符合特定條件下才將其轉(zhuǎn)移到D盤上,比如當(dāng)文件類型為TXT文件時(shí)才轉(zhuǎn)移。我們可以把盤符“D”及文件類型“TXT”直接寫死在程序里面。這樣的話,假如有其它程序是把“PDF”文件放在F盤不是又要重寫一個(gè)?
不要以為加一個(gè)接口就可以解決。如是,祝賀您步入了農(nóng)夫當(dāng)初的歧途:)!其實(shí)要傳這個(gè)參數(shù)進(jìn)去的實(shí)質(zhì)是進(jìn)程間通信問題。本人采取的是FileMapping,改寫了一下Detours的源碼。當(dāng)然其它進(jìn)程間通信方式也是可以的。
5、攔截DLL必須導(dǎo)出一個(gè)函數(shù)
一般搞個(gè)空函數(shù)就可以了。如果沒有任何導(dǎo)出函數(shù),您辛苦編寫的DLL將無法工作,還可能為此花上一大堆時(shí)間去排查。像如下聲明一個(gè)就行了:
////////////////////////////////////////////////////////////////////////////////
// Must at least ONE export function:
__declspec(dllexport) void ExportFunc(void)
{
}
六、后記
鄉(xiāng)村野夫,恍惚于世。本應(yīng)扶犁,貿(mào)入IT。
三十而立,一事無成。慨殤歲逝,輾轉(zhuǎn)反側(cè)。
寫文靜心,閑以思遠(yuǎn)。悠悠我祖,自愛陶潛。
1 介紹
Api hook包括兩部分:api調(diào)用的截取和api函數(shù)的重定向。通過api hook可以修改函數(shù)的參數(shù)和返回值。關(guān)于原理的詳細(xì)內(nèi)容參見《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庫中,驅(qū)動(dòng)detours執(zhí)行的是函數(shù) DetourAttach(…).
LONG DetourAttach(
PVOID * ppPointer,
PVOID pDetour
);
這個(gè)函數(shù)的職責(zé)是掛接目標(biāo)API,函數(shù)的第一個(gè)參數(shù)是一個(gè)指向?qū)⒁粧旖雍瘮?shù)地址的函數(shù)指針,第二個(gè)參數(shù)是指向?qū)嶋H運(yùn)行的函數(shù)的指針,一般來說是我們定義的替代函數(shù)的地址。但是,在掛接開始之前,還有以下幾件事需要完成:
需要對(duì)detours進(jìn)行初始化.
需要更新進(jìn)行detours的線程.
這些可以調(diào)用以下函數(shù)很容的做到:
DetourTransactionBegin()
DetourUpdateThread(GetCurrentThread())
在這兩件事做完以后,detour函數(shù)才是真正地附著到目標(biāo)函數(shù)上。在此之后,調(diào)用DetourTransactionCommit()是detour函數(shù)起作用并檢查函數(shù)的返回值判斷是正確還是錯(cuò)誤。
2.1 hook DLL 中的函數(shù)
在這個(gè)例子中,將要hook winsock中的函數(shù) send(…)和recv(…).在這些函數(shù)中,我將會(huì)在真正調(diào)用send或者recv函數(shù)前,把真正說要發(fā)送或者接收的消息寫到一個(gè)日志文件中去。注意:我們自定義的替代函式一定要與被hook的函數(shù)具有相同的參數(shù)和返回值。例如,send函數(shù)的定義是這樣的:
int send(
__in SOCKET s,
__in const char *buf,
__in int len,
__in int flags
);
因此,指向這個(gè)函數(shù)的指針看起來應(yīng)該是這樣的:
int (WINAPI *pSend)(SOCKET, const char*, int, int) = send;
把函數(shù)指針初始化成真正的函數(shù)地址是ok的;另外還有一種方式是把函數(shù)指針初始化為NULL,然后用函數(shù)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);
現(xiàn)在,需要hook的函數(shù)和重定向到的函數(shù)已經(jīng)定義好了。這里使用 WINAPI 是因?yàn)檫@些函數(shù)是用 __stdcall 返回值的導(dǎo)出函數(shù),現(xiàn)在開始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;
.
}
}
它基本上是用上面介紹的步驟開始和結(jié)束 —— 初始化,更新detours線程,用DetourAttach(…)開始hook函數(shù),最后調(diào)用 DetourTransactionCommit() 函數(shù), 當(dāng)調(diào)用成功時(shí)返回 NO_ERROR, 失敗是返回一些錯(cuò)誤碼.下面是我們的函數(shù)的實(shí)現(xiàn),我發(fā)送和接收的信息寫入到一個(gè)日志文件中:
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 函數(shù)
舉例來說明,假如有一個(gè)函數(shù),其原型為
int RunCmd(const char* cmd);
如果要hook這個(gè)函數(shù),可以按照以下幾步來做:
a) include 聲明這個(gè)函數(shù)的頭文件
b) 定義指向這個(gè)函數(shù)的函數(shù)指針,int (* RealRunCmd)(const char*) = RunCmd;
c) 定義detour函數(shù),例如: int DetourRunCmd(const char*);
d) 實(shí)現(xiàn)detour函數(shù),如:
Int DetourRunCmd(const char* cmd)
{
//extend the function,add what you want :)
Return RealRunCme(cmd);
}
這樣就完成了hook RunCmd函數(shù)的定義,所需要的就是調(diào)用DetourAttack
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)RealRunCmd, DetourRunCmd);
if(DetourTransactionCommit() == NO_ERROR)
{
//error
}
2.3 hook類成員函數(shù)
Hook類成員函數(shù)通過在static函數(shù)指針來實(shí)現(xiàn)
還是舉例說明,假如有個(gè)類定義如下:
class CData
{
public:
CData(void);
virtual ~CData(void);
int Run(const char* cmd);
};
現(xiàn)在需要hook int CData::Run(const char*) 這個(gè)函數(shù),可以按照以下幾步:
a) 聲明用于hook的類
class CDataHook
{
public:
int DetourRun(const char* cmd);
static int (CDataHook::* RealRun)(const char* cmd);
};
b) 初始化類中的static函數(shù)指針
int (CDataHook::* CDataHook::RealRun)(const char* cmd) = (int (CDataHook::*)(const char*))&CData::Run;
c) 定義detour函數(shù)
int CDataHook::DetourRun(const char* cmd)
{
//添加任意你想添加的代碼
int iRet = (this->*RealRun)(cmd);
return iRet;
}
e) 調(diào)用detourAttach函數(shù)
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)CDataHook::RealRun, (PVOID)(&(PVOID&)CDataHook::DetourRun));
if(DetourTransactionCommit() == NO_ERROR)
{
//error
}
2.4 DetourCreateProcessWithDll
使用這個(gè)函數(shù)相當(dāng)于用CREATE_SUSPENDED 標(biāo)志調(diào)用函數(shù)CreateProcess. 新進(jìn)程的主線程處于暫停狀態(tài),因此DLL能在函數(shù)被運(yùn)行錢被注入。注意:被注入的DLL最少要有一個(gè)導(dǎo)出函數(shù). 如用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
假如出現(xiàn)這種情況怎么辦?我們需要hook的函數(shù)既不是一個(gè)標(biāo)準(zhǔn)的WIN32 API,也不是導(dǎo)出函數(shù)。這時(shí)我們需要吧我們的程序和被所要注入的程序同事編譯,或者,把函數(shù)的地址硬編碼:
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 類的參數(shù)類型是不一樣的
int (WINAPI *SysMessageBoxA)(HWND hWnd,LPCSTR lpText, LPCSTR lpCaption, UINT uType) = MessageBoxA;
int (WINAPI *SysMessageBoxW)(HWND hWnd,LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) = MessageBoxW;
// 這個(gè)是接替API
int WINAPI MyMessageBox(HWND hWnd,LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
// 這里不應(yīng)該調(diào)用 MessageBoxA 不然就是遞歸調(diào)用了 因?yàn)槲覀僅OOK了
SysMessageBoxA(hWnd,"Hook成功!", "嘎嘎~~~",0);
return SysMessageBoxA(hWnd, lpText, lpCaption, uType); // 原請(qǐng)求反回
}
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
// 在這兩件事做完以后,detour函數(shù)才是真正地附著到目標(biāo)函數(shù)上
DetourTransactionBegin(); // 對(duì)detours進(jìn)行初始化.
DetourUpdateThread(GetCurrentThread()); // 更新進(jìn)行detours的線程
// 參數(shù)原有的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;
}
// 一般搞個(gè)空函數(shù)就可以了。如果沒有任何導(dǎo)出函數(shù),您辛苦編寫的DLL將無法工作,還可能為此花上一大堆時(shí)間去排查。像如下聲明一個(gè)就行了:
__declspec(dllexport) int ExportFunc(VOID)
{
return 5;
}
|
|