微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

MinHook - 最小化的 x86/x64 API 钩子库

背景

对windows API钩子感兴趣的人都知道有一个优秀的库被微软命名为'Detours'。它真的很有用,但是它的免费版本(Express)是不支持X64。它的收费版本(Professional)支持x64,但是对我来说太昂贵了。微软说它值一万美元。

因此我决定从零开始写我自己的库。但是我没有将Detours的功能完美的复制到我的库中,它仅有API钩子功能,因为这就是我想要的。

 

下载地址:http://www.codeproject.com/Articles/44326/MinHook-The-Minimalistic-x-x-API-Hooking-Libra

库的使用

请看下面的示例代码。这就是全部。有一次,我修复了一个严重的缺陷并改变了接口。它挂钩在MessageBoxW()函数修改了它的文本。它包含在源码中。请在X64和X86模式下尝试它。

#include <Windows.h>
#include "MinHook.h"
 
#if defined _M_X64
#pragma comment(lib,"libminHook.x64.lib")
#elif defined _M_IX86
#pragma comment(lib,"libminHook.x86.lib")
#endif
 
typedef int (WINAPI *MESSAGEBoxW)(HWND,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_CreateHook(&MessageBoxW,&DetourMessageBoxW,reinterpret_cast<void**>(&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,MB_OK);
 
    // Uninitialize MinHook.
    if (MH_Uninitialize() != MH_OK)
    {
        return 1;
    }
 
    return 0;
}

它是怎样工作的

软件的基本思想和微软的Detours以及Daniel Pistelli先生Hook-Engine是一样的。它用一条JMP(无条件跳转)到绕道函数的指令替换目标函数最初的几条指令。这是一个安全的,稳定的并经过验证的方法

 

重写目标函数

在X64/X86指令集中,有多种形式的JMP指令。我决定总是使用5个字节的32位相对JMP指令。实际上它是可用的最短的JMP指令。在这种情况下,越短越好。

在X86模式中,32位相对JMP覆盖了整个地址空间。因为在相对地址计算中溢出的位被忽略,所以在X86模式中,函数的地址是不重要的

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

但是在X64模式中,存在一个问题。JMP指令和整个地址空间比较仅仅覆盖了很窄的范围。因此我引入了一个新的功能名叫中继函数(Relay Function),它是到绕道函数的64位跳转,被放置在目标函数的附近。幸运的是,VirtualAlloc() API函数能接受分配的地址,在目标函数附近需找未分配的区域是容易的工作。

; 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

构建Trampoline函数

目标函数是detour的覆写。但是我们如何调用原始目标函数呢?在微软的Detours中有一个叫做“Trampoline”的函数(也被Pistelli先生称为“桥梁函数”)。这是原函数无条件跳转一个索引克隆,用来恢复到原函数。实际中的例子在这里。它们是MinHook内部所创造的具体实现。

我们应该拆机原函数以了解临界和被复制的指令。我采纳了Vyacheslav Patkov先生的“Hacker反汇编引擎(HDE)”作为反汇编工具。它体积小巧、轻量级且符合我的目标。我为检验目的反汇编了上千个Windows XP、Vista和7的API函数,并为它们构建了trampoline函数

; 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

假使orihinal函数包含跳转指令将会怎样?当然,他们应该被修改成与original有一样的地址。

; 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

RIP相对寻址模式对于x64模式一直是一个问题。他们的相对地址应该被修改为指向一样的地址。 

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

结论

虽然这个库很小,很简单,但是我认为他是很实用的。请享受它吧!



版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