Learn this skill quickly-. NET API interception techniques

Learn this skill quickly-. NET API interception techniques

How to tamper with the input parameters of a method without changing the source code? Forging return results?

最后更新 2/13/2023 8:21 PM
沙漠尽头的狼
预计阅读 16 分钟
分类
.NET
标签
.NET C# intercept harmony hook

Hello everyone, I am a wolf at the end of the desert.

This article first raises the following questions. Please look for answers in the article and answer them in the comment area:

  1. What is API interception?
  2. A method is called in many places. How can we record the time before and after the method is called without modifying the source code of the method?
  3. Same as 2. How to correct (tamper) the parameters of the method without modifying the source code?
  4. Same as 3, how to forge the return value of a method without modifying the source code? ...

1. preface

前言翻译自一个国外的文章,他写的更容易让人理解 - Hacking .NET – rewriting code you don’t control

Have you ever encountered a class library method that doesn't belong to you but wants to change its behavior? Typically, the method is private and there is no good way to override its behavior. You can see how it works (because you're great and use decompile tools like Resharper, dnSpy, right?), You just can't change it. You really need to change it for XXX reasons.

    • There are several options available to you: **
  1. Obtain the source code by decompiling or downloading the source code if it is available first. This is often risky because it often comes with a complex build process, many dependencies, and now you are responsible for maintaining the entire branch of the library, even if you only want to make a small change.

  2. 使用 ILDasm 反编译应用,直接修补 IL 代码,然后使用 ILAs 将其组装回来。在许多方面,这更好,因为您可以创建一个战略性的手术切口,而不是全面的“从头开始”的方法。缺点是您必须完全在IL中实现您的方法,这是一个不平凡的冒险。

If you are working with a signed library, either of the above methods will not work.

现在让我们看一下另一种解决方法-内存修补。这与游戏作弊引擎几十年来使用的技术相同,这些引擎附加到正在运行的进程,查找内存位置并改变其行为。听起来很复杂? 实际上,在 .NET 中做到这一点比听起来容易得多。我们将使用一个名为Harmony的库,该库在NuGet上可通过“Lib.Harmony”包获得。这是一个用于 .NET 的内存修补引擎,主要针对使用 Unity 构建的游戏,当然不止Unity

In this article, webmasters will show you how to change what you think is impossible-starting with hooking your own libraries and ending with hooking WPF libraries and. NET basics libraries.

2. Hook your own library

2.1. preparations

  1. 创建一个控制台程序 HelloHook,添加类 Student
namespace HelloHook;

public class Student
{
    public string GetDetails(string name)
    {
        return $"大家好,我是沙漠尽头的狼网站站长:{name}";
    }
}

Student类中定义了一个GetDetails方法,返回格式化的个人介绍信息,这个方法后面拦截试验使用。

  1. Program.cs中添加Student调用:
using HelloHook;

var student = new Student();
Console.WriteLine(student.GetDetails("沙漠尽头的狼"));

The output of the running program is as follows:

大家好,我是沙漠尽头的狼网站站长:沙漠尽头的狼

The basic work is ready to be completed. This is a simple console program. The content of the following article will be detailed based on these two projects.

2.2. 拦截GetDetails方法

  1. Introducing interception packages-Lib. Harmony

我们使用Lib.Harmony包,API的拦截就靠它了,在HelloHook工程中添加如下NuGet包:

<PackageReference Include="Lib.Harmony" Version="2.2.2" />
  1. interception processing

添加拦截类HookStudent

using HarmonyLib;

namespace HelloHook;

[HarmonyPatch(typeof(Student))]
[HarmonyPatch(nameof(Student.GetDetails))]
public class HookStudent
{
    public static bool Prefix()
    {
        Console.WriteLine($"Prefix");
        return true;
    }

    public static void Postfix()
    {
        Console.WriteLine($"Postfix");
    }

    public static void Finalizer()
    {
        Console.WriteLine($"Finalizer");
    }
}

