de4dot-cex/de4dot.mdecrypt/DynamicMethodsDecrypter.cs
2014-03-27 11:41:18 +01:00

699 lines
23 KiB
C#

/*
Copyright (C) 2011-2014 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 <http://www.gnu.org/licenses/>.
*/
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<D> {
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<CompileMethod> ourCompileMethodInfo = new FuncPtrInfo<CompileMethod>();
FuncPtrInfo<ReturnMethodToken> returnMethodTokenInfo = new FuncPtrInfo<ReturnMethodToken>();
FuncPtrInfo<ReturnNameOfMethod> returnNameOfMethodInfo = new FuncPtrInfo<ReturnNameOfMethod>();
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;
}
}
}