.NET HOOKING – HARMONIZING MANAGED TERRITORY

.NET HOOKING – HARMONIZING MANAGED TERRITORY

Key Points 要点

  • Check Point Research (CPR) provides an introduction to .NET managed hooking using the Harmony library.
    Check Point Research (CPR) 介绍了使用 Harmony 库的 .NET 托管挂钩。
  • We cover the most common examples of implementation using different types of Harmony patches.
    我们介绍了使用不同类型的 Harmony 补丁实现的最常见示例。
  • The practical example using Harmony hooking to defeat the notorious “ConfuserEx2” obfuscator results in the “ConfuserEx2_String_Decryptor” tool.
    使用 Harmony 钩子击败臭名昭著的“ConfuserEx2”混淆器的实际示例产生了“ConfuserEx2_String_Decryptor”工具。
  • CPR reveals a neat trick how to combine both debugging and hooking using the Harmony library (Harmony hooking from the dnSpyEx debugging context).
    CPR 揭示了一个巧妙的技巧,如何使用 Harmony 库(来自 dnSpyEx 调试上下文的 Harmony 钩子)将调试和挂钩结合起来。

Introduction 介绍

For a malware researcher, analyst, or reverse engineer, the ability to alter the functionality of certain parts of code is a crucial step, often necessary to reach a meaningful result during the analysis process. This kind of code instrumentation is usually reached by debugging, DBI (Dynamic Binary Instrumentation), or a simple hooking framework.
对于恶意软件研究人员、分析师或逆向工程人员来说,更改代码某些部分的功能是一个关键步骤,通常是在分析过程中获得有意义的结果所必需的。这种代码检测通常通过调试、DBI(动态二进制检测)或简单的钩子框架来实现。

Managing the code execution of the desired process has always worked well for non-managed, native code. We have many useful tools and frameworks that are proven to be very effective.
对于非托管的本机代码,管理所需进程的代码执行一直很有效。我们有许多有用的工具和框架,这些工具和框架被证明是非常有效的。

The situation is a little bit different when we start to talk about altering the functionality of managed code, more specifically, applications that run on top of .NET. The ability to instrument the code on the managed layer is limited by the small number of tools that can safely do so. One that stands head and shoulders above the rest is the Harmony, an open-source library for patching, replacing, and decorating .NET methods during runtime.
当我们开始讨论更改托管代码的功能时,情况略有不同,更具体地说,是在 .NET 上运行的应用程序。在托管层上检测代码的能力受到可以安全检测代码的工具数量有限的限制。Harmony 是一个比其他方法更胜一筹的库,它是一个开源库,用于在运行时修补、替换和修饰 .NET 方法。

In some cases, it is possible to use other specific libraries and frameworks to modify the code of .NET Assemblies with direct patching or a complete rebuild of those files on disk, but such a solution is not always feasible. It becomes even less so as the logic of the original code becomes more fragile and sophisticated.
在某些情况下,可以使用其他特定库和框架来修改 .NET 程序集的代码,方法是直接修补或在磁盘上完全重新生成这些文件,但这样的解决方案并不总是可行的。随着原始代码的逻辑变得更加脆弱和复杂,它变得更加不那么重要。

This is an especially sore point when dealing with malware samples that are protected and obfuscated in a way that touching them on the disk could easily destroy the original structure, causing undesirable behavior changes or a complete loss of functionality. Especially in these cases, modifying the code logic in memory during runtime is the convenient solution.
在处理恶意软件样本时,这是一个特别痛点,这些样本受到保护和混淆,在磁盘上触摸它们很容易破坏原始结构,导致不良行为更改或功能完全丧失。特别是在这些情况下,在运行时修改内存中的代码逻辑是方便的解决方案。

In this article, we introduce the concept of .NET managed hooking using the Harmony library, its internals, and common examples of implementation using different types of Harmony patches. We show how useful Harmony hooking can be in a practical exercise of defeating the notorious obfuscator “ConfuserEx2” that resulted in the release of the “ConfuserEx2_String_Decryptor” tool. The last section reveals a neat trick how to combine debugging and hooking using the Harmony library, or in other words, using Harmony hooking from the dnSpyEx debugging context.
在本文中,我们将介绍使用 Harmony 库的 .NET 托管挂钩的概念、其内部结构以及使用不同类型的 Harmony 修补程序实现的常见示例。我们展示了 Harmony 钩子在击败臭名昭著的混淆器“ConfuserEx2”的实际练习中是多么有用,该混淆器导致了“ConfuserEx2_String_Decryptor”工具的发布。最后一部分揭示了如何使用 Harmony 库将调试和挂钩结合起来,或者换句话说,从 dnSpyEx 调试上下文中使用 Harmony 挂钩。

Introduction to .NET Managed Hooking Using Harmony
使用 Harmony 的 .NET 托管挂钩简介

In one of our previous publications, we introduced a way to hook the native layer of .NET to alter the functionality of certain WIN API functions. See dotRunpeX (Invoke-DotRunpeXextract – preinjection of a native hooking library).
在之前的出版物中,我们介绍了一种挂钩 .NET 本机层以更改某些 WIN API 函数功能的方法。请参阅 dotRunpeX(Invoke-DotRunpeXextract – 本机挂钩库的预注入)。

Generally, the managed hooking of .NET works a little bit differently than the hooking of its native layer, as we are targeting the higher level of the code representation (Intermediate Language) that resides on the managed layer. This results in a similar instrumentation but with significantly better control over all executed instructions.
通常,.NET 的托管挂接工作方式与其本机层的挂接略有不同,因为我们的目标是驻留在托管层上的更高级别的代码表示形式(中间语言)。这导致了类似的检测,但对所有执行的指令的控制明显更好。

Most .NET-managed hooking tools work like simple patching libraries, allowing the original method to be replaced. This is the reason why we decided to go with the Harmony2 library, which goes one step further and gives us the ability to:
最。NET 管理的挂钩工具的工作方式类似于简单的修补库,允许替换原始方法。这就是我们决定使用 Harmony2 库的原因,它更进一步,使我们能够:

  1. Keep the original method intact.
    保持原始方法不变。
  2. Execute code before and/or after the original method.
    在原始方法之前和/或之后执行代码。
  3. Modify the original IL code of the method with IL code processors.
    使用 IL 代码处理器修改方法的原始 IL 代码。
  4. Multiple co-existing hooks/patches that don´t conflict with each other.
    多个共存的钩子/补丁,它们彼此不冲突。

The Harmony2 is a hooking library written in C# for patching, replacing, and decorating .NET methods during runtime. It uses a variation of hooking and focuses only on in-memory changes that don’t affect files on disk. It supports Mono and .NET environments on Windows, Unix, and macOS, targeting all programming languages that compile to CIL (also known as Intermediate LanguageIL codeMSIL).
Harmony2 是一个用 C# 编写的挂钩库,用于在运行时修补、替换和修饰 .NET 方法。它使用挂钩的变体,并且仅关注不影响磁盘上的文件的内存中更改。它支持 Windows、Unix 和 macOS 上的 Mono 和 .NET 环境,面向编译为 CIL(也称为中间语言、IL 代码、MSIL)的所有编程语言。

