- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
大家好,我是沙漠尽头的狼.
本文首发于 Dotnet9 ,介绍使用 Lib.Harmony 库拦截第三方 .NET 库方法,达到不修改其源码并能实现修改方法逻辑、预期行为的效果,并且不限于只拦截 public 访问修饰的类及方法,行文目录:
方法拦截是指在方法被调用之前或之后,通过插入自定义的代码来修改方法的行为。通过方法拦截,开发人员可以在不修改原始代码的情况下,对方法的输入参数进行验证、修改方法的返回值、记录方法的调用日志等操作.
本文使用 Lib.Harmony 库实现第三方库方法的拦截,关于该库站长写过[快学会这个技能-.NET API拦截技法]( 快学会这个技能-.NET API拦截技法 - Dotnet9 )一文,大家可以再看看,但该篇文章未介绍非public类及方法如何拦截,本文会有所补充反过来 .
创建一个 .NET 类库工程,比如叫 TestDll ,添加工具类 TestTool :
namespace TestDll;
public class TestTool
{
/// <summary>
/// 带数字的优美段落
/// </summary>
private readonly List<string> _sentences = new()
{
"一,是孤独的象征,寂寞的代言人, 它独自站在诗句的起点,引人遐想。",
"二,是相对的存在,对立的伴侣, 它们如影随形,互相依存。",
"三,是完美的数字,三角的稳定, 它给诗歌带来了和谐的节奏。",
"四,是平衡的象征,四季的轮回, 它让诗歌的结构更加坚实。",
"五,是生机勃勃的数字,五彩斑斓的花朵, 它们在诗歌中绽放出美丽的画面。 ",
"六,是平凡的数字,六边形的形状, 它们给诗歌带来了一种稳定的感觉。",
"七,是神秘的数字,七色的虹霓, 它们在诗歌中散发出神奇的光芒。",
"八,是无限的数字,八方的宇宙, 它们让诗歌的想象力无限延伸。",
"九,是完美的数字,九曲的江河, 它们给诗歌带来了一种流动的美感。 ",
"十,是圆满的数字,十全十美的象征, 它们让诗歌的结尾更加完美。"
};
/// <summary>
/// 取对应数字的段落
/// </summary>
/// <param name="number"></param>
/// <returns></returns>
public string GetNumberSentence(int number)
{
var mo = number % _sentences.Count;
// 个位为0,取最后一
if (mo == 0)
{
mo = 10;
}
if (mo == 6)
{
mo = 1;
}
var sentencesIndex = mo - 1;
return _sentences[sentencesIndex];
}
}
上面的方法 GetNumberSentence 逻辑:传入一个整型 number 参数,除10(集合_sentences项数)取模,返回10以内的数字美文段落,其中如果模为6会取数字1的段落(这是为了验证拦截逻辑设计添加的).
下面是写的一个 AvaloniaUI 程序测试界面,UI不是本文重点,这里就直接贴动图和代码截图了,文末也有源码链接:
分析上面的代码,我们想把 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 添加如下拦截替换类:
Prefix
内,对于原类中的属性、字段可通过反射获取(比如 _sentences
集合) mo == 6
的代码注释
using HarmonyLib;
using System.Reflection;
using TestDll;
namespace MultiVersionLibrary;
/// <summary>
/// HarmonyPatch特性关联拦截的类及方法
/// </summary>
[HarmonyPatch(typeof(TestTool))]
[HarmonyPatch(nameof(TestTool.GetNumberSentence))]
[HarmonyPatch(new Type[] { typeof(int) })]
internal class HookGetNumberSentence
{
/// <summary>
/// GetNumberSentence拦截替换方法
/// </summary>
/// <param name="__instance">拦截的TestTool实例</param>
/// <param name="number">GetNumberSentence方法同名参数定义,修改它达到方法参数篡改</param>
/// <param name="__result">GetNumberSentence方法返回值,修改它达到方法值伪造</param>
/// <returns></returns>
public static bool Prefix(ref object __instance, int number, ref string __result)
{
try
{
//将原方法逻辑全部复制,然后做部分修改
//1、_sentences是拦截类TestTool的私胡字段,我们通过反射获取值
var sentences =
__instance.GetType().GetField("_sentences", BindingFlags.NonPublic | BindingFlags.Instance)
?.GetValue(__instance) as List<string>;
if (sentences?.Any() != true)
{
__result = "啊,没有优美句子吗?";
return true;
}
var mo = number % sentences.Count;
// 个位为0,取最后一
if (mo == 0)
{
mo = 10;
}
// 2、注释我们认为有歧义的代码
//if (mo == 6)
//{
// mo = 1;
//}
var sentencesIndex = mo - 1;
__result = sentences[sentencesIndex];
return false;
}
catch (Exception ex)
{
return true;
}
}
}
别忘了在 Program 或 App.xaml 初始方法内注册自动拦截类方法:
var harmony = new Harmony("https://dotnet9.com");
harmony.PatchAll(Assembly.GetExecutingAssembly());
重新运行主程序,输入数字6时正常显示数字6对应的段落了:
这样就达到不修改第三库源码的情况实现结果篡改了,站长使用 .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 。
还是修改 TestTool 类,另外增加 GetNumberSentence2 方法,在方法中添加一个数字验证操作 mo = new CalNumber().GetValidNumber(mo); ,方法定义如下:
/// <summary>
/// 取对应数字的段落
/// </summary>
/// <param name="number"></param>
/// <returns></returns>
public string GetNumberSentence2(int number)
{
var mo = number % _sentences.Count;
// 个位为0,取最后一
if (mo == 0)
{
mo = 10;
}
// 新增数字验证方法
mo = new CalNumber().GetValidNumber(mo);
var sentencesIndex = mo - 1;
return _sentences[sentencesIndex];
}
验证方法定义如下:
CalNumber
类和 GetValidNumber
方法用 internal
声明,意为类或方法只能在当前工程内使用
internal class CalNumber
{
internal int GetValidNumber(int number)
{
// 这里可以加一些复杂的算法代码
if (number == 6)
{
number = 1;
}
return number;
}
}
并在主工程调用数字获取段落方法处改为:
public string? Number
{
get { return _number; }
set
{
_number = value;
TryParse(_number, out var factNumber);
// 换方法2了
Message = _testTool.GetNumberSentence2(factNumber);
}
}
输入6时又返回1的段落了:
问题来了:internal方法怎么拦截?
我们不直接注释代码 mo = new CalNumber().GetValidNumber(mo); ,万一验证方法非常重要,我们只是需要修改其中部分逻辑,总体原逻辑不应该改变.
新增拦截类 HookGetValidNumber ,现在不能再在类上添加特性了( [HarmonyPatch(typeof(CalNumber))] ),因为 CalNumber 不是public访问修饰,跨工程无法直接使用,语法不支持:
特性用不上,那就手工注册需要拦截的方法,这是本文的重点,代码在下面,简单提一下:
HarmonyMethod
方法包装; harmony.Patch(hookMethod, replaceHarmonyMethod);
用于关联被拦截方法与替换方法
/// <summary>
/// 手工注册关联被拦截方法与替换方法
/// </summary>
public static void StartHook()
{
var harmony = new Harmony("https://dotnet9.com");
var hookClassType = typeof(TestTool).Assembly.GetType("TestDll.CalNumber");
var hookMethod = hookClassType!.GetMethod("GetValidNumber", BindingFlags.NonPublic | BindingFlags.Instance,
new[] { typeof(int) });
var replaceMethod = typeof(HookGetValidNumber).GetMethod(nameof(Prefix));
var replaceHarmonyMethod = new HarmonyMethod(replaceMethod);
harmony.Patch(hookMethod, replaceHarmonyMethod);
}
替换方法定义如下:
Prefix
方法命名这里不加限制,只要和上面手工注册( var replaceMethod = typeof(HookGetValidNumber).GetMethod(nameof(Prefix));
)相同即可:
/// <summary>
/// GetNumberSentence拦截替换方法
/// </summary>
/// <param name="__instance">拦截的TestTool实例</param>
/// <param name="number">GetNumberSentence方法同名参数定义,修改它达到方法参数篡改</param>
/// <param name="__result">GetNumberSentence方法返回值,修改它达到方法值伪造</param>
/// <returns></returns>
public static bool Prefix(ref object __instance, int number, ref int __result)
{
//将原方法逻辑全部复制,然后做部分修改
// 这里可以加一些复杂的算法代码
if (number == 6)
{
number = 8;
}
__result = number;
return false;
}
最后在原自动注册代码下,再加一行手工注册代码就OK,打完收工:
// 1、自动注册拦截类:拦截类上添加被拦截类和方法特性
var harmony = new Harmony("https://dotnet9.com");
harmony.PatchAll(Assembly.GetExecutingAssembly());
// 2、自动注册拦截类,构造被拦截类和方法信息进行拦截
HookGetValidNumber.StartHook();
运行效果如下,输入6显示数字8段落:
使用 Lib.Harmony 库拦截注册有两种方式的用处如下:
最后此篇关于拦截|篡改|伪造.NET类库中不限于public的类和方法的文章就讲到这里了,如果你想了解更多关于拦截|篡改|伪造.NET类库中不限于public的类和方法的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我想了解 Ruby 方法 methods() 是如何工作的。 我尝试使用“ruby 方法”在 Google 上搜索,但这不是我需要的。 我也看过 ruby-doc.org,但我没有找到这种方法。
Test 方法 对指定的字符串执行一个正则表达式搜索,并返回一个 Boolean 值指示是否找到匹配的模式。 object.Test(string) 参数 object 必选项。总是一个
Replace 方法 替换在正则表达式查找中找到的文本。 object.Replace(string1, string2) 参数 object 必选项。总是一个 RegExp 对象的名称。
Raise 方法 生成运行时错误 object.Raise(number, source, description, helpfile, helpcontext) 参数 object 应为
Execute 方法 对指定的字符串执行正则表达式搜索。 object.Execute(string) 参数 object 必选项。总是一个 RegExp 对象的名称。 string
Clear 方法 清除 Err 对象的所有属性设置。 object.Clear object 应为 Err 对象的名称。 说明 在错误处理后,使用 Clear 显式地清除 Err 对象。此
CopyFile 方法 将一个或多个文件从某位置复制到另一位置。 object.CopyFile source, destination[, overwrite] 参数 object 必选
Copy 方法 将指定的文件或文件夹从某位置复制到另一位置。 object.Copy destination[, overwrite] 参数 object 必选项。应为 File 或 F
Close 方法 关闭打开的 TextStream 文件。 object.Close object 应为 TextStream 对象的名称。 说明 下面例子举例说明如何使用 Close 方
BuildPath 方法 向现有路径后添加名称。 object.BuildPath(path, name) 参数 object 必选项。应为 FileSystemObject 对象的名称
GetFolder 方法 返回与指定的路径中某文件夹相应的 Folder 对象。 object.GetFolder(folderspec) 参数 object 必选项。应为 FileSy
GetFileName 方法 返回指定路径(不是指定驱动器路径部分)的最后一个文件或文件夹。 object.GetFileName(pathspec) 参数 object 必选项。应为
GetFile 方法 返回与指定路径中某文件相应的 File 对象。 object.GetFile(filespec) 参数 object 必选项。应为 FileSystemObject
GetExtensionName 方法 返回字符串,该字符串包含路径最后一个组成部分的扩展名。 object.GetExtensionName(path) 参数 object 必选项。应
GetDriveName 方法 返回包含指定路径中驱动器名的字符串。 object.GetDriveName(path) 参数 object 必选项。应为 FileSystemObjec
GetDrive 方法 返回与指定的路径中驱动器相对应的 Drive 对象。 object.GetDrive drivespec 参数 object 必选项。应为 FileSystemO
GetBaseName 方法 返回字符串,其中包含文件的基本名 (不带扩展名), 或者提供的路径说明中的文件夹。 object.GetBaseName(path) 参数 object 必
GetAbsolutePathName 方法 从提供的指定路径中返回完整且含义明确的路径。 object.GetAbsolutePathName(pathspec) 参数 object
FolderExists 方法 如果指定的文件夹存在,则返回 True;否则返回 False。 object.FolderExists(folderspec) 参数 object 必选项
FileExists 方法 如果指定的文件存在返回 True;否则返回 False。 object.FileExists(filespec) 参数 object 必选项。应为 FileS
我是一名优秀的程序员,十分优秀!