日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > C# >内容正文

C#

纯C#实现Hook功能

發(fā)布時間:2025/7/25 C# 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 纯C#实现Hook功能 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

純C#實(shí)現(xiàn)Hook功能

發(fā)布一個自己寫的用于Hook?.Net方法的類庫,代碼量不大,完全的C#代碼實(shí)現(xiàn),是一個比較有趣的功能,分享出來希望能和大家共同探討

安裝:Install-Package?DotNetDetour
源碼:https://github.com/bigbaldy1128/DotNetDetour

1.為何想做這個
說到hook大家都應(yīng)該不陌生,就是改變函數(shù)的執(zhí)行流程,讓本應(yīng)該執(zhí)行的函數(shù)跑到另一個函數(shù)中執(zhí)行,這是個很有用也很有趣的功能(例如獲取函數(shù)參數(shù)信息,改變函數(shù)執(zhí)行流程,計(jì)算函數(shù)執(zhí)行時間等等),殺軟中主防的原理就是hook,通過hook攔截函數(shù)獲取參數(shù)信息來判斷是否是危險(xiǎn)行為,但這類程序大多是C++的,一直以來我都想實(shí)現(xiàn)可以hook?.net函數(shù)的庫,網(wǎng)上搜索了很多,但都不理想,所以想自己實(shí)現(xiàn)一個

2.實(shí)現(xiàn)原理
我采用的是inline?hook的方式,因?yàn)槲覍?net虛擬機(jī)以及一些內(nèi)部的結(jié)構(gòu)并不是很熟悉,并且有些東西的確找不到任何文檔,所以就采用原生代碼的inline?hook的方式來實(shí)現(xiàn)。

首先說一下inline?hook的基本原理,它是通過修改函數(shù)的前5字節(jié)指令為jmp?xxxxxxxx來實(shí)現(xiàn)的,例如一個C#方法:

用windbg調(diào)試查看方法信息:

查看已經(jīng)jit了的原生代碼:

這里的地址(0x008c0640)可以通過MethodInfo.MethodHandle.GetFunctionPointer().ToPointer()方法獲取

到了這里,我們就知道了修改從push?ebp開始的5個字節(jié)為jmp跳轉(zhuǎn)指令,跳入我們自己的函數(shù)就可以達(dá)到hook的目的,但執(zhí)行到我們的函數(shù)后,如果我們并不是要攔截執(zhí)行流程,那么我們最終是需要再調(diào)用原函數(shù)的,但原函數(shù)已經(jīng)被修改了,這會想到的辦法就是恢復(fù)那修改的5字節(jié)指令,但這又會引發(fā)另一個問題,就是當(dāng)我們恢復(fù)時,正好另一個線程調(diào)用到這個函數(shù),那么程序?qū)罎?#xff0c;或者說漏掉一次函數(shù)調(diào)用,修改時暫停其他線程并等待正跑在其中的CPU執(zhí)行完這5字節(jié)再去恢復(fù)指令也許是個不錯的辦法,但感覺并不容易實(shí)現(xiàn),而且影響性能,所以我放棄了這種辦法

那么如何才能調(diào)用修改前的函數(shù)呢,我首先想到是C中寫裸函數(shù)的方式,即自己用匯編拼出來一個原函數(shù)再執(zhí)行:
原函數(shù)前5字節(jié)指令+jmp跳轉(zhuǎn)指令
但其實(shí)這也是不可行的,聰明的人已經(jīng)發(fā)現(xiàn),圖中所示的函數(shù)的前5字節(jié)并不是一個完整的匯編指令,不同的函數(shù),長度都不一樣,.net的函數(shù)并不像某些原生函數(shù)那樣,會預(yù)留mov?edi,edi這樣的正好5字節(jié)的指令,我先想到的是復(fù)制函數(shù)的所有匯編指令生成新的函數(shù),但這樣也會出問題,因?yàn)橄馝8,E9這樣的相對跳轉(zhuǎn)指令,如果指令地址變了,那么跳轉(zhuǎn)的位置也就變了,程序就會崩潰,所以這也不可行。

到了這里,我有些不耐煩了,畢竟我是要hook所有函數(shù)的,而不是某個固定的函數(shù),而函數(shù)入口的指令又不相同,這可怎么辦,難道我需要計(jì)算出大于等于5字節(jié)的最小完整匯編指令長度?

按照這個思路,最終找到了一個用C寫的反匯編庫(BlackBone),其中提供了類似的方法,我稍作了修改后試用了下,的確不錯,可以準(zhǔn)確求出匯編指令長度,例如

push?ebp
mov?ebp,esp
mov?eax,dword?ptr?ds:[33F22ACh]

求出值是9,這樣我根據(jù)求出的值動態(tài)拼接一個函數(shù)出來即可,哈哈,到了這里,感覺實(shí)現(xiàn)的差不多了,但沒想到64位下又給了我當(dāng)頭一棒,之前的原函數(shù)指令可以寫成:

大于等于5字節(jié)的最小完整匯編指令+jmp跳轉(zhuǎn)指令即可構(gòu)成我們的原函數(shù)

