0
0
mirror of https://github.com/sp-tarkov/assembly-tool.git synced 2025-02-12 17:30:43 -05:00
assembly-tool/de4dot/de4dot.mdecrypt/DynamicMethodsDecrypter.cs
Archangel 77a24319b1 Update de4dot to use dnlib 4.4.0 and x64
Adds the source code used for this modification, this de4dot source code has been cleaned of any things not needed for deobfuscating the tarkov assembly

Co-authored-by: 静穏靄 <170472707+seionmoya@users.noreply.github.com>
2024-12-30 16:01:39 +01:00

680 lines
22 KiB
C#

/*
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 <http://www.gnu.org/licenses/>.
*/
using System;
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 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($"m_ptr is an invalid type: {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($"Could not get field {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) {
if (!VirtualProtect(jitterVtbl, IntPtr.Size, PAGE_EXECUTE_READWRITE, out uint 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() =>
*(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);
var 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) =>
(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($"Unknown size: {colInfo.Size}");
}
}
string ReturnNameOfMethod2() => ctx.method.Name.String;
int ReturnMethodToken2() => 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($"Could not find method {token:X8}");
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;
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");
var 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) {
if (!GetModuleHandleEx(4, addr, out var 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) {
RVA = rva;
Data = data;
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;
if (!VirtualProtect(addr, info.Data.Length, PAGE_EXECUTE_READWRITE, out uint 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;
}
}
}