/* 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 System.IO; using System.Text; using dnlib.DotNet; using dnlib.DotNet.Emit; using de4dot.blocks; namespace de4dot.code.deobfuscators.Rummage { class StringDecrypter { ModuleDefMD module; FieldDefAndDeclaringTypeDict stringInfos = new FieldDefAndDeclaringTypeDict(); IDecrypter decrypter; interface IDecrypter { RummageVersion Version { get; } MethodDef Method { get; } void Initialize(); string Decrypt(int stringId); } abstract class DecrypterBaseV11 : IDecrypter { RummageVersion version; MethodDef decrypterMethod; protected int fileDispl; protected BinaryReader reader; protected uint[] key; public RummageVersion Version { get { return version; } } public MethodDef Method { get { return decrypterMethod; } } protected DecrypterBaseV11(RummageVersion version, MethodDef decrypterMethod, int fileDispl) { this.version = version; this.decrypterMethod = decrypterMethod; this.fileDispl = fileDispl; } public void Initialize() { reader = new BinaryReader(new FileStream(decrypterMethod.DeclaringType.Module.Location, FileMode.Open, FileAccess.Read, FileShare.Read)); InitializeImpl(); } protected abstract void InitializeImpl(); protected static MethodDef FindDecrypterMethod(TypeDef type) { MethodDef cctor = null, decrypterMethod = null; foreach (var method in type.Methods) { if (!method.IsStatic || method.Body == null) return null; if (method.Name == ".cctor") cctor = method; else if (DotNetUtils.IsMethod(method, "System.String", "(System.Int32)")) decrypterMethod = method; else return null; } if (cctor == null || decrypterMethod == null) return null; return decrypterMethod; } public abstract string Decrypt(int stringId); protected string DecryptInternal(int stringId) { uint v0 = reader.ReadUInt32(); uint v1 = reader.ReadUInt32(); DeobUtils.XteaDecrypt(ref v0, ref v1, key, 32); int utf8Length = (int)v0; var decrypted = new uint[(utf8Length + 11) / 8 * 2 - 1]; decrypted[0] = v1; for (int i = 1; i + 1 < decrypted.Length; i += 2) { v0 = reader.ReadUInt32(); v1 = reader.ReadUInt32(); DeobUtils.XteaDecrypt(ref v0, ref v1, key, 32); decrypted[i] = v0; decrypted[i + 1] = v1; } var utf8 = new byte[utf8Length]; Buffer.BlockCopy(decrypted, 0, utf8, 0, utf8.Length); return Encoding.UTF8.GetString(utf8); } } class DecrypterV11 : DecrypterBaseV11 { DecrypterV11(MethodDef decrypterMethod, int fileDispl) : base(RummageVersion.V1_1_445, decrypterMethod, fileDispl) { } public static DecrypterV11 Create(MethodDef cctor) { var method = CheckType(cctor); if (method == null) return null; int fileDispl; if (!GetDispl(method, out fileDispl)) return null; return new DecrypterV11(method, fileDispl); } static readonly string[] requiredFields = new string[] { "System.UInt32[]", }; static readonly string[] requiredLocals = new string[] { "System.Byte[]", "System.Int32", "System.IO.FileStream", }; static MethodDef CheckType(MethodDef cctor) { var type = cctor.DeclaringType; if (!new FieldTypes(type).Exactly(requiredFields)) return null; if (!new LocalTypes(cctor).All(requiredLocals)) return null; return FindDecrypterMethod(type); } static bool GetDispl(MethodDef method, out int displ) { var instrs = method.Body.Instructions; for (int i = 0; i < instrs.Count - 2; i++) { var mul = instrs[i]; if (mul.OpCode.Code != Code.Mul) continue; var ldci4 = instrs[i + 1]; if (!ldci4.IsLdcI4()) continue; var sub = instrs[i + 2]; if (sub.OpCode.Code != Code.Sub) continue; displ = ldci4.GetLdcI4Value(); return true; } displ = 0; return false; } protected override void InitializeImpl() { InitKey(); } void InitKey() { reader.BaseStream.Position = reader.BaseStream.Length - 48; key = new uint[4]; for (int i = 0; i < key.Length; i++) key[i] = reader.ReadUInt32(); } public override string Decrypt(int stringId) { reader.BaseStream.Position = reader.BaseStream.Length + (stringId * 4 - fileDispl); return DecryptInternal(stringId); } } class DecrypterV21 : DecrypterBaseV11 { long baseOffs; public DecrypterV21(MethodDef decrypterMethod, int fileDispl) : base(RummageVersion.V2_1_729, decrypterMethod, fileDispl) { } public static DecrypterV21 Create(MethodDef cctor) { var method = CheckType(cctor); if (method == null) return null; int fileDispl; if (!GetDispl(method, out fileDispl)) return null; return new DecrypterV21(method, fileDispl); } static readonly string[] requiredFields = new string[] { "System.UInt32[]", "System.Int64", }; static readonly string[] requiredLocals = new string[] { "System.Byte[]", "System.Int32", "System.IO.FileStream", }; static MethodDef CheckType(MethodDef cctor) { var type = cctor.DeclaringType; if (!new FieldTypes(type).Exactly(requiredFields)) return null; if (!new LocalTypes(cctor).All(requiredLocals)) return null; return FindDecrypterMethod(type); } static bool GetDispl(MethodDef method, out int displ) { var instrs = method.Body.Instructions; for (int i = 0; i < instrs.Count - 6; i++) { var ldci4_1 = instrs[i]; if (!ldci4_1.IsLdcI4() || ldci4_1.GetLdcI4Value() != 4) continue; if (instrs[i + 1].OpCode.Code != Code.Mul) continue; if (instrs[i + 2].OpCode.Code != Code.Conv_I8) continue; if (instrs[i + 3].OpCode.Code != Code.Add) continue; var ldci4 = instrs[i + 4]; if (!ldci4.IsLdcI4()) continue; if (instrs[i + 5].OpCode.Code != Code.Conv_I8) continue; if (instrs[i + 6].OpCode.Code != Code.Sub) continue; displ = ldci4.GetLdcI4Value(); return true; } displ = 0; return false; } static readonly byte[] magic = new byte[32] { 0xC9, 0x76, 0xC3, 0x0D, 0xE2, 0x83, 0x72, 0xE4, 0xD5, 0xC7, 0x35, 0xF8, 0x86, 0xD0, 0x60, 0x69, 0xEE, 0xE1, 0x4C, 0x5E, 0x07, 0xA1, 0xC1, 0xFE, 0x61, 0xE3, 0xAA, 0xBC, 0xE4, 0xB1, 0xF0, 0x92, }; protected override void InitializeImpl() { baseOffs = InitializeBaseOffs(); InitKey(); } void InitKey() { reader.BaseStream.Position = baseOffs - 16; key = new uint[4]; for (int i = 0; i < key.Length; i++) key[i] = reader.ReadUInt32(); } long InitializeBaseOffs() { byte[] buf = new byte[0x1000]; // Must be 4096 bytes reader.BaseStream.Position = reader.BaseStream.Length - buf.Length; while (true) { if (reader.Read(buf, 0, buf.Length) != buf.Length) throw new ApplicationException("Could not read"); for (int bi = buf.Length - 1; bi > magic.Length; ) { int mi = magic.Length - 1; if (buf[bi--] != magic[mi--] || buf[bi] != magic[mi--]) continue; while (true) { if (buf[--bi] != magic[mi--]) break; if (mi == -1) return reader.BaseStream.Position - buf.Length + bi; } } reader.BaseStream.Position -= buf.Length * 2 - 0x20; } } public override string Decrypt(int stringId) { reader.BaseStream.Position = baseOffs + stringId * 4 - fileDispl; return DecryptInternal(stringId); } } class StringInfo { public readonly FieldDef field; public readonly int stringId; public string decrypted; public StringInfo(FieldDef field, int stringId) { this.field = field; this.stringId = stringId; } public override string ToString() { if (decrypted != null) return string.Format("{0:X8} - {1}", stringId, Utils.ToCsharpString(decrypted)); return string.Format("{0:X8}", stringId); } } public RummageVersion Version { get { return decrypter == null ? RummageVersion.Unknown : decrypter.Version; } } public TypeDef Type { get { return decrypter != null ? decrypter.Method.DeclaringType : null; } } public IEnumerable OtherTypes { get { var list = new List(stringInfos.Count); foreach (var info in stringInfos.GetValues()) list.Add(info.field.DeclaringType); return list; } } public bool Detected { get { return decrypter != null; } } public StringDecrypter(ModuleDefMD module) { this.module = module; } public void Find() { foreach (var type in module.GetTypes()) { var cctor = type.FindStaticConstructor(); if (cctor == null) continue; decrypter = DecrypterV11.Create(cctor); if (decrypter != null) break; decrypter = DecrypterV21.Create(cctor); if (decrypter != null) break; } } public void Initialize() { if (decrypter == null) return; decrypter.Initialize(); foreach (var type in module.Types) InitType(type); } void InitType(TypeDef type) { var cctor = type.FindStaticConstructor(); if (cctor == null) return; var info = GetStringInfo(cctor); if (info == null) return; stringInfos.Add(info.field, info); } StringInfo GetStringInfo(MethodDef method) { if (method == null || method.Body == null) return null; var instrs = method.Body.Instructions; for (int i = 0; i < instrs.Count - 2; i++) { var ldci4 = instrs[i]; if (!ldci4.IsLdcI4()) continue; int stringId = ldci4.GetLdcI4Value(); var call = instrs[i + 1]; if (call.OpCode.Code != Code.Call) continue; var calledMethod = call.Operand as IMethod; if (!MethodEqualityComparer.CompareDeclaringTypes.Equals(decrypter.Method, calledMethod)) continue; var stsfld = instrs[i + 2]; if (stsfld.OpCode.Code != Code.Stsfld) continue; var field = stsfld.Operand as FieldDef; if (field == null) continue; return new StringInfo(field, stringId); } return null; } public void Deobfuscate(Blocks blocks) { if (decrypter == null) return; foreach (var block in blocks.MethodBlocks.GetAllBlocks()) { var instrs = block.Instructions; for (int i = 0; i < instrs.Count; i++) { var instr = instrs[i]; if (instr.OpCode.Code != Code.Ldsfld) continue; var field = instr.Operand as IField; if (field == null) continue; var info = stringInfos.Find(field); if (info == null) continue; var decrypted = Decrypt(info); instrs[i] = new Instr(OpCodes.Ldstr.ToInstruction(decrypted)); Logger.v("Decrypted string: {0}", Utils.ToCsharpString(decrypted)); } } } string Decrypt(StringInfo info) { if (info.decrypted == null) info.decrypted = decrypter.Decrypt(info.stringId); return info.decrypted; } } }