看代码中的注释,HookStudent类上添加了两个HarmonyPatch特性:

  • 第一个是关联被拦截的类Student类型;
  • 第二个是关联被拦截的类方法GetDetails

即当程序中调用Student类的GetDetails方法时,HookStudent内定义的方法就会分别执行,三个方法执行顺序是Prefix->Postfix->Finalizer,当然约定的方法不止这三个,其实我们常用的应该是PrefixPostfix,约定方法的意义见后方说明,没说就看Harmony wiki...

2.3. Registration Block

Program.cs进行修改,添加Harmony对整个程序集的拦截:

using HarmonyLib;
using HelloHook;
using System.Reflection;

var student = new Student();
Console.WriteLine(student.GetDetails("沙漠尽头的狼"));  

var harmony = new Harmony("https://dotnet9.com");
harmony.PatchAll(Assembly.GetExecutingAssembly());

Console.WriteLine(student.GetDetails("沙漠尽头的狼"));

Console.ReadLine();

The output of the running program is as follows:

大家好,我是沙漠尽头的狼网站站长:沙漠尽头的狼
Prefix
Postfix
Finalizer
大家好,我是沙漠尽头的狼网站站长:沙漠尽头的狼

上面代码就完成了一个自定义类的拦截处理,使用PatchAll能自动发现补丁类HookStudent,从而自动拦截Student类的GetDetails方法调用,发现第二次调用student.GetDetails("沙漠尽头的狼")时,Harmony的三个生命周期方法都被调用了。

我们可以在拦截类的约定方法(PrefixPostfix等)里做一些日志记录(Console.WriteLine\ILogger.LogInfo等),类似于B/S中的AOP拦截,操作日志在这里记录正合适。

    • This is it? What are you talking about, this is just the beginning. **

2.4. What about the agreed parameter tampering? What about fake API results?

修改Program.cs,多打印几行数据方便区分:

using HarmonyLib;
using HelloHook;
using System.Reflection;

var student = new Student();
Console.WriteLine(student.GetDetails("沙漠尽头的狼"));

var harmony = new Harmony("https://dotnet9.com");
harmony.PatchAll(Assembly.GetExecutingAssembly());

Console.WriteLine(student.GetDetails("沙漠之狐"));
Console.WriteLine(student.GetDetails("Dotnet"));

Console.ReadLine();

在注册Harmony之前,打印一次,注册后打印两次,注意看参数的不同。

修改HookStudent,我们只使用Prefix方法,其他Postfix等方法类似,可看Harmony wiki了解更多的使用方法,修改如下:

using HarmonyLib;

namespace HelloHook;

[HarmonyPatch(typeof(Student))]
[HarmonyPatch(nameof(Student.GetDetails))]
public class HookStudent
{
    public static bool Prefix(ref string name, ref string __result)
    {
        if ("沙漠之狐".Equals(name))
        {
            __result = $"这是我的曾用网名";
            return false;
        }

        if (!"沙漠尽头的狼".Equals(name))
        {
            name = "非站长名";
        }

        return true;
    }
}

Run first and see the output:

大家好,我是沙漠尽头的狼网站站长:沙漠尽头的狼
这是我的曾用网名
大家好,我是沙漠尽头的狼网站站长:非站长名
  • The first line "Hello everyone, I am the webmaster of the wolf at the end of the desert: the wolf at the end of the desert", which is the normally formatted output before registration and interception;
  • The second line "This is my former screen name", which is the falsification of the results;
  • The third line "Hello everyone, I am the webmaster of the wolf website at the end of the desert: non-webmaster name", which implements parameter tampering here.
    • The result is forged **

注意看Prefix方法传入的参数ref string __result:其中ref表示引用传递,允许对结果进行修改;string与原方法的返回值类型必须一致;__result为返回值的约定命名,前面是两个"_",即命名必须为__result

if ("沙漠之狐".Equals(name))
{
    __result = $"这是我的曾用网名";
    return false;
}

注意返回值是false,表示不调用原生方法,这里就将被拦截的方法返回值伪造成功了。

    • Parameter tampering **

