/* Copyright (C) 2011-2015 de4dot@gmail.com This file is part of de4dot. de4dot is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. de4dot is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with de4dot. If not, see . */ using System; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Reflection; using System.Security; using dnlib.DotNet; using dnlib.DotNet.MD; using dnlib.PE; using de4dot.blocks; namespace System.Runtime.ExceptionServices { [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] class HandleProcessCorruptedStateExceptionsAttribute : Attribute { } } namespace de4dot.mdecrypt { public class DynamicMethodsDecrypter { static DynamicMethodsDecrypter instance; DecryptMethodsInfo decryptMethodsInfo; struct FuncPtrInfo { public D del; public IntPtr ptr; public IntPtr ptrInDll; public void Prepare(Delegate del) { RuntimeHelpers.PrepareDelegate(del); ptr = Marshal.GetFunctionPointerForDelegate(del); } } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct IMAGE_SECTION_HEADER { public ulong name; public uint VirtualSize; public uint VirtualAddress; public uint SizeOfRawData; public uint PointerToRawData; public uint PointerToRelocations; public uint PointerToLinenumbers; public ushort NumberOfRelocations; public ushort NumberOfLinenumbers; public uint Characteristics; } [StructLayout(LayoutKind.Sequential, Pack=1, Size=0x88)] struct CORINFO_METHOD_INFO { public IntPtr ftn; public IntPtr scope; public IntPtr ILCode; public uint ILCodeSize; public ushort maxStack; public ushort EHCount; // 0x64 other bytes here... } class DecryptContext { public DumpedMethod dm; public MethodDef method; } FuncPtrInfo ourCompileMethodInfo = new FuncPtrInfo(); FuncPtrInfo returnMethodTokenInfo = new FuncPtrInfo(); FuncPtrInfo returnNameOfMethodInfo = new FuncPtrInfo(); IntPtr origCompileMethod; IntPtr jitterTextFreeMem; IntPtr callMethod; CallMethod callMethodDelegate; IntPtr jitterInstance; IntPtr jitterVtbl; Module moduleToDecrypt; IntPtr hInstModule; IntPtr ourCompMem; bool compileMethodIsThisCall; IntPtr ourCodeAddr; MDTable methodDefTable; IntPtr methodDefTablePtr; ModuleDefMD dnlibModule; MethodDef moduleCctor; uint moduleCctorCodeRva; IntPtr moduleToDecryptScope; DecryptContext ctx = new DecryptContext(); public static DynamicMethodsDecrypter Instance { get { if (instance != null) return instance; return instance = new DynamicMethodsDecrypter(); } } static Version VersionNet45DevPreview = new Version(4, 0, 30319, 17020); static Version VersionNet45Rtm = new Version(4, 0, 30319, 17929); DynamicMethodsDecrypter() { if (UIntPtr.Size != 4) throw new ApplicationException("Only 32-bit dynamic methods decryption is supported"); // .NET 4.5 beta/preview/RC compileMethod has thiscall calling convention, but they // switched back to stdcall in .NET 4.5 RTM compileMethodIsThisCall = Environment.Version >= VersionNet45DevPreview && Environment.Version < VersionNet45Rtm; } [DllImport("kernel32", CharSet = CharSet.Ansi)] static extern IntPtr GetModuleHandle(string name); [DllImport("kernel32", CharSet = CharSet.Ansi)] static extern IntPtr GetProcAddress(IntPtr hModule, string name); [DllImport("kernel32")] static extern bool VirtualProtect(IntPtr addr, int size, uint newProtect, out uint oldProtect); const uint PAGE_EXECUTE_READWRITE = 0x40; [DllImport("kernel32")] static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, uint flAllocationType, uint flProtect); [DllImport("kernel32")] static extern bool GetModuleHandleEx(uint dwFlags, IntPtr lpModuleName, out IntPtr phModule); delegate IntPtr GetJit(); delegate int CompileMethod(IntPtr jitter, IntPtr comp, IntPtr info, uint flags, IntPtr nativeEntry, IntPtr nativeSizeOfCode, out bool handled); delegate int ReturnMethodToken(); delegate string ReturnNameOfMethod(); delegate int CallMethod(IntPtr compileMethod, IntPtr jitter, IntPtr comp, IntPtr info, uint flags, IntPtr nativeEntry, IntPtr nativeSizeOfCode); public DecryptMethodsInfo DecryptMethodsInfo { set { decryptMethodsInfo = value; } } public unsafe Module Module { set { if (moduleToDecrypt != null) throw new ApplicationException("Module has already been initialized"); moduleToDecrypt = value; hInstModule = Marshal.GetHINSTANCE(moduleToDecrypt); moduleToDecryptScope = GetScope(moduleToDecrypt); dnlibModule = ModuleDefMD.Load(hInstModule); methodDefTable = dnlibModule.TablesStream.MethodTable; methodDefTablePtr = new IntPtr((byte*)hInstModule + (uint)dnlibModule.MetaData.PEImage.ToRVA(methodDefTable.StartOffset)); InitializeDNLibMethods(); } } static IntPtr GetScope(Module module) { var obj = GetFieldValue(module.ModuleHandle, "m_ptr"); if (obj is IntPtr) return (IntPtr)obj; if (obj.GetType().ToString() == "System.Reflection.RuntimeModule") return (IntPtr)GetFieldValue(obj, "m_pData"); throw new ApplicationException(string.Format("m_ptr is an invalid type: {0}", obj.GetType())); } static object GetFieldValue(object obj, string fieldName) { var field = obj.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) throw new ApplicationException(string.Format("Could not get field {0}::{1}", obj.GetType(), fieldName)); return field.GetValue(obj); } unsafe void InitializeDNLibMethods() { moduleCctor = dnlibModule.GlobalType.FindStaticConstructor(); if (moduleCctor == null) moduleCctorCodeRva = 0; else { byte* p = (byte*)hInstModule + (uint)moduleCctor.RVA; if ((*p & 3) == 2) moduleCctorCodeRva = (uint)moduleCctor.RVA + 1; else moduleCctorCodeRva = (uint)((uint)moduleCctor.RVA + (p[1] >> 4) * 4); } } public unsafe void InstallCompileMethod() { var hJitterDll = GetJitterDllHandle(); jitterTextFreeMem = GetEndOfText(hJitterDll); var getJitPtr = GetProcAddress(hJitterDll, "getJit"); var getJit = (GetJit)Marshal.GetDelegateForFunctionPointer(getJitPtr, typeof(GetJit)); jitterInstance = getJit(); jitterVtbl = *(IntPtr*)jitterInstance; origCompileMethod = *(IntPtr*)jitterVtbl; PrepareMethods(); InitializeDelegateFunctionPointers(); CreateOurCode(); callMethodDelegate = (CallMethod)Marshal.GetDelegateForFunctionPointer(callMethod, typeof(CallMethod)); WriteCompileMethod(ourCompileMethodInfo.ptrInDll); } unsafe void WriteCompileMethod(IntPtr newCompileMethod) { uint oldProtect; if (!VirtualProtect(jitterVtbl, IntPtr.Size, PAGE_EXECUTE_READWRITE, out oldProtect)) throw new ApplicationException("Could not enable write access to jitter vtbl"); *(IntPtr*)jitterVtbl = newCompileMethod; VirtualProtect(jitterVtbl, IntPtr.Size, oldProtect, out oldProtect); } void InitializeDelegateFunctionPointers() { ourCompileMethodInfo.Prepare(ourCompileMethodInfo.del = TheCompileMethod); returnMethodTokenInfo.Prepare(returnMethodTokenInfo.del = ReturnMethodToken2); returnNameOfMethodInfo.Prepare(returnNameOfMethodInfo.del = ReturnNameOfMethod2); } public void LoadObfuscator() { RuntimeHelpers.RunModuleConstructor(moduleToDecrypt.ModuleHandle); } public unsafe bool CanDecryptMethods() { return *(IntPtr*)jitterVtbl != ourCompileMethodInfo.ptrInDll && *(IntPtr*)jitterVtbl != origCompileMethod; } unsafe static IntPtr GetEndOfText(IntPtr hDll) { byte* p = (byte*)hDll; p += *(uint*)(p + 0x3C); // add DOSHDR.e_lfanew p += 4; int numSections = *(ushort*)(p + 2); int sizeOptionalHeader = *(ushort*)(p + 0x10); p += 0x14; uint sectionAlignment = *(uint*)(p + 0x20); p += sizeOptionalHeader; var textName = new byte[8] { (byte)'.', (byte)'t', (byte)'e', (byte)'x', (byte)'t', 0, 0, 0 }; var name = new byte[8]; var pSection = (IMAGE_SECTION_HEADER*)p; for (int i = 0; i < numSections; i++, pSection++) { Marshal.Copy(new IntPtr(pSection), name, 0, name.Length); if (!CompareName(textName, name, name.Length)) continue; uint size = pSection->VirtualSize; uint rva = pSection->VirtualAddress; int displ = -8; return new IntPtr((byte*)hDll + rva + size + displ); } throw new ApplicationException("Could not find .text section"); } static bool CompareName(byte[] b1, byte[] b2, int len) { for (int i = 0; i < len; i++) { if (b1[i] != b2[i]) return false; } return true; } void PrepareMethods() { Marshal.PrelinkAll(GetType()); foreach (var methodInfo in GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance)) RuntimeHelpers.PrepareMethod(methodInfo.MethodHandle); } unsafe void CreateOurCode() { var code = new NativeCodeGenerator(); // our compileMethod() func int compileMethodOffset = code.Size; int numPushedArgs = compileMethodIsThisCall ? 5 : 6; code.WriteByte(0x51); // push ecx code.WriteByte(0x50); // push eax code.WriteByte(0x54); // push esp for (int i = 0; i < 5; i++) WritePushDwordPtrEspDispl(code, (sbyte)(0xC + numPushedArgs * 4)); // push dword ptr [esp+XXh] if (!compileMethodIsThisCall) WritePushDwordPtrEspDispl(code, (sbyte)(0xC + numPushedArgs * 4)); // push dword ptr [esp+XXh] else code.WriteByte(0x51); // push ecx code.WriteCall(ourCompileMethodInfo.ptr); code.WriteByte(0x5A); // pop edx code.WriteByte(0x59); // pop ecx code.WriteBytes(0x84, 0xD2); // test dl, dl code.WriteBytes(0x74, 0x03); // jz $+5 code.WriteBytes(0xC2, (ushort)(numPushedArgs * 4)); // retn 14h/18h for (int i = 0; i < numPushedArgs; i++) WritePushDwordPtrEspDispl(code, (sbyte)(numPushedArgs * 4)); // push dword ptr [esp+XXh] code.WriteCall(origCompileMethod); code.WriteBytes(0xC2, (ushort)(numPushedArgs * 4)); // retn 14h/18h // Our callMethod() code. 1st arg is the method to call. stdcall calling convention. int callMethodOffset = code.Size; code.WriteByte(0x58); // pop eax (ret addr) code.WriteByte(0x5A); // pop edx (method to call) if (compileMethodIsThisCall) code.WriteByte(0x59); // pop ecx (this ptr) code.WriteByte(0x50); // push eax (ret addr) code.WriteBytes(0xFF, 0xE2); // jmp edx // Returns token of method int getMethodTokenOffset = code.Size; code.WriteCall(returnMethodTokenInfo.ptr); code.WriteBytes(0xC2, (ushort)(IntPtr.Size * 2)); // Returns name of method int getMethodNameOffset = code.Size; code.WriteCall(returnNameOfMethodInfo.ptr); code.WriteBytes(0xC2, (ushort)(IntPtr.Size * 3)); ourCodeAddr = VirtualAlloc(IntPtr.Zero, new UIntPtr((ulong)code.Size), 0x00001000, PAGE_EXECUTE_READWRITE); IntPtr baseAddr = ourCodeAddr; ourCompileMethodInfo.ptrInDll = new IntPtr((byte*)baseAddr + compileMethodOffset); callMethod = new IntPtr((byte*)baseAddr + callMethodOffset); returnMethodTokenInfo.ptrInDll = new IntPtr((byte*)baseAddr + getMethodTokenOffset); returnNameOfMethodInfo.ptrInDll = new IntPtr((byte*)baseAddr + getMethodNameOffset); byte[] theCode = code.GetCode(baseAddr); Marshal.Copy(theCode, 0, baseAddr, theCode.Length); } // Writes push dword ptr [esp+displ] static void WritePushDwordPtrEspDispl(NativeCodeGenerator code, sbyte displ) { code.WriteBytes(0xFF, 0x74); code.WriteBytes(0x24, (byte)displ); } static IntPtr GetJitterDllHandle() { var hJitterDll = GetModuleHandle("mscorjit"); if (hJitterDll == IntPtr.Zero) hJitterDll = GetModuleHandle("clrjit"); if (hJitterDll == IntPtr.Zero) throw new ApplicationException("Could not get a handle to the jitter DLL"); return hJitterDll; } unsafe int TheCompileMethod(IntPtr jitter, IntPtr comp, IntPtr info, uint flags, IntPtr nativeEntry, IntPtr nativeSizeOfCode, out bool handled) { if (ourCompMem != IntPtr.Zero && comp == ourCompMem) { // We're decrypting methods var info2 = (CORINFO_METHOD_INFO*)info; ctx.dm.code = new byte[info2->ILCodeSize]; Marshal.Copy(info2->ILCode, ctx.dm.code, 0, ctx.dm.code.Length); ctx.dm.mhMaxStack = info2->maxStack; ctx.dm.mhCodeSize = info2->ILCodeSize; if ((ctx.dm.mhFlags & 8) != 0) ctx.dm.extraSections = ReadExtraSections((byte*)info2->ILCode + info2->ILCodeSize); UpdateFromMethodDefTableRow(); handled = true; return 0; } else { // We're not decrypting methods var info2 = (CORINFO_METHOD_INFO*)info; if (info2->scope != moduleToDecryptScope || decryptMethodsInfo.moduleCctorBytes == null || moduleCctorCodeRva == 0) { handled = false; return 0; } uint codeRva = (uint)((byte*)info2->ILCode - (byte*)hInstModule); if (moduleCctorCodeRva == codeRva) { fixed (byte* newIlCodeBytes = &decryptMethodsInfo.moduleCctorBytes[0]) { WriteCompileMethod(origCompileMethod); info2->ILCode = new IntPtr(newIlCodeBytes); info2->ILCodeSize = (uint)decryptMethodsInfo.moduleCctorBytes.Length; handled = true; return callMethodDelegate(origCompileMethod, jitter, comp, info, flags, nativeEntry, nativeSizeOfCode); } } } handled = false; return 0; } unsafe static byte* Align(byte* p, int alignment) { return (byte*)new IntPtr((long)((ulong)(p + alignment - 1) & ~(ulong)(alignment - 1))); } unsafe static byte[] ReadExtraSections(byte* p) { p = Align(p, 4); byte* startPos = p; p = ParseSection(p); int size = (int)(p - startPos); var sections = new byte[size]; Marshal.Copy(new IntPtr(startPos), sections, 0, sections.Length); return sections; } unsafe static byte* ParseSection(byte* p) { byte flags; do { p = Align(p, 4); flags = *p++; if ((flags & 1) == 0) throw new ApplicationException("Not an exception section"); if ((flags & 0x3E) != 0) throw new ApplicationException("Invalid bits set"); if ((flags & 0x40) != 0) { p--; int num = (int)(*(uint*)p >> 8) / 24; p += 4 + num * 24; } else { int num = *p++ / 12; p += 2 + num * 12; } } while ((flags & 0x80) != 0); return p; } unsafe void UpdateFromMethodDefTableRow() { uint methodIndex = ctx.dm.token - 0x06000001; byte* row = (byte*)methodDefTablePtr + methodIndex * methodDefTable.RowSize; ctx.dm.mdRVA = Read(row, methodDefTable.Columns[0]); ctx.dm.mdImplFlags = (ushort)Read(row, methodDefTable.Columns[1]); ctx.dm.mdFlags = (ushort)Read(row, methodDefTable.Columns[2]); ctx.dm.mdName = Read(row, methodDefTable.Columns[3]); ctx.dm.mdSignature = Read(row, methodDefTable.Columns[4]); ctx.dm.mdParamList = Read(row, methodDefTable.Columns[5]); } static unsafe uint Read(byte* row, ColumnInfo colInfo) { switch (colInfo.Size) { case 1: return *(row + colInfo.Offset); case 2: return *(ushort*)(row + colInfo.Offset); case 4: return *(uint*)(row + colInfo.Offset); default: throw new ApplicationException(string.Format("Unknown size: {0}", colInfo.Size)); } } string ReturnNameOfMethod2() { return ctx.method.Name.String; } int ReturnMethodToken2() { return ctx.method.MDToken.ToInt32(); } public DumpedMethods DecryptMethods() { if (!CanDecryptMethods()) throw new ApplicationException("Can't decrypt methods since compileMethod() isn't hooked yet"); InstallCompileMethod2(); var dumpedMethods = new DumpedMethods(); if (decryptMethodsInfo.methodsToDecrypt == null) { for (uint rid = 1; rid <= methodDefTable.Rows; rid++) dumpedMethods.Add(DecryptMethod(0x06000000 + rid)); } else { foreach (var token in decryptMethodsInfo.methodsToDecrypt) dumpedMethods.Add(DecryptMethod(token)); } return dumpedMethods; } unsafe DumpedMethod DecryptMethod(uint token) { if (!CanDecryptMethods()) throw new ApplicationException("Can't decrypt methods since compileMethod() isn't hooked yet"); ctx = new DecryptContext(); ctx.dm = new DumpedMethod(); ctx.dm.token = token; ctx.method = dnlibModule.ResolveMethod(MDToken.ToRID(token)); if (ctx.method == null) throw new ApplicationException(string.Format("Could not find method {0:X8}", token)); byte* mh = (byte*)hInstModule + (uint)ctx.method.RVA; byte* code; if (mh == (byte*)hInstModule) { ctx.dm.mhMaxStack = 0; ctx.dm.mhCodeSize = 0; ctx.dm.mhFlags = 0; ctx.dm.mhLocalVarSigTok = 0; code = null; } else if ((*mh & 3) == 2) { uint headerSize = 1; ctx.dm.mhMaxStack = 8; ctx.dm.mhCodeSize = (uint)(*mh >> 2); ctx.dm.mhFlags = 2; ctx.dm.mhLocalVarSigTok = 0; code = mh + headerSize; } else { uint headerSize = (uint)((mh[1] >> 4) * 4); ctx.dm.mhMaxStack = *(ushort*)(mh + 2); ctx.dm.mhCodeSize = *(uint*)(mh + 4); ctx.dm.mhFlags = *(ushort*)mh; ctx.dm.mhLocalVarSigTok = *(uint*)(mh + 8); code = mh + headerSize; } CORINFO_METHOD_INFO info = default(CORINFO_METHOD_INFO); info.ILCode = new IntPtr(code); info.ILCodeSize = ctx.dm.mhCodeSize; info.maxStack = ctx.dm.mhMaxStack; info.scope = moduleToDecryptScope; InitializeOurComp(); if (code == null) { ctx.dm.code = new byte[0]; UpdateFromMethodDefTableRow(); } else callMethodDelegate(*(IntPtr*)jitterVtbl, jitterInstance, ourCompMem, new IntPtr(&info), 0, new IntPtr(0x12345678), new IntPtr(0x3ABCDEF0)); var dm = ctx.dm; ctx = null; return dm; } unsafe void InitializeOurComp() { const int numIndexes = 15; if (ourCompMem == IntPtr.Zero) ourCompMem = Marshal.AllocHGlobal(numIndexes * IntPtr.Size); if (ourCompMem == IntPtr.Zero) throw new ApplicationException("Could not allocate memory"); IntPtr* mem = (IntPtr*)ourCompMem; for (int i = 0; i < numIndexes; i++) mem[i] = IntPtr.Zero; mem[1] = new IntPtr(mem + 2); mem[3] = new IntPtr(IntPtr.Size * 5); mem[5] = new IntPtr(IntPtr.Size * 7); mem[6] = new IntPtr(mem + 7); mem[7] = returnNameOfMethodInfo.ptrInDll; mem[8] = new IntPtr(mem); mem[13] = returnMethodTokenInfo.ptrInDll; // .NET 2.0 mem[14] = returnMethodTokenInfo.ptrInDll; // .NET 4.0 } bool hasInstalledCompileMethod2 = false; unsafe void InstallCompileMethod2() { if (hasInstalledCompileMethod2) return; if (!PatchCM(*(IntPtr*)jitterVtbl, origCompileMethod, ourCompileMethodInfo.ptrInDll)) throw new ApplicationException("Couldn't patch compileMethod"); hasInstalledCompileMethod2 = true; return; } static IntPtr GetModuleHandle(IntPtr addr) { IntPtr hModule; if (!GetModuleHandleEx(4, addr, out hModule)) throw new ApplicationException("GetModuleHandleEx() failed"); return hModule; } class PatchInfo { public int RVA; public byte[] Data; public byte[] Orig; public PatchInfo(int rva, byte[] data, byte[] orig) { this.RVA = rva; this.Data = data; this.Orig = orig; } } static readonly PatchInfo[] patches = new PatchInfo[] { new PatchInfo(0x000110A5, new byte[] { 0x33, 0xC0, 0xC2, 0x04, 0x00 }, new byte[] { 0xE9, 0x36, 0x3A, 0x00, 0x00 }), new PatchInfo(0x000110AF, new byte[] { 0x33, 0xC0, 0xC2, 0x04, 0x00 }, new byte[] { 0xE9, 0x4C, 0x3C, 0x00, 0x00 }), new PatchInfo(0x000110AA, new byte[] { 0x33, 0xC0, 0xC2, 0x04, 0x00 }, new byte[] { 0xE9, 0xF1, 0x3A, 0x00, 0x00 }), new PatchInfo(0x00011019, new byte[] { 0x33, 0xC0, 0xC2, 0x04, 0x00 }, new byte[] { 0xE9, 0x12, 0x4B, 0x00, 0x00 }), new PatchInfo(0x00011019, new byte[] { 0x33, 0xC0, 0xC2, 0x04, 0x00 }, new byte[] { 0xE9, 0x02, 0x4B, 0x00, 0x00 }), new PatchInfo(0x00011019, new byte[] { 0x33, 0xC0, 0xC2, 0x04, 0x00 }, new byte[] { 0xE9, 0xA2, 0x4B, 0x00, 0x00 }), }; static unsafe bool PatchCM(IntPtr addr, IntPtr origValue, IntPtr newValue) { var baseAddr = GetModuleHandle(addr); IntPtr patchAddr; using (var peImage = new PEImage(baseAddr)) patchAddr = FindCMAddress(peImage, baseAddr, origValue); if (patchAddr == IntPtr.Zero) return false; *(IntPtr*)patchAddr = newValue; PatchRT(baseAddr); return true; } [HandleProcessCorruptedStateExceptions, SecurityCritical] // Req'd on .NET 4.0 static unsafe bool PatchRT(IntPtr baseAddr) { foreach (var info in patches) { try { var addr = new IntPtr(baseAddr.ToInt64() + info.RVA); var data = new byte[info.Orig.Length]; Marshal.Copy(addr, data, 0, data.Length); if (!Equals(data, info.Orig)) continue; uint oldProtect; if (!VirtualProtect(addr, info.Data.Length, PAGE_EXECUTE_READWRITE, out oldProtect)) throw new ApplicationException("Could not enable write access"); Marshal.Copy(info.Data, 0, addr, info.Data.Length); VirtualProtect(addr, info.Data.Length, oldProtect, out oldProtect); return true; } catch { } } return false; } static bool Equals(byte[] a, byte[] b) { if (a == b) return true; if (a == null || b == null) return false; if (a.Length != b.Length) return false; for (int i = 0; i < a.Length; i++) { if (a[i] != b[i]) return false; } return true; } [HandleProcessCorruptedStateExceptions, SecurityCritical] // Req'd on .NET 4.0 static unsafe IntPtr FindCMAddress(PEImage peImage, IntPtr baseAddr, IntPtr origValue) { int offset = Environment.Version.Major == 2 ? 0x10 : 0x28; foreach (var section in peImage.ImageSectionHeaders) { const uint RW = 0x80000000 | 0x40000000; if ((section.Characteristics & RW) != RW) continue; byte* p = (byte*)baseAddr + (uint)section.VirtualAddress + ((section.VirtualSize + IntPtr.Size - 1) & ~(IntPtr.Size - 1)) - IntPtr.Size; for (; p >= (byte*)baseAddr; p -= IntPtr.Size) { try { byte* p2 = (byte*)*(IntPtr**)p; if ((ulong)p2 >= 0x10000) { if (*(IntPtr*)(p2 + 0x74) == origValue) return new IntPtr(p2 + 0x74); if (*(IntPtr*)(p2 + 0x78) == origValue) return new IntPtr(p2 + 0x78); } } catch { } try { byte* p2 = (byte*)*(IntPtr**)p; if ((ulong)p2 >= 0x10000) { p2 += offset; if (*(IntPtr*)p2 == origValue) return new IntPtr(p2); } } catch { } try { if (*(IntPtr*)p == origValue) return new IntPtr(p); } catch { } } } return IntPtr.Zero; } } }