mHook MinHook API Library Detour EasyHook

来源:互联网 发布:哈密顿圈算法 编辑:程序博客网 时间:2024/06/01 17:21

mHook MinHook API Library Detour EasyHook

 (2015-01-23 08:36:10)
 分类: 软件_Software

http://codefromthe70s.org/mhook23.aspx


MinHook - 最小化的 x86/x64 API 钩子库
英文原文:MinHook - The Minimalistic x86/x64 API Hooking Library
下载:
下载文件 - 828.88KB

http://www.codeproject.com/KB/winsdk/libminhook/minhook_110_bin.zip
下载源码 - 795.73KB

http://www.codeproject.com/KB/winsdk/libminhook/minhook_110_src.zip (编译源码需要Boost 1.40.0)


RaMMicHaeL已经为项目建立了分支并取得了很大的进展。我认为他在GitHub上的库是MinHook开发的主力。我推荐你从他的库获取最新的MinHook版本。
RaMMicHaeL的库: https://github.com/RaMMicHaeL/minhook
发行版本:https://github.com/RaMMicHaeL/minhook/releases

 

背景

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

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

MHOOK, AN API HOOKING LIBRARY, V2.3

New version with built-in disassembler and bugfixes. The old, lightweight version is still available here. The previous version (2.2), should you need it for some reason, is here.

Mhook is a library for installing API hooks. If you dabble in this area then you’ll already know that Microsoft Research's Detours pretty much sets the benchmark when it comes to API hooking. Why don't we get a comparison out of the way quickly then?

DETOURS VS. MHOOK

Detours is available for free with a noncommercial license but it only supports the x86 platform. Detours can also be licensed for commercial use which also gives you full x64 support, but you only get to see the licensing conditions after signing an NDA.

Mhook is freely distributed under an MIT license with support for x86 and x64.

Detours shies away from officially supporting the attachment of hooks to a running application. Of course, you are free to do it - but if you end up causing a random crash here or there, you can only blame yourself.

Mhook was meant to be able to set and remove hooks in running applications – after all, that’s what you need it for in the real world. It does its best to avoid overwriting code that might be under execution by another thread.

Detours supports transactional hooking and unhooking; that is, setting a bunch of hooks at the same time with an all-or-nothing approach. Hooks will only be set if all of them can be set, otherwise the library will roll back any changes made. Mhook does not do this.

Finally, Mhook is pretty lazy when it comes to managing memory for the trampolines it uses. Detours allocates blocks of memory as needed, and uses the resulting data area to store as many trampolines within as will fit. Mhook, on the other hand, uses one call to VirtualAlloc per hook being set. Every hook needs less than 100 bytes of storage so this is very wasteful, since VirtualAlloc ends up grabbing 64K from the process' virtual address space every time Mhook calls it. (Actual allocated memory will be a single page which is also quite wasteful.) In the end though, this probably does not really matter, unless you are setting a very large number of hooks in an application. Also, this is very easy to fix.

With that out of the way, if you’re still here, let’s delve into it.


翻译:

库的使用

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

#include
#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, 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(&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;
}

 

它是怎样工作的

软件的基本思想和微软的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(http://patkov-site.narod.ru/)先生的“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
 Garfielt

假使original函数包含跳转指令将会怎样?当然,他们应该被修改成与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)


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

>>>>>>>>>>>>>>>>>>>>>


API监控工具(SoftSnoop) V1.3.2 中文版API 调用监控工具

mHook <wbr>MinHook <wbr>API <wbr>Library <wbr>Detour <wbr>EasyHook

