/* 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 dnlib.DotNet; using dnlib.DotNet.Emit; using dnlib.DotNet.Writer; using dnlib.PE; using de4dot.blocks; using de4dot.blocks.cflow; namespace de4dot.code.deobfuscators { public abstract class DeobfuscatorBase : IDeobfuscator, IModuleWriterListener { public const string DEFAULT_VALID_NAME_REGEX = @"^[a-zA-Z_<{$][a-zA-Z_0-9<>{}$.`-]*$"; public const string DEFAULT_ASIAN_VALID_NAME_REGEX = @"^[\u2E80-\u9FFFa-zA-Z_<{$][\u2E80-\u9FFFa-zA-Z_0-9<>{}$.`-]*$"; class RemoveInfo { public T obj; public string reason; public RemoveInfo(T obj, string reason) { this.obj = obj; this.reason = reason; } } OptionsBase optionsBase; protected ModuleDefMD module; protected StaticStringInliner staticStringInliner = new StaticStringInliner(); IList> typesToRemove = new List>(); IList> methodsToRemove = new List>(); IList> fieldsToRemove = new List>(); IList> attrsToRemove = new List>(); IList> resourcesToRemove = new List>(); List namesToPossiblyRemove = new List(); MethodCallRemover methodCallRemover = new MethodCallRemover(); byte[] moduleBytes; protected InitializedDataCreator initializedDataCreator; bool keepTypes; MetaDataFlags? mdFlags; Dictionary objectsThatMustBeKept = new Dictionary(); protected byte[] ModuleBytes { get { return moduleBytes; } set { moduleBytes = value; } } public class OptionsBase : IDeobfuscatorOptions { public bool RenameResourcesInCode { get; set; } public NameRegexes ValidNameRegex { get; set; } public bool DecryptStrings { get; set; } public OptionsBase() { RenameResourcesInCode = true; } } public IDeobfuscatorOptions TheOptions { get { return optionsBase; } } public IOperations Operations { get; set; } public IDeobfuscatedFile DeobfuscatedFile { get; set; } public virtual StringFeatures StringFeatures { get; set; } public virtual RenamingOptions RenamingOptions { get; set; } public DecrypterType DefaultDecrypterType { get; set; } public virtual MetaDataFlags MetaDataFlags { get { return mdFlags ?? Operations.MetaDataFlags; } } public abstract string Type { get; } public abstract string TypeLong { get; } public abstract string Name { get; } protected virtual bool CanInlineMethods { get { return false; } } protected bool KeepTypes { get { return keepTypes; } set { keepTypes = value; } } protected bool CanRemoveTypes { get { return !Operations.KeepObfuscatorTypes && !KeepTypes; } } protected bool CanRemoveStringDecrypterType { get { return Operations.DecryptStrings != OpDecryptString.None && staticStringInliner.InlinedAllCalls; } } public virtual IEnumerable BlocksDeobfuscators { get { var list = new List(); if (CanInlineMethods) list.Add(new MethodCallInliner(false)); return list; } } public DeobfuscatorBase(OptionsBase optionsBase) { this.optionsBase = optionsBase; StringFeatures = StringFeatures.AllowAll; DefaultDecrypterType = DecrypterType.Static; } public virtual byte[] UnpackNativeFile(IPEImage peImage) { return null; } public virtual void Initialize(ModuleDefMD module) { SetModule(module); } protected void SetModule(ModuleDefMD module) { this.module = module; initializedDataCreator = new InitializedDataCreator(module); } protected void PreserveTokensAndTypes() { keepTypes = true; mdFlags = Operations.MetaDataFlags; mdFlags |= MetaDataFlags.PreserveRids | MetaDataFlags.PreserveUSOffsets | MetaDataFlags.PreserveBlobOffsets | MetaDataFlags.PreserveExtraSignatureData; } protected virtual bool CheckValidName(string name) { return optionsBase.ValidNameRegex.IsMatch(name); } public virtual int Detect() { ScanForObfuscator(); return DetectInternal(); } protected abstract void ScanForObfuscator(); protected abstract int DetectInternal(); public virtual bool GetDecryptedModule(int count, ref byte[] newFileData, ref DumpedMethods dumpedMethods) { return false; } public virtual IDeobfuscator ModuleReloaded(ModuleDefMD module) { throw new ApplicationException("moduleReloaded() must be overridden by the deobfuscator"); } public virtual void DeobfuscateBegin() { ModuleBytes = null; } public virtual void DeobfuscateMethodBegin(Blocks blocks) { } public virtual void DeobfuscateMethodEnd(Blocks blocks) { RemoveMethodCalls(blocks); } public virtual void DeobfuscateStrings(Blocks blocks) { staticStringInliner.Decrypt(blocks); } public virtual bool DeobfuscateOther(Blocks blocks) { return false; } public virtual void DeobfuscateEnd() { // Make sure the TypeDefCache isn't enabled while we modify types or remove stuff bool cacheState = module.EnableTypeDefFindCache; module.EnableTypeDefFindCache = false; if (CanRemoveTypes) { InitializeObjectsToKeepFromVTableFixups(); RemoveTypesWithInvalidBaseTypes(); DeleteEmptyCctors(); DeleteMethods(); DeleteFields(); DeleteCustomAttributes(); DeleteOtherAttributes(); DeleteTypes(); DeleteDllResources(); } RestoreBaseType(); FixMDHeaderVersion(); module.EnableTypeDefFindCache = cacheState; } void InitializeObjectsToKeepFromVTableFixups() { var fixups = module.VTableFixups; if (fixups == null || fixups.VTables.Count == 0) return; foreach (var vtable in fixups) { if (vtable == null) continue; foreach (var method in vtable) { if (method == null) continue; objectsThatMustBeKept[method] = true; } } } bool MustKeepObject(object o) { return o != null && objectsThatMustBeKept.ContainsKey(o); } static bool IsTypeWithInvalidBaseType(TypeDef moduleType, TypeDef type) { return type.BaseType == null && !type.IsInterface && type != moduleType; } void RestoreBaseType() { var moduleType = DotNetUtils.GetModuleType(module); foreach (var type in module.GetTypes()) { if (!IsTypeWithInvalidBaseType(moduleType, type)) continue; var corSig = module.CorLibTypes.GetCorLibTypeSig(type); if (corSig != null && corSig.ElementType == ElementType.Object) continue; Logger.v("Adding System.Object as base type: {0} ({1:X8})", Utils.RemoveNewlines(type), type.MDToken.ToInt32()); type.BaseType = module.CorLibTypes.Object.TypeDefOrRef; } } void FixMDHeaderVersion() { // Version 1.1 supports generics but it's a little different. Most tools // will have a problem reading the MD tables, so switch to the standard v2.0. if (module.TablesHeaderVersion == 0x0101) module.TablesHeaderVersion = 0x0200; } void RemoveTypesWithInvalidBaseTypes() { var moduleType = DotNetUtils.GetModuleType(module); foreach (var type in module.GetTypes()) { if (!IsTypeWithInvalidBaseType(moduleType, type) || MustKeepObject(type)) continue; AddTypeToBeRemoved(type, "Invalid type with no base type (anti-reflection)"); } } protected void FixEnumTypes() { foreach (var type in module.GetTypes()) { if (!type.IsEnum) continue; foreach (var field in type.Fields) { if (field.IsStatic) continue; field.IsRuntimeSpecialName = true; field.IsSpecialName = true; } } } protected void FixInterfaces() { foreach (var type in module.GetTypes()) { if (!type.IsInterface) continue; type.IsSealed = false; } } public abstract IEnumerable GetStringDecrypterMethods(); class MethodCallRemover { Dictionary> methodNameInfos = new Dictionary>(); MethodDefAndDeclaringTypeDict> methodRefInfos = new MethodDefAndDeclaringTypeDict>(); void CheckMethod(IMethod methodToBeRemoved) { var sig = methodToBeRemoved.MethodSig; if (sig.Params.Count != 0) throw new ApplicationException(string.Format("Method takes params: {0}", methodToBeRemoved)); if (sig.RetType.ElementType != ElementType.Void) throw new ApplicationException(string.Format("Method has a return value: {0}", methodToBeRemoved)); } public void Add(string method, MethodDef methodToBeRemoved) { if (methodToBeRemoved == null) return; CheckMethod(methodToBeRemoved); MethodDefAndDeclaringTypeDict dict; if (!methodNameInfos.TryGetValue(method, out dict)) methodNameInfos[method] = dict = new MethodDefAndDeclaringTypeDict(); dict.Add(methodToBeRemoved, true); } public void Add(MethodDef method, MethodDef methodToBeRemoved) { if (method == null || methodToBeRemoved == null) return; CheckMethod(methodToBeRemoved); var dict = methodRefInfos.Find(method); if (dict == null) methodRefInfos.Add(method, dict = new MethodDefAndDeclaringTypeDict()); dict.Add(methodToBeRemoved, true); } public void RemoveAll(Blocks blocks) { var allBlocks = blocks.MethodBlocks.GetAllBlocks(); RemoveAll(allBlocks, blocks, blocks.Method.Name.String); RemoveAll(allBlocks, blocks, blocks.Method); } void RemoveAll(IList allBlocks, Blocks blocks, string method) { MethodDefAndDeclaringTypeDict info; if (!methodNameInfos.TryGetValue(method, out info)) return; RemoveCalls(allBlocks, blocks, info); } void RemoveAll(IList allBlocks, Blocks blocks, MethodDef method) { var info = methodRefInfos.Find(method); if (info == null) return; RemoveCalls(allBlocks, blocks, info); } void RemoveCalls(IList allBlocks, Blocks blocks, MethodDefAndDeclaringTypeDict info) { var instrsToDelete = new List(); foreach (var block in allBlocks) { instrsToDelete.Clear(); for (int i = 0; i < block.Instructions.Count; i++) { var instr = block.Instructions[i]; if (instr.OpCode != OpCodes.Call) continue; var destMethod = instr.Operand as IMethod; if (destMethod == null) continue; if (info.Find(destMethod)) { Logger.v("Removed call to {0}", Utils.RemoveNewlines(destMethod)); instrsToDelete.Add(i); } } block.Remove(instrsToDelete); } } } public void AddCctorInitCallToBeRemoved(MethodDef methodToBeRemoved) { methodCallRemover.Add(".cctor", methodToBeRemoved); } public void AddModuleCctorInitCallToBeRemoved(MethodDef methodToBeRemoved) { methodCallRemover.Add(DotNetUtils.GetModuleTypeCctor(module), methodToBeRemoved); } public void AddCtorInitCallToBeRemoved(MethodDef methodToBeRemoved) { methodCallRemover.Add(".ctor", methodToBeRemoved); } public void AddCallToBeRemoved(MethodDef method, MethodDef methodToBeRemoved) { methodCallRemover.Add(method, methodToBeRemoved); } void RemoveMethodCalls(Blocks blocks) { methodCallRemover.RemoveAll(blocks); } protected void AddMethodsToBeRemoved(IEnumerable methods, string reason) { foreach (var method in methods) AddMethodToBeRemoved(method, reason); } protected void AddMethodToBeRemoved(MethodDef method, string reason) { if (method != null) methodsToRemove.Add(new RemoveInfo(method, reason)); } protected void AddFieldsToBeRemoved(IEnumerable fields, string reason) { foreach (var field in fields) AddFieldToBeRemoved(field, reason); } protected void AddFieldToBeRemoved(FieldDef field, string reason) { if (field != null) fieldsToRemove.Add(new RemoveInfo(field, reason)); } protected void AddAttributesToBeRemoved(IEnumerable attrs, string reason) { foreach (var attr in attrs) AddAttributeToBeRemoved(attr, reason); } protected void AddAttributeToBeRemoved(TypeDef attr, string reason) { if (attr == null) return; AddTypeToBeRemoved(attr, reason); attrsToRemove.Add(new RemoveInfo(attr, reason)); } protected void AddTypesToBeRemoved(IEnumerable types, string reason) { foreach (var type in types) AddTypeToBeRemoved(type, reason); } protected void AddTypeToBeRemoved(TypeDef type, string reason) { if (type != null) typesToRemove.Add(new RemoveInfo(type, reason)); } protected void AddResourceToBeRemoved(Resource resource, string reason) { if (resource != null) resourcesToRemove.Add(new RemoveInfo(resource, reason)); } void DeleteEmptyCctors() { var emptyCctorsToRemove = new List(); foreach (var type in module.GetTypes()) { var cctor = type.FindStaticConstructor(); if (cctor != null && DotNetUtils.IsEmpty(cctor) && !MustKeepObject(cctor)) emptyCctorsToRemove.Add(cctor); } if (emptyCctorsToRemove.Count == 0) return; Logger.v("Removing empty .cctor methods"); Logger.Instance.Indent(); foreach (var cctor in emptyCctorsToRemove) { var type = cctor.DeclaringType; if (type == null) continue; if (type.Methods.Remove(cctor)) Logger.v("{0:X8}, type: {1} ({2:X8})", cctor.MDToken.ToUInt32(), Utils.RemoveNewlines(type), type.MDToken.ToUInt32()); } Logger.Instance.DeIndent(); } void DeleteMethods() { if (methodsToRemove.Count == 0) return; Logger.v("Removing methods"); Logger.Instance.Indent(); foreach (var info in methodsToRemove) { var method = info.obj; if (method == null || MustKeepObject(method)) continue; var type = method.DeclaringType; if (type == null) continue; if (type.Methods.Remove(method)) Logger.v("Removed method {0} ({1:X8}) (Type: {2}) (reason: {3})", Utils.RemoveNewlines(method), method.MDToken.ToUInt32(), Utils.RemoveNewlines(type), info.reason); } Logger.Instance.DeIndent(); } void DeleteFields() { if (fieldsToRemove.Count == 0) return; Logger.v("Removing fields"); Logger.Instance.Indent(); foreach (var info in fieldsToRemove) { var field = info.obj; if (field == null || MustKeepObject(field)) continue; var type = field.DeclaringType; if (type == null) continue; if (type.Fields.Remove(field)) Logger.v("Removed field {0} ({1:X8}) (Type: {2}) (reason: {3})", Utils.RemoveNewlines(field), field.MDToken.ToUInt32(), Utils.RemoveNewlines(type), info.reason); } Logger.Instance.DeIndent(); } void DeleteTypes() { var types = module.Types; if (types == null || typesToRemove.Count == 0) return; Logger.v("Removing types"); Logger.Instance.Indent(); var moduleType = DotNetUtils.GetModuleType(module); foreach (var info in typesToRemove) { var typeDef = info.obj; if (typeDef == null || typeDef == moduleType || MustKeepObject(typeDef)) continue; bool removed; if (typeDef.DeclaringType != null) removed = typeDef.DeclaringType.NestedTypes.Remove(typeDef); else removed = types.Remove(typeDef); if (removed) Logger.v("Removed type {0} ({1:X8}) (reason: {2})", Utils.RemoveNewlines(typeDef), typeDef.MDToken.ToUInt32(), info.reason); } Logger.Instance.DeIndent(); } void DeleteCustomAttributes() { if (attrsToRemove.Count == 0) return; Logger.v("Removing custom attributes"); Logger.Instance.Indent(); DeleteCustomAttributes(module.CustomAttributes); if (module.Assembly != null) DeleteCustomAttributes(module.Assembly.CustomAttributes); Logger.Instance.DeIndent(); } void DeleteCustomAttributes(IList customAttrs) { if (customAttrs == null) return; foreach (var info in attrsToRemove) { var typeDef = info.obj; if (typeDef == null) continue; for (int i = 0; i < customAttrs.Count; i++) { if (new SigComparer().Equals(typeDef, customAttrs[i].AttributeType)) { customAttrs.RemoveAt(i); i--; Logger.v("Removed custom attribute {0} ({1:X8}) (reason: {2})", Utils.RemoveNewlines(typeDef), typeDef.MDToken.ToUInt32(), info.reason); break; } } } } void DeleteOtherAttributes() { Logger.v("Removing other attributes"); Logger.Instance.Indent(); DeleteOtherAttributes(module.CustomAttributes); if (module.Assembly != null) DeleteOtherAttributes(module.Assembly.CustomAttributes); Logger.Instance.DeIndent(); } void DeleteOtherAttributes(IList customAttributes) { for (int i = customAttributes.Count - 1; i >= 0; i--) { var attr = customAttributes[i].TypeFullName; if (attr == "System.Runtime.CompilerServices.SuppressIldasmAttribute") { Logger.v("Removed attribute {0}", Utils.RemoveNewlines(attr)); customAttributes.RemoveAt(i); } } } void DeleteDllResources() { if (!module.HasResources || resourcesToRemove.Count == 0) return; Logger.v("Removing resources"); Logger.Instance.Indent(); foreach (var info in resourcesToRemove) { var resource = info.obj; if (resource == null || MustKeepObject(resource)) continue; if (module.Resources.Remove(resource)) Logger.v("Removed resource {0} (reason: {1})", Utils.ToCsharpString(resource.Name), info.reason); } Logger.Instance.DeIndent(); } protected void SetInitLocals() { foreach (var type in module.GetTypes()) { foreach (var method in type.Methods) { if (IsFatHeader(method)) method.Body.InitLocals = true; } } } static bool IsFatHeader(MethodDef method) { if (method == null || method.Body == null) return false; var body = method.Body; if (body.InitLocals || body.MaxStack > 8) return true; if (body.Variables.Count > 0) return true; if (body.ExceptionHandlers.Count > 0) return true; if (GetCodeSize(method) > 63) return true; return false; } static int GetCodeSize(MethodDef method) { if (method == null || method.Body == null) return 0; int size = 0; foreach (var instr in method.Body.Instructions) size += instr.GetSize(); return size; } public override string ToString() { return Name; } protected void FindPossibleNamesToRemove(MethodDef method) { if (method == null || !method.HasBody) return; foreach (var instr in method.Body.Instructions) { if (instr.OpCode == OpCodes.Ldstr) namesToPossiblyRemove.Add((string)instr.Operand); } } protected void AddResources(string reason) { if (!module.HasResources) return; foreach (var name in namesToPossiblyRemove) { foreach (var resource in module.Resources) { if (resource.Name == name) { AddResourceToBeRemoved(resource, reason); break; } } } } protected bool RemoveProxyDelegates(ProxyCallFixerBase proxyCallFixer) { return RemoveProxyDelegates(proxyCallFixer, true); } protected bool RemoveProxyDelegates(ProxyCallFixerBase proxyCallFixer, bool removeCreators) { if (proxyCallFixer.Errors != 0) { Logger.v("Not removing proxy delegates and creator type since errors were detected."); return false; } AddTypesToBeRemoved(proxyCallFixer.DelegateTypes, "Proxy delegate type"); if (removeCreators && proxyCallFixer.RemovedDelegateCreatorCalls > 0) { AddTypesToBeRemoved(proxyCallFixer.DelegateCreatorTypes, "Proxy delegate creator type"); foreach (var tuple in proxyCallFixer.OtherMethods) AddMethodToBeRemoved(tuple.Item1, tuple.Item2); } return true; } protected Resource GetResource(IEnumerable strings) { return DotNetUtils.GetResource(module, strings); } protected CustomAttribute GetAssemblyAttribute(IType attr) { if (module.Assembly == null) return null; return module.Assembly.CustomAttributes.Find(attr); } protected CustomAttribute GetModuleAttribute(IType attr) { return module.CustomAttributes.Find(attr); } protected bool HasMetadataStream(string name) { foreach (var stream in module.MetaData.AllStreams) { if (stream.Name == name) return true; } return false; } List GetObjectsToRemove(IList> removeThese) where T : class, ICodedToken { var list = new List(removeThese.Count); foreach (var info in removeThese) { if (info.obj != null) list.Add(info.obj); } return list; } protected List GetTypesToRemove() { return GetObjectsToRemove(typesToRemove); } protected List GetMethodsToRemove() { return GetObjectsToRemove(methodsToRemove); } public virtual bool IsValidNamespaceName(string ns) { if (ns == null) return false; foreach (var part in ns.Split(new char[] { '.' })) { if (!CheckValidName(part)) return false; } return true; } public virtual bool IsValidTypeName(string name) { return name != null && CheckValidName(name); } public virtual bool IsValidMethodName(string name) { return name != null && CheckValidName(name); } public virtual bool IsValidPropertyName(string name) { return name != null && CheckValidName(name); } public virtual bool IsValidEventName(string name) { return name != null && CheckValidName(name); } public virtual bool IsValidFieldName(string name) { return name != null && CheckValidName(name); } public virtual bool IsValidGenericParamName(string name) { return name != null && CheckValidName(name); } public virtual bool IsValidMethodArgName(string name) { return name != null && CheckValidName(name); } public virtual bool IsValidMethodReturnArgName(string name) { return string.IsNullOrEmpty(name) || CheckValidName(name); } public virtual bool IsValidResourceKeyName(string name) { return name != null && CheckValidName(name); } public virtual void OnWriterEvent(ModuleWriterBase writer, ModuleWriterEvent evt) { } protected void FindAndRemoveInlinedMethods() { RemoveInlinedMethods(InlinedMethodsFinder.Find(module)); } protected void RemoveInlinedMethods(List inlinedMethods) { AddMethodsToBeRemoved(new UnusedMethodsFinder(module, inlinedMethods, GetRemovedMethods()).Find(), "Inlined method"); } protected MethodCollection GetRemovedMethods() { var removedMethods = new MethodCollection(); removedMethods.Add(GetMethodsToRemove()); removedMethods.AddAndNested(GetTypesToRemove()); return removedMethods; } protected bool IsTypeCalled(TypeDef decrypterType) { if (decrypterType == null) return false; var decrypterMethods = new MethodCollection(); decrypterMethods.AddAndNested(decrypterType); var removedMethods = GetRemovedMethods(); foreach (var type in module.GetTypes()) { foreach (var method in type.Methods) { if (method.Body == null) continue; if (decrypterMethods.Exists(method)) break; // decrypter type / nested type method if (removedMethods.Exists(method)) continue; foreach (var instr in method.Body.Instructions) { switch (instr.OpCode.Code) { case Code.Call: case Code.Callvirt: case Code.Newobj: var calledMethod = instr.Operand as IMethod; if (calledMethod == null) break; if (decrypterMethods.Exists(calledMethod)) return true; break; default: break; } } } } return false; } protected bool HasNativeMethods() { if (module.VTableFixups != null) return true; foreach (var type in module.GetTypes()) { foreach (var method in type.Methods) { var mb = method.MethodBody; if (mb == null) continue; if (mb is CilBody) continue; return true; } } return false; } protected static int ToInt32(bool b) { return b ? 1 : 0; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { } } }