Quickly learn this skill-.NET API interception technique

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

This article first raises the following questions, please find the answer in the article, and answer it in the comment area:

  1. What is API interception?

  2. A method is called in many places, how to record the time before and after the method call without modifying the source code of this 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 the method without modifying the source code? …

1. Foreword

The preface is translated from a foreign article, which is easier for people to understand – Hacking .NET – rewriting code you don’t control[1]:

Have you ever come across a class library method that doesn’t belong to you but wants to change its behavior? Usually the method is non-public and there is no nice way to override its behavior. You can see how it works (because you’re awesome and use decompilers like Resharper, dnSpy, etc., right?), you just can’t change it. You really need to change it for XXX reasons.

There are several options available to you:

  1. Get the source code by decompiling or downloading the source code if available first. This is often risky as it often comes with complex build processes, many dependencies, and now you are responsible for maintaining an entire branch of the library, even if you only want to make a small change.

  2. Use ILDasm to decompile the app, patch the IL code directly, and then use ILAs to assemble it back. In many ways, this is better because you can create a strategic surgical incision rather than a full-blown “scratch from scratch” approach. The downside is that you have to implement your method entirely in IL, which is a non-trivial venture.

Neither of the above methods will work if you’re dealing with signed libraries.

Now let’s look at another workaround – memory patching. This is the same technique that game cheat engines have used for decades, attaching to a running process, looking for memory locations and altering its behavior. Sound complicated? Actually, doing this in .NET is much easier than it sounds. We’ll be using a library called Harmony, available on NuGet as the “Lib.Harmony” package. This is a memory patching engine for .NET, mainly for games built with Unity, of course not only Unity.

In this article, Webmaster will show you how to change things you thought were impossible – starting from Hooking your own libraries and ending with Hooking WPF libraries and .NET base libraries.

2. Hook your own library

2.1. Preparation

  1. Create a console program HelloHook, add class Student:

namespace HelloHook;

public class Student
{
    public string GetDetails(string name)
    {
        return $"Hi everyone, I am the Dotnet9 webmaster: {name}";
    }
}

A GetDetails method is defined in the Student class to return the formatted personal introduction information. This method will be intercepted and used later in the experiment.

  1. Add Student call in Program.cs:

using HelloHook;

var student = new Student();
Console.WriteLine(student.GetDetails("The wolf at the end of the desert"));

The output of running the program is as follows:

Hi everyone, I am the webmaster of Dotnet9: The Wolf at the End of the Desert

The basic work is ready. This is a simple console program. The following content will be elaborated based on these two projects.

2.2. Intercept the GetDetails method

  1. Introduce the interception package – Lib.Harmony

We use the Lib.Harmony package, and the API interception depends on it. Add the following Nuget package to the HelloHook project:

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

Add interception class 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");
    }
}

Looking at the comments in the code, two HarmonyPatch features are added to the HookStudent class:

  • The first one is to associate the intercepted class Student type;

  • The second is to associate the intercepted class method GetDetails;

That is, when the GetDetails method of the Student class is called in the program, the methods defined in HookStudent will be executed separately, and the execution order of the three methods is Prefix ->Postfix->Finalizer, of course, there are more than these three agreed methods. In fact, the commonly used ones should be Prefix and Postfix. The meaning of the agreed methods is explained later. Just look at the Harmony wiki[2]

2.3. Registration interception

Modify Program.cs and add Harmony to intercept the entire assembly:

using HarmonyLib;
using HelloHook;
using System. Reflection;

var student = new Student();
Console.WriteLine(student.GetDetails("The wolf at the end of the desert"));

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

Console.WriteLine(student.GetDetails("The wolf at the end of the desert"));

Console. ReadLine();

The output of running the program is as follows:

Hi everyone, I'm the webmaster of Dotnet9: The Wolf at the End of the Desert
Prefix
Postfix
finalizer
Hello everyone, I am the webmaster of Dotnet9: The Wolf at the End of the Desert

The above code completes the interception processing of a custom class. Using PatchAll can automatically discover the patch class HookStudent, thereby automatically intercepting the Student class of GetDetails method call, found that when calling student.GetDetails("Wolf at the end of the desert") for the second time, the three life cycle methods of Harmony are all was called.

We can do some logging (Console.WriteLine\ILogger.LogInfo, etc.) in the agreed method of the interception class (Prefix and Postfix, etc.), similar to B/S AOP interception, the operation log is recorded here just right.

Is this the end? What to say, this is just the beginning.

2.4. What about tampering with the agreed parameters? And what about fake API results?

Modify Program.cs to print a few more lines of data for easy distinction:

using HarmonyLib;
using HelloHook;
using System. Reflection;

var student = new Student();
Console.WriteLine(student.GetDetails("The wolf at the end of the desert"));

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

