程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 純C#完成Hook功用詳解

純C#完成Hook功用詳解

編輯:C#入門知識

純C#完成Hook功用詳解。本站提示廣大學習愛好者:(純C#完成Hook功用詳解)文章只能為提供參考,不一定能成為您想要的結果。以下是純C#完成Hook功用詳解正文


發布一個自己寫的用於Hook .Net辦法的類庫,代碼量不大,完全的C#代碼完成,是一個比擬風趣的功用,分享出來希望能和大家共同討論

裝置:Install-Package DotNetDetour

源碼:http://xiazai.jb51.net/201701/yuanma/DotNetDetour_jb51.rar

1.為何想做這個

說到hook大家都應該不生疏,就是改動函數的執行流程,讓本應該執行的函數跑到另一個函數中執行,這是個很有用也很風趣的功用(例如獲取函數參數信息,改動函數執行流程,計算函數執行時間等等),殺軟中主防的原理就是hook,經過hook阻攔函數獲取參數信息來判別能否是風險行為,但這類順序大多是C++的,不斷以來我都想完成可以hook .net函數的庫,網上搜索了很多,但都不理想,所以想自己完成一個

2.完成原理

我采用的是inline hook的方式,由於我對.net虛擬機以及一些外部的構造並不是很熟習,並且有些東西確實找不就任何文檔,所以就采用原生代碼的inline hook的方式來完成。

首先說一下inline hook的根本原理,它是經過修正函數的前5字節指令為jmp xxxxxxxx來完成的,例如一個C#辦法:

用windbg調試檢查辦法信息:

檢查曾經jit了的原生代碼:

這裡的地址(0x008c0640)可以經過MethodInfo.MethodHandle.GetFunctionPointer().ToPointer()辦法獲取

到了這裡,我們就知道了修正從push ebp開端的5個字節為jmp跳轉指令,跳入我們自己的函數就可以到達hook的目的,但執行到我們的函數後,假如我們並不是要阻攔執行流程,那麼我們最終是需求再調用原函數的,但原函數曾經被修正了,這會想到的方法就是恢復那修正的5字節指令,但這又會引發另一個問題,就是當我們恢復時,正好另一個線程調用到這個函數,那麼順序將會解體,或許說漏掉一次函數調用,修正時暫停其他線程並等候正跑在其中的CPU執行完這5字節再去恢復指令也許是個不錯的方法,但覺得並不容易完成,而且影響功能,所以我保持了這種方法

那麼如何才干調用修正前的函數呢,我首先想到是C中寫裸函數的方式,即自己用匯編拼出來一個原函數再執行:

原函數前5字節指令+jmp跳轉指令

但其實這也是不可行的,聰明的人曾經發現,圖中所示的函數的前5字節並不是一個完好的匯編指令,不同的函數,長度都不一樣,.net的函數並不像某些原生函數那樣,會預留mov edi,edi這樣的正好5字節的指令,我先想到的是復制函數的一切匯編指令生成新的函數,但這樣也會出問題,由於像E8,E9這樣的絕對跳轉指令,假如指令地址變了,那麼跳轉的地位也就變了,順序就會解體,所以這也不可行。

到了這裡,我有些不耐煩了,畢竟我是要hook一切函數的,而不是某個固定的函數,而函數入口的指令又不相反,這可怎樣辦,難道我需求計算出大於等於5字節的最小完好匯編指令長度?

依照這個思緒,最終找到了一個用C寫的反匯編庫(BlackBone),其中提供了相似的辦法,我稍作了修正後試用了下,確實不錯,可以精確求出匯編指令長度,例如

push ebp

mov ebp,esp

mov eax,dword ptr ds:[33F22ACh]

求出值是9,這樣我依據求出的值靜態拼接一個函數出來即可,哈哈,到了這裡,覺得完成的差不多了,但沒想到64位下又給了我當頭一棒,之前的原函數指令可以寫成:

大於等於5字節的最小完好匯編指令+jmp跳轉指令即可構成我們的原函數

但我們知道,C#中要想執行匯編,是需求用Marshal.AllocHGlobal來分配非托管空間的,而這樣分配的地址與我們要跳轉到的原函數的地址在64位下是超越2GB地址范圍的,普通的跳轉指令是無法完成的,所以想到了用ret指令完成,而64位地址又不能直接push,所以最後寫出如下匯編:

push rax

mov rax,target_addr

push rax

mov rax,qword ptr ss:[rsp+8]

ret 8

由於某些C#函數居然第一行就是修正rax存放器的值,所以只能是先保管rax,推入堆棧後再恢復,這裡匯編操作就方便多了,之前完成另一個東西,用到IL指令,但發現只要dup這種復制棧頂元素的指令,卻沒有獲取堆棧中某個非棧頂元素值的指令,所以說還是匯編靈敏啊,想怎樣寫就怎樣寫,啥都能完成。

