diff --git a/README-CEx.md b/README-CEx.md new file mode 100644 index 000000000..9c2b017a4 --- /dev/null +++ b/README-CEx.md @@ -0,0 +1,137 @@ +## de4dot CEx +A de4dot fork with full support for vanilla ConfuserEx + +## Features +* Supports x86 (native) mode +* Supports normal mode +* Decrypts and inlines constants +* Decrypts resources +* Fixes control flow +* Fixes proxy calls +* Deobfuscated assemblies are runnable + +## Notes +* You have to unpack the obfuscated assembly **before** running this deobfuscator. The easiest way is to dump the module/s just after the methods have been decrypted. +* This deobfuscator uses method invocation for constant decryption, therefore you always **risk** running malware if it's present in the obfuscated assembly. Be cautious and use a VM/Sandboxie! + +### [Original README](./README.md) +--- + +## Samples + +### Before (obfuscated symbols shortened): +```csharp +ublic byte[] ShiftAddress(uint address) +{ + byte[] array = new byte[4]; + for (;;) + { + IL_07: + int num = -2174478396; + for (;;) + { + uint num2; + switch ((num2 = (uint).a(num)) % 7u) + { + case 0u: + goto IL_07; + case 1u: + { + int num3 = 0; + num = (int)(num2 * 81144519u ^ 2359132411u); + continue; + } + case 2u: + num = (int)(num2 * 2975731004u ^ 34171348176); + continue; + case 3u: + { + int num3; + num3++; + num = (int)(num2 * 2174567110u ^ 244457623u); + continue; + } + case 5u: + { + int num3; + num = ((num3 >= 4) ? 631278122 : 1299552879); + continue; + } + case 6u: + { + int num3; + array[num3] = (byte)(address >> num3 * 8 & 255u); + num = 556578930; + continue; + } + } + return array; + } + } + return array; +} +``` + +### After: +```csharp +public byte[] ShiftAddress(uint address) +{ + byte[] array = new byte[4]; + for (int i = 0; i < 4; i++) + { + array[i] = (byte)(address >> i * 8 & 255u); + } + return array; +} +``` + +### Before (obfuscated symbols shortened): +```csharp +public bool WriteBytes(uint address, List buffer) +{ + byte[] array = buffer.ToArray(); + IntPtr intPtr; + uint num = Memory.a(this.Handle, b((long)((ulong)address)), array, (uint)array.Length, out intPtr); + for (;;) + { + IL_25: + int num2 = 482469350; + for (;;) + { + uint num3; + switch ((num3 = (uint).c(num2)) % 5u) + { + case 0u: + this.d.Account.Log.WriteLine(.e(3167610260u)); + num2 = (int)(num3 * 3588940066u ^ 1074051690u); + continue; + case 2u: + return false; + case 3u: + goto IL_25; + case 4u: + num2 = (int)(((num != 0u) ? 4496537787u : 434512514u) ^ num3 * 589449693u); + continue; + } + goto Block_1; + } + } + Block_1: + return true; +} +``` + +### After: +```csharp +public bool WriteBytes(uint address, List buffer) +{ + byte[] array = buffer.ToArray(); + IntPtr intPtr; + if (Memory.WriteProcessMemory(this.Handle, (IntPtr)((long)((ulong)address)), array, (uint)array.Length, out intPtr) == 0u) + { + this.Owner.Console.Log.WriteLine("WriteBytes failed: WriteProcessMemory failed"); + return false; + } + return true; +} +``` diff --git a/README.md b/README.md index bd2a5f591..6514adead 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,13 @@ Supported obfuscators/packers * Spices.Net * Xenocode +There are some other obfuscators/packers that are not in supported list explicitly, so their status is unclear: + +* Confuser +* ConfuserEx - Merged from https://github.com/ViRb3/de4dot-cex , [read the full ReadMe by @ViRb3](README-CEx.md) for more info. You may need a native library for [BeaEngine](https://github.com/BeaEngine/beaengine) (version `5.3.0`) for your platform to use the corresponding functionality! If you use another version you may need to recompile with the updated [C# bindings](https://github.com/BeaEngine/beaengine/tree/master/headers/C%23%20headers). +* Osu +* PCL + Some of the above obfuscators are rarely used (eg. Goliath.NET), so they have had much less testing. Help me out by reporting bugs or problems you find. Warning diff --git a/de4dot.blocks/Block.cs b/de4dot.blocks/Block.cs index a2f58e82e..2384dbdb8 100644 --- a/de4dot.blocks/Block.cs +++ b/de4dot.blocks/Block.cs @@ -22,8 +22,25 @@ You should have received a copy of the GNU General Public License using dnlib.DotNet.Emit; namespace de4dot.blocks { - public class Block : BaseBlock { - List instructions = new List(); + public enum BlockType + { + Normal, + Switch, + SwitchCase + } + + public class Block : BaseBlock + { + public Block() + { + SwitchData = new SwitchData(this); + } + + public BlockType BlockType = BlockType.Normal; + public SwitchData SwitchData; + public bool Processed = false; + + List instructions = new List(); // List of all explicit (non-fall-through) targets. It's just one if it's a normal // branch, but if it's a switch, it could be many targets. diff --git a/de4dot.blocks/SwitchData.cs b/de4dot.blocks/SwitchData.cs new file mode 100644 index 000000000..1e70f61f2 --- /dev/null +++ b/de4dot.blocks/SwitchData.cs @@ -0,0 +1,20 @@ +namespace de4dot.blocks +{ + public class SwitchData + { + protected readonly Block _block; + + public int? Key; + public bool IsKeyHardCoded; + + public SwitchData(Block block) + { + _block = block; + } + + public virtual bool Initialize() + { + return false; + } + } +} diff --git a/de4dot.blocks/cflow/Int32Value.cs b/de4dot.blocks/cflow/Int32Value.cs index 7d111fdf2..6e8ba8337 100644 --- a/de4dot.blocks/cflow/Int32Value.cs +++ b/de4dot.blocks/cflow/Int32Value.cs @@ -450,8 +450,8 @@ public static Int32Value Shl(Int32Value a, Int32Value b) { return CreateUnknown(); if (b.Value == 0) return a; - if (b.Value < 0 || b.Value >= sizeof(int) * 8) - return CreateUnknown(); + //if (b.Value < 0 || b.Value >= sizeof(int) * 8) + // return CreateUnknown(); int shift = b.Value; uint validMask = (a.ValidMask << shift) | (uint.MaxValue >> (sizeof(int) * 8 - shift)); return new Int32Value(a.Value << shift, validMask); @@ -462,8 +462,8 @@ public static Int32Value Shr(Int32Value a, Int32Value b) { return CreateUnknown(); if (b.Value == 0) return a; - if (b.Value < 0 || b.Value >= sizeof(int) * 8) - return CreateUnknown(); + //if (b.Value < 0 || b.Value >= sizeof(int) * 8) + // return CreateUnknown(); int shift = b.Value; uint validMask = a.ValidMask >> shift; if (a.IsBitValid(sizeof(int) * 8 - 1)) @@ -476,8 +476,8 @@ public static Int32Value Shr_Un(Int32Value a, Int32Value b) { return CreateUnknown(); if (b.Value == 0) return a; - if (b.Value < 0 || b.Value >= sizeof(int) * 8) - return CreateUnknown(); + //if (b.Value < 0 || b.Value >= sizeof(int) * 8) + // return CreateUnknown(); int shift = b.Value; uint validMask = (a.ValidMask >> shift) | (uint.MaxValue << (sizeof(int) * 8 - shift)); return new Int32Value((int)((uint)a.Value >> shift), validMask); diff --git a/de4dot.code/ObfuscatedFile.cs b/de4dot.code/ObfuscatedFile.cs index ccdb8faae..484d630a3 100644 --- a/de4dot.code/ObfuscatedFile.cs +++ b/de4dot.code/ObfuscatedFile.cs @@ -264,12 +264,13 @@ IDeobfuscator DetectObfuscator2(IEnumerable deobfuscators) { foreach (var deob in deobfuscators) { this.deob = deob; // So we can call deob.CanInlineMethods in deobfuscate() int val; - try { - val = deob.Detect(); - } + //TODO: Re-enable exception handler + //try { + val = deob.Detect(); + /*} catch { val = deob.Type == "un" ? 1 : 0; - } + }*/ Logger.v("{0,3}: {1}", val, deob.TypeLong); if (val > 0 && deob.Type != "un") allDetected.Add(deob); @@ -552,9 +553,10 @@ void DeobfuscateMethods() { } int oldIndentLevel = Logger.Instance.IndentLevel; - try { + //TODO: Re-enable exception handler + //try { Deobfuscate(method, cflowDeobfuscator, methodPrinter, isVerbose, isVV); - } + /*} catch (Exception ex) { if (!CanLoadMethodBody(method)) { if (isVerbose) @@ -569,8 +571,9 @@ void DeobfuscateMethods() { } finally { Logger.Instance.IndentLevel = oldIndentLevel; - } - RemoveNoInliningAttribute(method); + }*/ + + RemoveNoInliningAttribute(method); if (isVerbose) Logger.Instance.DeIndent(); diff --git a/de4dot.code/deobfuscators/ConfuserEx/ConstantDecrypter.cs b/de4dot.code/deobfuscators/ConfuserEx/ConstantDecrypter.cs new file mode 100644 index 000000000..a47c1972f --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/ConstantDecrypter.cs @@ -0,0 +1,543 @@ +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(tempModule); + 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); + } + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/ConstantInliner.cs b/de4dot.code/deobfuscators/ConfuserEx/ConstantInliner.cs new file mode 100644 index 000000000..a04c0d642 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/ConstantInliner.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using de4dot.blocks; +using de4dot.blocks.cflow; +using dnlib.DotNet; +using dnlib.DotNet.Emit; + +namespace de4dot.code.deobfuscators.ConfuserEx +{ + internal class ConstantsInliner : IBlocksDeobfuscator + { + private readonly ArrayValueInliner _arrayValueInliner; + private readonly ByteValueInliner _byteValueInliner; + private readonly DoubleValueInliner _doubleValueInliner; + private readonly Int16ValueInliner _int16ValueInliner; + private readonly Int32ValueInliner _int32ValueInliner; + private readonly Int64ValueInliner _int64ValueInliner; + private readonly SByteValueInliner _sbyteValueInliner; + private readonly SingleValueInliner _singleValueInliner; + private readonly UInt16ValueInliner _uint16ValueInliner; + private readonly UInt32ValueInliner _uint32ValueInliner; + private readonly UInt64ValueInliner _uint64ValueInliner; + private Blocks _blocks; + + public ConstantsInliner(SByteValueInliner sbyteValueInliner, ByteValueInliner byteValueInliner, + Int16ValueInliner int16ValueInliner, UInt16ValueInliner uint16ValueInliner, + Int32ValueInliner int32ValueInliner, + UInt32ValueInliner uint32ValueInliner, Int64ValueInliner int64ValueInliner, + UInt64ValueInliner uint64ValueInliner, + SingleValueInliner singleValueInliner, DoubleValueInliner doubleValueInliner, + ArrayValueInliner arrayValueInliner) + { + _sbyteValueInliner = sbyteValueInliner; + _byteValueInliner = byteValueInliner; + _int16ValueInliner = int16ValueInliner; + _uint16ValueInliner = uint16ValueInliner; + _int32ValueInliner = int32ValueInliner; + _uint32ValueInliner = uint32ValueInliner; + _int64ValueInliner = int64ValueInliner; + _uint64ValueInliner = uint64ValueInliner; + _singleValueInliner = singleValueInliner; + _doubleValueInliner = doubleValueInliner; + _arrayValueInliner = arrayValueInliner; + } + + public bool ExecuteIfNotModified { get; set; } + + public void DeobfuscateBegin(Blocks blocks) + { + _blocks = blocks; + } + + public bool Deobfuscate(List allBlocks) + { + var modified = false; + foreach (var block in allBlocks) + { + modified |= _sbyteValueInliner.Decrypt(_blocks.Method, allBlocks) != 0; + modified |= _byteValueInliner.Decrypt(_blocks.Method, allBlocks) != 0; + modified |= _int16ValueInliner.Decrypt(_blocks.Method, allBlocks) != 0; + modified |= _uint16ValueInliner.Decrypt(_blocks.Method, allBlocks) != 0; + modified |= _int32ValueInliner.Decrypt(_blocks.Method, allBlocks) != 0; + modified |= _uint32ValueInliner.Decrypt(_blocks.Method, allBlocks) != 0; + modified |= _int64ValueInliner.Decrypt(_blocks.Method, allBlocks) != 0; + modified |= _uint64ValueInliner.Decrypt(_blocks.Method, allBlocks) != 0; + modified |= _singleValueInliner.Decrypt(_blocks.Method, allBlocks) != 0; + modified |= _doubleValueInliner.Decrypt(_blocks.Method, allBlocks) != 0; + modified |= _arrayValueInliner.Decrypt(_blocks.Method, allBlocks) != 0; + } + return modified; + } + } + + public class SByteValueInliner : ValueInlinerBase + { + protected override void InlineReturnValues(IList callResults) + { + foreach (var callResult in callResults) + { + var block = callResult.block; + var num = callResult.callEndIndex - callResult.callStartIndex + 1; + + block.Replace(callResult.callStartIndex, num, Instruction.CreateLdcI4((int) callResult.returnValue)); + RemoveUnboxInstruction(block, callResult.callStartIndex + 1, "System.SByte"); + Logger.v("Decrypted sbyte: {0}", callResult.returnValue); + } + } + } + + public class ByteValueInliner : ValueInlinerBase + { + protected override void InlineReturnValues(IList callResults) + { + foreach (var callResult in callResults) + { + var block = callResult.block; + var num = callResult.callEndIndex - callResult.callStartIndex + 1; + + block.Replace(callResult.callStartIndex, num, Instruction.CreateLdcI4((int) callResult.returnValue)); + RemoveUnboxInstruction(block, callResult.callStartIndex + 1, "System.Byte"); + Logger.v("Decrypted byte: {0}", callResult.returnValue); + } + } + } + + public class Int16ValueInliner : ValueInlinerBase + { + protected override void InlineReturnValues(IList callResults) + { + foreach (var callResult in callResults) + { + var block = callResult.block; + var num = callResult.callEndIndex - callResult.callStartIndex + 1; + + block.Replace(callResult.callStartIndex, num, Instruction.CreateLdcI4((int) callResult.returnValue)); + RemoveUnboxInstruction(block, callResult.callStartIndex + 1, "System.Int16"); + Logger.v("Decrypted int16: {0}", callResult.returnValue); + } + } + } + + public class UInt16ValueInliner : ValueInlinerBase + { + protected override void InlineReturnValues(IList callResults) + { + foreach (var callResult in callResults) + { + var block = callResult.block; + var num = callResult.callEndIndex - callResult.callStartIndex + 1; + + block.Replace(callResult.callStartIndex, num, Instruction.CreateLdcI4((int) callResult.returnValue)); + RemoveUnboxInstruction(block, callResult.callStartIndex + 1, "System.UInt16"); + Logger.v("Decrypted uint16: {0}", callResult.returnValue); + } + } + } + + public class UInt32ValueInliner : ValueInlinerBase + { + protected override void InlineReturnValues(IList callResults) + { + foreach (var callResult in callResults) + { + var block = callResult.block; + var num = callResult.callEndIndex - callResult.callStartIndex + 1; + + block.Replace(callResult.callStartIndex, num, Instruction.CreateLdcI4((int) callResult.returnValue)); + RemoveUnboxInstruction(block, callResult.callStartIndex + 1, "System.UInt32"); + Logger.v("Decrypted uint32: {0}", callResult.returnValue); + } + } + } + + public class UInt64ValueInliner : ValueInlinerBase + { + protected override void InlineReturnValues(IList callResults) + { + foreach (var callResult in callResults) + { + var block = callResult.block; + var num = callResult.callEndIndex - callResult.callStartIndex + 1; + + block.Replace(callResult.callStartIndex, num, + OpCodes.Ldc_I8.ToInstruction((long) callResult.returnValue)); + RemoveUnboxInstruction(block, callResult.callStartIndex + 1, "System.UInt64"); + Logger.v("Decrypted uint64: {0}", callResult.returnValue); + } + } + } + + public class ArrayValueInliner : ValueInlinerBase + { + private readonly InitializedDataCreator _initializedDataCreator; + + public ArrayValueInliner(InitializedDataCreator initializedDataCreator) + { + _initializedDataCreator = initializedDataCreator; + } + + protected override void InlineReturnValues(IList callResults) + { + foreach (var callResult in callResults) + { + var block = callResult.block; + var num = callResult.callEndIndex - callResult.callStartIndex + 1; + + var generic = ((MethodSpec) callResult.GetMethodRef()).GenericInstMethodSig.GenericArguments; + var sig = generic[0].Next.ToTypeDefOrRef(); + + _initializedDataCreator.AddInitializeArrayCode(block, callResult.callStartIndex, num, sig, + callResult.returnValue as byte[]); + RemoveUnboxInstruction(block, callResult.callStartIndex + 1, sig.ToString()); //TODO: sig.ToString() ?? + Logger.v("Decrypted array <{1}>: {0}", callResult.returnValue, sig.ToString()); + } + } + } +} \ No newline at end of file diff --git a/de4dot.code/deobfuscators/ConfuserEx/ControlFlowFixer.cs b/de4dot.code/deobfuscators/ConfuserEx/ControlFlowFixer.cs new file mode 100644 index 000000000..8dcc62656 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/ControlFlowFixer.cs @@ -0,0 +1,350 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using de4dot.blocks; +using de4dot.blocks.cflow; +using de4dot.code.deobfuscators.ConfuserEx.x86; +using dnlib.DotNet; +using dnlib.DotNet.Emit; + +namespace de4dot.code.deobfuscators.ConfuserEx +{ + internal class ControlFlowFixer : IBlocksDeobfuscator + { + public bool ExecuteIfNotModified { get; } = false; + public List NativeMethods = new List(); + + private readonly InstructionEmulator _instructionEmulator = new InstructionEmulator(); + + private Blocks _blocks; + private Local _switchKey; + + private int? CalculateKey(SwitchData switchData) + { + var popValue = _instructionEmulator.Peek(); + if (popValue == null || !popValue.IsInt32() || !(popValue as Int32Value).AllBitsValid()) + return null; + + _instructionEmulator.Pop(); + int num = ((Int32Value)popValue).Value; + + if (switchData is NativeSwitchData) + { + var nativeSwitchData = (NativeSwitchData)switchData; + var nativeMethod = new X86Method(nativeSwitchData.NativeMethodDef, _blocks.Method.Module as ModuleDefMD); //TODO: Possible null + return nativeMethod.Execute(num); + } + if (switchData is NormalSwitchData) + { + var normalSwitchData = (NormalSwitchData)switchData; + return num ^ normalSwitchData.Key.Value; + } + return null; + } + + private int? CalculateSwitchCaseIndex(Block block, SwitchData switchData, int key) + { + if (switchData is NativeSwitchData) + { + _instructionEmulator.Push(new Int32Value(key)); + _instructionEmulator.Emulate(block.Instructions, block.SwitchData.IsKeyHardCoded ? 2 : 1, block.Instructions.Count - 1); + + var popValue = _instructionEmulator.Peek(); + _instructionEmulator.Pop(); + return ((Int32Value)popValue).Value; + } + if (switchData is NormalSwitchData) + { + var normalSwitchData = (NormalSwitchData)switchData; + return key % normalSwitchData.DivisionKey; + } + return null; + } + + private void ProcessHardcodedSwitch(Block switchBlock) // a single-case switch + { + var targets = switchBlock.Targets; + _instructionEmulator.Push(new Int32Value(switchBlock.SwitchData.Key.Value)); + + int? key = CalculateKey(switchBlock.SwitchData); + if (!key.HasValue) + throw new Exception("CRITICAL ERROR: KEY HAS NO VALUE"); + + int? switchCaseIndex = CalculateSwitchCaseIndex(switchBlock, switchBlock.SwitchData, key.Value); + if (!switchCaseIndex.HasValue) + throw new Exception("CRITICAL ERROR: SWITCH CASE HAS NO VALUE"); + if (targets.Count < switchCaseIndex) + throw new Exception("CRITICAL ERROR: KEY OUT OF RANGE"); + + var targetBlock = targets[switchCaseIndex.Value]; + targetBlock.SwitchData.Key = key; + + switchBlock.Instructions.Clear(); + switchBlock.ReplaceLastNonBranchWithBranch(0, targetBlock); + } + + private void ProcessBlock(List switchCaseBlocks, Block block, Block switchBlock) + { + var targets = switchBlock.Targets; + _instructionEmulator.Emulate(block.Instructions, 0, block.Instructions.Count); + + if (_instructionEmulator.Peek().IsUnknown()) + throw new Exception("CRITICAL ERROR: STACK VALUE UNKNOWN"); + + int? key = CalculateKey(switchBlock.SwitchData); + if (!key.HasValue) + throw new Exception("CRITICAL ERROR: KEY HAS NO VALUE"); + + int? switchCaseIndex = CalculateSwitchCaseIndex(switchBlock, switchBlock.SwitchData, key.Value); + if (!switchCaseIndex.HasValue) + throw new Exception("CRITICAL ERROR: SWITCH CASE HAS NO VALUE"); + if (targets.Count < switchCaseIndex) + throw new Exception("CRITICAL ERROR: KEY OUT OF RANGE"); + + var targetBlock = targets[switchCaseIndex.Value]; + targetBlock.SwitchData.Key = key; + + block.Add(new Instr(OpCodes.Pop.ToInstruction())); // neutralize the arithmetics and leave de4dot to remove them + block.ReplaceLastNonBranchWithBranch(0, targetBlock); + + ProcessFallThroughs(switchCaseBlocks, switchBlock, targetBlock, key.Value); + block.Processed = true; + } + + private void ProcessTernaryBlock(List switchCaseBlocks, Block ternaryBlock, Block switchBlock) + { + var targets = switchBlock.Targets; + + for (int i = 0; i < 2; i++) // loop both source blocks + { + var sourceBlock = ternaryBlock.Sources[0]; + + if(ternaryBlock.SwitchData.Key.HasValue) // single instruction: pop -- no key! + SetLocalSwitchKey(ternaryBlock.SwitchData.Key.Value); // set old key for both iterations! + + _instructionEmulator.Emulate(sourceBlock.Instructions, 0, sourceBlock.Instructions.Count); + _instructionEmulator.Emulate(ternaryBlock.Instructions, 0, ternaryBlock.Instructions.Count); + + if (_instructionEmulator.Peek().IsUnknown()) + throw new Exception("CRITICAL ERROR: STACK VALUE UNKNOWN"); + + int? key = CalculateKey(switchBlock.SwitchData); + if (!key.HasValue) + throw new Exception("CRITICAL ERROR: KEY HAS NO VALUE"); + + int? switchCaseIndex = CalculateSwitchCaseIndex(switchBlock, switchBlock.SwitchData, key.Value); + if (!switchCaseIndex.HasValue) + throw new Exception("CRITICAL ERROR: SWITCH CASE HAS NO VALUE"); + if (targets.Count < switchCaseIndex) + throw new Exception("CRITICAL ERROR: KEY OUT OF RANGE"); + + var targetBlock = targets[switchCaseIndex.Value]; + targetBlock.SwitchData.Key = key; + + sourceBlock.Instructions[sourceBlock.Instructions.Count - 1] = new Instr(OpCodes.Pop.ToInstruction()); + sourceBlock.ReplaceLastNonBranchWithBranch(0, targets[switchCaseIndex.Value]); + + ProcessFallThroughs(switchCaseBlocks, switchBlock, targets[switchCaseIndex.Value], key.Value); + // the second source block now becomes the first one + } + + //switchCaseBlock.Instructions.Clear(); + ternaryBlock.Add(new Instr(OpCodes.Pop.ToInstruction())); // don't add pop before both iterations have finished + ternaryBlock.Processed = true; + } + + + public void DeobfuscateBegin(Blocks blocks) + { + _blocks = blocks; + _instructionEmulator.Initialize(_blocks, true); + } + + public bool Deobfuscate(List methodBlocks) + { + List switchBlocks = GetSwitchBlocks(methodBlocks); // blocks that contain a switch + int modifications = 0; + + foreach (Block switchBlock in switchBlocks) + { + if (switchBlock.SwitchData.IsKeyHardCoded) + { + ProcessHardcodedSwitch(switchBlock); + modifications++; + continue; + } + + _switchKey = Instr.GetLocalVar(_blocks.Locals, + switchBlock.Instructions[switchBlock.Instructions.Count - 4]); + + if (DeobfuscateSwitchBlock(methodBlocks, switchBlock)) + modifications++; + } + return modifications > 0; + } + + private bool DeobfuscateSwitchBlock(List methodBlocks, Block switchBlock) + { + List switchFallThroughs = methodBlocks.FindAll(b => b.FallThrough == switchBlock); // blocks that fallthrough to the switch block + _instructionEmulator.Initialize(_blocks, true); //TODO: Remove temporary precaution + + int blocksLeft = switchFallThroughs.Count; // how many blocks left to proccess + int blockIndex = 0; // block that sets the first switch destination + int failedCount = 0; + + while (blocksLeft > 0) + { + if (blockIndex > switchFallThroughs.Count - 1) + blockIndex = 0; + + if (failedCount > switchFallThroughs.Count) + { + Console.WriteLine("Some blocks couldn't be processed!"); + break; + } + + Block switchCaseBlock = switchFallThroughs[blockIndex]; + if (switchCaseBlock.Processed) + { + blockIndex++; + continue; + } + + if (NeedSwitchKey(switchCaseBlock)) + { + if (!switchCaseBlock.SwitchData.Key.HasValue) + { + failedCount++; + blockIndex++; + continue; + } + SetLocalSwitchKey(switchCaseBlock.SwitchData.Key.Value); + } + + if (switchCaseBlock.IsTernary()) { + ProcessTernaryBlock(switchFallThroughs, switchCaseBlock, switchBlock); + } + else { + ProcessBlock(switchFallThroughs, switchCaseBlock, switchBlock); + } + + failedCount = 0; + blocksLeft--; + blockIndex++; + } + + if (blocksLeft == switchFallThroughs.Count) // Have we modified anything? + return false; + + return true; + } + + + public bool IsConfuserExSwitchBlock(Block block) + { + if (block.LastInstr.OpCode.Code != Code.Switch || ((Instruction[])block.LastInstr.Operand)?.Length == 0) + return false; + + var instructions = block.Instructions; + var lastIndex = instructions.Count - 1; + + if (instructions.Count < 4) + return false; + if (!instructions[lastIndex - 3].IsStloc()) + return false; + if (!instructions[lastIndex - 2].IsLdcI4()) + return false; + if (instructions[lastIndex - 1].OpCode != OpCodes.Rem_Un) + return false; + + var nativeSwitchData = new NativeSwitchData(block); + if (nativeSwitchData.Initialize()) + { + block.SwitchData = nativeSwitchData; + if (!NativeMethods.Contains(nativeSwitchData.NativeMethodDef)) // add for remove + NativeMethods.Add(nativeSwitchData.NativeMethodDef); + return true; + } + + var normalSwitchData = new NormalSwitchData(block); + if (normalSwitchData.Initialize()) + { + block.SwitchData = normalSwitchData; + return true; + } + + return false; + } + + public List GetSwitchBlocks(List blocks) // get the blocks which contain the switch statement + { + List switchBlocks = new List(); + + foreach (Block block in blocks) + if (IsConfuserExSwitchBlock(block)) + switchBlocks.Add(block); + + return switchBlocks; + } + + + private readonly List _processedFallThroughs = new List(); + + // add the switch key to all appropriate fallthroughs + private void ProcessFallThroughs(List switchCaseBlocks, Block switchBlock, Block targetBlock, int switchKey) + { + DoProcessFallThroughs(switchCaseBlocks, switchBlock, targetBlock, switchKey); + _processedFallThroughs.Clear(); + } + + private void DoProcessFallThroughs(List switchCaseBlocks, Block switchBlock, Block targetBlock, int switchKey) + { + if (_processedFallThroughs.Contains(targetBlock)) + return; + _processedFallThroughs.Add(targetBlock); + + if (targetBlock.FallThrough == switchBlock && switchCaseBlocks.Contains(targetBlock) && !targetBlock.SwitchData.Key.HasValue) + targetBlock.SwitchData.Key = switchKey; + + var fallThrough = targetBlock.FallThrough; + if (fallThrough == null) + return; + + if (fallThrough.LastInstr.OpCode != OpCodes.Ret && fallThrough != switchBlock) + DoProcessFallThroughs(switchCaseBlocks, switchBlock, fallThrough, switchKey); + + if (targetBlock.CountTargets() > 1) + foreach (Block targetBlockTarget in targetBlock.Targets) + { + if (targetBlockTarget == switchBlock) + return; + DoProcessFallThroughs(switchCaseBlocks, switchBlock, targetBlockTarget, switchKey); + } + } + + + private bool NeedSwitchKey(Block block) + { + foreach (var instr in block.Instructions) + if (instr.IsLdloc() && Instr.GetLocalVar(_blocks.Locals, instr) == _switchKey) + return true; + return false; + } + + private int? GetSwitchKey() + { + var val = _instructionEmulator.GetLocal(_switchKey); + if (!val.IsInt32()) + return null; + var value = val as Int32Value; + if (value == null || !value.AllBitsValid()) + return null; + return value.Value; + } + + private void SetLocalSwitchKey(int key) + { + _instructionEmulator.SetLocal(_switchKey, new Int32Value(key)); + } + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/Deobfuscator.cs b/de4dot.code/deobfuscators/ConfuserEx/Deobfuscator.cs new file mode 100644 index 000000000..0c34ddbd7 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/Deobfuscator.cs @@ -0,0 +1,295 @@ +/* + Copyright (C) 2011-2017 TheProxy + + This file is part of modified 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.Collections.Generic; +using de4dot.blocks; +using de4dot.blocks.cflow; +using dnlib.DotNet; +using dnlib.DotNet.Emit; + +namespace de4dot.code.deobfuscators.ConfuserEx +{ + public class DeobfuscatorInfo : DeobfuscatorInfoBase + { + internal const string THE_NAME = "ConfuserEx"; + public const string THE_TYPE = "crx"; + private const string DEFAULT_REGEX = DeobfuscatorBase.DEFAULT_ASIAN_VALID_NAME_REGEX; + + public DeobfuscatorInfo() + : base(DEFAULT_REGEX) + { + } + + public override string Name => THE_NAME; + public override string Type => THE_TYPE; + + public override IDeobfuscator CreateDeobfuscator() + { + return new Deobfuscator(new Deobfuscator.Options + { + RenameResourcesInCode = false, + ValidNameRegex = validNameRegex.Get() + }); + } + + private class Deobfuscator : DeobfuscatorBase + { + private readonly ControlFlowFixer _controlFlowFixer = new ControlFlowFixer(); + + private bool _canRemoveLzma = true; + private ConstantsDecrypter _constantDecrypter; + private bool _detectedConfuserExAttribute, _deobfuscating; + private LzmaFinder _lzmaFinder; + private ProxyCallFixer _proxyCallFixer; + private ResourceDecrypter _resourceDecrypter; + private string _version = ""; + + public Deobfuscator(Options options) + : base(options) + { + } + + public override string Type => THE_TYPE; + public override string TypeLong => THE_NAME; + public override string Name => $"{TypeLong} {_version}"; + + public override IEnumerable BlocksDeobfuscators + { + get + { + var list = new List(); + list.Add(_controlFlowFixer); + + if (_deobfuscating && _int32ValueInliner != null) + { + var constantInliner = new ConstantsInliner(_sbyteValueInliner, _byteValueInliner, + _int16ValueInliner, + _uint16ValueInliner, _int32ValueInliner, _uint32ValueInliner, _int64ValueInliner, + _uint64ValueInliner, _singleValueInliner, _doubleValueInliner, _arrayValueInliner) + { + ExecuteIfNotModified = true + }; + list.Add(constantInliner); + } + return list; + } + } + + protected override int DetectInternal() + { + var val = 0; + if (_detectedConfuserExAttribute) val += 2; + if (_lzmaFinder.FoundLzma) val += 10; + if (_constantDecrypter.Detected) val += 10; + if (_resourceDecrypter.Detected) val += 10; + return val; + } + + protected override void ScanForObfuscator() + { + _lzmaFinder = new LzmaFinder(module, DeobfuscatedFile); + _lzmaFinder.Find(); + + _constantDecrypter = new ConstantsDecrypter(module, _lzmaFinder.Method, DeobfuscatedFile); + _resourceDecrypter = new ResourceDecrypter(module, _lzmaFinder.Method, DeobfuscatedFile); + + if (_lzmaFinder.FoundLzma) + { + _constantDecrypter.Find(); + _resourceDecrypter.Find(); + } + + _proxyCallFixer = new ProxyCallFixer(module, DeobfuscatedFile); + _proxyCallFixer.FindDelegateCreatorMethod(); + _proxyCallFixer.Find(); + + DetectConfuserExAttribute(); + } + + private void DetectConfuserExAttribute() + { + foreach (var attribute in module.CustomAttributes) + { + if (attribute.TypeFullName != "ConfusedByAttribute") + continue; + foreach (var argument in attribute.ConstructorArguments) + { + if (argument.Type.ElementType != ElementType.String) + continue; + var value = argument.Value.ToString(); + if (!value.Contains("ConfuserEx")) + continue; + _detectedConfuserExAttribute = true; + _version = value.Replace("ConfuserEx", "").Trim(); + return; + } + } + } + + public override void DeobfuscateBegin() + { + if (_constantDecrypter.Detected) + { + _sbyteValueInliner = new SByteValueInliner(); + _byteValueInliner = new ByteValueInliner(); + _int16ValueInliner = new Int16ValueInliner(); + _uint16ValueInliner = new UInt16ValueInliner(); + _int32ValueInliner = new Int32ValueInliner(); + _uint32ValueInliner = new UInt32ValueInliner(); + _int64ValueInliner = new Int64ValueInliner(); + _uint64ValueInliner = new UInt64ValueInliner(); + _singleValueInliner = new SingleValueInliner(); + _doubleValueInliner = new DoubleValueInliner(); + _arrayValueInliner = new ArrayValueInliner(initializedDataCreator); + foreach (var info in _constantDecrypter.Decrypters) + { + staticStringInliner.Add(info.Method, + (method, gim, args) => _constantDecrypter.DecryptString(info, gim, (uint) args[0])); + _sbyteValueInliner.Add(info.Method, + (method, gim, args) => _constantDecrypter.DecryptSByte(info, gim, (uint) args[0])); + _byteValueInliner.Add(info.Method, + (method, gim, args) => _constantDecrypter.DecryptByte(info, gim, (uint) args[0])); + _int16ValueInliner.Add(info.Method, + (method, gim, args) => _constantDecrypter.DecryptInt16(info, gim, (uint) args[0])); + _uint16ValueInliner.Add(info.Method, + (method, gim, args) => _constantDecrypter.DecryptUInt16(info, gim, (uint) args[0])); + _int32ValueInliner.Add(info.Method, + (method, gim, args) => _constantDecrypter.DecryptInt32(info, gim, (uint) args[0])); + _uint32ValueInliner.Add(info.Method, + (method, gim, args) => _constantDecrypter.DecryptUInt32(info, gim, (uint) args[0])); + _int64ValueInliner.Add(info.Method, + (method, gim, args) => _constantDecrypter.DecryptInt64(info, gim, (uint) args[0])); + _uint64ValueInliner.Add(info.Method, + (method, gim, args) => _constantDecrypter.DecryptUInt64(info, gim, (uint) args[0])); + _singleValueInliner.Add(info.Method, + (method, gim, args) => _constantDecrypter.DecryptSingle(info, gim, (uint) args[0])); + _doubleValueInliner.Add(info.Method, + (method, gim, args) => _constantDecrypter.DecryptDouble(info, gim, (uint) args[0])); + _arrayValueInliner.Add(info.Method, + (method, gim, args) => _constantDecrypter.DecryptArray(info, gim, (uint) args[0])); + } + _deobfuscating = true; + } + if (_resourceDecrypter.Detected) + { + _resourceDecrypter.Fix(); + } + + base.DeobfuscateBegin(); + } + + public override void DeobfuscateEnd() + { + FindAndRemoveInlinedMethods(); + + var toRemoveFromCctor = new List(); + + if (_constantDecrypter.Detected) + if (CanRemoveStringDecrypterType) + { + toRemoveFromCctor.Add(_constantDecrypter.Method); + AddMethodToBeRemoved(_constantDecrypter.Method, "Constant Decrypter Initializer"); + foreach (var dec in _constantDecrypter.Decrypters) + AddMethodToBeRemoved(dec.Method, "Constant Decrypter Method"); + AddFieldsToBeRemoved(_constantDecrypter.Fields, "Constant Decrypter Fields"); + AddTypeToBeRemoved(_constantDecrypter.Type, "Array field signature type"); + } + else + { + _canRemoveLzma = false; + } + + if (_resourceDecrypter.Detected && _resourceDecrypter.CanRemoveLzma) + { + toRemoveFromCctor.Add(_resourceDecrypter.Method); + AddMethodToBeRemoved(_resourceDecrypter.Method, "Resource decrypter Initializer method"); + AddMethodToBeRemoved(_resourceDecrypter.AssembyResolveMethod, + "Resource decrypter AssemblyResolve method"); + AddFieldsToBeRemoved(_resourceDecrypter.Fields, "Constant Decrypter Fields"); + AddTypeToBeRemoved(_resourceDecrypter.Type, "Array field signature type"); + } + + if (!_constantDecrypter.CanRemoveLzma || !_resourceDecrypter.CanRemoveLzma) + { + _canRemoveLzma = false; + } + + if (_lzmaFinder.FoundLzma && _canRemoveLzma) + { + AddMethodToBeRemoved(_lzmaFinder.Method, "Lzma Decompress method"); + AddTypesToBeRemoved(_lzmaFinder.Types, "Lzma Nested Types"); + } + + if (_proxyCallFixer.Detected) + { + AddTypesToBeRemoved(_proxyCallFixer.DelegateTypes, "Proxy delegates"); + AddMethodsToBeRemoved(_proxyCallFixer.DelegateCreatorMethods, "Proxy creator methods"); + AddTypesToBeRemoved(_proxyCallFixer.AttributeTypes, "Proxy creator attributes"); + AddMethodsToBeRemoved(_proxyCallFixer.NativeMethods, "Proxy native methods"); + } + + AddMethodsToBeRemoved(_controlFlowFixer.NativeMethods, "Control flow native methods"); + + var moduleCctor = DotNetUtils.GetModuleTypeCctor(module); + if (moduleCctor != null) + foreach (var instr in moduleCctor.Body.Instructions) + if (instr.OpCode == OpCodes.Call && instr.Operand is MethodDef + && toRemoveFromCctor.Contains((MethodDef) instr.Operand)) + instr.OpCode = OpCodes.Nop; + + //TODO: Might not always be correct + //No more mixed! + module.IsILOnly = true; + + base.DeobfuscateEnd(); + } + + public override IEnumerable GetStringDecrypterMethods() + { + return new List(); + } + + public override void DeobfuscateMethodEnd(Blocks blocks) + { + _proxyCallFixer.Deobfuscate(blocks); + base.DeobfuscateMethodEnd(blocks); + } + + internal class Options : OptionsBase + { + } + + #region ConstantInliners + + private SByteValueInliner _sbyteValueInliner; + private ByteValueInliner _byteValueInliner; + private Int16ValueInliner _int16ValueInliner; + private UInt16ValueInliner _uint16ValueInliner; + private Int32ValueInliner _int32ValueInliner; + private UInt32ValueInliner _uint32ValueInliner; + private Int64ValueInliner _int64ValueInliner; + private UInt64ValueInliner _uint64ValueInliner; + private SingleValueInliner _singleValueInliner; + private DoubleValueInliner _doubleValueInliner; + private ArrayValueInliner _arrayValueInliner; + + #endregion + } + } +} \ No newline at end of file diff --git a/de4dot.code/deobfuscators/ConfuserEx/LzmaFinder.cs b/de4dot.code/deobfuscators/ConfuserEx/LzmaFinder.cs new file mode 100644 index 000000000..a5d6aed32 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/LzmaFinder.cs @@ -0,0 +1,192 @@ +using System.Collections.Generic; +using System.Linq; +using de4dot.blocks; +using dnlib.DotNet; +using dnlib.DotNet.Emit; + +namespace de4dot.code.deobfuscators.ConfuserEx +{ + public class LzmaFinder + { + private readonly ISimpleDeobfuscator _deobfuscator; + + private readonly ModuleDef _module; + + public LzmaFinder(ModuleDef module, ISimpleDeobfuscator deobfuscator) + { + this._module = module; + this._deobfuscator = deobfuscator; + } + + public MethodDef Method { get; private set; } + + public List Types { get; } = new List(); + + public bool FoundLzma => Method != null && Types.Count != 0; + + public void Find() + { + var moduleType = DotNetUtils.GetModuleType(_module); + if (moduleType == null) + return; + foreach (var method in moduleType.Methods) + { + if (!method.HasBody || !method.IsStatic) + continue; + if (!DotNetUtils.IsMethod(method, "System.Byte[]", "(System.Byte[])")) + continue; + _deobfuscator.Deobfuscate(method, SimpleDeobfuscatorFlags.Force); + if (!IsLzmaMethod(method)) + continue; + Method = method; + var type = ((MethodDef) method.Body.Instructions[3].Operand).DeclaringType; + ExtractNestedTypes(type); + } + } + + private bool IsLzmaMethod(MethodDef method) + { + var instructions = method.Body.Instructions; + + if (instructions.Count < 60) + return false; + + var firstInstruction = instructions.FirstOrDefault( + instr => + instr.OpCode == OpCodes.Newobj && + instr.Operand.ToString() == "System.Void System.IO.MemoryStream::.ctor(System.Byte[])"); + + if (firstInstruction == null) + return false; + + var i = instructions.IndexOf(firstInstruction) + 1; + + if (!instructions[i++].IsStloc()) + return false; + if (instructions[i++].OpCode != OpCodes.Newobj) + return false; + if (!instructions[i++].IsStloc()) //.Class1 @class = new .Class1(); + return false; + + if (!instructions[i].IsLdcI4() || instructions[i++].GetLdcI4Value() != 5) + return false; + if (instructions[i++].OpCode != OpCodes.Newarr) + return false; + if (!instructions[i++].IsStloc()) //byte[] buffer = new byte[5]; + return false; + + if (!instructions[i++].IsLdloc()) + return false; + if (!instructions[i++].IsLdloc()) + return false; + if (!instructions[i].IsLdcI4() || instructions[i++].GetLdcI4Value() != 0) + return false; + if (!instructions[i].IsLdcI4() || instructions[i++].GetLdcI4Value() != 5) + return false; + if (instructions[i].OpCode != OpCodes.Callvirt || instructions[i++].Operand.ToString() != + "System.Int32 System.IO.Stream::Read(System.Byte[],System.Int32,System.Int32)") + return false; + if (instructions[i++].OpCode != OpCodes.Pop) //memoryStream.Read(buffer, 0, 5); + return false; + + if (!instructions[i++].IsLdloc()) + return false; + if (!instructions[i++].IsLdloc()) + return false; + if (instructions[i++].OpCode != OpCodes.Callvirt) //@class.method_5(buffer); + return false; + + firstInstruction = + instructions.FirstOrDefault( + instr => + instr.OpCode == OpCodes.Callvirt && + instr.Operand.ToString() == "System.Int32 System.IO.Stream::ReadByte()"); + + if (firstInstruction == null) + return false; + if (i >= instructions.IndexOf(firstInstruction)) + return false; + + i = instructions.IndexOf(firstInstruction) + 1; + + if (!instructions[i++].IsStloc()) //int num2 = memoryStream.ReadByte(); + return false; + + if (!instructions[i++].IsLdloc()) + return false; + if (!instructions[i++].IsLdloc()) + return false; + if (instructions[i++].OpCode != OpCodes.Conv_U1) + return false; + if (instructions[i++].OpCode != OpCodes.Conv_U8) + return false; + if (!instructions[i].IsLdcI4() || instructions[i++].GetLdcI4Value() != 8) + return false; + if (!instructions[i++].IsLdloc()) + return false; + if (instructions[i++].OpCode != OpCodes.Mul) + return false; + if (!instructions[i].IsLdcI4() || instructions[i++].GetLdcI4Value() != 0x3F) + return false; + if (instructions[i++].OpCode != OpCodes.And) + return false; + if (instructions[i++].OpCode != OpCodes.Shl) + return false; + if (instructions[i++].OpCode != OpCodes.Or) + return false; + if (!instructions[i++].IsStloc()) //num |= (long)((long)((ulong)((byte)num2)) << 8 * i); + return false; + + firstInstruction = + instructions.FirstOrDefault( + instr => + instr.OpCode == OpCodes.Newobj && + instr.Operand.ToString() == + "System.Void System.IO.MemoryStream::.ctor(System.Byte[],System.Boolean)"); + + if (firstInstruction == null) + return false; + if (i >= instructions.IndexOf(firstInstruction)) + return false; + + i = instructions.IndexOf(firstInstruction) + 1; + + if (!instructions[i++].IsStloc()) //MemoryStream stream_ = new MemoryStream(array, true); + return false; + + if (!instructions[i++].IsLdloc()) + return false; + if (instructions[i].OpCode != OpCodes.Callvirt || instructions[i++].Operand.ToString() != + "System.Int64 System.IO.Stream::get_Length()") + return false; + if (instructions[i].OpCode != OpCodes.Ldc_I8 || (long) instructions[i++].Operand != 13L) + return false; + if (instructions[i++].OpCode != OpCodes.Sub) + return false; + if (!instructions[i++].IsStloc()) //long long_ = memoryStream.Length - 13L; + return false; + + return true; + } + + private void ExtractNestedTypes(TypeDef type) + { + foreach (var method in type.Methods) + if (method.HasBody) + { + var instr = method.Body.Instructions; + foreach (var inst in instr) + if (inst.Operand is MethodDef) + { + var ntype = (inst.Operand as MethodDef).DeclaringType; + if (!ntype.IsNested) + continue; + if (Types.Contains(ntype)) + continue; + Types.Add(ntype); + ExtractNestedTypes(ntype); + } + } + } + } +} \ No newline at end of file diff --git a/de4dot.code/deobfuscators/ConfuserEx/NativeSwitchData.cs b/de4dot.code/deobfuscators/ConfuserEx/NativeSwitchData.cs new file mode 100644 index 000000000..f4568b0ee --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/NativeSwitchData.cs @@ -0,0 +1,45 @@ +using de4dot.blocks; +using de4dot.code.deobfuscators.ConfuserEx.x86; +using dnlib.DotNet; +using dnlib.DotNet.Emit; + +namespace de4dot.code.deobfuscators.ConfuserEx +{ + public class NativeSwitchData : SwitchData + { + public NativeSwitchData(Block switchBlock) : base(switchBlock) + { + } + + public MethodDef NativeMethodDef; + + public override bool Initialize() + { + var instr = _block.Instructions; + if (instr.Count <= 4) + return false; + + if (instr[0].IsLdcI4() && instr[1].OpCode == OpCodes.Call) + { + IsKeyHardCoded = true; + Key = instr[0].GetLdcI4Value(); + } + + if (!IsKeyHardCoded && instr[0].OpCode != OpCodes.Call) + return false; + + var nativeMethodDef = _block.Instructions[IsKeyHardCoded ? 1 : 0].Operand as MethodDef; + + if (nativeMethodDef == null || !nativeMethodDef.IsStatic || !nativeMethodDef.IsNative) + return false; + if (!DotNetUtils.IsMethod(nativeMethodDef, "System.Int32", "(System.Int32)")) + return false; + for (var i = IsKeyHardCoded ? 2 : 1; i < instr.Count - 1; i++) + if (!instr[i].IsValidInstr()) + return false; + + NativeMethodDef = nativeMethodDef; + return true; + } + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/NormalSwitchData.cs b/de4dot.code/deobfuscators/ConfuserEx/NormalSwitchData.cs new file mode 100644 index 000000000..f59d8821f --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/NormalSwitchData.cs @@ -0,0 +1,40 @@ +using de4dot.blocks; +using dnlib.DotNet.Emit; + +namespace de4dot.code.deobfuscators.ConfuserEx +{ + public class NormalSwitchData : SwitchData + { + public readonly Block Block; + public NormalSwitchData(Block switchBlock) : base(switchBlock) + { + Block = switchBlock; + } + + public int DivisionKey; + + public override bool Initialize() + { + var instr = _block.Instructions; + if (instr.Count != 7) + return false; + + if (!instr[0].IsLdcI4()) + return false; + if (instr[1].OpCode != OpCodes.Xor) + return false; + if (instr[2].OpCode != OpCodes.Dup) + return false; + if (!instr[3].IsStloc()) + return false; + if (!instr[4].IsLdcI4()) + return false; + if (instr[5].OpCode != OpCodes.Rem_Un) + return false; + + Key = instr[0].GetLdcI4Value(); + DivisionKey = instr[4].GetLdcI4Value(); + return true; + } + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/ProxyCallFixer.cs b/de4dot.code/deobfuscators/ConfuserEx/ProxyCallFixer.cs new file mode 100644 index 000000000..6a8e4611e --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/ProxyCallFixer.cs @@ -0,0 +1,416 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using de4dot.blocks; +using de4dot.blocks.cflow; +using de4dot.code.deobfuscators.ConfuserEx.x86; +using dnlib.DotNet; +using dnlib.DotNet.Emit; + +namespace de4dot.code.deobfuscators.ConfuserEx +{ + internal class Context + { + public int ByteNum; + public MethodDef CreateMethod; + public uint FieldToken; + + public Context(uint fieldToken, int byteNum, MethodDef createMethod) + { + FieldToken = fieldToken; + ByteNum = byteNum; // 2nd parameter of the Delegate CreateMethod + CreateMethod = createMethod; + } + } + + internal class ProxyCallFixer : ProxyCallFixer4 + { + private readonly InstructionEmulator _instructionEmulator = new InstructionEmulator(); + private readonly List _processedMethods = new List(); + private readonly ISimpleDeobfuscator _simpleDeobfuscator; + public List AttributeTypes = new List(); + public List DelegateCreatorMethods = new List(); + public List NativeMethods = new List(); + + public ProxyCallFixer(ModuleDefMD module, ISimpleDeobfuscator simpleDeobfuscator) : base(module) + { + _simpleDeobfuscator = simpleDeobfuscator; + } + + public ProxyCallFixer(ModuleDefMD module, ProxyCallFixer4 oldOne) : base(module, oldOne) + { + } + + protected override object CheckCctor(TypeDef type, MethodDef cctor) + { + if (!_processedMethods.Contains(cctor)) + { + _simpleDeobfuscator.Deobfuscate(cctor); + _processedMethods.Add(cctor); + } + + var contexts = new List(); + var instructions = cctor.Body.Instructions; + instructions.SimplifyMacros(cctor.Body.Variables, cctor.Parameters); + for (var i = 0; i < instructions.Count; i++) + { + var instrs = + DotNetUtils.GetInstructions(instructions, i, OpCodes.Ldtoken, OpCodes.Ldc_I4, OpCodes.Call); + if (instrs == null) + continue; + + var fieldToken = ((IField) instrs[0].Operand).MDToken.ToUInt32(); + var byteNum = (int) instrs[1].Operand; + var createMethod = instrs[2].Operand as MethodDef; + + if (!DelegateCreatorMethods.Contains(createMethod)) + DelegateCreatorMethods.Add(createMethod); + + contexts.Add(new Context(fieldToken, byteNum, createMethod)); + } + return contexts.Count == 0 ? null : contexts; + } + + private void DeobfuscateIfNeeded(MethodDef method) + { + if (!_processedMethods.Contains(method)) + { + _simpleDeobfuscator.Deobfuscate(method); + _processedMethods.Add(method); + } + } + + private byte[] GetExtraDataToken(byte[] sigData) + { + var extraData = new byte[4]; + + // [original signature] [extra signature] + // ... X C0 X X X + Array.Copy(sigData, sigData.Length - 3, extraData, 1, 3); // last 3 bytes of signature + extraData[0] = sigData[sigData.Length - 5]; // the byte before C0 + Array.Reverse(extraData); // decryptorMethod reads the bytes backwards + return extraData; + } + + protected override void GetCallInfo(object context, FieldDef field, out IMethod calledMethod, + out OpCode callOpcode) + { + var contexts = (List) context; + var ctx = contexts.First(c => c.FieldToken == field.MDToken.ToInt32()); + var originalMethod = + DotNetUtils.Clone(ctx + .CreateMethod); // backup original method and restore because changes are not universal + DeobfuscateIfNeeded(ctx.CreateMethod); + + var instructions = ctx.CreateMethod.Body.Instructions; + var variables = ctx.CreateMethod.Body.Variables; + var parameters = ctx.CreateMethod.Parameters; + + instructions.SimplifyMacros(variables, parameters); + var sigData = module.ReadBlob(ctx.FieldToken); + var extraDataToken = GetExtraDataToken(sigData); + var modifierMDToken = ((CModOptSig) field.FieldType).Modifier.MDToken.ToInt32(); + + ReplaceMetadataToken(ref instructions, modifierMDToken, variables[0]); + ReplaceFieldNameChars(ref instructions, field.Name, variables[0]); + InlineArrays(ref instructions, extraDataToken, variables[1], variables[2]); + RemoveDecrementorBlock(ref instructions, variables[2]); + + var firstInstruction = GetEmulationStartIndex(instructions, variables[1], variables[2]); + var lastInstruction = + instructions.IndexOf( + instructions.First( + i => i.OpCode == OpCodes.Callvirt && i.Operand.ToString().Contains("GetCustomAttributes"))) - 4; + + var nativeMode = false; + if (instructions[lastInstruction - 1].OpCode == OpCodes.Call) // x86 protection + { + lastInstruction--; // don't try emulating native method + nativeMode = true; + } + + var result = EmulateManagedMethod(ctx.CreateMethod, firstInstruction, lastInstruction); + if (nativeMode) + { + var nativeMethod = (MethodDef) instructions[lastInstruction].Operand; + if (!NativeMethods.Contains(nativeMethod)) + NativeMethods.Add(nativeMethod); + result = EmulateNativeMethod(nativeMethod, result); + } + + result *= GetMagicNumber(field.CustomAttributes[0]); + calledMethod = module.ResolveMemberRef(new MDToken(result).Rid); + + if (calledMethod == null) + throw new Exception(); + + var charNum = GetCharNum(instructions, parameters.Last()); + callOpcode = GetCallOpCode(calledMethod, charNum, ctx.ByteNum); + + ctx.CreateMethod.Body = originalMethod.Body; // restore + } + + private OpCode GetCallOpCode(IMethod calledMethod, int charNum, int byteNum) + { + if (calledMethod.ResolveMethodDef().IsStatic) return OpCodes.Call; + + var charOpCode = (byte) (charNum ^ byteNum); + + if (charOpCode == 0x28) + return OpCodes.Call; + if (charOpCode == 0x6F) + return OpCodes.Callvirt; + if (charOpCode == 0x73) + return OpCodes.Newobj; + throw new Exception(); + } + + private int EmulateNativeMethod(MethodDef externalMethod, int parameter) + { + var nativeMethod = new X86Method(externalMethod, module); //TODO: Possible null + return nativeMethod.Execute(parameter); + } + + private int EmulateManagedMethod(MethodDef method, int startIndex, int endIndex, + params Tuple[] parameters) + { + _instructionEmulator.Initialize(method, false); + foreach (var parameter in parameters) + _instructionEmulator.SetArg(parameter.Item1, new Int32Value(parameter.Item2)); + + for (var i = startIndex; i < endIndex; i++) _instructionEmulator.Emulate(method.Body.Instructions[i]); + + return ((Int32Value) _instructionEmulator.Pop()).Value; + } + + private int GetMagicNumber(CustomAttribute customAttribute) + { + var attributeType = customAttribute.AttributeType.ResolveTypeDef(); + if (!AttributeTypes.Contains(attributeType)) + AttributeTypes.Add(attributeType); + + var ctor = attributeType.FindConstructors().First(); + DeobfuscateIfNeeded(ctor); + + var magicNum = Convert.ToInt32(customAttribute.ConstructorArguments[0].Value); + var parameter = new Tuple(); + parameter.Item1 = ctor.Parameters[1]; + parameter.Item2 = magicNum; + + return EmulateManagedMethod(ctor, 3, ctor.Body.Instructions.Count - 2, parameter); + } + + public void FindDelegateCreatorMethod() + { + var globalType = module.GlobalType; + foreach ( + var method in + globalType.Methods.Where( + m => m.Parameters.Count == 2 && m.Parameters[0].Type.TypeName == "RuntimeFieldHandle")) + { + _simpleDeobfuscator.Deobfuscate(method); + SetDelegateCreatorMethod(method); + } + } //TODO: Improve detection + + + /* 0x000005B7 6F1500000A IL_001F: callvirt instance uint8[][mscorlib] System.Reflection.Module::ResolveSignature(int32) + 0x000005BC FE0E0100 IL_0024: stloc.1 + 0x000005C0 FE0C0100 IL_0028: ldloc.1 + 0x000005C4 8E IL_002C: ldlen + 0x000005C5 69 IL_002D: conv.i4 + 0x000005C6 FE0E0200 IL_002E: stloc.2 */ + private int GetEmulationStartIndex(IList instructions, Local localArray, Local localArraySize) + { + for (var i = 0; i < instructions.Count; i++) + { + var instrs = DotNetUtils.GetInstructions(instructions, i, OpCodes.Callvirt, OpCodes.Stloc, + OpCodes.Ldloc, OpCodes.Ldlen, OpCodes.Conv_I4, OpCodes.Stloc); + + if (instrs == null) + continue; + if (!instrs[0].Operand.ToString().Contains("ResolveSignature")) + continue; + if ((Local) instrs[1].Operand != localArray) + continue; + if ((Local) instrs[2].Operand != localArray) + continue; + if ((Local) instrs[5].Operand != localArraySize) + continue; + + return i + 6; + } + return -1; + } + + /* 0x000008F3 03 IL_02BB: ldarg.1 + 0x000008F4 61 IL_02BC: xor */ + private int GetCharNum(IList instructions, Parameter byteParam) + { + for (var i = 0; i < instructions.Count; i++) + { + var instrs = DotNetUtils.GetInstructions(instructions, i, OpCodes.Ldarg, OpCodes.Xor); + + if (instrs == null) + continue; + if ((Parameter) instrs[0].Operand != byteParam) + continue; + + return (int) instructions[i - 5].Operand; + } + throw new Exception(); + } + + + private void ReplaceFieldNameChars(ref IList instructions, string fieldName, Local fieldLocal) + { + bool foundInstrs; + do + { + foundInstrs = ReplaceFieldNameChar(ref instructions, fieldName, fieldLocal); + } while (foundInstrs); + } + + /* 0x00000375 06 IL_007D: ldloc.0 + 0x00000376 6F1500000A IL_007E: callvirt + 0x0000037B 19 IL_0083: ldc.i4.3 + 0x0000037C 6F1600000A IL_0084: callvirt */ + private bool ReplaceFieldNameChar(ref IList instructions, string fieldName, Local fieldLocal) + { + for (var i = 0; i < instructions.Count; i++) + { + var instrs = DotNetUtils.GetInstructions(instructions, i, OpCodes.Ldloc, OpCodes.Callvirt, + OpCodes.Ldc_I4, OpCodes.Callvirt); + + if (instrs == null) + continue; + if ((Local) instrs[0].Operand != fieldLocal) + continue; + if (!instrs[1].Operand.ToString().Contains("get_Name")) + continue; + if (!instrs[3].Operand.ToString().Contains("get_Chars")) + continue; + + var charIndex = (int) instrs[2].Operand; + int @char = fieldName[charIndex]; + + instructions[i].OpCode = OpCodes.Ldc_I4; + instructions[i].Operand = @char; + instructions[i + 1].OpCode = OpCodes.Nop; + instructions[i + 2].OpCode = OpCodes.Nop; + instructions[i + 3].OpCode = OpCodes.Nop; + return true; + } + return false; + } + + /* 0x0000034A 08 IL_0052: ldloc.2 + 0x0000034B 17 IL_0053: ldc.i4.1 + 0x0000034C 59 IL_0054: sub + 0x0000034D 25 IL_0055: dup + 0x0000034E 0C IL_0056: stloc.2 + 0x0000034F 91 IL_0057: ldelem.u1 */ + private void InlineArrays(ref IList instructions, byte[] values, Local localArray, Local localInt) + { + bool foundInstrs; + var i = 0; + do + { + foundInstrs = InlineArray(ref instructions, values[i++], localArray, localInt); + } while (i < 4 && foundInstrs); + } + + private bool InlineArray(ref IList instructions, int value, Local localArray, Local localInt) + { + for (var i = 0; i < instructions.Count; i++) + { + var instrs = DotNetUtils.GetInstructions(instructions, i, OpCodes.Ldloc, OpCodes.Ldloc, OpCodes.Ldc_I4, + OpCodes.Sub, OpCodes.Dup, OpCodes.Stloc, OpCodes.Ldelem_U1); + + if (instrs == null) + continue; + if ((Local) instrs[0].Operand != localArray) + continue; + if ((Local) instrs[1].Operand != localInt) + continue; + if ((int) instrs[2].Operand != 1) + continue; + if ((Local) instrs[5].Operand != localInt) + continue; + + instructions[i].OpCode = OpCodes.Ldc_I4; + instructions[i].Operand = value; + instructions[i + 1].OpCode = OpCodes.Nop; + instructions[i + 2].OpCode = OpCodes.Nop; + instructions[i + 3].OpCode = OpCodes.Nop; + instructions[i + 4].OpCode = OpCodes.Nop; + instructions[i + 5].OpCode = OpCodes.Nop; + instructions[i + 6].OpCode = OpCodes.Nop; + return true; + } + return false; + } + + /* 0x00000371 08 IL_0079: ldloc.2 + 0x00000372 17 IL_007A: ldc.i4.1 + 0x00000373 59 IL_007B: sub + 0x00000374 0C IL_007C: stloc.2 */ + private void RemoveDecrementorBlock(ref IList instructions, Local localInt) + { + for (var i = 0; i < instructions.Count; i++) + { + var instrs = DotNetUtils.GetInstructions(instructions, i, OpCodes.Ldloc, OpCodes.Ldc_I4, OpCodes.Sub, + OpCodes.Stloc); + + if (instrs == null) + continue; + if ((Local) instrs[0].Operand != localInt) + continue; + if ((int) instrs[1].Operand != 1) + continue; + if ((Local) instrs[3].Operand != localInt) + continue; + + instructions[i].OpCode = OpCodes.Nop; + instructions[i + 1].OpCode = OpCodes.Nop; + instructions[i + 2].OpCode = OpCodes.Nop; + instructions[i + 3].OpCode = OpCodes.Nop; + return; + } + } + + /* 0x000005CF FE0C0000 IL_0037: ldloc.0 + 0x000005D3 6F1600000A IL_003B: callvirt instance class [mscorlib] System.Type[][mscorlib] System.Reflection.FieldInfo::GetOptionalCustomModifiers() + 0x000005D8 2000000000 IL_0040: ldc.i4.0 + 0x000005DD 9A IL_0045: ldelem.ref + 0x000005DE 6F1400000A IL_0046: callvirt instance int32[mscorlib] System.Reflection.MemberInfo::get_MetadataToken() */ + private void ReplaceMetadataToken(ref IList instructions, int metadataToken, Local fieldLocal) + { + for (var i = 0; i < instructions.Count; i++) + { + var instrs = DotNetUtils.GetInstructions(instructions, i, OpCodes.Ldloc, OpCodes.Callvirt, + OpCodes.Ldc_I4, + OpCodes.Ldelem_Ref, OpCodes.Callvirt); + + if (instrs == null) + continue; + if ((Local) instrs[0].Operand != fieldLocal) + continue; + if (!instrs[1].Operand.ToString().Contains("GetOptionalCustomModifiers")) + continue; + if ((int) instrs[2].Operand != 0) + continue; + if (!instrs[4].Operand.ToString().Contains("get_MetadataToken")) + continue; + + instructions[i].OpCode = OpCodes.Ldc_I4; + instructions[i].Operand = metadataToken; + instructions[i + 1].OpCode = OpCodes.Nop; + instructions[i + 2].OpCode = OpCodes.Nop; + instructions[i + 3].OpCode = OpCodes.Nop; + instructions[i + 4].OpCode = OpCodes.Nop; + return; + } + } + } +} \ No newline at end of file diff --git a/de4dot.code/deobfuscators/ConfuserEx/ResourceDecrypter.cs b/de4dot.code/deobfuscators/ConfuserEx/ResourceDecrypter.cs new file mode 100644 index 000000000..4f7637012 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/ResourceDecrypter.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using de4dot.blocks; +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using dnlib.DotNet.Writer; +using FieldAttributes = dnlib.DotNet.FieldAttributes; +using MethodAttributes = dnlib.DotNet.MethodAttributes; +using TypeAttributes = dnlib.DotNet.TypeAttributes; + +namespace de4dot.code.deobfuscators.ConfuserEx +{ + public class ResourceDecrypter + { + private FieldDef _arrayField, _asmField; + + private byte[] _decryptedBytes; + private readonly ISimpleDeobfuscator _deobfuscator; + private readonly MethodDef _lzmaMethod; + + private readonly ModuleDef _module; + + public ResourceDecrypter(ModuleDef module, MethodDef lzmaMethod, ISimpleDeobfuscator deobfsucator) + { + this._module = module; + this._lzmaMethod = lzmaMethod; + _deobfuscator = deobfsucator; + } + + public bool CanRemoveLzma { get; private set; } + + public TypeDef Type { get; private set; } + public MethodDef Method { get; private set; } + public MethodDef AssembyResolveMethod { get; private set; } + public List Fields => new List {_arrayField, _asmField}; + + public bool Detected => Method != null && _decryptedBytes != null && _arrayField != null && _asmField != 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 = inst.Operand as MethodDef; + if (!method.HasBody || !method.IsStatic) + continue; + if (!DotNetUtils.IsMethod(method, "System.Void", "()")) + continue; + + _deobfuscator.Deobfuscate(method, SimpleDeobfuscatorFlags.Force); + + if (!IsResDecryptInit(method, out FieldDef aField, out FieldDef asmField, out MethodDef mtd)) + continue; + + try + { + _decryptedBytes = DecryptArray(method, aField.InitialValue); + } + catch(Exception e) + { + Console.WriteLine(e.Message); + return; + } + + _arrayField = aField; + Type = DotNetUtils.GetType(_module, aField.FieldSig.Type); + _asmField = asmField; + AssembyResolveMethod = mtd; + Method = method; + CanRemoveLzma = true; + } + } + + private bool IsResDecryptInit(MethodDef method, out FieldDef aField, out FieldDef asField, out MethodDef mtd) + { + aField = null; + asField = null; + mtd = null; + var instr = method.Body.Instructions; + if (instr.Count < 15) + return false; + + if (!instr[0].IsLdcI4()) + return false; + if (!instr[1].IsStloc()) //uint num = 96u; + return false; + + if (!instr[2].IsLdcI4()) + return false; + if (instr[0].GetLdcI4Value() != instr[2].GetLdcI4Value()) + return false; + if (instr[3].OpCode != OpCodes.Newarr) + return false; + if (instr[3].Operand.ToString() != "System.UInt32") + return false; + if (instr[4].OpCode != OpCodes.Dup) + return false; + if (instr[5].OpCode != OpCodes.Ldtoken) + return false; + aField = instr[5].Operand as FieldDef; + if (aField?.InitialValue == null) + return false; + if (aField.Attributes != (FieldAttributes.Assembly | FieldAttributes.Static | FieldAttributes.HasFieldRVA)) + return false; + if (instr[6].OpCode != OpCodes.Call) + return false; + if (instr[6].Operand.ToString() != + "System.Void System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(System.Array,System.RuntimeFieldHandle)" + ) + return false; + if (!instr[7].IsStloc()) // uint[] array = new uint[] {.....}; + return false; + + var l = instr.Count; + if (!instr[l - 10].IsLdloc()) + return false; + if (instr[l - 9].OpCode != OpCodes.Call) + return false; + if (instr[l - 9].Operand != _lzmaMethod) + return false; + if (instr[l - 8].OpCode != OpCodes.Call) + return false; + if (instr[l - 8].Operand.ToString() != + "System.Reflection.Assembly System.Reflection.Assembly::Load(System.Byte[])") + return false; + if (instr[l - 7].OpCode != OpCodes.Stsfld) //.assembly_0 = Assembly.Load(array4); + return false; + asField = instr[l - 7].Operand as FieldDef; + if (asField == null) + return false; + + if (instr[l - 6].OpCode != OpCodes.Call) + return false; + if (instr[l - 6].Operand.ToString() != "System.AppDomain System.AppDomain::get_CurrentDomain()") + return false; + if (instr[l - 5].OpCode != OpCodes.Ldnull) + return false; + if (instr[l - 4].OpCode != OpCodes.Ldftn) + return false; + mtd = instr[l - 4].Operand as MethodDef; + if (mtd == null) + return false; + + if (DotNetUtils.IsMethod(mtd, "", "()")) + return false; + + _deobfuscator.Deobfuscate(mtd, SimpleDeobfuscatorFlags.Force); + + if (!IsAssembyResolveMethod(mtd, asField)) + return false; + + if (instr[l - 3].OpCode != OpCodes.Newobj) + return false; + if (instr[l - 2].OpCode != OpCodes.Callvirt) + //AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(.smethod_1); + 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 < 8; 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(tempModule); + 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 bool IsAssembyResolveMethod(MethodDef method, FieldDef field) + { + var instr = method.Body.Instructions; + if (instr.Count != 10) + return false; + + if (instr[0].OpCode != OpCodes.Ldsfld) + return false; + if (instr[0].Operand != field) + return false; + if (instr[1].OpCode != OpCodes.Callvirt) + return false; + if (instr[1].Operand.ToString() != "System.String System.Reflection.Assembly::get_FullName()") + return false; + if (!instr[2].IsLdarg()) + return false; + if (instr[3].OpCode != OpCodes.Callvirt) + return false; + if (instr[3].Operand.ToString() != "System.String System.ResolveEventArgs::get_Name()") + return false; + if (instr[4].OpCode != OpCodes.Call) + return false; + if (instr[4].Operand.ToString() != "System.Boolean System.String::op_Equality(System.String,System.String)") + return false; + if (!instr[5].IsBrfalse()) + return false; + if (instr[6].OpCode != OpCodes.Ldsfld) + return false; + if (instr[6].Operand != field) + return false; + if (instr[7].OpCode != OpCodes.Ret) + return false; + if (instr[8].OpCode != OpCodes.Ldnull) + return false; + if (instr[9].OpCode != OpCodes.Ret) + return false; + + return true; + } + + public void Fix() + { + ModuleDef newModule; + try + { + newModule = ModuleDefMD.Load(_decryptedBytes); + } + catch + { + CanRemoveLzma = false; + return; + } + var toRemove = new List(); + var toAdd = new List(); + foreach (var cryptedResource in _module.Resources) + foreach (var resource in newModule.Resources) + if (cryptedResource.Name == resource.Name) + { + toRemove.Add(cryptedResource); + toAdd.Add(resource); + } + + foreach (var resToRemove in toRemove) + _module.Resources.Remove(resToRemove); + foreach (var resToAdd in toAdd) + _module.Resources.Add(resToAdd); + } + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/Utils.cs b/de4dot.code/deobfuscators/ConfuserEx/Utils.cs new file mode 100644 index 000000000..9d6c21ca8 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/Utils.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using de4dot.blocks; +using dnlib.DotNet; +using dnlib.DotNet.Emit; + +namespace de4dot.code.deobfuscators.ConfuserEx +{ + public static class Utils + { + public static bool IsArithmetical(this Instr instr) + { + switch (instr.OpCode.Code) + { + case Code.Add: + case Code.Add_Ovf: + case Code.Add_Ovf_Un: + case Code.Div: + case Code.Div_Un: + case Code.Mul: + case Code.Mul_Ovf: + case Code.Mul_Ovf_Un: + case Code.Not: + case Code.Shl: + case Code.Shr: + case Code.Shr_Un: + case Code.Sub: + case Code.Sub_Ovf: + case Code.Sub_Ovf_Un: + case Code.Xor: + case Code.And: + case Code.Rem: + case Code.Rem_Un: + case Code.Ceq: + case Code.Cgt: + case Code.Cgt_Un: + case Code.Clt: + case Code.Clt_Un: + case Code.Neg: + case Code.Or: + return true; + } + return false; + } + + public static bool IsConv(this Instr instr) + { + switch (instr.OpCode.Code) + { + case Code.Conv_I1: + case Code.Conv_I2: + case Code.Conv_I4: + case Code.Conv_I8: + case Code.Conv_U1: + case Code.Conv_U2: + case Code.Conv_U4: + case Code.Conv_U8: + case Code.Conv_R4: + case Code.Conv_R8: + case Code.Conv_Ovf_I1: + case Code.Conv_Ovf_I1_Un: + case Code.Conv_Ovf_I2: + case Code.Conv_Ovf_I2_Un: + case Code.Conv_Ovf_I4: + case Code.Conv_Ovf_I4_Un: + case Code.Conv_Ovf_I8: + case Code.Conv_Ovf_I8_Un: + case Code.Conv_Ovf_U1: + case Code.Conv_Ovf_U1_Un: + case Code.Conv_Ovf_U2: + case Code.Conv_Ovf_U2_Un: + case Code.Conv_Ovf_U4: + case Code.Conv_Ovf_U4_Un: + case Code.Conv_Ovf_U8: + case Code.Conv_Ovf_U8_Un: + return true; + } + return false; + } + + public static bool IsLdc(this Instr instr) + { + switch (instr.OpCode.Code) + { + case Code.Ldc_I4: + case Code.Ldc_I4_S: + case Code.Ldc_I4_0: + case Code.Ldc_I4_1: + case Code.Ldc_I4_2: + case Code.Ldc_I4_3: + case Code.Ldc_I4_4: + case Code.Ldc_I4_5: + case Code.Ldc_I4_6: + case Code.Ldc_I4_7: + case Code.Ldc_I4_8: + case Code.Ldc_I4_M1: + case Code.Ldc_I8: + case Code.Ldc_R4: + case Code.Ldc_R8: + return true; + } + return false; + } + + public static bool IsLoc(this Instr instr) + { + switch (instr.OpCode.Code) + { + case Code.Ldloc: + case Code.Ldloc_S: + case Code.Ldloc_0: + case Code.Ldloc_1: + case Code.Ldloc_2: + case Code.Ldloc_3: + case Code.Ldloca: + case Code.Ldloca_S: + case Code.Stloc: + case Code.Stloc_S: + case Code.Stloc_0: + case Code.Stloc_1: + case Code.Stloc_2: + case Code.Stloc_3: + return true; + } + return false; + } + + public static bool IsValidInstr(this Instr instr) + { + return IsArithmetical(instr) || instr.IsConv() || IsLdc(instr) || IsLoc(instr) || + instr.OpCode == OpCodes.Dup; + } + + public static bool IsDup(this Block block) + { + if (block.Sources.Count != 1) + return false; + if (block.Instructions.Count != 2) + return false; + if (!block.FirstInstr.IsLdcI4()) + return false; + if (block.LastInstr.OpCode != OpCodes.Dup) + if (!block.LastInstr.IsLdcI4() || block.LastInstr.GetLdcI4Value() != block.FirstInstr.GetLdcI4Value()) + return false; + return true; + } + + public static MethodDefUser Clone(MethodDef origin) + { + var ret = new MethodDefUser(origin.Name, origin.MethodSig, origin.ImplAttributes, origin.Attributes); + + foreach (GenericParam genericParam in origin.GenericParameters) + ret.GenericParameters.Add(new GenericParamUser(genericParam.Number, genericParam.Flags, "-")); + + ret.Body = origin.Body; + return ret; + } + + public static T[] ConvertArray(T1[] array) + { + var l = Marshal.SizeOf(typeof(T)); + var l1 = Marshal.SizeOf(typeof(T1)); + var buffer = new T[array.Length * l1 / l]; + Buffer.BlockCopy(array, 0, buffer, 0, array.Length * l1); + return buffer; + } + } + + public static class Extensions + { + public static bool IsTernaryPredicate(this Block ternaryPredicateBlock) + { + if (!ternaryPredicateBlock.LastInstr.IsConditionalBranch()) + return false; + + if (ternaryPredicateBlock.CountTargets() > 2) + return false; + + var source1 = ternaryPredicateBlock.Targets[0]; + var source2 = ternaryPredicateBlock.FallThrough; + + //if (!IsDup(source1) || !IsDup(source2)) + // return false; + + if (source1.CountTargets() > 1 || source2.CountTargets() > 1) + return false; + + var mainBlock = source1.FallThrough; + + if (mainBlock != source2.FallThrough) + return false; + + if (mainBlock.Sources.Count != 2) + return false; + if (mainBlock.LastInstr.OpCode == OpCodes.Ret) + return false; + + return true; + } + + public static bool IsTernary(this Block block) + { + var sources = block.Sources; + if (sources.Count != 2) + return false; + if (!sources[0].IsDup() || !sources[1].IsDup()) //TODO: Case without DUP? + return false; + if (sources[0].CountTargets() > 1 || sources[1].CountTargets() > 1) + return false; + if (sources[0].FallThrough != block || sources[1].FallThrough != block) + return false; + if (sources[0].Sources[0] != sources[1].Sources[0]) + return false; + if (!sources[0].Sources[0].IsConditionalBranch()) + return false; + if (block.LastInstr.OpCode == OpCodes.Ret) + return false; + + return true; + } + + public static List GetTernaryPredicates(this List switchCaseBlocks) + { + var ternaryPredicates = new List(); + + foreach (var preBlock in switchCaseBlocks) + if (IsTernary(preBlock)) // switchCaseBlock -> 2x sourceBlock -> ternaryPredicateBlock + ternaryPredicates.Add(preBlock.Sources[0].Sources[0]); + + return ternaryPredicates; + } + + public static Block GetTernaryPredicateMainBlock(this Block ternaryPredicateBlock) + { + return ternaryPredicateBlock.FallThrough.FallThrough; + } + } +} \ No newline at end of file diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Constants.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Constants.cs new file mode 100644 index 000000000..264a023bd --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Constants.cs @@ -0,0 +1,236 @@ + +namespace de4dot.Bea +{ + public static class BeaConstants + { + public static int INSTRUCT_LENGTH = 80; + + public enum SegmentRegister : byte + { + ESReg = 0x1, + DSReg = 0x2, + FSReg = 0x4, + GSReg = 0x8, + CSReg = 0x10, + SSReg = 0x20 + } + + public enum PrefixType : byte + { + NotUsedPrefix = 0, + InUsePrefix = 1, + SuperfluousPrefix = 2, + InvalidPrefix = 4, + MandatoryPrefix = 8 + } + + public enum InstructionType : uint + { + GENERAL_PURPOSE_INSTRUCTION = 0x10000, + FPU_INSTRUCTION = 0x20000, + MMX_INSTRUCTION = 0x30000, + SSE_INSTRUCTION = 0x40000, + SSE2_INSTRUCTION = 0x50000, + SSE3_INSTRUCTION = 0x60000, + SSSE3_INSTRUCTION = 0x70000, + SSE41_INSTRUCTION = 0x80000, + SSE42_INSTRUCTION = 0x90000, + SYSTEM_INSTRUCTION = 0xa0000, + VM_INSTRUCTION = 0xb0000, + UNDOCUMENTED_INSTRUCTION = 0xc0000, + AMD_INSTRUCTION = 0xd0000, + ILLEGAL_INSTRUCTION = 0xe0000, + AES_INSTRUCTION = 0xf0000, + CLMUL_INSTRUCTION = 0x100000, + AVX_INSTRUCTION = 0x110000, + AVX2_INSTRUCTION = 0x120000, + MPX_INSTRUCTION = 0x130000, + AVX512_INSTRUCTION = 0x140000, + SHA_INSTRUCTION = 0x150000, + BMI2_INSTRUCTION = 0x160000, + CET_INSTRUCTION = 0x170000, + BMI1_INSTRUCTION = 0x180000, + XSAVEOPT_INSTRUCTION = 0x190000, + FSGSBASE_INSTRUCTION = 0x1a0000, + CLWB_INSTRUCTION = 0x1b0000, + CLFLUSHOPT_INSTRUCTION = 0x1c0000, + FXSR_INSTRUCTION = 0x1d0000, + XSAVE_INSTRUCTION = 0x1e0000, + SGX_INSTRUCTION = 0x1f0000, + PCONFIG_INSTRUCTION = 0x200000, + UINTR_INSTRUCTION = 0x210000, + KL_INSTRUCTION = 0x220000, + AMX_INSTRUCTION = 0x230000, + + DATA_TRANSFER = 0x1, + ARITHMETIC_INSTRUCTION, + LOGICAL_INSTRUCTION, + SHIFT_ROTATE, + BIT_UInt8, + CONTROL_TRANSFER, + STRING_INSTRUCTION, + InOutINSTRUCTION, + ENTER_LEAVE_INSTRUCTION, + FLAG_CONTROL_INSTRUCTION, + SEGMENT_REGISTER, + MISCELLANEOUS_INSTRUCTION, + COMPARISON_INSTRUCTION, + LOGARITHMIC_INSTRUCTION, + TRIGONOMETRIC_INSTRUCTION, + UNSUPPORTED_INSTRUCTION, + LOAD_CONSTANTS, + FPUCONTROL, + STATE_MANAGEMENT, + CONVERSION_INSTRUCTION, + SHUFFLE_UNPACK, + PACKED_SINGLE_PRECISION, + SIMD128bits, + SIMD64bits, + CACHEABILITY_CONTROL, + FP_INTEGER_CONVERSION, + SPECIALIZED_128bits, + SIMD_FP_PACKED, + SIMD_FP_HORIZONTAL, + AGENT_SYNCHRONISATION, + PACKED_ALIGN_RIGHT, + PACKED_SIGN, + PACKED_BLENDING_INSTRUCTION, + PACKED_TEST, + PACKED_MINMAX, + HORIZONTAL_SEARCH, + PACKED_EQUALITY, + STREAMING_LOAD, + INSERTION_EXTRACTION, + DOT_PRODUCT, + SAD_INSTRUCTION, + ACCELERATOR_INSTRUCTION, + ROUND_INSTRUCTION + } + + public enum EFlagState : byte + { + TE_ = 1, + MO_ = 2, + RE_ = 4, + SE_ = 8, + UN_ = 0x10, + PR_ = 0x20 + } + + public enum BranchType : short + { + JO = 1, + JC, + JE, + JA, + JS, + JP, + JL, + JG, + JB, + JECXZ, + JmpType, + CallType, + RetType, + JNO = -1, + JNC = -2, + JNE = -3, + JNA = -4, + JNS = -5, + JNP = -6, + JNL = -7, + JNG = -8, + JNB = -9 + } + + public enum ArgumentType : uint + { + NO_ARGUMENT = 0x10000, + REGISTER_TYPE = 0x20000, + MEMORY_TYPE = 0x30000, + CONSTANT_TYPE = 0x40000, + + GENERAL_REG = 0x1, + MMX_REG = 0x2, + SSE_REG = 0x4, + AVX_REG = 0x8, + AVX512_REG = 0x10, + SPECIAL_REG = 0x20, + CR_REG = 0x40, + DR_REG = 0x80, + MEMORY_MANAGEMENT_REG = 0x100, + MPX_REG = 0x200, + OPMASK_REG = 0x400, + SEGMENT_REG = 0x800, + FPU_REG = 0x1000, + TMM_REG = 0x2000, + + RELATIVE_ = 0x4000000, + ABSOLUTE_ = 0x8000000, + + READ = 0x1, + WRITE = 0x2, + + REG0 = 0x1, + REG1 = 0x2, + REG2 = 0x4, + REG3 = 0x8, + REG4 = 0x10, + REG5 = 0x20, + REG6 = 0x40, + REG7 = 0x80, + REG8 = 0x100, + REG9 = 0x200, + REG10 = 0x400, + REG11 = 0x800, + REG12 = 0x1000, + REG13 = 0x2000, + REG14 = 0x4000, + REG15 = 0x8000, + REG16 = 0x10000, + REG17 = 0x20000, + REG18 = 0x40000, + REG19 = 0x80000, + REG20 = 0x100000, + REG21 = 0x200000, + REG22 = 0x400000, + REG23 = 0x800000, + REG24 = 0x1000000, + REG25 = 0x2000000, + REG26 = 0x4000000, + REG27 = 0x8000000, + REG28 = 0x10000000, + REG29 = 0x20000000, + REG30 = 0x40000000, + REG31 = 0x80000000 + } + + public enum SpecialInfo : int + { + UNKNOWN_OPCODE = -1, + OUT_OF_BLOCK = -2, + + /* === mask = 0xff */ + NoTabulation = 0x00000000, + Tabulation = 0x00000001, + + /* === mask = 0xff00 */ + MasmSyntax = 0x00000000, + GoAsmSyntax = 0x00000100, + NasmSyntax = 0x00000200, + ATSyntax = 0x00000400, + IntrinsicMemSyntax= 0x00000800, + + /* === mask = 0xff0000 */ + PrefixedNumeral = 0x00010000, + SuffixedNumeral = 0x00000000, + + /* === mask = 0xff000000 */ + ShowSegmentRegs = 0x01000000, + ShowEVEXMasking = 0x02000000, + + LowPosition = 0, + HighPosition = 1 + } + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Engine.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Engine.cs new file mode 100644 index 000000000..8e518df93 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Engine.cs @@ -0,0 +1,45 @@ +using System.IO; +using System.Runtime.InteropServices; + +namespace de4dot.Bea +{ + public static class BeaEngine + { + // 'de4dot\bin\de4dot.blocks.dll' -> 'de4dot\bin\' + private static string _executingPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); + + static BeaEngine() + { + //TODO: Better handle native DLL discovery + SetDllDirectory(_executingPath); + } + + [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)] + private static extern bool SetDllDirectory(string lpPathName); + + [DllImport("BeaEngine")] + public static extern int Disasm([In, Out, MarshalAs(UnmanagedType.LPStruct)] Disasm disasm); + + [DllImport("BeaEngine")] + private static extern string BeaEngineVersion(); + + [DllImport("BeaEngine")] + private static extern string BeaEngineRevision(); + + public static string Version + { + get + { + return BeaEngineVersion(); + } + } + + public static string Revision + { + get + { + return BeaEngineRevision(); + } + } + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Structs.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Structs.cs new file mode 100644 index 000000000..c5611cef7 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Structs.cs @@ -0,0 +1,140 @@ +using System; +using System.Runtime.InteropServices; + +namespace de4dot.Bea +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public class REX_Struct + { + public byte W_; + public byte R_; + public byte X_; + public byte B_; + public byte state; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public class PrefixInfo + { + public int Number; + public int NbUndefined; + public byte LockPrefix; + public byte OperandSize; + public byte AddressSize; + public byte RepnePrefix; + public byte RepPrefix; + public byte FSPrefix; + public byte SSPrefix; + public byte GSPrefix; + public byte ESPrefix; + public byte CSPrefix; + public byte DSPrefix; + public byte BranchTaken; + public byte BranchNotTaken; + public REX_Struct REX; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] + public string alignment; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public class EFLStruct + { + public byte OF_; + public byte SF_; + public byte ZF_; + public byte AF_; + public byte PF_; + public byte CF_; + public byte TF_; + public byte IF_; + public byte DF_; + public byte NT_; + public byte RF_; + public byte alignment; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public class RegisterType + { + public Int64 type; + public Int64 gpr; + public Int64 mmx; + public Int64 xmm; + public Int64 ymm; + public Int64 zmm; + public Int64 special; + public Int64 cr; + public Int64 dr; + public Int64 mem_management; + public Int64 mpx; + public Int64 opmask; + public Int64 segment; + public Int64 fpu; + public Int64 tmm; + } + + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public class MemoryType + { + public Int32 BaseRegister; + public Int32 IndexRegister; + public Int32 Scale; + public Int64 Displacement; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public class InstructionType + { + public Int32 Category; + public Int32 Opcode; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 24)] + public string Mnemonic; + public Int32 BranchType; + public EFLStruct Flags; + public UInt64 AddrValue; + public Int64 Immediat; + public RegisterType ImplicitModifiedRegs; + public RegisterType ImplicitUsedRegs; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public class ArgumentType + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 24)] + public string OpMnemonic; + public Int32 OpType; + public Int32 OpSize; + public Int32 OpPosition; + public UInt32 AccessMode; + public MemoryType Memory; + public RegisterType Registers; + public UInt32 SegmentReg; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public class Disasm + { + public IntPtr EIP; + public UInt64 VirtualAddr; + public UInt32 SecurityBlock; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + public string CompleteInstr; + public UInt32 Archi; + public UInt64 Options; + public InstructionType Instruction; + public ArgumentType Operand1; + public ArgumentType Operand2; + public ArgumentType Operand3; + public ArgumentType Operand4; + public ArgumentType Operand5; + public ArgumentType Operand6; + public ArgumentType Operand7; + public ArgumentType Operand8; + public ArgumentType Operand9; + public PrefixInfo Prefix; + public Int32 Error; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48, ArraySubType = UnmanagedType.U4)] + UInt32[] Reserved_; + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86ADD.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86ADD.cs new file mode 100644 index 000000000..898c3c1f6 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86ADD.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using de4dot.Bea; +using de4dot.code.deobfuscators.ConfuserEx.x86; + +namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +{ + class X86ADD : X86Instruction + { + public X86ADD(Disasm rawInstruction) : base() + { + Operands = new IX86Operand[2]; + Operands[0] = GetOperand(rawInstruction.Operand1); + Operands[1] = GetOperand(rawInstruction.Operand2); + } + + public override X86OpCode OpCode { get { return X86OpCode.ADD; } } + + public override void Execute(Dictionary registers, Stack localStack) + { + if (Operands[1] is X86ImmediateOperand) + registers[((X86RegisterOperand) Operands[0]).Register.ToString()] += + ((X86ImmediateOperand) Operands[1]).Immediate; + else + registers[((X86RegisterOperand) Operands[0]).Register.ToString()] += + registers[((X86RegisterOperand) Operands[1]).Register.ToString()]; + } + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86DIV.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86DIV.cs new file mode 100644 index 000000000..07b4903a8 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86DIV.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using de4dot.Bea; +using de4dot.code.deobfuscators.ConfuserEx.x86; + +namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +{ + class X86DIV : X86Instruction + { + public X86DIV(Disasm rawInstruction) : base() + { + Operands = new IX86Operand[2]; + Operands[0] = GetOperand(rawInstruction.Operand1); + Operands[1] = GetOperand(rawInstruction.Operand2); + } + + public override X86OpCode OpCode { get { return X86OpCode.DIV; } } + + public override void Execute(Dictionary registers, Stack localStack) + { + + } + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86IMUL.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86IMUL.cs new file mode 100644 index 000000000..c8d4d6163 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86IMUL.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using de4dot.Bea; +using de4dot.code.deobfuscators.ConfuserEx.x86; + +namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +{ + class X86IMUL : X86Instruction + { + public X86IMUL(Disasm rawInstruction) : base() + { + Operands = new IX86Operand[3]; + Operands[0] = GetOperand(rawInstruction.Operand1); + Operands[1] =GetOperand( rawInstruction.Operand2); + Operands[2] = GetOperand(rawInstruction.Operand3); + } + + public override X86OpCode OpCode { get { return X86OpCode.IMUL; } } + + public override void Execute(Dictionary registers, Stack localStack) + { + var source = ((X86RegisterOperand) Operands[0]).Register.ToString(); + var target1 = ((X86RegisterOperand) Operands[1]).Register.ToString(); + + if (Operands[2] is X86ImmediateOperand) + registers[source] = registers[target1]*((X86ImmediateOperand) Operands[2]).Immediate; + else + registers[source] = registers[target1]*registers[((X86RegisterOperand) Operands[2]).Register.ToString()]; + } + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86MOV.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86MOV.cs new file mode 100644 index 000000000..fb9533444 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86MOV.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using de4dot.Bea; +using de4dot.code.deobfuscators.ConfuserEx.x86; + +namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +{ + internal class X86MOV : X86Instruction + { + public X86MOV(Disasm rawInstruction) : base() + { + Operands = new IX86Operand[2]; + Operands[0] = GetOperand(rawInstruction.Operand1); + Operands[1] = GetOperand(rawInstruction.Operand2); + } + + public override X86OpCode OpCode + { + get { return X86OpCode.MOV; } + } + + public override void Execute(Dictionary registers, Stack localStack) + { + if (Operands[1] is X86ImmediateOperand) + registers[((X86RegisterOperand) Operands[0]).Register.ToString()] = + (Operands[1] as X86ImmediateOperand).Immediate; + else + { + var regOperand = (X86RegisterOperand) Operands[0]; + registers[regOperand.Register.ToString()] = + registers[(Operands[1] as X86RegisterOperand).Register.ToString()]; + } + } + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86NEG.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86NEG.cs new file mode 100644 index 000000000..68ead3e13 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86NEG.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using de4dot.Bea; +using de4dot.code.deobfuscators.ConfuserEx.x86; + +namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +{ + class X86NEG : X86Instruction + { + public X86NEG(Disasm rawInstruction) : base() + { + Operands = new IX86Operand[1]; + Operands[0] = GetOperand(rawInstruction.Operand1); + } + + public override X86OpCode OpCode { get { return X86OpCode.NEG; } } + + public override void Execute(Dictionary registers, Stack localStack) + { + registers[((X86RegisterOperand) Operands[0]).Register.ToString()] = + -registers[((X86RegisterOperand) Operands[0]).Register.ToString()]; + } + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86NOT.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86NOT.cs new file mode 100644 index 000000000..6b98143ba --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86NOT.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using de4dot.Bea; +using de4dot.code.deobfuscators.ConfuserEx.x86; + +namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +{ + class X86NOT : X86Instruction + { + public X86NOT(Disasm rawInstruction) : base() + { + Operands = new IX86Operand[1]; + Operands[0] = GetOperand(rawInstruction.Operand1); + } + + public override X86OpCode OpCode { get { return X86OpCode.NOT; } } + + public override void Execute(Dictionary registers, Stack localStack) + { + registers[((X86RegisterOperand)Operands[0]).Register.ToString()] = + ~registers[((X86RegisterOperand) Operands[0]).Register.ToString()]; + } + + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86POP.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86POP.cs new file mode 100644 index 000000000..adf82a5d2 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86POP.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using de4dot.Bea; +using de4dot.code.deobfuscators.ConfuserEx.x86; + +namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +{ + class X86POP : X86Instruction + { + public X86POP(Disasm rawInstruction) : base() + { + Operands = new IX86Operand[1]; + Operands[0] = GetOperand(rawInstruction.Operand1); + } + + public override X86OpCode OpCode { get { return X86OpCode.POP; } } + + public override void Execute(Dictionary registers, Stack localStack) + { + // Pretend to pop stack + if (localStack.Count < 1) + return; + + registers[((X86RegisterOperand) Operands[0]).Register.ToString()] = localStack.Pop(); + } + + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86PUSH.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86PUSH.cs new file mode 100644 index 000000000..5c92c5a56 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86PUSH.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using de4dot.Bea; +using de4dot.code.deobfuscators.ConfuserEx.x86; + +namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +{ + class X86PUSH : X86Instruction + { + public X86PUSH(Disasm rawInstruction) : base() + { + Operands = new IX86Operand[1]; + Operands[0] = GetOperand(rawInstruction.Operand1); + } + + public override X86OpCode OpCode { get { return X86OpCode.PUSH; } } + + public override void Execute(Dictionary registers, Stack localStack) + { + // Pretend to pop stack + if (localStack.Count < 1) + return; + + registers[((X86RegisterOperand) Operands[0]).Register.ToString()] = localStack.Pop(); + } + + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86SUB.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86SUB.cs new file mode 100644 index 000000000..1967ddba8 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86SUB.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using de4dot.Bea; +using de4dot.code.deobfuscators.ConfuserEx.x86; + +namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +{ + class X86SUB : X86Instruction + { + public X86SUB(Disasm rawInstruction) : base() + { + Operands = new IX86Operand[2]; + Operands[0] = GetOperand(rawInstruction.Operand1); + Operands[1] = GetOperand(rawInstruction.Operand2); + } + + public override X86OpCode OpCode { get { return X86OpCode.SUB; } } + + public override void Execute(Dictionary registers, Stack localStack) + { + if (Operands[1] is X86ImmediateOperand) + registers[((X86RegisterOperand)Operands[0]).Register.ToString()] -= + ((X86ImmediateOperand)Operands[1]).Immediate; + else + registers[((X86RegisterOperand)Operands[0]).Register.ToString()] -= + registers[((X86RegisterOperand)Operands[1]).Register.ToString()]; + } + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86XOR.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86XOR.cs new file mode 100644 index 000000000..4d415d3b6 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86XOR.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using de4dot.Bea; +using de4dot.code.deobfuscators.ConfuserEx.x86; + +namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +{ + class X86XOR : X86Instruction + { + public X86XOR(Disasm rawInstruction) : base() + { + Operands = new IX86Operand[2]; + Operands[0] = GetOperand(rawInstruction.Operand1); + Operands[1] = GetOperand(rawInstruction.Operand2); + } + + public override X86OpCode OpCode { get { return X86OpCode.XOR; } } + + public override void Execute(Dictionary registers, Stack localStack) + { + if (Operands[1] is X86ImmediateOperand) + registers[((X86RegisterOperand)Operands[0]).Register.ToString()] ^= + ((X86ImmediateOperand)Operands[1]).Immediate; + else + registers[((X86RegisterOperand)Operands[0]).Register.ToString()] ^= + registers[((X86RegisterOperand)Operands[1]).Register.ToString()]; + } + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/UnmanagedBuff.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/UnmanagedBuff.cs new file mode 100644 index 000000000..648b95cd1 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/UnmanagedBuff.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.InteropServices; + +namespace de4dot.code.deobfuscators.ConfuserEx.x86 +{ + public class UnmanagedBuffer + { + public readonly IntPtr Ptr; + public readonly int Length; + + public UnmanagedBuffer(byte[] data) + { + Ptr = Marshal.AllocHGlobal(data.Length); + Marshal.Copy(data, 0, Ptr, data.Length); + Length = data.Length; + } + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/X86Instruction.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/X86Instruction.cs new file mode 100644 index 000000000..dd351af10 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/X86Instruction.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using System.Globalization; +using de4dot.Bea; + +namespace de4dot.code.deobfuscators.ConfuserEx.x86 +{ + public enum X86OpCode + { + MOV, + ADD, + SUB, + IMUL, + DIV, + NEG, + NOT, + XOR, + POP, + PUSH + } + + public enum X86Register + { + EAX = 537001985, + ECX = 537001986, + EDX = 537001988, + EBX = 537001992, + ESP = 537001989, + EBP = 537001990, + ESI = 537002048, + EDI = 537002112 + } + + public interface IX86Operand + { + } + + public class X86RegisterOperand : IX86Operand + { + public X86Register Register { get; set; } + + public X86RegisterOperand(X86Register reg) + { + Register = reg; + } + } + + public class X86ImmediateOperand : IX86Operand + { + public int Immediate { get; set; } + + public X86ImmediateOperand(int imm) + { + Immediate = imm; + } + } + + public abstract class X86Instruction + { + public abstract X86OpCode OpCode { get; } + public IX86Operand[] Operands { get; set; } + public abstract void Execute(Dictionary registers, Stack localStack); + + public static IX86Operand GetOperand(ArgumentType argument) + { + if (argument.OpType == -2013265920) + return + new X86ImmediateOperand(int.Parse(argument.OpMnemonic.TrimEnd('h'), + NumberStyles.HexNumber)); + return new X86RegisterOperand((X86Register)argument.OpType); + } + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/X86Method.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/X86Method.cs new file mode 100644 index 000000000..fbed7c192 --- /dev/null +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/X86Method.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using ConfuserDeobfuscator.Engine.Routines.Ex.x86; +using ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions; +using de4dot.Bea; +using dnlib.DotNet; + +namespace de4dot.code.deobfuscators.ConfuserEx.x86 +{ + public sealed class X86Method + { + public List Instructions; + + public Stack LocalStack = new Stack(); + public Dictionary Registers = new Dictionary + { + {"EAX", 0}, + {"EBX", 0}, + {"ECX", 0}, + {"EDX", 0}, + {"ESP", 0}, + {"EBP", 0}, + {"ESI", 0}, + {"EDI", 0} + }; + + private readonly ModuleDefMD _module; + public X86Method(MethodDef method,ModuleDefMD module) + { + this._module = module; + Instructions = new List(); + ParseInstructions(method); + } + + private void ParseInstructions(MethodDef method) + { + var rawInstructions = new List(); + + while (true) + { + byte[] bytes = ReadChunk(method, _module); + + var disasm = new Disasm(); + var buff = new UnmanagedBuffer(bytes); + + disasm.EIP = new IntPtr(buff.Ptr.ToInt32()); + + var instruction = BeaEngine.Disasm(disasm); + //_readOffset -= 8 - instruction; // revert offset back for each byte that was not a part of this instruction + var mnemonic = disasm.Instruction.Mnemonic.Trim(); + + if (mnemonic == "ret") //TODO: Check if this is the only return in function, e.g. check for jumps that go beyond this address + { + Marshal.FreeHGlobal(buff.Ptr); + break; + } + + rawInstructions.Add(Clone(disasm)); + //disasm.EIP = new IntPtr(disasm.EIP.ToInt32() + instruction); + + Marshal.FreeHGlobal(buff.Ptr); + } + + //while(rawInstructions.First().Instruction.Mnemonic.Trim() == "pop") + // rawInstructions.Remove(rawInstructions.First()); + + while (rawInstructions.Last().Instruction.Mnemonic.Trim() == "pop") + rawInstructions.Remove(rawInstructions.Last()); + + + foreach (var instr in rawInstructions) + { + switch (instr.Instruction.Mnemonic.Trim()) + { + case "mov": + Instructions.Add(new X86MOV(instr)); + break; + case "add": + Instructions.Add(new X86ADD(instr)); + break; + case "sub": + Instructions.Add(new X86SUB(instr)); + break; + case "imul": + Instructions.Add(new X86IMUL(instr)); + break; + case "div": + Instructions.Add(new X86DIV(instr)); + break; + case "neg": + Instructions.Add(new X86NEG(instr)); + break; + case "not": + Instructions.Add(new X86NOT(instr)); + break; + case "xor": + Instructions.Add(new X86XOR(instr)); + break; + case "pop": + Instructions.Add(new X86POP(instr)); + break; + } + } + } + + private uint _readOffset; + public byte[] ReadChunk(MethodDef method, ModuleDefMD module) + { + var stream = module.Metadata.PEImage.CreateReader(); + var offset = module.Metadata.PEImage.ToFileOffset(method.RVA); + + byte[] buffer = new byte[8]; + + if (_readOffset == 0u) //TODO: Don't use hardcoded offset + _readOffset = (uint) offset + 20u; // skip to actual calculation code + + stream.Position = _readOffset; + + stream.ReadBytes(buffer, 0, 8); // read 8 bytes to make sure that's a whole instruction + _readOffset += 8; + + return buffer; + } + + public int Execute(params int[] @params) + { + foreach (var param in @params) + LocalStack.Push(param); + + foreach (var instr in Instructions) + instr.Execute(Registers, LocalStack); + + return Registers["EAX"]; + } + + public static Disasm Clone(Disasm disasm) + { + return new Disasm + { + Archi = disasm.Archi, + Operand1 = disasm.Operand1, + Operand2 = disasm.Operand2, + Operand3 = disasm.Operand3, + CompleteInstr = disasm.CompleteInstr, + EIP = disasm.EIP, + Instruction = disasm.Instruction, + Options = disasm.Options, + Prefix = disasm.Prefix, + SecurityBlock = disasm.SecurityBlock, + VirtualAddr = disasm.VirtualAddr + }; + } + } +} diff --git a/de4dot.code/deobfuscators/Lzma.cs b/de4dot.code/deobfuscators/Lzma.cs new file mode 100644 index 000000000..7d4641140 --- /dev/null +++ b/de4dot.code/deobfuscators/Lzma.cs @@ -0,0 +1,688 @@ +using System; +using System.IO; + +namespace de4dot.code.deobfuscators +{ + internal static class Lzma + { + const uint kNumStates = 12; + + const int kNumPosSlotBits = 6; + + const uint kNumLenToPosStates = 4; + + const uint kMatchMinLen = 2; + + const int kNumAlignBits = 4; + const uint kAlignTableSize = 1 << kNumAlignBits; + + const uint kStartPosModelIndex = 4; + const uint kEndPosModelIndex = 14; + + const uint kNumFullDistances = 1 << ((int)kEndPosModelIndex / 2); + + const int kNumPosStatesBitsMax = 4; + const uint kNumPosStatesMax = (1 << kNumPosStatesBitsMax); + + const int kNumLowLenBits = 3; + const int kNumMidLenBits = 3; + const int kNumHighLenBits = 8; + const uint kNumLowLenSymbols = 1 << kNumLowLenBits; + const uint kNumMidLenSymbols = 1 << kNumMidLenBits; + + public static byte[] Decompress(byte[] data) + { + var s = new MemoryStream(data); + var decoder = new LzmaDecoder(); + var prop = new byte[5]; + s.Read(prop, 0, 5); + decoder.SetDecoderProperties(prop); + long outSize = 0; + for (int i = 0; i < 8; i++) + { + int v = s.ReadByte(); + outSize |= ((long)(byte)v) << (8 * i); + } + var b = new byte[(int)outSize]; + var z = new MemoryStream(b, true); + long compressedSize = s.Length - 13; + decoder.Code(s, z, compressedSize, outSize); + return b; + } + + struct BitDecoder + { + public const int kNumBitModelTotalBits = 11; + public const uint kBitModelTotal = (1 << kNumBitModelTotalBits); + const int kNumMoveBits = 5; + + uint Prob; + + public void Init() + { + Prob = kBitModelTotal >> 1; + } + + public uint Decode(Decoder rangeDecoder) + { + uint newBound = (rangeDecoder.Range >> kNumBitModelTotalBits) * Prob; + if (rangeDecoder.Code < newBound) + { + rangeDecoder.Range = newBound; + Prob += (kBitModelTotal - Prob) >> kNumMoveBits; + if (rangeDecoder.Range < Decoder.kTopValue) + { + rangeDecoder.Code = (rangeDecoder.Code << 8) | (byte)rangeDecoder.Stream.ReadByte(); + rangeDecoder.Range <<= 8; + } + return 0; + } + rangeDecoder.Range -= newBound; + rangeDecoder.Code -= newBound; + Prob -= (Prob) >> kNumMoveBits; + if (rangeDecoder.Range < Decoder.kTopValue) + { + rangeDecoder.Code = (rangeDecoder.Code << 8) | (byte)rangeDecoder.Stream.ReadByte(); + rangeDecoder.Range <<= 8; + } + return 1; + } + } + + struct BitTreeDecoder + { + readonly BitDecoder[] Models; + readonly int NumBitLevels; + + public BitTreeDecoder(int numBitLevels) + { + NumBitLevels = numBitLevels; + Models = new BitDecoder[1 << numBitLevels]; + } + + public void Init() + { + for (uint i = 1; i < (1 << NumBitLevels); i++) + Models[i].Init(); + } + + public uint Decode(Decoder rangeDecoder) + { + uint m = 1; + for (int bitIndex = NumBitLevels; bitIndex > 0; bitIndex--) + m = (m << 1) + Models[m].Decode(rangeDecoder); + return m - ((uint)1 << NumBitLevels); + } + + public uint ReverseDecode(Decoder rangeDecoder) + { + uint m = 1; + uint symbol = 0; + for (int bitIndex = 0; bitIndex < NumBitLevels; bitIndex++) + { + uint bit = Models[m].Decode(rangeDecoder); + m <<= 1; + m += bit; + symbol |= (bit << bitIndex); + } + return symbol; + } + + public static uint ReverseDecode(BitDecoder[] Models, UInt32 startIndex, + Decoder rangeDecoder, int NumBitLevels) + { + uint m = 1; + uint symbol = 0; + for (int bitIndex = 0; bitIndex < NumBitLevels; bitIndex++) + { + uint bit = Models[startIndex + m].Decode(rangeDecoder); + m <<= 1; + m += bit; + symbol |= (bit << bitIndex); + } + return symbol; + } + } + + class Decoder + { + public const uint kTopValue = (1 << 24); + public uint Code; + public uint Range; + public Stream Stream; + + public void Init(Stream stream) + { + // Stream.Init(stream); + Stream = stream; + + Code = 0; + Range = 0xFFFFFFFF; + for (int i = 0; i < 5; i++) + Code = (Code << 8) | (byte)Stream.ReadByte(); + } + + public void ReleaseStream() + { + Stream = null; + } + + public void Normalize() + { + while (Range < kTopValue) + { + Code = (Code << 8) | (byte)Stream.ReadByte(); + Range <<= 8; + } + } + + public uint DecodeDirectBits(int numTotalBits) + { + uint range = Range; + uint code = Code; + uint result = 0; + for (int i = numTotalBits; i > 0; i--) + { + range >>= 1; + /* + result <<= 1; + if (code >= range) + { + code -= range; + result |= 1; + } + */ + uint t = (code - range) >> 31; + code -= range & (t - 1); + result = (result << 1) | (1 - t); + + if (range < kTopValue) + { + code = (code << 8) | (byte)Stream.ReadByte(); + range <<= 8; + } + } + Range = range; + Code = code; + return result; + } + } + + class LzmaDecoder + { + readonly BitDecoder[] m_IsMatchDecoders = new BitDecoder[kNumStates << kNumPosStatesBitsMax]; + readonly BitDecoder[] m_IsRep0LongDecoders = new BitDecoder[kNumStates << kNumPosStatesBitsMax]; + readonly BitDecoder[] m_IsRepDecoders = new BitDecoder[kNumStates]; + readonly BitDecoder[] m_IsRepG0Decoders = new BitDecoder[kNumStates]; + readonly BitDecoder[] m_IsRepG1Decoders = new BitDecoder[kNumStates]; + readonly BitDecoder[] m_IsRepG2Decoders = new BitDecoder[kNumStates]; + + readonly LenDecoder m_LenDecoder = new LenDecoder(); + + readonly LiteralDecoder m_LiteralDecoder = new LiteralDecoder(); + readonly OutWindow m_OutWindow = new OutWindow(); + readonly BitDecoder[] m_PosDecoders = new BitDecoder[kNumFullDistances - kEndPosModelIndex]; + readonly BitTreeDecoder[] m_PosSlotDecoder = new BitTreeDecoder[kNumLenToPosStates]; + readonly Decoder m_RangeDecoder = new Decoder(); + readonly LenDecoder m_RepLenDecoder = new LenDecoder(); + bool _solid = false; + + uint m_DictionarySize; + uint m_DictionarySizeCheck; + BitTreeDecoder m_PosAlignDecoder = new BitTreeDecoder(kNumAlignBits); + + uint m_PosStateMask; + + public LzmaDecoder() + { + m_DictionarySize = 0xFFFFFFFF; + for (int i = 0; i < kNumLenToPosStates; i++) + m_PosSlotDecoder[i] = new BitTreeDecoder(kNumPosSlotBits); + } + + void SetDictionarySize(uint dictionarySize) + { + if (m_DictionarySize != dictionarySize) + { + m_DictionarySize = dictionarySize; + m_DictionarySizeCheck = Math.Max(m_DictionarySize, 1); + uint blockSize = Math.Max(m_DictionarySizeCheck, (1 << 12)); + m_OutWindow.Create(blockSize); + } + } + + void SetLiteralProperties(int lp, int lc) + { + m_LiteralDecoder.Create(lp, lc); + } + + void SetPosBitsProperties(int pb) + { + uint numPosStates = (uint)1 << pb; + m_LenDecoder.Create(numPosStates); + m_RepLenDecoder.Create(numPosStates); + m_PosStateMask = numPosStates - 1; + } + + void Init(Stream inStream, Stream outStream) + { + m_RangeDecoder.Init(inStream); + m_OutWindow.Init(outStream, _solid); + + uint i; + for (i = 0; i < kNumStates; i++) + { + for (uint j = 0; j <= m_PosStateMask; j++) + { + uint index = (i << kNumPosStatesBitsMax) + j; + m_IsMatchDecoders[index].Init(); + m_IsRep0LongDecoders[index].Init(); + } + m_IsRepDecoders[i].Init(); + m_IsRepG0Decoders[i].Init(); + m_IsRepG1Decoders[i].Init(); + m_IsRepG2Decoders[i].Init(); + } + + m_LiteralDecoder.Init(); + for (i = 0; i < kNumLenToPosStates; i++) + m_PosSlotDecoder[i].Init(); + // m_PosSpecDecoder.Init(); + for (i = 0; i < kNumFullDistances - kEndPosModelIndex; i++) + m_PosDecoders[i].Init(); + + m_LenDecoder.Init(); + m_RepLenDecoder.Init(); + m_PosAlignDecoder.Init(); + } + + public void Code(Stream inStream, Stream outStream, + Int64 inSize, Int64 outSize) + { + Init(inStream, outStream); + + var state = new State(); + state.Init(); + uint rep0 = 0, rep1 = 0, rep2 = 0, rep3 = 0; + + UInt64 nowPos64 = 0; + var outSize64 = (UInt64)outSize; + if (nowPos64 < outSize64) + { + m_IsMatchDecoders[state.Index << kNumPosStatesBitsMax].Decode(m_RangeDecoder); + state.UpdateChar(); + byte b = m_LiteralDecoder.DecodeNormal(m_RangeDecoder, 0, 0); + m_OutWindow.PutByte(b); + nowPos64++; + } + while (nowPos64 < outSize64) + { + // UInt64 next = Math.Min(nowPos64 + (1 << 18), outSize64); + // while(nowPos64 < next) + { + uint posState = (uint)nowPos64 & m_PosStateMask; + if (m_IsMatchDecoders[(state.Index << kNumPosStatesBitsMax) + posState].Decode(m_RangeDecoder) == 0) + { + byte b; + byte prevByte = m_OutWindow.GetByte(0); + if (!state.IsCharState()) + b = m_LiteralDecoder.DecodeWithMatchByte(m_RangeDecoder, + (uint)nowPos64, prevByte, m_OutWindow.GetByte(rep0)); + else + b = m_LiteralDecoder.DecodeNormal(m_RangeDecoder, (uint)nowPos64, prevByte); + m_OutWindow.PutByte(b); + state.UpdateChar(); + nowPos64++; + } + else + { + uint len; + if (m_IsRepDecoders[state.Index].Decode(m_RangeDecoder) == 1) + { + if (m_IsRepG0Decoders[state.Index].Decode(m_RangeDecoder) == 0) + { + if (m_IsRep0LongDecoders[(state.Index << kNumPosStatesBitsMax) + posState].Decode(m_RangeDecoder) == 0) + { + state.UpdateShortRep(); + m_OutWindow.PutByte(m_OutWindow.GetByte(rep0)); + nowPos64++; + continue; + } + } + else + { + UInt32 distance; + if (m_IsRepG1Decoders[state.Index].Decode(m_RangeDecoder) == 0) + { + distance = rep1; + } + else + { + if (m_IsRepG2Decoders[state.Index].Decode(m_RangeDecoder) == 0) + distance = rep2; + else + { + distance = rep3; + rep3 = rep2; + } + rep2 = rep1; + } + rep1 = rep0; + rep0 = distance; + } + len = m_RepLenDecoder.Decode(m_RangeDecoder, posState) + kMatchMinLen; + state.UpdateRep(); + } + else + { + rep3 = rep2; + rep2 = rep1; + rep1 = rep0; + len = kMatchMinLen + m_LenDecoder.Decode(m_RangeDecoder, posState); + state.UpdateMatch(); + uint posSlot = m_PosSlotDecoder[GetLenToPosState(len)].Decode(m_RangeDecoder); + if (posSlot >= kStartPosModelIndex) + { + var numDirectBits = (int)((posSlot >> 1) - 1); + rep0 = ((2 | (posSlot & 1)) << numDirectBits); + if (posSlot < kEndPosModelIndex) + rep0 += BitTreeDecoder.ReverseDecode(m_PosDecoders, + rep0 - posSlot - 1, m_RangeDecoder, numDirectBits); + else + { + rep0 += (m_RangeDecoder.DecodeDirectBits( + numDirectBits - kNumAlignBits) << kNumAlignBits); + rep0 += m_PosAlignDecoder.ReverseDecode(m_RangeDecoder); + } + } + else + rep0 = posSlot; + } + if (rep0 >= nowPos64 || rep0 >= m_DictionarySizeCheck) + { + if (rep0 == 0xFFFFFFFF) + break; + } + m_OutWindow.CopyBlock(rep0, len); + nowPos64 += len; + } + } + } + m_OutWindow.Flush(); + m_OutWindow.ReleaseStream(); + m_RangeDecoder.ReleaseStream(); + } + + public void SetDecoderProperties(byte[] properties) + { + int lc = properties[0] % 9; + int remainder = properties[0] / 9; + int lp = remainder % 5; + int pb = remainder / 5; + UInt32 dictionarySize = 0; + for (int i = 0; i < 4; i++) + dictionarySize += ((UInt32)(properties[1 + i])) << (i * 8); + SetDictionarySize(dictionarySize); + SetLiteralProperties(lp, lc); + SetPosBitsProperties(pb); + } + + static uint GetLenToPosState(uint len) + { + len -= kMatchMinLen; + if (len < kNumLenToPosStates) + return len; + return unchecked((kNumLenToPosStates - 1)); + } + + class LenDecoder + { + readonly BitTreeDecoder[] m_LowCoder = new BitTreeDecoder[kNumPosStatesMax]; + readonly BitTreeDecoder[] m_MidCoder = new BitTreeDecoder[kNumPosStatesMax]; + BitDecoder m_Choice = new BitDecoder(); + BitDecoder m_Choice2 = new BitDecoder(); + BitTreeDecoder m_HighCoder = new BitTreeDecoder(kNumHighLenBits); + uint m_NumPosStates; + + public void Create(uint numPosStates) + { + for (uint posState = m_NumPosStates; posState < numPosStates; posState++) + { + m_LowCoder[posState] = new BitTreeDecoder(kNumLowLenBits); + m_MidCoder[posState] = new BitTreeDecoder(kNumMidLenBits); + } + m_NumPosStates = numPosStates; + } + + public void Init() + { + m_Choice.Init(); + for (uint posState = 0; posState < m_NumPosStates; posState++) + { + m_LowCoder[posState].Init(); + m_MidCoder[posState].Init(); + } + m_Choice2.Init(); + m_HighCoder.Init(); + } + + public uint Decode(Decoder rangeDecoder, uint posState) + { + if (m_Choice.Decode(rangeDecoder) == 0) + return m_LowCoder[posState].Decode(rangeDecoder); + uint symbol = kNumLowLenSymbols; + if (m_Choice2.Decode(rangeDecoder) == 0) + symbol += m_MidCoder[posState].Decode(rangeDecoder); + else + { + symbol += kNumMidLenSymbols; + symbol += m_HighCoder.Decode(rangeDecoder); + } + return symbol; + } + } + + class LiteralDecoder + { + Decoder2[] m_Coders; + int m_NumPosBits; + int m_NumPrevBits; + uint m_PosMask; + + public void Create(int numPosBits, int numPrevBits) + { + if (m_Coders != null && m_NumPrevBits == numPrevBits && + m_NumPosBits == numPosBits) + return; + m_NumPosBits = numPosBits; + m_PosMask = ((uint)1 << numPosBits) - 1; + m_NumPrevBits = numPrevBits; + uint numStates = (uint)1 << (m_NumPrevBits + m_NumPosBits); + m_Coders = new Decoder2[numStates]; + for (uint i = 0; i < numStates; i++) + m_Coders[i].Create(); + } + + public void Init() + { + uint numStates = (uint)1 << (m_NumPrevBits + m_NumPosBits); + for (uint i = 0; i < numStates; i++) + m_Coders[i].Init(); + } + + uint GetState(uint pos, byte prevByte) + { + return ((pos & m_PosMask) << m_NumPrevBits) + (uint)(prevByte >> (8 - m_NumPrevBits)); + } + + public byte DecodeNormal(Decoder rangeDecoder, uint pos, byte prevByte) + { + return m_Coders[GetState(pos, prevByte)].DecodeNormal(rangeDecoder); + } + + public byte DecodeWithMatchByte(Decoder rangeDecoder, uint pos, byte prevByte, byte matchByte) + { + return m_Coders[GetState(pos, prevByte)].DecodeWithMatchByte(rangeDecoder, matchByte); + } + + struct Decoder2 + { + BitDecoder[] m_Decoders; + + public void Create() + { + m_Decoders = new BitDecoder[0x300]; + } + + public void Init() + { + for (int i = 0; i < 0x300; i++) m_Decoders[i].Init(); + } + + public byte DecodeNormal(Decoder rangeDecoder) + { + uint symbol = 1; + do + symbol = (symbol << 1) | m_Decoders[symbol].Decode(rangeDecoder); while (symbol < 0x100); + return (byte)symbol; + } + + public byte DecodeWithMatchByte(Decoder rangeDecoder, byte matchByte) + { + uint symbol = 1; + do + { + uint matchBit = (uint)(matchByte >> 7) & 1; + matchByte <<= 1; + uint bit = m_Decoders[((1 + matchBit) << 8) + symbol].Decode(rangeDecoder); + symbol = (symbol << 1) | bit; + if (matchBit != bit) + { + while (symbol < 0x100) + symbol = (symbol << 1) | m_Decoders[symbol].Decode(rangeDecoder); + break; + } + } while (symbol < 0x100); + return (byte)symbol; + } + } + }; + } + + class OutWindow + { + byte[] _buffer; + uint _pos; + Stream _stream; + uint _streamPos; + uint _windowSize; + + public void Create(uint windowSize) + { + if (_windowSize != windowSize) + { + _buffer = new byte[windowSize]; + } + _windowSize = windowSize; + _pos = 0; + _streamPos = 0; + } + + public void Init(Stream stream, bool solid) + { + ReleaseStream(); + _stream = stream; + if (!solid) + { + _streamPos = 0; + _pos = 0; + } + } + + public void ReleaseStream() + { + Flush(); + _stream = null; + Buffer.BlockCopy(new byte[_buffer.Length], 0, _buffer, 0, _buffer.Length); + } + + public void Flush() + { + uint size = _pos - _streamPos; + if (size == 0) + return; + _stream.Write(_buffer, (int)_streamPos, (int)size); + if (_pos >= _windowSize) + _pos = 0; + _streamPos = _pos; + } + + public void CopyBlock(uint distance, uint len) + { + uint pos = _pos - distance - 1; + if (pos >= _windowSize) + pos += _windowSize; + for (; len > 0; len--) + { + if (pos >= _windowSize) + pos = 0; + _buffer[_pos++] = _buffer[pos++]; + if (_pos >= _windowSize) + Flush(); + } + } + + public void PutByte(byte b) + { + _buffer[_pos++] = b; + if (_pos >= _windowSize) + Flush(); + } + + public byte GetByte(uint distance) + { + uint pos = _pos - distance - 1; + if (pos >= _windowSize) + pos += _windowSize; + return _buffer[pos]; + } + } + + struct State + { + public uint Index; + + public void Init() + { + Index = 0; + } + + public void UpdateChar() + { + if (Index < 4) Index = 0; + else if (Index < 10) Index -= 3; + else Index -= 6; + } + + public void UpdateMatch() + { + Index = (uint)(Index < 7 ? 7 : 10); + } + + public void UpdateRep() + { + Index = (uint)(Index < 7 ? 8 : 11); + } + + public void UpdateShortRep() + { + Index = (uint)(Index < 7 ? 9 : 11); + } + + public bool IsCharState() + { + return Index < 7; + } + } + } +} \ No newline at end of file diff --git a/de4dot.code/deobfuscators/ProxyCallFixerBase.cs b/de4dot.code/deobfuscators/ProxyCallFixerBase.cs index 4a023e3a8..f31066f0d 100644 --- a/de4dot.code/deobfuscators/ProxyCallFixerBase.cs +++ b/de4dot.code/deobfuscators/ProxyCallFixerBase.cs @@ -493,4 +493,242 @@ static IMethod GetCalledMethod(Instr instr) { return instr.Operand as IMethod; } } + + // + // Combines the above 1st and 2nd templates + // + public abstract class ProxyCallFixer4 : ProxyCallFixerBase + { + FieldDefAndDeclaringTypeDict fieldToDelegateInfo = new FieldDefAndDeclaringTypeDict(); + MethodDefAndDeclaringTypeDict proxyMethodToDelegateInfo = new MethodDefAndDeclaringTypeDict(); + + protected ProxyCallFixer4(ModuleDefMD module) + : base(module) + { + } + + protected ProxyCallFixer4(ModuleDefMD module, ProxyCallFixer4 oldOne) + : base(module, oldOne) + { + foreach (var key in oldOne.fieldToDelegateInfo.GetKeys()) + fieldToDelegateInfo.Add(Lookup(key, "Could not find field"), Copy(oldOne.fieldToDelegateInfo.Find(key))); + + foreach (var oldMethod in oldOne.proxyMethodToDelegateInfo.GetKeys()) + { + var oldDi = oldOne.proxyMethodToDelegateInfo.Find(oldMethod); + var method = Lookup(oldMethod, "Could not find proxy method"); + proxyMethodToDelegateInfo.Add(method, Copy(oldDi)); + } + } + + protected void AddDelegateInfo(DelegateInfo di) + { + fieldToDelegateInfo.Add(di.field, di); + } + + protected DelegateInfo GetDelegateInfo(IField field) + { + if (field == null) + return null; + return fieldToDelegateInfo.Find(field); + } + + public void Find() + { + if (delegateCreatorMethods.Count == 0) + return; + + Logger.v("Finding all proxy delegates"); + foreach (var type in GetDelegateTypes()) + { + var cctor = type.FindStaticConstructor(); + if (cctor == null || !cctor.HasBody) + continue; + if (!type.HasFields) + continue; + + object context = CheckCctor(type, cctor); + if (context == null) + continue; + + Logger.v("Found proxy delegate: {0} ({1:X8})", Utils.RemoveNewlines(type), type.MDToken.ToUInt32()); + RemovedDelegateCreatorCalls++; + var fieldToMethod = GetFieldToMethodDictionary(type); + + Logger.Instance.Indent(); + foreach (var field in type.Fields) + { + MethodDef proxyMethod; + bool supportType1 = fieldToMethod.TryGetValue(field, out proxyMethod); + bool supportType2 = field.IsStatic; + + if (!supportType1 && !supportType2) + continue; + + IMethod calledMethod; + OpCode callOpcode; + GetCallInfo(context, field, out calledMethod, out callOpcode); + + if (calledMethod == null) + continue; + + if (supportType1) + { + Add2(proxyMethod, new DelegateInfo(field, calledMethod, callOpcode)); + } + if (supportType2) + { + AddDelegateInfo(new DelegateInfo(field, calledMethod, callOpcode)); + } + + Logger.v("Field: {0}, Opcode: {1}, Method: {2} ({3:X8})", + Utils.RemoveNewlines(field.Name), + callOpcode, + Utils.RemoveNewlines(calledMethod), + calledMethod.MDToken.Raw); + } + Logger.Instance.DeIndent(); + delegateTypesDict[type] = true; + } + } + + protected void Add2(MethodDef method, DelegateInfo di) + { + proxyMethodToDelegateInfo.Add(method, di); + } + + protected abstract object CheckCctor(TypeDef type, MethodDef cctor); + protected abstract void GetCallInfo(object context, FieldDef field, out IMethod calledMethod, out OpCode callOpcode); + + Dictionary GetFieldToMethodDictionary(TypeDef type) + { + var dict = new Dictionary(); + foreach (var method in type.Methods) + { + if (!method.IsStatic || !method.HasBody || method.Name == ".cctor") + continue; + + var instructions = method.Body.Instructions; + for (int i = 0; i < instructions.Count; i++) + { + var instr = instructions[i]; + if (instr.OpCode.Code != Code.Ldsfld) + continue; + var field = instr.Operand as FieldDef; + if (field == null) + continue; + + dict[field] = method; + break; + } + } + return dict; + } + + protected override bool Deobfuscate(Blocks blocks, IList allBlocks) + { + var removeInfos = new Dictionary>(); + + foreach (var block in allBlocks) + { + var instrs = block.Instructions; + for (int i = 0; i < instrs.Count; i++) + { + var instr = instrs[i]; + + if (instr.OpCode == OpCodes.Call) + { + var method = instr.Operand as IMethod; + if (method == null) + continue; + + var di = proxyMethodToDelegateInfo.Find(method); + if (di == null) + continue; + + Add(removeInfos, block, i, di); + } + else if (instr.OpCode == OpCodes.Ldsfld) + { + var di = GetDelegateInfo(instr.Operand as IField); + if (di == null) + continue; + + var callInfo = FindProxyCall(di, block, i); + if (callInfo != null) + { + Add(removeInfos, block, i, null); + Add(removeInfos, callInfo.Block, callInfo.Index, di); + } + else + { + errors++; + Logger.w("Could not fix proxy call. Method: {0} ({1:X8}), Proxy type: {2} ({3:X8})", + Utils.RemoveNewlines(blocks.Method), + blocks.Method.MDToken.ToInt32(), + Utils.RemoveNewlines(di.field.DeclaringType), + di.field.DeclaringType.MDToken.ToInt32()); + } + } + } + } + + return FixProxyCalls(blocks.Method, removeInfos); + } + + protected virtual BlockInstr FindProxyCall(DelegateInfo di, Block block, int index) + { + return FindProxyCall(di, block, index, new Dictionary(), 1); + } + + BlockInstr FindProxyCall(DelegateInfo di, Block block, int index, Dictionary visited, int stack) + { + if (visited.ContainsKey(block)) + return null; + if (index <= 0) + visited[block] = true; + + var instrs = block.Instructions; + for (int i = index + 1; i < instrs.Count; i++) + { + if (stack <= 0) + return null; + var instr = instrs[i]; + instr.Instruction.UpdateStack(ref stack, false); + if (stack < 0) + return null; + + if (instr.OpCode != OpCodes.Call && instr.OpCode != OpCodes.Callvirt) + { + if (stack <= 0) + return null; + continue; + } + var calledMethod = instr.Operand as IMethod; + if (calledMethod == null) + return null; + if (stack != (DotNetUtils.HasReturnValue(calledMethod) ? 1 : 0)) + continue; + if (calledMethod.Name != "Invoke") + return null; + + return new BlockInstr + { + Block = block, + Index = i, + }; + } + if (stack <= 0) + return null; + + foreach (var target in block.GetTargets()) + { + var info = FindProxyCall(di, target, -1, visited, stack); + if (info != null) + return info; + } + + return null; + } + } } diff --git a/de4dot.cui/FilesDeobfuscator.cs b/de4dot.cui/FilesDeobfuscator.cs index ab79adde0..d724b055d 100644 --- a/de4dot.cui/FilesDeobfuscator.cs +++ b/de4dot.cui/FilesDeobfuscator.cs @@ -205,11 +205,11 @@ bool Add(IObfuscatedFile file, bool skipUnknownObfuscator, bool isFromPossibleFi allFiles[key] = true; int oldIndentLevel = Logger.Instance.IndentLevel; - try { + //try { file.DeobfuscatorContext = options.DeobfuscatorContext; file.Load(options.CreateDeobfuscators()); - } - catch (NotSupportedException) { + //} + /*catch (NotSupportedException) { return false; // Eg. unsupported architecture } catch (BadImageFormatException) { @@ -226,14 +226,15 @@ bool Add(IObfuscatedFile file, bool skipUnknownObfuscator, bool isFromPossibleFi return false; // Not a .NET file } catch (Exception ex) { + throw; Logger.Instance.Log(false, null, LoggerEvent.Warning, "Could not load file ({0}): {1}", ex.GetType(), file.Filename); return false; } finally { Logger.Instance.IndentLevel = oldIndentLevel; - } + }*/ - var deob = file.Deobfuscator; + var deob = file.Deobfuscator; if (skipUnknownObfuscator && deob.Type == "un") { Logger.v("Skipping unknown obfuscator: {0}", file.Filename); RemoveModule(file.ModuleDefMD); diff --git a/de4dot.cui/Program.cs b/de4dot.cui/Program.cs index 2aa00488a..6d7aba46f 100644 --- a/de4dot.cui/Program.cs +++ b/de4dot.cui/Program.cs @@ -73,6 +73,7 @@ static IList CreateDeobfuscatorInfos() { new de4dot.code.deobfuscators.CodeVeil.DeobfuscatorInfo(), new de4dot.code.deobfuscators.CodeWall.DeobfuscatorInfo(), new de4dot.code.deobfuscators.Confuser.DeobfuscatorInfo(), + new de4dot.code.deobfuscators.ConfuserEx.DeobfuscatorInfo(), new de4dot.code.deobfuscators.CryptoObfuscator.DeobfuscatorInfo(), new de4dot.code.deobfuscators.DeepSea.DeobfuscatorInfo(), new de4dot.code.deobfuscators.Dotfuscator.DeobfuscatorInfo(),