private void LogAllSections(string from, IntPtr src, uint size)
        {
            MMDbgLog.Log($"{from} failed for 0x{(long) src:X16} + {size} - logging all memory sections");
            Exception ex = new Win32Exception();

            MMDbgLog.Log($"reason: {ex.Message}");

            try {
                IntPtr proc = GetCurrentProcess();
                IntPtr addr = (IntPtr)0x00000000000010000;
                int    i    = 0;
                while (true)
                {
                    if (VirtualQueryEx(proc, addr, out MEMORY_BASIC_INFORMATION infoBasic, sizeof(MEMORY_BASIC_INFORMATION)) == 0)
                    {
                        break;
                    }

                    ulong srcL    = (ulong)src;
                    ulong srcR    = srcL + size;
                    ulong infoL   = (ulong)infoBasic.BaseAddress;
                    ulong infoR   = infoL + (ulong)infoBasic.RegionSize;
                    bool  overlap = infoL <= srcR && srcL <= infoR;

                    MMDbgLog.Log($"{(overlap ? "*" : "-")} #{i++}");
                    MMDbgLog.Log($"addr: 0x{(long) infoBasic.BaseAddress:X16}");
                    MMDbgLog.Log($"size: 0x{(long) infoBasic.RegionSize:X16}");
                    MMDbgLog.Log($"aaddr: 0x{(long) infoBasic.AllocationBase:X16}");
                    MMDbgLog.Log($"state: {infoBasic.State}");
                    MMDbgLog.Log($"type: {infoBasic.Type}");
                    MMDbgLog.Log($"protect: {infoBasic.Protect}");
                    MMDbgLog.Log($"aprotect: {infoBasic.AllocationProtect}");

                    long regionSize = (long)infoBasic.RegionSize;
                    if (regionSize <= 0 || (int)regionSize != regionSize)
                    {
                        if (IntPtr.Size == 8)
                        {
                            try {
                                addr = (IntPtr)((ulong)infoBasic.BaseAddress + (ulong)infoBasic.RegionSize);
                            } catch (OverflowException) {
                                MMDbgLog.Log("overflow");
                                break;
                            }
                            continue;
                        }
                        break;
                    }

                    try {
                        addr = (IntPtr)((uint)infoBasic.BaseAddress + (uint)infoBasic.RegionSize);
                    } catch (OverflowException) {
                        MMDbgLog.Log("overflow");
                        break;
                    }
                }
            } finally {
                throw ex;
            }
        }
Beispiel #2
0
        private IntPtr NotThePreStub(IntPtr ptrGot, IntPtr ptrParsed)
        {
            if (ThePreStub == IntPtr.Zero)
            {
                ThePreStub = (IntPtr)(-2);

                // FIXME: Find a better less likely called NGEN'd candidate that points to ThePreStub.
                // This was "found" by tModLoader.
                // Can be missing in .NET 5.0 outside of Windows for some reason.
                MethodInfo mi = typeof(System.Net.HttpWebRequest).Assembly
                                .GetType("System.Net.Connection")
                                ?.GetMethod("SubmitRequest", BindingFlags.NonPublic | BindingFlags.Instance);

                if (mi != null)
                {
                    ThePreStub = GetNativeStart(mi);
                    MMDbgLog.Log($"ThePreStub: 0x{(long) ThePreStub:X16}");
                }
                else if (PlatformHelper.Is(Platform.Windows))
                {
                    // FIXME: This should be -1 (always return ptrGot) on all plats, but SubmitRequest is Windows-only?
                    ThePreStub = (IntPtr)(-1);
                }
            }

            return((ptrParsed == ThePreStub || ThePreStub == (IntPtr)(-1)) ? ptrGot : ptrParsed);
        }
        public static DetourRuntimeNETCorePlatform Create()
        {
            try {
                IntPtr jit     = GetJitObject();
                Guid   jitGuid = GetJitGuid(jit);

                DetourRuntimeNETCorePlatform platform = null;

                if (jitGuid == DetourRuntimeNET50Platform.JitVersionGuid)
                {
                    platform = new DetourRuntimeNET50Platform();
                }
                else if (jitGuid == DetourRuntimeNETCore30Platform.JitVersionGuid)
                {
                    platform = new DetourRuntimeNETCore30Platform();
                }
                // TODO: add more known JIT GUIDs

                if (platform == null)
                {
                    return(new DetourRuntimeNETCorePlatform());
                }

                platform?.InstallJitHooks(jit);
                return(platform);
            } catch (Exception e) {
                MMDbgLog.Log("Could not get JIT information for the runtime, falling out to the version without JIT hooks");
                MMDbgLog.Log($"Error: {e}");
            }

            return(new DetourRuntimeNETCorePlatform());
        }
 protected virtual void JitHookCore(
     RuntimeTypeHandle declaringType,
     RuntimeMethodHandle methodHandle,
     IntPtr methodBodyStart,
     ulong methodBodySize,
     RuntimeTypeHandle[] genericClassArguments,
     RuntimeTypeHandle[] genericMethodArguments
     )
 {
     try {
         Type declType = Type.GetTypeFromHandle(declaringType);
         if (genericClassArguments != null && declType.IsGenericTypeDefinition)
         {
             declType = declType.MakeGenericType(genericClassArguments.Select(Type.GetTypeFromHandle).ToArray());
         }
         MethodBase method = MethodBase.GetMethodFromHandle(methodHandle, declType.TypeHandle);
         // method is null for P/Invokes, ComImports and other dynamic interop methods.
         // Just to be 100% sure that it ISN'T an already known-but-"hidden" pinned method though...
         if (method == null)
         {
             method = GetPin(methodHandle).Method;
         }
         try {
             OnMethodCompiled?.Invoke(method, methodBodyStart, methodBodySize);
         } catch (Exception e) {
             MMDbgLog.Log($"Error executing OnMethodCompiled event: {e}");
         }
     } catch (Exception e) {
         MMDbgLog.Log($"Error in JitHookCore: {e}");
     }
 }
