diff --git a/de4dot.code/deobfuscators/SmartAssembly/AutomatedErrorReportingFinder.cs b/de4dot.code/deobfuscators/SmartAssembly/AutomatedErrorReportingFinder.cs index 03542b30..68614084 100644 --- a/de4dot.code/deobfuscators/SmartAssembly/AutomatedErrorReportingFinder.cs +++ b/de4dot.code/deobfuscators/SmartAssembly/AutomatedErrorReportingFinder.cs @@ -17,6 +17,7 @@ along with de4dot. If not, see . */ +using System; using System.Collections.Generic; using Mono.Cecil; using Mono.Cecil.Cil; @@ -27,6 +28,15 @@ namespace de4dot.code.deobfuscators.SmartAssembly { ModuleDefinition module; ExceptionLoggerRemover exceptionLoggerRemover = new ExceptionLoggerRemover(); TypeDefinition automatedErrorReportingType; + int constantArgs; + AerVersion aerVersion; + + enum AerVersion { + V0, + V1, + V2, + V3, + } public ExceptionLoggerRemover ExceptionLoggerRemover { get { return exceptionLoggerRemover; } @@ -57,6 +67,7 @@ namespace de4dot.code.deobfuscators.SmartAssembly { const int MIN_HELPER_METHODS = 6; + constantArgs = 0; var methods = new List(); MethodDefinition mainMethod = null; foreach (var method in type.Methods) { @@ -64,18 +75,39 @@ namespace de4dot.code.deobfuscators.SmartAssembly { methods.Add(method); else if (isAutomatedErrorReportingMethod(method)) mainMethod = method; + else + continue; + initializeConstantArgs(method); } - if (mainMethod == null || methods.Count < MIN_HELPER_METHODS) + if (mainMethod == null) return false; - methods.Sort((a, b) => Utils.compareInt32(a.Parameters.Count, b.Parameters.Count)); - for (int i = 0; i < methods.Count; i++) { - var method = methods[i]; - if (method.Parameters.Count != i + 1) - return false; - var methodCalls = DotNetUtils.getMethodCallCounts(method); - if (methodCalls.count(mainMethod.FullName) != 1) + if (isV0(type)) { + foreach (var method in type.Methods) { + if (method.IsStatic) + exceptionLoggerRemover.add(method); + } + } + else { + if (methods.Count < MIN_HELPER_METHODS) return false; + + if (isV1(mainMethod)) + aerVersion = AerVersion.V1; + else if (isV2(mainMethod)) + aerVersion = AerVersion.V2; + else + aerVersion = AerVersion.V3; + + methods.Sort((a, b) => Utils.compareInt32(a.Parameters.Count, b.Parameters.Count)); + for (int i = 0; i < methods.Count; i++) { + var method = methods[i]; + if (method.Parameters.Count != i + constantArgs) + return false; + var methodCalls = DotNetUtils.getMethodCallCounts(method); + if (methodCalls.count(mainMethod.FullName) != 1) + return false; + } } exceptionLoggerRemover.add(mainMethod); @@ -83,11 +115,78 @@ namespace de4dot.code.deobfuscators.SmartAssembly { exceptionLoggerRemover.add(method); automatedErrorReportingType = type; + if (aerVersion == AerVersion.V1) { + foreach (var method in type.Methods) { + if (DotNetUtils.isMethod(method, "System.Exception", "(System.Int32,System.Object[])")) + exceptionLoggerRemover.add(method); + } + } + initUnhandledExceptionFilterMethods(); return true; } + void initializeConstantArgs(MethodDefinition method) { + if (constantArgs > 0) + return; + constantArgs = getConstantArgs(method); + } + + static int getConstantArgs(MethodDefinition method) { + if (method.Parameters.Count >= 2) { + if (isV1(method) || isV2(method)) + return 2; + } + return 1; + } + + static string[] v0Fields = new string[] { + "System.Int32", + "System.Object[]", + }; + static bool isV0(TypeDefinition type) { + if (!new FieldTypes(type).exactly(v0Fields)) + return false; + if (type.Methods.Count != 3) + return false; + if (type.HasEvents || type.HasProperties) + return false; + MethodDefinition ctor = null, meth1 = null, meth2 = null; + foreach (var method in type.Methods) { + if (method.Name == ".ctor") { + ctor = method; + continue; + } + if (!method.IsStatic) + return false; + if (DotNetUtils.isMethod(method, "System.Exception", "(System.Int32,System.Object[])")) + meth1 = method; + else if (DotNetUtils.isMethod(method, "System.Exception", "(System.Int32,System.Exception,System.Object[])")) + meth2 = method; + else + return false; + } + + return ctor != null && meth1 != null && meth2 != null; + } + + static bool isV1(MethodDefinition method) { + if (method.Parameters.Count < 2) + return false; + var p0 = method.Parameters[0].ParameterType.FullName; + var p1 = method.Parameters[1].ParameterType.FullName; + return p0 == "System.Int32" && p1 == "System.Exception"; + } + + static bool isV2(MethodDefinition method) { + if (method.Parameters.Count < 2) + return false; + var p0 = method.Parameters[0].ParameterType.FullName; + var p1 = method.Parameters[1].ParameterType.FullName; + return p0 == "System.Exception" && p1 == "System.Int32"; + } + bool isAutomatedErrorReportingMethodHelper(MethodDefinition method) { if (!method.HasBody || !method.IsStatic || method.Name == ".ctor") return false; @@ -95,9 +194,9 @@ namespace de4dot.code.deobfuscators.SmartAssembly { return false; if (method.Parameters.Count == 0) return false; - if (method.Parameters[0].ParameterType.FullName != "System.Exception") + if (!isV1(method) && !isV2(method) && method.Parameters[0].ParameterType.FullName != "System.Exception") return false; - for (int i = 1; i < method.Parameters.Count; i++) { + for (int i = getConstantArgs(method); i < method.Parameters.Count; i++) { if (method.Parameters[i].ParameterType.FullName != "System.Object") return false; } @@ -108,7 +207,11 @@ namespace de4dot.code.deobfuscators.SmartAssembly { if (!method.HasBody || !method.IsStatic || method.Name == ".ctor") return false; return DotNetUtils.isMethod(method, "System.Void", "(System.Exception,System.Object[])") || - DotNetUtils.isMethod(method, "System.Exception", "(System.Exception,System.Object[])"); + DotNetUtils.isMethod(method, "System.Exception", "(System.Exception,System.Object[])") || + // 2.x + DotNetUtils.isMethod(method, "System.Exception", "(System.Exception,System.Int32,System.Object[])") || + // 1.x + DotNetUtils.isMethod(method, "System.Exception", "(System.Int32,System.Exception,System.Object[])"); } void initUnhandledExceptionFilterMethods() { @@ -141,14 +244,39 @@ namespace de4dot.code.deobfuscators.SmartAssembly { if (method == null || !method.IsStatic) return null; - if (method.Parameters.Count < 2) - return null; if (DotNetUtils.hasReturnValue(method)) return null; - if (method.Parameters[0].ParameterType.ToString() != "System.Exception") - return null; - if (method.Parameters[method.Parameters.Count - 1].ParameterType.ToString() != "System.Object[]") - return null; + + switch (aerVersion) { + case AerVersion.V0: + case AerVersion.V1: + if (!DotNetUtils.isMethod(method, "System.Void", "(System.Int32,System.Object[])")) + return null; + break; + + case AerVersion.V2: + if (method.Parameters.Count < 2) + return null; + if (method.Parameters[0].ParameterType.ToString() != "System.Exception") + return null; + if (method.Parameters[1].ParameterType.ToString() != "System.Int32") + return null; + if (method.Parameters[method.Parameters.Count - 1].ParameterType.ToString() != "System.Object[]") + return null; + break; + + case AerVersion.V3: + if (method.Parameters.Count < 1) + return null; + if (method.Parameters[0].ParameterType.ToString() != "System.Exception") + return null; + if (method.Parameters[method.Parameters.Count - 1].ParameterType.ToString() != "System.Object[]") + return null; + break; + + default: + throw new ApplicationException("Invalid AER version"); + } return method; } diff --git a/de4dot.code/deobfuscators/SmartAssembly/Deobfuscator.cs b/de4dot.code/deobfuscators/SmartAssembly/Deobfuscator.cs index 0fb26ab3..902cba2b 100644 --- a/de4dot.code/deobfuscators/SmartAssembly/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/SmartAssembly/Deobfuscator.cs @@ -318,7 +318,9 @@ namespace de4dot.code.deobfuscators.SmartAssembly { resourceResolverInfo.findTypes(); addModuleCctorInitCallToBeRemoved(assemblyResolverInfo.CallResolverMethod); + addCallToBeRemoved(module.EntryPoint, assemblyResolverInfo.CallResolverMethod); addModuleCctorInitCallToBeRemoved(resourceResolverInfo.CallResolverMethod); + addCallToBeRemoved(module.EntryPoint, resourceResolverInfo.CallResolverMethod); resourceDecrypterInfo.setSimpleZipType(getGlobalSimpleZipType(), DeobfuscatedFile); diff --git a/de4dot.code/deobfuscators/SmartAssembly/ResolverInfoBase.cs b/de4dot.code/deobfuscators/SmartAssembly/ResolverInfoBase.cs index 47b80cba..e77e8040 100644 --- a/de4dot.code/deobfuscators/SmartAssembly/ResolverInfoBase.cs +++ b/de4dot.code/deobfuscators/SmartAssembly/ResolverInfoBase.cs @@ -53,11 +53,18 @@ namespace de4dot.code.deobfuscators.SmartAssembly { if (resolverType != null) return true; - var cctor = DotNetUtils.getMethod(DotNetUtils.getModuleType(module), ".cctor"); - if (cctor == null) - return false; + if (findTypes(DotNetUtils.getMethod(DotNetUtils.getModuleType(module), ".cctor"))) + return true; + if (findTypes(module.EntryPoint)) + return true; - foreach (var tuple in DotNetUtils.getCalledMethods(module, cctor)) { + return false; + } + + bool findTypes(MethodDefinition initMethod) { + if (initMethod == null) + return false; + foreach (var tuple in DotNetUtils.getCalledMethods(module, initMethod)) { var method = tuple.Item2; if (method.Name == ".cctor" || method.Name == ".ctor") continue; @@ -98,16 +105,50 @@ namespace de4dot.code.deobfuscators.SmartAssembly { return true; } + if (hasLdftn(attachAppMethod)) { + simpleDeobfuscator.deobfuscate(attachAppMethod); + foreach (var resolverHandler in getResolverHandlers(type, attachAppMethod)) { + if (!resolverHandler.HasBody) + continue; + if (!checkResolverType2(resolverHandler.DeclaringType)) + continue; + deobfuscate(resolverHandler); + if (checkHandlerMethod(resolverHandler)) { + callResolverMethod = attachAppMethod; + callResolverType = type; + resolverType = resolverHandler.DeclaringType; + return true; + } + } + } + return false; } + static bool hasLdftn(MethodDefinition method) { + if (method == null || method.Body == null) + return false; + foreach (var instr in method.Body.Instructions) { + if (instr.OpCode.Code == Code.Ldftn) + return true; + } + return false; + } + + bool checkResolverType2(TypeDefinition type) { + if (type.Properties.Count > 1 || type.Events.Count > 0) + return false; + if (!checkResolverType(type)) + return false; + + return true; + } + bool checkResolverType(TypeDefinition type, MethodDefinition initMethod) { resolverType = null; if (!initMethod.HasBody) return false; - if (type.Properties.Count > 1 || type.Events.Count > 0) - return false; - if (!checkResolverType(type)) + if (!checkResolverType2(type)) return false; deobfuscate(initMethod); diff --git a/de4dot.code/deobfuscators/SmartAssembly/StringDecrypter.cs b/de4dot.code/deobfuscators/SmartAssembly/StringDecrypter.cs index 1b7ddc9b..7e35ed46 100644 --- a/de4dot.code/deobfuscators/SmartAssembly/StringDecrypter.cs +++ b/de4dot.code/deobfuscators/SmartAssembly/StringDecrypter.cs @@ -24,6 +24,7 @@ namespace de4dot.code.deobfuscators.SmartAssembly { class StringDecrypter { int stringOffset; byte[] decryptedData; + StringDecrypterVersion stringDecrypterVersion; public bool CanDecrypt { get { return decryptedData != null; } @@ -43,6 +44,8 @@ namespace de4dot.code.deobfuscators.SmartAssembly { stringOffset = stringDecrypterInfo.StringOffset; decryptedData = stringDecrypterInfo.decrypt(); } + + stringDecrypterVersion = StringDecrypterInfo.DecrypterVersion; } } @@ -65,8 +68,20 @@ namespace de4dot.code.deobfuscators.SmartAssembly { decryptedData[index++]; } - var decodedData = Convert.FromBase64String(Encoding.UTF8.GetString(decryptedData, index, len)); - return Encoding.UTF8.GetString(decodedData, 0, decodedData.Length); + switch (StringDecrypterInfo.DecrypterVersion) { + case StringDecrypterVersion.V1: + // Some weird problem with 1.x decrypted strings. They all have a \x01 char at the end. + var buf = Convert.FromBase64String(Encoding.ASCII.GetString(decryptedData, index, len)); + if (buf.Length % 2 != 0) + Array.Resize(ref buf, buf.Length - 1); + return Encoding.Unicode.GetString(buf); + + case StringDecrypterVersion.V2: + return Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.ASCII.GetString(decryptedData, index, len))); + + default: + return Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(decryptedData, index, len))); + } } } } diff --git a/de4dot.code/deobfuscators/SmartAssembly/StringDecrypterInfo.cs b/de4dot.code/deobfuscators/SmartAssembly/StringDecrypterInfo.cs index d39b9ea5..f32f1571 100644 --- a/de4dot.code/deobfuscators/SmartAssembly/StringDecrypterInfo.cs +++ b/de4dot.code/deobfuscators/SmartAssembly/StringDecrypterInfo.cs @@ -24,6 +24,14 @@ using Mono.Cecil.Cil; using de4dot.blocks; namespace de4dot.code.deobfuscators.SmartAssembly { + enum StringDecrypterVersion { + V1, + V2, + V3, + V4, + Unknown, + } + class StringDecrypterInfo { ModuleDefinition module; ResourceDecrypter resourceDecrypter; @@ -32,6 +40,11 @@ namespace de4dot.code.deobfuscators.SmartAssembly { int stringOffset; TypeDefinition simpleZipType; MethodDefinition stringDecrypterMethod; + StringDecrypterVersion decrypterVersion; + + public StringDecrypterVersion DecrypterVersion { + get { return decrypterVersion; } + } public TypeDefinition GetStringDelegate { get; set; } public TypeDefinition StringsType { get; set; } @@ -70,35 +83,100 @@ namespace de4dot.code.deobfuscators.SmartAssembly { this.stringsEncodingClass = stringsEncodingClass; } + static string[] fields2x = new string[] { + "System.IO.Stream", + "System.Int32", + }; + static string[] fields3x = new string[] { + "System.Byte[]", + "System.Int32", + }; + StringDecrypterVersion guessVersion(MethodDefinition cctor) { + if (cctor == null) + return StringDecrypterVersion.V1; + var fieldTypes = new FieldTypes(stringsEncodingClass); + if (fieldTypes.exactly(fields2x)) + return StringDecrypterVersion.V2; + if (fieldTypes.exactly(fields3x)) + return StringDecrypterVersion.V3; + return StringDecrypterVersion.Unknown; + } + public bool init(IDeobfuscator deob, ISimpleDeobfuscator simpleDeobfuscator) { var cctor = DotNetUtils.getMethod(stringsEncodingClass, ".cctor"); - if (cctor == null) - throw new ApplicationException("Could not find .cctor"); - simpleDeobfuscator.deobfuscate(cctor); + if (cctor != null) + simpleDeobfuscator.deobfuscate(cctor); - stringsResource = findStringResource(cctor); - if (stringsResource == null) { - simpleDeobfuscator.decryptStrings(cctor, deob); - stringsResource = findStringResource(cctor); - if (stringsResource == null) - return false; - } - - var offsetVal = findOffsetValue(cctor); - if (offsetVal == null) - throw new ApplicationException("Could not find string offset"); - stringOffset = offsetVal.Value; + decrypterVersion = guessVersion(cctor); if (!findDecrypterMethod()) throw new ApplicationException("Could not find string decrypter method"); - simpleZipType = findSimpleZipType(cctor); + if (!findStringsResource(deob, simpleDeobfuscator, cctor ?? stringDecrypterMethod)) + return false; + + if (decrypterVersion <= StringDecrypterVersion.V3) { + MethodDefinition initMethod; + if (decrypterVersion == StringDecrypterVersion.V3) + initMethod = cctor; + else if (decrypterVersion == StringDecrypterVersion.V2) + initMethod = stringDecrypterMethod; + else + initMethod = stringDecrypterMethod; + + stringOffset = 0; + if (decrypterVersion != StringDecrypterVersion.V1) { + var pkt = module.Assembly.Name.PublicKeyToken; + if (pkt != null) { + for (int i = 0; i < pkt.Length - 1; i += 2) + stringOffset ^= ((int)pkt[i] << 8) + pkt[i + 1]; + } + + if (DotNetUtils.findLdcI4Constant(initMethod, 0xFFFFFF) && + DotNetUtils.findLdcI4Constant(initMethod, 0xFFFF)) { + stringOffset ^= ((stringDecrypterMethod.MetadataToken.ToInt32() & 0xFFFFFF) - 1) % 0xFFFF; + } + } + } + else { + var offsetVal = findOffsetValue(cctor); + if (offsetVal == null) + throw new ApplicationException("Could not find string offset"); + stringOffset = offsetVal.Value; + decrypterVersion = StringDecrypterVersion.V4; + } + + simpleZipType = cctor == null ? null : findSimpleZipType(cctor); if (simpleZipType != null) resourceDecrypter = new ResourceDecrypter(new ResourceDecrypterInfo(module, simpleZipType, simpleDeobfuscator)); return true; } + bool findStringsResource(IDeobfuscator deob, ISimpleDeobfuscator simpleDeobfuscator, MethodDefinition initMethod) { + if (stringsResource != null) + return true; + + if (decrypterVersion <= StringDecrypterVersion.V3) { + stringsResource = DotNetUtils.getResource(module, module.Mvid.ToString("B")) as EmbeddedResource; + if (stringsResource != null) + return true; + } + + if (initMethod != null) { + stringsResource = findStringResource(initMethod); + if (stringsResource != null) + return true; + + simpleDeobfuscator.decryptStrings(initMethod, deob); + stringsResource = findStringResource(initMethod); + if (stringsResource != null) + return true; + } + + return false; + } + public byte[] decrypt() { if (!CanDecrypt) throw new ApplicationException("Can't decrypt strings"); @@ -107,10 +185,7 @@ namespace de4dot.code.deobfuscators.SmartAssembly { // Find the embedded resource where all the strings are encrypted EmbeddedResource findStringResource(MethodDefinition method) { - foreach (var ldstr in method.Body.Instructions) { - if (ldstr.OpCode.Code != Code.Ldstr) - continue; - var s = ldstr.Operand as string; + foreach (var s in DotNetUtils.getCodeStrings(method)) { if (s == null) continue; var resource = DotNetUtils.getResource(module, s) as EmbeddedResource; @@ -181,6 +256,9 @@ namespace de4dot.code.deobfuscators.SmartAssembly { } bool findDecrypterMethod() { + if (stringDecrypterMethod != null) + return true; + var methods = new List(DotNetUtils.findMethods(stringsEncodingClass.Methods, "System.String", new string[] { "System.Int32" })); if (methods.Count != 1) return false; diff --git a/de4dot.code/deobfuscators/SmartAssembly/StringEncoderClassFinder.cs b/de4dot.code/deobfuscators/SmartAssembly/StringEncoderClassFinder.cs index 0ce7b099..58e68202 100644 --- a/de4dot.code/deobfuscators/SmartAssembly/StringEncoderClassFinder.cs +++ b/de4dot.code/deobfuscators/SmartAssembly/StringEncoderClassFinder.cs @@ -170,12 +170,29 @@ namespace de4dot.code.deobfuscators.SmartAssembly { } } + static string[] fields1x = new string[] { + "System.IO.Stream", + }; + static string[] fields2x = new string[] { + "System.IO.Stream", + "System.Int32", + }; + static string[] fields3x = new string[] { + "System.Byte[]", + "System.Int32", + }; bool couldBeStringDecrypterClass(TypeDefinition type) { - if (DotNetUtils.findFieldType(type, "System.Collections.Hashtable", true) == null && - DotNetUtils.findFieldType(type, "System.Collections.Generic.Dictionary`2", true) == null) { - return false; + var fields = new FieldTypes(type); + if (fields.exists("System.Collections.Hashtable") || + fields.exists("System.Collections.Generic.Dictionary`2") || + fields.exactly(fields2x) || + fields.exactly(fields3x)) { + if (DotNetUtils.getMethod(type, ".cctor") == null) + return false; } - if (DotNetUtils.getMethod(type, ".cctor") == null) + else if (fields.exactly(fields1x)) { + } + else return false; var methods = new List(DotNetUtils.getNormalMethods(type));