/* 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.Text; using System.Text.RegularExpressions; using dnlib.DotNet; using dnlib.DotNet.Emit; using de4dot.blocks; namespace de4dot.code.deobfuscators.Eazfuscator_NET { class AssemblyResolver { ModuleDefMD module; DecrypterType decrypterType; TypeDef resolverType; MethodDef initMethod; MethodDef handlerMethod; MethodDef decryptMethod; TypeDef otherType; List assemblyInfos = new List(); FrameworkType frameworkType; byte[] decryptKey; CodeCompilerMethodCallRestorer codeCompilerMethodCallRestorer; public class AssemblyInfo { public bool IsEncrypted { get; set; } public bool IsCompressed { get; set; } public string ResourceName { get; set; } public string Filename { get; set; } public string AssemblyFullName { get; set; } public string SimpleName { get; set; } public string Extension { get; set; } public EmbeddedResource Resource { get; set; } public byte[] Data { get; set; } public override string ToString() { return AssemblyFullName ?? Filename; } } public TypeDef Type { get { return resolverType; } } public TypeDef OtherType { get { return otherType; } } public MethodDef InitMethod { get { return initMethod; } } public IEnumerable AssemblyInfos { get { return assemblyInfos; } } public bool Detected { get { return resolverType != null; } } public AssemblyResolver(ModuleDefMD module, DecrypterType decrypterType) { this.module = module; this.frameworkType = DotNetUtils.GetFrameworkType(module); this.decrypterType = decrypterType; this.codeCompilerMethodCallRestorer = new CodeCompilerMethodCallRestorer(module); } public void Find() { CheckCalledMethods(DotNetUtils.GetModuleTypeCctor(module)); } bool CheckCalledMethods(MethodDef method) { if (method == null || method.Body == null) return false; foreach (var instr in method.Body.Instructions) { if (instr.OpCode.Code != Code.Call) continue; var calledMethod = instr.Operand as MethodDef; if (calledMethod == null || !calledMethod.IsStatic || calledMethod.Body == null) continue; if (!DotNetUtils.IsMethod(calledMethod, "System.Void", "()")) continue; if (frameworkType == FrameworkType.Silverlight) { if (!CheckInitMethodSilverlight(calledMethod)) continue; } else { if (!CheckInitMethod(calledMethod)) continue; } decryptMethod = GetDecryptMethod(); UpdateDecrypterType(); FindCodeDomMethods(); return true; } return false; } bool CheckInitMethodSilverlight(MethodDef method) { var type = method.DeclaringType; if (type.NestedTypes.Count != 2) return false; var resolveHandler = GetResolveMethodSilverlight(method); if (resolveHandler == null) return false; initMethod = method; resolverType = type; handlerMethod = resolveHandler; return true; } static MethodDef GetResolveMethodSilverlight(MethodDef initMethod) { foreach (var instr in initMethod.Body.Instructions) { if (instr.OpCode.Code != Code.Call) continue; var calledMethod = instr.Operand as MethodDef; if (calledMethod == null) continue; if (!DotNetUtils.IsMethod(calledMethod, "System.Void", "()")) continue; if (!DeobUtils.HasInteger(calledMethod, ',') || !DeobUtils.HasInteger(calledMethod, '|')) continue; return calledMethod; } return null; } bool CheckInitMethod(MethodDef method) { var type = method.DeclaringType; if (type.NestedTypes.Count < 2 || type.NestedTypes.Count > 6) return false; if (DotNetUtils.GetPInvokeMethod(type, "kernel32", "MoveFileEx") == null) return false; var resolveHandler = DeobUtils.GetResolveMethod(method); if (resolveHandler == null) return false; if (!DeobUtils.HasInteger(resolveHandler, ',') || !DeobUtils.HasInteger(resolveHandler, '|')) return false; initMethod = method; resolverType = type; handlerMethod = resolveHandler; return true; } MethodDef GetDecryptMethod() { foreach (var method in resolverType.Methods) { if (!method.IsStatic || method.Body == null) continue; if (!DotNetUtils.IsMethod(method, "System.Byte[]", "(System.Byte[])")) continue; if (!DeobUtils.HasInteger(method, 32) || !DeobUtils.HasInteger(method, 121)) continue; return method; } return null; } void UpdateDecrypterType() { var theDecrypterType = GetDecrypterType(decryptMethod); if (theDecrypterType == null) return; decrypterType.Type = theDecrypterType; if (!decrypterType.Initialize()) throw new ApplicationException("Could not initialize decrypterType"); } TypeDef GetDecrypterType(MethodDef method) { if (method == null || method.Body == null) return null; foreach (var instr in method.Body.Instructions) { if (instr.OpCode.Code != Code.Call) continue; var calledMethod = instr.Operand as MethodDef; if (calledMethod == null || !calledMethod.IsStatic || calledMethod.DeclaringType == resolverType) continue; if (!DotNetUtils.IsMethod(calledMethod, "System.Void", "(System.Byte[])")) continue; return calledMethod.DeclaringType; } return null; } public void Initialize(ISimpleDeobfuscator simpleDeobfuscator, IDeobfuscator deob) { if (handlerMethod == null) return; FindOtherType(); simpleDeobfuscator.Deobfuscate(handlerMethod); simpleDeobfuscator.DecryptStrings(handlerMethod, deob); if (!CreateAssemblyInfos()) throw new ApplicationException("Could not initialize assembly infos"); if (decryptMethod != null) { simpleDeobfuscator.Deobfuscate(decryptMethod); simpleDeobfuscator.DecryptStrings(decryptMethod, deob); if (!CreateDecryptKey()) throw new ApplicationException("Could not initialize decryption key"); } } void FindOtherType() { foreach (var type in module.Types) { // This type is added by EF 3.1+. The last number seems to be an int32 hash of // the assembly name, but - replaced with _. if (!Regex.IsMatch(type.FullName, @"^pc1eOx2WJVV[_0-9]+$")) continue; otherType = type; break; } } bool CreateDecryptKey() { if (decryptMethod == null) return false; foreach (var s in DotNetUtils.GetCodeStrings(decryptMethod)) { decryptKey = DecodeBase64(s); if (decryptKey == null || decryptKey.Length == 0) continue; if (decrypterType.Detected) { var data = new byte[8]; ulong magic = decrypterType.GetMagic(); data[0] = (byte)magic; data[7] = (byte)(magic >> 8); data[6] = (byte)(magic >> 16); data[5] = (byte)(magic >> 24); data[4] = (byte)(magic >> 32); data[1] = (byte)(magic >> 40); data[3] = (byte)(magic >> 48); data[2] = (byte)(magic >> 56); for (int i = 0; i < decryptKey.Length; i++) decryptKey[i] ^= (byte)(i + data[i % data.Length]); } return true; } decryptKey = null; return false; } static byte[] DecodeBase64(string s) { try { return Convert.FromBase64String(s); } catch (FormatException) { return null; } } bool CreateAssemblyInfos() { int numElements = DeobUtils.HasInteger(handlerMethod, 3) ? 3 : 2; foreach (var s in DotNetUtils.GetCodeStrings(handlerMethod)) { var infos = CreateAssemblyInfos(s, numElements); if (infos == null) continue; assemblyInfos = infos; return true; } return false; } List CreateAssemblyInfos(string s, int numElements) { try { return TryCreateAssemblyInfos(s, numElements); } catch (FormatException) { return null; // Convert.FromBase64String() failed } } List TryCreateAssemblyInfos(string s, int numElements) { var ary = s.Split(','); if (ary.Length == 0 || ary.Length % numElements != 0) return null; var infos = new List(); for (int i = 0; i < ary.Length; i += numElements) { var info = new AssemblyInfo(); info.AssemblyFullName = Encoding.UTF8.GetString(Convert.FromBase64String(ary[i])); info.ResourceName = ary[i + 1]; if (numElements >= 3) info.Filename = Encoding.UTF8.GetString(Convert.FromBase64String(ary[i + 2])); else info.Filename = Utils.GetAssemblySimpleName(info.AssemblyFullName) + ".dll"; int index = info.ResourceName.IndexOf('|'); if (index >= 0) { var flags = info.ResourceName.Substring(0, index); info.ResourceName = info.ResourceName.Substring(index + 1); info.IsEncrypted = flags.IndexOf('a') >= 0; info.IsCompressed = flags.IndexOf('b') >= 0; } infos.Add(info); } return infos; } public void InitializeEmbeddedFiles() { foreach (var info in assemblyInfos) { info.Resource = DotNetUtils.GetResource(module, info.ResourceName) as EmbeddedResource; if (info.Resource == null) throw new ApplicationException(string.Format("Could not find resource {0}", Utils.ToCsharpString(info.ResourceName))); info.Data = info.Resource.GetResourceData(); if (info.IsEncrypted) Decrypt(info.Data); if (info.IsCompressed) info.Data = Decompress(info.Data); InitializeNameAndExtension(info); } } static void InitializeNameAndExtension(AssemblyInfo info) { try { var mod = ModuleDefMD.Load(info.Data); info.AssemblyFullName = mod.Assembly.FullName; info.SimpleName = mod.Assembly.Name.String; info.Extension = DeobUtils.GetExtension(mod.Kind); return; } catch { } Logger.w("Could not load assembly from decrypted resource {0}", Utils.ToCsharpString(info.ResourceName)); int index = info.Filename.LastIndexOf('.'); if (index < 0) { info.SimpleName = info.Filename; info.Extension = ""; } else { info.SimpleName = info.Filename.Substring(0, index); info.Extension = info.Filename.Substring(index); } } static readonly byte[] key2 = new byte[] { 148, 68, 208, 52 }; void Decrypt(byte[] encryptedData) { var indexes = new byte[256]; for (int i = 0; i < indexes.Length; i++) indexes[i] = (byte)i; byte i1 = 0, i2 = 0; for (int i = 0; i < indexes.Length; i++) { i2 += (byte)(decryptKey[i % decryptKey.Length] + indexes[i]); Swap(indexes, i, i2); } byte val = 0; for (int i = 0; i < encryptedData.Length; i++) { if ((i & 0x1F) == 0) { i2 += indexes[++i1]; Swap(indexes, i1, i2); val = indexes[(byte)(indexes[i1] + indexes[i2])]; } encryptedData[i] ^= (byte)(val ^ key2[(i >> 2) & 3] ^ key2[(i + 1) & 3]); } } static void Swap(byte[] data, int i, int j) { byte tmp = data[i]; data[i] = data[j]; data[j] = tmp; } byte[] Decompress(byte[] compressedData) { // First dword is sig: 0x9B728BC7 // Second dword is decompressed length return DeobUtils.Inflate(compressedData, 8, compressedData.Length - 8, true); } public AssemblyInfo Get(string asmFullName) { var simpleName = Utils.GetAssemblySimpleName(asmFullName); for (int i = 0; i < assemblyInfos.Count; i++) { var info = assemblyInfos[i]; if (info.SimpleName != simpleName) continue; assemblyInfos.RemoveAt(i); return info; } return null; } public void Deobfuscate(Blocks blocks) { codeCompilerMethodCallRestorer.Deobfuscate(blocks); } void FindCodeDomMethods() { if (resolverType == null) return; foreach (var nestedType in resolverType.NestedTypes) { if (nestedType.Fields.Count != 0) continue; var CompileAssemblyFromDom1 = GetTheOnlyMethod(nestedType, "System.CodeDom.Compiler.CodeDomProvider", "CompileAssemblyFromDom", "System.CodeDom.Compiler.CompilerResults", "System.CodeDom.Compiler.CompilerParameters,System.CodeDom.CodeCompileUnit[]"); var CompileAssemblyFromFile1 = GetTheOnlyMethod(nestedType, "System.CodeDom.Compiler.CodeDomProvider", "CompileAssemblyFromFile", "System.CodeDom.Compiler.CompilerResults", "System.CodeDom.Compiler.CompilerParameters,System.String[]"); var CompileAssemblyFromSource1 = GetTheOnlyMethod(nestedType, "System.CodeDom.Compiler.CodeDomProvider", "CompileAssemblyFromSource", "System.CodeDom.Compiler.CompilerResults", "System.CodeDom.Compiler.CompilerParameters,System.String[]"); var CompileAssemblyFromDom2 = GetTheOnlyMethod(nestedType, "System.CodeDom.Compiler.ICodeCompiler", "CompileAssemblyFromDom", "System.CodeDom.Compiler.CompilerResults", "System.CodeDom.Compiler.CompilerParameters,System.CodeDom.CodeCompileUnit"); var CompileAssemblyFromDomBatch2 = GetTheOnlyMethod(nestedType, "System.CodeDom.Compiler.ICodeCompiler", "CompileAssemblyFromDomBatch", "System.CodeDom.Compiler.CompilerResults", "System.CodeDom.Compiler.CompilerParameters,System.CodeDom.CodeCompileUnit[]"); var CompileAssemblyFromFile2 = GetTheOnlyMethod(nestedType, "System.CodeDom.Compiler.ICodeCompiler", "CompileAssemblyFromFile", "System.CodeDom.Compiler.CompilerResults", "System.CodeDom.Compiler.CompilerParameters,System.String"); var CompileAssemblyFromFileBatch2 = GetTheOnlyMethod(nestedType, "System.CodeDom.Compiler.ICodeCompiler", "CompileAssemblyFromFileBatch", "System.CodeDom.Compiler.CompilerResults", "System.CodeDom.Compiler.CompilerParameters,System.String[]"); var CompileAssemblyFromSource2 = GetTheOnlyMethod(nestedType, "System.CodeDom.Compiler.ICodeCompiler", "CompileAssemblyFromSource", "System.CodeDom.Compiler.CompilerResults", "System.CodeDom.Compiler.CompilerParameters,System.String"); var CompileAssemblyFromSourceBatch2 = GetTheOnlyMethod(nestedType, "System.CodeDom.Compiler.ICodeCompiler", "CompileAssemblyFromSourceBatch", "System.CodeDom.Compiler.CompilerResults", "System.CodeDom.Compiler.CompilerParameters,System.String[]"); if (CompileAssemblyFromDom1 == null && CompileAssemblyFromFile1 == null && CompileAssemblyFromSource1 == null && CompileAssemblyFromDom2 == null && CompileAssemblyFromDomBatch2 == null && CompileAssemblyFromFile2 == null && CompileAssemblyFromFileBatch2 == null && CompileAssemblyFromSource2 == null && CompileAssemblyFromSourceBatch2 == null) { continue; } codeCompilerMethodCallRestorer.Add_CodeDomProvider_CompileAssemblyFromDom(CompileAssemblyFromDom1); codeCompilerMethodCallRestorer.Add_CodeDomProvider_CompileAssemblyFromFile(CompileAssemblyFromFile1); codeCompilerMethodCallRestorer.Add_CodeDomProvider_CompileAssemblyFromSource(CompileAssemblyFromSource1); codeCompilerMethodCallRestorer.Add_ICodeCompiler_CompileAssemblyFromDom(CompileAssemblyFromDom2); codeCompilerMethodCallRestorer.Add_ICodeCompiler_CompileAssemblyFromDomBatch(CompileAssemblyFromDomBatch2); codeCompilerMethodCallRestorer.Add_ICodeCompiler_CompileAssemblyFromFile(CompileAssemblyFromFile2); codeCompilerMethodCallRestorer.Add_ICodeCompiler_CompileAssemblyFromFileBatch(CompileAssemblyFromFileBatch2); codeCompilerMethodCallRestorer.Add_ICodeCompiler_CompileAssemblyFromSource(CompileAssemblyFromSource2); codeCompilerMethodCallRestorer.Add_ICodeCompiler_CompileAssemblyFromSourceBatch(CompileAssemblyFromSourceBatch2); break; } } static MethodDef GetTheOnlyMethod(TypeDef type, string typeName, string methodName, string returnType, string parameters) { MethodDef foundMethod = null; foreach (var method in type.Methods) { if (!method.IsStatic || method.Body == null || method.HasGenericParameters) continue; if (method.IsPrivate) continue; if (!DotNetUtils.IsMethod(method, returnType, "(" + typeName + "," + parameters + ")")) continue; if (!DotNetUtils.CallsMethod(method, returnType + " " + typeName + "::" + methodName + "(" + parameters + ")")) continue; if (foundMethod != null) return null; foundMethod = method; } return foundMethod; } } }