C# Hook原理及EasyHook简易教程

前言

  在说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进行卸载,否则容易影响到其他钩子的执行,并且钩子太多会影响目标进程的正常运行。

EasyHook

  C#本身调用WindowsAPI进行Hook功能受到很大的限制,而C++则不受此限制,因此就有一些聪明的人想到了聪明的方法:使用C++将基本操作封装成库,由C#进行调用,由此诞生了伟大的EasyHook,它不仅使用方便,而且开源免费,还支持64位版本。

  接下来我们一起使用C#操作EasyHook来实现一个Demo,完成对MessageBox的改写。

  首先我们建立一个WinForm项目程序,并添加一个类库ClassLibrary1,再从官网easyhook.github.io/或Nuget获取到dll后引用到我们的项目中,注意:32位和64位版本都需要引用,建立项目如图所示:

c22ce299f29b2813f901018644e4afec.png

  其中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进行强签名操作,操作方法请参考:docs.microsoft.com/zh-c

  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:

[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);}[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);}

        其中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没有标题,且内容是正常的:

927b91e50d243329f41b0f102b2efa85.png

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

8468cb2a54c42dd0a7282f28cd8ffba4.png

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

2409a12b7866aa96572020df5fbe4c65.png

至此,C#调用EasyHook对目标进程Hook已经实现。

后记

从这次实践中我们可以感受到,C#对程序进行Hook是完全可行的,虽然不能直接操作内存和地址,但是我们可以通过操作WindowsAPI与使用EasyHook的方式完成,尤其是后者,大大减少了代码数量与使用难度。

但是EasyHook目前中文资料非常少,我在使用的过程中也遇到了很大困难,Hook其他函数的方法也未能完全实现,希望能够集思广益,与大家共同思考交流!

本人刚研究Hook时间不久,文中难免出现纰漏,恳请各位评论指正。

源代码已经上传至百度网盘:链接: pan.baidu.com/s/1wyin9E 密码: dv9b

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/296297.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

squid 服务器的应用

实验名称:squid 服务器的应用 实验目标: 任务一:实现正向代理 任务二:实现透明代理 任务三:实现反向代理 提示1、在启动squid服务程序之前需要先确认Linux主机具有完整的域名,如果没有可以在hosts文件中进行…

有的人走着走着就散了!

1 有的人走着走着就走了上坡路▼2 没有感情的甩绳机器▼3 大男孩们陪小朋友踢球到底谁玩的比较开心▼4 给我妈妈演示一下我在肚子里的时候是怎么踹你的▼5 狗子:谢谢您嘞,我这是耳朵不是抹布!▼6 从没想到会在这种情况下和你相遇▼7 所…

官宣 .NET 6 RC (Release Candidate) 2

我们很高兴发布 .NET 6 RC(Release Candidate) 2。它是生产环境中支持的两个“go live”候选版本中的第二个。在过去的几个月里,团队一直专注于质量的改进。这个版本中有很多的新特性,但在接近尾声时我们才会把他们完全整合在一起。该团队目前正在验证端…

SQL Server索引进阶第十篇:索引的内部结构

索引设计是数据库设计中比较重要的一个环节,对数据库的性能其中至关重要的作用,但是索引的设计却又不是那么容易的事情,性能也不是那么轻易就获取到的,很多的技术人员因为不恰当的创建索引,最后使得其效果适得其反&…

JDK安装及java环境配置_JDK安装及Java环境变量配置

2.点击Accept License Agreement,下载适合自己电脑版本的JDK.由于我的电脑是windows10 64位专业版。点击红色下载按钮。保存位置自己决定,只要自己安装时能找到就行。3.找到安装文件,双击。4.下一步,这里会让你选择安装目录。注意…

33张你没看过的酷炫化学动图, 秒懂化学反应原理!

化学的神奇魅力可是不是随便说说的,神奇起来让人叹为观止。下面就让腾远君带领大家看看传说中的37张神图,了解化学之美吧。1 . 硫氰酸汞分解(“法老之蛇”)原理:硫氰酸汞受热分解,部分产物燃烧。2Hg(SCN)2→…

从编译器层面理解C#中的闭包的这个坑!

前言在公众号上看到一篇文章《正确使用和理解C#中的闭包》,里面提到了闭包的一个坑:当捕获的外部变量为for循环的迭代变量时,C#认为变量i是定义在循环体外的。所以,当添加委托集合的for循环执行完时,i的值已经变为3了&…

C# 使用 HelpProvider 控件调用帮助文件

