In question Dynamically replace the contents of a C# method? I found a good response from @ Logman. I do not have standing to ask it in the comments.
using System; using System.Reflection; using System.Runtime.CompilerServices; namespace ReplaceHandles { class Program { static void Main(string[] args) { Injection.replace(); Target target = new Target(); target.test(); Console.Read(); } } public class Injection { public static void replace() { MethodInfo methodToReplace = typeof(Target).GetMethod("test", BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); MethodInfo methodToInject = typeof(Target2).GetMethod("test", BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle); RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle); ReplaceInner(methodToReplace, methodToInject); } static void ReplaceInner(MethodInfo methodToReplace, MethodInfo methodToInject) { unsafe { if (IntPtr.Size == 4) { int* inj = (int*)methodToInject.MethodHandle.Value.ToPointer() + 2; int* tar = (int*)methodToReplace.MethodHandle.Value.ToPointer() + 2; *tar = *inj; } else { ulong* inj = (ulong*)methodToInject.MethodHandle.Value.ToPointer() + 1; ulong* tar = (ulong*)methodToReplace.MethodHandle.Value.ToPointer() + 1; *tar = *inj; } } } } public class Base { public virtual void test() { } } public class Target : Base { public override void test() { Console.WriteLine("Target.test()"); } public void test3() { Console.WriteLine("Target.test3()"); } } public class Target2 { public void test() { Console.WriteLine("Target.test2()"); } } }
Everything works, but does not work the replacement of overridden methods.
Updated Answer
First of all, keep in mind that
Method Address = Method Virtual Address + base address of class that declares this member..
If the method to replace is a virtual overridden method, please use the following.
if (methodToReplace.IsVirtual) { ReplaceVirtualInner(methodToReplace, methodToInject); } else { ReplaceInner(methodToReplace, methodToInject); }
Tested with both platform target x86 and x64: it works!!!.
static void ReplaceVirtualInner(MethodInfo methodToReplace, MethodInfo methodToInject) { unsafe { UInt64* methodDesc = (UInt64*)(methodToReplace.MethodHandle.Value.ToPointer()); int index = (int)(((*methodDesc) >> 32) & 0xFF); if (IntPtr.Size == 4) { uint* classStart = (uint*)methodToReplace.DeclaringType.TypeHandle.Value.ToPointer(); classStart += 10; classStart = (uint*)*classStart; uint* tar = classStart + index; uint* inj = (uint*)methodToInject.MethodHandle.Value.ToPointer() + 2; //int* tar = (int*)methodToReplace.MethodHandle.Value.ToPointer() + 2; *tar = *inj; } else { ulong* classStart = (ulong*)methodToReplace.DeclaringType.TypeHandle.Value.ToPointer(); classStart += 8; classStart = (ulong*)*classStart; ulong* tar = classStart + index; ulong* inj = (ulong*)methodToInject.MethodHandle.Value.ToPointer() + 1; //ulong* tar = (ulong*)methodToReplace.MethodHandle.Value.ToPointer() + 1; *tar = *inj; } } }
Original Answer
You have to run (from a cmd
sell) the exe
compiled in Release
mode, not in Debug
.
I've tried and I confirm it does not throw exceptions in that case.
C:\dev\Calc>C:\dev\Calc\bin\Release\Calc.exe Target.targetMethod1() Target.targetMethod2() Not injected 2 Target.targetMethod3(Test) Target.targetMethod4() Version x64 Release Version x64 Release Version x64 Release Version x64 Release Injection.injectionMethod1 Injection.injectionMethod2 Injected 2 Injection.injectionMethod3 Test
as you can see, the above runs whithout the following exception
C:\dev\Calc>C:\dev\Calc\bin\Debug\Calc.exe Target.targetMethod1() Target.targetMethod2() Not injected 2 Target.targetMethod3(Test) Target.targetMethod4() Version x64 Debug Version x64 Debug Version x64 Debug Version x64 Debug Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt. at InjectionTest.Target.targetMethod1() in C:\dev\Calc\Program.cs:line 38 at InjectionTest.Target.test() in C:\dev\Calc\Program.cs:line 31 at InjectionTest.Program.Main(String[] args) in C:\dev\Calc\Program.cs:line 21
and the reason is explained in this comment
in debug compiler adds some middle man code and to inject your method you need to recalculate address of your method
After the question's edit
Looking at the revised question, I confirm that there is an issue if the Base
method is declared as virtual
. I'm trying to find a workaround.
workaround 1
My first idea was to replace the new
keyworkd instead of the override
(so when the Base method is not virtual
). That makes it work, so I guess that (when we have a virtual method) the injection shoud happen at the base class level maybe... and the different behaviour must have to do with using callvirt
vs call