diff --git a/de4dot.code/de4dot.code.csproj b/de4dot.code/de4dot.code.csproj index d124243d..bb780311 100644 --- a/de4dot.code/de4dot.code.csproj +++ b/de4dot.code/de4dot.code.csproj @@ -87,6 +87,7 @@ + diff --git a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs index 50d95bdb..7ddcf055 100644 --- a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs @@ -29,6 +29,7 @@ namespace de4dot.code.deobfuscators.DeepSea { BoolOption removeInlinedMethods; BoolOption decryptResources; BoolOption dumpEmbeddedAssemblies; + BoolOption restoreFields; public DeobfuscatorInfo() : base() { @@ -36,6 +37,7 @@ namespace de4dot.code.deobfuscators.DeepSea { removeInlinedMethods = new BoolOption(null, makeArgName("remove-inlined"), "Remove inlined methods", true); decryptResources = new BoolOption(null, makeArgName("rsrc"), "Decrypt resources", true); dumpEmbeddedAssemblies = new BoolOption(null, makeArgName("embedded"), "Dump embedded assemblies", true); + restoreFields = new BoolOption(null, makeArgName("fields"), "Restore fields", true); } public override string Name { @@ -53,6 +55,7 @@ namespace de4dot.code.deobfuscators.DeepSea { RemoveInlinedMethods = removeInlinedMethods.get(), DecryptResources = decryptResources.get(), DumpEmbeddedAssemblies = dumpEmbeddedAssemblies.get(), + RestoreFields = restoreFields.get(), }); } @@ -62,6 +65,7 @@ namespace de4dot.code.deobfuscators.DeepSea { removeInlinedMethods, decryptResources, dumpEmbeddedAssemblies, + restoreFields, }; } } @@ -74,12 +78,14 @@ namespace de4dot.code.deobfuscators.DeepSea { StringDecrypter stringDecrypter; ResourceResolver resourceResolver; AssemblyResolver assemblyResolver; + FieldsRestorer fieldsRestorer; internal class Options : OptionsBase { public bool InlineMethods { get; set; } public bool RemoveInlinedMethods { get; set; } public bool DecryptResources { get; set; } public bool DumpEmbeddedAssemblies { get; set; } + public bool RestoreFields { get; set; } } public override string Type { @@ -170,6 +176,11 @@ done: public override void deobfuscateBegin() { base.deobfuscateBegin(); + if (options.RestoreFields) { + fieldsRestorer = new FieldsRestorer(module); + fieldsRestorer.initialize(); + } + foreach (var method in stringDecrypter.DecrypterMethods) { staticStringInliner.add(method, (method2, args) => { return stringDecrypter.decrypt(method2, args); @@ -213,9 +224,18 @@ done: addMethodToBeRemoved(assemblyResolver.HandlerMethod, "Assembly resolver handler method"); } + public override void deobfuscateMethodEnd(blocks.Blocks blocks) { + if (options.RestoreFields) + fieldsRestorer.deobfuscate(blocks); + base.deobfuscateMethodEnd(blocks); + } + public override void deobfuscateEnd() { removeInlinedMethods(); + if (options.RestoreFields) + addTypesToBeRemoved(fieldsRestorer.FieldStructs, "Type with moved fields"); + if (Operations.DecryptStrings != OpDecryptString.None) { addMethodsToBeRemoved(stringDecrypter.DecrypterMethods, "String decrypter method"); stringDecrypter.cleanup(); diff --git a/de4dot.code/deobfuscators/DeepSea/FieldsRestorer.cs b/de4dot.code/deobfuscators/DeepSea/FieldsRestorer.cs new file mode 100644 index 00000000..af67fcb0 --- /dev/null +++ b/de4dot.code/deobfuscators/DeepSea/FieldsRestorer.cs @@ -0,0 +1,164 @@ +/* + Copyright (C) 2011-2012 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 Mono.Cecil; +using Mono.Cecil.Cil; +using de4dot.blocks; + +namespace de4dot.code.deobfuscators.DeepSea { + // DS 4.x can move fields from a class to a struct. This class restores the fields. + class FieldsRestorer { + ModuleDefinition module; + TypeDefinitionDict structToOwner = new TypeDefinitionDict(); + Dictionary oldFieldToken = new Dictionary(); + + public List FieldStructs { + get { + var list = new List(structToOwner.Count); + foreach (var structType in structToOwner.getKeys()) { + if (structType.Methods.Count != 0) + continue; + + list.Add(structType); + } + return list; + } + } + + public FieldsRestorer(ModuleDefinition module) { + this.module = module; + } + + public void initialize() { + foreach (var kv in getMovedTypes()) { + var structType = kv.Key; + var ownerType = kv.Value; + structToOwner.add(structType, ownerType); + + for (int i = 0; i < ownerType.Fields.Count; i++) { + if (DotNetUtils.getType(module, ownerType.Fields[i].FieldType) != structType) + continue; + oldFieldToken[ownerType.Fields[i].MetadataToken.ToInt32()] = true; + ownerType.Fields.RemoveAt(i); + break; + } + + var structTypeFields = new List(structType.Fields); + structType.Fields.Clear(); + foreach (var field in structTypeFields) + ownerType.Fields.Add(field); + + // Add a field so peverify won't complain if this type isn't removed + structType.Fields.Add(new FieldDefinition("a", FieldAttributes.Public, module.TypeSystem.Byte)); + } + } + + Dictionary getMovedTypes() { + var fieldTypeToTypes = new Dictionary>(); + foreach (var type in module.GetTypes()) { + foreach (var field in type.Fields) { + var fieldType = DotNetUtils.getType(module, field.FieldType); + if (fieldType == null || !fieldType.IsValueType) + continue; + if ((fieldType.Attributes & ~TypeAttributes.Sealed) != TypeAttributes.NestedAssembly) + continue; + if (fieldType.NestedTypes.Count > 0) + continue; + if (fieldType.GenericParameters.Count > 0) + continue; + if (fieldType.Fields.Count == 0) + continue; + if (hasNonStaticMethods(fieldType)) + continue; + + List list; + if (!fieldTypeToTypes.TryGetValue(fieldType, out list)) + fieldTypeToTypes[fieldType] = list = new List(); + list.Add(type); + } + } + + var candidates = new Dictionary(); + foreach (var kv in fieldTypeToTypes) { + if (kv.Value.Count != 1) + continue; + candidates[kv.Key] = kv.Value[0]; + } + + foreach (var type in module.GetTypes()) { + foreach (var field in type.Fields) { + if (field.DeclaringType != type) + removeType(candidates, field.FieldType); + } + foreach (var method in type.Methods) { + removeType(candidates, method.MethodReturnType.ReturnType); + foreach (var parameter in method.Parameters) + removeType(candidates, parameter.ParameterType); + if (method.Body != null) { + foreach (var local in method.Body.Variables) + removeType(candidates, local.VariableType); + } + } + } + + return candidates; + } + + void removeType(Dictionary candidates, TypeReference type) { + var typeDef = DotNetUtils.getType(module, type); + if (typeDef == null) + return; + candidates.Remove(typeDef); + } + + static bool hasNonStaticMethods(TypeDefinition type) { + foreach (var method in type.Methods) { + if (method.Name == ".cctor") + continue; + if (!method.IsStatic) + return true; + if (method.GenericParameters.Count > 0) + continue; + if (method.Body == null) + return true; + if (method.HasPInvokeInfo || method.PInvokeInfo != null) + return true; + } + return false; + } + + public void deobfuscate(Blocks blocks) { + foreach (var block in blocks.MethodBlocks.getAllBlocks()) { + var instrs = block.Instructions; + for (int i = instrs.Count - 1; i >= 0; i--) { + var instr = instrs[i]; + if (instr.OpCode.Code != Code.Ldflda) + continue; + var field = instr.Operand as FieldReference; + if (field == null) + continue; + if (!oldFieldToken.ContainsKey(field.MetadataToken.ToInt32())) + continue; + instrs.RemoveAt(i); + } + } + } + } +}