在说C# Hook之前,我们先来说说什么是Hook技术。相信大家都接触过外挂,不管是修改游戏客户端的也好,盗取密码的也罢,它们都是如何实现的呢?
实际上,Windows平台是基于事件驱动机制的,整个系统都是通过消息的传递来实现的。当进程有响应时(包括响应鼠标和键盘事件),则Windows会向应用程序发送一个消息给应用程序的消息队列,应用程序进而从消息队列中取出消息并发送给相应窗口进行处理。
而Hook则是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。
所以Hook就可以实现在键盘/鼠标响应后,窗口处理消息之前,就对此消息进行处理,比如监听键盘输入,鼠标点击坐标等等。某些盗号木马就是Hook了指定的进程,从而监听键盘输入了什么内容,进而盗取账户密码。
C# Hook
我们知道C#是运行在.NET平台之上,而且是基于CLR动态运行的,所以只能操作封装好的函数,且无法直接操作内存数据。而且在C#常用的功能中,并未封装Hook相关的类与方法,所以如果用C#实现Hook,必须采用调用WindowsAPI的方式进行实现。
WindowsAPI函数属于非托管类型的函数,我们在调用时必须遵循以下几步:
1、查找包含调用函数的DLL,如User32.dll,Kernel32.dll等。
2、将该DLL加载到内存中,并注明入口
3、将所需参数转化为C#存在的类型,如指针对应Intptr,句柄对应int类型等等
4、调用函数
我们本篇需要使用的函数有以下几个:
SetWindowsHookEx 用于安装钩子
UnhookWindowsHookEx 用于卸载钩子
CallNextHookEx 执行下一个钩子
详细API介绍请参考MSDN官方声明
接下来在C#中需要首先声明此API函数:
到这一步我们就完成了对主窗体代码的编写,现在我们开始编写注入DLL的方法:
1、先引入MessageBox相关的WindowsAPI:
#region MessageBoxW
[DllImport("user32.dll", EntryPoint = "MessageBoxW", CharSet = CharSet.Unicode)]
public static extern IntPtr MessageBoxW(int hWnd, string text, string caption, uint type);
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
delegate IntPtr DMessageBoxW(int hWnd, string text, string caption, uint type);
static IntPtr MessageBoxW_Hooked(int hWnd, string text, string caption, uint type)
{
return MessageBoxW(hWnd, "已注入-" + text, "已注入-" + caption, type);
}
#endregion
#region MessageBoxA
[DllImport("user32.dll", EntryPoint = "MessageBoxA", CharSet = CharSet.Ansi)]
public static extern IntPtr MessageBoxA(int hWnd, string text, string caption, uint type);
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)]
delegate IntPtr DMessageBoxA(int hWnd, string text, string caption, uint type);
static IntPtr MessageBoxA_Hooked(int hWnd, string text, string caption, uint type)
{
return MessageBoxA(hWnd, "已注入-" + text, "已注入-" + caption, type);
}
#endregion
其中MessageBoxA与MessageBoxW是微软用于区分不同操作系统中的编码类型,早期的Windows并不属于真正的32位操作系统,执行的API函数属于ANSI类型,而从Windows2000开始,属于Unicode类型,Windows在实际操作中,调用的MessageBox会自动根据平台区分使用前者还是后者,我们在这里就需要把二者都包含其中。
而DMessageBoxA与DMessageBoxW属于IntPtr类型的委托,用于我们在Hook函数之后传入我们需要修改的方法,此处我们改变了MessageBox的内容和标题,分别在前缀加上了"已注入-"的标记。
2、完成定义之后我们就需要对函数进行Hook,此处使用LocalHook.GetProcAddress("user32.dll", "MessageBoxW")函数,通过指定的DLL与函数名,获取函数在实际内存中的地址,获取到之后,传入LocalHook.Create()方法,用于创建本地钩子:
public void Run(
RemoteHooking.IContext context,
string channelName
, HookParameter parameter
)
{
try
{
MessageBoxWHook = LocalHook.Create(
LocalHook.GetProcAddress("user32.dll", "MessageBoxW"),
new DMessageBoxW(MessageBoxW_Hooked),
this);
MessageBoxWHook.ThreadACL.SetExclusiveACL(new int[1]);
MessageBoxAHook = LocalHook.Create(
LocalHook.GetProcAddress("user32.dll", "MessageBoxA"),
new DMessageBoxW(MessageBoxA_Hooked),
this);
MessageBoxAHook.ThreadACL.SetExclusiveACL(new int[1]);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return;
}
try
{
while (true)
{
Thread.Sleep(10);
}
}
catch
{
}
}
其中MessageBoxWHook与MessageBoxAHook均为LocalHook类型的变量,MessageBoxAHook.ThreadACL.SetExclusiveACL(new int[1]); 这句代码用于将本地钩子加入当前线程中执行。
运行之后我们来查看Hook的效果,先打开一个测试窗体,弹出MessageBox,这时候MessageBox没有标题,且内容是正常的:

接着我们对目标进程进行注入,获取进程ID后点击注入,提示已经成功注入目标进程:

此时点击目标进程MessageBox,可以发现已经Hook成功,并改变了内容和标题:

至此,C#调用EasyHook对目标进程Hook已经实现。
后记
从这次实践中我们可以感受到,C#对程序进行Hook是完全可行的,虽然不能直接操作内存和地址,但是我们可以通过操作WindowsAPI与使用EasyHook的方式完成,尤其是后者,大大减少了代码数量与使用难度。
但是EasyHook目前中文资料非常少,我在使用的过程中也遇到了很大困难,Hook其他函数的方法也未能完全实现,希望能够集思广益,与大家共同思考交流!
本人刚研究Hook时间不久,文中难免出现纰漏,恳请各位评论指正。
源代码已经上传至百度网盘:链接: https://pan.baidu.com/s/1wyin9Ezn6AwFQlQxMenQeg 密码: dv9b
EasyHook函数四步走
- 找函数
- 匹配委托
- 编写hook函数
-
注入hook(这里包含了装载hook和卸载hook)
首先第一步骤,windows下的很多api,都是已经写好了的,我们程序中写的很多函数都是依靠他们作为底层来完成的,我们直接拿来用就行,就我们的程序而言,就是哪个动态链接库(这里写了一堆一堆的函数),里面的哪个函数,可以做什么。所以第一步,我们需要知道这个函数在哪里,其实也就是easyHook需要知道,他要hook哪个函数的地址。
第二步,匹配委托,简单的理解就是,函数指针(再通俗点就是我们编写函数的地址),要替换函数,要知道替换函数的地址,还要知道我们自己写的函数地址才能实现注入,注意这里写委托一定要和欲hook的函数参数和返回值一样,否则,就会出现大问题,我知道你不想搞一个大事情,所以,老老实实按照原型写委托吧。
第三步,编写我们自己的函数就是上文中说到的sayHelloHook,我们自己实现函数要做哪些事情,最后别忘了一定要和原函数,参数,返回值对应。
第四步,最轻松的一步,由easyHook替我们完成,他会自己实现函数的替换,注入啥的。我们只需要声明调用就ok。
参考
C# Hook原理及EasyHook简易教程
[.Net] C#使用easyHook笔记一
Hook原理