
HOOK API是一个永恒的话题,如果没有HOOK,许多技术将很难实现,也许根本不能实现。
这里所说的API,是广义上的API,它包括DOS下的中断,WINDOWS里的API、中断服务、IFS和
NDIS过滤等。比如大家熟悉的即时翻译软件,就是靠HOOK TextOut()或ExtTextOut()这两个
函数实现的,在 *** 作系统用这两个函数输出文本之前,就把相应的英文替换成中文而达到即
时翻译;IFS和NDIS过滤也是如此,在读写磁盘和收发数据之前,系统会调用第三方提供的
回调函数来判断 *** 作是否可以放行,它与普通HOOK不同,它是 *** 作系统允许的,由 *** 作系统
提供接口来安装回调函数。
甚至如果没有HOOK,就没有病毒,因为不管是DOS下的病毒或WINDOWS里的病毒,
都是靠HOOK系统服务来实现自己的功能的:DOS下的病毒靠HOOK INT 21来感染文件(文件型病毒),靠HOOK INT 13来感染引导扇区(引导型病毒);WINDOWS下的病毒靠HOOK 系统API(包括RING0层的和RING3层的),或者安装IFS(CIH病毒所用的方法)来感染文件。因此可以说“没有HOOK,就没有今天多姿多彩的软件世界”。
由于涉及到专利和知识产权,或者是商业机密,微软一直不提倡大家HOOK它的系统API,
提供IFS和NDIS等其他过滤接口,也是为了适应杀毒软件和防火墙的需要才开放的。所以在
大多数时候,HOOK API要靠自己的力量来完成。
HOOK API有一个原则,这个原则就是:被HOOK的API的原有功能不能受到任何影响。就象
医生救人,如果把病人身体里的病毒杀死了,病人也死了,那么这个“救人”就没有任何意义了。
如果你HOOK API之后,你的目的达到了,但API的原有功能失效了,这样不是HOOK,而是REPLACE, *** 作系统的正常功能就会受到影响,甚至会崩溃。
HOOK API的技术,说起来也不复杂,就是改变程序流程的技术。在CPU的指令里,有几条
指令可以改变程序的流程:JMP,CALL,INT,RET,RETF,IRET等指令。理论上只要改变API
入口和出口的任何机器码,都可以HOOK,但是实际实现起来要复杂很多,因为要处理好以下问题:
1,CPU指令长度问题,在32位系统里,一条JMP/CALL指令的长度是5个字节,因此你只有替换API
里超过5个字节长度的机器码(或者替换几条指令长度加起来是5字节的指令),否则会影响被更
改的小于5个字节的机器码后面的数条指令,甚至程序流程会被打乱,产生不可预料的后果;
2,参数问题,为了访问原API的参数,你要通过EBP或ESP来引用参数,因此你要非常清楚你的HOOK代码里此时的EBP/ESP的值是多少;
3,时机的问题,有些HOOK必须在API的开头,有些必须在API的尾部,比如HOOK CreateFilaA(),
如果你在API尾部HOOK API,那么此时你就不能写文件,甚至不能访问文件;HOOK RECV(),
如果你在API头HOOK,此时还没有收到数据,你就去查看RECV()的接收缓冲区,里面当然没有
你想要的数据,必须等RECV()正常执行后,在RECV()的尾部HOOK,此时去查看RECV()的缓冲区,
里面才有想要的数据;
4,上下文的问题,有些HOOK代码不能执行某些 *** 作,否则会破坏原API的上下文,原API就失效了;
5,同步问题,在HOOK代码里尽量不使用全局变量,而使用局部变量,这样也是模块化程序的需要;
6,最后要注意的是,被替换的CPU指令的原有功能一定要在HOOK代码的某个地方模拟实现。
下面以ws2_32dll里的send()为例子来说明如何HOOK这个函数:
Exported fn(): send - Ord:0013h
地址 机器码 汇编代码
:71A21AF4 55 push ebp //将被HOOK的机器码(第1种方法)
:71A21AF5 8BEC mov ebp, esp //将被HOOK的机器码(第2种方法)
:71A21AF7 83EC10 sub esp, 00000010
:71A21AFA 56 push esi
:71A21AFB 57 push edi
:71A21AFC 33FF xor edi, edi
:71A21AFE 813D1C20A371931CA271 cmp dword ptr [71A3201C], 71A21C93 //将被HOOK的机器码(第4种方法)
:71A21B08 0F84853D0000 je 71A25893
:71A21B0E 8D45F8 lea eax, dword ptr [ebp-08]
:71A21B11 50 push eax
:71A21B12 E869F7FFFF call 71A21280
:71A21B17 3BC7 cmp eax, edi
:71A21B19 8945FC mov dword ptr [ebp-04], eax
:71A21B1C 0F85C4940000 jne 71A2AFE6
:71A21B22 FF7508 push [ebp+08]
:71A21B25 E826F7FFFF call 71A21250
:71A21B2A 8BF0 mov esi, eax
:71A21B2C 3BF7 cmp esi, edi
:71A21B2E 0F84AB940000 je 71A2AFDF
:71A21B34 8B4510 mov eax, dword ptr [ebp+10]
:71A21B37 53 push ebx
:71A21B38 8D4DFC lea ecx, dword ptr [ebp-04]
:71A21B3B 51 push ecx
:71A21B3C FF75F8 push [ebp-08]
:71A21B3F 8D4D08 lea ecx, dword ptr [ebp+08]
:71A21B42 57 push edi
:71A21B43 57 push edi
:71A21B44 FF7514 push [ebp+14]
:71A21B47 8945F0 mov dword ptr [ebp-10], eax
:71A21B4A 8B450C mov eax, dword ptr [ebp+0C]
:71A21B4D 51 push ecx
:71A21B4E 6A01 push 00000001
:71A21B50 8D4DF0 lea ecx, dword ptr [ebp-10]
:71A21B53 51 push ecx
:71A21B54 FF7508 push [ebp+08]
:71A21B57 8945F4 mov dword ptr [ebp-0C], eax
:71A21B5A 8B460C mov eax, dword ptr [esi+0C]
:71A21B5D FF5064 call [eax+64]
:71A21B60 8BCE mov ecx, esi
:71A21B62 8BD8 mov ebx, eax
:71A21B64 E8C7F6FFFF call 71A21230 //将被HOOK的机器码(第3种方法)
:71A21B69 3BDF cmp ebx, edi
:71A21B6B 5B pop ebx
:71A21B6C 0F855F940000 jne 71A2AFD1
:71A21B72 8B4508 mov eax, dword ptr [ebp+08]
:71A21B75 5F pop edi
:71A21B76 5E pop esi
:71A21B77 C9 leave
:71A21B78 C21000 ret 0010
下面用4种方法来HOOK这个API:
1,把API入口的第一条指令是PUSH EBP指令(机器码0x55)替换成INT 3(机器码0xcc),
然后用WINDOWS提供的调试函数来执行自己的代码,这中方法被SOFT ICE等DEBUGER广泛采用,
它就是通过BPX在相应的地方设一条INT 3指令来下断点的。但是不提倡用这种方法,因为它
会与WINDOWS或调试工具产生冲突,而汇编代码基本都要调试;
2,把第二条mov ebp,esp指令(机器码8BEC,2字节)替换为INT F0指令(机器码CDF0),
然后在IDT里设置一个中断门,指向我们的代码。我这里给出一个HOOK代码:
lea ebp,[esp+12] //模拟原指令mov ebp,esp的功能
pushfd //保存现场
pushad //保存现场
//在这里做你想做的事情
popad //恢复现场
popfd //恢复现场
iretd //返回原指令的下一条指令继续执行原函数(71A21AF7地址处)
这种方法很好,但缺点是要在IDT设置一个中断门,也就是要进RING0。
3,更改CALL指令的相对地址(CALL分别在71A21B12、71A21B25、71A21B64,但前面2条CALL之前有一个条件
跳转指令,有可能不被执行到,因此我们要HOOK 71A21B64处的CALL指令)。为什么要找CALL指令下手?
因为它们都是5字节的指令,而且都是CALL指令,只要保持 *** 作码0xE8不变,改变后面的相对地址就可以转
到我们的HOOK代码去执行了,在我们的HOOK代码后面再转到目标地址去执行。
假设我们的HOOK代码在71A20400处,那么我们把71A21B64处的CALL指令改为CALL 71A20400(原指令是这样的:CALL 71A21230)
而71A20400处的HOOK代码是这样的:
71A20400:
pushad
//在这里做你想做的事情
popad
jmp 71A21230 //跳转到原CALL指令的目标地址,原指令是这样的:call 71A21230
这种方法隐蔽性很好,但是比较难找这条5字节的CALL指令,计算相对地址也复杂。
4,替换71A21AFE地址上的cmp dword ptr [71A3201C], 71A21C93指令(机器码:813D1C20A371931CA271,10字节)成为
call 71A20400
nop
nop
nop
nop
nop
(机器码:E8 XX XX XX XX 90 90 90 90 90,10字节)
在71A20400的HOOK代码是:
pushad
mov edx,71A3201Ch //模拟原指令cmp dword ptr [71A3201C], 71A21C93
cmp dword ptr [edx],71A21C93h //模拟原指令cmp dword ptr [71A3201C], 71A21C93
pushfd
//在这里做你想做的事
popfd
popad
ret
这种方法隐蔽性最好,但不是每个API都有这样的指令,要具体情况具体 *** 作。
以上几种方法是常用的方法,值得一提的是很多人都是改API开头的5个字节,但是现在很多杀毒软件用这样的方法
检查API是否被HOOK,或其他病毒木马在你之后又改了前5个字节,这样就会互相覆盖,最后一个HOOK API的 *** 作才是有效的,
一、 介绍
本文将讨论在NET应用程序中全局系统钩子的使用。为此,我开发了一个可重用的类库并创建一个相应的示例程序(见下图)。
你可能注意到另外的关于使用系统钩子的文章。本文与之类似但是有重要的差别。这篇文章将讨论在NET中使用全局系统钩子,而其它文章仅讨论本地系统钩子。这些思想是类似的,但是实现要求是不同的。
二、 背景
如果你对Windows系统钩子的概念不熟悉,让我作一下简短的描述:
・一个系统钩子允许你插入一个回调函数-它拦截某些Windows消息(例如,鼠标相联系的消息)。
・一个本地系统钩子是一个系统钩子-它仅在指定的消息由一个单一线程处理时被调用。
・一个全局系统钩子是一个系统钩子-它当指定的消息被任何应用程序在整个系统上所处理时被调用。
已有若干好文章来介绍系统钩子概念。在此,不是为了重新收集这些介绍性的信息,我只是简单地请读者参考下面有关系统钩子的一些背景资料文章。如果你对系统钩子概念很熟悉,那么你能够从本文中得到你能够得到的任何东西。
・关于MSDN库中的钩子知识。
・Dino Esposito的《Cutting Edge-Windows Hooks in the NET Framework》。
・Don Kackman的《在C#中应用钩子》。
本文中我们要讨论的是扩展这个信息来创建一个全局系统钩子-它能被NET类所使用。我们将用C#和一个DLL和非托管C++来开发一个类库-它们一起将完成这个目标。
三、 使用代码
在我们深入开发这个库之前,让我们快速看一下我们的目标。在本文中,我们将开发一个类库-它安装全局系统钩子并且暴露这些由钩子处理的事件,作为我们的钩子类的一个NET事件。为了说明这个系统钩子类的用法,我们将在一个用C#编写的Windows表单应用程序中创建一个鼠标事件钩子和一个键盘事件钩子。
这些类库能用于创建任何类型的系统钩子,其中有两个预编译的钩子-MouseHook和KeyboardHook。我们也已经包含了这些类的特定版本,分别称为MouseHookExt和KeyboardHookExt。根据这些类所设置的模型,你能容易构建系统钩子-针对Win32 API中任何15种钩子事件类型中的任何一种。另外,这个完整的类库中还有一个编译的HTML帮助文件-它把这些类归档化。请确信你看了这个帮助文件-如果你决定在你的应用程序中使用这个库的话。
MouseHook类的用法和生命周期相当简单。首先,我们创建MouseHook类的一个实例。
mouseHook = new MouseHook();//mouseHook是一个成员变量
接下来,我们把MouseEvent事件绑定到一个类层次的方法上。
mouseHookMouseEvent+=new MouseHookMouseEventHandler(mouseHook_MouseEvent);
//
private void mouseHook_MouseEvent(MouseEvents mEvent, int x, int y){
string msg =stringFormat("鼠标事件::(,)",mEventToString(),x,y);
AddText(msg);//增加消息到文本框
}
为开始收到鼠标事件,简单地安装下面的钩子即可。
mouseHookInstallHook();
为停止接收事件,只需简单地卸载这个钩子。
mouseHookUninstallHook();
你也可以调用Dispose来卸载这个钩子。
在你的应用程序退出时,卸载这个钩子是很重要的。让系统钩子一直安装着将减慢系统中的所有的应用程序的消息处理。它甚至能够使一个或多个进程变得很不稳定。因此,请确保在你使用完钩子时一定要移去你的系统钩子。我们确定在我们的示例应用程序会移去该系统钩子-通过在Form的Dispose方法中添加一个Dispose调用。
protected override void Dispose(bool disposing) {
if (disposing) {
if (mouseHook != null) {
mouseHookDispose();
mouseHook = null;
}
//
}
}
使用该类库的情况就是如此。该类库中有两个系统钩子类并且相当容易扩充。
四、 构建库
这个库共有两个主要组件。第一部分是一个C#类库-你可以直接使用于你的应用程序中。该类库,反过来,在内部使用一个非托管的C++ DLL来直接管理系统钩子。我们将首先讨论开发该C++部分。接下来,我们将讨论怎么在C#中使用这个库来构建一个通用的钩子类。就象我们讨论C++/C#交互一样,我们将特别注意C++方法和数据类型是怎样映射到NET方法和数据类型的。
你可能想知道为什么我们需要两个库,特别是一个非托管的C++ DLL。你还可能注意到在本文的背景一节中提到的两篇参考文章,其中并没有使用任何非托管的代码。为此,我的回答是,"对!这正是我写这篇文章的原因"。当你思考系统钩子是怎样实际地实现它们的功能时,我们需要非托管的代码是十分重要的。为了使一个全局的系统钩子能够工作,Windows把你的DLL插入到每个正在运行的进程的进程空间中。既然大多数进程不是NET进程,所以,它们不能直接执行NET装配集。我们需要一种非托管的代码代理- Windows可以把它插入到所有将要被钩住的进程中。
首先是提供一种机制来把一个NET代理传递到我们的C++库。这样,我们用C++语言定义下列函数(SetUserHookCallback)和函数指针(HookProc)。
int SetUserHookCallback(HookProc userProc, UINT hookID)
typedef void (CALLBACK HookProc)(int code, WPARAM w, LPARAM l)
SetUserHookCallback的第二个参数是钩子类型-这个函数指针将使用它。现在,我们必须用C#来定义相应的方法和代理以使用这段代码。下面是我们怎样把它映射到C#。
private static extern SetCallBackResults
SetUserHookCallback(HookProcessedHandler hookCallback, HookTypes hookType)
protected delegate void HookProcessedHandler(int code, UIntPtr wparam, IntPtr lparam)
public enum HookTypes {
JournalRecord = 0,
JournalPlayback = 1,
//
KeyboardLL = 13,
MouseLL = 14
};
首先,我们使用DllImport属性导入SetUserHookCallback函数,作为我们的抽象基钩子类SystemHook的一个静态的外部的方法。为此,我们必须映射一些外部数据类型。首先,我们必须创建一个代理作为我们的函数指针。这是通过定义上面的HookProcessHandler 来实现的。我们需要一个函数,它的C++签名为(int,WPARAM,LPARAM)。在Visual Studio NET C++编译器中,int与C#中是一样的。也就是说,在C++与C#中int就是Int32。事情并不总是这样。一些编译器把C++ int作为Int16对待。我们坚持使用Visual Studio NET C++编译器来实现这个工程,因此,我们不必担心编译器差别所带来的另外的定义。
接下来,我们需要用C#传递WPARAM和LPARAM值。这些确实是指针,它们分别指向C++的UINT和LONG值。用C#来说,它们是指向uint和int的指针。如果你还不确定什么是WPARAM,你可以通过在C++代码中单击右键来查询它,并且选择"Go to definition"。这将会引导你到在windefh中的定义。
//从windefh:
typedef UINT_PTR WPARAM;
typedef LONG_PTR LPARAM;
因此,我们选择SystemUIntPtr和SystemIntPtr作为我们的变量类型-它们分别相应于WPARAM和LPARAM类型,当它们使用在C#中时。
现在,让我们看一下钩子基类是怎样使用这些导入的方法来传递一个回叫函数(代理)到C++中-它允许C++库直接调用你的系统钩子类的实例。首先,在构造器中,SystemHook类创建一个到私有方法InternalHookCallback的代理-它匹配HookProcessedHandler代理签名。然后,它把这个代理和它的HookType传递到C++库以使用SetUserHookCallback方法来注册该回叫函数,如上面所讨论的。下面是其代码实现:
public SystemHook(HookTypes type){
_type = type;
_processHandler = new HookProcessedHandler(InternalHookCallback);
SetUserHookCallback(_processHandler, _type);
}
InternalHookCallback的实现相当简单。InternalHookCallback在用一个catch-all try/catch块包装它的同时仅传递到抽象方法HookCallback的调用。这将简化在派生类中的实现并且保护C++代码。记住,一旦一切都准备妥当,这个C++钩子就会直接调用这个方法。
[MethodImpl(MethodImplOptionsNoInlining)]
private void InternalHookCallback(int code, UIntPtr wparam, IntPtr lparam){
try
catch {}
}
我们已增加了一个方法实现属性-它告诉编译器不要内联这个方法。这不是可选的。至少,在我添加try/catch之前是需要的。看起来,由于某些原因,编译器在试图内联这个方法-这将给包装它的代理带来各种麻烦。然后,C++层将回叫,而该应用程序将会崩溃。
现在,让我们看一下一个派生类是怎样用一个特定的HookType来接收和处理钩子事件。下面是虚拟的MouseHook类的HookCallback方法实现:
protected override void HookCallback(int code, UIntPtr wparam, IntPtr lparam){
if (MouseEvent == null)
int x = 0, y = 0;
MouseEvents mEvent = (MouseEvents)wparamToUInt32();
switch(mEvent) {
case MouseEventsLeftButtonDown:
GetMousePosition(wparam, lparam, ref x, ref y);
break;
//
}
MouseEvent(mEvent, new Point(x, y));
}
首先,注意这个类定义一个事件MouseEvent-该类在收到一个钩子事件时激发这个事件。这个类在激发它的事件之前,把数据从WPARAM和 LPARAM类型转换成NET中有意义的鼠标事件数据。这样可以使得类的消费者免于担心解释这些数据结构。这个类使用导入的 GetMousePosition函数-我们在C++ DLL中定义的用来转换这些值。为此,请看下面几段的讨论。
在这个方法中,我们检查是否有人在听这一个事件。如果没有,不必继续处理这一事件。然后,我们把WPARAM转换成一个MouseEvents枚举类型。我们已小心地构造了MouseEvents枚举来准确匹配它们在C ++中相应的常数。这允许我们简单地把指针的值转换成枚举类型。但是要注意,这种转换即使在WPARAM的值不匹配一个枚举值的情况下也会成功。 mEvent的值将仅是未定义的(不是null,只是不在枚举值范围之内)。为此,请详细分析SystemEnumIsDefined方法。
接下来,在确定我们收到的事件类型后,该类激活这个事件,并且通知消费者鼠标事件的类型及在该事件过程中鼠标的位置。
最后注意,有关转换WPARAM和LPARAM值:对于每个类型的事件,这些变量的值和意思是不同的。因此,在每一种钩子类型中,我们必须区别地解释这些值。我选择用C++实现这种转换,而不是尽量用C#来模仿复杂的C++结构和指针。例如,前面的类就使用了一个叫作GetMousePosition的 C++函数。下面是C++ DLL中的这个方法:
bool GetMousePosition(WPARAM wparam, LPARAM lparam, int amp; x, int amp; y) {
MOUSEHOOKSTRUCT pMouseStruct = (MOUSEHOOKSTRUCT )lparam;
x = pMouseStruct->ptx;
y = pMouseStruct->pty;
return true;
}
不是尽量映射MOUSEHOOKSTRUCT结构指针到C#,我们简单地暂时把它回传到C++层以提取我们需要的值。注意,因为我们需要从这个调用中返回一些值,我们把我们的整数作为参考变量传递。这直接映射到C#中的int。但是,我们可以重载这个行为,通过选择正确的签名来导入这个方法。
private static extern bool InternalGetMousePosition(UIntPtr wparam,IntPtr lparam, ref int x, ref int y)
通过把integer参数定义为ref int,我们得到通过C++参照传递给我们的值。如果我们想要的话,我们还可以使用out int。
五、 限制
一些钩子类型并不适合实现全局钩子。我当前正在考虑解决办法-它将允许使用受限制的钩子类型。到目前为止,不要把这些类型添加回该库中,因为它们将导致应用程序的失败(经常是系统范围的灾难性失败)。下一节将集中讨论这些限制背后的原因和解决办法。
HookTypesCallWindowProcedure
HookTypesCallWindowProret
HookTypesComputerBasedTraining
HookTypesDebug
HookTypesForegroundIdle
HookTypesJournalRecord
HookTypesJournalPlayback
HookTypesGetMessage
HookTypesSystemMessageFilter
六、 两种类型的钩子
在本节中,我将尽量解释为什么一些钩子类型被限制在一定的范畴内而另外一些则不受限制。如果我使用有点偏差术语的话,请原谅我。我还没有找到任何有关这部分题目的文档,因此,我编造了我自己的词汇。另外,如果你认为我根本就不对,请告诉我好了。
当Windows调用传递到SetWindowsHookEx()的回调函数时它们会因不同类型的钩子而被区别调用。基本上有两种情况:切换执行上下文的钩子和不切换执行上下文的钩子。用另一种方式说,也就是,在放钩子的应用程序进程空间执行钩子回调函数的情况和在被钩住的应用程序进程空间执行钩子回调函数的情况。
钩子类型例如鼠标和键盘钩子都是在被Windows调用之前切换上下文的。整个过程大致如下:
1 应用程序X拥有焦点并执行。
2 用户按下一个键。
3 Windows从应用程序X接管上下文并把执行上下文切换到放钩子的应用程序。
4 Windows用放钩子的应用程序进程空间中的键消息参数调用钩子回调函数。
5 Windows从放钩子的应用程序接管上下文并把执行上下文切换回应用程序X。
6 Windows把消息放进应用程序X的消息排队。
7 稍微一会儿之后,当应用程序X执行时,它从自己的消息排队中取出消息并且调用它的内部按键(或松开或按下)处理器。
8 应用程序X继续执行
例如CBT钩子(window创建,等等。)的钩子类型并不切换上下文。对于这些类型的钩子,过程大致如下:
1 应用程序X拥有焦点并执行。
2 应用程序X创建一个窗口。
3 Windows用在应用程序X进程空间中的CBT事件消息参数调用钩子回调函数。
4 应用程序X继续执行
这应该说明了为什么某种类型的钩子能够用这个库结构工作而一些却不能。记住,这正是该库要做的。在上面第4步和第3步之后,分别插入下列步骤:
1 Windows调用钩子回调函数。
2 目标回调函数在非托管的DLL中执行。
3 目标回调函数查找它的相应托管的调用代理。
4 托管代理被以适当的参数执行。
5 目标回调函数返回并执行相应于指定消息的钩子处理。
第三步和第四步因非切换钩子类型而注定失败。第三步将失败,因为相应的托管回调函数不会为该应用程序而设置。记住,这个DLL使用全局变量来跟踪这些托管代理并且该钩子DLL被加载到每一个进程空间。但是这个值仅在放钩子的应用程序进程空间中设置。对于另外其它情况,它们全部为null。
Tim Sylvester在他的《Other hook types》一文中指出,使用一个共享内存区段将会解决这个问题。这是真实的,但是也如Tim所指出的,那些托管代理地址对于除了放钩子的应用程序之外的任何进程是无意义的。这意味着,它们是无意义的并且不能在回调函数的执行过程中调用。那样会有麻烦的。
因此,为了把这些回调函数使用于不执行上下文切换的钩子类型,你需要某种进程间的通讯。
我已经试验过这种思想-使用非托管的DLL钩子回调函数中的进程外COM对象进行IPC。如果你能使这种方法工作,我将很高兴了解到这点。至于我的尝试,结果并不理想。基本原因是很难针对各种进程和它们的线程(CoInitialize(NULL))而正确地初始化COM单元。这是一个在你可以使用 COM对象之前的基本要求。
我不怀疑,一定有办法来解决这个问题。但是我还没有试用过它们,因为我认为它们仅有有限的用处。例如,CBT钩子可以让你取消一个窗口创建,如果你希望的话。可以想像,为使这能够工作将会发生什么。
1 钩子回调函数开始执行。
2 调用非托管的钩子DLL中的相应的钩子回调函数。
3 执行必须被路由回到主钩子应用程序。
4 该应用程序必须决定是否允许这一创建。
5 调用必须被路由回仍旧在运行中的钩子回调函数。
6 在非托管的钩子DLL中的钩子回调函数从主钩子应用程序接收到要采取的行动。
7 在非托管的钩子DLL中的钩子回调函数针对CBT钩子调用采取适当的行动。
8 完成钩子回调函数的执行。
这不是不可能的,但是不算好的。我希望这会消除在该库中的围绕被允许的和受限制的钩子类型所带来的神秘。
七、 其它
・库文档:我们已经包含了有关ManagedHooks类库的比较完整的代码文档。当以"Documentation"构建配置进行编译时,这被经由Visual StudioNET转换成标准帮助XML。最后,我们已使用NDoc来把它转换成编译的HTML帮助(CHM)。你可以看这个帮助文件,只需简单地在该方案的解决方案资源管理器中点击Hookschm文件或通过查找与该文相关的可下载的ZIP文件。
・增强的智能感知:如果你不熟悉Visual StudioNET怎样使用编译的XML文件(pre-NDoc output)来为参考库的工程增强智能感知,那么让我简单地介绍一下。如果你决定在你的应用程序中使用这个类库,你可以考虑复制该库的一个稳定构建版本到你想参考它的位置。同时,还要把XML文档文件 (SystemHooks\ManagedHooks\bin\Debug\KennedyManagedHooksxml)复制到相同的位置。当你添加一个参考到该库时,Visual StudioNET将自动地读该文件并使用它来添加智能感知文档。这是很有用的,特别是对于象这样的第三方库。
・单元测试:我相信,所有的库都应有与之相应的单元测试。既然我是一家公司(主要负责针对NET环境软件的单元测试)的合伙人和软件工程师,任何人不会对此感到惊讶。因而,你将会在名为ManagedHooksTests的解决方案中找到一个单元测试工程。为了运行该单元测试,你需要下载和安装 HarnessIt-这个下载是我们的商业单元测试软件的一个自由的试用版本。在该单元测试中,我对这给予了特殊的注意-在此处,方法的无效参数可能导致 C++内存异常的发生。尽管这个库是相当简单的,但该单元测试确实能够帮助我在一些更为微妙的情况下发现一些错误。
・非托管的/托管的调试:有关混合解决方案(例如,本文的托管的和非托管的代码)最为技巧的地方之一是调试问题。如果你想单步调试该C++代码或在C++代码中设置断点,你必须启动非托管的调试。这是一个Visual StudioNET中的工程设置。注意,你可以非常顺利地单步调试托管的和非托管的层,但是,在调试过程中,非托管的调试确实严重地减慢应用程序的装载时间和执行速度。
八、 最后警告
很明显,系统钩子相当有力量;然而,使用这种力量应该是有责任性的。在系统钩子出了问题时,它们不仅仅垮掉你的应用程序。它们可以垮掉在你的当前系统中运行的每个应用程序。但是到这种程度的可能性一般是很小的。尽管如此,在使用系统钩子时,你还是需要再三检查你的代码。
我发现了一项可以用来开发应用程序的有用的技术-它使用系统钩子来在微软的虚拟PC上安装你的喜爱的开发 *** 作系统的一个拷贝和Visual StudioNET。然后,你就可以在此虚拟的环境中开发你的应用程序。用这种方式,当你的钩子应用程序出现错误时,它们将仅退出你的 *** 作系统的虚拟实例而不是你的真正的 *** 作系统。我已经不得不重启动我的真正的OS-在这个虚拟OS由于一个钩子错误崩溃时,但是这并不经常。
很抱歉,API Hook不等同于消息Hook,虽然都叫Hook,但有着本质的区别,也就没所谓全局和局部之分了。消息Hook是Windows提供给应用程序的一个正常的应用,而API,则是“利用”Windows的特性来达到的目的。
要Hook目标进程一个API,无非两种方法:利用PE文件的结构,把目标进程的每个可执行模块(包括exe和dll)中的导入表部分修改,把目标API的地址修改为你自己写的函数。这种方法以《Windows核心编程》的第22章介绍的挂接API的方法最为经典。
上种方法有个很大的弊端,就是如果目标进程已经在你的程序运行之前运行了,并且已经使用GetProcAddress得到了目标API的地址,这种方法就无用了,因为往后目标进程极有可能不会再去通过导入表来得到API的地址了。前段时间我做IE的socket的钩子,用到了另一种方法:
这种方法的前身是,把目标API的前5个字节修改为一个jmp跳转指令,直接跳到你的API里,接着你再把这5个字节写回去,然后再次用同样的参数去调用目标API,目标API返回之后再把那5个字节再回去(有点像缺页中断的处理过程)。这样在你就能拦截所有的对目标API的调用了。但是,这种方法的最大的缺点就是,对多线程来说效果不好。于是我就引出了另一种方法:当对目标API的调用导致的跳转到我的代码中后,不把那5个字节写回去,而是在你的API中,用汇编实现那5个字节原来的功能,之后直接跳转到目标API的第6个字节去运行,这样你的API就永远不会被漏掉了,而且只要你注意寄存器的保存与恢复,以及栈的平衡,这种方法就会很稳定。当然,这种方法会限于API版本的限制,不同的版本,开始的5个字节可能不一样,你就要分别对待了。
说了这么多废话,其中最核心的一点就是,把自己的代码注入到目标进程中,最简单的方法还是用Dll。当然,不用Dll的方法也有,比如罗云彬《Windows环境下32位汇编语言程序设计》中提到的那样,用汇编写——比Dll更麻烦。
以上言论的版权由真诚到永远所有
用SetWindowsHookEx拦截QQ密码框,这个我也试过,拦截的是乱码。
原因据说是QQ用了nKey键盘保护。
QQ2011版的我没有试过,10版的我试过,用WinIO可以拦截密码。
不过WinIO加载了驱动,360会有提示,用DirectX监视键盘也拦截不下来。
谁知道11版的WinIO还管事不?,你可以从网上下一个WinIO。
WinIO包含一个库文件,一个动态链接库文件,一个驱动文件。
我告诉你个天下最好的办法吧:
1将Kernel32dll或者user32dll替换成你的。
2在键盘这个硬件上做手脚。
hook技术很难勾住控制台程序,当然,如果有办法把握时机向控制台程序INJECT动态库,也是可以hook住控制台程序的。一般而言,使用了user32dll的程序(GUI程序)是比较容易hook的。
根据《windows核心编程》中所说的,可以尝试使用CAPIHooker和CreateRemoteThread来对控制台程序进行远程注入,这样便有机会挂住控制台程序。这可能是一种方法。当然也可能有其它的方法。
总而言之,使用控制台程序不容易hook。
以上就是关于C#中,如何HOOK一个API函数!全部的内容,包括:C#中,如何HOOK一个API函数!、c++如何用hook禁用鼠标如何让编写、本人正在学习钩子,求一个关于HOOK的实例!等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)