Harmony dotnet中的动态patch
目录
警告
本文最后更新于 2023-05-11,文中内容可能已过时。
先说结论,没解决
# 前言
想做一个类似于java agent的效果出来,hook某些函数来留后门。搜了一下有很多解决方案,但是都不友好。以下是其中一种Harmony框架的动态patch方案,这种方案更倾向于热更新。
# 项目地址
GitHub https://github.com/pardeike/Harmony
文档地址 https://harmony.pardeike.net/articles/intro.html
# 基本使用
根据文档来看
1 demo
Harmony有两种patch模式
- 注解自动patch
- 写代码手动patch
自动模式,根据自身程序集的注解来patch
var assembly = Assembly.GetExecutingAssembly();
harmony.PatchAll(assembly);
// or implying current assembly:
harmony.PatchAll();
注解HarmonyPatch用法如下
public class OriginalCode
{
public void Test(int counter, string name)
{
// ...
}
}
[HarmonyPatch(typeof(OriginalCode), nameof(OriginalCode.Test))]
class Patch
{
static void Prefix(int counter, ref string name)
{
FileLog.Log("counter = " + counter); // read
name = "test"; // write with ref keyword
}
}
手动模式 一个一个函数找对应关系patch
// add null checks to the following lines, they are omitted for clarity
// when possible, don't use string and instead use nameof(...)
var original = typeof(TheClass).GetMethod("TheMethod");
var prefix = typeof(MyPatchClass1).GetMethod("SomeMethod");
var postfix = typeof(MyPatchClass2).GetMethod("SomeMethod");
harmony.Patch(original, new HarmonyMethod(prefix), new HarmonyMethod(postfix));
// You can use named arguments to specify certain patch types only:
harmony.Patch(original, postfix: new HarmonyMethod(postfix));
harmony.Patch(original, prefix: new HarmonyMethod(prefix), transpiler: new HarmonyMethod(transpiler));
2 原理
Harmony通过挂钩的形式改变程序执行流,在方法前后可以插入自定义函数,并且可以在hook事件之间传递实例对象及参数。
在OriginalMethod执行时在方法体前插入jmp跳转到patch函数,以此实现hook。
3 API
有几大特殊事件函数
- Prefix 函数执行前
- Postfix 函数执行后
- Transpiler 转译器
- Finalizer 异常终结器
一个例子就看懂了,运行代码观察patch前后
using HarmonyLib;
using System;
using WebApplication1;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
namespace ClassLibrary1
{
public class Annotations
{
private readonly bool _isRunning;
public IEnumerable<int> GetNumbers()
{
Console.WriteLine(nameof(GetNumbers));
yield return 1;
yield return 2;
yield return 3;
}
}
[HarmonyPatch(typeof(Annotations))]
[HarmonyPatch(nameof(Annotations.GetNumbers))]
public class AnnotationsPatcher
{
static AccessTools.FieldRef<Annotations, bool> isRunningRef =
AccessTools.FieldRefAccess<Annotations, bool>("_isRunning");
public static void Patch()
{
var harmony = new Harmony(nameof(AnnotationsPatcher));
Console.WriteLine(new Annotations().GetNumbers().Join());
harmony.PatchAll();
Console.WriteLine(new Annotations().GetNumbers().Join());
}
static bool Prefix(Annotations __instance)
{
Console.WriteLine("Prefix");
return true;
}
/// <summary>Not working</summary>
static IEnumerable<int> Postfix(IEnumerable<int> values)
{
yield return 0;
foreach (var value in values)
if (value > 1)
yield return value * 10;
yield return 99;
Console.WriteLine(nameof(Postfix));
}
// looks for STDFLD someField and inserts CALL MyExtraMethod before it
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
Console.WriteLine(nameof(Transpiler));
//var found = false;
foreach (var instruction in instructions)
{
//if (instruction.opcode == OpCodes.Stfld && instruction.operand == f_someField)
//{
// yield return new CodeInstruction(OpCodes.Call, m_MyExtraMethod);
// found = true;
//}
yield return instruction;
}
//if (found == false)
// ReportError("Cannot find <Stdfld someField> in OriginalType.OriginalMethod");
}
}
class Program
{
static void Main(string[] args)
{
AnnotationsPatcher.Patch();
Console.ReadKey();
}
}
}
输出
GetNumbers
1, 2, 3
Transpiler
Prefix
GetNumbers
Postfix
0, 20, 30, 99
改变了GetNumbers的执行顺序和返回值
# 用途
本来是想用来做类似于java agent那种内存马,但是发现通过dll注入将patch.dll注入到target.exe进程中不起作用,猜测是appdomain不同的问题导致patch不生效,待解决吧。
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。

如果你觉得这篇文章对你有所帮助,欢迎赞赏或关注微信公众号~


