找回密码
 立即注册
首页 资源区 代码 .NET外挂系列:5. harmony 中补丁参数的有趣玩法(下) ...

.NET外挂系列:5. harmony 中补丁参数的有趣玩法(下)

VerlaMcCle 2025-5-28 22:07:26
一:背景

1. 讲故事

开局一张表,故事全靠编,为了能够承上启下,先把参数列表放出来。
参数名说明__instance访问非静态方法的实例(类似 this)。__result获取/修改返回值,要想修改用 ref。__resultRef修改返回引用(方法返回是 ref 返回 )。__state在前缀和后缀间传递自定义数据 。___fields读写私有字段(三下划线开头,修改需加 ref)。__args以 object[] 形式访问所有参数(修改数组即修改参数)。方法参数同名直接映射原参数。__n__n 表示直接访问第 n 个参数,从 0 开始)。__originalMethod获取原方法的 MethodBase。__runOriginal判断原方法是否被执行。如果说上一篇聊到的参数是无害的,那这篇所聊到的参数就具有破坏性了,会让一些底层方法产生匪夷所思的输出结果。
二:补丁参数解读

1. __result

这个参数可以获取被注入方法的返回值,你可以对他进行查看和修改,为了让例子更有趣一点,我们对 DateTime.Now 进行注入,让它永远的丢失时分秒,是不是有点像黑客? 哈哈,参考代码如下:
  1.     internal class Program
  2.     {
  3.         static void Main(string[] args)
  4.         {
  5.             var harmony = new Harmony("com.example.patch");
  6.             harmony.PatchAll();
  7.             var time = DateTime.Now;
  8.             Console.WriteLine($"当前时间:{time}");
  9.             Console.ReadLine();
  10.         }
  11.     }
  12.     [HarmonyPatch(typeof(DateTime), "Now", MethodType.Getter)]
  13.     public class DateTimeHook
  14.     {
  15.         public static void Postfix(ref DateTime __result)
  16.         {
  17.             __result = __result.Date;
  18.         }
  19.     }
复制代码
1.png

是不是让人很恼火,明明调的是 DateTime.Now ,怎么时分秒不见了。。。
2. __args

在 harmony 中有三种方式可以获取原方法的参数,分别为:

  • object[] __args 获取,支持读写。
  • __n  下标获取,支持读写。
  • parameter 同名法,默认只读,写的话要加 ref。
为了让例子更加有趣和黑客,我们对 HttpClient 的底层方法 SendAsync 进行拦截,然后纂改url,指向一个来历不明的网址,参考代码如下:
  1.     internal class Program
  2.     {
  3.         static async Task Main(string[] args)
  4.         {
  5.             // 应用Harmony补丁
  6.             var harmony = new Harmony("com.example.httpclient");
  7.             harmony.PatchAll();
  8.             var url = "https://www.cnblogs.com";
  9.             var httpClient = new HttpClient();
  10.             
  11.             Console.WriteLine($"1.request:{url}");
  12.             var response = await httpClient.GetAsync(url);
  13.             var content = await response.Content.ReadAsStringAsync();
  14.             Console.WriteLine($"2.response:\n{content.Substring(0, 500)}");
  15.             Console.ReadKey();
  16.         }
  17.     }
  18.     [HarmonyPatch(typeof(HttpClient), "SendAsync", new Type[] { typeof(HttpRequestMessage), typeof(HttpCompletionOption), typeof(CancellationToken) })]
  19.     class HttpClientPatch
  20.     {
  21.         static void Prefix(object[] __args)
  22.         {
  23.             HttpRequestMessage request = (HttpRequestMessage)__args[0];
  24.             request.RequestUri = new Uri("http://www.baidu.com");
  25.         }
  26.     }
复制代码
2.png

从卦中看,我明明请求的是 博客园,怎么给我返回 百度 的内容,是不是非常诡异。。。
可能有朋友看到了,这里有一个 (HttpRequestMessage)__args[0]; 强转的逻辑,能不能在 Prefix(object[] __args) 中直接接收 HttpRequestMessage 参数呢?可以的,这这就 harmony 的另外一种同名参数法,也就是参数名一定要和底层的 SendAsync 方法签名保持一致,截图如下:
3.png

修改后的代码如下,是不是非常的清爽。
  1.     [HarmonyPatch(typeof(HttpClient), "SendAsync", new Type[] { typeof(HttpRequestMessage), typeof(HttpCompletionOption), typeof(CancellationToken) })]
  2.     class HttpClientPatch
  3.     {
  4.         static void Prefix(HttpRequestMessage request)
  5.         {
  6.             request.RequestUri = new Uri("http://www.baidu.com");
  7.         }
  8.     }
复制代码
可能有些人会遇到这样的情况,比如 SendAsync 方法的第一个参数是 internel 类型,由于是程序集可访问,所以你无法在另一个程序集的 Prefix 中声明此类型,这时候怎么办呢?可以借助 harmony 提供的 __n 索引法,下标是从0开始的。修改代码如下:
  1.     [HarmonyPatch(typeof(HttpClient), "SendAsync", new Type[] { typeof(HttpRequestMessage), typeof(HttpCompletionOption), typeof(CancellationToken) })]
  2.     class HttpClientPatch
  3.     {
  4.         static void Prefix(object __0)
  5.         {
  6.             Type requestType = __0.GetType();
  7.             PropertyInfo requestUriProperty = requestType.GetProperty("RequestUri");
  8.             Uri newUri = new Uri("http://www.baidu.com");
  9.             requestUriProperty.SetValue(__0, newUri);
  10.         }
  11.     }
复制代码
3. ___fields

这个参数也是一个非常简单粗暴的特性,它可以用三下划线___引出当前 this 实例上的私有字段,使用场景可以是这样的,我们知道 new Thread 默认是没有 ThreadName 的,这在高级调试中往往有所不便,所以可加这样的一段逻辑:一旦发现无名的 ThreadName 就给它赋一个默认的名字,参考代码如下:
  1.     internal class Program
  2.     {
  3.         static void Main(string[] args)
  4.         {
  5.             var harmony = new Harmony("com.example.patch");
  6.             harmony.PatchAll();
  7.             var thread = new Thread(() => { });
  8.             thread.Start();
  9.             Console.WriteLine($"1.查看线程名:{thread.Name?.ToString()}");
  10.             Console.ReadLine();
  11.         }
  12.     }
  13.     [HarmonyPatch(typeof(Thread), "Name", MethodType.Getter)]
  14.     public class ThreadStartHook
  15.     {
  16.         public static void Prefix(Thread __instance, ref string ____name)
  17.         {
  18.             if (string.IsNullOrEmpty(____name))
  19.             {
  20.                 ____name = $"Default Threadid:{__instance.ManagedThreadId}";
  21.             }
  22.         }
  23.     }
复制代码
4.png

三:总结

这篇文章我们聊到的一些参数多多少少都带点黑客性质,建议大家不要乱用,这里声明一下,我所说的一切都是为.NET高级调试训练营服务的,也是给学员们提供的拓展资料。

来源:新程序网络收集,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册