diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/Deobfuscator.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/Deobfuscator.cs index 47d18400..9e46e331 100644 --- a/de4dot.code/deobfuscators/dotNET_Reactor/v4/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/Deobfuscator.cs @@ -333,6 +333,9 @@ namespace de4dot.code.deobfuscators.dotNET_Reactor.v4 { break; } + if (methodsDecrypter.DecrypterTypeVersion != DnrDecrypterType.V1) + return DeobfuscatorInfo.THE_NAME; + if (methodsDecrypter.Method == null) { if (minVer >= 3800) return DeobfuscatorInfo.THE_NAME + " >= 3.8"; diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/EncryptedResource.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/EncryptedResource.cs index 4ea0301a..285ed802 100644 --- a/de4dot.code/deobfuscators/dotNET_Reactor/v4/EncryptedResource.cs +++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/EncryptedResource.cs @@ -24,13 +24,24 @@ using System.Security.Cryptography; using dnlib.DotNet; using dnlib.DotNet.Emit; using de4dot.blocks; +using de4dot.blocks.cflow; namespace de4dot.code.deobfuscators.dotNET_Reactor.v4 { + enum DnrDecrypterType { + Unknown, + V1, + V2, + } + class EncryptedResource { ModuleDefMD module; MethodDef resourceDecrypterMethod; EmbeddedResource encryptedDataResource; - byte[] key, iv; + IDecrypter decrypter; + + public DnrDecrypterType DecrypterTypeVersion { + get { return decrypter == null ? DnrDecrypterType.Unknown : decrypter.DecrypterType; } + } public TypeDef Type { get { return resourceDecrypterMethod == null ? null : resourceDecrypterMethod.DeclaringType; } @@ -58,8 +69,7 @@ namespace de4dot.code.deobfuscators.dotNET_Reactor.v4 { resourceDecrypterMethod = Lookup(oldOne.resourceDecrypterMethod, "Could not find resource decrypter method"); if (oldOne.encryptedDataResource != null) encryptedDataResource = DotNetUtils.GetResource(module, oldOne.encryptedDataResource.Name.String) as EmbeddedResource; - key = oldOne.key; - iv = oldOne.iv; + this.decrypter = oldOne.decrypter; if (encryptedDataResource == null && oldOne.encryptedDataResource != null) throw new ApplicationException("Could not initialize EncryptedResource"); @@ -69,30 +79,12 @@ namespace de4dot.code.deobfuscators.dotNET_Reactor.v4 { return DeobUtils.Lookup(module, def, errorMessage); } - public bool CouldBeResourceDecrypter(MethodDef method, IEnumerable additionalTypes) { + public bool CouldBeResourceDecrypter(MethodDef method, IList additionalTypes) { return CouldBeResourceDecrypter(method, additionalTypes, true); } - public bool CouldBeResourceDecrypter(MethodDef method, IEnumerable additionalTypes, bool checkResource) { - if (!method.IsStatic) - return false; - if (method.Body == null) - return false; - - var localTypes = new LocalTypes(method); - var requiredTypes = new List { - "System.Byte[]", - "System.IO.BinaryReader", - "System.IO.MemoryStream", - "System.Security.Cryptography.CryptoStream", - "System.Security.Cryptography.ICryptoTransform", - }; - requiredTypes.AddRange(additionalTypes); - if (!localTypes.All(requiredTypes)) - return false; - if (!localTypes.Exists("System.Security.Cryptography.RijndaelManaged") && - !localTypes.Exists("System.Security.Cryptography.AesManaged") && - !localTypes.Exists("System.Security.Cryptography.SymmetricAlgorithm")) + public bool CouldBeResourceDecrypter(MethodDef method, IList additionalTypes, bool checkResource) { + if (GetDecrypterType(method, additionalTypes) == DnrDecrypterType.Unknown) return false; if (checkResource && FindMethodsDecrypterResource(method) == null) @@ -101,6 +93,24 @@ namespace de4dot.code.deobfuscators.dotNET_Reactor.v4 { return true; } + public DnrDecrypterType GuessDecrypterType() { + return GetDecrypterType(resourceDecrypterMethod, null); + } + + static DnrDecrypterType GetDecrypterType(MethodDef method, IList additionalTypes) { + if (method == null || !method.IsStatic || method.Body == null) + return DnrDecrypterType.Unknown; + + if (additionalTypes == null) + additionalTypes = new string[0]; + var localTypes = new LocalTypes(method); + if (DecrypterV1.CouldBeResourceDecrypter(method, localTypes, additionalTypes)) + return DnrDecrypterType.V1; + else if (DecrypterV2.CouldBeResourceDecrypter(method, localTypes, additionalTypes)) + return DnrDecrypterType.V2; + return DnrDecrypterType.Unknown; + } + public void Initialize(ISimpleDeobfuscator simpleDeobfuscator) { if (resourceDecrypterMethod == null) return; @@ -111,10 +121,10 @@ namespace de4dot.code.deobfuscators.dotNET_Reactor.v4 { if (encryptedDataResource == null) return; - key = ArrayFinder.GetInitializedByteArray(resourceDecrypterMethod, 32); + var key = ArrayFinder.GetInitializedByteArray(resourceDecrypterMethod, 32); if (key == null) throw new ApplicationException("Could not find resource decrypter key"); - iv = ArrayFinder.GetInitializedByteArray(resourceDecrypterMethod, 16); + var iv = ArrayFinder.GetInitializedByteArray(resourceDecrypterMethod, 16); if (iv == null) throw new ApplicationException("Could not find resource decrypter IV"); if (NeedReverse()) @@ -126,6 +136,13 @@ namespace de4dot.code.deobfuscators.dotNET_Reactor.v4 { iv[i * 2 + 1] = publicKeyToken.Data[i]; } } + + var decrypterType = GetDecrypterType(resourceDecrypterMethod, new string[0]); + switch (decrypterType) { + case DnrDecrypterType.V1: decrypter = new DecrypterV1(iv, key); break; + case DnrDecrypterType.V2: decrypter = new DecrypterV2(iv, key, resourceDecrypterMethod); break; + default: throw new ApplicationException("Unknown decrypter type"); + } } static int[] pktIndexes = new int[16] { 1, 0, 3, 1, 5, 2, 7, 3, 9, 4, 11, 5, 13, 6, 15, 7 }; @@ -162,22 +179,244 @@ namespace de4dot.code.deobfuscators.dotNET_Reactor.v4 { return null; } - public byte[] Decrypt() { - if (encryptedDataResource == null || key == null || iv == null) - throw new ApplicationException("Can't decrypt resource"); - - return DeobUtils.AesDecrypt(encryptedDataResource.GetResourceData(), key, iv); + interface IDecrypter { + DnrDecrypterType DecrypterType { get; } + byte[] Decrypt(EmbeddedResource resource); + byte[] Encrypt(byte[] data); } - public byte[] Encrypt(byte[] data) { - if (key == null || iv == null) - throw new ApplicationException("Can't encrypt resource"); + class DecrypterV1 : IDecrypter { + readonly byte[] key, iv; - using (var aes = new RijndaelManaged { Mode = CipherMode.CBC }) { - using (var transform = aes.CreateEncryptor(key, iv)) { - return transform.TransformFinalBlock(data, 0, data.Length); + public DnrDecrypterType DecrypterType { + get { return DnrDecrypterType.V1; } + } + + public DecrypterV1(byte[] iv, byte[] key) { + this.iv = iv; + this.key = key; + } + + public static bool CouldBeResourceDecrypter(MethodDef method, LocalTypes localTypes, IList additionalTypes) { + var requiredTypes = new List { + "System.Byte[]", + "System.IO.BinaryReader", + "System.IO.MemoryStream", + "System.Security.Cryptography.CryptoStream", + "System.Security.Cryptography.ICryptoTransform", + }; + requiredTypes.AddRange(additionalTypes); + if (!localTypes.All(requiredTypes)) + return false; + if (!localTypes.Exists("System.Security.Cryptography.RijndaelManaged") && + !localTypes.Exists("System.Security.Cryptography.AesManaged") && + !localTypes.Exists("System.Security.Cryptography.SymmetricAlgorithm")) + return false; + + return true; + } + + public byte[] Decrypt(EmbeddedResource resource) { + return DeobUtils.AesDecrypt(resource.GetResourceData(), key, iv); + } + + public byte[] Encrypt(byte[] data) { + using (var aes = new RijndaelManaged { Mode = CipherMode.CBC }) { + using (var transform = aes.CreateEncryptor(key, iv)) { + return transform.TransformFinalBlock(data, 0, data.Length); + } } } } + + class DecrypterV2 : IDecrypter { + readonly byte[] key, iv; + readonly MethodDef method; + List instructions; + readonly List locals; + readonly InstructionEmulator instrEmulator = new InstructionEmulator(); + Local emuLocal; + + public DnrDecrypterType DecrypterType { + get { return DnrDecrypterType.V2; } + } + + public DecrypterV2(byte[] iv, byte[] key, MethodDef method) { + this.iv = iv; + this.key = key; + this.method = method; + this.locals = new List(method.Body.Variables); + if (!Initialize()) + throw new ApplicationException("Could not initialize decrypter"); + } + + public static bool CouldBeResourceDecrypter(MethodDef method, LocalTypes localTypes, IList additionalTypes) { + var requiredTypes = new List { + "System.UInt32", + "System.String", + "System.Int32", + "System.Byte[]", + "System.IO.BinaryReader", + }; + requiredTypes.AddRange(additionalTypes); + if (!localTypes.All(requiredTypes)) + return false; + + return true; + } + + bool Initialize() { + for (int i = 0; i < iv.Length; i++) + key[i] ^= iv[i]; + + var origInstrs = method.Body.Instructions; + + int emuStartIndex; + if (!FindStart(origInstrs, out emuStartIndex, out emuLocal)) + return false; + int emuEndIndex; + if (!FindEnd(origInstrs, emuStartIndex, out emuEndIndex)) + return false; + + int count = emuEndIndex - emuStartIndex + 1; + instructions = new List(count); + for (int i = 0; i < count; i++) + instructions.Add(origInstrs[emuStartIndex + i].Clone()); + + return true; + } + + bool FindStart(IList instrs, out int startIndex, out Local tmpLocal) { + for (int i = 0; i + 8 < instrs.Count; i++) { + if (instrs[i].OpCode.Code != Code.Conv_U) + continue; + if (instrs[i + 1].OpCode.Code != Code.Ldelem_U1) + continue; + if (instrs[i + 2].OpCode.Code != Code.Or) + continue; + if (CheckLocal(instrs[i + 3], false) == null) + continue; + Local local; + if ((local = CheckLocal(instrs[i + 4], true)) == null) + continue; + if (CheckLocal(instrs[i + 5], true) == null) + continue; + if (instrs[i + 6].OpCode.Code != Code.Add) + continue; + if (CheckLocal(instrs[i + 7], false) != local) + continue; + var instr = instrs[i + 8]; + int newStartIndex = i + 8; + if (instr.IsBr()) { + instr = instr.Operand as Instruction; + newStartIndex = instrs.IndexOf(instr); + } + if (newStartIndex < 0 || instr == null) + continue; + if (CheckLocal(instr, true) != local) + continue; + + startIndex = newStartIndex; + tmpLocal = local; + return true; + } + + startIndex = 0; + tmpLocal = null; + return false; + } + + bool FindEnd(IList instrs, int startIndex, out int endIndex) { + for (int i = startIndex; i < instrs.Count; i++) { + var instr = instrs[i]; + if (instr.OpCode.FlowControl != FlowControl.Next) + break; + if (instr.IsStloc() && instr.GetLocal(locals) == emuLocal) { + endIndex = i - 1; + return true; + } + } + + endIndex = 0; + return false; + } + + Local CheckLocal(Instruction instr, bool isLdloc) { + if (isLdloc && !instr.IsLdloc()) + return null; + else if (!isLdloc && !instr.IsStloc()) + return null; + + return instr.GetLocal(locals); + } + + public byte[] Decrypt(EmbeddedResource resource) { + var encrypted = resource.GetResourceData(); + var decrypted = new byte[encrypted.Length]; + + uint sum = 0; + for (int i = 0; i < encrypted.Length; i += 4) { + sum = CalculateMagic(sum + ReadUInt32(key, i % key.Length)); + WriteUInt32(decrypted, i, sum ^ ReadUInt32(encrypted, i)); + } + + return decrypted; + } + + uint CalculateMagic(uint input) { + instrEmulator.Initialize(method, method.Parameters, locals, method.Body.InitLocals, false); + instrEmulator.SetLocal(emuLocal, new Int32Value((int)input)); + + foreach (var instr in instructions) + instrEmulator.Emulate(instr); + + var tos = instrEmulator.Pop() as Int32Value; + if (tos == null || !tos.AllBitsValid()) + throw new ApplicationException("Couldn't calculate magic value"); + return (uint)tos.Value; + } + + static uint ReadUInt32(byte[] ary, int index) { + int sizeLeft = ary.Length - index; + if (sizeLeft >= 4) + return BitConverter.ToUInt32(ary, index); + switch (sizeLeft) { + case 1: return ary[index]; + case 2: return (uint)(ary[index] | (ary[index + 1] << 8)); + case 3: return (uint)(ary[index] | (ary[index + 1] << 8) | (ary[index + 2] << 16)); + default: throw new ApplicationException("Can't read data"); + } + } + + static void WriteUInt32(byte[] ary, int index, uint value) { + int sizeLeft = ary.Length - index; + if (sizeLeft >= 1) + ary[index] = (byte)value; + if (sizeLeft >= 2) + ary[index + 1] = (byte)(value >> 8); + if (sizeLeft >= 3) + ary[index + 2] = (byte)(value >> 16); + if (sizeLeft >= 4) + ary[index + 3] = (byte)(value >> 24); + } + + public byte[] Encrypt(byte[] data) { + //TODO: Support re-encryption + Logger.e("Re-encryption is not supported. Assembly will probably crash at runtime."); + return (byte[])data.Clone(); + } + } + + public byte[] Decrypt() { + if (encryptedDataResource == null || decrypter == null) + throw new ApplicationException("Can't decrypt resource"); + return decrypter.Decrypt(encryptedDataResource); + } + + public byte[] Encrypt(byte[] data) { + if (decrypter == null) + throw new ApplicationException("Can't encrypt resource"); + return decrypter.Encrypt(data); + } } } diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/MethodsDecrypter.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/MethodsDecrypter.cs index fedb2f96..b0801aa3 100644 --- a/de4dot.code/deobfuscators/dotNET_Reactor/v4/MethodsDecrypter.cs +++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/MethodsDecrypter.cs @@ -57,6 +57,10 @@ namespace de4dot.code.deobfuscators.dotNET_Reactor.v4 { get { return encryptedResource.Resource; } } + public DnrDecrypterType DecrypterTypeVersion { + get { return encryptedResource.GuessDecrypterType(); } + } + public MethodsDecrypter(ModuleDefMD module) { this.module = module; this.encryptedResource = new EncryptedResource(module); @@ -301,10 +305,10 @@ namespace de4dot.code.deobfuscators.dotNET_Reactor.v4 { if (instructions[i].OpCode.Code != Code.Ldind_I8) continue; var ldci4 = instructions[i + 1]; - if (!ldci4.IsLdcI4()) - continue; - - return ldci4.GetLdcI4Value(); + if (ldci4.IsLdcI4()) + return ldci4.GetLdcI4Value(); + if (ldci4.OpCode.Code == Code.Ldc_I8) + return (long)ldci4.Operand; } return 0; }