Giter Club home page Giter Club logo

dotnetdetour's Introduction

Build Status 996.icu LICENSE HitCount

📖DotNetDetour

DotNetDetour是一个用于.net方法hook的类库

特点

  • 支持32bit和64bit的.net程序
  • 支持静态方法,实例方法、属性方法、泛型类型的方法、泛型方法的hook
  • 支持.net基础类库方法的hook
  • 无任何性能影响,无需知道和改动被hook的方法源码

实现原理

https://bbs.csdn.net/topics/391958344

基础示例

  1. git clone本项目最新源码使用;或者NuGet安装(可能未及时更新):Install-Package DotNetDetour, 或者:Install-Package kissstudio.DotNetDetour

  2. 参考以下例子实现IMethodHook接口,使用特性标记要Hook的方法

namespace Test.Solid {
    //假设有一个已存在的类(并且无法修改源码,如.Net框架的方法)
    public class SolidClass{
        public string Run(string msg){
            return msg+"(run)";
        }
    }
}

namespace Test{
    //我们自行实现一个类来修改Run方法的行为,此类用IMethodHook接口修饰
    public class MyClass:IMethodHook{
        //我们实现一个新Run方法,并标记为HookMethod,覆盖SolidClass中的Run方法
        [HookMethod("Test.Solid.SolidClass")]
        public string Run(string msg){
            return "Hook " + Run_Original(msg);
        }
        
        //实现一个占位方法,此方法代表被Hook覆盖的原始方法
        [OriginalMethod]
        public string Run_Original(string msg){
            return null; //这里写什么无所谓,能编译过即可
        }
    }
}
  1. 在程序中执行安装操作(只需运行一次即可),最佳运行时机:必须在被Hook方法被调用前执行,最好程序启动时运行一次即可。
MethodHook.Install();
  1. 当执行到被Hook的方法时,该调用将被转到我们的Hook方法执行:
var msg=new SolidClass().Run("Hello World!");

//Hook Hello World!(run)

📖Hook场景

普通方法Hook

静态和非静态的普通方法Hook操作都是一模一样的,两步到位:新建一个类实现IMethodHook接口,编写普通Hook方法,用HookMethod特性标记此方法,有无static修饰、返回值类型(仅针对引用性质的类型,非int等值类型)不同都不影响,但参数签名要和被Hook的原始方法一致,值类型和引用类型尽量不要混用。

第一步:新建一个类实现IMethodHook接口

我们编写的Hook方法所在的类需要实现IMethodHook接口,此接口是一个空接口,用于快速的查找Hook方法。

或者使用IMethodHookWithSet接口(算Plus版吧),此接口带一个HookMethod(MethodBase method)方法,这个类每成功进行一个Hook的初始化,就会传入被Hook的原始方法(可判断方法名称来确定是初始化的哪个方法),这个方法可用于获取方法所在的类(如:私有类型),可用于简化后续的反射操作;注意:此方法应当当做静态方法来进行编码。

第二步:编写Hook方法,用HookMethod特性标记

