using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using de4dot.blocks; using de4dot.blocks.cflow; using de4dot.code.deobfuscators.ConfuserEx.x86; using dnlib.DotNet; using dnlib.DotNet.Writer; using FieldAttributes = dnlib.DotNet.FieldAttributes; using MethodAttributes = dnlib.DotNet.MethodAttributes; using OpCodes = dnlib.DotNet.Emit.OpCodes; using TypeAttributes = dnlib.DotNet.TypeAttributes; namespace de4dot.code.deobfuscators.ConfuserEx { public class ConstantDecrypterBase { private readonly InstructionEmulator _instructionEmulator = new InstructionEmulator(); private X86Method _nativeMethod; public MethodDef Method { get; set; } public byte[] Decrypted { get; set; } public uint Magic1 { get; set; } public uint Magic2 { get; set; } public bool CanRemove { get; set; } = true; // native mode public MethodDef NativeMethod { get; internal set; } // normal mode public uint Num1 { get; internal set; } public uint Num2 { get; internal set; } private int? CalculateKey() { var popValue = _instructionEmulator.Peek(); if (popValue == null || !popValue.IsInt32() || !(popValue as Int32Value).AllBitsValid()) return null; _instructionEmulator.Pop(); var result = _nativeMethod.Execute(((Int32Value) popValue).Value); return result; } private uint CalculateMagic(uint index) { uint uint_0; if (NativeMethod != null) { _instructionEmulator.Push(new Int32Value((int)index)); _nativeMethod = new X86Method(NativeMethod, Method.Module as ModuleDefMD); //TODO: Possible null var key = CalculateKey(); uint_0 = (uint)key.Value; } else { uint_0 = index * Num1 ^ Num2; } uint_0 &= 0x3fffffff; uint_0 <<= 2; return uint_0; } public string DecryptString(uint index) { index = CalculateMagic(index); var count = BitConverter.ToInt32(Decrypted, (int) index); return string.Intern(Encoding.UTF8.GetString(Decrypted, (int) index + 4, count)); } public T DecryptConstant(uint index) { index = CalculateMagic(index); var array = new T[1]; Buffer.BlockCopy(Decrypted, (int) index, array, 0, Marshal.SizeOf(typeof(T))); return array[0]; } public byte[] DecryptArray(uint index) { index = CalculateMagic(index); var count = BitConverter.ToInt32(Decrypted, (int) index); //int lengt = BitConverter.ToInt32(Decrypted, (int)index+4); we actualy dont need that var buffer = new byte[count - 4]; Buffer.BlockCopy(Decrypted, (int) index + 8, buffer, 0, count - 4); return buffer; } } public class ConstantsDecrypter { private readonly ISimpleDeobfuscator _deobfuscator; private readonly MethodDef _lzmaMethod; private readonly ModuleDef _module; private readonly string[] _strDecryptCalledMethods = { "System.Text.Encoding System.Text.Encoding::get_UTF8()", "System.String System.Text.Encoding::GetString(System.Byte[],System.Int32,System.Int32)", "System.Array System.Array::CreateInstance(System.Type,System.Int32)", "System.String System.String::Intern(System.String)", "System.Void System.Buffer::BlockCopy(System.Array,System.Int32,System.Array,System.Int32,System.Int32)", "System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle)", "System.Type System.Type::GetElementType()" }; private byte[] _decryptedBytes; private FieldDef _decryptedField, _arrayField; internal TypeDef ArrayType; public ConstantsDecrypter(ModuleDef module, MethodDef lzmaMethod, ISimpleDeobfuscator deobfsucator) { _module = module; _lzmaMethod = lzmaMethod; _deobfuscator = deobfsucator; } public bool CanRemoveLzma { get; private set; } public TypeDef Type => ArrayType; public MethodDef Method { get; private set; } public List Fields => new List {_decryptedField, _arrayField}; public List Decrypters { get; } = new List(); public bool Detected => Method != null && _decryptedBytes != null && Decrypters.Count != 0 && _decryptedField != null && _arrayField != null; public void Find() { var moduleCctor = DotNetUtils.GetModuleTypeCctor(_module); if (moduleCctor == null) return; foreach (var inst in moduleCctor.Body.Instructions) { if (inst.OpCode != OpCodes.Call) continue; if (!(inst.Operand is MethodDef)) continue; var method = (MethodDef) inst.Operand; if (!method.HasBody || !method.IsStatic) continue; if (!DotNetUtils.IsMethod(method, "System.Void", "()")) continue; _deobfuscator.Deobfuscate(method, SimpleDeobfuscatorFlags.Force); if (!IsStringDecrypterInit(method, out FieldDef aField, out FieldDef dField)) continue; try { _decryptedBytes = DecryptArray(method, aField.InitialValue); } catch (Exception e) { Console.WriteLine(e.Message); return; } _arrayField = aField; _decryptedField = dField; ArrayType = DotNetUtils.GetType(_module, _arrayField.FieldSig.Type); Method = method; Decrypters.AddRange(FindStringDecrypters(moduleCctor.DeclaringType)); CanRemoveLzma = true; } } private bool IsStringDecrypterInit(MethodDef method, out FieldDef aField, out FieldDef dField) { aField = null; dField = null; var instructions = method.Body.Instructions; if (instructions.Count < 15) return false; if (!instructions[0].IsLdcI4()) return false; if (!instructions[1].IsStloc()) //uint num = 96u; return false; if (!instructions[2].IsLdcI4()) return false; if (instructions[0].GetLdcI4Value() != instructions[2].GetLdcI4Value()) return false; if (instructions[3].OpCode != OpCodes.Newarr) return false; if (instructions[3].Operand.ToString() != "System.UInt32") return false; if (instructions[4].OpCode != OpCodes.Dup) return false; if (instructions[5].OpCode != OpCodes.Ldtoken) return false; aField = instructions[5].Operand as FieldDef; if (aField?.InitialValue == null) return false; if (aField.Attributes != (FieldAttributes.Assembly | FieldAttributes.Static | FieldAttributes.HasFieldRVA)) return false; if (instructions[6].OpCode != OpCodes.Call) return false; if (instructions[6].Operand.ToString() != "System.Void System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(System.Array,System.RuntimeFieldHandle)" ) return false; if (!instructions[7].IsStloc()) // uint[] array = new uint[] {.....}; return false; var l = instructions.Count; if (!instructions[l - 4].IsLdloc()) return false; if (instructions[l - 3].OpCode != OpCodes.Call) return false; if (instructions[l - 3].Operand != _lzmaMethod) return false; if (instructions[l - 2].OpCode != OpCodes.Stsfld) //.byte_0 = .smethod_0(array4); return false; dField = instructions[l - 2].Operand as FieldDef; if (dField == null) return false; return true; } private byte[] DecryptArray(MethodDef method, byte[] encryptedArray) { ModuleDefUser tempModule = new ModuleDefUser("TempModule"); AssemblyDef tempAssembly = new AssemblyDefUser("TempAssembly"); tempAssembly.Modules.Add(tempModule); var tempType = new TypeDefUser("", "TempType", tempModule.CorLibTypes.Object.TypeDefOrRef); tempType.Attributes = TypeAttributes.Public | TypeAttributes.Class; MethodDef tempMethod = Utils.Clone(method); tempMethod.ReturnType = new SZArraySig(tempModule.CorLibTypes.Byte); tempMethod.MethodSig.Params.Add(new SZArraySig(tempModule.CorLibTypes.Byte)); tempMethod.Attributes = MethodAttributes.Public | MethodAttributes.Static; for (int i = 0; i < 5; i++) tempMethod.Body.Instructions.RemoveAt(2); // read encrypted array from argument tempMethod.Body.Instructions.Insert(2, OpCodes.Ldarg_0.ToInstruction()); for (int i = 0; i < 2; i++) tempMethod.Body.Instructions.RemoveAt(tempMethod.Body.Instructions.Count - 2); // make return decrypted array tempType.Methods.Add(tempMethod); tempModule.Types.Add(tempType); using (MemoryStream memoryStream = new MemoryStream()) { ModuleWriterOptions moduleWriterOptions = new ModuleWriterOptions(); moduleWriterOptions.MetaDataOptions = new MetaDataOptions(); tempModule.Write(memoryStream, moduleWriterOptions); Assembly patchedAssembly = Assembly.Load(memoryStream.ToArray()); var type = patchedAssembly.ManifestModule.GetType("TempType"); var methods = type.GetMethods(); MethodInfo patchedMethod = methods.First(m => m.IsPublic && m.IsStatic); byte[] decryptedBytes = (byte[]) patchedMethod.Invoke(null, new object[]{encryptedArray}); return Lzma.Decompress(decryptedBytes); } } private IEnumerable FindStringDecrypters(TypeDef type) { foreach (var method in type.Methods) { if (!method.HasBody) continue; if (!method.Signature.ContainsGenericParameter) continue; var sig = method.MethodSig; if (sig?.Params.Count != 1) continue; if (sig.Params[0].GetElementType() != ElementType.U4) continue; if (!(sig.RetType.RemovePinnedAndModifiers() is GenericMVar)) continue; if (sig.GenParamCount != 1) continue; _deobfuscator.Deobfuscate(method, SimpleDeobfuscatorFlags.Force); if (IsNativeStringDecrypter(method, out MethodDef nativeMethod)) { yield return new ConstantDecrypterBase { Decrypted = _decryptedBytes, Method = method, NativeMethod = nativeMethod }; } if (IsNormalStringDecrypter(method, out int num1, out int num2)) { yield return new ConstantDecrypterBase { Decrypted = _decryptedBytes, Method = method, Num1 = (uint)num1, Num2 = (uint)num2 }; } } } private bool IsNormalStringDecrypter(MethodDef method, out int num1, out int num2) { num1 = 0; num2 = 0; var instr = method.Body.Instructions; if (instr.Count < 25) return false; var i = 0; if (!instr[i++].IsLdarg()) return false; if (!instr[i].IsLdcI4()) return false; num1 = (int)instr[i++].Operand; if (instr[i++].OpCode != OpCodes.Mul) return false; if (!instr[i].IsLdcI4()) return false; num2 = (int)instr[i++].Operand; if (instr[i++].OpCode != OpCodes.Xor) return false; if (!instr[i++].IsStarg()) //uint_0 = (uint_0 * 2857448701u ^ 1196001109u); return false; if (!instr[i++].IsLdarg()) return false; if (!instr[i].IsLdcI4() || instr[i++].GetLdcI4Value() != 0x1E) return false; if (instr[i++].OpCode != OpCodes.Shr_Un) return false; if (!instr[i++].IsStloc()) //uint num = uint_0 >> 30; return false; i++; //TODO: Implement //if (!instr[10].IsLdloca()) // return; if (instr[i++].OpCode != OpCodes.Initobj) return false; if (!instr[i++].IsLdarg()) return false; if (!instr[i].IsLdcI4() || instr[i++].GetLdcI4Value() != 0x3FFFFFFF) return false; if (instr[i++].OpCode != OpCodes.And) return false; if (!instr[i++].IsStarg()) //uint_0 &= 1073741823u; return false; if (!instr[i++].IsLdarg()) return false; if (!instr[i].IsLdcI4() || instr[i++].GetLdcI4Value() != 2) return false; if (instr[i++].OpCode != OpCodes.Shl) return false; if (!instr[i++].IsStarg()) //uint_0 <<= 2; return false; foreach (var mtd in _strDecryptCalledMethods) if (!DotNetUtils.CallsMethod(method, mtd)) return false; //TODO: Implement //if (!DotNetUtils.LoadsField(method, decryptedField)) // return; return true; } private bool IsNativeStringDecrypter(MethodDef method, out MethodDef nativeMethod) { nativeMethod = null; var instr = method.Body.Instructions; if (instr.Count < 25) return false; var i = 0; if (!instr[i++].IsLdarg()) return false; if (instr[i].OpCode != OpCodes.Call) return false; nativeMethod = instr[i++].Operand as MethodDef; if (nativeMethod == null || !nativeMethod.IsStatic || !nativeMethod.IsNative) return false; if (!DotNetUtils.IsMethod(nativeMethod, "System.Int32", "(System.Int32)")) return false; if (!instr[i++].IsStarg()) //uint_0 = (uint_0 * 2857448701u ^ 1196001109u); return false; if (!instr[i++].IsLdarg()) return false; if (!instr[i].IsLdcI4() || instr[i++].GetLdcI4Value() != 0x1E) return false; if (instr[i++].OpCode != OpCodes.Shr_Un) return false; if (!instr[i++].IsStloc()) //uint num = uint_0 >> 30; return false; i++; //TODO: Implement //if (!instr[10].IsLdloca()) // return; if (instr[i++].OpCode != OpCodes.Initobj) return false; if (!instr[i++].IsLdarg()) return false; if (!instr[i].IsLdcI4() || instr[i++].GetLdcI4Value() != 0x3FFFFFFF) return false; if (instr[i++].OpCode != OpCodes.And) return false; if (!instr[i++].IsStarg()) //uint_0 &= 1073741823u; return false; if (!instr[i++].IsLdarg()) return false; if (!instr[i].IsLdcI4() || instr[i++].GetLdcI4Value() != 2) return false; if (instr[i++].OpCode != OpCodes.Shl) return false; if (!instr[i++].IsStarg()) //uint_0 <<= 2; return false; foreach (var mtd in _strDecryptCalledMethods) if (!DotNetUtils.CallsMethod(method, mtd)) return false; //TODO: Implement //if (!DotNetUtils.LoadsField(method, decryptedField)) // return; return true; } private static bool VerifyGenericArg(MethodSpec gim, ElementType etype) { var gims = gim?.GenericInstMethodSig; if (gims == null || gims.GenericArguments.Count != 1) return false; return gims.GenericArguments[0].GetElementType() == etype; } public string DecryptString(ConstantDecrypterBase info, MethodSpec gim, uint magic1) { if (!VerifyGenericArg(gim, ElementType.String)) return null; return info.DecryptString(magic1); } public object DecryptSByte(ConstantDecrypterBase info, MethodSpec gim, uint magic1) { if (!VerifyGenericArg(gim, ElementType.I1)) return null; return info.DecryptConstant(magic1); } public object DecryptByte(ConstantDecrypterBase info, MethodSpec gim, uint magic1) { if (!VerifyGenericArg(gim, ElementType.U1)) return null; return info.DecryptConstant(magic1); } public object DecryptInt16(ConstantDecrypterBase info, MethodSpec gim, uint magic1) { if (!VerifyGenericArg(gim, ElementType.I2)) return null; return info.DecryptConstant(magic1); } public object DecryptUInt16(ConstantDecrypterBase info, MethodSpec gim, uint magic1) { if (!VerifyGenericArg(gim, ElementType.U2)) return null; return info.DecryptConstant(magic1); } public object DecryptInt32(ConstantDecrypterBase info, MethodSpec gim, uint magic1) { if (!VerifyGenericArg(gim, ElementType.I4)) return null; return info.DecryptConstant(magic1); } public object DecryptUInt32(ConstantDecrypterBase info, MethodSpec gim, uint magic1) { if (!VerifyGenericArg(gim, ElementType.U4)) return null; return info.DecryptConstant(magic1); } public object DecryptInt64(ConstantDecrypterBase info, MethodSpec gim, uint magic1) { if (!VerifyGenericArg(gim, ElementType.I8)) return null; return info.DecryptConstant(magic1); } public object DecryptUInt64(ConstantDecrypterBase info, MethodSpec gim, uint magic1) { if (!VerifyGenericArg(gim, ElementType.U8)) return null; return info.DecryptConstant(magic1); } public object DecryptSingle(ConstantDecrypterBase info, MethodSpec gim, uint magic1) { if (!VerifyGenericArg(gim, ElementType.R4)) return null; return info.DecryptConstant(magic1); } public object DecryptDouble(ConstantDecrypterBase info, MethodSpec gim, uint magic1) { if (!VerifyGenericArg(gim, ElementType.R8)) return null; return info.DecryptConstant(magic1); } public object DecryptArray(ConstantDecrypterBase info, MethodSpec gim, uint magic1) { if (!VerifyGenericArg(gim, ElementType.SZArray)) return null; return info.DecryptArray(magic1); } } }