最後就是這個原函數的調用進程了,由於是靜態拼接的函數,所以想到的就是用Marshal.GetDelegateForFunctionPointer轉成委托來執行,後來發現不對,由於我雖然拼接的是匯編,而這個匯編是C#辦法jit後的匯編,這個並不是C辦法編譯後的匯編,經過把非托管指針轉換為委托的方式運轉函數是會添加很多不需求的操作的,例如托管類型與非托管類型的轉換,但我拼接出的函數是不需求這些進程的,這個怎樣辦,看來只能用調用C#普通函數的方式調用,這個怎樣完成呢,其實很好辦,只需寫一個空殼函數,然後修正這個函數的辦法表中的原生指令指針即可,詳細辦法如下:

*((ulong*)((uint*)method.MethodHandle.Value.ToPointer() + 2)) = (ulong)ptr;

method是空殼函數的MethodInfo,ptr是靜態拼接的原函數的地址

好,到了這裡就根本完成中心功用了,最不益處理的就是這個原函數調用,我的完好的64位原函數指令拼接就完成了,代碼很少,如下所示:

byte[] jmp_inst =

{

 0x50,            //push rax

 0x48,0xB8,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90, //mov rax,target_addr

 0x50,            //push rax

 0x48,0x8B,0x44,0x24,0x08,       //mov rax,qword ptr ss:[rsp+8]

 0xC2,0x08,0x00          //ret 8

};

 

protected override void CreateOriginalMethod(MethodInfo method)

{

 uint oldProtect;

 var needSize = NativeAPI.SizeofMin5Byte(srcPtr);

 byte[] src_instr = new byte[needSize];

 for (int i = 0; i < needSize; i++)

 {

  src_instr[i] = srcPtr[i];

 }

 fixed (byte* p = &jmp_inst[3])

 {

  *((ulong*)p) = (ulong)(srcPtr + needSize);

 }

 var totalLength = src_instr.Length + jmp_inst.Length;

 IntPtr ptr = Marshal.AllocHGlobal(totalLength);

 Marshal.Copy(src_instr, 0, ptr, src_instr.Length);

 Marshal.Copy(jmp_inst, 0, ptr + src_instr.Length, jmp_inst.Length);

 NativeAPI.VirtualProtect(ptr, (uint)totalLength, Protection.PAGE_EXECUTE_READWRITE, out oldProtect);

 RuntimeHelpers.PrepareMethod(method.MethodHandle);

 *((ulong*)((uint*)method.MethodHandle.Value.ToPointer() + 2)) = (ulong)ptr;

}

3.類庫開發所用到的言語

之前我說,我的這個庫是完全用C#完成的,但其中確實用到了一個C寫的反匯編庫,於是我用C#把那個庫重寫了一遍,說來也復雜,C的代碼粘過去,C#啟用unsafe代碼,改了10分鐘就好了,真心是十分方便,畢竟C#是支持指針和構造體的,而且根底類型十分豐厚,這裡得給C#點個贊!

4.詳細運用

運用十分復雜,首先新建控制台順序並添加一個類,承繼接口IMethodMonitor,Get是你自己的函數,Ori是原函數會在運轉時靜態生成,在Get中你可以干你想干的任何事情

public class CustomMonitor : IMethodMonitor //自定義一個類並承繼IMethodMonitor接口

{

 [Monitor("TargetNamespace", "TargetClass")] //你要hook的目的辦法的稱號空間,類名

 public string Get() //辦法簽名要與目的辦法分歧

 {

  return "B" + Ori();

 }

 

 [MethodImpl(MethodImplOptions.NoInlining)]

 [Original] //原函數標志

 public string Ori() //辦法簽名要與目的辦法分歧

 {

  return null; //這裡寫什麼無所謂,能編譯過即可

 }

}

然後定義目的函數,例如

public string Get()

 {

 return "A";

 }

最後調用Monitor.Install()裝置監視器,例如:

Console.WrtieLine(Get());

Monitor.Install()

Console.WrtieLine(Get());

你會發現第一次調用Get輸入的值是"A",第二次是"BA"

當然這個庫只是hook,但hook普通都需求dll注入來配合,由於hook本身進程沒什麼意義,hook他人的進程才有意義,我之後會發布一個用於.net順序近程注入的類庫,注入的是.net的dll哦,不是C++的

好了,講了這麼多,其實這個庫代碼量並不大,但次要是自己研討的一個效果,很多東西都是自己揣摩出來的,所以覺得這個進程很有意思,也希望高手能指出改良方案,畢竟覺得目前這種辦法雖然完成了功用,但是並不是很好,總覺得以hook .net虛擬機的方式來完成會更復雜一些,或許網絡上曾經有了現成的處理方案我沒有找到,總之,拋磚引玉,希望大家能共同討論

以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支持。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved