/* Copyright (C) 2011 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 Mono.Cecil; using Mono.Cecil.Cil; using de4dot.deobfuscators; namespace de4dot.renamer { class Module : IResolver { IObfuscatedFile obfuscatedFile; MemberRefFinder memberRefFinder; TypeDefDict allTypes = new TypeDefDict(); IList> typeRefsToRename = new List>(); IList> methodRefsToRename = new List>(); IList> fieldRefsToRename = new List>(); List allMethods; public Func IsValidName { get { return obfuscatedFile.IsValidName; } } class RefToDef where R : MemberReference where D : R { public R reference; public D definition; public RefToDef(R reference, D definition) { this.reference = reference; this.definition = definition; } } public string Filename { get { return obfuscatedFile.Filename; } } public ModuleDefinition ModuleDefinition { get { return obfuscatedFile.ModuleDefinition; } } public string Pathname { get { return ModuleDefinition.FullyQualifiedName; } } public Module(IObfuscatedFile obfuscatedFile) { this.obfuscatedFile = obfuscatedFile; } public IEnumerable getAllTypes() { return allTypes.getAll(); } IEnumerable getRenamedTypeNames() { foreach (var typeDef in allTypes.getAll()) { if (typeDef.OldFullName != typeDef.TypeDefinition.FullName) { yield return new Renamed { OldName = typeDef.OldFullName, NewName = typeDef.TypeDefinition.FullName }; } } } public void onBeforeRenamingTypeDefinitions() { if (obfuscatedFile.RemoveNamespaceWithOneType) removeOneClassNamespaces(); } void removeOneClassNamespaces() { var nsToTypes = new Dictionary>(StringComparer.Ordinal); foreach (var typeDef in allTypes.getAll()) { List list; var ns = typeDef.TypeDefinition.Namespace; if (string.IsNullOrEmpty(ns)) continue; if (IsValidName(ns)) continue; if (!nsToTypes.TryGetValue(ns, out list)) nsToTypes[ns] = list = new List(); list.Add(typeDef); } var sortedNamespaces = new List>(nsToTypes.Values); sortedNamespaces.Sort((a, b) => { return string.CompareOrdinal(a[0].TypeDefinition.Namespace, b[0].TypeDefinition.Namespace); }); foreach (var list in sortedNamespaces) { const int maxClasses = 1; if (list.Count != maxClasses) continue; var ns = list[0].TypeDefinition.Namespace; Log.v("Removing namespace: {0}", ns); foreach (var type in list) type.NewNamespace = ""; } } static string renameResourceString(string s, string oldTypeName, string newTypeName) { if (!Utils.StartsWith(s, oldTypeName, StringComparison.Ordinal)) return s; if (s.Length == oldTypeName.Length) return newTypeName; // s.Length > oldTypeName.Length if (s[oldTypeName.Length] != '.') return s; if (!s.EndsWith(".resources", StringComparison.Ordinal)) return s; return newTypeName + s.Substring(oldTypeName.Length); } public void renameResources() { var renamedTypes = new List(getRenamedTypeNames()); // Rename the longest names first. Otherwise eg. b.g.resources could be renamed // Class0.g.resources instead of Class1.resources when b.g was renamed Class1. renamedTypes.Sort((a, b) => { if (a.OldName.Length > b.OldName.Length) return -1; if (a.OldName.Length < b.OldName.Length) return 1; return 0; }); renameResourceNamesInCode(renamedTypes); renameResources(renamedTypes); } void renameResourceNamesInCode(IEnumerable renamedTypes) { // This is needed to speed up this method var oldToNewTypeName = new Dictionary(StringComparer.Ordinal); foreach (var renamed in renamedTypes) oldToNewTypeName[renamed.OldName] = renamed.NewName; List validResourceNames = new List(); if (ModuleDefinition.Resources != null) { foreach (var resource in ModuleDefinition.Resources) { var name = resource.Name; if (name.EndsWith(".resources", StringComparison.Ordinal)) validResourceNames.Add(name); } } foreach (var method in allMethods) { if (!method.HasBody) continue; foreach (var instr in method.Body.Instructions) { if (instr.OpCode != OpCodes.Ldstr) continue; var s = (string)instr.Operand; if (string.IsNullOrEmpty(s)) continue; // Ignore emtpy strings since we'll get lots of false warnings string newName = null; string oldName = null; if (oldToNewTypeName.ContainsKey(s)) { oldName = s; newName = oldToNewTypeName[s]; } else if (s.EndsWith(".resources", StringComparison.Ordinal)) { // This should rarely, if ever, execute... foreach (var renamed in renamedTypes) { // Slow loop var newName2 = renameResourceString(s, renamed.OldName, renamed.NewName); if (newName2 != s) { newName = newName2; oldName = renamed.OldName; break; } } } if (newName == null || string.IsNullOrEmpty(oldName)) continue; bool isValid = false; foreach (var validName in validResourceNames) { if (Utils.StartsWith(validName, oldName, StringComparison.Ordinal)) { isValid = true; break; } } if (!isValid) continue; if (s == "" || !obfuscatedFile.RenameResourcesInCode) Log.v("Possible resource name in code: '{0}' => '{1}' in method {2}", s, newName, method); else { instr.Operand = newName; Log.v("Renamed resource string in code: '{0}' => '{1}' ({2})", s, newName, method); break; } } } } void renameResources(IEnumerable renamedTypes) { if (ModuleDefinition.Resources == null) return; foreach (var resource in ModuleDefinition.Resources) { var s = resource.Name; foreach (var renamed in renamedTypes) { var newName = renameResourceString(s, renamed.OldName, renamed.NewName); if (newName != s) { resource.Name = newName; Log.v("Renamed resource in resources: {0} => {1}", s, newName); break; } } } } public void findAllMemberReferences(ref int typeIndex) { memberRefFinder = new MemberRefFinder(); memberRefFinder.findAll(ModuleDefinition, ModuleDefinition.Types); allMethods = new List(memberRefFinder.methodDefinitions.Keys); var allTypesList = new List(); foreach (var type in new List(memberRefFinder.typeDefinitions.Keys)) { memberRefFinder.removeTypeDefinition(type); var typeDef = new TypeDef(type, this, typeIndex++); allTypes.add(typeDef); allTypesList.Add(typeDef); typeDef.addMembers(); foreach (var ev in type.Events) memberRefFinder.removeEventDefinition(ev); foreach (var field in type.Fields) memberRefFinder.removeFieldDefinition(field); foreach (var method in type.Methods) memberRefFinder.removeMethodDefinition(method); foreach (var property in type.Properties) memberRefFinder.removePropertyDefinition(property); } // Add all nested types to the correct TypeDef's types list var allTypesCopy = new List(allTypesList); var typeToIndex = new Dictionary(); for (int i = 0; i < allTypesList.Count; i++) typeToIndex[allTypesList[i].TypeDefinition] = i; foreach (var typeDef in allTypesList) { if (typeDef.TypeDefinition.NestedTypes == null) continue; foreach (var nestedTypeDefinition in typeDef.TypeDefinition.NestedTypes) { int index = typeToIndex[nestedTypeDefinition]; var nestedTypeDef = allTypesCopy[index]; allTypesCopy[index] = null; if (nestedTypeDef == null) // Impossible throw new ApplicationException("Nested type belongs to two or more types"); typeDef.add(nestedTypeDef); nestedTypeDef.NestingType = typeDef; } } // Make sure we got all definitions if (memberRefFinder.typeDefinitions.Count > 0) throw new ApplicationException("There are types left"); if (memberRefFinder.eventDefinitions.Count > 0) throw new ApplicationException("There are events left"); if (memberRefFinder.fieldDefinitions.Count > 0) throw new ApplicationException("There are fields left"); if (memberRefFinder.methodDefinitions.Count > 0) throw new ApplicationException("There are methods left"); if (memberRefFinder.propertyDefinitions.Count > 0) throw new ApplicationException("There are properties left"); } public void resolveAllRefs(IResolver resolver) { foreach (var typeRef in memberRefFinder.typeReferences.Keys) { var typeDef = resolver.resolve(typeRef); if (typeDef != null) typeRefsToRename.Add(new RefToDef(typeRef, typeDef.TypeDefinition)); } foreach (var methodRef in memberRefFinder.methodReferences.Keys) { var methodDef = resolver.resolve(methodRef); if (methodDef != null) methodRefsToRename.Add(new RefToDef(methodRef, methodDef.MethodDefinition)); } foreach (var fieldRef in memberRefFinder.fieldReferences.Keys) { var fieldDef = resolver.resolve(fieldRef); if (fieldDef != null) fieldRefsToRename.Add(new RefToDef(fieldRef, fieldDef.FieldDefinition)); } } public void renameTypeReferences() { foreach (var refToDef in typeRefsToRename) { refToDef.reference.Name = refToDef.definition.Name; refToDef.reference.Namespace = refToDef.definition.Namespace; } } public void renameMemberReferences() { foreach (var refToDef in methodRefsToRename) refToDef.reference.Name = refToDef.definition.Name; foreach (var refToDef in fieldRefsToRename) refToDef.reference.Name = refToDef.definition.Name; } public void onTypesRenamed() { rebuildAllTypesDict(); } void rebuildAllTypesDict() { var newAllTypes = new TypeDefDict(); foreach (var typeDef in allTypes.getAll()) { typeDef.onTypesRenamed(); newAllTypes.add(typeDef); } allTypes = newAllTypes; } static TypeReference getNonGenericTypeReference(TypeReference typeReference) { if (typeReference == null) return null; if (!typeReference.IsGenericInstance) return typeReference; var type = (GenericInstanceType)typeReference; return type.ElementType; } public TypeDef resolve(TypeReference typeReference) { return this.allTypes.find(getNonGenericTypeReference(typeReference)); } public MethodDef resolve(MethodReference methodReference) { var typeDef = this.allTypes.find(getNonGenericTypeReference(methodReference.DeclaringType)); if (typeDef == null) return null; return typeDef.find(methodReference); } public FieldDef resolve(FieldReference fieldReference) { var typeDef = this.allTypes.find(getNonGenericTypeReference(fieldReference.DeclaringType)); if (typeDef == null) return null; return typeDef.find(fieldReference); } } }