Mono Cecil十分强大,强大到可以静态注入程序集(注入后生成新的程序集)和动态注入程序集(注入后不改变目标程序集,只在运行时改变程序集行为),它甚至可以用来调试PDB MDB调试符号格式文件。
注:仔细看了下,并不支持“动态”注入,cecil只支持从硬盘加载或从内存读取一个已经被加载了的assembly,然后修改它的副本,最后另存为或者直接调用这个副本。
原程序集并不会发生任何改变,也就是说,如果你要修改一个程序集的行为,那么你必须在原软件加载它之前,修改它,并代替旧的。assembly一旦别加载,cecil是无法修改它的。
若想实现动态注入,动态修改已经被加载了的程序集功能(最典型的就是Unity的游戏,程序集的加载由mono.dll加载,我们比较难去操控它的加载),
可以参考这个教程:Dynamically replace the contents of a C# method?
或者这个开源库:Harmony
他们的原理都是,先用RuntimeHelpers预先从内存中加载原函数,以及要替换掉原函数的注入函数,然后分别获取它们的指针,即它们在内存中的位置,
然后交换它们指针,交换内存空间,即可以实现,替换已经加载了的程序集里的函数内容。
(32位跟64位需要分别处理,因为指针大小不一样,还有这个只支持X86架构,ARM需要另外处理)
MethodInfo methodToReplace = typeof(Target).GetMethod("targetMethod"+ funcNum, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); MethodInfo methodToInject = typeof(Injection).GetMethod("injectionMethod"+ funcNum, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle); RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle); unsafe { if (IntPtr.Size == 4) { int* inj = (int*)methodToInject.MethodHandle.Value.ToPointer() + 2; int* tar = (int*)methodToReplace.MethodHandle.Value.ToPointer() + 2; #if DEBUG Console.WriteLine(" Version x86 Debug "); byte* injInst = (byte*)*inj; byte* tarInst = (byte*)*tar; int* injSrc = (int*)(injInst + 1); int* tarSrc = (int*)(tarInst + 1); *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5); #else Console.WriteLine(" Version x86 Release "); *tar = *inj; #endif } else { long* inj = (long*)methodToInject.MethodHandle.Value.ToPointer()+1; long* tar = (long*)methodToReplace.MethodHandle.Value.ToPointer()+1; #if DEBUG Console.WriteLine(" Version x64 Debug "); byte* injInst = (byte*)*inj; byte* tarInst = (byte*)*tar; int* injSrc = (int*)(injInst + 1); int* tarSrc = (int*)(tarInst + 1); *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5); #else Console.WriteLine(" Version x64 Release "); *tar = *inj; #endif } }