看传入的参数ref string nameref表示参数是引用传递,允许对参数进行修改;string name必须与原方法参数定义一样。

if (!"沙漠尽头的狼".Equals(name))
{
    name = "非站长名";
}

Prefix方法默认返回的true,表示需要调用原生方法,这里会将篡改的参数传入原生方法,原生方法执行结果会将篡改的参数组合返回为“大家好,我是沙漠尽头的狼网站站长:非站长名”。

    • Note: **

原生参数name和返回值__result是可选的,如果不进行篡改,去掉ref也是可以的。

上面的示例源码点这

3. Hook WPF API

我们创建一个简单的WPF程序HookWpf,拦截MessageBox.Show方法:

public static MessageBoxResult Show(string messageBoxText, string caption)

首先在App中使用自动拦截注册:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {

        base.OnStartup(e);

        var harmony = new Harmony("https://dotnet9.com");
        harmony.PatchAll(Assembly.GetExecutingAssembly());
    }
}

定义拦截类HookMessageBox

using HarmonyLib;
using System.Windows;

namespace HookWpf;

[HarmonyPatch(typeof(MessageBox))]
[HarmonyPatch(nameof(MessageBox.Show))]
[HarmonyPatch(new [] { typeof(string), typeof(string) })]
public class HookMessageBox
{
    public static bool Prefix(ref string messageBoxText, string caption)
    {
        if (messageBoxText.Contains("垃圾"))
        {
            messageBoxText = "这是一个不错的网站哟";
        }

        return true;
    }
}

HookMessageBox上关联拦截的MessageBox.Show重载方法,在Prefix里对提示框内容进行合法性验证,不合法进行修正。

最后就是在窗体MainWindow.xaml里添加两个弹出提示框的按钮:

<Window x:Class="HookWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <StackPanel>
        <Button Content="弹出默认提示框" Width="120" Height="30" Click="ShowDialog_OnClick"></Button>
        <Button Content="这是一个垃圾网站" Width="120" Height="30" Click="ShowBadMessageDialog_OnClick"></Button>
    </StackPanel>
</Window>

Click the event on the background processing button to pop up a prompt box:

using System.Windows;

namespace HookWpf;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void ShowDialog_OnClick(object sender, RoutedEventArgs e)
    {
        MessageBox.Show("https://dotnet9.com 是一个热衷于技术分享的程序员网站", "Dotnet9");
    }

    private void ShowBadMessageDialog_OnClick(object sender, RoutedEventArgs e)
    {
        MessageBox.Show("这是一个垃圾网站", "https://dotnet9.com");
    }
}

The operation results are as follows:

The above effect completes the verification of the content of the prompt box. If the content contains the "junk" keyword, change it to something nice (this is a good website).

本示例源码在这

4. Hook. NET default API

创建控制台程序HookDotnetAPI,引入Lib.Harmonynuget包,Program.cs修改如下:

using HarmonyLib;

var dotnet9Domain = "https://dotnet9.com";
Console.WriteLine($"9的位置:{dotnet9Domain.IndexOf('9',0)}");

var harmony = new Harmony("com.dotnet9");
harmony.PatchAll();

Console.WriteLine($"9的位置:{dotnet9Domain.IndexOf('9', 0)}");

[HarmonyPatch(typeof(String))]
[HarmonyPatch(nameof(string.IndexOf))]
[HarmonyPatch(new Type[] { typeof(char), typeof(int) })]
public static class HookClass
{
    public static bool Prefix(ref int __result)
    {
        __result = 100;
        return false;
    }
}

使用方法和前面类似,string.IndexOf方法被拦截后,总是返回100,不论查找的字符位置在哪,当然这个测试代码没有任何意义,这里只是演示而已,运行结果如下:

9的位置:14
9的位置:100

5. Summary and sharing

5.1. summary

Harmony's principle is to use reflection to obtain methods in the corresponding class, and then add feature tags for logical control to achieve the effect of updating without destroying the original code.

