diff --git a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs index 8e25f2bd..637fbe72 100644 --- a/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs +++ b/de4dot.code/deobfuscators/DeepSea/Deobfuscator.cs @@ -136,7 +136,7 @@ namespace de4dot.code.deobfuscators.DeepSea { foreach (var method in stringDecrypter.DecrypterMethods) { staticStringInliner.add(method, (method2, args) => { - return stringDecrypter.decrypt(method2, (int)args[0]); + return stringDecrypter.decrypt(method2, args); }); } DeobfuscatedFile.stringDecryptersAdded(); @@ -180,8 +180,10 @@ namespace de4dot.code.deobfuscators.DeepSea { public override void deobfuscateEnd() { removeInlinedMethods(); - if (Operations.DecryptStrings != OpDecryptString.None) + if (Operations.DecryptStrings != OpDecryptString.None) { addMethodsToBeRemoved(stringDecrypter.DecrypterMethods, "String decrypter method"); + stringDecrypter.cleanup(); + } base.deobfuscateEnd(); } diff --git a/de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs b/de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs index 8e518695..a439c698 100644 --- a/de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs +++ b/de4dot.code/deobfuscators/DeepSea/StringDecrypter.cs @@ -17,6 +17,7 @@ along with de4dot. If not, see . */ +using System; using System.Collections.Generic; using System.Text; using Mono.Cecil; @@ -26,22 +27,245 @@ using de4dot.blocks; namespace de4dot.code.deobfuscators.DeepSea { class StringDecrypter { ModuleDefinition module; - MethodDefinitionAndDeclaringTypeDict methodToInfo = new MethodDefinitionAndDeclaringTypeDict(); + MethodDefinitionAndDeclaringTypeDict methodToInfo = new MethodDefinitionAndDeclaringTypeDict(); - class DecrypterInfo { + interface IDecrypterInfo { + MethodDefinition Method { get; } + string decrypt(object[] args); + void cleanup(); + } + + static short[] findKey(MethodDefinition initMethod, FieldDefinition keyField) { + var instrs = initMethod.Body.Instructions; + for (int i = 0; i < instrs.Count - 2; i++) { + var ldci4 = instrs[i]; + if (!DotNetUtils.isLdcI4(ldci4)) + continue; + var newarr = instrs[i + 1]; + if (newarr.OpCode.Code != Code.Newarr) + continue; + if (newarr.Operand.ToString() != "System.Char") + continue; + + var stloc = instrs[i + 2]; + if (!DotNetUtils.isStloc(stloc)) + continue; + var local = DotNetUtils.getLocalVar(initMethod.Body.Variables, stloc); + + int startInitIndex = i; + i++; + var array = ArrayFinder.getInitializedInt16Array(DotNetUtils.getLdcI4Value(ldci4), initMethod, ref i); + if (array == null) + continue; + + var field = getStoreField(initMethod, startInitIndex, local); + if (field == null) + continue; + if (MemberReferenceHelper.compareFieldReferenceAndDeclaringType(keyField, field)) + return array; + } + + return null; + } + + static FieldDefinition getStoreField(MethodDefinition method, int startIndex, VariableDefinition local) { + var instrs = method.Body.Instructions; + for (int i = 0; i < instrs.Count - 1; i++) { + var ldloc = instrs[i]; + if (!DotNetUtils.isLdloc(ldloc)) + continue; + if (DotNetUtils.getLocalVar(method.Body.Variables, ldloc) != local) + continue; + + var stsfld = instrs[i + 1]; + if (stsfld.OpCode.Code != Code.Stsfld) + continue; + return stsfld.Operand as FieldDefinition; + } + + return null; + } + + class DecrypterInfo4 : IDecrypterInfo { MethodDefinition cctor; - public MethodDefinition method; + public MethodDefinition Method { get; set; } + int magic; + FieldDefinition cachedStringsField; + FieldDefinition keyField; + FieldDefinition encryptedStringsField; + FieldDefinition encryptedDataField; + short[] key; + ushort[] encryptedData; + + public DecrypterInfo4(MethodDefinition cctor, MethodDefinition method) { + this.cctor = cctor; + this.Method = method; + } + + public bool initialize() { + if (!findMagic(Method, out magic)) + return false; + + var charArrayFields = findFields(); + if (charArrayFields == null || charArrayFields.Count != 2) + return false; + + encryptedStringsField = findEncryptedStrings(cctor, charArrayFields, out encryptedDataField); + if (encryptedStringsField == null) + return false; + if (encryptedDataField.InitialValue.Length % 2 == 1) + return false; + encryptedData = new ushort[encryptedDataField.InitialValue.Length / 2]; + Buffer.BlockCopy(encryptedDataField.InitialValue, 0, encryptedData, 0, encryptedDataField.InitialValue.Length); + + charArrayFields.Remove(encryptedStringsField); + keyField = charArrayFields[0]; + + key = findKey(); + if (key == null || key.Length == 0) + return false; + + return true; + } + + static bool findMagic(MethodDefinition method, out int magic) { + var instrs = method.Body.Instructions; + for (int i = 0; i < instrs.Count - 2; i++) { + var ldarg = instrs[i]; + if (DotNetUtils.getArgIndex(ldarg) < 0) + continue; + var ldci4 = instrs[i + 1]; + if (!DotNetUtils.isLdcI4(ldci4)) + continue; + if (instrs[i + 2].OpCode.Code != Code.Xor) + continue; + magic = DotNetUtils.getLdcI4Value(ldci4); + return true; + } + magic = 0; + return false; + } + + List findFields() { + var charArrayFields = new List(); + + foreach (var instr in Method.Body.Instructions) { + if (instr.OpCode.Code != Code.Stsfld && instr.OpCode.Code != Code.Ldsfld) + continue; + var field = instr.Operand as FieldDefinition; + if (field == null) + continue; + if (!MemberReferenceHelper.compareTypes(Method.DeclaringType, field.DeclaringType)) + continue; + switch (field.FieldType.FullName) { + case "System.Char[]": + if (!charArrayFields.Contains(field)) + charArrayFields.Add(field); + break; + + case "System.String[]": + if (cachedStringsField != null && cachedStringsField != field) + return null; + cachedStringsField = field; + break; + + default: + break; + } + } + + if (cachedStringsField == null) + return null; + + return charArrayFields; + } + + static FieldDefinition findEncryptedStrings(MethodDefinition initMethod, List ourFields, out FieldDefinition dataField) { + for (int i = 0; i < initMethod.Body.Instructions.Count; i++) { + var instrs = DotNetUtils.getInstructions(initMethod.Body.Instructions, i, OpCodes.Ldtoken, OpCodes.Call, OpCodes.Stsfld); + if (instrs == null) + continue; + + dataField = instrs[0].Operand as FieldDefinition; + if (dataField == null || dataField.InitialValue == null || dataField.InitialValue.Length == 0) + continue; + + var savedField = instrs[2].Operand as FieldDefinition; + if (savedField == null || !matches(ourFields, savedField)) + continue; + + return savedField; + } + + dataField = null; + return null; + } + + static bool matches(IEnumerable ourFields, FieldReference field) { + foreach (var ourField in ourFields) { + if (MemberReferenceHelper.compareFieldReferenceAndDeclaringType(ourField, field)) + return true; + } + return false; + } + + short[] findKey() { + var pkt = cctor.Module.Assembly.Name.PublicKeyToken; + if (pkt != null && pkt.Length > 0) + return getPublicKeyTokenKey(pkt); + return findKey(cctor); + } + + short[] findKey(MethodDefinition initMethod) { + return StringDecrypter.findKey(initMethod, keyField); + } + + static short[] getPublicKeyTokenKey(byte[] publicKeyToken) { + var key = new short[publicKeyToken.Length]; + for (int i = 0; i < publicKeyToken.Length; i++) { + int b = publicKeyToken[i]; + key[i] = (short)((b << 4) ^ b); + } + return key; + } + + public string decrypt(object[] args) { + return decrypt((int)args[0], (int)args[1]); + } + + string decrypt(int magic2, int magic3) { + int index = magic ^ magic2 ^ magic3; + int cachedIndex = (ushort)encryptedData[index++]; + int stringLen = (int)(ushort)encryptedData[index++] + ((int)(ushort)encryptedData[index++] << 16); + var sb = new StringBuilder(stringLen); + for (int i = 0; i < stringLen; i++) + sb.Append((char)(encryptedData[index++] ^ key[cachedIndex++ % key.Length])); + return sb.ToString(); + } + + public void cleanup() { + encryptedDataField.InitialValue = new byte[0]; + } + } + + class DecrypterInfo3 : IDecrypterInfo { + MethodDefinition cctor; + public MethodDefinition Method { get; set; } FieldDefinition cachedStringsField; FieldDefinition keyField; int magic; string[] encryptedStrings; short[] key; - public DecrypterInfo(MethodDefinition cctor, MethodDefinition method) { + public DecrypterInfo3(MethodDefinition cctor, MethodDefinition method) { this.cctor = cctor; - this.method = method; + this.Method = method; } - public string decrypt(int magic2) { + public string decrypt(object[] args) { + return decrypt((int)args[0]); + } + + string decrypt(int magic2) { var es = encryptedStrings[magic ^ magic2]; var sb = new StringBuilder(es.Length); for (int i = 0; i < es.Length; i++) @@ -50,13 +274,13 @@ namespace de4dot.code.deobfuscators.DeepSea { } public bool initialize() { - if (!findMagic(method, out magic)) + if (!findMagic(Method, out magic)) return false; if (!findFields()) return false; - if (!findEncryptedStrings(method)) + if (!findEncryptedStrings(Method)) return false; key = findKey(); @@ -67,13 +291,13 @@ namespace de4dot.code.deobfuscators.DeepSea { } bool findFields() { - foreach (var instr in method.Body.Instructions) { + foreach (var instr in Method.Body.Instructions) { if (instr.OpCode.Code != Code.Stsfld && instr.OpCode.Code != Code.Ldsfld) continue; var field = instr.Operand as FieldDefinition; if (field == null) continue; - if (!MemberReferenceHelper.compareTypes(method.DeclaringType, field.DeclaringType)) + if (!MemberReferenceHelper.compareTypes(Method.DeclaringType, field.DeclaringType)) continue; switch (field.FieldType.FullName) { case "System.Char[]": @@ -104,32 +328,7 @@ namespace de4dot.code.deobfuscators.DeepSea { } short[] findKey(MethodDefinition initMethod) { - var instrs = initMethod.Body.Instructions; - for (int i = 0; i < instrs.Count - 1; i++) { - var ldci4 = instrs[i]; - if (!DotNetUtils.isLdcI4(ldci4)) - continue; - var newarr = instrs[i + 1]; - if (newarr.OpCode.Code != Code.Newarr) - continue; - if (newarr.Operand.ToString() != "System.Char") - continue; - - i++; - var array = ArrayFinder.getInitializedInt16Array(DotNetUtils.getLdcI4Value(ldci4), initMethod, ref i); - if (array == null) - continue; - i++; - if (i >= instrs.Count) - return null; - var stsfld = instrs[i]; - if (stsfld.OpCode.Code != Code.Stsfld) - continue; - if (MemberReferenceHelper.compareFieldReferenceAndDeclaringType(keyField, stsfld.Operand as FieldReference)) - return array; - } - - return null; + return StringDecrypter.findKey(initMethod, keyField); } static short[] getPublicKeyTokenKey(byte[] publicKeyToken) { @@ -145,7 +344,7 @@ namespace de4dot.code.deobfuscators.DeepSea { var instrs = method.Body.Instructions; for (int i = 0; i < instrs.Count - 2; i++) { var ldarg = instrs[i]; - if (DotNetUtils.getArgIndex(ldarg) != 0) + if (DotNetUtils.getArgIndex(ldarg) < 0) continue; var ldci4 = instrs[i + 1]; if (!DotNetUtils.isLdcI4(ldci4)) @@ -186,6 +385,9 @@ namespace de4dot.code.deobfuscators.DeepSea { return switchInstr; } + public void cleanup() { + } + public override string ToString() { return string.Format("M:{0:X8} N:{1}", magic, encryptedStrings.Length); } @@ -199,7 +401,7 @@ namespace de4dot.code.deobfuscators.DeepSea { get { var methods = new List(methodToInfo.Count); foreach (var info in methodToInfo.getValues()) - methods.Add(info.method); + methods.Add(info.Method); return methods; } } @@ -218,9 +420,30 @@ namespace de4dot.code.deobfuscators.DeepSea { continue; if (!hasPublicKeyToken) simpleDeobfuscator.deobfuscate(cctor); + + foreach (var method in type.Methods) { + if (method.Body == null) + continue; + + IDecrypterInfo info = null; + + if (DotNetUtils.isMethod(method, "System.String", "(System.Int32)")) { + simpleDeobfuscator.deobfuscate(method); + info = getInfoV3(cctor, method); + } + else if (DotNetUtils.isMethod(method, "System.String", "(System.Int32,System.Int32)")) { + simpleDeobfuscator.deobfuscate(method); + info = getInfoV4(cctor, method); + } + + if (info == null) + continue; + methodToInfo.add(method, info); + } + foreach (var method in DotNetUtils.findMethods(type.Methods, "System.String", new string[] { "System.Int32" }, true)) { simpleDeobfuscator.deobfuscate(method); - var info = getInfo(cctor, method); + var info = getInfoV3(cctor, method); if (info == null) continue; methodToInfo.add(method, info); @@ -245,20 +468,28 @@ namespace de4dot.code.deobfuscators.DeepSea { return foundCharAry && foundStringAry; } - DecrypterInfo getInfo(MethodDefinition cctor, MethodDefinition method) { - if (method == null || method.Body == null) - return null; - - var info = new DecrypterInfo(cctor, method); + DecrypterInfo3 getInfoV3(MethodDefinition cctor, MethodDefinition method) { + var info = new DecrypterInfo3(cctor, method); if (!info.initialize()) return null; - return info; } - public string decrypt(MethodReference method, int magic2) { + DecrypterInfo4 getInfoV4(MethodDefinition cctor, MethodDefinition method) { + var info = new DecrypterInfo4(cctor, method); + if (!info.initialize()) + return null; + return info; + } + + public string decrypt(MethodReference method, object[] args) { var info = methodToInfo.find(method); - return info.decrypt(magic2); + return info.decrypt(args); + } + + public void cleanup() { + foreach (var info in methodToInfo.getValues()) + info.cleanup(); } } }