One of the main advantages of the Harmony library is that it operates only on in-memory code, so it does not touch the files on disk in any way. This comes in very handy, especially in cases where we are dealing with dotnet malware protected by some obfuscator in a way that the deobfuscation via .NET Assembly rebuilding is time-consuming and needs to be done very carefully so as not to destroy the original structure, which can lead to a complete loss of functionality.
Harmony 库的主要优点之一是它只在内存代码上运行,因此它不会以任何方式触及磁盘上的文件。这非常方便,尤其是在我们处理受某些混淆器保护的 dotnet 恶意软件的情况下,通过 .NET 程序集重建进行反混淆非常耗时,并且需要非常小心地进行,以免破坏原始结构,这可能导致功能完全丧失。

The other advantage is that we are not limited to only hook .NET methods defined in the scope of one specific .NET Assembly, but we can alter the functionality of all referenced assemblies, especially those that come with and build the .NET Runtime.
另一个优点是,我们不仅限于挂钩在一个特定 .NET 程序集的作用域中定义的 .NET 方法,而且我们可以更改所有引用的程序集的功能,尤其是那些附带和生成 .NET 运行时的程序集。

Bootstrapping and Injection
引导和注入

It is important to note that the Harmony library does not provide the functionality to run our own code within an application that is not designed to execute foreign code. We need a way to inject at least the few lines that start the Harmony patching. This is usually done with a loader/injector (e.g., the GUI-based tool ExtremeDumper can be used to inject the Harmony library into the .NET process) but can also be easily reached by using Reflection.
需要注意的是,Harmony 库不提供在非执行外部代码的应用程序中运行我们自己的代码的功能。我们需要一种方法来注入至少开始 Harmony 修补的几行。这通常使用加载器/注入器来完成(例如,基于 GUI 的工具 ExtremeDumper 可用于将 Harmony 库注入 .NET 进程),但也可以通过使用 Reflection 轻松实现。

Types of Patches 补丁类型

For .NET method hooking, the Harmony library provides several types of patches: PrefixPostfixTranspilerFinalizer, and Reverse Patch. Each of them serves a different purpose and gives different capabilities. The Prefix, Postfix, Transpiler, and Reverse Patch are the main subjects of this article, but we briefly explain all types below.
对于 .NET 方法挂钩,Harmony 库提供了多种类型的修补程序:前缀、后缀、转译器、终结器和反向修补程序。它们中的每一个都有不同的用途并具有不同的功能。前缀、后缀、转译器和反向补丁是本文的主要主题,但我们在下面简要解释所有类型。

Prefix 前缀

A Prefix is a method that is executed before the original method. It is commonly used to:
前缀是在原始方法之前执行的方法。它通常用于:

  • Access and edit the arguments of the original method.
    访问和编辑原始方法的参数。
  • Skip the original method.
    跳过原始方法。
  • Set the result of the original method – skipping the original is necessary.
    设置原始方法的结果 – 跳过原始方法是必要的。
  • Set a custom state that can be recalled in the Postfix.
    设置可在后缀中调用的自定义状态。
  • Run a piece of code at the beginning that is guaranteed to be executed.
    在开始时运行一段保证执行的代码。

Postfix 后缀

A Postfix is a method that is executed after the original method. It is commonly used to:
Postfix 是在原始方法之后执行的方法。它通常用于:

  • Read or change the result of the original method.
    读取或更改原始方法的结果。
  • Access the arguments of the original method.
    访问原始方法的参数。
  • Read a custom state from the Prefix.
    从 Prefix 中读取自定义状态。

Transpiler 转译器

This method defines the Transpiler that modifies the code of the original method. It is supposed to be used in advanced cases where we need to modify the IL Code of the original method body. Transpilers are called in an earlier stage where the instructions of the original method are fed into them for processing, changing them to a final output of the instruction set that builds the new “original” method. Transpilers are commonly used to:
此方法定义修改原始方法代码的转译器。它应该用于我们需要修改原始方法体的 IL 代码的高级情况。转译器是在早期阶段调用的,其中原始方法的指令被输入其中进行处理,将它们更改为构建新的“原始”方法的指令集的最终输出。转译器通常用于:

  • Modify the original method in detail (on the level of its IL Code).
  • Change the whole logic of the original method.
  • Change certain IL instructions.

Finalizer

A Finalizer is a method that handles exceptions and can change them. It is executed after all Postfixes. It wraps the original, all Prefixes, and Postfixes in a try/catch logic and is either called with null (no exception) or with an exception if one occurred. It is commonly used to:
终结器是一种处理异常并可以更改异常的方法。它是在所有后缀之后执行的。它将原始、所有前缀和后缀包装在 try/catch 逻辑中,并且要么调用 null (无异常),要么在发生异常时调用。它通常用于:

  • Run a piece of code at the end that is guaranteed to be executed.
    在最后运行一段保证执行的代码。
  • Handle exceptions and suppress them.
    处理异常并禁止显示异常。
  • Handle exceptions and alter them.
    处理异常并更改它们。

Reverse Patch 反向补丁

A Reverse Patch method is different from the previous types in that it patches our own methods instead of foreign original ones. It is a stub method in our code that “becomes” the original or part of the original (via Reverse Patch Transpiler) method so we can use it or call it independently. The defined stub method needs to look like the original in some way (the same method signature). A Reverse Patch is commonly used to:
反向补丁方法与以前的类型不同,因为它修补了我们自己的方法,而不是国外的原始方法。它是我们代码中的一个存根方法,它“成为”原始方法或原始方法的一部分(通过反向补丁转译器),因此我们可以使用它或独立调用它。定义的存根方法需要在某种程度上看起来像原始方法(相同的方法签名)。反向补丁通常用于:

  • Provide the unmodified original method (not affected by any applied patches) that can be called from our code, patches, or even from the patches that are applied to the method from which the Reverse Patch is created.
    提供未修改的原始方法(不受任何应用补丁的影响),该方法可以从我们的代码、补丁中调用,甚至可以从应用于创建反向补丁的方法的补丁中调用。
  • Provide a snapshot of the method that is constructed from the original method and all the applied Transpilers.
    提供从原始方法和所有应用的转译器构造的方法的快照。
  • Provide a part of the original method by using the Reverse Patch Transpiler.
    使用反向修补程序转译器提供原始方法的一部分。
  • Easily call private methods.
    轻松调用私有方法。

The simplified hooking logic summary of the Harmony library is shown below:
Harmony 库的简化钩子逻辑摘要如下图所示:

