天天看点

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