diff --git a/de4dot.code/de4dot.code.csproj b/de4dot.code/de4dot.code.csproj index 7c946ab2..f97e575e 100644 --- a/de4dot.code/de4dot.code.csproj +++ b/de4dot.code/de4dot.code.csproj @@ -160,6 +160,11 @@ + + + + + @@ -211,7 +216,7 @@ - + diff --git a/de4dot.code/deobfuscators/ILProtector/DecryptedMethodInfo.cs b/de4dot.code/deobfuscators/ILProtector/DecryptedMethodInfo.cs new file mode 100644 index 00000000..23080df2 --- /dev/null +++ b/de4dot.code/deobfuscators/ILProtector/DecryptedMethodInfo.cs @@ -0,0 +1,42 @@ +/* + Copyright (C) 2011-2013 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; + +namespace de4dot.code.deobfuscators.ILProtector { + [Serializable] + class DecryptedMethodInfo { + public int id; + public byte[] data; + + public DecryptedMethodInfo(int id, int size) { + this.id = id; + this.data = new byte[size]; + } + + public DecryptedMethodInfo(int id, byte[] data) { + this.id = id; + this.data = data; + } + + public override string ToString() { + return string.Format("ID: {0}, Size: 0x{1:X}", id, data.Length); + } + } +} diff --git a/de4dot.code/deobfuscators/ILProtector/Deobfuscator.cs b/de4dot.code/deobfuscators/ILProtector/Deobfuscator.cs index a0c4fea2..0594e3da 100644 --- a/de4dot.code/deobfuscators/ILProtector/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/ILProtector/Deobfuscator.cs @@ -56,7 +56,8 @@ namespace de4dot.code.deobfuscators.ILProtector { string obfuscatorName = DeobfuscatorInfo.THE_NAME; MainType mainType; - MethodsDecrypter methodsDecrypter; + StaticMethodsDecrypter staticMethodsDecrypter; + DynamicMethodsRestorer dynamicMethodsRestorer; internal class Options : OptionsBase { } @@ -85,33 +86,45 @@ namespace de4dot.code.deobfuscators.ILProtector { protected override void ScanForObfuscator() { mainType = new MainType(module); mainType.Find(); - methodsDecrypter = new MethodsDecrypter(module, mainType); - if (mainType.Detected) - methodsDecrypter.Find(); - if (mainType.Detected && methodsDecrypter.Detected && methodsDecrypter.Version != null) - obfuscatorName += " " + methodsDecrypter.Version; + staticMethodsDecrypter = new StaticMethodsDecrypter(module, mainType); + if (mainType.Detected) + staticMethodsDecrypter.Find(); + + if (mainType.Detected && !staticMethodsDecrypter.Detected) + dynamicMethodsRestorer = new DynamicMethodsRestorer(module, mainType); + + if (mainType.Detected && staticMethodsDecrypter.Detected && staticMethodsDecrypter.Version != null) + obfuscatorName += " " + staticMethodsDecrypter.Version; } public override void DeobfuscateBegin() { base.DeobfuscateBegin(); if (mainType.Detected) { - if (methodsDecrypter.Detected) { - methodsDecrypter.Decrypt(); - AddTypesToBeRemoved(methodsDecrypter.DelegateTypes, "Obfuscator method delegate type"); - AddResourceToBeRemoved(methodsDecrypter.Resource, "Encrypted methods resource"); - AddTypeToBeRemoved(mainType.InvokerDelegate, "Invoker delegate type"); - AddFieldToBeRemoved(mainType.InvokerInstanceField, "Invoker delegate instance field"); - foreach (var pm in mainType.ProtectMethods) - AddMethodToBeRemoved(pm, "Obfuscator 'Protect' init method"); - mainType.CleanUp(); + if (staticMethodsDecrypter.Detected) { + staticMethodsDecrypter.Decrypt(); + RemoveObfuscatorJunk(staticMethodsDecrypter); + } + else if (dynamicMethodsRestorer != null) { + dynamicMethodsRestorer.Decrypt(); + RemoveObfuscatorJunk(dynamicMethodsRestorer); } else Logger.w("New ILProtector version. Can't decrypt methods (yet)"); } } + void RemoveObfuscatorJunk(MethodsDecrypterBase methodsDecrypter) { + AddTypesToBeRemoved(methodsDecrypter.DelegateTypes, "Obfuscator method delegate type"); + AddResourceToBeRemoved(methodsDecrypter.Resource, "Encrypted methods resource"); + AddTypeToBeRemoved(mainType.InvokerDelegate, "Invoker delegate type"); + AddFieldToBeRemoved(mainType.InvokerInstanceField, "Invoker delegate instance field"); + foreach (var pm in mainType.ProtectMethods) + AddMethodToBeRemoved(pm, "Obfuscator 'Protect' init method"); + mainType.CleanUp(); + } + public override IEnumerable GetStringDecrypterMethods() { return new List(); } diff --git a/de4dot.code/deobfuscators/ILProtector/DynamicMethodsDecrypter.cs b/de4dot.code/deobfuscators/ILProtector/DynamicMethodsDecrypter.cs new file mode 100644 index 00000000..dcae0f9f --- /dev/null +++ b/de4dot.code/deobfuscators/ILProtector/DynamicMethodsDecrypter.cs @@ -0,0 +1,490 @@ +/* + Copyright (C) 2011-2013 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.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; +using System.Security; +using dnlib.DotNet; +using de4dot.blocks; + +namespace de4dot.code.deobfuscators.ILProtector { + sealed class DynamicMethodsDecrypter : IDisposable { + ModuleDefMD module; + Module reflectionModule; + Module reflectionProtectModule; + TypeDef protectMainType; + Type reflectionProtectMainType; + FieldInfo invokerFieldInfo; + ModuleDefMD moduleProtect; + IDecrypter decrypter; + + interface IDecrypter { + byte[] Decrypt(int methodId, uint rid); + } + + abstract class DecrypterBase : IDecrypter { + protected readonly DynamicMethodsDecrypter dmd; + protected readonly int appDomainId; + protected readonly int asmHashCode; + + class PatchData { + public int RVA { get; set; } + public byte[] Data { get; set; } + + public PatchData() { + } + + public PatchData(int rva, byte[] data) { + this.RVA = rva; + this.Data = data; + } + } + + class PatchInfo { + public int RvaDecryptMethod { get; set; } + public List PatchData { get; set; } + + public PatchInfo() { + } + + public PatchInfo(int rvaDecryptMethod, PatchData patchData) { + this.RvaDecryptMethod = rvaDecryptMethod; + this.PatchData = new List { patchData }; + } + } + + static readonly byte[] nops2 = new byte[] { 0x90, 0x90 }; + static readonly byte[] nops6 = new byte[] { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 }; + static readonly Dictionary patchInfos32 = new Dictionary { + { new Version(2, 0, 8, 0), new PatchInfo(0x00020B20, new PatchData(0x00005733, nops2)) }, + { new Version(2, 0, 8, 5), new PatchInfo(0x000221A0, new PatchData(0x00005742, nops2)) }, + { new Version(2, 0, 9, 0), new PatchInfo(0x00023360, new PatchData(0x000056F2, nops6)) }, + { new Version(2, 0, 10, 0), new PatchInfo(0x00023B30, new PatchData(0x00005B12, nops6)) }, + { new Version(2, 0, 11, 0), new PatchInfo(0x000207C0, new PatchData(0x00018432, nops6)) }, + }; + static readonly Dictionary patchInfos64 = new Dictionary { + { new Version(2, 0, 8, 0), new PatchInfo(0x00026090, new PatchData(0x00005E0C, nops6)) }, + { new Version(2, 0, 8, 5), new PatchInfo(0x000273D0, new PatchData(0x000060CA, nops6)) }, + { new Version(2, 0, 9, 0), new PatchInfo(0x00028B00, new PatchData(0x00005F70, nops6)) }, + { new Version(2, 0, 10, 0), new PatchInfo(0x00029630, new PatchData(0x00006510, nops6)) }, + { new Version(2, 0, 11, 0), new PatchInfo(0x000257C0, new PatchData(0x0001C9A0, nops6)) }, + }; + + [DllImport("kernel32")] + static extern bool VirtualProtect(IntPtr addr, int size, uint newProtect, out uint oldProtect); + const uint PAGE_EXECUTE_READWRITE = 0x40; + + public DecrypterBase(DynamicMethodsDecrypter dmd) { + this.dmd = dmd; + this.appDomainId = AppDomain.CurrentDomain.Id; + this.asmHashCode = dmd.reflectionModule.Assembly.GetHashCode(); + } + + protected IntPtr GetDelegateAddress(FieldDef delegateField) { + FieldInfo delegateFieldInfo = dmd.reflectionProtectModule.ResolveField(0x04000000 + (int)delegateField.Rid); + object mainTypeInst = ((Delegate)dmd.invokerFieldInfo.GetValue(null)).Target; + return GetNativeAddressOfDelegate((Delegate)delegateFieldInfo.GetValue(mainTypeInst)); + } + + static IntPtr GetNativeAddressOfDelegate(Delegate del) { + var field = typeof(Delegate).GetField("_methodPtrAux", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (field == null) + return IntPtr.Zero; + + return (IntPtr)field.GetValue(del); + } + + protected void PatchRuntime(IntPtr decryptAddr) { + if (!PatchRuntimeInternal(decryptAddr)) + throw new ApplicationException("Probably a new version. Could not patch runtime."); + } + + bool PatchRuntimeInternal(IntPtr decryptAddr) { + var patchInfos = IntPtr.Size == 4 ? patchInfos32 : patchInfos64; + var protectVersion = dmd.reflectionProtectModule.Assembly.GetName().Version; + PatchInfo info; + if (!patchInfos.TryGetValue(protectVersion, out info)) + return false; + return PatchRuntime(decryptAddr, info); + } + + [HandleProcessCorruptedStateExceptions, SecurityCritical] // Req'd on .NET 4.0 + static bool PatchRuntime(IntPtr decryptAddr, PatchInfo info) { + try { + IntPtr baseAddr = new IntPtr(decryptAddr.ToInt64() - info.RvaDecryptMethod); + if ((baseAddr.ToInt64() & 0xFFFF) != 0) + return false; + + if (Marshal.ReadInt16(baseAddr) != 0x5A4D) + return false; + + foreach (var patchData in info.PatchData) { + var patchAddr = new IntPtr(baseAddr.ToInt64() + patchData.RVA); + uint oldProtect; + if (!VirtualProtect(patchAddr, patchData.Data.Length, PAGE_EXECUTE_READWRITE, out oldProtect)) + return false; + Marshal.Copy(patchData.Data, 0, patchAddr, patchData.Data.Length); + VirtualProtect(patchAddr, patchData.Data.Length, oldProtect, out oldProtect); + } + + return true; + } + catch { + } + + return false; + } + + public abstract byte[] Decrypt(int methodId, uint rid); + } + + // 1.0.7.1 - 1.0.8.0 + class DecrypterV1_0_7_1 : DecrypterBase { + DecryptMethod decryptMethod; + + unsafe delegate bool DecryptMethod(int appDomainId, int asmHashCode, int methodId, out byte* pMethodCode, out int methodSize); + + public DecrypterV1_0_7_1(DynamicMethodsDecrypter dmd, FieldDef delegateField) + : base(dmd) { + IntPtr addr = GetDelegateAddress(delegateField); + decryptMethod = (DecryptMethod)Marshal.GetDelegateForFunctionPointer(addr, typeof(DecryptMethod)); + } + + public unsafe override byte[] Decrypt(int methodId, uint rid) { + byte* pMethodCode; + int methodSize; + if (!decryptMethod(appDomainId, asmHashCode, methodId, out pMethodCode, out methodSize)) + return null; + byte[] methodData = new byte[methodSize]; + Marshal.Copy(new IntPtr(pMethodCode), methodData, 0, methodData.Length); + return methodData; + } + } + + // 2.0.0.0 - 2.0.7.6 + class DecrypterV2_0_0_0 : DecrypterBase { + DecryptMethod decryptMethod; + + unsafe delegate bool DecryptMethod(int clrMajorVersion, int appDomainId, int asmHashCode, int methodId, out byte* pMethodCode, out int methodSize); + + public DecrypterV2_0_0_0(DynamicMethodsDecrypter dmd, FieldDef delegateField) + : base(dmd) { + IntPtr addr = GetDelegateAddress(delegateField); + decryptMethod = (DecryptMethod)Marshal.GetDelegateForFunctionPointer(addr, typeof(DecryptMethod)); + } + + public unsafe override byte[] Decrypt(int methodId, uint rid) { + byte* pMethodCode; + int methodSize; + if (!decryptMethod(Environment.Version.Major, appDomainId, asmHashCode, methodId, out pMethodCode, out methodSize)) + return null; + byte[] methodData = new byte[methodSize]; + Marshal.Copy(new IntPtr(pMethodCode), methodData, 0, methodData.Length); + return methodData; + } + } + + // 2.0.8.0 + class DecrypterV2_0_8_0 : DecrypterBase { + DecryptMethod decryptMethod; + byte[] decryptedData; + + delegate bool DecryptMethod(int clrMajorVersion, int appDomainId, int asmHashCode, int methodId, [MarshalAs(UnmanagedType.FunctionPtr)] DecryptCallback decryptCallback, [MarshalAs(UnmanagedType.Interface)] out Delegate createdDelegate); + unsafe delegate bool DecryptCallback(byte* pMethodCode, int methodSize, [MarshalAs(UnmanagedType.Interface)] ref Delegate createdDelegate); + + public DecrypterV2_0_8_0(DynamicMethodsDecrypter dmd, FieldDef delegateField) + : base(dmd) { + IntPtr addr = GetDelegateAddress(delegateField); + decryptMethod = (DecryptMethod)Marshal.GetDelegateForFunctionPointer(addr, typeof(DecryptMethod)); + PatchRuntime(addr); + } + + public unsafe override byte[] Decrypt(int methodId, uint rid) { + Delegate createdDelegate; + if (!decryptMethod(Environment.Version.Major, appDomainId, asmHashCode, methodId, MyDecryptCallback, out createdDelegate)) + return null; + return decryptedData; + } + + unsafe bool MyDecryptCallback(byte* pMethodCode, int methodSize, ref Delegate createdDelegate) { + decryptedData = new byte[methodSize]; + Marshal.Copy(new IntPtr(pMethodCode), decryptedData, 0, decryptedData.Length); + return true; + } + } + + // 2.0.8.5 + class DecrypterV2_0_8_5 : DecrypterBase { + DecryptMethod decryptMethod; + byte[] decryptedData; + bool decryptReturnValue; + + delegate bool DecryptMethod(int clrMajorVersion, int appDomainId, int asmHashCode, int methodId, [MarshalAs(UnmanagedType.Interface)] StackTrace stackTrace, [MarshalAs(UnmanagedType.FunctionPtr)] DecryptCallback decryptCallback, [MarshalAs(UnmanagedType.Interface)] out Delegate createdDelegate); + unsafe delegate bool DecryptCallback(byte* pMethodCode, int methodSize, [MarshalAs(UnmanagedType.Interface)] ref Delegate createdDelegate); + + public DecrypterV2_0_8_5(DynamicMethodsDecrypter dmd, FieldDef delegateField) + : base(dmd) { + IntPtr addr = GetDelegateAddress(delegateField); + decryptMethod = (DecryptMethod)Marshal.GetDelegateForFunctionPointer(addr, typeof(DecryptMethod)); + PatchRuntime(addr); + } + + public unsafe override byte[] Decrypt(int methodId, uint rid) { + Delegate createdDelegate; + decryptReturnValue = false; + if (!decryptMethod(Environment.Version.Major, appDomainId, asmHashCode, methodId, new StackTrace(), MyDecryptCallback, out createdDelegate) && + !decryptReturnValue) + return null; + return decryptedData; + } + + unsafe bool MyDecryptCallback(byte* pMethodCode, int methodSize, ref Delegate createdDelegate) { + createdDelegate = new DecryptCallback(MyDecryptCallback); + decryptedData = new byte[methodSize]; + Marshal.Copy(new IntPtr(pMethodCode), decryptedData, 0, decryptedData.Length); + return decryptReturnValue = true; + } + } + + // 2.0.9.0 - 2.0.11.0 + class DecrypterV2_0_9_0 : DecrypterBase { + DecryptMethod decryptMethod; + byte[] decryptedData; + + delegate bool DecryptMethod(int clrMajorVersion, int appDomainId, int asmHashCode, int methodId, [MarshalAs(UnmanagedType.Interface)] StackTrace stackTrace, [MarshalAs(UnmanagedType.FunctionPtr)] DecryptCallback decryptCallback); + unsafe delegate bool DecryptCallback(byte* pMethodCode, int methodSize, int methodId); + + public DecrypterV2_0_9_0(DynamicMethodsDecrypter dmd, FieldDef delegateField) + : base(dmd) { + IntPtr addr = GetDelegateAddress(delegateField); + decryptMethod = (DecryptMethod)Marshal.GetDelegateForFunctionPointer(addr, typeof(DecryptMethod)); + PatchRuntime(addr); + } + + public unsafe override byte[] Decrypt(int methodId, uint rid) { + var encMethod = this.dmd.reflectionModule.ResolveMethod(0x06000000 + (int)rid); + var stackTrace = StackTracePatcher.WriteStackFrame(new StackTrace(), 1, encMethod); + if (!decryptMethod(Environment.Version.Major, appDomainId, asmHashCode, methodId, stackTrace, MyDecryptCallback)) + return null; + return decryptedData; + } + + unsafe bool MyDecryptCallback(byte* pMethodCode, int methodSize, int methodId) { + decryptedData = new byte[methodSize]; + Marshal.Copy(new IntPtr(pMethodCode), decryptedData, 0, decryptedData.Length); + return true; + } + } + + public DynamicMethodsDecrypter(ModuleDefMD module, Module reflectionModule) { + this.module = module; + this.reflectionModule = reflectionModule; + } + + public void Initialize() { + RuntimeHelpers.RunModuleConstructor(reflectionModule.ModuleHandle); + var reflectionProtectAssembly = GetProtectAssembly(); + if (reflectionProtectAssembly == null) + throw new ApplicationException("Could not find 'Protect' assembly"); + reflectionProtectModule = reflectionProtectAssembly.ManifestModule; + moduleProtect = ModuleDefMD.Load(reflectionProtectModule); + protectMainType = FindMainType(moduleProtect); + if (protectMainType == null) + throw new ApplicationException("Could not find Protect.MainType"); + var invokerField = FindInvokerField(module); + + reflectionProtectMainType = reflectionProtectModule.ResolveType(0x02000000 + (int)protectMainType.Rid); + invokerFieldInfo = reflectionModule.ResolveField(0x04000000 + (int)invokerField.Rid); + + decrypter = CreateDecrypter(); + if (decrypter == null) + throw new ApplicationException("Probably a new version. Could not create a decrypter."); + } + + public DecryptedMethodInfo Decrypt(int methodId, uint rid) { + byte[] methodData = decrypter.Decrypt(methodId, rid); + if (methodData == null) + throw new ApplicationException(string.Format("Probably a new version. Could not decrypt method. ID:{0}, RID:{1:X4}", methodId, rid)); + return new DecryptedMethodInfo(methodId, methodData); + } + + public void Dispose() { + if (moduleProtect != null) + moduleProtect.Dispose(); + moduleProtect = null; + } + + IDecrypter CreateDecrypter() { + return CreateDecrypterV1_0_7_1() ?? + CreateDecrypterV2_0_0_0() ?? + CreateDecrypterV2_0_8_0() ?? + CreateDecrypterV2_0_8_5() ?? + CreateDecrypterV2_0_9_0(); + } + + IDecrypter CreateDecrypterV1_0_7_1() { + var delegateField = FindDelegateFieldV1_0_7_1(protectMainType); + if (delegateField == null) + return null; + + return new DecrypterV1_0_7_1(this, delegateField); + } + + IDecrypter CreateDecrypterV2_0_0_0() { + var delegateField = FindDelegateFieldV2_0_0_0(protectMainType); + if (delegateField == null) + return null; + + return new DecrypterV2_0_0_0(this, delegateField); + } + + IDecrypter CreateDecrypterV2_0_8_0() { + var delegateField = FindDelegateFieldV2_0_8_0(protectMainType, FindDecryptCallbackV2_0_8_0(protectMainType)); + if (delegateField == null) + return null; + + return new DecrypterV2_0_8_0(this, delegateField); + } + + IDecrypter CreateDecrypterV2_0_8_5() { + var delegateField = FindDelegateFieldV2_0_8_5(protectMainType, FindDecryptCallbackV2_0_8_0(protectMainType)); + if (delegateField == null) + return null; + + return new DecrypterV2_0_8_5(this, delegateField); + } + + IDecrypter CreateDecrypterV2_0_9_0() { + var delegateField = FindDelegateFieldV2_0_9_0(protectMainType, FindDecryptCallbackV2_0_9_0(protectMainType)); + if (delegateField == null) + return null; + + return new DecrypterV2_0_9_0(this, delegateField); + } + + static readonly byte[] ilpPublicKeyToken = new byte[8] { 0x20, 0x12, 0xD3, 0xC0, 0x55, 0x1F, 0xE0, 0x3D }; + static Assembly GetProtectAssembly() { + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { + if (!string.IsNullOrEmpty(asm.Location)) + continue; + var asmName = asm.GetName(); + if (asmName.Name != "Protect") + continue; + if (!Compare(asmName.GetPublicKeyToken(), ilpPublicKeyToken)) + continue; + + return asm; + } + return null; + } + + static bool Compare(byte[] a, byte[] b) { + if (a == null && b == null) + 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; + } + + static TypeDef FindMainType(ModuleDef module) { + foreach (var type in module.Types) { + if (type.FindMethod("Finalize") != null) + return type; + } + return null; + } + + static FieldDef FindInvokerField(ModuleDef module) { + return FindDelegateField(module.GlobalType, "System.Delegate", "(System.Int32)"); + } + + static FieldDef FindDelegateFieldV1_0_7_1(TypeDef mainType) { + return FindDelegateField(mainType, "System.Boolean", "(System.Int32,System.Int32,System.Int32,System.Byte*&,System.Int32&)"); + } + + static FieldDef FindDelegateFieldV2_0_0_0(TypeDef mainType) { + return FindDelegateField(mainType, "System.Boolean", "(System.Int32,System.Int32,System.Int32,System.Int32,System.Byte*&,System.Int32&)"); + } + + static FieldDef FindDecryptCallbackV2_0_8_0(TypeDef mainType) { + return FindDelegateField(mainType, "System.Boolean", "(System.Byte*,System.Int32,System.Delegate&)"); + } + + static FieldDef FindDecryptCallbackV2_0_9_0(TypeDef mainType) { + return FindDelegateField(mainType, "System.Boolean", "(System.Byte*,System.Int32,System.Int32)"); + } + + static FieldDef FindDelegateFieldV2_0_8_0(TypeDef mainType, FieldDef decryptCallbackField) { + if (decryptCallbackField == null) + return null; + var type = decryptCallbackField.FieldSig.GetFieldType().ToTypeDefOrRef() as TypeDef; + if (type == null) + return null; + return FindDelegateField(mainType, "System.Boolean", string.Format("(System.Int32,System.Int32,System.Int32,System.Int32,{0},System.Delegate&)", type.FullName)); + } + + static FieldDef FindDelegateFieldV2_0_8_5(TypeDef mainType, FieldDef decryptCallbackField) { + if (decryptCallbackField == null) + return null; + var type = decryptCallbackField.FieldSig.GetFieldType().ToTypeDefOrRef() as TypeDef; + if (type == null) + return null; + return FindDelegateField(mainType, "System.Boolean", string.Format("(System.Int32,System.Int32,System.Int32,System.Int32,System.Diagnostics.StackTrace,{0},System.Delegate&)", type.FullName)); + } + + static FieldDef FindDelegateFieldV2_0_9_0(TypeDef mainType, FieldDef decryptCallbackField) { + if (decryptCallbackField == null) + return null; + var type = decryptCallbackField.FieldSig.GetFieldType().ToTypeDefOrRef() as TypeDef; + if (type == null) + return null; + return FindDelegateField(mainType, "System.Boolean", string.Format("(System.Int32,System.Int32,System.Int32,System.Int32,System.Diagnostics.StackTrace,{0})", type.FullName)); + } + + static FieldDef FindDelegateField(TypeDef mainType, string returnType, string parameters) { + foreach (var field in mainType.Fields) { + var fieldSig = field.FieldSig; + if (fieldSig == null) + continue; + var fieldType = fieldSig.Type.ToTypeDefOrRef() as TypeDef; + if (fieldType == null) + continue; + if (fieldType.BaseType != null && fieldType.BaseType.FullName != "System.MulticastDelegate") + continue; + var invokeMethod = fieldType.FindMethod("Invoke"); + if (!DotNetUtils.IsMethod(invokeMethod, returnType, parameters)) + continue; + + return field; + } + return null; + } + } +} diff --git a/de4dot.code/deobfuscators/ILProtector/DynamicMethodsDecrypterService.cs b/de4dot.code/deobfuscators/ILProtector/DynamicMethodsDecrypterService.cs new file mode 100644 index 00000000..ead8f085 --- /dev/null +++ b/de4dot.code/deobfuscators/ILProtector/DynamicMethodsDecrypterService.cs @@ -0,0 +1,72 @@ +/* + Copyright (C) 2011-2013 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.Reflection; +using System.Collections.Generic; +using dnlib.DotNet; +using AssemblyData; + +namespace de4dot.code.deobfuscators.ILProtector { + sealed class DynamicMethodsDecrypterService : IUserGenericService { + public const int MSG_DECRYPT_METHODS = 0; + + Module reflObfModule; + ModuleDefMD obfModule; + + [CreateUserGenericService] + public static IUserGenericService Create() { + return new DynamicMethodsDecrypterService(); + } + + public void Dispose() { + if (obfModule != null) + obfModule.Dispose(); + obfModule = null; + } + + public void AssemblyLoaded(Assembly assembly) { + this.reflObfModule = assembly.ManifestModule; + this.obfModule = ModuleDefMD.Load(reflObfModule); + } + + public object HandleMessage(int msg, object[] args) { + switch (msg) { + case MSG_DECRYPT_METHODS: + return DecryptMethods(args[0] as IList); + + default: + throw new ApplicationException(string.Format("Invalid msg: {0:X8}", msg)); + } + } + + IList DecryptMethods(IList methodIds) { + using (var decrypter = new DynamicMethodsDecrypter(obfModule, reflObfModule)) { + decrypter.Initialize(); + + var infos = new List(); + + for (int i = 0; i < methodIds.Count; i += 2) + infos.Add(decrypter.Decrypt(methodIds[i], (uint)methodIds[i + 1])); + + return infos; + } + } + } +} diff --git a/de4dot.code/deobfuscators/ILProtector/DynamicMethodsRestorer.cs b/de4dot.code/deobfuscators/ILProtector/DynamicMethodsRestorer.cs new file mode 100644 index 00000000..ecbff882 --- /dev/null +++ b/de4dot.code/deobfuscators/ILProtector/DynamicMethodsRestorer.cs @@ -0,0 +1,69 @@ +/* + Copyright (C) 2011-2013 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.Collections.Generic; +using dnlib.DotNet; +using AssemblyData; +using de4dot.code.AssemblyClient; + +namespace de4dot.code.deobfuscators.ILProtector { + // Calls class to dynamically decrypt methods, then restores them. + class DynamicMethodsRestorer : MethodsDecrypterBase { + public DynamicMethodsRestorer(ModuleDefMD module, MainType mainType) + : base(module, mainType) { + } + + protected override void DecryptInternal() { + IList decryptedData; + var serverVersion = NewProcessAssemblyClientFactory.GetServerClrVersion(module); + using (var client = new NewProcessAssemblyClientFactory(serverVersion).Create(AssemblyServiceType.Generic)) { + client.Connect(); + client.WaitConnected(); + + client.GenericService.LoadUserService(typeof(DynamicMethodsDecrypterService), null); + client.GenericService.LoadAssembly(module.Location); + decryptedData = client.GenericService.SendMessage(DynamicMethodsDecrypterService.MSG_DECRYPT_METHODS, new object[] { GetMethodIds() }) as IList; + } + + if (decryptedData == null) + throw new ApplicationException("Unknown return value from dynamic methods decrypter service"); + + foreach (var info in decryptedData) + methodInfos[info.id] = info; + } + + IList GetMethodIds() { + var ids = new List(); + + foreach (var type in module.GetTypes()) { + foreach (var method in type.Methods) { + int? id = GetMethodId(method); + if (id == null) + continue; + + ids.Add(id.Value); + ids.Add((int)method.Rid); + } + } + + return ids; + } + } +} diff --git a/de4dot.code/deobfuscators/ILProtector/MainType.cs b/de4dot.code/deobfuscators/ILProtector/MainType.cs index f853b289..92b8a399 100644 --- a/de4dot.code/deobfuscators/ILProtector/MainType.cs +++ b/de4dot.code/deobfuscators/ILProtector/MainType.cs @@ -53,15 +53,20 @@ namespace de4dot.code.deobfuscators.ILProtector { CheckMethod(DotNetUtils.GetModuleTypeCctor(module)); } - static string[] ilpLocals = new string[] { + static string[] ilpLocalsV1x = new string[] { "System.Boolean", "System.IntPtr", "System.Object[]", }; + static string[] ilpLocalsV2x = new string[] { + "System.IntPtr", + }; bool CheckMethod(MethodDef cctor) { if (cctor == null || cctor.Body == null) return false; - if (!new LocalTypes(cctor).Exactly(ilpLocals)) + var localTypes = new LocalTypes(cctor); + if (!localTypes.Exactly(ilpLocalsV1x) && + !localTypes.Exactly(ilpLocalsV2x)) return false; var type = cctor.DeclaringType; @@ -70,20 +75,31 @@ namespace de4dot.code.deobfuscators.ILProtector { methods = GetPinvokeMethods(type, "P0"); if (methods.Count != 2) return false; - if (type.Fields.Count != 1) + if (type.Fields.Count < 1 || type.Fields.Count > 2) return false; - var theField = type.Fields[0]; - var theDelegate = theField.FieldType.TryGetTypeDef(); - if (theDelegate == null || !DotNetUtils.DerivesFromDelegate(theDelegate)) + if (!GetDelegate(type, out invokerInstanceField, out invokerDelegate)) return false; protectMethods = methods; - invokerDelegate = theDelegate; - invokerInstanceField = theField; return true; } + bool GetDelegate(TypeDef type, out FieldDef field, out TypeDef del) { + foreach (var fld in type.Fields) { + var theDelegate = fld.FieldType.TryGetTypeDef(); + if (theDelegate != null && DotNetUtils.DerivesFromDelegate(theDelegate)) { + field = fld; + del = theDelegate; + return true; + } + } + + field = null; + del = null; + return false; + } + static List GetPinvokeMethods(TypeDef type, string name) { var list = new List(); foreach (var method in type.Methods) { diff --git a/de4dot.code/deobfuscators/ILProtector/MethodsDecrypterBase.cs b/de4dot.code/deobfuscators/ILProtector/MethodsDecrypterBase.cs new file mode 100644 index 00000000..2ac46930 --- /dev/null +++ b/de4dot.code/deobfuscators/ILProtector/MethodsDecrypterBase.cs @@ -0,0 +1,125 @@ +/* + Copyright (C) 2011-2013 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.Collections.Generic; +using dnlib.DotNet; +using dnlib.DotNet.Emit; + +namespace de4dot.code.deobfuscators.ILProtector { + abstract class MethodsDecrypterBase { + protected ModuleDefMD module; + protected MainType mainType; + protected EmbeddedResource methodsResource; + protected Dictionary methodInfos = new Dictionary(); + List delegateTypes = new List(); + + public EmbeddedResource Resource { + get { return methodsResource; } + } + + public IEnumerable DelegateTypes { + get { return delegateTypes; } + } + + public MethodsDecrypterBase(ModuleDefMD module, MainType mainType) { + this.module = module; + this.mainType = mainType; + } + + public void Decrypt() { + DecryptInternal(); + RestoreMethods(); + } + + protected abstract void DecryptInternal(); + + void RestoreMethods() { + if (methodInfos.Count == 0) + return; + + Logger.v("Restoring {0} methods", methodInfos.Count); + Logger.Instance.Indent(); + foreach (var type in module.GetTypes()) { + foreach (var method in type.Methods) { + if (method.Body == null) + continue; + + if (RestoreMethod(method)) { + Logger.v("Restored method {0} ({1:X8}). Instrs:{2}, Locals:{3}, Exceptions:{4}", + Utils.RemoveNewlines(method.FullName), + method.MDToken.ToInt32(), + method.Body.Instructions.Count, + method.Body.Variables.Count, + method.Body.ExceptionHandlers.Count); + } + } + } + Logger.Instance.DeIndent(); + if (methodInfos.Count != 0) + Logger.w("{0} methods weren't restored", methodInfos.Count); + } + + bool RestoreMethod(MethodDef method) { + int? methodId = GetMethodId(method); + if (methodId == null) + return false; + + var parameters = method.Parameters; + var methodInfo = methodInfos[methodId.Value]; + methodInfos.Remove(methodId.Value); + var methodReader = new MethodReader(module, methodInfo.data, parameters); + methodReader.Read(); + + RestoreMethod(method, methodReader); + delegateTypes.Add(methodReader.DelegateType); + + return true; + } + + static void RestoreMethod(MethodDef method, MethodReader methodReader) { + // body.MaxStackSize = + method.Body.InitLocals = methodReader.InitLocals; + methodReader.RestoreMethod(method); + } + + protected int? GetMethodId(MethodDef method) { + if (method == null || method.Body == null) + return null; + + var instrs = method.Body.Instructions; + for (int i = 0; i < instrs.Count - 1; i++) { + var ldsfld = instrs[i]; + if (ldsfld.OpCode.Code != Code.Ldsfld) + continue; + + var ldci4 = instrs[i + 1]; + if (!ldci4.IsLdcI4()) + continue; + + var field = ldsfld.Operand as FieldDef; + if (field == null || field != mainType.InvokerInstanceField) + continue; + + return ldci4.GetLdcI4Value(); + } + + return null; + } + } +} diff --git a/de4dot.code/deobfuscators/ILProtector/MethodsDecrypter.cs b/de4dot.code/deobfuscators/ILProtector/StaticMethodsDecrypter.cs similarity index 71% rename from de4dot.code/deobfuscators/ILProtector/MethodsDecrypter.cs rename to de4dot.code/deobfuscators/ILProtector/StaticMethodsDecrypter.cs index 47be38aa..c614017b 100644 --- a/de4dot.code/deobfuscators/ILProtector/MethodsDecrypter.cs +++ b/de4dot.code/deobfuscators/ILProtector/StaticMethodsDecrypter.cs @@ -22,16 +22,9 @@ using System.Collections.Generic; using System.IO; using dnlib.IO; using dnlib.DotNet; -using dnlib.DotNet.Emit; -using de4dot.blocks; namespace de4dot.code.deobfuscators.ILProtector { - class MethodsDecrypter { - ModuleDefMD module; - MainType mainType; - EmbeddedResource methodsResource; - Dictionary methodInfos = new Dictionary(); - List delegateTypes = new List(); + class StaticMethodsDecrypter : MethodsDecrypterBase { IDecrypter decrypter; interface IDecrypter { @@ -153,13 +146,13 @@ namespace de4dot.code.deobfuscators.ILProtector { } } - // 1.0.6 + // 1.0.6 - 1.0.7.0 class DecrypterV106 : DecrypterBase { byte[] decryptionKey6; byte[] decryptionKey7; DecrypterV106(byte[] key0, byte[] key6, byte[] key7, int startOffset) { - this.ilpVersion = "1.0.6"; + this.ilpVersion = "1.0.6 - 1.0.7.0"; this.startOffset = startOffset; this.decryptionKey = key0; this.decryptionKey6 = key6; @@ -185,7 +178,7 @@ namespace de4dot.code.deobfuscators.ILProtector { var key0 = DeobUtils.Sha1Sum(sha1Data); // 1.0.6.0 var key6 = GetKey(reader, key0, keyXorOffs6); // 1.0.6.6 - var key7 = GetKey(reader, key0, keyXorOffs7); // 1.0.6.7 + var key7 = GetKey(reader, key0, keyXorOffs7); // 1.0.6.7 - 1.0.7.0 return new DecrypterV106(key0, key6, key7, encryptedOffs); } catch (IOException) { @@ -232,29 +225,6 @@ namespace de4dot.code.deobfuscators.ILProtector { } } - class MethodInfo2 { - public int id; - public int offset; - public byte[] data; - public MethodInfo2(int id, int offset, int size) { - this.id = id; - this.offset = offset; - this.data = new byte[size]; - } - - public override string ToString() { - return string.Format("{0} {1:X8} 0x{2:X}", id, offset, data.Length); - } - } - - public EmbeddedResource Resource { - get { return methodsResource; } - } - - public IEnumerable DelegateTypes { - get { return delegateTypes; } - } - public string Version { get { return decrypter == null ? null : decrypter.Version; } } @@ -263,9 +233,8 @@ namespace de4dot.code.deobfuscators.ILProtector { get { return methodsResource != null; } } - public MethodsDecrypter(ModuleDefMD module, MainType mainType) { - this.module = module; - this.mainType = mainType; + public StaticMethodsDecrypter(ModuleDefMD module, MainType mainType) + : base(module, mainType) { } public void Find() { @@ -300,108 +269,35 @@ namespace de4dot.code.deobfuscators.ILProtector { return decrypter != null; } - public void Decrypt() { + protected override void DecryptInternal() { if (methodsResource == null || decrypter == null) return; foreach (var info in ReadMethodInfos(decrypter.GetMethodsData(methodsResource))) methodInfos[info.id] = info; - - RestoreMethods(); } - static MethodInfo2[] ReadMethodInfos(byte[] data) { + static DecryptedMethodInfo[] ReadMethodInfos(byte[] data) { + var toOffset = new Dictionary(); var reader = MemoryImageStream.Create(data); int numMethods = (int)reader.Read7BitEncodedUInt32(); int totalCodeSize = (int)reader.Read7BitEncodedUInt32(); - var methodInfos = new MethodInfo2[numMethods]; + var methodInfos = new DecryptedMethodInfo[numMethods]; int offset = 0; for (int i = 0; i < numMethods; i++) { int id = (int)reader.Read7BitEncodedUInt32(); int size = (int)reader.Read7BitEncodedUInt32(); - methodInfos[i] = new MethodInfo2(id, offset, size); + var info = new DecryptedMethodInfo(id, size); + methodInfos[i] = info; + toOffset[info] = offset; offset += size; } long dataOffset = reader.Position; foreach (var info in methodInfos) { - reader.Position = dataOffset + info.offset; + reader.Position = dataOffset + toOffset[info]; reader.Read(info.data, 0, info.data.Length); } return methodInfos; } - - void RestoreMethods() { - if (methodInfos.Count == 0) - return; - - Logger.v("Restoring {0} methods", methodInfos.Count); - Logger.Instance.Indent(); - foreach (var type in module.GetTypes()) { - foreach (var method in type.Methods) { - if (method.Body == null) - continue; - - if (RestoreMethod(method)) { - Logger.v("Restored method {0} ({1:X8}). Instrs:{2}, Locals:{3}, Exceptions:{4}", - Utils.RemoveNewlines(method.FullName), - method.MDToken.ToInt32(), - method.Body.Instructions.Count, - method.Body.Variables.Count, - method.Body.ExceptionHandlers.Count); - } - } - } - Logger.Instance.DeIndent(); - if (methodInfos.Count != 0) - Logger.w("{0} methods weren't restored", methodInfos.Count); - } - - const int INVALID_METHOD_ID = -1; - bool RestoreMethod(MethodDef method) { - int methodId = GetMethodId(method); - if (methodId == INVALID_METHOD_ID) - return false; - - var parameters = method.Parameters; - var methodInfo = methodInfos[methodId]; - methodInfos.Remove(methodId); - var methodReader = new MethodReader(module, methodInfo.data, parameters); - methodReader.Read(); - - RestoreMethod(method, methodReader); - delegateTypes.Add(methodReader.DelegateType); - - return true; - } - - static void RestoreMethod(MethodDef method, MethodReader methodReader) { - // body.MaxStackSize = - method.Body.InitLocals = methodReader.InitLocals; - methodReader.RestoreMethod(method); - } - - int GetMethodId(MethodDef method) { - if (method == null || method.Body == null) - return INVALID_METHOD_ID; - - var instrs = method.Body.Instructions; - for (int i = 0; i < instrs.Count - 1; i++) { - var ldsfld = instrs[i]; - if (ldsfld.OpCode.Code != Code.Ldsfld) - continue; - - var ldci4 = instrs[i + 1]; - if (!ldci4.IsLdcI4()) - continue; - - var field = ldsfld.Operand as FieldDef; - if (field == null || field != mainType.InvokerInstanceField) - continue; - - return ldci4.GetLdcI4Value(); - } - - return INVALID_METHOD_ID; - } } }