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



你可能感兴趣的:(Detours 源码阅读笔记)