Console.WriteLine(student.GetDetails("Desert Fox"));
Console.WriteLine(student.GetDetails("Dotnet"));

Console. ReadLine();

Before registering Harmony, print once, and print twice after registering, pay attention to the difference in parameters.

Modify HookStudent, we only use the Prefix method, other methods such as Postfix are similar, see Harmony wiki[3] To learn more about how to use it, modify it as follows:

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 ("Desert Fox".Equals(name))
        {
            __result = $"This is my previous screen name";
            return false;
        }

        if (!"Wolf at the end of the desert".Equals(name))
        {
            name = "non-webmaster name";
        }

        return true;
    }
}

First run to see the output:

Hi everyone, I'm the webmaster of Dotnet9: The Wolf at the End of the Desert
This is my former screen name
Hello everyone, I am the webmaster of Dotnet9: non-webmaster name
  • Line 1 “Hi everyone, I am the webmaster of Dotnet9: The Wolf at the End of the Desert”, this is the normal formatted output before the interception is registered;

  • The second line “This is my former screen name”, here is the forgery of the result;

  • Line 3 “Hi everyone, I am the Dotnet9 webmaster: not the name of the webmaster”, here is the tampering of the parameters.

Fake results

Pay attention to the parameter ref string __result passed in by the Prefix method: where ref means passing by reference, allowing the result to be modified; string and the return value type of the original method must be consistent; __result is the agreed name of the return value, preceded by two “_”, that is, the name must be __result.

if ("Desert Fox".Equals(name))
{
    __result = $"This is my previous screen name";
    return false;
}

Note that the return value is false, which means that the native method is not called, and the return value of the intercepted method is forged successfully.

Parameter tampering

Look at the incoming parameter ref string name: ref indicates that the parameter is passed by reference and allows modification of the parameter; string name must be the same as the original method parameter same definition.

if (!"Wolf at the end of the desert".Equals(name))
{
    name = "non-webmaster name";
}

The true returned by the Prefix method by default indicates that the native method needs to be called. Here, the tampered parameters will be passed into the native method, and the execution result of the native method will return the tampered parameter combination as “Hi everyone, I’m the Dotnet9 webmaster: non-webmaster name”.

Note:

The native parameter name and the return value __result are optional. If no tampering is required, it is also possible to remove the ref.

Click here [4] for the source code of the above example.

3. Intercept (Hook) WPF API

We create a simple WPF program HookWpf to intercept the MessageBox.Show method:

public static MessageBoxResult Show(string messageBoxText, string caption)

First use automatic interception registration in 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());
    }
}

Define the interception class 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("garbage"))
        {
            messageBoxText = "This is a good website";
        }

        return true;
    }
}

The MessageBox.Show overloaded method associated with the interception on the class HookMessageBox verifies the validity of the content of the prompt box in Prefix, and corrects it if it is not legal .

Finally, add two pop-up prompt box buttons in the form 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="Pop up the default prompt box" Width="120" Height="30" Click="ShowDialog_OnClick"></Button>
        <Button Content="This is a spam site" Width="120" Height="30" Click="ShowBadMessageDialog_OnClick"></Button>
    </StackPanel>
</Window>

The button click event is processed in the background, and a prompt box pops up:

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 is a website for programmers keen on technology sharing", "Dotnet9");
    }

    private void ShowBadMessageDialog_OnClick(object sender, RoutedEventArgs e)
    {
        MessageBox.Show("This is a spam website", "https://dotnet9.com");
    }
}

The result of the operation is as follows:

09f21c1e16dbf86e60d01f180d6b1096.gif

The above effect completes the verification of the content of the prompt box. If the content contains “garbage” keywords, replace it with nice words (this is a good website).

The source code of this example is here [5].

4. Interception (Hook) .NET default API

Create a console program HookDotnetAPI, import the Lib.Harmony nuget package, and modify Program.cs as follows:

using HarmonyLib;

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

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

Console.WriteLine($"Location of 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;
    }
}

The method of use is similar to the previous one. After the string.IndexOf method is intercepted, it always returns 100, no matter where the searched character is. Of course, this test code has no meaning. This is just a demonstration. The running results are as follows :

Position of

9: 14
Position of 9: 100

5. Summary and sharing

5.1. Summary

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

Harmony library for patching, replacing and decorating .NET/.NET Core methods at runtime. But the technique will work with any .NET version. Its multiple changes to the same method are cumulative rather than overridden.

Re-analyze the possible scenarios that need to be intercepted to deepen your memory of this article:

  1. For some methods of .NET, we may not be able to modify them 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 the source code, although its source code can be modified, but in case the third library is iteratively upgraded later, and we have to update it again, it may be troublesome to make the modification and upgrade it by ourselves;