HelpProvider控件可以将帮助文件(.htm文件或.chm文件)与 Windows 应用程序相关联,为特定对话框或对话框中的特定控件提供区分上下文的帮助,打开帮助文件到特定部分。如目录、索引或搜索功能的主页。如图1 所示为 HelpProvider 控件。图1 HelpProvider…

哈哈哈,弟弟被卡桶里了......

1 哈哈哈哈哈弟弟被卡桶里面了(via.小妮)(注意安全,请勿模仿!)▼2 弟弟是个狠人▼3 Self-potato: 一种不需要沙发也能无意义地待着的生活方式(via.字幕少女)▼4 防晒的正确打开方…

LVM基本应用 扩展及缩减实现

LVM: Logical Volume Manage首先;pv管理工具: pvs:简要pv信息显示 pvdisplay:显示pv的详细信息pvcreate /dev/DEVICE: 创建pvvg管理工具: vgs vgdisplayvgcreate [-s #[kKmMgGtTpPeE]] VolumeGroupName Physical…

java 线程 插件_我的第一个Chrome插件:天气预报应用

1.Chrome插件开发基础开发Chrome插件很简单,只要会基本的前台技术HTML、CSS、JS就可以开发了。Chrome插件一般包括两个HTML页面background和popup。background页面只在启动浏览器加载插件时载入一次,它不直接显示出来而是在后台运行。它包含了插件的主要…

保送北大,连发三篇Science,这位80后川妹子近日再发重磅级研究成果!

全世界只有3.14 % 的人关注了爆炸吧知识本文转自募格学术2020年9月21日,启函生物杨璐菡博士等在 Nature 子刊 Nature Biomedical Engineering杂志上发表了题为:Extensive germline genome engineering in pigs 的研究论文。杨璐菡杨璐菡带领的研究团队成…

Linq 下的 Take() 方法内部机制是怎样的?

咨询区 Rahul Kishore:我的web需要访问数据库,但是表比较大,我仅仅想要获取该表中 N 条数据,我查阅了 MSDN 文档,看到了一个 Take() 方法,我现在很疑惑它的运行机制是下面哪一种?先从数据库中获…

如何直接soap字符串,访问webservice

2019独角兽企业重金招聘Python工程师标准>>> 1.Webservice.GetVcardByUserNo(String userId,String userNo);这个是封装了的webservice接口。 2.在程序中连续两次调用该接口时,ksoap2在解析第二次调用返回的结果时抛异常。 异常信息如下&…

《哈利波特》电影全集+有声书免费领取!带你重返儿时魔法世界……

全世界只有3.14 % 的人关注了爆炸吧知识说到哈利波特系列,几乎人人皆知,享誉世界,风靡全球的哈利波特究竟有什么无穷魔力呢?《哈利波特》是英国作家JK罗琳的魔幻文学系列小说,共7集,其中前六部以霍格沃茨魔…

.NET6下周发布真的香,可不少人却只会.NET Framework!

倒计时7天,.NET6VS2022C#10将同时发布正式版,宣告.NET步入全新篇章,各种新语法、新框架、新技术都如约而至,令人期待!近年来,.NET跨平台持续推出新版本,开源社区也不断涌现各种优秀框架&#xf…

java环境怎样搭建_如何学习JAVA?怎么搭建JAVA环境?怎么安装JDK?

JAVA在学习JAVA前,我们必须了解并搭建好JAVA所需的开发环境,要让你写代码能让机器听得懂并执行,JDK(Java Developers Kits)自然是是必须的安装JDK前的准备首先我们要先知道自己的电脑系统是几位版本的,右键”此电脑“点击菜单里的…

神奇的机械动态图,看了一遍又一遍!最后一个真神奇~

全世界只有3.14 % 的人关注了爆炸吧知识神奇的机械科技动态图,看了一遍又一遍!最后一个真神奇~▲金属切割的慢镜头,美!▲齿轮变速原理演示▲塑料成型机器▲切丝的食品机器▲螺旋状的通心粉制造▲高温融化锁的过程▲一次…

Envoy实现.NET架构的网关(三)代理GRPC

.NET网关与Gateway实战-Envoy与kong课程Envoy实现.NET架构的网关(一)静态配置与文件动态配置Envoy实现.NET架构的网关(二)基于控制平面的动态配置什么是GRPCgRPC是一种与语言无关的高性能远程过程调用 (RPC) 框架。gRPC 的主要好处…

Linux 下用来查询安装包信息的RPM选项

Linux 下用来查询安装包信息的RPM选项RPM是RedHat的包管理器,用来安装、卸载、升级和查询基于RedHat Linux的安装包。RHEL和基于它的系统使用rpm命令来完成这些功能。AD:RPM是RedHat的包管理器,用来安装、卸载、升级和查询基于RedHat Linux的…