zoukankan      html  css  js  c++  java
  • 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进行卸载,否则容易影响到其他钩子的执行,并且钩子太多会影响目标进程的正常运行。

      关于实例详细操作过程不再赘述,请参考: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

  • 相关阅读:
    每日日报2020.12.1
    每日日报2020.11.30
    981. Time Based Key-Value Store
    1146. Snapshot Array
    565. Array Nesting
    79. Word Search
    43. Multiply Strings
    Largest value of the expression
    1014. Best Sightseeing Pair
    562. Longest Line of Consecutive One in Matrix
  • 原文地址:https://www.cnblogs.com/soundcode/p/15386695.html
Copyright © 2011-2022 走看看