From f264fe2d9409e95dd8b76a93719807b8e1c5954f Mon Sep 17 00:00:00 2001 From: Melissa Eckardt Date: Thu, 2 Apr 2026 11:26:49 +0200 Subject: [PATCH 1/7] ConfuserEx: Fix issues with native method emulation --- .../ConfuserEx/ProxyCallFixer.cs | 26 ++- .../ConfuserEx/x86/Bea/Constants.cs | 2 +- .../ConfuserEx/x86/Bea/Engine.cs | 89 +++++------ .../ConfuserEx/x86/Bea/Structs.cs | 22 +-- .../ConfuserEx/x86/Instructions/X86ADD.cs | 7 +- .../ConfuserEx/x86/Instructions/X86DIV.cs | 7 +- .../ConfuserEx/x86/Instructions/X86IMUL.cs | 7 +- .../ConfuserEx/x86/Instructions/X86MOV.cs | 7 +- .../ConfuserEx/x86/Instructions/X86NEG.cs | 7 +- .../ConfuserEx/x86/Instructions/X86NOT.cs | 7 +- .../ConfuserEx/x86/Instructions/X86POP.cs | 7 +- .../ConfuserEx/x86/Instructions/X86PUSH.cs | 7 +- .../ConfuserEx/x86/Instructions/X86SUB.cs | 7 +- .../ConfuserEx/x86/Instructions/X86XOR.cs | 7 +- .../ConfuserEx/x86/X86Instruction.cs | 150 +++++++++--------- .../deobfuscators/ConfuserEx/x86/X86Method.cs | 58 +++---- 16 files changed, 197 insertions(+), 220 deletions(-) diff --git a/de4dot.code/deobfuscators/ConfuserEx/ProxyCallFixer.cs b/de4dot.code/deobfuscators/ConfuserEx/ProxyCallFixer.cs index 6a8e4611e..bf47ac46b 100644 --- a/de4dot.code/deobfuscators/ConfuserEx/ProxyCallFixer.cs +++ b/de4dot.code/deobfuscators/ConfuserEx/ProxyCallFixer.cs @@ -139,10 +139,10 @@ protected override void GetCallInfo(object context, FieldDef field, out IMethod } result *= GetMagicNumber(field.CustomAttributes[0]); - calledMethod = module.ResolveMemberRef(new MDToken(result).Rid); + calledMethod = module.ResolveToken(result) as IMethod; if (calledMethod == null) - throw new Exception(); + throw new Exception($"Resolved token is wrong: {result:X8}"); var charNum = GetCharNum(instructions, parameters.Last()); callOpcode = GetCallOpCode(calledMethod, charNum, ctx.ByteNum); @@ -150,19 +150,17 @@ protected override void GetCallInfo(object context, FieldDef field, out IMethod ctx.CreateMethod.Body = originalMethod.Body; // restore } - private OpCode GetCallOpCode(IMethod calledMethod, int charNum, int byteNum) - { - if (calledMethod.ResolveMethodDef().IsStatic) return OpCodes.Call; + private OpCode GetCallOpCode(IMethod calledMethod, int charNum, int byteNum) { + if (calledMethod is not MemberRef && 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(); + return charOpCode switch { + 0x28 => OpCodes.Call, + 0x6F => OpCodes.Callvirt, + 0x73 => OpCodes.Newobj, + _ => throw new Exception($"Invalid charOpCode {charOpCode:X2}") + }; } private int EmulateNativeMethod(MethodDef externalMethod, int parameter) @@ -256,7 +254,7 @@ private int GetCharNum(IList instructions, Parameter byteParam) if ((Parameter) instrs[0].Operand != byteParam) continue; - return (int) instructions[i - 5].Operand; + return (int) instructions[i - 5].Operand; // ldc.i4 placed by ReplaceFieldNameChar() } throw new Exception(); } @@ -413,4 +411,4 @@ private void ReplaceMetadataToken(ref IList instructions, int metad } } } -} \ 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 index 264a023bd..48af52eab 100644 --- a/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Constants.cs +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Constants.cs @@ -3,7 +3,7 @@ namespace de4dot.Bea { public static class BeaConstants { - public static int INSTRUCT_LENGTH = 80; + public const int INSTRUCT_LENGTH = 80; public enum SegmentRegister : byte { diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Engine.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Engine.cs index 8e518df93..ec1715a08 100644 --- a/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Engine.cs +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Engine.cs @@ -1,45 +1,44 @@ -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(); - } - } - } -} +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace de4dot.Bea +{ + public static class BeaEngine + { + // 'de4dot\bin\de4dot.blocks.dll' -> 'de4dot\bin\' + private static readonly string ExecutingPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); + + static BeaEngine() + { + //TODO: Better handle native DLL discovery + if (IsWindows()) + SetDllDirectory(ExecutingPath); + } + + private static bool IsWindows() + { +#if NET5_0_OR_GREATER + return OperatingSystem.IsWindows(); +#else + return Environment.OSVersion.Platform == PlatformID.Win32NT; +#endif + } + + [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 IntPtr BeaEngineVersion(); // returning string would call free() on a const char* + + [DllImport("BeaEngine")] + private static extern IntPtr BeaEngineRevision(); + + public static string Version => Marshal.PtrToStringAnsi(BeaEngineVersion()); + + public static string Revision => Marshal.PtrToStringAnsi(BeaEngineRevision()); + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Structs.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Structs.cs index c5611cef7..ee010ae23 100644 --- a/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Structs.cs +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Structs.cs @@ -13,7 +13,7 @@ public class REX_Struct public byte state; } - [StructLayout(LayoutKind.Sequential, Pack = 1)] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] public class PrefixInfo { public int Number; @@ -32,7 +32,7 @@ public class PrefixInfo public byte BranchTaken; public byte BranchNotTaken; public REX_Struct REX; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2)] public string alignment; } @@ -53,7 +53,7 @@ public class EFLStruct public byte alignment; } - [StructLayout(LayoutKind.Sequential, Pack = 1)] + [StructLayout(LayoutKind.Sequential, Pack = 4)] public class RegisterType { public Int64 type; @@ -74,16 +74,16 @@ public class RegisterType } - [StructLayout(LayoutKind.Sequential, Pack = 1)] + [StructLayout(LayoutKind.Sequential, Pack = 4)] public class MemoryType { - public Int32 BaseRegister; - public Int32 IndexRegister; + public Int64 BaseRegister; + public Int64 IndexRegister; public Int32 Scale; public Int64 Displacement; } - [StructLayout(LayoutKind.Sequential, Pack = 1)] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] public class InstructionType { public Int32 Category; @@ -98,12 +98,12 @@ public class InstructionType public RegisterType ImplicitUsedRegs; } - [StructLayout(LayoutKind.Sequential, Pack = 1)] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] public class ArgumentType { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 24)] public string OpMnemonic; - public Int32 OpType; + public Int64 OpType; public Int32 OpSize; public Int32 OpPosition; public UInt32 AccessMode; @@ -112,13 +112,13 @@ public class ArgumentType public UInt32 SegmentReg; } - [StructLayout(LayoutKind.Sequential, Pack = 1)] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] public class Disasm { public IntPtr EIP; public UInt64 VirtualAddr; public UInt32 SecurityBlock; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = BeaConstants.INSTRUCT_LENGTH)] public string CompleteInstr; public UInt32 Archi; public UInt64 Options; diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86ADD.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86ADD.cs index 898c3c1f6..e098f03fe 100644 --- a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86ADD.cs +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86ADD.cs @@ -1,16 +1,15 @@ using System.Collections.Generic; using de4dot.Bea; -using de4dot.code.deobfuscators.ConfuserEx.x86; -namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +namespace de4dot.code.deobfuscators.ConfuserEx.x86.Instructions { class X86ADD : X86Instruction { - public X86ADD(Disasm rawInstruction) : base() + public X86ADD(Disasm rawInstruction) { Operands = new IX86Operand[2]; Operands[0] = GetOperand(rawInstruction.Operand1); - Operands[1] = GetOperand(rawInstruction.Operand2); + Operands[1] = GetOperand(rawInstruction.Operand2); } public override X86OpCode OpCode { get { return X86OpCode.ADD; } } diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86DIV.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86DIV.cs index 07b4903a8..e06861c9f 100644 --- a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86DIV.cs +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86DIV.cs @@ -1,16 +1,15 @@ using System.Collections.Generic; using de4dot.Bea; -using de4dot.code.deobfuscators.ConfuserEx.x86; -namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +namespace de4dot.code.deobfuscators.ConfuserEx.x86.Instructions { class X86DIV : X86Instruction { - public X86DIV(Disasm rawInstruction) : base() + public X86DIV(Disasm rawInstruction) { Operands = new IX86Operand[2]; Operands[0] = GetOperand(rawInstruction.Operand1); - Operands[1] = GetOperand(rawInstruction.Operand2); + Operands[1] = GetOperand(rawInstruction.Operand2); } public override X86OpCode OpCode { get { return X86OpCode.DIV; } } diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86IMUL.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86IMUL.cs index c8d4d6163..ea952e50c 100644 --- a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86IMUL.cs +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86IMUL.cs @@ -1,17 +1,16 @@ using System.Collections.Generic; using de4dot.Bea; -using de4dot.code.deobfuscators.ConfuserEx.x86; -namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +namespace de4dot.code.deobfuscators.ConfuserEx.x86.Instructions { class X86IMUL : X86Instruction { - public X86IMUL(Disasm rawInstruction) : base() + public X86IMUL(Disasm rawInstruction) { Operands = new IX86Operand[3]; Operands[0] = GetOperand(rawInstruction.Operand1); Operands[1] =GetOperand( rawInstruction.Operand2); - Operands[2] = GetOperand(rawInstruction.Operand3); + Operands[2] = GetOperand(rawInstruction.Operand3); } public override X86OpCode OpCode { get { return X86OpCode.IMUL; } } diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86MOV.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86MOV.cs index fb9533444..bf8cec943 100644 --- a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86MOV.cs +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86MOV.cs @@ -1,16 +1,15 @@ using System.Collections.Generic; using de4dot.Bea; -using de4dot.code.deobfuscators.ConfuserEx.x86; -namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +namespace de4dot.code.deobfuscators.ConfuserEx.x86.Instructions { internal class X86MOV : X86Instruction { - public X86MOV(Disasm rawInstruction) : base() + public X86MOV(Disasm rawInstruction) { Operands = new IX86Operand[2]; Operands[0] = GetOperand(rawInstruction.Operand1); - Operands[1] = GetOperand(rawInstruction.Operand2); + Operands[1] = GetOperand(rawInstruction.Operand2); } public override X86OpCode OpCode diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86NEG.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86NEG.cs index 68ead3e13..a530f7bff 100644 --- a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86NEG.cs +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86NEG.cs @@ -1,15 +1,14 @@ using System.Collections.Generic; using de4dot.Bea; -using de4dot.code.deobfuscators.ConfuserEx.x86; -namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +namespace de4dot.code.deobfuscators.ConfuserEx.x86.Instructions { class X86NEG : X86Instruction { - public X86NEG(Disasm rawInstruction) : base() + public X86NEG(Disasm rawInstruction) { Operands = new IX86Operand[1]; - Operands[0] = GetOperand(rawInstruction.Operand1); + Operands[0] = GetOperand(rawInstruction.Operand1); } public override X86OpCode OpCode { get { return X86OpCode.NEG; } } diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86NOT.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86NOT.cs index 6b98143ba..62a472f2a 100644 --- a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86NOT.cs +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86NOT.cs @@ -1,15 +1,14 @@ using System.Collections.Generic; using de4dot.Bea; -using de4dot.code.deobfuscators.ConfuserEx.x86; -namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +namespace de4dot.code.deobfuscators.ConfuserEx.x86.Instructions { class X86NOT : X86Instruction { - public X86NOT(Disasm rawInstruction) : base() + public X86NOT(Disasm rawInstruction) { Operands = new IX86Operand[1]; - Operands[0] = GetOperand(rawInstruction.Operand1); + Operands[0] = GetOperand(rawInstruction.Operand1); } public override X86OpCode OpCode { get { return X86OpCode.NOT; } } diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86POP.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86POP.cs index adf82a5d2..c75081912 100644 --- a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86POP.cs +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86POP.cs @@ -1,15 +1,14 @@ using System.Collections.Generic; using de4dot.Bea; -using de4dot.code.deobfuscators.ConfuserEx.x86; -namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +namespace de4dot.code.deobfuscators.ConfuserEx.x86.Instructions { class X86POP : X86Instruction { - public X86POP(Disasm rawInstruction) : base() + public X86POP(Disasm rawInstruction) { Operands = new IX86Operand[1]; - Operands[0] = GetOperand(rawInstruction.Operand1); + Operands[0] = GetOperand(rawInstruction.Operand1); } public override X86OpCode OpCode { get { return X86OpCode.POP; } } diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86PUSH.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86PUSH.cs index 5c92c5a56..901368937 100644 --- a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86PUSH.cs +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86PUSH.cs @@ -1,15 +1,14 @@ using System.Collections.Generic; using de4dot.Bea; -using de4dot.code.deobfuscators.ConfuserEx.x86; -namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +namespace de4dot.code.deobfuscators.ConfuserEx.x86.Instructions { class X86PUSH : X86Instruction { - public X86PUSH(Disasm rawInstruction) : base() + public X86PUSH(Disasm rawInstruction) { Operands = new IX86Operand[1]; - Operands[0] = GetOperand(rawInstruction.Operand1); + Operands[0] = GetOperand(rawInstruction.Operand1); } public override X86OpCode OpCode { get { return X86OpCode.PUSH; } } diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86SUB.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86SUB.cs index 1967ddba8..48b3965e3 100644 --- a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86SUB.cs +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86SUB.cs @@ -1,16 +1,15 @@ using System.Collections.Generic; using de4dot.Bea; -using de4dot.code.deobfuscators.ConfuserEx.x86; -namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +namespace de4dot.code.deobfuscators.ConfuserEx.x86.Instructions { class X86SUB : X86Instruction { - public X86SUB(Disasm rawInstruction) : base() + public X86SUB(Disasm rawInstruction) { Operands = new IX86Operand[2]; Operands[0] = GetOperand(rawInstruction.Operand1); - Operands[1] = GetOperand(rawInstruction.Operand2); + Operands[1] = GetOperand(rawInstruction.Operand2); } public override X86OpCode OpCode { get { return X86OpCode.SUB; } } diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86XOR.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86XOR.cs index 4d415d3b6..1d82dd398 100644 --- a/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86XOR.cs +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Instructions/X86XOR.cs @@ -1,16 +1,15 @@ using System.Collections.Generic; using de4dot.Bea; -using de4dot.code.deobfuscators.ConfuserEx.x86; -namespace ConfuserDeobfuscator.Engine.Routines.Ex.x86.Instructions +namespace de4dot.code.deobfuscators.ConfuserEx.x86.Instructions { class X86XOR : X86Instruction { - public X86XOR(Disasm rawInstruction) : base() + public X86XOR(Disasm rawInstruction) { Operands = new IX86Operand[2]; Operands[0] = GetOperand(rawInstruction.Operand1); - Operands[1] = GetOperand(rawInstruction.Operand2); + Operands[1] = GetOperand(rawInstruction.Operand2); } public override X86OpCode OpCode { get { return X86OpCode.XOR; } } diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/X86Instruction.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/X86Instruction.cs index dd351af10..73aa96b0c 100644 --- a/de4dot.code/deobfuscators/ConfuserEx/x86/X86Instruction.cs +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/X86Instruction.cs @@ -1,72 +1,78 @@ -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); - } - } -} +using System; +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 = (int)BeaConstants.ArgumentType.REG0, + ECX = (int)BeaConstants.ArgumentType.REG1, + EDX = (int)BeaConstants.ArgumentType.REG2, + EBX = (int)BeaConstants.ArgumentType.REG3, + ESP = (int)BeaConstants.ArgumentType.REG4, + EBP = (int)BeaConstants.ArgumentType.REG5, + ESI = (int)BeaConstants.ArgumentType.REG6, + EDI = (int)BeaConstants.ArgumentType.REG7 + } + + 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 & 0xFFFFFF) == (long)BeaConstants.ArgumentType.CONSTANT_TYPE) + return + new X86ImmediateOperand(int.Parse(argument.OpMnemonic.TrimEnd('h'), + NumberStyles.HexNumber)); + if (argument.OpType == (long)BeaConstants.ArgumentType.REGISTER_TYPE) + if (argument.Registers.type == (long)BeaConstants.ArgumentType.GENERAL_REG) + return new X86RegisterOperand((X86Register)argument.Registers.gpr); + else + throw new Exception("Unexpected register type: " + (BeaConstants.ArgumentType)argument.Registers.type); + throw new Exception("Unexepcted operand type: " + (BeaConstants.ArgumentType)argument.OpType); + } + } +} diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/X86Method.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/X86Method.cs index fbed7c192..2c9f8f075 100644 --- a/de4dot.code/deobfuscators/ConfuserEx/x86/X86Method.cs +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/X86Method.cs @@ -2,9 +2,8 @@ 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 de4dot.code.deobfuscators.ConfuserEx.x86.Instructions; using dnlib.DotNet; namespace de4dot.code.deobfuscators.ConfuserEx.x86 @@ -34,6 +33,8 @@ public X86Method(MethodDef method,ModuleDefMD module) ParseInstructions(method); } + private const int X86MaxInsSize = 15; + private void ParseInstructions(MethodDef method) { var rawInstructions = new List(); @@ -41,14 +42,16 @@ private void ParseInstructions(MethodDef method) while (true) { byte[] bytes = ReadChunk(method, _module); - - var disasm = new Disasm(); var buff = new UnmanagedBuffer(bytes); - disasm.EIP = new IntPtr(buff.Ptr.ToInt32()); + var disasm = new Disasm { Archi = 32, EIP = new IntPtr(buff.Ptr.ToInt64()) }; + + var disasmResult = BeaEngine.Disasm(disasm); + if (disasmResult < 0) { + break; + } - var instruction = BeaEngine.Disasm(disasm); - //_readOffset -= 8 - instruction; // revert offset back for each byte that was not a part of this instruction + _readOffset -= (uint)(X86MaxInsSize - disasmResult); // 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 @@ -57,18 +60,13 @@ private void ParseInstructions(MethodDef method) break; } - rawInstructions.Add(Clone(disasm)); - //disasm.EIP = new IntPtr(disasm.EIP.ToInt32() + instruction); + rawInstructions.Add(disasm); 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()); - + rawInstructions.RemoveAt(rawInstructions.Count - 1); foreach (var instr in rawInstructions) { @@ -101,25 +99,29 @@ private void ParseInstructions(MethodDef method) case "pop": Instructions.Add(new X86POP(instr)); break; + default: + Logger.w("ConfuserEx native: Unhandled instruction {0}", instr.CompleteInstr.Trim()); + break; } } } private uint _readOffset; - public byte[] ReadChunk(MethodDef method, ModuleDefMD module) + + private byte[] ReadChunk(MethodDef method, ModuleDefMD module) { var stream = module.Metadata.PEImage.CreateReader(); - var offset = module.Metadata.PEImage.ToFileOffset(method.RVA); + var offset = module.Metadata.PEImage.ToFileOffset(method.RVA); - byte[] buffer = new byte[8]; + byte[] buffer = new byte[X86MaxInsSize]; 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; + stream.ReadBytes(buffer, 0, X86MaxInsSize); + _readOffset += X86MaxInsSize; return buffer; } @@ -134,23 +136,5 @@ public int Execute(params int[] @params) 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 - }; - } } } From 30fdca1ae3f124c7dd2bfc2a423f00da81508ac4 Mon Sep 17 00:00:00 2001 From: Melissa Eckardt Date: Thu, 2 Apr 2026 11:33:32 +0200 Subject: [PATCH 2/7] CI: Upload artifacts for branches --- .github/workflows/build.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c683c36d8..873bd198a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,13 +31,11 @@ jobs: Remove-Item publish-net8.0\*.pdb, publish-net8.0\*.xml - uses: actions/upload-artifact@v4 - if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/') with: name: de4dotEx-net48 path: Release/net48 - uses: actions/upload-artifact@v4 - if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/') with: name: de4dotEx-net8.0-win-x64 path: publish-net8.0 @@ -60,7 +58,6 @@ jobs: rm -rf publish-net8.0/*.pdb publish-net8.0/*.xml - name: Upload publish folder - if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/') uses: actions/upload-artifact@v4 with: name: de4dotEx-net8.0-linux-x64 From 8d2eef566cec5df9da8a74c928201ef8869d22ec Mon Sep 17 00:00:00 2001 From: Melissa Eckardt Date: Thu, 2 Apr 2026 14:06:17 +0200 Subject: [PATCH 3/7] Bea: Remove unneeded setting of DllDirectory --- .../ConfuserEx/x86/Bea/Engine.cs | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Engine.cs b/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Engine.cs index ec1715a08..f221ce919 100644 --- a/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Engine.cs +++ b/de4dot.code/deobfuscators/ConfuserEx/x86/Bea/Engine.cs @@ -1,33 +1,10 @@ using System; -using System.IO; using System.Runtime.InteropServices; namespace de4dot.Bea { public static class BeaEngine { - // 'de4dot\bin\de4dot.blocks.dll' -> 'de4dot\bin\' - private static readonly string ExecutingPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); - - static BeaEngine() - { - //TODO: Better handle native DLL discovery - if (IsWindows()) - SetDllDirectory(ExecutingPath); - } - - private static bool IsWindows() - { -#if NET5_0_OR_GREATER - return OperatingSystem.IsWindows(); -#else - return Environment.OSVersion.Platform == PlatformID.Win32NT; -#endif - } - - [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); From 5459a484574250bcdee7e9830688c650c8c79d12 Mon Sep 17 00:00:00 2001 From: Melissa Eckardt Date: Thu, 2 Apr 2026 15:03:39 +0200 Subject: [PATCH 4/7] CI: Build BeaEngine --- .github/workflows/build.yml | 94 ++++++++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 873bd198a..5fe11cc12 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,6 +8,12 @@ on: branches: [ "master" ] workflow_dispatch: +env: + DOTNET_RUNTIME: net8.0 + SETUP_DOTNET_VERSION: 8.0.x + + BEAENGINE_TAG: v5.3.0 + jobs: build-windows: name: Build Windows artifacts @@ -27,8 +33,51 @@ jobs: if ($LASTEXITCODE) { exit $LASTEXITCODE } Remove-Item Release\net48\*.pdb, Release\net48\*.xml, Release\net48\Test.Rename.* - dotnet publish -c Release -f net8.0 -o publish-net8.0 de4dot - Remove-Item publish-net8.0\*.pdb, publish-net8.0\*.xml + dotnet publish -c Release -f $env:DOTNET_RUNTIME -o publish-$env:DOTNET_RUNTIME de4dot + Remove-Item publish-$env:DOTNET_RUNTIME\*.pdb, publish-$env:DOTNET_RUNTIME\*.xml + + - name: Build BeaEngine + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + + git clone --depth 1 --branch $env:BEAENGINE_TAG https://github.com/BeaEngine/beaengine.git beaengine + + # Win32 for .NET 4.8 + cmake -S beaengine -B build-beaengine-32 ` + -A Win32 ` + -DoptBUILD_DLL=ON ` + -DCMAKE_BUILD_TYPE=Release + + cmake --build build-beaengine-32 --config Release + + $dll32 = Get-ChildItem -Recurse build-beaengine-32 -Filter BeaEngine*.dll | Select-Object -First 1 + + if (-not $dll32) { + throw "32-bit BeaEngine.dll not found" + } + + Copy-Item $dll32.FullName Release/net48/BeaEngine.dll + Copy-Item beaengine/src/COPYING.txt Release/net48/LICENSES/LICENSE.BeaEngine.GPL.txt + Copy-Item beaengine/src/COPYING.LESSER.txt Release/net48/LICENSES/LICENSE.BeaEngine.LGPL.txt + + # Win64 for .NET 8+ + cmake -S beaengine -B build-beaengine-64 ` + -A x64 ` + -DoptBUILD_DLL=ON ` + -DCMAKE_BUILD_TYPE=Release + + cmake --build build-beaengine-64 --config Release + + $dll64 = Get-ChildItem -Recurse build-beaengine-64 -Filter BeaEngine*.dll | Select-Object -First 1 + + if (-not $dll64) { + throw "64-bit BeaEngine.dll not found" + } + + Copy-Item $dll64.FullName publish-$env:DOTNET_RUNTIME/BeaEngine.dll + Copy-Item beaengine/src/COPYING.txt publish-$env:DOTNET_RUNTIME/LICENSES/LICENSE.BeaEngine.GPL.txt + Copy-Item beaengine/src/COPYING.LESSER.txt publish-$env:DOTNET_RUNTIME/LICENSES/LICENSE.BeaEngine.LGPL.txt - uses: actions/upload-artifact@v4 with: @@ -37,8 +86,8 @@ jobs: - uses: actions/upload-artifact@v4 with: - name: de4dotEx-net8.0-win-x64 - path: publish-net8.0 + name: de4dotEx-${{ env.DOTNET_RUNTIME }}-win-x64 + path: publish-${{ env.DOTNET_RUNTIME }} build-linux: name: Build Linux artifacts & package .deb @@ -50,18 +99,37 @@ jobs: - name: Set up .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.0.x + dotnet-version: ${{ env.SETUP_DOTNET_VERSION }} + + - name: Publish ${{ env.DOTNET_RUNTIME }} + run: | + dotnet publish -c Release -f "$DOTNET_RUNTIME" -o "publish-$DOTNET_RUNTIME" de4dot + rm -rf "publish-$DOTNET_RUNTIME/*.pdb" "publish-$DOTNET_RUNTIME/*.xml" - - name: Publish net8.0 + - name: Build BeaEngine run: | - dotnet publish -c Release -f net8.0 -o publish-net8.0 de4dot - rm -rf publish-net8.0/*.pdb publish-net8.0/*.xml + set -euo pipefail + + git clone --depth 1 --branch "$BEAENGINE_TAG" https://github.com/BeaEngine/beaengine.git + + cmake -S beaengine -B build64 \ + -DoptBUILD_DLL=ON \ + -DCMAKE_BUILD_TYPE=Release + + cmake --build build64 + + so64=$(find build64 -name "libBeaEngine*.so" | head -n 1) + [[ -n "$so64" ]] || { echo "libBeaEngine not found"; exit 1; } + + cp "$so64" "publish-$DOTNET_RUNTIME/libBeaEngine.so" + cp beaengine/src/COPYING.txt "publish-$DOTNET_RUNTIME/LICENSES/LICENSE.BeaEngine.GPL.txt" + cp beaengine/src/COPYING.LESSER.txt "publish-$DOTNET_RUNTIME/LICENSES/LICENSE.BeaEngine.LGPL.txt" - name: Upload publish folder uses: actions/upload-artifact@v4 with: - name: de4dotEx-net8.0-linux-x64 - path: publish-net8.0 + name: de4dotEx-${{ env.DOTNET_RUNTIME }}-linux-x64 + path: publish-${{ env.DOTNET_RUNTIME }} - name: Extract version from Git tag if: startsWith(github.ref, 'refs/tags/') @@ -74,7 +142,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/') run: | mkdir -p deb-root/opt/de4dotEx - cp -r publish-net8.0/* deb-root/opt/de4dotEx/ + cp -r "publish-$DOTNET_RUNTIME/*" deb-root/opt/de4dotEx/ mkdir -p deb-root/usr/local/bin ln -s /opt/de4dotEx/de4dot deb-root/usr/local/bin/de4dot @@ -87,7 +155,7 @@ jobs: Priority: optional Architecture: amd64 Maintainer: G DATA Advanced Analytics GmbH - Depends: libicu77 | libicu76 | libicu74 | libicu72 | libicu70 | libicu67 | libicu66 + Depends: libicu78 | libicu77 | libicu76 | libicu74 | libicu72 | libicu70 | libicu67 | libicu66 Description: .NET deobfuscator and unpacker de4dot is a .NET deobfuscator and unpacker. It will try its best to restore a packed and obfuscated assembly to almost the original @@ -103,5 +171,5 @@ jobs: - uses: actions/upload-artifact@v4 if: startsWith(github.ref, 'refs/tags/') with: - name: de4dotEx-${{ steps.get_version.outputs.VERSION }}-net8.0-x64-deb + name: de4dotEx-${{ steps.get_version.outputs.VERSION }}-${{ env.DOTNET_RUNTIME }}-x64-deb path: de4dotEx-${{ steps.get_version.outputs.VERSION }}.deb From 1b3c2a28414d026cb4b384f25520e18f850cfae5 Mon Sep 17 00:00:00 2001 From: Melissa Eckardt Date: Thu, 2 Apr 2026 15:17:43 +0200 Subject: [PATCH 5/7] CI: Fix rm command --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5fe11cc12..4c52d6509 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -103,8 +103,8 @@ jobs: - name: Publish ${{ env.DOTNET_RUNTIME }} run: | - dotnet publish -c Release -f "$DOTNET_RUNTIME" -o "publish-$DOTNET_RUNTIME" de4dot - rm -rf "publish-$DOTNET_RUNTIME/*.pdb" "publish-$DOTNET_RUNTIME/*.xml" + dotnet publish -c Release -f "${DOTNET_RUNTIME}" -o publish-${DOTNET_RUNTIME} de4dot + rm -rf publish-${DOTNET_RUNTIME}/*.pdb publish-${DOTNET_RUNTIME}/*.xml - name: Build BeaEngine run: | @@ -121,9 +121,9 @@ jobs: so64=$(find build64 -name "libBeaEngine*.so" | head -n 1) [[ -n "$so64" ]] || { echo "libBeaEngine not found"; exit 1; } - cp "$so64" "publish-$DOTNET_RUNTIME/libBeaEngine.so" - cp beaengine/src/COPYING.txt "publish-$DOTNET_RUNTIME/LICENSES/LICENSE.BeaEngine.GPL.txt" - cp beaengine/src/COPYING.LESSER.txt "publish-$DOTNET_RUNTIME/LICENSES/LICENSE.BeaEngine.LGPL.txt" + cp "$so64" publish-${DOTNET_RUNTIME}/libBeaEngine.so + cp beaengine/src/COPYING.txt publish-${DOTNET_RUNTIME}/LICENSES/LICENSE.BeaEngine.GPL.txt + cp beaengine/src/COPYING.LESSER.txt publish-${DOTNET_RUNTIME}/LICENSES/LICENSE.BeaEngine.LGPL.txt - name: Upload publish folder uses: actions/upload-artifact@v4 From f78c07241764052ba4cc429654c33d59e1d37631 Mon Sep 17 00:00:00 2001 From: Melissa Eckardt Date: Thu, 2 Apr 2026 15:28:06 +0200 Subject: [PATCH 6/7] CI: Improve BeaEngine cmake flags --- .github/workflows/build.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c52d6509..55d76f57e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,7 +47,10 @@ jobs: cmake -S beaengine -B build-beaengine-32 ` -A Win32 ` -DoptBUILD_DLL=ON ` - -DCMAKE_BUILD_TYPE=Release + -DoptHAS_OPTIMIZED=ON ` + -DoptHAS_SYMBOLS=OFF ` + -DCMAKE_C_FLAGS_RELEASE="/MT" ` + -DCMAKE_CXX_FLAGS_RELEASE="/MT" cmake --build build-beaengine-32 --config Release @@ -65,7 +68,10 @@ jobs: cmake -S beaengine -B build-beaengine-64 ` -A x64 ` -DoptBUILD_DLL=ON ` - -DCMAKE_BUILD_TYPE=Release + -DoptHAS_OPTIMIZED=ON ` + -DoptHAS_SYMBOLS=OFF ` + -DCMAKE_C_FLAGS_RELEASE="/MT" ` + -DCMAKE_CXX_FLAGS_RELEASE="/MT" cmake --build build-beaengine-64 --config Release @@ -114,7 +120,8 @@ jobs: cmake -S beaengine -B build64 \ -DoptBUILD_DLL=ON \ - -DCMAKE_BUILD_TYPE=Release + -DoptHAS_OPTIMIZED=ON \ + -DoptHAS_SYMBOLS=OFF cmake --build build64 From 68df25dd5e6335c373305ff8f6adf15ecc53395d Mon Sep 17 00:00:00 2001 From: Melissa Eckardt Date: Thu, 2 Apr 2026 15:37:11 +0200 Subject: [PATCH 7/7] CI: Fix BeaEngine cmake malpractice --- .github/workflows/build.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 55d76f57e..58b307be5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,14 +43,18 @@ jobs: git clone --depth 1 --branch $env:BEAENGINE_TAG https://github.com/BeaEngine/beaengine.git beaengine + # Remove cmake malpractice + (Get-Content beaengine/CMakeLists.txt) | + Where-Object { $_ -notmatch '\s*list\s*\(APPEND\s+BEA_FLAGS\s+/M' } | + Set-Content beaengine/CMakeLists.txt + # Win32 for .NET 4.8 cmake -S beaengine -B build-beaengine-32 ` -A Win32 ` -DoptBUILD_DLL=ON ` -DoptHAS_OPTIMIZED=ON ` -DoptHAS_SYMBOLS=OFF ` - -DCMAKE_C_FLAGS_RELEASE="/MT" ` - -DCMAKE_CXX_FLAGS_RELEASE="/MT" + -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded cmake --build build-beaengine-32 --config Release @@ -70,8 +74,7 @@ jobs: -DoptBUILD_DLL=ON ` -DoptHAS_OPTIMIZED=ON ` -DoptHAS_SYMBOLS=OFF ` - -DCMAKE_C_FLAGS_RELEASE="/MT" ` - -DCMAKE_CXX_FLAGS_RELEASE="/MT" + -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded cmake --build build-beaengine-64 --config Release