什么是Detours
简单地说,Detours是微软提供的一个开发库,使用它可以简单、高校、稳定地实现API HOOK的功能。
Detours是一个可以在x86、x64和IA64平台上测试任意Win32函数的程序开发库。它可以通过为目标函数重写在内存中的代码而达到拦截Win32函数的目的。Detours还可以将任意的DLL或数据片段(称之为有效载荷)注入到任意Win32二进制文件中。Detours 被广泛应用在微软内部和其他行业中。
在使用 Detours 劫持之前必须得拥有这两个东西:detours.h 和 detours.lib
从GitHub上下载源码
https://github.com/microsoft/Detours
打开VS的开发人员命令提示符
cd 进入下载的目录 G:\C++工具源码\Detours-master
键入 nmake 开始编译
编译完成后
bin.X86 里面是编译出来的Demo
lib.X86 detours静态库
include 头文件
将这2个文件添加到自己的项目中
LONG DetourAttach(
PVOID * ppPointer,
PVOID pDetour
);
这个函数的职责是挂接目标API,函数的第一个参数是一个指向将要被挂接函数地址的函数指针,第二个参数是指向实际运行的函数的指针,一般来说是我们定义的替代函数的地址。但是,在挂接开始之前,还有以下几件事需要完成: 需要对detours进行初始化. 需要更新进行detours的线程. 这些可以调用以下函数很容的做到:
DetourRestoreAfterWith() //恢复之前状态,避免反复拦截
DetourTransactionBegin() //开始劫持
DetourUpdateThread(GetCurrentThread())//刷新当前的线程
DetourTransactionCommit() //提交修改,立即HOOk
在这两件事做完以后,detour函数才是真正地附着到目标函数上。在此之后,调用DetourTransactionCommit()是detour函数起作用并检查函数的返回值判断是正确还是错误。
#include "stdafx.h"
#include<detours.h>
#include<Windows.h>
#pragma comment(lib,"detours.lib")
void HookOn();
void HookOff();
static int (WINAPI *OldMesssageBoxA)
(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
) = MessageBoxA;
static int (WINAPI *OldMesssageBoxW)
(
HWND hWnd,
LPCWSTR lpText,
LPCWSTR lpCaption,
UINT uType
) = MessageBoxW;
// 注意了,自定义函数得和被 HOOK 的函数一样,否则会发生异常。
int WINAPI MyFunction0
(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);
int WINAPI MyFunction1
(
HWND hWnd,
LPCWSTR lpText,
LPCWSTR lpCaption,
UINT uType
);
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
HookOn();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
HookOff();
break;
}
return TRUE;
}
void HookOn()
{
//开始事务
DetourTransactionBegin();
//更新线程信息
DetourUpdateThread(GetCurrentThread());
//将拦截的函数附加到原函数的地址上,这里可以拦截多个函数。
DetourAttach(&(PVOID&)OldMesssageBoxA, MyFunction0);
DetourAttach(&(PVOID&)OldMesssageBoxW, MyFunction1);
//结束事务
DetourTransactionCommit();
}
void HookOff()
{
//开始事务
DetourTransactionBegin();
//更新线程信息
DetourUpdateThread(GetCurrentThread());
//将拦截的函数从原函数的地址上解除,这里可以解除多个函数。
DetourDetach(&(PVOID&)OldMesssageBoxA, MyFunction0);
DetourDetach(&(PVOID&)OldMesssageBoxW, MyFunction1);
//结束事务
DetourTransactionCommit();
}
// 调用被 HOOK 的函数可以用被 HOOK 函数的指针,不能用原函数。
int WINAPI MyFunction0(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType)
{
return OldMesssageBoxA(NULL, "Hooking your MessageBoxA!", "Warming", MB_OKCANCEL);
}
int WINAPI MyFunction1(HWND hWnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType)
{
return OldMesssageBoxW(NULL, L"Hooking your MessageBoxW!", L"Warming", MB_OKCANCEL);
}
原理
Detours定义了三个概念:
(1) Target函数:要拦截的函数,通常为Windows的API。
(2) Trampoline函数:Target函数的复制品。因为Detours将会改写Target函数,所以先把Target函数复制保存好,一方面仍然保存Target函数的过程调用语义,另一方面便于以后的恢复。
(3) Detour 函数:用来替代Target函数的函数。
Detours在Target函数的开头加入JMP Address_of_ Detour_ Function指令(共5个字节)把对Target函数的调用引导到自己的Detour函数, 把Target函数的开头的5个字节加上JMP Address_of_ Target _ Function+5作为Trampoline函数。例子如下:
拦截前:Target _ Function:
;Target函数入口,以下为假想的常见的子程序入口代码
push ebp
mov ebp, esp
push eax
push ebx
Trampoline:
;以下是Target函数的继续部分
……
拦截后: Target _ Function:
jmp Detour_Function
Trampoline:
;以下是Target函数的继续部分
……
Trampoline_Function:
; Trampoline函数入口, 开头的5个字节与Target函数相同
push ebp
mov ebp, esp
push eax
push ebx
;跳回去继续执行Target函数
jmp Target_Function+5
HOOK后的MessageBoxA
替换后的MyFunction0
调用MessageBox
上面是5字节的HOOK,如果函数的开始5字节不是完整的汇编指令集,Detours会自动Hook大于5字节且在保证最少字节数的情况下汇编指令完整的。
HOOK之前
HOOK之后 HOOK了8字节
利用Detours实现任意位置的HOOK
PVOID g_pTestHook;
void TestSrc()
{
MessageBoxA(NULL, "TestSrc", "", 0);
}
void __declspec(naked) TestHooK()
{
MessageBoxA(NULL, "Test Src Start", "", 0);
MessageBoxA(NULL, "Test Src End", "", 0);
_asm {
jmp g_pTestHook
}
}
void HookOn()
{
TestSrc();
g_pTestHook = PVOID((DWORD)TestSrc + 1); //HOOK 函数的第二个字节
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&g_pTestHook, TestHooK);
DetourTransactionCommit();
TestSrc();
}
库函数介绍
HOOK相关的函数
DetourTransactionBegin
LONG DetourTransactionBegin(VOID);
//如果成功,则返回NO_ERROR;否则,返回0。否则,返回错误代码。
开始事务
开始事务之后,程序可以调用 DetourAttach或 DetourAttachEx API HOOK目标函数,调用DetourDetach API恢复目标函数,或者调用 DetourUpdateThread API在事务更新中线程。
在程序调用DetourTransactionCommit或 DetourTransactionCommitEx API 提交事务之后,附加,分离和线程操作才会生效 。或者,程序可以使用DetourTransactionAbort API 中止事务 。
DetourUpdateThread
LONG DetourUpdateThread(
_In_ HANDLE hThread //待处理事务更新线程的句柄
);
//如果成功,则返回NO_ERROR;否则,返回0。否则,返回错误代码。
DetourUpdateThread
由DetourTransactionBegin API 打开的事务提交时,使用指定的线程进行更新 。
提交事务时,不会更新未在事务中登记的线程。结果,他们可能试图执行新旧代码的非法组合。
DetourAttach
LONG DetourAttach(
_Inout_ PVOID * ppPointer, //被HOOk函数地址 这个一个二级指针成功调用之后被改变成新的函数地址 前5个字节然后jmp到原函数的第六个字节
_In_ PVOID pDetour //替换的函数地址
);
HOOK函数
DetourAttachEx
LONG DetourAttachEx(
_Inout_ PVOID * ppPointer, //被HOOk函数地址 这个一个二级指针成功调用之后被改变成新的函数地址 前5个字节然后jmp到原函数的第六个字节
_In_ PVOID pDetour, //替换的函数地址
_Out_opt_ PDETOUR_TRAMPOLINE * ppRealTrampoline //用于接收中转函数地址的信息 包括中转函数的地址,代码(前5个字节)等
_Out_opt_ PVOID * ppRealTarget //用于接收目标函数的最终地址
_Out_opt_ PVOID * ppRealDetour //用于接收替换函数的最终地址
);
struct _DETOUR_TRAMPOLINE
{
BYTE rbCode[30]; // target code + jmp to pbRemain 中转函数地址
BYTE cbCode; // size of moved target code. 修改前几个字节 5
BYTE cbCodeBreak; // padding to make debugging easier.
BYTE rbRestore[22]; // original target code.
BYTE cbRestore; // size of original target code.
BYTE cbRestoreBreak; // padding to make debugging easier.
_DETOUR_ALIGN rAlign[8]; // instruction alignment array.
PBYTE pbRemain; // first instruction after moved code. [free list]
PBYTE pbDetour; // first instruction of detour function.
};
HOOK函数
DetourDetach
LONG DetourDetach(
_Inout_ PVOID * ppPointer, //被HOOK函数地址的二级指针
_In_ PVOID pDetour //替换函数的地址
);
取消HOOK
用于查找目标函数的API
DetourFindFunction
PVOID DetourFindFunction(
_In_ LPCSTR pszModule, //应在其中找到函数的DLL或二进制文件的路径。
_In_ LPCSTR pszFunction //要找到的函数的名称。
);
//如果成功,则返回函数pszFunction的地址;否则,返回NULL。
DetourFindFunction
尝试通过动态链接命名模块的导出表来检索命名函数的函数指针,如果失败,则使用DbgHelp API(如果可用)调试符号。
DetourCodeFromPointer
PVOID DetourCodeFromPointer(
_In_ PVOID pPointer, //指向函数的指针。
_Out_opt_ PVOID *ppGlobals //变量,用于接收函数的全局数据的地址。
);
//返回指向实现该功能的代码的指针。
返回一个指向实现函数指针的代码的指针。
DetourCodeFromPointer
返回指向实现函数指针所指向的函数的代码的指针。当二进制文件与DLL静态链接时,指向DLL函数的指针通常不指向DLL中的代码,而是指向二进制文件的导入表中的跳转语句。DetourCodeFromPointer
返回实际目标函数的地址,而不是跳转语句。
用于获取加载的二进制文件信息的API
DetourEnumerateModules
HMODULE DetourEnumerateModules(
_In_opt_ HMODULE hModuleLast //终止模块的句柄,NULL枚举全部
);
枚举模块
DetourGetEntryPoint
PVOID DetourGetEntryPoint(
_In_opt_ HMODULE hModule //模块句柄
);
返回模块的入口点。
DetourGetModuleSize
ULONG DetourGetModuleSize(
_In_ HMODULE hModule //模块句柄
);
返回模块的加载大小。
DetourEnumerateExports
BOOL DetourEnumerateExports(
_In_ HMODULE hModule, //模块的句柄
_In_opt_ PVOID pContext, //程序的上下文
_In_ PF_DETOUR_ENUMERATE_EXPORT_CALLBACK pfExport //枚举回调函数
);
typedef BOOL (CALLBACK *PF_DETOUR_ENUMERATE_EXPORT_CALLBACK)(_In_opt_ PVOID pContext,
_In_ ULONG nOrdinal,
_In_opt_ LPCSTR pszName,
_In_opt_ PVOID pCode);
枚举模块的导出函数
DetourEnumerateImports
BOOL DetourEnumerateImports(
_In_opt_ HMODULE hModule, //模块的句柄
_In_opt_ PVOID pContext, //特定于程序的上下文,该上下文将传递给pfImportFile和 pfImportFunc
_In_opt_ PF_DETOUR_IMPORT_FILE_CALLBACK pfImportFile, //每个模块导入的文件将调用一次回调函数
_In_opt_ PF_DETOUR_IMPORT_FUNC_CALLBACK pfImportFunc //每个模块导入的函数将调用一次回调函数
);
typedef BOOL (CALLBACK *PF_DETOUR_IMPORT_FILE_CALLBACK)(_In_opt_ PVOID pContext,
_In_opt_ HMODULE hModule,
_In_opt_ LPCSTR pszFile);
typedef BOOL (CALLBACK *PF_DETOUR_IMPORT_FUNC_CALLBACK)(_In_opt_ PVOID pContext,
_In_ DWORD nOrdinal,
_In_opt_ LPCSTR pszFunc,
_In_opt_ PVOID pvFunc);
枚举导入函数
DetourEnumerateImportsEx
BOOL DetourEnumerateImportsEx(
_In_opt_ HMODULE hModule, //模块的句柄
_In_opt_ PVOID pContext, //上下文将传递给pfImportFile和 pfImportFunc
_In_opt_ PF_DETOUR_IMPORT_FILE_CALLBACK pfImportFile, //每个模块导入的文件将调用一次回调函数
_In_opt_ PF_DETOUR_IMPORT_FUNC_CALLBACK_EX pfImportFunc //每个模块导入的函数将调用一次回调函数
);
typedef BOOL (CALLBACK *PF_DETOUR_IMPORT_FUNC_CALLBACK_EX)(_In_opt_ PVOID pContext,
_In_ DWORD nOrdinal,
_In_opt_ LPCSTR pszFunc,
_In_opt_ PVOID* ppvFunc);
枚举从模块导入函数
DetourFindPayload
_Writable_bytes_(*pcbData)
_Readable_bytes_(*pcbData)
_Success_(return != NULL)
PVOID DetourFindPayload(
_In_opt_ HMODULE hModule, //模块句柄
_In_ REFGUID rguid,
_Out_ DWORD * pcbData
);
定位二进制文件中映射的payloads
DetourGetContainingModule
HMODULE DetourGetContainingModule(
_In_ PVOID vpAddr //函数地址
);
根据函数地址,查找所在的模块
DetourGetSizeOfPayloads
DWORD DetourGetSizeOfPayloads(
_In_ HMODULE hModule
);
获取Payload的大小
修改PE文件相关的API
- DetourBinaryOpen打开一个PE二进制文件。
- DetourBinaryEnumeratePayloads枚举二进制文件中的payloads。
- DetourBinaryFindPayload查找指定的payload。
- DetourBinarySetPayload设置或者替换一个指定的payload。
- DetourBinaryDeletePayload删除一个指定的payload。
- DetourBinaryPurgePayloads删除二进制中的所有payloads。
- DetourBinaryEditImportsModifythePEbinaryimporttable。
- DetourBinaryResetImports复位PE二进制导入地址表。
- DetourBinaryWrite把PE映像写入到一个文件中。
- DetourBinaryClose关闭PE二进制映像。
- DetourBinaryBind使用BindImage绑定二进制映像。
DLL和区段注入进程相关的API
DetourCreateProcessWithDllEx
BOOL DetourCreateProcessWithDllEx(
_In_opt_ LPCTSTR lpApplicationName, //CreateProcess API定义的应用程序名称
_Inout_opt_ LPTSTR lpCommandLine, //CreateProcess API定义的命令行
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, //CreateProcess API定义的流程属性
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, //CreateProcess API定义的线程属性
_In_ BOOL bInheritHandles, //CreateProcess API定义的句柄标志
_In_ DWORD dwCreationFlags, //CreateProcess API定义的创建标志
_In_opt_ LPVOID lpEnvironment, //CreateProcess API定义的流程环境变量
_In_opt_ LPCTSTR lpCurrentDirectory, //CreateProcess API定义的当前目录
_In_ LPSTARTUPINFOW lpStartupInfo, //CreateProcess API定义的启动信息
_Out_ LPPROCESS_INFORMATION lpProcessInformation, //CreateProcess API定义的处理句柄信息
_In_ LPCSTR lpDllName, //要插入到新进程中的DLL的路径名。为了同时支持32位和64位应用程序,如果DLL包含32位代码,则DLL名称应以“ 32”结尾;如果DLL包含64位代码,则DLL名称应以“ 64”结尾。如果目标进程的大小与父进程的大小不同,则Detours会自动在路径名中将“ 32”替换为“ 64”或将“ 64”替换为“ 32”。
_In_opt_ PDETOUR_CREATE_PROCESS_ROUTINEW pfCreateProcessW //指向程序特定替代CreateProcess API的指针;如果应使用标准CreateProcess API创建新进程,则为NULL。
);
启动程序并注入DLL
DetourCreateProcessWithDlls
BOOL DetourCreateProcessWithDlls(
_In_opt_ LPCTSTR lpApplicationName,
_Inout_opt_ LPTSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCTSTR lpCurrentDirectory,
_In_ LPSTARTUPINFOW lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation,
_In_ DWORD nDlls,
_In_reads_(nDlls) LPCSTR *rlpDlls,
_In_opt_ PDETOUR_CREATE_PROCESS_ROUTINEW pfCreateProcessW
);
启动程序并注入DLL
DetourCopyPayloadToProcess
BOOL DetourCopyPayloadToProcess(
_In_ HANDLE hProcess, //进程句柄
_In_ REFGUID rguid, //区段id
_In_reads_bytes_(cbData) PVOID pvData, //指向区段的指针
_In_ DWORD cbData //区段的大小
);