原文链接:https://www.cnblogs.com/zhaotianff/p/18073138
有前面的文章中,我介绍了一个Windows API的监控工具(API Monitor)的使用。它内部就是使用了Hook机制,能Hook Windows API,能在我们钩选的API函数被调用时,进入中断。
这里我们借助github上一个库来自己实现一下类似API Monitor的功能。
项目地址:https://github.com/TsudaKageyu/minhook
The Minimalistic x86/x64 API Hooking Library for Windows
完整的介绍可以看到:MinHook - The Minimalistic x86/x64 API Hooking Library - CodeProject
下面的内容来自作者codeproject原文,感兴趣的可以自行阅读,就不作翻译了。
As you who are interested in Windows API hooking know, there is an excellent library for it by Microsoft Research named Detours. It's really useful, but its free edition (called 'Express') doesn't support the x64 environment. Though its commercial edition (called 'Professional') supports x64, it's too expensive for me to afford. It costs around US$10,000!
So I decided to write my own library or "poorman's Detours" from scratch. But I haven't designed my library as the perfect clone of Detours. It has just the API hooking functionality because that's all I want.
As of April 2016, this library is used in some projects: 7+ Taskbar Tweaker, Better Explorer, ConEmu, DxWnd, Mumble, NonVisual Desktop Access, Open Broadcaster Software, QTTabBar, x360ce, mods for some games and more. I am happy finding that this project is helpful to people.
The basic concept of this software is the same as Microsoft Detours and Daniel Pistelli's Hook-Engine. It replaces the prologue of the target function with x86's JMP
(unconditional jump) instruction to the detour function. It's safe, stable, and a proven method.
In the x64/x86 instruction set, there are some forms of the JMP
instruction. I decided to always use a 32 bit relative JMP
of 5 bytes. It's the shortest form that can be used in reality. In this case, shorter is better.
In x86 mode, 32bit relative JMP
covers the whole address space. Because the overflowed bits are just ignored in the relative address arithmetic, in x86 mode, the function addresses don't matter.
ASM
; x86 mode (assumed that the target function is at 0x40000000) ; 32bit relative JMPs of 5 bytes cover whole address space 0x40000000: E9 FBFFFFBF JMP 0x0 (EIP+0xBFFFFFFB) 0x40000000: E9 FAFFFFBF JMP 0xFFFFFFFF (EIP+0xBFFFFFFA) ; Shorter forms are useless in this case ; 8bit JMPs of 2 bytes cover -126 ~ +129 bytes 0x40000000: EB 80 JMP 0x3FFFFF82 (EIP-0x80) 0x40000000: EB 7F JMP 0x40000081 (EIP+0x7F) ; 16bit JMPs of 4 bytes cover -32764 ~ +32771 bytes 0x40000000: 66E9 0080 JMP 0x3FFF8004 (EIP-0x8000) 0x40000000: 66E9 FF7F JMP 0x40008003 (EIP+0x7FFF)
But, in x64 mode, it's a problem. It only covers the very narrow range in comparison with the whole address space. So I introduced a new function called 'Relay Function' which just has a 64 bit jump to the detour function and is placed near the target function. Fortunately, the VirtualAlloc()
API function can accept the address to allocate, and it's an easy job to look for unallocated regions near the target function.
ASM
; x64 mode (assumed that the target function is at 0x140000000) ; 32bit relative JMPs of 5 bytes cover about -2GB ~ +2GB 0x140000000: E9 00000080 JMP 0xC0000005 (RIP-0x80000000) 0x140000000: E9 FFFFFF7F JMP 0x1C0000004 (RIP+0x7FFFFFFF) ; Target function (Jump to the Relay Function) 0x140000000: E9 FBFF0700 JMP 0x140080000 (RIP+0x7FFFB) ; Relay function (Jump to the Detour Function) 0x140080000: FF25 FAFF0000 JMP [0x140090000 (RIP+0xFFFA)] 0x140090000: xxxxxxxxxxxxxxxx ; 64bit address of the Detour Function
The target function is overwritten to detour. And, how do we call the original target function? In many cases, we have to call the original function from within the detour function. MinHook has a function called "Trampoline Function" for the purpose of calling the original function (and Daniel Pistelli call it "Bridge Function"). This is a clone of the prologue of the original function with the trailing unconditional jump for resuming into the original function. The real world examples are here. They are what MinHook
actually creates.
We should disassemble the original function to know the instructions boundary and the instructions to be copied. I adopted Vyacheslav Patkov's Hacker Disassembler Engine (HDE) as a disassembler. It's small, light-weight and suitable for my purpose. I disassembled thousands of API functions on Windows XP, Vista, and 7 for examination purposes, and built the trampoline function for them.
ASM
; Original "USER32.dll!MessageBoxW" in x64 mode 0x770E11E4: 4883EC 38 SUB RSP, 0x38 0x770E11E8: 4533DB XOR R11D, R11D ; Trampoline 0x77064BD0: 4883EC 38 SUB RSP, 0x38 0x77064BD4: 4533DB XOR R11D, R11D 0x77064BD7: FF25 5BE8FEFF JMP QWORD NEAR [0x77053438 (RIP-0x117A5)] ; Address Table 0x77053438: EB110E7700000000 ; Address of the Target Function +7 (for resuming) ; Original "USER32.dll!MessageBoxW" in x86 mode 0x7687FECF: 8BFF MOV EDI, EDI 0x7687FED1: 55 PUSH EBP 0x7687FED2: 8BEC MOV EBP, ESP ; Trampoline 0x0014BE10: 8BFF MOV EDI, EDI 0x0014BE12: 55 PUSH EBP 0x0014BE13: 8BEC MOV EBP, ESP 0x0014BE15: E9 BA407376 JMP 0x7687FED4
What if the original function contains the branch instructions? Of course, they should be modified to point to the same address as the original.
ASM
Shrink ▲
; Original "kernel32.dll!IsProcessorFeaturePresent" in x64 mode 0x771BD130: 83F9 03 CMP ECX, 0x3 0x771BD133: 7414 JE 0x771BD149 ; Trampoline ; (Became a little complex, because 64 bit version of JE doesn't exist) 0x77069860: 83F9 03 CMP ECX, 0x3 0x77069863: 74 02 JE 0x77069867 0x77069865: EB 06 JMP 0x7706986D 0x77069867: FF25 1BE1FEFF JMP QWORD NEAR [0x77057988 (RIP-0x11EE5)] 0x7706986D: FF25 1DE1FEFF JMP QWORD NEAR [0x77057990 (RIP-0x11EE3)] ; Address Table 0x77057988: 49D11B7700000000 ; Where the original JE points. 0x77057990: 35D11B7700000000 ; Address of the Target Function +5 (for resuming) ; Original "gdi32.DLL!GdiFlush" in x86 mode 0x76479FF4: E8 DDFFFFFF CALL 0x76479FD6 ; Trampoline 0x00147D64: E8 6D223376 CALL 0x76479FD6 0x00147D69: E9 8B223376 JMP 0x76479FF9 ; Original "kernel32.dll!CloseProfileUserMapping" in x86 mode 0x763B7918: 33C0 XOR EAX, EAX 0x763B791A: 40 INC EAX 0x763B791B: C3 RET 0x763B791C: 90 NOP ; Trampoline (Additional jump is not required, because this is a perfect function) 0x0014585C: 33C0 XOR EAX, EAX 0x0014585E: 40 INC EAX 0x0014585F: C3 RET
The RIP relative addressing mode is also a problem in the x64 mode. Their relative addresses should be modified to point to the same addresses.
ASM
; Original "kernel32.dll!GetConsoleInputWaitHandle" in x64 mode 0x771B27F0: 488B05 11790C00 MOV RAX, [0x7727A108 (RIP+0xC7911)] ; Trampoline 0x77067EB8: 488B05 49222100 MOV RAX, [0x7727A108 (RIP+0x212249)] 0x77067EBF: FF25 4BE3FEFF JMP QWORD NEAR [0x77056210 (RIP-0x11CB5)] ; Address Table 0x77056210: F7271B7700000000 ; Address of the Target Function +7 (for resuming) ; Original "user32.dll!TileWindows" in x64 mode 0x770E023C: 4883EC 38 SUB RSP, 0x38 0x770E0240: 488D05 71FCFFFF LEA RAX, [0x770DFEB8 (RIP-0x38F)] ; Trampoline 0x77064A80: 4883EC 38 SUB RSP, 0x38 0x77064A84: 488D05 2DB40700 LEA RAX, [0x770DFEB8 (RIP+0x7B42D)] 0x77064A8B: FF25 CFE8FEFF JMP QWORD NEAR [0x77053360 (RIP-0x11731)] ; Address Table 0x77053360: 47020E7700000000 ; Address of the Target Function +11 (for resuming)
这个项目已经有很多年了,我大概是21年,通过github上的一个项目了解到这个库,
github上其实也有一些类似的库,如
EasyHook
https://github.com/EasyHook/EasyHook
Detours
https://github.com/microsoft/Detours
本文介绍的是minHook使用
将代码从github导下来(也可以使用动态库和静态库的方式,这里不作介绍了。我这里直接将源码放到项目里一起编译。)
另外,也可以使用nuget包管理器进行下载:NuGet Gallery | minhook 1.3.3
1、声明要HOOK的函数指针,如这里以MessageBoxW为例
typedef int (WINAPI* MESSAGEBOXW)(HWND, LPCWSTR, LPCWSTR, UINT);
2、定义函数指针变量,这个指针将会指向原始的API调用
MESSAGEBOXW fpMessageBoxW = NULL;
3、定义hook后的函数(也就是在调用MessageBoxW时,不会再调用原始函数,而是调用我们定义的函数)
这里我们在hook的函数里还是调用原始函数,但是将消息框内容改成了"Hooked"
int WINAPI DetourMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) { return fpMessageBoxW(hWnd, L"Hooked!", lpCaption, uType); }
4、初始化minHook
if (MH_Initialize() != MH_OK) { return ; }
5、创建apihook(创建好后处理禁用的状态,需要启用才能生效)
if (MH_CreateHookEx(&MessageBoxW, &DetourMessageBoxW, &fpMessageBoxW) != MH_OK) { return; }
6、启用apihook
if (MH_EnableHook(&MessageBoxW) != MH_OK) { return; }
7、使用完成后,禁用apihook,并卸载minHook
if (MH_DisableHook(&MessageBoxW) != MH_OK) { return ; } if (MH_Uninitialize() != MH_OK) { return ; }
另外minHook提供了一个可以指定模块和函数名的版本MH_CreateHookApi
MH_STATUS WINAPI MH_CreateHookApi( LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal);
在前面的使用步骤中,调用的是MH_CreateHookEx,直接将函数以参数传递,MH_CreateHookApi函数可以指定模块路径和模块中导出的函数名。
还可以使用C++的模板功能进行一些简化,如下:
template
inline MH_STATUS MH_CreateHookEx(LPVOID pTarget, LPVOID pDetour, T** ppOriginal)
{
return MH_CreateHook(pTarget, pDetour, reinterpret_cast(ppOriginal));
}
//原始调用
if (MH_CreateHook(&MessageBoxW, &DetourMessageBoxW,
reinterpret_cast(&fpMessageBoxW)) != MH_OK)
{
return ;
}
//简化后的调用
if (MH_CreateHookEx(&MessageBoxW, &DetourMessageBoxW, &fpMessageBoxW) != MH_OK)
{
return;
}
template
inline MH_STATUS MH_CreateHookApiEx(
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, T** ppOriginal)
{
return MH_CreateHookApi(
pszModule, pszProcName, pDetour, reinterpret_cast(ppOriginal));
}
//原始调用
if (MH_CreateHookApi(L"user32","MessageBoxW", &DetourMessageBoxW,
reinterpret_cast(&fpMessageBoxW)) != MH_OK)
{
return;
}
//简化后的调用
if (MH_CreateHookApiEx(
L"user32", "MessageBoxW", &DetourMessageBoxW, &fpMessageBoxW) != MH_OK)
{
return;
}
创建一个C++控制台程序,将前面导下来的src和include里面的文件复制到工程中,添加如下代码(来自codeproject):
这段代码的作用就是先在Hook的情况下,调用MessageBoxW函数,然后在未Hook的情况下,再调用MessageBoxW函数
#include
#include "minHook/MinHook.h"
template
inline MH_STATUS MH_CreateHookEx(LPVOID pTarget, LPVOID pDetour, T** ppOriginal)
{
return MH_CreateHook(pTarget, pDetour, reinterpret_cast(ppOriginal));
}
template
inline MH_STATUS MH_CreateHookApiEx(
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, T** ppOriginal)
{
return MH_CreateHookApi(
pszModule, pszProcName, pDetour, reinterpret_cast(ppOriginal));
}
typedef int (WINAPI* MESSAGEBOXW)(HWND, LPCWSTR, LPCWSTR, UINT);
// Pointer for calling original MessageBoxW.
MESSAGEBOXW fpMessageBoxW = NULL;
// Detour function which overrides MessageBoxW.
int WINAPI DetourMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType)
{
return fpMessageBoxW(hWnd, L"Hooked!", lpCaption, uType);
}
int main()
{
// Initialize MinHook.
if (MH_Initialize() != MH_OK)
{
return 1;
}
// Create a hook for MessageBoxW, in disabled state.
if (MH_CreateHookEx(&MessageBoxW, &DetourMessageBoxW, &fpMessageBoxW) != MH_OK)
{
return 1;
}
// Enable the hook for MessageBoxW.
if (MH_EnableHook(&MessageBoxW) != MH_OK)
{
return 1;
}
// Expected to tell "Hooked!".
MessageBoxW(NULL, L"Not hooked...", L"MinHook Sample", MB_OK);
// Disable the hook for MessageBoxW.
if (MH_DisableHook(&MessageBoxW) != MH_OK)
{
return 1;
}
// Expected to tell "Not hooked...".
MessageBoxW(NULL, L"Not hooked...", L"MinHook Sample", MB_OK);
// Uninitialize MinHook.
if (MH_Uninitialize() != MH_OK)
{
return 1;
}
return 0;
}
示例代码
MinHook - The Minimalistic x86/x64 API Hooking Library - CodeProject