但我們知道,C#中要想執(zhí)行匯編,是需要用Marshal.AllocHGlobal來分配非托管空間的,而這樣分配的地址與我們要跳轉(zhuǎn)到的原函數(shù)的地址在64位下是超過2GB地址范圍的,一般的跳轉(zhuǎn)指令是無法實(shí)現(xiàn)的,所以想到了用ret指令實(shí)現(xiàn),而64位地址又不能直接push,所以最后寫出如下匯編:

push?rax
mov?rax,target_addr
push?rax
mov?rax,qword?ptr?ss:[rsp+8]
ret?8

由于某些C#函數(shù)竟然第一行就是修改rax寄存器的值,所以只能是先保存rax,推入堆棧后再恢復(fù),這里匯編操作就方便多了,之前實(shí)現(xiàn)另一個東西,用到IL指令,但發(fā)現(xiàn)只有dup這種復(fù)制棧頂元素的指令,卻沒有獲取堆棧中某個非棧頂元素值的指令,所以說還是匯編靈活啊,想怎么寫就怎么寫,啥都能實(shí)現(xiàn)。

最后就是這個原函數(shù)的調(diào)用過程了,因?yàn)槭莿討B(tài)拼接的函數(shù),所以想到的就是用Marshal.GetDelegateForFunctionPointer轉(zhuǎn)成委托來執(zhí)行,后來發(fā)現(xiàn)不對,因?yàn)槲译m然拼接的是匯編,而這個匯編是C#方法jit后的匯編,這個并不是C方法編譯后的匯編,通過把非托管指針轉(zhuǎn)換為委托的方式運(yùn)行函數(shù)是會添加很多不需要的操作的,例如托管類型與非托管類型的轉(zhuǎn)換,但我拼接出的函數(shù)是不需要這些過程的,這個怎么辦,看來只能用調(diào)用C#普通函數(shù)的方式調(diào)用,這個怎么實(shí)現(xiàn)呢,其實(shí)很好辦,只需寫一個空殼函數(shù),然后修改這個函數(shù)的方法表中的原生指令指針即可,具體方法如下:

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


method是空殼函數(shù)的MethodInfo,?????????ptr是動態(tài)拼接的原函數(shù)的地址

好,到了這里就基本完成核心功能了,最不好處理的就是這個原函數(shù)調(diào)用,我的完整的64位原函數(shù)指令拼接就實(shí)現(xiàn)了,代碼很少,如下所示:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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.類庫開發(fā)所用到的語言
之前我說,我的這個庫是完全用C#實(shí)現(xiàn)的,但其中的確用到了一個C寫的反匯編庫,于是我用C#把那個庫重寫了一遍,說來也簡單,C的代碼粘過來,C#啟用unsafe代碼,改了10分鐘就好了,真心是非常方便,畢竟C#是支持指針和結(jié)構(gòu)體的,而且基礎(chǔ)類型非常豐富,這里得給C#點(diǎn)個贊!

4.具體使用
使用非常簡單,首先新建控制臺程序并添加一個類,繼承接口IMethodMonitor,Get是你自己的函數(shù),Ori是原函數(shù)會在運(yùn)行時動態(tài)生成,在Get中你可以干你想干的任何事情

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public?class?CustomMonitor?:?IMethodMonitor?//自定義一個類并繼承IMethodMonitor接口 { ????[Monitor("TargetNamespace",?"TargetClass")]?//你要hook的目標(biāo)方法的名稱空間,類名 ????public?string?Get()?//方法簽名要與目標(biāo)方法一致 ????{ ????????return?"B"?+?Ori(); ????} ????[MethodImpl(MethodImplOptions.NoInlining)] ????[Original]?//原函數(shù)標(biāo)記 ????public?string?Ori()?//方法簽名要與目標(biāo)方法一致 ????{ ????????return?null;?//這里寫什么無所謂,能編譯過即可 ????} }


然后定義目標(biāo)函數(shù),例如

1 2 3 4 public?string?Get() ?{ ????return?"A"; ?}


最后調(diào)用Monitor.Install()安裝監(jiān)視器,例如:

1 2 3 Console.WrtieLine(Get()); Monitor.Install() Console.WrtieLine(Get());


你會發(fā)現(xiàn)第一次調(diào)用Get輸出的值是"A",第二次是"BA"

當(dāng)然這個庫只是hook,但hook一般都需要dll注入來配合,因?yàn)閔ook自身進(jìn)程沒什么意義,hook別人的進(jìn)程才有意義,我之后會發(fā)布一個用于.net程序遠(yuǎn)程注入的類庫,注入的是.net的dll哦,不是C++的

好了,講了這么多,其實(shí)這個庫代碼量并不大,但主要是自己研究的一個成果,很多東西都是自己琢磨出來的,所以覺得這個過程很有意思,也希望高手能指出改進(jìn)方案,畢竟感覺目前這種方法雖然實(shí)現(xiàn)了功能,但是并不是很好,總覺得以hook?.net虛擬機(jī)的方式來實(shí)現(xiàn)會更簡單一些,或者網(wǎng)絡(luò)上已經(jīng)有了現(xiàn)成的解決方案我沒有找到,總之,拋磚引玉,希望大家能共同探討

轉(zhuǎn)載于:https://www.cnblogs.com/zyb2016/p/5685005.html

總結(jié)

以上是生活随笔為你收集整理的纯C#实现Hook功能的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。