Beispiel #5
0
 private static MemberInfo _Cache(string cacheKey, MemberInfo value)
 {
     if (cacheKey != null && value == null)
     {
         MMDbgLog.Log($"ResolveRefl failure: {cacheKey}");
     }
     if (cacheKey != null && value != null)
     {
         lock (ResolveReflectionCache) {
             ResolveReflectionCache[cacheKey] = new WeakReference(value);
         }
     }
     return(value);
 }
Beispiel #6
0
        protected override unsafe IntPtr GetFunctionPointer(MethodBase method, RuntimeMethodHandle handle)
        {
            MMDbgLog.Log($"mets: {method.GetID()}");
            MMDbgLog.Log($"meth: 0x{(long) handle.Value:X16}");
            MMDbgLog.Log($"getf: 0x{(long) handle.GetFunctionPointer():X16}");

            if (method.IsVirtual && (method.DeclaringType?.IsValueType ?? false))
            {
                /* .NET has got TWO MethodDescs and thus TWO ENTRY POINTS for virtual struct methods (f.e. override ToString).
                 * More info: https://mattwarren.org/2017/08/02/A-look-at-the-internals-of-boxing-in-the-CLR/#unboxing-stub-creation
                 *
                 * Observations made so far:
                 * - GetFunctionPointer ALWAYS returns a pointer to the unboxing stub handle.
                 * - On x86, the "real" entry point is often found 8 bytes after the unboxing stub entry point.
                 * - Methods WILL be called INDIRECTLY using the pointer found in the "real" MethodDesc.
                 * - The "real" MethodDesc will be updated, which isn't an issue except that we can't patch the stub in time.
                 * - The "real" stub will stay untouched.
                 * - LDFTN RETURNS A POINTER TO THE "REAL" ENTRY POINT.
                 */
                MMDbgLog.Log($"ldfn: 0x{(long) method.GetLdftnPointer():X16}");
                return(method.GetLdftnPointer());
            }


            IntPtr ptr = base.GetFunctionPointer(method, handle);

            /* Many (if not all) NGEN'd methods (f.e. those from mscorlib.ni.dll) are handled in a special manner.
             * When debugged using WinDbg, !dumpmd for the handle gives a different CodeAddr than ldftn or GetFunctionPointer.
             * When using !ip2md on the ldftn / GetFunctionPointer result, no MD is found.
             * There is only one MD, we're already accessing it, but we still can't access the "real" entry point.
             * Luckily a jmp to it exists within the stub returned by GetFunctionPointer.
             * Sadly detecting when to read it is... ugly, to say the least.
             * This pretty much acts as the reverse of DetourNative*Platform.Apply
             * Maybe this should be Native*Platform-ified in the future, but for now...
             */

            // IMPORTANT: IN SOME CIRCUMSTANCES, THIS CAN FIND ThePreStub AS THE ENTRY POINT.

            if (PlatformHelper.Is(Platform.ARM))
            {
                // TODO: Debug detouring NGEN'd methods on ARM.
            }
            else if (IntPtr.Size == 4)
            {
                int iptr = (int)ptr;
                // x86
                if (*(byte *)(iptr + 0x00) == 0xb8 && // mov ... (mscorlib_ni!???)
                    *(byte *)(iptr + 0x05) == 0x90 && // nop
                    *(byte *)(iptr + 0x06) == 0xe8 && // call ... (clr!PrecodeRemotingThunk)
                    *(byte *)(iptr + 0x0b) == 0xe9    // jmp {DELTA}
                    )
                {
                    // delta = to - (from + 1 + sizeof(int))
                    // to = delta + (from + 1 + sizeof(int))
                    int from  = iptr + 0x0b;
                    int delta = *(int *)(from + 1);
                    int to    = delta + (from + 1 + sizeof(int));
                    ptr = NotThePreStub(ptr, (IntPtr)to);
                    MMDbgLog.Log($"ngen: 0x{(long) ptr:X16}");
                    return(ptr);
                }
            }
            else
            {
                long lptr = (long)ptr;
                // x64 .NET Framework
                if (*(uint *)(lptr + 0x00) == 0x74___c9_85_48 && // in reverse order: test rcx, rcx | je ...
                    *(uint *)(lptr + 0x05) == 0x49___01_8b_48 && // in reverse order: rax, qword ptr [rcx] | mov ...
                    *(uint *)(lptr + 0x12) == 0x74___c2_3b_49 && // in reverse order: cmp rax, r10 | je ...
                    *(ushort *)(lptr + 0x17) == 0xb8_48          // in reverse order: mov {TARGET}
                    )
                {
                    ptr = NotThePreStub(ptr, (IntPtr)(*(ulong *)(lptr + 0x19)));
                    MMDbgLog.Log($"ngen: 0x{(long) ptr:X16}");
                    return(ptr);
                }

                // x64 .NET Core
                if (*(byte *)(lptr + 0x00) == 0xe9 && // jmp {DELTA}
                    *(byte *)(lptr + 0x05) == 0x5f    // pop rdi
                    )
                {
                    // delta = to - (from + 1 + sizeof(int))
                    // to = delta + (from + 1 + sizeof(int))
                    long from  = lptr;
                    int  delta = *(int *)(from + 1);
                    long to    = delta + (from + 1 + sizeof(int));
                    ptr = NotThePreStub(ptr, (IntPtr)to);
                    MMDbgLog.Log($"ngen: 0x{(long) ptr:X16}");
                    return(ptr);
                }
            }


            return(ptr);
        }
        protected override MethodInfo _Generate(DynamicMethodDefinition dmd, object context)
        {
            MethodBase       orig = dmd.OriginalMethod;
            MethodDefinition def  = dmd.Definition;

            Type[] argTypes;

            if (orig != null)
            {
                ParameterInfo[] args = orig.GetParameters();
                int             offs = 0;
                if (!orig.IsStatic)
                {
                    offs++;
                    argTypes    = new Type[args.Length + 1];
                    argTypes[0] = orig.GetThisParamType();
                }
                else
                {
                    argTypes = new Type[args.Length];
                }
                for (int i = 0; i < args.Length; i++)
                {
                    argTypes[i + offs] = args[i].ParameterType;
                }
            }
            else
            {
                int offs = 0;
                if (def.HasThis)
                {
                    offs++;
                    argTypes = new Type[def.Parameters.Count + 1];
                    Type type = def.DeclaringType.ResolveReflection();
                    if (type.IsValueType)
                    {
                        type = type.MakeByRefType();
                    }
                    argTypes[0] = type;
                }
                else
                {
                    argTypes = new Type[def.Parameters.Count];
                }
                for (int i = 0; i < def.Parameters.Count; i++)
                {
                    argTypes[i + offs] = def.Parameters[i].ParameterType.ResolveReflection();
                }
            }

            string name    = dmd.Name ?? $"DMD<{orig?.GetID(simple: true) ?? def.GetID(simple: true)}>";
            Type   retType = (orig as MethodInfo)?.ReturnType ?? def.ReturnType?.ResolveReflection();

            MMDbgLog.Log($"new DynamicMethod: {retType} {name}({string.Join(",", argTypes.Select(type => type?.ToString()).ToArray())})");
            if (orig != null)
            {
                MMDbgLog.Log($"orig: {(orig as MethodInfo)?.ReturnType?.ToString() ?? "NULL"} {orig.Name}({string.Join(",", orig.GetParameters().Select(arg => arg?.ParameterType?.ToString() ?? "NULL").ToArray())})");
            }
            MMDbgLog.Log($"mdef: {def.ReturnType?.ToString() ?? "NULL"} {name}({string.Join(",", def.Parameters.Select(arg => arg?.ParameterType?.ToString() ?? "NULL").ToArray())})");

            DynamicMethod dm = new DynamicMethod(
                name,
                typeof(void), argTypes,
                orig?.DeclaringType ?? dmd.OwnerType ?? typeof(DynamicMethodDefinition),
                true // If any random errors pop up, try setting this to false first.
                );

            // DynamicMethods don't officially "support" certain return types, such as ByRef types.
            _DynamicMethod_returnType.SetValue(dm, retType);

            ILGenerator il = dm.GetILGenerator();

            _DMDEmit.Generate(dmd, dm, il);

            return(dm);
        }