.NET HOOKING – HARMONIZING MANAGED TERRITORY
Figure 1: Harmony library – Hooking logic (Source: “https://harmony.pardeike.net/articles/intro.html”).
图 1:Harmony 库 – 钩子逻辑(来源:“https://harmony.pardeike.net/articles/intro.html”)。

Common Examples of Implementation

In this section, we go through simple examples of implementation, focusing on the four most commonly used types of patches: PrefixPostfixTranspiler, and Reverse Patch. These patches are used for typical scenarios like reading and changing the arguments of the method, changing the result (return value), modifying the IL code of the original method, and as a way to call the unmodified version of the original method.

There are two common ways of creating and organizing patches using the Harmony library. One of them is using annotation – Patch Class – that simplifies patching by assuming that we set up annotated classes and define patch methods inside them. This is usually used to better organize patches and perform more types of patches on a single original method (combination of Prefix, Postfix, etc.).

The second one – Manual Patching – gives us more control to put patches wherever we like as we refer to them ourselves, but we must rely greatly on the direct use of the reflection. For simplicity, in the common examples of implementation below, only Manual Patching is used.

Before we jump to hooking with the Harmony library, we need to create our sample application that will be a target for hooking and used in all the examples of common implementation. For that purpose, we can build a simple C#, Console App using .NET Framework (Release – x86) as shown below:

namespace Example
{
internal class Program
{
public static int Sum(int a, int b)
{
return a + b;
}
static void Main()
{
Console.WriteLine(“Hook Me If You Can!!!”);
int result = Sum(333, 333);
Console.WriteLine($“Result of the Sum: {result}”);
}
}
}

Checking the compiled binary and its execution in dnSpyEx:

Figure 2: The sample compiled application that will be used as a
target for hooking - dnSpyEx execution.
Figure 2: The sample compiled application that will be used as a target for hooking – dnSpyEx execution.
图 2:将用作挂钩目标的示例编译应用程序 – dnSpyEx 执行。

As we noted previously, the Harmony library does not provide the functionality to run our own code within an application that is not designed to execute foreign code. Therefore, we need to create a loader/injector that will execute the hooking logic inside of the sample targeted application. The simplest and most straightforward solution is for us to use Reflection.
正如我们之前提到的,Harmony 库不提供在非执行外部代码的应用程序中运行我们自己的代码的功能。因此,我们需要创建一个加载器/注入器,它将在示例目标应用程序内执行挂钩逻辑。最简单、最直接的解决方案是让我们使用 Reflection。

By using reflection, we can simply create a loader – another C#, Console App using .NET Framework (Release – x86) – that directly loads the target application (using reflection) and implements the hooking logic with the use of Harmony library. Such a loader is used in all examples below where it is mainly the hooking logic that changes.
通过使用反射,我们可以简单地创建一个加载器(另一个 C#,使用 .NET Framework 的控制台应用程序(版本 – x86)——它直接加载目标应用程序(使用反射)并使用 Harmony 库实现挂钩逻辑。下面的所有示例都使用了这样的加载器,其中主要是挂钩逻辑发生了变化。

Prefix – Changing the Method´s Arguments and Executing
Prefix – 更改方法的参数并执行

One of the most common practical cases is just a simple hook that modifies the original arguments and continues the execution of the original method. For this purpose, we need to use the Prefix type of patch. The original method that is going to be hooked by a Prefix type of patch is not a method implemented by our targeted application itself but is a referenced method that resides inside of System.Console.dll Assembly with a signature: void WriteLine(System.String).
最常见的实际情况之一就是一个简单的钩子,它修改了原始参数并继续执行原始方法。为此,我们需要使用 Prefix 类型的补丁。将被 Prefix 类型的修补程序挂钩的原始方法不是由我们的目标应用程序本身实现的方法,而是驻留在 System.Console.dll Assembly 内部的引用方法,其签名为: void WriteLine(System.String) 。

Step-by-Step Guide: 分步指南:

  1. Implement Harmony patch for Prefix “Manual Patching” PreFix_WriteLine – To change the argument value, it needs to be passed by reference, so adding the ref keyword is necessary. The return bool value of the Prefix patch method PreFix_WriteLine signals if the execution of the original method should be skipped (false – skip, true – do not skip).
    为前缀“手动修补” PreFix_WriteLine 实现 Harmony 补丁 – 要更改参数值,需要通过引用传递它,因此需要添加 ref 关键字。Prefix patch 方法的返回 bool 值指示是否应跳过原始方法 PreFix_WriteLine 的执行 ( – skip, false true – do not skip)。
  2. Load the targeted application using reflection.
    使用反射加载目标应用程序。
  3. Install the Prefix patch for method void Console.WriteLine(System.String).
    安装方法 void Console.WriteLine(System.String) 的前缀修补程序。
  4. Execute the EntryPoint of the targeted application (method Example.Program.Main()).
    执行 EntryPoint 目标应用程序(方法 Example.Program.Main() )。
using System; 使用系统;
using System.Reflection; 使用 System.Reflection;
using HarmonyLib; 使用 HarmonyLib;
namespace CaptainHook 命名空间 CaptainHook
{
internal class Hook
内部类 Hook
{
static void Main()
static void Main()
{
// Loading the targeted application using reflection
Assembly assembly = Assembly.LoadFile(@“C:\Example.exe”);
// Installing the Prefix patch for method “void Console.WriteLine(System.String)”
MethodInfo target = typeof(Console).GetMethod(“WriteLine”, new[] { typeof(string) });
if (target == null)
throw new Exception(“Could not resolve Console.WriteLine”);
Harmony harmony = new Harmony(“WriteLine”);
MethodInfo patch = typeof(Hook).GetMethod(“PreFix_WriteLine”);
harmony.Patch(target, new HarmonyMethod(patch));
// Executing the targeted application
assembly.EntryPoint.Invoke(null, null);
}
// Implementation of Harmony patch used for Prefix “Manual Patching”
public static bool PreFix_WriteLine(ref string value)
{
if (value.Contains(“Hook”)) { value = “Captain Hook Was Here!!!”; }
return true; // Do not skip executing original Console.WriteLine()
}
}
}

As we can see below, the method void Console.WriteLine(System.String) was successfully hooked, and all its invocations were intercepted by the implemented Prefix patch PreFix_WriteLine. It only changed when the original argument contained the string value “Hook” (e.g., “Hook Me If You Can!!!” → “Captain Hook Was Here!!!”).

Figure 3: Prefix hook - Changing the arguments of the
“Console.WriteLine()” method.
Figure 3: Prefix hook – Changing the arguments of the “Console.WriteLine()” method.

Prefix – Changing the Result and Skipping the Original

It can sometimes be useful to completely skip the original method and simply set the result (the return value) for all its invocations. To skip the execution of the original method, we need to choose the Prefix type of patch as it is the one that is going to be executed before the original and can easily skip the original´s execution. The original method Sum() that is going to be hooked by a Prefix type of patch is now a method implemented by our targeted application itself with a signature: System.Int32 Example.Program.Sum(System.Int32, System.Int32).

Figure 4: The dnSpyEx view of the compiled original method “Sum()” -
the target for hooking.
Figure 4: The dnSpyEx view of the compiled original method “Sum()” – the target for hooking.
图 4:编译的原始方法“Sum()”的 dnSpyEx 视图 – 挂钩目标。

Currently, this case is slightly different in the sense that we are hooking an original method that is part of the targeted application´s type – runtime type. The steps will change a little as well.
目前,这种情况略有不同,因为我们挂钩了一个原始方法,该方法属于目标应用程序的类型(运行时类型)。步骤也会略有变化。

Step-by-Step Guide: 分步指南:

  1. Implement Harmony patch for Prefix “Manual Patching” PreFix_Sum – To change the return value, the argument called __result (the type must match the return type of the original or be assignable from it) must be added to the original method´s parameters and passed by reference. Therefore, it is necessary to add the ref keyword to it. The return bool value (false) of the Prefix patch method PreFix_Sum signals that the execution of the original method should be skipped (false – skip, true – do not skip).
    为前缀“手动修补” PreFix_Sum 实现 Harmony 补丁 – 若要更改返回值,必须将调用 __result 的参数(类型必须与原始方法的返回类型匹配或可从中分配)添加到原始方法的参数中,并通过引用传递。因此,有必要向其添加 ref 关键字。Prefix patch 方法的返回 bool 值 ( false ) 表示应跳过原始方法 PreFix_Sum 的执行 ( – skip, false true – do not skip)。
  2. Load the targeted application using reflection.
    使用反射加载目标应用程序。
  3. Install the Prefix patch for method System.Int32 Example.Program.Sum(System.Int32, System.Int32) – As the targeted method is a part of the runtime type (loaded Assembly), we can´t use the typeof() operator or the nameof() expression, but we can use the Harmony AccessTools as a shortcut for reflection to quickly locate it.
    安装方法 System.Int32 Example.Program.Sum(System.Int32, System.Int32) 的前缀补丁 – 由于目标方法是运行时类型(加载的程序集)的一部分,因此我们不能使用 typeof() 运算符或 nameof() 表达式,但可以使用 Harmony AccessTools 作为反射的快捷方式来快速定位它。
  4. Execute the EntryPoint of the targeted application (method Example.Program.Main()).
    执行 EntryPoint 目标应用程序(方法 Example.Program.Main() )。
using System; 使用系统;
using System.Reflection; 使用 System.Reflection;
using HarmonyLib; 使用 HarmonyLib;
namespace CaptainHook 命名空间 CaptainHook
{
internal class Hook
内部类 Hook
{
static void Main()
static void Main()
{
// Loading the targeted application using reflection
使用反射加载目标应用程序
Assembly assembly = Assembly.LoadFile(@“C:\Example.exe”);
程序集程序集 = Assembly.LoadFile(@“C:\Example.exe”);
// Installing the Prefix patch for method “int Example.Program.Sum(int, int)”
安装方法“int Example.Program.Sum(int, int)”的前缀补丁
// The targeted method resides in a runtime type – loaded Assembly
目标方法驻留在运行时类型 – loaded Assembly 中
// We can use the Harmony AccessTools as a shortcut for reflection
我们可以使用 Harmony AccessTools 作为反思的快捷方式
MethodInfo target = AccessTools.Method(“Example.Program:Sum”);
MethodInfo 目标 = AccessTools.Method(“Example.Program:Sum”);
if (target == null)
if (目标 == null)
throw new Exception(“Could not resolve Example.Program.Sum”);
throw new Exception(“无法解析 Example.Program.Sum”);
Harmony harmony = new Harmony(“Sum”);
MethodInfo patch = typeof(Hook).GetMethod(“PreFix_Sum”);
harmony.Patch(target, new HarmonyMethod(patch));
// Executing the targeted application
assembly.EntryPoint.Invoke(null, null);
}
// Implementation of Harmony patch used for Prefix “Manual Patching”
public static bool PreFix_Sum(ref int __result, int a, int b)
{
__result = a + b + 666000; // Setting the result – return value
return false; // Skip executing the original Example.Program.Sum()
}
}
}

The method System.Int32 Example.Program.Sum(System.Int32, System.Int32) was successfully hooked, and its invocation was intercepted by the implemented Prefix patch PreFix_Sum. The execution of the original method was skipped, and the result (return value) was altered in a way that worked with the original arguments but modified the result as we wished (e.g., from “Sum(333, 333) → 666” to “Sum(333, 333) → 666 + 666000 → 666666”).

Figure 5: Prefix hook - Changing the result of the
“Example.Program.Sum()” and skipping its execution.
Figure 5: Prefix hook – Changing the result of the “Example.Program.Sum()” and skipping its execution.

Postfix – Changing the Result of the Original

Another case is when we want to let the original method execute and instrument the execution with logic that depends on its result (return value). To do so, we use the Postfix type of patch, as that one is executed right after the original. Based on the returned result, we can implement different logic that either sets a new result or leaves it as is. The original method Sum() that is going to be hooked by a Postfix type of patch is a method implemented by our targeted application itself with a signature: System.Int32 Example.Program.Sum(System.Int32, System.Int32).

Step-by-Step Guide:

  1. Implement Harmony patch for Postfix “Manual Patching” PostFix_Sum – To change the return value, the argument called __result (the type must match the return type of the original or be assignable from it) must be added to the original method´s parameters and passed by reference. Therefore, adding the ref keyword to it is necessary.
  2. Load the targeted application using reflection.
  3. Install the Postfix patch for method System.Int32 Example.Program.Sum(System.Int32, System.Int32) – As the targeted method is a part of the runtime type (loaded Assembly), we can´t use the typeof() operator or the nameof() expression, but we can use the Harmony AccessTools as a shortcut for reflection to quickly locate it.
  4. Execute the EntryPoint of the targeted application (method Example.Program.Main()).
using System;
using System.Reflection;
using HarmonyLib;
namespace CaptainHook
{
internal class Hook
{
static void Main()
{
// Loading the targeted application using reflection
Assembly assembly = Assembly.LoadFile(@“C:\Example.exe”);
// Installing the Postfix patch for method “int Example.Program.Sum(int, int)”
// The targeted method resides in a runtime type – loaded Assembly
// We can use the Harmony AccessTools as a shortcut for reflection
MethodInfo target = AccessTools.Method(“Example.Program:Sum”);
if (target == null)
throw new Exception(“Could not resolve Example.Program.Sum”);
Harmony harmony = new Harmony(“Sum”);
MethodInfo patch = typeof(Hook).GetMethod(“PostFix_Sum”);
harmony.Patch(target, postfix: new HarmonyMethod(patch));
// Executing the targeted application
assembly.EntryPoint.Invoke(null, null);
}
// Implementation of Harmony patch used for Postfix “Manual Patching”
public static void PostFix_Sum(ref int __result, int a, int b)
{ // Checking the result of the original Example.Program.Sum()
if (__result == 666)
{
__result = a + b + 666000; // Setting the result – return value
}
}
}
}

As we can see below, the method System.Int32 Example.Program.Sum(System.Int32, System.Int32) was successfully hooked, and its invocation was intercepted by the implemented Postfix patch PostFix_Sum. The original method was executed, and the result (return value) was altered only when it was equal to “666” (e.g., “666”  “666 + 666000”  “666666”).
如下图所示,该方法 System.Int32 Example.Program.Sum(System.Int32, System.Int32) 被成功钩住,其调用被实现的 Postfix patch PostFix_Sum 截获。执行原始方法,仅当结果(返回值)等于“666”时才更改(例如,“666”→“666 + 666000”→“666666”)。

Figure 6: Postfix hook - Changing the specific result of the
“Example.Program.Sum()”.
Figure 6: Postfix hook – Changing the specific result of the “Example.Program.Sum()”.
图 6:Postfix 钩子 – 更改 “Example.Program.Sum()” 的特定结果。

Transpiler – Changing the IL Code of the Original
Transpiler – 更改原始文件的 IL 代码

For the more advanced cases where we can´t reach certain instrumentation via patch types like Prefix or Postfix, and we want to work on the IL code level of the targeted method, the Transpiler type of patch comes into play.
对于更高级的情况,我们无法通过补丁类型(如前缀或后缀)访问某些检测,并且我们希望在目标方法的 IL 代码级别上工作,Transpiler 类型的补丁开始发挥作用。

We can think about the Transpiler as a post-compiler stage that can alter the “source code” of the original method, except that at runtime, it’s the IL code that changes. We can use the Transpiler to simply patch a few instructions or completely change the logic of the targeted method – all during runtime.
我们可以将转译器视为一个后编译器阶段,可以更改原始方法的“源代码”,只是在运行时,更改的是 IL 代码。我们可以使用 Transpiler 简单地修补一些指令或完全更改目标方法的逻辑——所有这些都在运行时进行。

The IL code of the original method Example.Program.Sum() that is going to be altered by a Transpiler type of patch is a method implemented by our targeted application itself with an original IL code as follows:
原始方法 Example.Program.Sum() 的 IL 代码将被 Transpiler 类型的补丁更改,该代码是由我们的目标应用程序本身使用原始 IL 代码实现的方法,如下所示:

/* 0x00000251 02 */ IL_0000: ldarg.0
/* 0x00000251 02 */ IL_0000: ldarg.0
/* 0x00000252 03 */ IL_0001: ldarg.1
/* 0x00000252 03 */ IL_0001: ldarg.1
/* 0x00000253 58 */ IL_0002: add
/* 0x00000253 58 */ IL_0002:添加
/* 0x00000254 2A */ IL_0003: ret
/* 0x00000254 2A */ IL_0003: ret

As we want to slightly change the logic of this method, the IL code altered by our Transpiler patch should change in memory into:
由于我们想稍微改变一下这个方法的逻辑,所以我们的转译器补丁修改的IL代码应该在内存中变成:

/* 0x00000000 02 */ IL_0000: ldarg.0
/* 0x00000000 02 */ IL_0000: ldarg.0
/* 0x00000001 03 */ IL_0001: ldarg.1
/* 0x00000001 03 */ IL_0001: ldarg.1
/* 0x00000002 59 */ IL_0002: sub
/* 0x00000002 59 */ IL_0002: sub
/* 0x00000003 17 */ IL_0003: ldc.i4.1
/* 0x00000003 17 */ IL_0003: ldc.i4.1
/* 0x00000004 58 */ IL_0004: add
/* 0x00000004 58 */ IL_0004:添加
/* 0x00000005 2A */ IL_0005: ret
/* 0x00000005 2A */ IL_0005: ret

Step-by-Step Guide: 分步指南:

  1. Implement Harmony patch for Transpiler “Manual Patching” Transpiler_Sum – To define our stub method as Transpiler, both the return and argument types must be of type IEnumerable<CodeInstruction>.
  2. Load the targeted application using reflection.
  3. Install the Transpiler patch for the method System.Int32 Example.Program.Sum(System.Int32, System.Int32) – As the targeted method is a part of the runtime type (loaded Assembly), we can´t use the typeof() operator or the nameof() expression, but we can use the Harmony AccessTools as a shortcut for reflection to quickly locate it.
  4. Execute the EntryPoint of the targeted application (method Example.Program.Main()).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
namespace CaptainHook
{
internal class Hook
{
static void Main()
{
// Loading the targeted application using reflection
Assembly assembly = Assembly.LoadFile(@“C:\Example.exe”);
// Installing the Transpiler patch for method “int Example.Program.Sum(int, int)”
// The targeted method resides in a runtime type – loaded Assembly
// We can use the Harmony AccessTools as a shortcut for reflection
MethodInfo target = AccessTools.Method(“Example.Program:Sum”);
if (target == null)
throw new Exception(“Could not resolve Example.Program.Sum”);
Harmony harmony = new Harmony(“Sum”);
MethodInfo patch = typeof(Hook).GetMethod(“Transpiler_Sum”);
harmony.Patch(target, transpiler: new HarmonyMethod(patch));
// Executing the targeted application
assembly.EntryPoint.Invoke(null, null);
}
// Implementation of Harmony patch used for Transpiler “Manual Patching”
public static IEnumerable<CodeInstruction> Transpiler_Sum(IEnumerable<CodeInstruction> codes)
{ // Changing the IL code of the original Example.Program.Sum()
// (a + b) will turn into (a – b + 1)
var instructions = new List<CodeInstruction>(codes);
for (var i = 0; i < instructions.Count; i++)
{
if (instructions[i].opcode == OpCodes.Add)
{
instructions.Insert(i, new CodeInstruction(OpCodes.Sub));
instructions.Insert(i + 1, new CodeInstruction(OpCodes.Ldc_I4_1));
break;
}
}
return instructions.AsEnumerable();
}
}
}

We can see that the IL code of the original System.Int32 Example.Program.Sum(System.Int32, System.Int32) was successfully altered by the implemented Transpiler patch Transpiler_Sum and changed its logic from (a + b) to (a – b + 1): “Sum(333, 333) → (333 – 333 + 1) → 1”.

Figure 7: Transpiler patch - Changing the IL code of the
“Example.Program.Sum()”.
Figure 7: Transpiler patch – Changing the IL code of the “Example.Program.Sum()”.

Reverse Patch and Unpatching – Calling the Original

As we already covered the most common types of patches (PrefixPostfixTranspiler) and we know that all of them modify the original targeted method in some way, a few important questions come to mind:

  1. How to remove patches applied to the targeted method so it can become the original again?
  2. How to call the intact original method that is not affected by any implanted patches?

Answering the first question is quite easy, as one of the most useful features of the Harmony library is the ability to keep the original method content that is not affected by any applied patches. This original method content can be used for the reconstruction to remove all applied patches or, in other words, “unpatching”.

Once a method is patched, the original method is destroyed, and all future versions will come from Harmony (using the original IL code). Therefore, the “unpatching” is not a real unpatching but instead a patching with zero patches. It can completely remove all patches, or patches that are related to certain Harmony instances, or only a specific single patch. The different implementations of unpatching are trivial and can be demonstrated in the code below:

// Loading the targeted application using reflection
Assembly assembly = Assembly.LoadFile(@“C:\Example.exe”);
// Installing the Prefix patch for method “void Console.WriteLine(System.String)”
MethodInfo target = typeof(Console).GetMethod(“WriteLine”, new[] { typeof(string) });
MethodInfo 目标 = typeof(Console)。GetMethod(“WriteLine”, new[] { typeof(string) });
if (target == null) if (目标 == null)
throw new Exception(“Could not resolve Console.WriteLine”);
throw new Exception(“无法解析 Console.WriteLine”);
Harmony harmony = new Harmony(“WriteLine”);
Harmony harmony = new Harmony(“WriteLine”);
MethodInfo patch = typeof(Hook).GetMethod(“PreFix_WriteLine”);
MethodInfo patch = typeof(Hook)。GetMethod(“PreFix_WriteLine”);
harmony.Patch(target, new HarmonyMethod(patch));
和谐。Patch(target, new HarmonyMethod(patch));
// Executing the targeted application – Console.WriteLine() is PATCHED
执行目标应用程序 – Console.WriteLine() 已修补
assembly.EntryPoint.Invoke(null, null);
集会。EntryPoint.Invoke(null, null);
—————————————————————————-
// Unpatching completely all applied patches
完全取消修补所有已应用的补丁
harmony.UnpatchAll(); 和谐。UnpatchAll();
// Unpatching all applied patches of one specific Hramony instance
取消修补一个特定 Hramony 实例的所有已应用补丁
harmony.UnpatchAll(“WriteLine”);
和谐。UnpatchAll(“写入线”);
// Unpatching specific method from all applied Prefix patches
从所有应用的前缀补丁中取消修补特定方法
harmony.Unpatch(target, HarmonyPatchType.Prefix);
和谐。Unpatch(目标, HarmonyPatchType.Prefix);
// Removing specific patch from specific method
从特定方法中删除特定补丁
harmony.Unpatch(target, patch);
和谐。unpatch(target, patch);
—————————————————————————-
// Executing the targeted application – Console.WriteLine() is UNPATCHED
执行目标应用程序 – Console.WriteLine() 未修补
assembly.EntryPoint.Invoke(null, null);
集会。EntryPoint.Invoke(null, null);
// Implementation of Harmony patch used for Prefix “Manual Patching”
用于前缀“手动修补”的 Harmony 补丁的实现
public static bool PreFix_WriteLine(ref string value)
public static bool PreFix_WriteLine(ref 字符串值)
{
value = “Captain Hook Was Here!!!”;
value = “胡克船长在这里!!”;
return true; // Do not skip executing original Console.WriteLine()
返回 true;不要跳过执行原始 Console.WriteLine()
}

Noteworthy is that we can even process the unpatching from inside the patch itself. In that case, the first call to the targeted hooked method is intercepted by the patch executing all applied logic, but all subsequent calls are processed by the original.
值得注意的是,我们甚至可以从补丁本身内部处理取消补丁。在这种情况下,对目标挂钩方法的第一次调用将被执行所有应用逻辑的修补程序截获,但所有后续调用都由原始调用处理。

// Implementation of Harmony patch used for Prefix “Manual Patching”
用于前缀“手动修补”的 Harmony 补丁的实现
public static bool PreFix_WriteLine(ref string value)
public static bool PreFix_WriteLine(ref 字符串值)
{
// Unpatching completely all applied patches even this one
完全取消修补所有已应用的补丁,甚至是这个补丁
(new Harmony(“ItDoesNotMatter”)).UnpatchAll();
(新 Harmony(“ItDoesNotMatter”))。UnpatchAll();
value = “Captain Hook Was Here!!!”;
value = “胡克船长在这里!!”;
return true; // Do not skip executing original Console.WriteLine()
返回 true;不要跳过执行原始 Console.WriteLine()
}

The “unpatching” is usually the best way to go if we want to either completely remove the applied patches for any reason or just remove a single patch instance. There is another much more convenient solution when it comes to using the original form of the method. If we need something like a copy of the original method, untouched by any kind of patches, that can be used and independently called from any location, even from its patch itself, and still not have to touch the applied patches in any way, a specific type of patch called “Reverse Patch” is the right solution.
如果我们出于任何原因想要完全删除已应用的补丁,或者只删除单个补丁实例,“取消修补”通常是最好的方法。在使用该方法的原始形式时,还有另一种更方便的解决方案。如果我们需要一个类似原始方法的副本,不受任何类型的补丁的影响,可以从任何位置使用和独立调用,甚至从其补丁本身,并且仍然不必以任何方式接触应用的补丁,那么一种称为“反向补丁”的特定类型的补丁是正确的解决方案。

The Reverse Patch is a way to create a copy of the original method that we can later use and call ourselves. We can call it from our code, or patches, or even from the patches that are applied to the method from which the Reverse Patch is created. This kind of patch is different than the previous types in that it patches our own methods instead of foreign original ones. It is a stub method in our own code that “becomes” the original method.

To be honest, we don’t have another way to go. What could possibly go wrong if we take the example from the section “Prefix – Changing the Method´s Arguments and Executing” that applied a Prefix patch to the method void WriteLine(System.String) that resides in System.Console.dll Assembly and try to print some “string” to the console from the patch itself?

Figure 8: Calling the patched method from the patch itself -
StackOverflowException.
Figure 8: Calling the patched method from the patch itself – StackOverflowException.

If we take the same example but implement the Reverse Patch for the original method void Console.WriteLine(System.String), we should be able to use it without any limitation causing similar exceptions.

Step-by-Step Guide:

  1. Implement Harmony patch for Prefix “Manual Patching” PreFix_WriteLine – To change the argument value, it needs to be passed by reference, so adding the ref keyword is necessary. The return bool value of the Prefix patch method PreFix_WriteLine signals if the execution of the original method should be skipped (false – skip, true – do not skip).
  2. Implement Harmony patch for Reverse Patch “Manual Patching” MyWriteLine – An empty stub method that needs to match the method signature of the original void Console.WriteLine(System.String).
  3. Load the targeted application using reflection.
  4. Install the Prefix patch for the method void Console.WriteLine(System.String).
  5. Install the Reverse Patch for the method void Console.WriteLine(System.String).
  6. Execute the EntryPoint of the targeted application (method Example.Program.Main()).
using System;
using System.Reflection;
using HarmonyLib;
namespace CaptainHook
{
internal class Hook
{
static void Main()
{
// Loading the targeted application using reflection
Assembly assembly = Assembly.LoadFile(@“C:\Example.exe”);
// Installing the Prefix patch for method “void Console.WriteLine(System.String)”
MethodInfo target = typeof(Console).GetMethod(“WriteLine”, new[] { typeof(string) });
if (target == null)
throw new Exception(“Could not resolve Console.WriteLine”);
Harmony harmony = new Harmony(“WriteLine”);
MethodInfo patch = typeof(Hook).GetMethod(“PreFix_WriteLine”);
harmony.Patch(target, new HarmonyMethod(patch));
// Installing the Reverse Patch for method “void Console.WriteLine(System.String)”
MethodInfo rPatch = typeof(Hook).GetMethod(“MyWriteLine”);
var rPatcher = harmony.CreateReversePatcher(target, new HarmonyMethod(rPatch));
rPatcher.Patch();
// Executing the targeted application
assembly.EntryPoint.Invoke(null, null);
// Calling the Reverse Patch for Console.WriteLine()
MyWriteLine(“Reverse Patch for Console.WriteLine() from Main!!!”);
}
// Implementation of Harmony patch used for Reverse Patch “Manual Patching”
public static void MyWriteLine(string value) { }
// Implementation of Harmony patch used for Prefix “Manual Patching”
public static bool PreFix_WriteLine(ref string value)
{
// Calling the Reverse Patch for Console.WriteLine() from PreFix_WriteLine
MyWriteLine(“Reverse Patch for Console.WriteLine() from PreFix_WriteLine!!!”);
if (value.Contains(“Hook”)) { value = “Captain Hook Was Here!!!”; }
return true; // Do not skip executing original Console.WriteLine()
}
}
}

The method void Console.WriteLine(System.String) was successfully hooked, and all its invocations were intercepted by the implemented Prefix patch PreFix_WriteLine. It only changed when the original argument contained the string value “Hook” (e.g., “Hook Me If You Can!!!” → “Captain Hook Was Here!!!”). Furthermore, we can independently use the Reverse Patch MyWriteLine for the original void Console.WriteLine(System.String) and call it even from the Prefix patch PreFix_WriteLine that was applied to it.
该方法 void Console.WriteLine(System.String) 已成功挂钩,其所有调用都被实现的 Prefix patch PreFix_WriteLine 截获。只有当原始参数包含字符串值“Hook”时,它才会更改(例如,“Hook Me If You Can!!”→“Captain Hook Was Here!!”)。此外,我们可以独立使用原始 void Console.WriteLine(System.String) 补丁的反向补丁 MyWriteLine ,甚至可以从应用于它的 Prefix 补丁 PreFix_WriteLine 中调用它。

Figure 9: Reverse Patch - Calling the original method
“Console.WriteLine()”.
Figure 9: Reverse Patch – Calling the original method “Console.WriteLine()”.
图 9:反向补丁 – 调用原始方法“Console.WriteLine()”。

It is worth noting that during our walk-through of all common implementation examples, we tried to make it simple but still worked quite hard with the “Manual Patching” to create and organize patches using the Harmony library (avoiding the usage of “Annotations”) as it highly relies on the reflection. Furthermore, we mostly directly used the reflection to get runtime types, methods, etc. The Harmony library also provides some utilities that can simplify a lot of things – e.g., a helper class AccessTools (wrapper that simplifies the reflection), Traverse (like a LINQ wrapper for classes), and more.
值得注意的是,在我们对所有常见实现示例的演练中,我们试图让它变得简单,但仍然非常努力地使用“手动补丁”来使用 Harmony 库创建和组织补丁(避免使用“注释”),因为它高度依赖于反射。此外,我们大多直接使用反射来获取运行时类型、方法等。Harmony 库还提供了一些可以简化很多事情的实用程序,例如,帮助程序类(简化反射的包装器)( Traverse 如类 AccessTools 的 LINQ 包装器)等等。

Practical Harmony Usage – ConfuserEx2 String Decryptor
实用的 Harmony 用法 – ConfuserEx2 字符串解密器

Encouraged by the knowledge from the previous sections, we can move forward to a simple, practical example that results in the creation of the ConfuserEx2 String Decryptor tool. 

Usually, when we are dealing with obfuscated dotnet malware, the first thing we target are encrypted constants, specifically string objects. Reconstruction of the original strings can often be a “shortcut” for revealing the crucial functionality, configuration, and other important aspects of the malware. 

A significant amount of malware samples come in the form of obfuscated or protected code. Currently, there are more than a hundred known dotnet obfuscators that are either commercially or freely available as open-source projects. One of the most used protectors from the second group is the notorious ConfuserEx2. This protector evolved from the first release known as Confuser to ConfuserEx, which later resulted in a fork introduced as ConfuserEx2. 

While the vanilla version of ConfuserEx2 is heavily used by commodity malware, it is not so unusual to see it being used by advanced threat actors in a form that is slightly customized but enough to disable the functionality of freely available, known deobfuscators. 

Even in the case of the vanilla version of ConfuserEx2, there are no publicly available, dedicated deobfuscators that can reliably allow the reconstruction of constants (mainly string objects and char[] arrays) protected by this obfuscator. In this section, we show how to create one such tool that uses the Harmony library to bypass some of the ConfuserEx2 checks, allowing us to proceed with the deobfuscation logic. 

To find out how the ConfuserEx2 constants protection works, where the main problem is, and how we can defeat it, we built a simple C#, Console App using .NET Framework that became the target for the obfuscation.

Figure 10: Example program as a target for the ConfuserEx2
obfuscation (dnSpyEx view).
Figure 10: Example program as a target for the ConfuserEx2 obfuscation (dnSpyEx view).

After applying ConfuserEx2 constants protection, our original example program turns into the obfuscated code that holds all string objects and char[] arrays in an encrypted form.

Figure 11: ConfuserEx2 obfuscated example program - constants
protected.
Figure 11: ConfuserEx2 obfuscated example program – constants protected.

One of the most straightforward ways to deobfuscate and reconstruct the original constants is based on a dynamic approach using reflection to simply invoke all the constants’ decryption functions with proper arguments. But once we check such decryption functions in a ConfuserEx2-protected binary, we can immediately spot something that protects the function invocation in exactly the way we want to do so.

.NET HOOKING – HARMONIZING MANAGED TERRITORY
Figure 12: One of the constants’ decryption functions.

Notice the Assembly.GetExecutingAssembly().Equals(Assembly.GetCallingAssembly()) check compares the .NET Assembly containing the currently executing code with the .NET Assembly that invoked the currently executing method and expects them to be equal. Of course, the dynamic approach using reflection to get rid of the obfuscation will fail to pass this check.

This is the exact moment where the Harmony library comes into play. We can implement a Prefix type of patch for the original method GetCallingAssembly() that hooks this function in a way that always returns the obfuscated .NET Assembly, no matter which assembly was originally responsible for the function invocation. This patch allows us to bypass the check and continue with our dynamic approach.

// Targeted obfuscated .NET Assembly loaded via Reflection
static Assembly LoadedAssembly;
// Installing the Prefix patch for method “Assembly GetCallingAssembly()”
private static void InstallHook()
{
var target = typeof(Assembly).GetMethod(“GetCallingAssembly”);
if (target == null)
throw new Exception(“Could not resolve Assembly.GetCallingAssembly”);
var harmony = new Harmony(“GetCallingAssembly”);
var stub = typeof(Program).GetMethod(“PreFix_GetCallingAssembly”);
harmony.Patch(target, new HarmonyMethod(stub));
}
// Implementation of Harmony patch used for Prefix “Manual Patching”
public static bool PreFix_GetCallingAssembly(ref Assembly __result)
{
__result = LoadedAssembly; // Setting the result – return value
return false; // Skip executing the original GetCallingAssembly()
}

Enriching the Prefix patch implementation above with a .NET Assembly parsing logic (using AsmResolver library) to properly find all invocations of constants’ decryption functions with corresponding arguments, and further invoking them via reflection, allows us to obtain the decrypted form of constants that helps to rebuild the deobfuscated sample. The complete source code is available here and results in a tool called “ConfuserEx2_String_Decryptor”.

Using this tool to deobfuscate our example ConfuserEx2 protected program is shown below (40s GIF):

.NET HOOKING – HARMONIZING MANAGED TERRITORY
Figure 13: Deobfuscation with the “ConfuserEx2_String_Decryptor” tool (40s GIF). 

Harmony Hooking from the DnSpyEx Debugging Context 

Regarding the instrumentation of the .NET code, the Harmony library has already proved to be very useful for some specific tasks (shown in the “Common Examples of Implementation” section), especially when it comes to automating. However, we still do not have the best and easiest control over the exact moment when the Harmony patches are installed and uninstalled. 

Let’s take an example code like the one shown below and imagine altering the functionality of the 5th → 8th invocations of the void Console.WriteLine(System.String) method. 

static void Main() 
{
Console.WriteLine(“Hook Me If You Can!!!”); 
Console.WriteLine(“Hook Me If You Can!!!”); 
Console.WriteLine(“Hook Me If You Can!!!”); 
Console.WriteLine(“Hook Me If You Can!!!”); 
Console.WriteLine(“Hook Me If You Can!!!”); // Alter This 
Console.WriteLine(“Hook Me If You Can!!!”); // Alter This 
Console.WriteLine(“Hook Me If You Can!!!”); // Alter This 
Console.WriteLine(“Hook Me If You Can!!!”); // Alter This 
Console.WriteLine(“Hook Me If You Can!!!”); 
Console.WriteLine(“Hook Me If You Can!!!”); 
Console.WriteLine(“Hook Me If You Can!!!”); 
Console.WriteLine(“Hook Me If You Can!!!”); 
}

In this simple case, we can very likely come up with some clumsy solution using just the Harmony library, but this will not work in much more complex situations. 

Usually, when we want to have absolute control over the instrumentation of .NET code, using a managed debugger like dnSpyEx is the most convenient way. The main disadvantage is that in the case of a non-scriptable debugger, altering the functionality of certain parts of the code becomes a manual, time-consuming piece of work, far removed from automation.

If there was a way to effectively combine the debugging of dnSpyEx with the hooking features of the Harmony library, we could harness the strengths of both tools to their fullest extent. And the exciting part is, it’s already a reality.

First, we prepare the Harmony hooking library that is going to be used from the dnSpyEx debugging context. This example library hooks the void Console.WriteLine(System.String) function using the Prefix type of patch and contains two other methods, InstallHook() and UninstallHook(), that invoke the patching and unpatching.

using System;
using System.Reflection;
using HarmonyLib;
namespace Captain_HooK
{
public class HooK
{
public static void UninstallHook()
{
(new Harmony(“ItDoesNotMatter”)).UnpatchAll(“WriteLine”);
}
public static void InstallHook()
{
MethodInfo target = typeof(Console).GetMethod(“WriteLine”, new[] { typeof(string) });
if (target == null)
throw new Exception(“Could not resolve Console.WriteLine”);
Harmony harmony = new Harmony(“WriteLine”);
MethodInfo stub = typeof(HooK).GetMethod(“PreFix_WriteLine”);
harmony.Patch(target, new HarmonyMethod(stub));
}
public static bool PreFix_WriteLine(ref string value)
{
value = “Captain Hook Was Here!!!”;
return true;
}
}
}

Next, we need to be able to load our prepared Harmony library from the dnSpyEx debugger and invoke the InstallHook() and UninstallHook() functions in the context of the debugged process. For that purpose, we can use the “Watch Window” in the dnSpyEx debugger that has the ability to evaluate C# expressions in the context of the debugged application. An example of such expression evaluation is shown below.

Figure 14: DnSpyEx debugger - Example of expression evaluation via
“Watch Window”.
Figure 14: DnSpyEx debugger – Example of expression evaluation via “Watch Window”. 

The last thing we need to prepare is the magic incantations that will use the expression evaluation to invoke the functions InstallHook() and UninstallHook() at the precise moment we want to do so. 

// Invoke InstallHook() 
((System.Reflection.Assembly.LoadFile(@“C:\Captain_HooK.dll”).GetType(“Captain_HooK.HooK”)).GetMethod(“InstallHook”)).Invoke(null, null); 
// Invoke UninstallHook() 
((System.Reflection.Assembly.LoadFile(@“C:\Captain_HooK.dll”).GetType(“Captain_HooK.HooK”)).GetMethod(“UninstallHook”)).Invoke(null, null); 

Putting it all together, using the dnSpyEx “Watch Window” expression evaluation to perform the Harmony hooking from the debugging context is shown below (45s GIF): 

.NET HOOKING – HARMONIZING MANAGED TERRITORY
Figure 15: Harmony hooking from the dnSpyEx debugging context (45s GIF). 

Conclusion 结论

In this article, we introduced .NET managed hooking using the Harmony library and its internals. We presented examples of implementations that covered the most common usage of the Harmony library and its different types of patches. 

These examples demonstrate how powerful .NET hooking can be and more importantly, how easy and straightforward it is to implement .NET instrumentation once we use the Harmony library. 

We showed how useful the Harmony hooking is in a practical example involving the notorious obfuscator “ConfuserEx2” that results in the release of the publicly available tool “ConfuserEx2_String_Decryptor”. 

Finally, we revealed a neat trick how to use the Harmony hooking from the dnSpyEx debugging context. This combination of debugging and hooking gave us the ability to automate the instrumentation of .NET code, still preserving full control over its state of execution. 

One of the main advantages of the .NET hooking is that it operates only on in-memory code, so it does not touch the files on disk in any way. This comes in very handy, especially in cases where we are dealing with dotnet malware protected by an obfuscator in a way that the deobfuscation via .NET Assembly rebuilding is time-consuming and needs to be done very carefully so as not to destroy the original structure, which can later lead to a complete loss of functionality. 

The other advantage is that we are not only limited to hook .NET methods defined in the scope of one specific .NET Assembly, but we can alter the functionality of all referenced assemblies, especially those that come with and build the .NET Runtime. 

Even though we mostly focused on the basics of .NET hooking and just briefly touched on its practical uses, our readers gained a lot of information on how to construct a shortcut for malware analysis in areas like: .NET Instrumentation, Tracing, Deobfuscation, etc. 

References 引用

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

Harmony Library – Documentation: https://harmony.pardeike.net/
Harmony 库 – 文档:https://harmony.pardeike.net/

ExtremeDumper: https://github.com/wwh1004/ExtremeDumper
ExtremeDumper:https://github.com/wwh1004/ExtremeDumper

DnSpyEx: https://github.com/dnSpyEx/dnSpy
DnSpyEx:https://github.com/dnSpyEx/dnSpy

ConfuserEx2 Protector: https://github.com/mkaring/ConfuserEx
ConfuserEx2 保护器:https://github.com/mkaring/ConfuserEx

AsmResolver: https://github.com/Washi1337/AsmResolver
AsmResolver:https://github.com/Washi1337/AsmResolver

ConfuserEx2 String Decryptor: https://github.com/Dump-GUY/ConfuserEx2_String_Decryptor
ConfuserEx2 字符串解密器:https://github.com/Dump-GUY/ConfuserEx2_String_Decryptor

原文始发于cp<r>( Jiri Vinopal):.NET HOOKING – HARMONIZING MANAGED TERRITORY

版权声明:admin 发表于 2024年1月9日 上午11:42。
转载请注明:.NET HOOKING – HARMONIZING MANAGED TERRITORY | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...