Harmony uses a library to patch, replace and decorate. NET/. NET Core methods at runtime. But this technology can be used with any. NET version. Its multiple changes to the same method are cumulative rather than overridden.

Analyze the possible scenarios that need to be intercepted again to deepen your memory of this article:

  1. Some methods of. NET may not be directly modified by us directly at the code level;
  2. The third library does not provide source code, but we want to change some of its methods;
  3. The third library provides source code. Although it can be modified, if the third library is iteratively upgraded later and we have to update it again, it may be troublesome to upgrade the changes we make ourselves;
    • Intercept Note **: As you can see, this opens up a host of new possibilities. Remember that the greater the power, the greater the responsibility. Because you override behavior in a way that the original developers did not intend, there is no guarantee that your patch code will work when they release a new version of the code. That is, point 3 above does not rule out that the structure of the third library upgrade API has also changed, and we have to modify the interception logic accordingly.

从《Harmony wiki patching》中翻译出以下使用注意事项:

  1. The latest version 2.0 supports. NET Core;
  2. Harmony支持手动(Patch,参考Harmony wiki上的使用)和自动(PatchAll,本文演示使用的这种方式,Lib.Harmony使用的是C#的特性机制);
  3. It creates a DynamicMethod method for each original method and weaves code into it that calls custom methods at the beginning (Prefix) and end (Postfix). It also allows you to write a transpiler to process the original IL code, allowing for more detailed manipulation of the original method;
  4. Getter/Setter, virtual/non-virtual methods, static methods;
  5. The patch method must be a static method;
  6. Prefix needs to return type void or bool (void means not intercepted);
  7. Postfix needs to return the type void, or the type returned must be the same as the first parameter (pass-through mode);
  8. If the original method is not a static method, you can use a parameter named__instance (two underscores) to access the object instance;
  9. You can use a parameter named__result (two underscores) to access the return value of the method, and if it is Prefix, you get the default value of the return value;
  10. You can use the parameter named__state (two underscores) to store any type of value in the Prefix patch, and then use it in Postfix. It is your responsibility to initialize its value in Prefix;
  11. You can use the same parameter as the original method to access the corresponding parameter, if you want to write a non-reference type, remember to use the ref keyword;
  12. The parameters used by the patch must correspond strictly to the type (or object type) and name;
  13. Our patch only needs to define the parameters we need to use, not write all the parameters;
  14. To allow patch reuse, inject the original method with a parameter named__originalMethod (two underscores).

最后忘了补一条,.NET 7中使用Harmony还有点点问题,站长在测试WPF API和.NET基础库拦截Demo时一直不生效,折腾了2、3个晚上,以为是自己的使用问题,最后看到Harmony issue .NET 7 Runtime Skipping Patches #504,将程序降级为.NET 6即可。

5.2 share

读者朋友们,相信不少人使用过Harmony或者其他的 .NET Hook库,可在评论中留言分享,可提出自己的疑问,或自己的使用心得:

  1. I have used this library for API Hooks, which is XXX;
  2. I have implemented similar functions myself, and the link to share the article is XXX;
  3. Can I intercept this API? The scene is XXXX

[My Share]+[Your Share] ∈ [A small force in the. NET community]

6. reference

写作本文时以下文章都做了参考,建议大家都看看,特别是 Harmony wiki中写了Harmony的详细使用方法:

7. supplementary

  • Manual registration and interception on September 23, 2023

非public类及方法如何拦截拦截|篡改|伪造.NET类库中不限于public的类和方法

Keep Exploring

延伸阅读

更多文章
同分类 / 同标签 4/22/2026

Support for. NET by operating system versions (250707 update)

Use virtual machines and test machines to test the support of each version of the operating system for. NET. After installing the operating system, it is passed by measuring the corresponding running time of the installation and being able to run the Stardust Agent.

继续阅读
同分类 / 同标签 2/7/2026

Summary of experience in using AOT

From the very beginning of project creation, you should develop a good habit of conducting AOT release testing in a timely manner whenever new features are added or newer syntax is used.

继续阅读