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