SoftSnoop可以监视系统调用了哪些API,从而在SoftICE里可以用这些API设置断点!是一个很好用的可以捕获进程调用的Api的程序,但是我在使用过程中发现其只能对静态导入的Api实现捕获,因此极大地限制了其使用范围,前几天在看了它的源码,分析之后产生了改进的想法,我对其中ApiHook的部分进行了修改,使之能捕获动态加载的dll中的Api调用!
更新日志:
相对于SoftSnoop1.3版新增功能
(1)可以捕获从任一模块产生的到任一模块的Api调用,包括动态加载的模块;可以在调用Api之前输出Api名称,Api所属模块,Api参数及调用这个Api的地址;可以在Api返回之后输出Api返回值;
(2)可以像OllyDbg那样附着到正在运行的目标进程并捕获其Api,可以随意终止和继续捕获,目标进程不受影响;
(3)可以选择是否对目标进程进行调试,对一些检查调试器的程序可以选择不调试目标进程,对Api捕获没有影响;由于修改了ApiHook方式,因此对于加过壳或修改了输入表的程序也可以进行Api捕获;
(4)添加了中文版;
(5)解决了1.3版中在加载目标进程时偶尔会产生内存访问越界的问题;
怎么用?
如果想让SoftSnoop显示自己感兴趣的Api的参数,请看ApiDef目录下的SS.txt;如果只想捕获特点模块或特定的Api,或者只想捕获来自某些特定模块的Api,请在选项窗口进行设置;其它使用方法都很简单,界面上一目了然,也不用多说了;另外大家觉得有什么不方便的地方自己DIY即可,这就是有源码的好处:)
实现原理介绍:
SoftSnoop1.3版的ApiHook是通过修改目标进程的输入表的方法来实现的,这种方法的优点在于实现简单,但缺点在于无法捕获动态加载的模块中的Api,另外对于加过壳的程序和修改了输入表的程序也是无效的。
另一种应用层的ApiHook方法是修改目标Api的前几条指令为跳转指令,当产生Api调用时先跳到我们的程序,我们处理之后再跳回去继续执行;这种ApiHook的实现方法在这里有详细的介绍:http://中的ScanModules()函数,大家如果感兴趣可以看看;
(2)对于扫描得到的每个模块,通过读取其输出表来获得其输出的Api的入口地址和名称,然后将这些Api添加到待Hook列表中;
对于问题2,分两种情况进行介绍:
(1)由SoftSnoop创建的目标进程,通过在修改其输入表来实现其加载我们的ApiSnoop.dll,修改时机是目标进程已经被加载到内存中但还没有执行的时候,具体实现方法是创建目标进程时加CREATE_SUSPENDED参数,然后修改其输入表,把我们的ApiSnoop.dll加进去,然后让目标进程继续执行,这样我们的ApiSnoop.dll就会被加载到目标进程的地址空间;这里要说明的是,SoftSnoop1.3版使用的方法没有公开源码,是封装在ForceLibrary.dll里的,其缺点在于必须对目标进程进行调试才能实现加载ApiSnoop.dll,恰好Detours库中提供了DetourCreateProcessWithDll()函数能够实现这个功能,因此我就直接使用了这种方法;
(2)附着到正在执行的目标进程http://www.168ftp.com/,通过CreateRemoteThread实现让目标进程加载我们的ApiSnoop.dll;
其它一些问题的实现方法是:Api调用的参数和来源地址可以通过读堆栈得到,至于如何让Api返回时执行我们的代码从而获得其返回地址,我沿用了1.3版的实现方法:修改堆栈中的返回地址。
另外1.3版里主程序和SoftSnoop.dll是通过消息进行交互的,这种交互只能是单向的,在1.3.2版里我使用了事件+内存映射文件的方法实现了双向交互。
下一步的工作:
如果大家觉得这个工具还行,还值得继续开发的话,请给我支持和建议。
我想可能的改进之处包括:
(1)在Api调用之前停下来,允许用户查看和修改Api调用的参数;在Api调用返回的时候可以停下来,允许用户查看参数变化并修改Api返回值;
(2)增强调试支持,因为修改了目标进程加载方式,因此原先1.3版中的一些设置断点的功能可能有所减弱;
(3)增加反汇编支持,不过这个可能没有必要,使用OllyDbg就行了:)

 

>>>>>>>>>>>>>>>>>>>>>>>

 

 