HookMethod(type,targetMethodName,originalMethodName) ,type参数支持:Type类型对象、类型完全限定名。如果能直接获取到类型对象,就使用Type类型对象;否则必须使用此类型的完全限定名(如:私有类型),如:System.Int32System.Collections.Generic.List`1[[System.String]]

[HookMethod("Namespace.xxx.MyClass", "TargetMethodName", "OriginalMethodName")]
public string MyMethod(string param){...}

[HookMethod(typeof(MyClass))]
public string MyMethod(string param){...}

如果我们的方法名称和被Hook的目标方法名称一致,无需提供targetMethodName参数。

如果我们提供目标原始方法的占位方法OriginalMethod,并且名称为目标原始方法名称 + _Original,或者当前类内只有一个Hook方法,无需提供originalMethodName参数。

注意:方法参数

参数签名要和被Hook的原始方法一致,如果不一致将导致无法找到原始方法(原因:存在重载方法无法确认是哪个的问题)。

如果存在我们无法使用的参数类型的时候(如:私有类型),我们可以用object等其他引用类型代替此类型(注意不要用值类型,否则可能出现内存访问错误),并把此参数用RememberType进行标记:

//目标方法:
public string SolidMethod(MyClass data, int code){...}

//我们的Hook方法:
public string MyMethod([RememberType("Namespace.xxx.MyClass")]object data, int code){...}

可选:提供OriginalMethod特性标记的原始方法

如果我们还想调用被Hook的原始方法,我们可以提供一个占位方法,此方法用OriginalMethod进行标记即可。此方法只起到代表原始方法的作用,不需要可以不提供,要求:参数签名必须和我们写的Hook方法一致(原因:存在重载方法无法确认是哪个的问题)。

此方法默认名称格式为目标原始方法名称 + _Original,不使用这个名称也可以,但如果使用其他名称并且当前类中有多个Hook方法,必须在Hook方法HookMethod特性中进行设置originalMethodName进行关联。

[OriginalMethod]
public string SolidMethod_Original(object data, int code){

可选:给我们的Hook方法传递参数

我们编写Hook方法是在被Hook的原始方法被调用时才会执行的,我们可能无法修改调用过程的参数(如果是能修改方法的话就跳过此节),虽然我们编写的Hook方法可以是非静态方法,但我们应当把它当静态方法来看待,虽然可以用属性字段(非静态的也当做静态)之类的给我们的Hook方法传递数据,但如果遇到并发,是不可靠的。

我们可以通过当前线程相关的上下文来传递数据,比如:HttpContextCallContextAsyncLocalThreadLoacl。推荐使用CallContext.LogicalSetData来传递数据,如果可以用HttpContext就更好了(底层也是用CallContext.HostContext来实现的)。ThreadLoacl只能当前线程用,遇到异步、多线程就不行了。AsyncLocal当然是最好的,但稍微低些版本的.Net Framework还没有这个。

[HookMethod("Namespace.xxx.MyClass", "TargetMethodName", "OriginalMethodName")]
public string MyMethod(string param){
    if (CallContext.LogicalGetData("key") == (object)"value") {
        //执行特定Hook代码
        return;
    }
    //执行其他Hook代码
    ...
}

//调用
CallContext.LogicalSetData("key", "value");
new MyClass().MyMethod("");
CallContext.LogicalSetData("key", null);

注:虽然大部分多线程、异步环境下调用上下文是会被正确复制传递的,但如果哪里使用了ConfigeAwait(false)或者其他影响上下文的操作(定时回调、部分异步IO回调好像也没有传递),当我们的Hook方法执行时,可能上下文数据并没有传递进来。

异步方法Hook

异步方法的Hook方法需要用async来修饰、返回Task类型,其他和普通方法Hook没有区别。

小提醒:不要在存在SynchronizationContext(如:HttpContext、UI线程)的线程环境中直接在同步方法中调用异步方法,真发生异步行为时100%死锁,可以强制关闭SynchronizationContext来规避此种问题,但会引发一系列问题。如果使用过程中发生死锁,跟我们进行的Hook操作没有关系

[HookMethod(typeof(MyClass))]
public async Task<int> MyMethodAsync() {...}

//异步环境调用
val=await new MyClass().MyMethodAsync();

//同步环境调用
var bak = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
try {
    val=new MyClass().MyMethodAsync().Result;
} finally {
    SynchronizationContext.SetSynchronizationContext(bak);
}

属性Hook

属性其实是get_xxx()名称的普通方法,比如MyProperty属性Hook get_MyProperty()这个普通方法即可。

[HookMethod("Namespace.xxx.MyClass")]
public string get_MyProperty(){...}

[OriginalMethod]
public string get_MyProperty_Original(){...}

或者在get块上方进行标记,规则和普通方法一致:

public string MyProperty{
    [HookMethod("Namespace.xxx.MyClass")]
    get{ ... }
}

public string MyProperty_Original{
    [OriginalMethod]
    get{ ... }
}

注:Hook属性时有可能能成功设置此Hook,但不一定会执行我们的代码,可能是编译过程中优化了整个调用过程,跳过了部分属性方法,直接返回了最深层次的调用值,如下面这种类似的属性获取方式:

int A{get{return B;}}
int B{get{return C;}}
int C{get{return 123;}}

我们Hook A属性,能成功设置Hook方法,但我们调用A属性时,并不会执行我们的Hook方法。换B也不行,只有Hook C才行。也许是编译的时候把A、B的调用直接优化成了对C的调用,我们只需要对最深层次的属性调用进行Hook就能避免此问题。(这个只是演示可能会出现的问题,我们自己特意写代码去测试并不能复现)。

字段Hook

不支持,应该直接用反射来操作。

构造方法Hook

我们编写个返回值为void、方法名称为类名称的普通方法即可实现。如果方法名称无法使用类名称时,需在HookMethod中设置targetMethodName.ctor。其他规则和普通方法一致。

[HookMethod("Namespace.xxx.MyClass")]
public void MyClass(string param) {
    ...
    MyClass_Original(param);//可选调用自身实例化方法
    ...
}

[OriginalMethod]
public void MyClass_Original(string param) {}

泛型类的方法Hook

形如class MyClass<T>{ T MyMethod(T param, object param2){...} }这种泛型,对里面的方法进行Hook。泛型类中方法的Hook和普通方法Hook没有多大区别,只是在提供HookMethod特性的type参数时需要对类型具体化,比如调用的地方使用的是int类型,那么我们就Hook int类型的此类:typeof(MyClass<int>)Namespace.xxx.MyClass&#96;1[[System.Int32]],其他和普通方法规则相同。

由于存在引用类型值类型两种类型,并且表现不一致,我们在具体化时要分开对待。

值类型泛型参数

每种使用到的值类型泛型参数的具体类型都需要单独实现Hook,intbool等为值类型都要单独实现,如int类型写法:

[HookMethod("Namespace.xxx.MyClass`1[[System.Int32]]")]
public int MyMethod(int param, object param2) {

引用类型泛型参数

每种使用到引用类型参数的具体类型都共用一个Hook,注意是:同一个泛型类中的同一个方法只能用一个相同方法进行Hookstring普通object等都是引用类型都共用一个Hook,如string类型写法:

[HookMethod("Namespace.xxx.MyClass`1[[System.Object]]")]
public object MyMethod(object param, object param2) {
    if(param is string){
        ... //string 类型实现代码
    } else if(param is xxxx){
        ... //其他引用类型实现代码
    }

泛型方法Hook

形如T MyMethod<T>(T param, object param2)这种泛型方法,我们对这种方法进行Hook时需要把类型具体化,并用RememberType(isGeneric: true)特性标记涉及到的泛型参数,比如调用的地方是int类型,那么我们就Hook int类型的此方法int MyMethod([RememberType(isGeneric: true)]int param, object param2),其实最终还是一个普通方法,按普通方法规则来写代码。

由于存在引用类型值类型两种类型,并且表现不一致,我们在具体化时要分开对待。

值类型泛型参数

每种使用到值类型泛型参数的都单独实现Hook,intbool等为值类型都要单独实现,如int类型写法:

[HookMethod("Namespace.xxx.MyClass")]
public int MyMethod([RememberType(isGeneric: true)]int param) {

引用类型泛型参数

不支持,引用类型泛型参数的方法Hook目前是不支持的,如:MyMethod<object>(object_xxx)MyMethod<string>(string_xxx)都是不支持的。表现为泛型方法被正确Hook后,并不会走我们的逻辑,具体原因不明。

📖其他

关于测试项目内存访问异常

vs的测试功能会启动一个执行引擎,其默认选项是复用执行引擎。 反复运行测试时对修改汇编指令会造成影响。 从菜单关闭该选项Test -> Test Settings -> Keep Test Execution Engine Running,即可解除此影响。

另外调试测试是不能得出正确的结果的,可能是汇编代码不能在调试模式下工作。

老版本兼容

bigbaldy1128 2016-5开源此项目后,到2018-12 kissstudioxiangyuecn 升级了此项目代码,把相关代码方式升级和合理化了一番(参考 #4 #5 )。

已对3个主要的方法都进行了变更:

  1. MonitorClrMethodHook -> MethodHook
  2. MonitorAttributeRelocatedMethodAttribute -> HookMethodAttribute
  3. OriginalAttributeShadowMethodAttribute(不兼容) -> OriginalMethodAttribute

ShadowMethodAttribute外(升级需要改动被标记的方法名称,因而无法兼容),这3个变更都是兼容的,但不推荐继续使用老方法,并且将来可能会从类库里面移除。

dotnetdetour's People

Contributors

bigbaldy1128 avatar xiangyuecn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dotnetdetour's Issues

Hook Class访问内部成员的Method

举个例子
` public class Student
{
private int ID;
private string Name;

    public Student(string name ,int id = -1)
    {
        Name = name;
        ID = id;
    }

    public string printName()
    {
        return Name;
    }
}

public static class Main
{
    public static void Test()
    {
        Student student = new Student("A",97);

        Console.WriteLine(student.printName());
    }
}`

我要Hook printName,让它多回一个ID
怎么办?

debug模式下hook成功,可是release模式下hook失败?

class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(Xuanli.GetName("audy"));
            MethodHook.Install();
            Console.WriteLine(Xuanli.GetName("audy"));

        }
    }

不知道为什么debug模式下可以hook成功,可是release模式下就hook失败

跨程序执行怎么不成功?

比如程序A里面执行程序A里面的方法FunA ,我们写一个程序B 让程序A执行他们自己的FunA 方法的时候,直接拦截执行到我们的程序B 里面的方法FunB

hook同名属性会有问题

@kissstudio @bigbaldy1128

A、B class ,均有名字一样的属性(返回类型不一样),导致运行时影子(原始)属性返回值均为null

伪代码如图示:
class A{
public List HH{get;}
}

class B{
public List HH{get;}
}

不能hook非公开的方法,无法创建带有非公开类型参数的hook,魔改了一下Monitor.cs

举个例子

我要hook A类里面的两个私有方法aa、bb:

  • aa算是比较正常的一个私有方法;
  • bb是一个带外部不能访问类型参数的方法。

例子代码

namespace ConsoleApplication1 {
	class Program {
		static void Main(string[] args) {
			Monitor.Install();
			new A().CallB();
			Console.ReadLine();
		}
	}
	class A {
		public A() { aa(); }
		//需要hook的方法1
		private void aa() {
			Console.WriteLine("A.aa()");
		}
		//需要hook的方法2
		private void bb(B b) {
			if (b == null) {
				Console.WriteLine("A.bb(null)");
			} else {
				Console.WriteLine("A.bb(B)");
				b.Run();
			}
		}

/********到此为止*****************/

		public void CallB() {
			bb(new B());
		}
		private class B{
			public void Run() {
				Console.WriteLine("B.Run()");
			}
		}
	}
	public class AHook : IMethodMonitor {
		[Monitor("ConsoleApplication1", "A")]
		private void aa() {
			Console.WriteLine("hook aa()");
			org();
		}
		[MethodImpl(MethodImplOptions.NoInlining)]
		[Original]
		private void org() {
			return;
		}
	}
	public class BHook : IMethodMonitor {
		[Monitor("ConsoleApplication1", "A")]
		private void bb(object b) {
			Console.WriteLine("hook bb()");
			org(b);
			org(null);
		}
		[MethodImpl(MethodImplOptions.NoInlining)]
		[Original]
		private void org(object b) {
			return;
		}
	}
}

未修改前执行结果

两个方法都无法hook

A.aa()
A.bb(B)
B.Run()

魔改后的结果

两个方法成功hook,并且调用未发现异常

hook aa()
A.aa()
hook bb()
A.bb(B)
B.Run()
A.bb(null)

Monitor.cs改动

Install()

两处GetMethods()调用,改成加上flags参数,flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static

InstallInternal()

GetExportedTypes()调用改成GetTypes()

两处GetMethod(methodName, paramTypes)调用,换一种方式查找Method,否则私有类型参数的Hook 函数无法编写;示例代码如下:

源代码(有两处要改):
src = type.GetMethod(methodName, paramTypes);
改成
src = getFn(type, methodName, paramTypes);

------

		//查找最优函数,忽略参数匹配
		private static MethodInfo getFn(Type type, string name, Type[] paramTypes) {
			var all = type.GetMethods(AllFlag);
			MethodInfo rtv = null;
			var find = 0;
			foreach (var item in all) {
				if (item.Name == name) {
					rtv = item;
					find++;
				}
			}
			if (find == 0) {
				return null;
			}
			if (find == 1) {
				return rtv;
			}

			//获取最佳的
			var list = new List<fnItem>();
			foreach (var item in all) {
				if (item.Name == name) {
					int score = 0;
					var ps = item.GetParameters();
					score += 100 - Math.Abs(paramTypes.Length - ps.Length);
					//参数数量不一样基本忽略
					if (score == 100) {
						for (int i = 0, len = paramTypes.Length; i < len; i++) {
							if (paramTypes[i] == ps[i].ParameterType) {
								score += 100;
							}
						}
					}
					list.Add(new fnItem() { fn = item, score = score });
				}
			}
			list.Sort((a, b) => {
				return b.score - a.score;
			});
			return list[0].fn;
		}
		private class fnItem {
			public MethodInfo fn;
			public int score;
		}

Exception on AssemblyUtil.cs

An exception is thrown in AssemblyUtil.GetImplementedObjectsByInterface

Message: The invoked member is not supported in a dynamic assembly.

Please surround the function with a try catch to return an empty list.

.net framework 2.0支持问题

要hook系统库的internal类和函数,是.net framework 2.0的。master分支在.net 2.0下编译不过去,有没有编译.net 2.0的版本?

替换指针的方法有可能失败, 可能是.net版本或者加壳导致的,建议直接从original的代码段直接jmp到新函数

另外可以使用长跳转 就不需要动eax了
byte[] jmp_inst =
{
/*
0x50, //push rax
0x48,0xB8,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90, //mov rax,target_addr
0x50, //push rax
0x48,0x8B,0x44,0x24,0x08, //mov rax,qword ptr ss:[rsp+8]
0xC2,0x08,0x00 //ret 8
*/
0xff,0x25,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
最后8个字节写入绝对地址.

我看你们代码有用到0xe9跳转,在64位下有可能发生跳不到的情况 e9跳转只支持4个字节 建议都换成长跳转 14字节

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.