Interception Note: As you can see, this opens up a ton of new possibilities. Remember, with great power comes great responsibility. Since you’re overriding behavior in a way the original developer didn’t intend, there’s no guarantee your patched code will work when they release a new version of the code. That is, the third point above does not rule out that the API structure of the third library upgrade has also changed, and we have to modify the interception logic accordingly.

The following precautions are translated from “Harmony wiki patching[6]“:

  1. The latest version 2.0 supports .NET Core;

  2. Harmony supports manual (Patch, refer to the use on Harmony wiki[7]) and automatic (PatchAll, this method is demonstrated in this article, Lib.Harmony uses the feature mechanism of C#);

  3. It creates a DynamicMethod method for each primitive method and weaves into it code that calls the custom method at the beginning (Prefix) and at the end (Postfix). It also allows you to write filters (Transpilers) to process the raw IL code, allowing more detailed operations on 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 void or bool type (void does not intercept);

  7. Postfix needs to return the void type, or the returned type must be consistent with the first parameter (through mode);

  8. If the original method is not a static method, you can use the parameter named __instance (two underscores) to access the object instance;

  9. You can use the parameter named __result (two underscores) to access the return value of the method. If it is Prefix, you will get the default value of the return value;

  10. You can use a parameter named __state (two underscores) to store any type of value in the Prefix patch, and then use it in Postfix, you are responsible for initializing its value in Prefix;

  11. You can use the parameter with the same name 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 strictly correspond to the type (or use the object type) and name;

  13. Our patch only needs to define the parameters we need to use, without writing all the parameters;

  14. To allow patch reuse, the original method can be injected with a parameter named __originalMethod (two underscores).

Finally, I forgot to add that there is still a little problem with using Harmony in .NET 7. The webmaster has been testing the WPF API and the .NET basic library to intercept the demo. Finally, see Harmony issue .NET 7 Runtime Skipping Patches #504[8], just downgrade the program to .NET 6.

5.2 Sharing

Dear readers, I believe that many people have used Harmony or other .NET Hook libraries. You can leave a message to share in the comments, and you can ask your own questions or your own experience:

  1. I have used this library for API Hook, it is XXX;

  2. I have implemented a similar function myself, and the link to share the article is XXX;

  3. Want to ask, can I intercept this API? The scene is XXXX

[My share] + [Your share] ∈ [a small force in the .NET circle]

6. Reference

When writing this article, the following articles have been used for reference. I suggest everyone read them, especially the detailed usage of Harmony written in the Harmony wiki[9]:

  • Harmony[10]

  • Harmony wiki[11]

  • Harmony API Documentation[12]

  • Hacking .NET – rewriting code you don’t control[13]

  • Rimworld Mod Making Tutorial 6 Use Harmony to Patch C# Code[14]

  • Simple start with dynamic IL weaving framework Harmony[15]

  • An open source framework for implementing dynamic IL injection (Hook or patch tool): Lib.Harmony[16]

  • .NET 7 Runtime Skipping Patches #504[17]

References

[1]

Hacking .NET – rewriting code you don’t control: https://stakhov.pro/hacking-net-rewriting-code-you-dont-control/

[2]

Harmony wiki: https://github.com/pardeike/Harmony/wiki

[3]

Harmony wiki: https://github.com/pardeike/Harmony/wiki

[4]

Click here for the source code: https://github.com/dotnet9/TerminalMACS.ManagerForWPF/tree/master/src/Demo/HookDemos/HelloHook

[5]

The source code is here: https://github.com/dotnet9/TerminalMACS.ManagerForWPF/tree/master/src/Demo/HookDemos/HookWpf

[6]

Harmony wiki patching: https://github.com/pardeike/Harmony/wiki/Patching

[7]

Harmony wiki: https://github.com/pardeike/Harmony/wiki

[8]

.NET 7 Runtime Skipping Patches #504: https://github.com/pardeike/Harmony/issues/504

[9]

Harmony wiki: https://github.com/pardeike/Harmony/wiki

[10]

Harmony: https://github.com/pardeike/Harmony

[11]

Harmony wiki: https://github.com/pardeike/Harmony/wiki

[12]

Harmony API documentation: https://harmony.pardeike.net/api/HarmonyLib.html

[13]

Hacking .NET – rewriting code you don’t control: https://stakhov.pro/hacking-net-rewriting-code-you-dont-control/

[14]

Rimworld Mod Making Tutorial 6 Use Harmony to Patch C# code: https://blog.csdn.net/qq_29799917/article/details/105017151

[15]

Simple start with dynamic IL weaving framework Harmony: https://www.cnblogs.com/qhca/p/12336332.html

[16]

An open source framework for implementing dynamic IL injection (Hook or patch tool): Lib.Harmony: https://config.net.cn/opensource/dataformat/a133596e-9d5a-43ef-9012-1e2a19c00e33-p1. html

[17]

.NET 7 Runtime Skipping Patches #504: https://github.com/pardeike/Harmony/issues/504