前言

  在说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原理及EasyHook简易教程相关推荐

  1. xposed hook java_[原创]Android Hook 系列教程(一) Xposed Hook 原理分析

    章节内容 一. Android Hook 系列教程(一) Xposed Hook 原理分析 二. Android Hook 系列教程(二) 自己写APK实现Hook Java层函数 三. Androi ...

  2. 移动开发之【微信小程序】的原理与权限问题以及相关的简易教程

    这几天圈子里到处都在传播着这样一个东西,微信公众平台提供了一种新的开放能力,开发者可以快速开发一个小程序,取名曰:微信公众平台-小程序 据说取代移动开发安卓和苹果,那这个东东究竟是干吗用的?但很多人觉 ...

  3. Ocelot简易教程(一)之Ocelot是什么

    Ocelot简易教程(一)之Ocelot是什么 原文:Ocelot简易教程(一)之Ocelot是什么 作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/955 ...

  4. 文件上传利器SWFUpload入门简易教程

    凡做过网站开发的都应该知道表单file的确鸡肋. Ajax解决了不刷新页面提交表单,但是却没有解决文件上传不刷新页面,当然也有其它技术让不刷新页面而提交文件,该技术主要是利用隐藏的iFrame, 较A ...

  5. WebGL简易教程(十五):加载gltf模型

    文章目录 1. 概述 2. 实例 2.1. 数据 2.2. 程序 2.2.1. 文件读取 2.2.2. glTF格式解析 2.2.2.1. 场景节点 2.2.2.2. 网格 2.2.2.3. 缓冲,缓 ...

  6. WebGL简易教程——目录

    文章目录 1. 绪论 2. 目录 3. 资源 1. 绪论 最近研究WebGL,看了<WebGL编程指南>这本书,结合自己的专业知识写的一系列教程.之前在看OpenGL/WebGL的时候总是 ...

  7. WebGL简易教程(十四):阴影

    文章目录 1. 概述 2. 示例 2.1. 着色器部分 2.1.1. 帧缓存着色器 2.1.2. 颜色缓存着色器 2.2. 绘制部分 2.2.1. 整体结构 2.2.2. 具体改动 2.2.2.1. ...

  8. Numpy简易教程——图像的数组表示

    Numpy简易教程--图像的数组表示 文章目录 Numpy简易教程--图像的数组表示 一.图像的表示模式 二.PIL库的使用 1.PIL库简介与下载,导入 2.图像的数组表示 3.图像的变换 下一篇: ...

  9. 【对讲机的那点事】自制VHF/UHF四分之一波长天线简易教程

    根据传输线的理论,1/4波长的开路线相当于一个串联谐振电路,所以其整个负载是呈纯电阻性的,在有些场合,由于环境的因素,天线的长度往往受到限制,所以出现了加载天线.根据传输线的理论,长度小于1/4波长的 ...

最新文章

  1. UOJ228:基础数据结构练习题——题解
  2. net读取exchange数据
  3. jdbc连接mysql8的一些坑_mysql8.0 jdbc连接注意事项
  4. LeetCode算法入门- Valid Parentheses -day11
  5. jsp中${}的意思--之EL表达式
  6. React应用优化:避免不必要的render
  7. windows 2008 快速安装RODC
  8. 公司人事管理系统(C++)
  9. 前端架构之 React 领域驱动设计
  10. 计算机考研压分的学校,考研压分院校盘点 | 都说这些学校今年压分严重?!
  11. Error Based Injection和sql注入函数
  12. CE6 CPU 使用率
  13. 月均播放超2.8亿,vlog会是品牌B站推广新风口吗?
  14. 电阻、电容及电感的高频等效电路及特性曲线
  15. Flutter 本地图片加载不出来
  16. 《论语》全译——公冶长篇第五
  17. 2.利用tensorflow框架对服装进行分类
  18. 腾讯技术解读|CDG—金融科技和腾讯广告AMS的神秘武器
  19. python中的Nonetype如何处理
  20. Python程序:任意输入一个三位数,然后把三位数的位置反转输出。

热门文章

  1. C++Miller Rabin算法的实现(附完整源码)
  2. python 聚类算法包_Python聚类算法之DBSACN实例分析 python怎么用sklearn包进行聚类
  3. mysql rollup 排序_MySQL基础实用知识集合(二)
  4. presto-server-0.198集群安装
  5. OpenTSDB介绍
  6. Ubuntu14下安装svn仓库,以及权限配置
  7. 3.QT事件处理,消息过滤器
  8. 2.oracle分页,找到员工表中薪水大于本部门平均薪水的员工
  9. js获取url中的参数,url中传递中文的时候通过js解码的方式
  10. Python 卡方检验演算