/* Copyright (C) 2011-2015 de4dot@gmail.com This file is part of de4dot. de4dot is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. de4dot is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with de4dot. If not, see . */ using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Security.Cryptography; using dnlib.DotNet; using dnlib.DotNet.Emit; using de4dot.blocks; namespace de4dot.code.deobfuscators.CryptoObfuscator { class ResourceDecrypter { const int BUFLEN = 0x8000; ModuleDefMD module; TypeDef resourceDecrypterType; byte[] buffer1 = new byte[BUFLEN]; byte[] buffer2 = new byte[BUFLEN]; byte desEncryptedFlag; byte deflatedFlag; byte bitwiseNotEncryptedFlag; FrameworkType frameworkType; bool flipFlagsBits; bool skipBeforeFlag; int skipBytes; public ResourceDecrypter(ModuleDefMD module, ISimpleDeobfuscator simpleDeobfuscator) { this.module = module; frameworkType = DotNetUtils.GetFrameworkType(module); Find(simpleDeobfuscator); } void Find(ISimpleDeobfuscator simpleDeobfuscator) { switch (frameworkType) { case FrameworkType.Silverlight: FindSilverlight(); break; case FrameworkType.Desktop: case FrameworkType.CompactFramework: if (!module.IsClr1x) { if (FindDesktopOrCompactFramework()) break; } FindDesktopOrCompactFrameworkV1(); break; } InitializeHeaderInfo(simpleDeobfuscator); } static string[] requiredTypes = new string[] { "System.IO.MemoryStream", "System.Object", "System.Int32", }; bool FindDesktopOrCompactFramework() { resourceDecrypterType = null; foreach (var type in module.Types) { if (type.Fields.Count < 5) continue; if (!new FieldTypes(type).All(requiredTypes)) continue; var cctor = type.FindStaticConstructor(); if (cctor == null) continue; if (!CheckCctor(cctor)) continue; resourceDecrypterType = type; return true; } return false; } bool CheckCctor(MethodDef cctor) { if (cctor.Body == null) return false; int stsfldCount = 0; foreach (var instr in cctor.Body.Instructions) { if (instr.OpCode.Code == Code.Stsfld) { var field = instr.Operand as IField; if (!new SigComparer().Equals(cctor.DeclaringType, field.DeclaringType)) return false; stsfldCount++; } } return stsfldCount >= cctor.DeclaringType.Fields.Count; } static string[] requiredLocals_v1 = new string[] { "System.Boolean", "System.Byte", "System.Byte[]", "System.Int32", "System.Security.Cryptography.DESCryptoServiceProvider", }; bool FindDesktopOrCompactFrameworkV1() { resourceDecrypterType = null; foreach (var type in module.Types) { if (type.Fields.Count != 0) continue; foreach (var method in GetDecrypterMethods(type)) { if (method == null) continue; if (!new LocalTypes(method).Exactly(requiredLocals_v1)) continue; if (!DotNetUtils.CallsMethod(method, "System.Int64", "()")) continue; if (!DotNetUtils.CallsMethod(method, "System.Int32", "(System.Byte[],System.Int32,System.Int32)")) continue; if (!DotNetUtils.CallsMethod(method, "System.Void", "(System.Array,System.Int32,System.Array,System.Int32,System.Int32)")) continue; if (!DotNetUtils.CallsMethod(method, "System.Security.Cryptography.ICryptoTransform", "()")) continue; if (!DotNetUtils.CallsMethod(method, "System.Byte[]", "(System.Byte[],System.Int32,System.Int32)")) continue; resourceDecrypterType = type; return true; } } return false; } static string[] requiredLocals_sl = new string[] { "System.Byte", "System.Byte[]", "System.Int32", }; void FindSilverlight() { foreach (var type in module.Types) { if (type.Fields.Count > 0) continue; if (type.HasNestedTypes || type.HasGenericParameters) continue; foreach (var method in GetDecrypterMethods(type)) { if (method == null) continue; if (!new LocalTypes(method).All(requiredLocals_sl)) continue; resourceDecrypterType = type; return; } } } void InitializeHeaderInfo(ISimpleDeobfuscator simpleDeobfuscator) { skipBytes = 0; foreach (var method in GetDecrypterMethods(resourceDecrypterType)) { if (UpdateFlags(method, simpleDeobfuscator)) return; } desEncryptedFlag = 1; deflatedFlag = 2; bitwiseNotEncryptedFlag = 4; } static bool CheckFlipBits(MethodDef method, out int index) { int nots = 0, i; var instrs = method.Body.Instructions; index = -1; for (i = 0; i < instrs.Count - 1; i++) { var ldloc = instrs[i]; if (!ldloc.IsLdloc()) continue; var local = ldloc.GetLocal(method.Body.Variables); if (local == null || local.Type.GetElementType().GetPrimitiveSize() < 0) continue; if (instrs[i + 1].OpCode.Code == Code.Not) { nots++; index = i + 1; } } return (nots & 1) == 1; } bool UpdateFlags(MethodDef method, ISimpleDeobfuscator simpleDeobfuscator) { if (method == null || method.Body == null || method.Body.Variables.Count < 3) return false; var constants = new List(); simpleDeobfuscator.Deobfuscate(method); var instructions = method.Body.Instructions; for (int i = 2; i < instructions.Count; i++) { var and = instructions[i]; if (and.OpCode.Code != Code.And) continue; var ldci4 = instructions[i - 1]; if (!ldci4.IsLdcI4()) continue; int flagValue = ldci4.GetLdcI4Value(); if (!IsFlag(flagValue)) continue; var ldloc = instructions[i - 2]; if (!ldloc.IsLdloc()) continue; var local = ldloc.GetLocal(method.Body.Variables); if (local.Type.GetElementType().GetPrimitiveSize() < 0) continue; constants.Add(flagValue); } int notIndex, skipIndex; flipFlagsBits = CheckFlipBits(method, out notIndex); skipBytes = GetHeaderSkipBytes(method, out skipIndex); skipBeforeFlag = skipIndex < notIndex; switch (frameworkType) { case FrameworkType.Desktop: if (!module.IsClr1x) { if (constants.Count == 2) { desEncryptedFlag = (byte)constants[0]; deflatedFlag = (byte)constants[1]; return true; } } if (constants.Count == 1) { desEncryptedFlag = (byte)constants[0]; return true; } break; case FrameworkType.Silverlight: if (constants.Count == 1) { bitwiseNotEncryptedFlag = (byte)constants[0]; return true; } break; case FrameworkType.CompactFramework: if (constants.Count == 1) { desEncryptedFlag = (byte)constants[0]; return true; } break; } return false; } static int GetHeaderSkipBytes(MethodDef method, out int index) { var instrs = method.Body.Instructions; for (int i = 0; i < instrs.Count - 1; i++) { var ldci4 = instrs[i]; if (!ldci4.IsLdcI4()) continue; int loopCount = ldci4.GetLdcI4Value(); if (loopCount < 2 || loopCount > 4) continue; var blt = instrs[i + 1]; if (blt.OpCode.Code != Code.Blt && blt.OpCode.Code != Code.Blt_S && blt.OpCode.Code != Code.Clt) continue; index = i; return loopCount - 1; } index = 0; return 0; } static bool IsFlag(int value) { for (uint tmp = (uint)value; tmp != 0; tmp >>= 1) { if ((tmp & 1) != 0) return tmp == 1; } return false; } static IEnumerable GetDecrypterMethods(TypeDef type) { if (type == null) yield break; foreach (var method in type.Methods) { if (DotNetUtils.IsMethod(method, "System.Byte[]", "(System.IO.Stream)")) yield return method; else if (DotNetUtils.IsMethod(method, "System.Byte[]", "(System.Int64,System.IO.Stream)")) yield return method; else if (DotNetUtils.IsMethod(method, "System.Byte[]", "(System.Int64,System.IO.Stream,System.UInt32)")) yield return method; else if (DotNetUtils.IsMethod(method, "System.Byte[]", "(System.Int32,System.IO.Stream)")) yield return method; else if (DotNetUtils.IsMethod(method, "System.Byte[]", "(System.Int16,System.IO.Stream)")) yield return method; else if (DotNetUtils.IsMethod(method, "System.Byte[]", "(System.Byte,System.IO.Stream)")) yield return method; else if (DotNetUtils.IsMethod(method, "System.Byte[]", "(System.SByte,System.IO.Stream)")) yield return method; else if (DotNetUtils.IsMethod(method, "System.Byte[]", "(System.Byte,System.IO.Stream,System.Int32)")) yield return method; else if (DotNetUtils.IsMethod(method, "System.Byte[]", "(System.SByte,System.IO.Stream,System.UInt32)")) yield return method; else if (DotNetUtils.IsMethod(method, "System.Byte[]", "(System.Char,System.IO.Stream)")) yield return method; } } public byte[] Decrypt(Stream resourceStream) { Stream sourceStream = resourceStream; int sourceStreamOffset = 1; bool didSomething = false; if (skipBeforeFlag) { sourceStream.Position += skipBytes; sourceStreamOffset += skipBytes; } byte flags = (byte)sourceStream.ReadByte(); if (flipFlagsBits) flags = (byte)~flags; if (!skipBeforeFlag) { sourceStream.Position += skipBytes; sourceStreamOffset += skipBytes; } byte allFlags = (byte)(desEncryptedFlag | deflatedFlag | bitwiseNotEncryptedFlag); if ((flags & ~allFlags) != 0) Logger.w("Found unknown resource encryption flags: 0x{0:X2}", flags); if ((flags & desEncryptedFlag) != 0) { var memStream = new MemoryStream((int)resourceStream.Length); using (var provider = new DESCryptoServiceProvider()) { var iv = new byte[8]; sourceStream.Read(iv, 0, 8); provider.IV = iv; provider.Key = GetKey(sourceStream); using (var transform = provider.CreateDecryptor()) { while (true) { int count = sourceStream.Read(buffer1, 0, buffer1.Length); if (count <= 0) break; int count2 = transform.TransformBlock(buffer1, 0, count, buffer2, 0); memStream.Write(buffer2, 0, count2); } var finalData = transform.TransformFinalBlock(buffer1, 0, 0); memStream.Write(finalData, 0, finalData.Length); } } sourceStream = memStream; sourceStreamOffset = 0; didSomething = true; } if ((flags & deflatedFlag) != 0) { var memStream = new MemoryStream((int)resourceStream.Length); sourceStream.Position = sourceStreamOffset; using (var inflater = new DeflateStream(sourceStream, CompressionMode.Decompress)) { while (true) { int count = inflater.Read(buffer1, 0, buffer1.Length); if (count <= 0) break; memStream.Write(buffer1, 0, count); } } sourceStream = memStream; sourceStreamOffset = 0; didSomething = true; } if ((flags & bitwiseNotEncryptedFlag) != 0) { var memStream = new MemoryStream((int)resourceStream.Length); sourceStream.Position = sourceStreamOffset; for (int i = sourceStreamOffset; i < sourceStream.Length; i++) memStream.WriteByte((byte)~sourceStream.ReadByte()); sourceStream = memStream; sourceStreamOffset = 0; didSomething = true; } if (didSomething && sourceStream is MemoryStream) { var memStream = (MemoryStream)sourceStream; return memStream.ToArray(); } else { int len = (int)(sourceStream.Length - sourceStream.Position); byte[] data = new byte[len]; sourceStream.Read(data, 0, len); return data; } } byte[] GetKey(Stream resourceStream) { byte[] key = new byte[8]; resourceStream.Read(key, 0, key.Length); for (int i = 0; i < key.Length; i++) { if (key[i] != 0) return key; } key = PublicKeyBase.GetRawData(module.Assembly.PublicKeyToken); if (key == null) throw new ApplicationException("PublicKeyToken is null, can't decrypt resources"); return key; } } }