攔截|篡改|偽造.NET類庫中不限于public的類和方法
大家好,我是沙漠盡頭的狼。
本文首發(fā)于Dotnet9,介紹使用Lib.Harmony
庫攔截第三方.NET
庫方法,達到不修改其源碼并能實現(xiàn)修改方法邏輯、預期行為的效果,并且不限于只攔截public
訪問修飾的類及方法,行文目錄:
什么是方法攔截?
示例程序攔截
非public方法怎么攔截?
總結
1. 什么是方法攔截?
方法攔截是指在方法被調用之前或之后,通過插入自定義的代碼來修改方法的行為。通過方法攔截,開發(fā)人員可以在不修改原始代碼的情況下,對方法的輸入?yún)?shù)進行驗證、修改方法的返回值、記錄方法的調用日志等操作。
本文使用Lib.Harmony
庫實現(xiàn)第三方庫方法的攔截,關于該庫站長寫過[快學會這個技能-.NET API攔截技法](快學會這個技能-.NET API攔截技法 - Dotnet9)一文,大家可以再看看,但該篇文章未介紹非public類及方法如何攔截,本文會有所補充反過來 。
2. 示例程序攔截
2.1. 編寫取數(shù)字段落的程序
創(chuàng)建一個.NET
類庫工程,比如叫TestDll
,添加工具類TestTool
:
上面的方法GetNumberSentence
邏輯:傳入一個整型number
參數(shù),除10(集合_sentences項數(shù))取模,返回10以內的數(shù)字美文段落,其中如果模為6會取數(shù)字1的段落(這是為了驗證攔截邏輯設計添加的)。
下面是寫的一個AvaloniaUI
程序測試界面,UI不是本文重點,這里就直接貼動圖和代碼截圖了,文末也有源碼鏈接:


2.2. 為什么個位數(shù)字為6時,總是顯示數(shù)字1的段落呢?
分析上面的代碼,我們想把mo == 6
時讓mo = 1
邏輯去掉,除了使用dnSpy
這些反編譯工具修改代碼,我們還可以使用Lib.Harmony
(快學會這個技能-.NET API攔截技法 - Dotnet9)攔截GetNumberSentence
方法。
安裝
Lib.Harmony
包
<PackageReference Include="Lib.Harmony" Version="2.3.0-prerelease.2" />
編寫攔截替換類
參考快學會這個技能-.NET API攔截技法 - Dotnet9添加如下攔截替換類:
在攔截類上注冊需要攔截的原類類型、原方法名和參數(shù)數(shù)據(jù)類型
可以先將原方法內代碼復制到攔截替換方法
Prefix
內,對于原類中的屬性、字段可通過反射獲取(比如_sentences
集合)將
mo == 6
的代碼注釋
別忘了在Program
或App.xaml
初始方法內注冊自動攔截類方法:
重新運行主程序,輸入數(shù)字6時正常顯示數(shù)字6對應的段落了:

這樣就達到不修改第三庫源碼的情況實現(xiàn)結果篡改了,站長使用.NET 8
攔截會有異常,后改為?.NET 6
?得以正常運行,異常信息如下,可能是Lib.Harmony
還不支持.NET 8
吧:
HarmonyLib.HarmonyException:“Patching exception in method System.String TestDll.TestTool::GetNumberSentence(System.Int32 number)”
TypeInitializationException: The type initializer for 'MonoMod.Utils.DMDEmitDynamicMethodGenerator' threw an exception.
InvalidOperationException: Cannot find returnType fieeld on DynamicMethod
3. 非public方法怎么攔截?
3.1. 修改數(shù)字段落獲取方法
還是修改TestTool
類,另外增加GetNumberSentence2
方法,在方法中添加一個數(shù)字驗證操作mo = new CalNumber().GetValidNumber(mo);
,方法定義如下:
驗證方法定義如下:
CalNumber
類和GetValidNumber
方法用internal
聲明,意為類或方法只能在當前工程內使用
并在主工程調用數(shù)字獲取段落方法處改為:
輸入6時又返回1的段落了:

問題來了:internal方法怎么攔截?
我們不直接注釋代碼mo = new CalNumber().GetValidNumber(mo);
,萬一驗證方法非常重要,我們只是需要修改其中部分邏輯,總體原邏輯不應該改變。
3.2. internal方法怎么攔截?
新增攔截類HookGetValidNumber
,現(xiàn)在不能再在類上添加特性了([HarmonyPatch(typeof(CalNumber))]
),因為CalNumber
不是public訪問修飾,跨工程無法直接使用,語法不支持:

特性用不上,那就手工注冊需要攔截的方法,這是本文的重點,代碼在下面,簡單提一下:
手工注冊代碼和自動注冊聲明特性類似,只是換個寫法;
攔截替換方法需要使用
HarmonyMethod
方法包裝;harmony.Patch(hookMethod, replaceHarmonyMethod);
用于關聯(lián)被攔截方法與替換方法
替換方法定義如下:
Prefix
方法命名這里不加限制,只要和上面手工注冊(var replaceMethod = typeof(HookGetValidNumber).GetMethod(nameof(Prefix));
)相同即可:數(shù)字等于6,修改偽造結果為8
最后在原自動注冊代碼下,再加一行手工注冊代碼就OK,打完收工:
運行效果如下,輸入6顯示數(shù)字8段落:

4. 總結
技術交流加群請?zhí)砑诱鹃L微信號:dotnet9com
文中示例代碼:MultiVersionLibrary
使用Lib.Harmony
庫攔截注冊有兩種方式的用處如下:
自動注冊:
通過在攔截類上使用特性關聯(lián)被攔截類和方法定義,可以實現(xiàn)自動注冊攔截邏輯。這種方式適用于需要攔截的類和方法比較多的情況,可以減少手動注冊的工作量,提高開發(fā)效率。
自動注冊通常只能關聯(lián)public類或方法,因為IDE會根據(jù)代碼的可見性進行過濾和提示。
手工注冊:
通過代碼構造被攔截類和方法定義進行手工注冊,可以更加靈活地控制攔截邏輯。這種方式適用于需要對攔截邏輯進行定制化處理的情況,可以根據(jù)具體需求選擇需要攔截的類和方法,并對攔截邏輯進行精細化配置。
手工注冊更加靈活,可以攔截包括internal在內的各種類和方法。手工注冊可以通過編寫代碼來實現(xiàn)對非public類和方法的關聯(lián),但需要注意的是,這樣做可能會增加代碼的復雜性和維護成本。
時間如流水,只能流去不流回。