Beispiel #8
0
        protected override unsafe IntPtr GetFunctionPointer(MethodBase method, RuntimeMethodHandle handle)
        {
            MMDbgLog.Log($"mets: {method.GetID()}");
            MMDbgLog.Log($"meth: 0x{(long) handle.Value:X16}");
            MMDbgLog.Log($"getf: 0x{(long) handle.GetFunctionPointer():X16}");

            if (method.IsVirtual && (method.DeclaringType?.IsValueType ?? false))
            {
                /* .NET has got TWO MethodDescs and thus TWO ENTRY POINTS for virtual struct methods (f.e. override ToString).
                 * More info: https://mattwarren.org/2017/08/02/A-look-at-the-internals-of-boxing-in-the-CLR/#unboxing-stub-creation
                 *
                 * Observations made so far:
                 * - GetFunctionPointer ALWAYS returns a pointer to the unboxing stub handle.
                 * - On x86, the "real" entry point is often found 8 bytes after the unboxing stub entry point.
                 * - Methods WILL be called INDIRECTLY using the pointer found in the "real" MethodDesc.
                 * - The "real" MethodDesc will be updated, which isn't an issue except that we can't patch the stub in time.
                 * - The "real" stub will stay untouched.
                 * - LDFTN RETURNS A POINTER TO THE "REAL" ENTRY POINT.
                 */
                MMDbgLog.Log($"ldfn: 0x{(long) method.GetLdftnPointer():X16}");
                return(method.GetLdftnPointer());
            }

            bool regenerated = false;

ReloadFuncPtr:

            IntPtr ptr = base.GetFunctionPointer(method, handle);

            /* Many (if not all) NGEN'd methods (f.e. those from mscorlib.ni.dll) are handled in a special manner.
             * When debugged using WinDbg, !dumpmd for the handle gives a different CodeAddr than ldftn or GetFunctionPointer.
             * When using !ip2md on the ldftn / GetFunctionPointer result, no MD is found.
             * There is only one MD, we're already accessing it, but we still can't access the "real" entry point.
             * Luckily a jmp to it exists within the stub returned by GetFunctionPointer.
             * Sadly detecting when to read it is... ugly, to say the least.
             * This pretty much acts as the reverse of DetourNative*Platform.Apply
             * Maybe this should be Native*Platform-ified in the future, but for now...
             */

            // IMPORTANT: IN SOME CIRCUMSTANCES, THIS CAN FIND ThePreStub AS THE ENTRY POINT.

            if (PlatformHelper.Is(Platform.ARM))
            {
                // TODO: Debug detouring NGEN'd methods on ARM.
            }
            else if (IntPtr.Size == 4)
            {
                int iptr = (int)ptr;
                // x86
                if (*(byte *)(iptr + 0x00) == 0xb8 && // mov ... (mscorlib_ni!???)
                    *(byte *)(iptr + 0x05) == 0x90 && // nop
                    *(byte *)(iptr + 0x06) == 0xe8 && // call ... (clr!PrecodeRemotingThunk)
                    *(byte *)(iptr + 0x0b) == 0xe9    // jmp {DELTA}
                    )
                {
                    // delta = to - (from + 1 + sizeof(int))
                    // to = delta + (from + 1 + sizeof(int))
                    int from  = iptr + 0x0b;
                    int delta = *(int *)(from + 1);
                    int to    = delta + (from + 1 + sizeof(int));
                    ptr = NotThePreStub(ptr, (IntPtr)to);
                    MMDbgLog.Log($"ngen: 0x{(long) ptr:X8}");
                    return(ptr);
                }

                // .NET Core
                if (*(byte *)(iptr + 0x00) == 0xe9 && // jmp {DELTA}
                    *(byte *)(iptr + 0x05) == 0x5f    // pop rdi
                    )
                {
                    // delta = to - (from + 1 + sizeof(int))
                    // to = delta + (from + 1 + sizeof(int))
                    int from  = iptr;
                    int delta = *(int *)(from + 1);
                    int to    = delta + (from + 1 + sizeof(int));
                    ptr = NotThePreStub(ptr, (IntPtr)to);
                    MMDbgLog.Log($"ngen: 0x{(int) ptr:X8}");
                    return(ptr);
                }
            }
            else
            {
                long lptr = (long)ptr;
                // x64 .NET Framework
                if (*(uint *)(lptr + 0x00) == 0x74___c9_85_48 && // in reverse order: test rcx, rcx | je ...
                    *(uint *)(lptr + 0x05) == 0x49___01_8b_48 && // in reverse order: rax, qword ptr [rcx] | mov ...
                    *(uint *)(lptr + 0x12) == 0x74___c2_3b_49 && // in reverse order: cmp rax, r10 | je ...
                    *(ushort *)(lptr + 0x17) == 0xb8_48          // in reverse order: mov {TARGET}
                    )
                {
                    ptr = NotThePreStub(ptr, (IntPtr)(*(ulong *)(lptr + 0x19)));
                    MMDbgLog.Log($"ngen: 0x{(long) ptr:X16}");
                    return(ptr);
                }

                // FIXME: on Core, it seems that *every* method has this stub, not just NGEN'd methods
                //        It also seems to correctly find the body, but because ThePreStub is always -1,
                //          it never returns that.
                //        One consequence of this seems to be that re-JITting a method calling a patched
                //          method causes it to use a new stub, except not patched.

                // It seems that if there is *any* pause between the method being prepared, and this being
                //   called, there is a chance that the JIT will do something funky and reset the thunk for
                //   the method (which is what GetFunctionPointer gives) back to a call to PrecodeFixupThunk.
                // This can be observed by checking for the first byte being 0xe8 instead of 0xe9.
                // If this happens at the wrong moment, we won't get the opportunity to patch the actual method
                //   body because our only pointer to it will have been deleted.

                // In conclusion: *Do we need to disable re-JITing while patching?*

                // Correction for the above: It seems that .NET Core ALWAYS has one indirection before the method
                //   body, and that indirection is used as an easy way to call into the JIT when necessary. Also,
                //   the JIT never generates a call directly to ThePreStub, but instead generates a call to
                //   PrecodeFixupThunk which then calls ThePreStub.

                // x64 .NET Core
                if (*(byte *)(lptr + 0x00) == 0xe9 && // jmp {DELTA}
                    *(byte *)(lptr + 0x05) == 0x5f    // pop rdi
                    )
                {
                    // delta = to - (from + 1 + sizeof(int))
                    // to = delta + (from + 1 + sizeof(int))
                    long from  = lptr;
                    int  delta = *(int *)(from + 1);
                    long to    = delta + (from + 1 + sizeof(int));
                    ptr = NotThePreStub(ptr, (IntPtr)to);
                    MMDbgLog.Log($"ngen: 0x{(long) ptr:X16}");
                    return(ptr);
                }

                // x64 .NET Core, but the thunk was reset
                // This can also just be an optimized method immediately calling another method.
                if (*(byte *)(lptr + 0x00) == 0xe8 && !regenerated)   // call
                {
                    MMDbgLog.Log($"Method thunk reset; regenerating");
                    regenerated = true;
                    int  precodeThunkOffset = *(int *)(lptr + 1);
                    long precodeThunk       = precodeThunkOffset + (lptr + 1 + sizeof(int));
                    MMDbgLog.Log($"PrecodeFixupThunk: 0x{precodeThunk:X16}");
                    PrepareMethod(method, handle);
                    goto ReloadFuncPtr;
                }
            }


            return(ptr);
        }