C# Hook原理及EasyHook簡易教程

来源:https://www.cnblogs.com/wackysoft/archive/2018/03/11/8544365.html
-Advertisement-
Play Games

前言 在說C# Hook之前,我們先來說說什麼是Hook技術。相信大家都接觸過外掛,不管是修改游戲客戶端的也好,盜取密碼的也罷,它們都是如何實現的呢? 實際上,Windows平臺是基於事件驅動機制的,整個系統都是通過消息的傳遞來實現的。當進程有響應時(包括響應滑鼠和鍵盤事件),則Windows會嚮應 ...


前言

  在說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

 

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、前言 JUC這部分還有線程池這一塊沒有分析,需要抓緊時間分析,下麵開始ThreadPoolExecutor,其是線程池的基礎,分析完了這個類會簡化之後的分析,線程池可以解決兩個不同問題:由於減少了每個任務調用的開銷,它們通常可以在執行大量非同步任務時提供增強的性能,並且還可以提供綁定和管理資源(包 ...
  • 內容:Java變數,基本數據類型 邏輯類型:boolean =true/false整數類型:byte占一個位元組,short占兩個位元組,int占四個位元組,long占8個位元組 int 可以表達十進位範圍 (2147483648,4294967296)字元類型:char占兩個位元組 (加單引號)ch=97, ...
  • 前言 在一個小項目的需求中,我需要一個短鏈生成伺服器來縮短一些某個網站的鏈接。 剛開始我使用的是新浪的短鏈生成服務,後來心血來潮Google了一下短鏈生成的演算法,在知乎上看到了 "一個非常棒的構思" ,也就是直接使用資料庫的id的62進位形式作為短鏈索引。 當天我們就把新浪的短鏈換成了自己的服務,不 ...
  • 作為一個java的學習者,我相信JDBC是大家最早接觸也是入門級別的資料庫連接方式,所以我們先來回憶一下JDBC作為一種用於執行SQL語句的Java API是如何工作的。下麵的一段代碼就是最基本的JDBC開發流程。 在上代碼之前要先導入JDBC的jar包,由於我用的資料庫是mysql,所以要先導 入 ...
  • 兩個星期前,微軟發佈了 "EF Core 2.1 Preview 1" ,同時還發佈了 ".NET Core 2.1 Preview 1" 和 "ASP.NET Core 2.1 Preview 1" ;EF Core 2.1 Preview 1 除了 "許多小改進和超過100種產品錯誤修複之外" ...
  • 本次 Windows Developer Day,最值得期待的莫過於 Windows AI Platform 了,可以說是千呼萬喚始出來。觀看直播的開發者們,留言最多的也是 Windows AI Platform。 下麵結合微軟提供的展示過程,文檔和 Git Sample 來詳細分析一下。 基礎概念 ...
  • 以前實現數據的緩存有多種方法,如客戶端的Cookie,伺服器端的Session、Application。 一、Cookie Cookie是保存客戶端的一組數據,主要用來保存用戶的個人信息,主要存放瀏覽器請求伺服器時的請求信息,這些信息是非敏感信息。主要用於當用戶訪問您的系統時,應用程式可以檢索以前存 ...
  • RabbitMQ原理及教程:http://www.cnblogs.com/AlvinLee/p/6141834.html EasyNetQ是什麼以及常見用法這裡都不做講解。可以參考這篇博文:http://blog.csdn.net/hesi9555/article/details/70139346 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...