C# Hook
前言
在说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函数:

[DllImport("user32.dll",CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn,IntPtr hInstance, int threadId);
[DllImport("user32.dll",CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll",CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode,IntPtr wParam, IntPtr lParam);

声明后即可实现调用,SetWindowsHookEx()把一个应用程序定义的钩子子程安装到钩子链表中,SetWindowsHookEx函数总是在Hook链的开头安装Hook子程。当指定类型的Hook监视的事件发生时,系统就调用与这个Hook关联的Hook链的开头的Hook子程。每一个Hook链中的Hook子程都决定是否把这个事件传递到下一个Hook子程。Hook子程传递事件到下一个Hook子程需要调用CallNextHookEx函数。 且钩子使用完成后需要调用UnhookWindowsHookEx进行卸载,否则容易影响到其他钩子的执行,并且钩子太多会影响目标进程的正常运行。
关于实例详细操作过程不再赘述,请参考:http://blog.csdn.net/ensoo/article/details/2045101 及 https://www.cnblogs.com/ceoliujia/archive/2010/05/20/1740217.html
EasyHook
C#本身调用WindowsAPI进行Hook功能受到很大的限制,而C++则不受此限制,因此就有一些聪明的人想到了聪明的方法:使用C++将基本操作封装成库,由C#进行调用,由此诞生了伟大的EasyHook,它不仅使用方便,而且开源免费,还支持64位版本。
接下来我们一起使用C#操作EasyHook来实现一个Demo,完成对MessageBox的改写。
首先我们建立一个WinForm项目程序,并添加一个类库ClassLibrary1,再从官网https://easyhook.github.io/或Nuget获取到dll后引用到我们的项目中,注意:32位和64位版本都需要引用,建立项目如图所示:

其中WinForm程序用于获取目标进程,并对目标进程进行注入,相关步骤如下:
1、根据进程ID获取相关进程,并判断是否为64位;
2、将所需DLL注册到GAC(全局程序集缓存),注册到GAC的目的是需要在目标进程中调用EasyHook及我们所编写的DLL;

private bool RegGACAssembly()
{
var dllName = "EasyHook.dll";
var dllPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, dllName);
if (!RuntimeEnvironment.FromGlobalAccessCache(Assembly.LoadFrom(dllPath)))
{
new System.EnterpriseServices.Internal.Publish().GacInstall(dllPath);
Thread.Sleep(100);
}
dllName = "ClassLibrary1.dll";
dllPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, dllName);
new System.EnterpriseServices.Internal.Publish().GacRemove(dllPath);
if (!RuntimeEnvironment.FromGlobalAccessCache(Assembly.LoadFrom(dllPath)))
{
new System.EnterpriseServices.Internal.Publish().GacInstall(dllPath);
Thread.Sleep(100);
}
return true;
}

此处需要注意,要将自己编写的类库DLL加入GAC,需要对DLL进行强签名操作,操作方法请参考:https://docs.microsoft.com/zh-cn/dotnet/framework/app-domains/how-to-sign-an-assembly-with-a-strong-name
3、注入目标进程,此处需使用EasyHook的RemoteHooking.Inject()方法进行注入:

private static bool InstallHookInternal(int processId)
{
try
{
var parameter = new HookParameter
{
Msg = "已经成功注入目标进程",
HostProcessId = RemoteHooking.GetCurrentProcessId()
};
RemoteHooking.Inject(
processId,
InjectionOptions.Default,
typeof(HookParameter).Assembly.Location,
typeof(HookParameter).Assembly.Location,
string.Empty,
parameter
);
}
catch (Exception ex)
{
Debug.Print(ex.ToString());
return false;
}
return true;
}

HookParameter类为定义在ClassLibrary1中的一个类,包含消息与进程ID:
[Serializable]
public class HookParameter
{
public string Msg { get; set; }
public int HostProcessId { get; set; }
}
到这一步我们就完成了对主窗体代码的编写,现在我们开始编写注入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
C# Hook的更多相关文章
- svnserver hook python
在使用中可能会遇到的错误排除 :1.Error: svn: 解析"D:\www\test"出错,或svn: E020024: Error resolving case of 'D: ...
- Android Hook技术
原文:http://blog.csdn.net/u011068702/article/details/53208825 附:Android Hook 全面入侵监听器 第一步.先爆项目demo照片,代码 ...
- Frida HOOK微信实现骰子作弊
由于微信摇骰子的功能在本地进行随机后在发送,所以存在可以hook掉判断骰子数的方法进行修改作弊. 1.frida实现hook java层函数1)写个用来测试的demo,当我们点击按钮的时候会弹出窗口显 ...
- java的关闭钩子(Shutdown Hook)
Runtime.getRuntime().addShutdownHook(shutdownHook); 这个方法的含义说明: 这个方法的意思就是在jvm中增加一个关闭的钩子,当jv ...
- IDT HOOK思路整理
IDT(中断描述符表)分为IRQ(真正的硬件中断)和软件中断(又叫异常). HOOK的思路为,替换键盘中断处理的函数地址为自己的函数地址.这样在键盘驱动和过滤驱动之前就可以截获键盘输入. 思路确定之后 ...
- Android Hook 借助Xposed
主要就是使用到了Xposed中的两个比较重要的方法,handleLoadPackage获取包加载时候的回调并拿到其对应的classLoader:findAndHookMethod对指定类的方法进行Ho ...
- iOS App 无代码入侵的方法hook
继续Objective-C runtime的研究 最近公司项目在做用户行为分析 于是App端在某些页面切换,交互操作的时候需要给统计系统发送一条消息 在几十个Controller 的项目里,一个一个地 ...
- Hook机制里登场的角色
稍有接触过 WordPress 主题或插件制作修改的朋友,对 WordPress 的Hook机制应该不陌生,但通常刚接触WordPress Hook 的新手,对其运作原理可能会有点混乱或模糊.本文针对 ...
- java hook
linux下 hook的触发,需要 发送信号为15. 后续补充具体内容.
- HDU 1698 Just a Hook(线段树 区间替换)
Just a Hook [题目链接]Just a Hook [题目类型]线段树 区间替换 &题解: 线段树 区间替换 和区间求和 模板题 只不过不需要查询 题里只问了全部区间的和,所以seg[ ...
随机推荐
- 洛谷—— P3388 【模板】割点(割顶)
https://www.luogu.org/problem/show?pid=3388 题目背景 割点 题目描述 给出一个n个点,m条边的无向图,求图的割点. 输入输出格式 输入格式: 第一行输入n, ...
- sigprocmask和信号阻塞
注意阻塞和忽略,是有区别的. 阻塞只是暂时的,忽略是就没了. 参数:how:用于指定信号修改的方式,可能选择有三种:SIG_BLOCK //加入信号到进程屏蔽.SIG_UNBLOCK //从进程屏蔽里 ...
- HDU 4259
虽然知道是置换,却很久没有思路.忽然想到,由初始状态A经过变换后回到A状态,应该是不停循环可重复的.于是,猜想数字的位置也是重复循环的.拿了个例子验证了一下,某然是这样.例如第二个10,3的例子有1- ...
- 使用docker搭建hadoop分布式集群
使用docker搭建部署hadoop分布式集群 在网上找了非常长时间都没有找到使用docker搭建hadoop分布式集群的文档,没办法,仅仅能自己写一个了. 一:环境准备: 1:首先要有一个Cento ...
- MongoDB 数据库下载和安装
MongoDB是一款非常流行的非关系型数据库,将面向对象数据存储做的非常好.这里就不具体介绍它的使用,本文主要解说怎样安装MongoDB数据库.把自己安装过程中碰到的问题和最后解决方法分享给大家,希望 ...
- (插播)关于使用jenkins + unity +Xcode 来进行自己主动打包的处理。
近期了解了下jenkins流程化服务的东西,个人感觉jenkins是一个非常方便的工具.主要是方便.设置好流程性得命令.仅仅需确定下就能够达到自己主动化. 减轻了错误得发生和简化了带来的复杂得步骤.今 ...
- bzoj1212: [HNOI2004]L语言(字典树)
1212: [HNOI2004]L语言 题目:传送门 题解: 看完题目之后就觉得可以暴力在字典树上之间询问,一开始还傻了以为用文章来建,肯定用单词啊: 那么我们可以用一个v数组表示当前字符串1~i的区 ...
- 递归进制转换_strrev
#define _CRT_SECURE_NO_WARNINGS #include <stdlib.h> #include <stdio.h> #include <stri ...
- (转载)15 个 Android 通用流行框架大全
15 个 Android 通用流行框架大全 时间:2017-03-20 11:36来源:未知 作者:admin 点击: 2089 次 15 个 Android 通用流行框架大全 1. 缓存 Dis ...
- JavaScript在不同环境下的全局对象
Node.js 环境下,全局的对象是 global. 浏览器下 window === self 而不是 global,今天才发现的,我惊呆了!