外加对一个Dll依赖:
对Exe文件用Lord PE的PE编辑器添加对一个DLL的依赖。Exe脱壳可以直接用Peid搞定。HookFunction及UnhookFunction是inline hook函数,可以在mhook等开源hook库中找到类似函数。

 

 

>>>>>>>>>>>>>>>>>>

 

最近看见不少关于APIHook的帖子,但是大部分都只做了一个简单的描述,即使有些提供了例子的帖子,也多多少少有些执行上问题。
 
因此,本人根据以前写的半成品,并参照《Windows核心技术开发》的22章节,对原有代码进行重新设计,在进行详细的测试之后,基本的框架已经可以出炉了。
 
首先,介绍一下APIHook。进行API挂接时,所需的基本函数有【LoadLibraryA,LoadLibraryW,LoadLibraryExA,LoadLibraryExW,GetProcAddress】。
这5个API是不可缺少的,其余的API挂接都可以算作扩展,因此构架上将其分成两个以上的模块是最合适,也是最容易扩展的。
 
基于上述考虑,我们将实现部分分成APIHook.DLL和UserAPIHook.DLL两个部分。当然用户扩展模块还可以按照类别分成User1,User2,...UserN等多个模块,这样的话,可以在增加新的API挂接时,可以不用修改原有的DLL。
 
好了我们首先来介绍一下APIHook.DLL的构架。
其中包含接口和实现两个部分。
1)接口
对于这次实现的DLL,只要当前EXE的进程加载该DLL,DLL的初始化处理中,会自动扫描进程中所有的DLL模块,并且将上述5个基本函数挂接上。
原理也很简单,就是将所有的DLL的输入节都进行修改。
基于这一点,如果只是在本进程内使用的话,也不需要什么接口。因此,此处实现的接口是用来挂接其他进程的,技术上将DLL注入其他进程有很多方法,这里只实现两种,HOOK和CreateRemoteThread。
 
1.1)HOOK
使用系统的SetWindowsHookEx方法来实现,接口上提供以下接口:


代码:

BOOL SetupHook(DWORD dwThreadID, HMODULE hModule); BOOL SetupHookWithHWnd(HWND hWnd, HMODULE hModule); BOOL SetupUnhook();

前面两个用来设置Hook,可以根据线程ID或者窗口句柄来。其中第二个参数后面再介绍。
最后一个用来取消Hook。
 
前面两个调用,最终是通过内部函数SetupHook来实现。

 

>>>>>>>>>>>>>>>>>>>>>>>>>>

 

EasyHook远程代码注入

http://blog.csdn.net/baggiowangyu/article/details/7675098

 

    最近一段时间由于使用MinHook的API挂钩不稳定,经常因为挂钩地址错误而导致宿主进程崩溃。听同事介绍了一款智能强大的挂钩引擎EasyHook。它比微软的detours好的一点是它的x64注入支持是免费开源的。不想微软的detours,想搞x64还得购买。
    好了,闲话不多说,先下载EasyHook的开发库,当然有兴趣的同学可以下载源码进行学习。下载地址:http://easyhook.codeplex.com/releases/view/24401。我给的这个是2.6版本的。
    EasyHook提供了两种模式的注入管理。一种是托管代码的注入,另一种是非托管代码的注入。我是学习C++的,所以直接学习了例子中的非托管项目UnmanagedHook。里面给了一个简单的挂钩MessageBeep API的示例。我需要将其改造成支持远程注入的。下面先给出钩子DLL代码:

 

>>>>>>>>>>>>>>>>>>>

 

Hook DirectX EndScene函数,即游戏在绘图结束后调用的函数,而且游戏会在一秒内经常调用这个函数,简直就是把它当消息队列使!
https://github.com/spazzarama/Direct3DHook


而EasyHook的使用方式和详细原理,请参考文档:
http://www.codeproject.com/Articles/27637/EasyHook-The-reinvention-of-Windows-API-hooking

0 0
原创粉丝点击