diff --git a/De4DotCommon.props b/De4DotCommon.props
index 212d40db..2d2bc905 100644
--- a/De4DotCommon.props
+++ b/De4DotCommon.props
@@ -7,7 +7,7 @@
net48
net8.0
strict
- latest
+ 12.0
true
$(MSBuildThisFileDirectory)\de4dot.snk
3.4.0
diff --git a/de4dot.blocks/cflow/CflowUtils.cs b/de4dot.blocks/cflow/CflowUtils.cs
index 99da3579..f3d6bd66 100644
--- a/de4dot.blocks/cflow/CflowUtils.cs
+++ b/de4dot.blocks/cflow/CflowUtils.cs
@@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License
*/
using System.Collections.Generic;
+using dnlib.DotNet.Emit;
namespace de4dot.blocks.cflow {
static class CflowUtils {
@@ -27,9 +28,38 @@ static class CflowUtils {
int index = intValue.Value;
if (targets == null || index < 0 || index >= targets.Count)
- return fallThrough;
+ return TryResolveConditionalFallthrough(fallThrough, intValue);
else
return targets[index];
}
+
+ /**
+ * Some obfuscators have default blocks that dispatch additional cases.
+ * Example: 4e86d71a19f7f69471776817dc67585064b4b60542bc60e9450739bca63226ee
+ */
+ private static Block? TryResolveConditionalFallthrough(Block? block, Int32Value value) {
+ while (true) {
+ if (block == null) return null;
+
+ var instrs = block.Instructions;
+ if (instrs.Count < 3) return block;
+ if (!instrs[0].IsLdloc()) return block;
+ if (!instrs[1].IsLdcI4()) return block;
+ if (instrs[2].OpCode.Code is not (Code.Beq or Code.Beq_S or Code.Bne_Un or Code.Bne_Un_S)) return block;
+
+ int constant = instrs[1].GetLdcI4Value();
+ var branch = instrs[2];
+
+ int v = value.Value;
+
+ bool taken =
+ branch.OpCode.Code is Code.Beq or Code.Beq_S ? (v == constant) :
+ branch.OpCode.Code is Code.Bne_Un or Code.Bne_Un_S && (v != constant);
+
+ if (taken)
+ return block.Targets![0];
+ block = block.FallThrough;
+ }
+ }
}
}
diff --git a/de4dot.code/ObfuscatedFile.cs b/de4dot.code/ObfuscatedFile.cs
index 72b2bcdf..dc76ee20 100644
--- a/de4dot.code/ObfuscatedFile.cs
+++ b/de4dot.code/ObfuscatedFile.cs
@@ -609,7 +609,13 @@ void Deobfuscate(MethodDef method, BlocksCflowDeobfuscator cflowDeobfuscator, Me
deob.DeobfuscateMethodBegin(blocks);
if (options.ControlFlowDeobfuscation) {
cflowDeobfuscator.Initialize(blocks);
- cflowDeobfuscator.Deobfuscate();
+ try {
+ cflowDeobfuscator.Deobfuscate();
+ }
+ catch (Exception) {
+ Logger.e("Error during cflow deobfuscation of {0} ({1:X8})", Utils.RemoveNewlines(method), method.MDToken.ToUInt32());
+ throw;
+ }
}
if (deob.DeobfuscateOther(blocks) && options.ControlFlowDeobfuscation)
diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/CflowConstantsInliner.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/CflowConstantsInliner.cs
index 54f58dcc..d023bb6c 100644
--- a/de4dot.code/deobfuscators/dotNET_Reactor/v4/CflowConstantsInliner.cs
+++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/CflowConstantsInliner.cs
@@ -38,18 +38,13 @@ void Find() {
continue;
if (i + 1 >= instrs.Count)
continue;
- var stsfld = instrs[i + 1];
- if (stsfld.OpCode.Code != Code.Stsfld)
+ var store = instrs[i + 1];
+ if (store.OpCode.Code is not (Code.Stsfld or Code.Stfld))
continue;
- var key = stsfld.Operand as FieldDef;
- if (key == null)
+ if (store.Operand is not FieldDef key)
continue;
- var value = ldcI4.GetLdcI4Value();
- if (!dictionary.ContainsKey(key))
- dictionary.Add(key, value);
- else
- dictionary[key] = value;
+ dictionary[key] = ldcI4.GetLdcI4Value();
}
if (dictionary.Count < 100) {
@@ -76,14 +71,21 @@ public void InlineAllConstants() {
var instrs = method.Body.Instructions;
for (var i = 0; i < instrs.Count; i++) {
- var ldsfld = instrs[i];
- if (ldsfld.OpCode.Code != Code.Ldsfld)
+ bool nopNext = false;
+ var load = instrs[i];
+ if (load.OpCode.Code != Code.Ldsfld)
continue;
- var ldsfldValue = ldsfld.Operand as FieldDef;
- if (ldsfldValue == null)
+ if (i < instrs.Count - 1 && instrs[i + 1].OpCode.Code == Code.Ldfld) {
+ load = instrs[i + 1];
+ nopNext = true;
+ }
+ if (load.Operand is not FieldDef loadField)
continue;
- if (dictionary.TryGetValue(ldsfldValue, out var value))
+ if (dictionary.TryGetValue(loadField, out var value)) {
instrs[i] = Instruction.CreateLdcI4(value);
+ if (nopNext)
+ instrs[i + 1] = Instruction.Create(OpCodes.Nop);
+ }
}
}
}
diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/Deobfuscator.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/Deobfuscator.cs
index 64f7db4a..7102f70a 100644
--- a/de4dot.code/deobfuscators/dotNET_Reactor/v4/Deobfuscator.cs
+++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/Deobfuscator.cs
@@ -25,6 +25,7 @@ You should have received a copy of the GNU General Public License
using dnlib.DotNet.Writer;
using de4dot.blocks;
using de4dot.blocks.cflow;
+using de4dot.code.deobfuscators.dotNET_Reactor.v4.vm;
namespace de4dot.code.deobfuscators.dotNET_Reactor.v4 {
public class DeobfuscatorInfo : DeobfuscatorInfoBase {
@@ -42,6 +43,7 @@ public class DeobfuscatorInfo : DeobfuscatorInfoBase {
BoolOption removeNamespaces;
BoolOption removeAntiStrongName;
BoolOption renameShort;
+ BoolOption devirtualize;
public DeobfuscatorInfo()
: base(DEFAULT_REGEX) {
@@ -55,6 +57,7 @@ public DeobfuscatorInfo()
removeNamespaces = new BoolOption(null, MakeArgName("ns1"), "Clear namespace if there's only one class in it", true);
removeAntiStrongName = new BoolOption(null, MakeArgName("sn"), "Remove anti strong name code", true);
renameShort = new BoolOption(null, MakeArgName("sname"), "Rename short names", false);
+ devirtualize = new BoolOption(null, MakeArgName("devirtualize"), "Devirtualize methods", true);
}
public override string Name => THE_NAME;
@@ -73,6 +76,7 @@ public override IDeobfuscator CreateDeobfuscator() =>
RemoveNamespaces = removeNamespaces.Get(),
RemoveAntiStrongName = removeAntiStrongName.Get(),
RenameShort = renameShort.Get(),
+ Devirtualize = devirtualize.Get(),
});
protected override IEnumerable GetOptionsInternal() =>
@@ -87,6 +91,7 @@ protected override IEnumerable GetOptionsInternal() =>
removeNamespaces,
removeAntiStrongName,
renameShort,
+ devirtualize,
};
}
@@ -106,6 +111,7 @@ class Deobfuscator : DeobfuscatorBase {
AntiStrongName antiStrongname;
EmptyClass emptyClass;
ProxyCallFixer proxyCallFixer;
+ Devirtualizer devirtualizer;
bool unpackedNativeFile = false;
bool canRemoveDecrypterType = true;
@@ -122,6 +128,7 @@ internal class Options : OptionsBase {
public bool RemoveNamespaces { get; set; }
public bool RemoveAntiStrongName { get; set; }
public bool RenameShort { get; set; }
+ public bool Devirtualize { get; set; }
}
public override string Type => DeobfuscatorInfo.THE_TYPE;
@@ -205,7 +212,8 @@ protected override int DetectInternal() {
ToInt32(stringDecrypter.Detected) +
ToInt32(booleanDecrypter.Detected) +
ToInt32(assemblyResolver.Detected) +
- ToInt32(resourceResolver.Detected);
+ ToInt32(resourceResolver.Detected) +
+ ToInt32(devirtualizer.Detected);
if (sum > 0)
val += 100 + 10 * (sum - 1);
@@ -222,6 +230,8 @@ protected override void ScanForObfuscator() {
methodsDecrypter.Find();
proxyCallFixer = new ProxyCallFixer(module, DeobfuscatedFile);
proxyCallFixer.FindDelegateCreator(module);
+ devirtualizer = new Devirtualizer(DeobfuscatedFile, module);
+ devirtualizer.Find();
stringDecrypter = new StringDecrypter(module);
stringDecrypter.Find(DeobfuscatedFile);
booleanDecrypter = new BooleanDecrypter(module);
@@ -277,6 +287,12 @@ Methods decrypter locals (not showing its own types):
+ "System.Byte&"
*/
+ if (devirtualizer.Detected) {
+ if (devirtualizer.StreamHasPrependedByte)
+ return DeobfuscatorInfo.THE_NAME + " >= 7.0"; // not sure when exactly this was introduced, might also be 7.3
+ return DeobfuscatorInfo.THE_NAME + " >= 6.2";
+ }
+
LocalTypes localTypes;
int minVer = -1;
foreach (var info in stringDecrypter.DecrypterInfos) {
@@ -430,6 +446,7 @@ public override IDeobfuscator ModuleReloaded(ModuleDefMD module) {
newOne.peImage = new MyPEImage(fileData);
newOne.methodsDecrypter = new MethodsDecrypter(module, methodsDecrypter);
newOne.proxyCallFixer = new ProxyCallFixer(module, proxyCallFixer);
+ newOne.devirtualizer = new Devirtualizer(module, devirtualizer);
newOne.stringDecrypter = new StringDecrypter(module, stringDecrypter);
newOne.booleanDecrypter = new BooleanDecrypter(module, booleanDecrypter);
newOne.assemblyResolver = new AssemblyResolver(module, assemblyResolver);
@@ -510,6 +527,10 @@ public override void DeobfuscateBegin() {
proxyCallFixer.Find();
proxyCallFixer.DeobfuscateAll();
+ if (devirtualizer.Detected && options.Devirtualize)
+ devirtualizer.Devirtualize();
+
+ // Inlines '{7212c6df-0f39-43d5-b7b8-3f24c0ebccff}'::m_1b8cd98d5e234215af7340e19a570660 references.
var cflowInliner = new CflowConstantsInliner(module, DeobfuscatedFile);
cflowInliner.InlineAllConstants();
AddTypeToBeRemoved(cflowInliner.Type, "Cflow constants type");
@@ -640,7 +661,6 @@ public override void DeobfuscateMethodEnd(Blocks blocks) {
metadataTokenObfuscator.Deobfuscate(blocks);
FixTypeofDecrypterInstructions(blocks);
RemoveAntiStrongNameCode(blocks);
- stringDecrypter.DeobfuscateXored(blocks);
base.DeobfuscateMethodEnd(blocks);
}
@@ -687,6 +707,11 @@ public override void DeobfuscateEnd() {
else
Logger.v("Could not remove decrypter type");
+ if (devirtualizer.CanRemoveType) {
+ AddTypeToBeRemoved(devirtualizer.VMType, "VM type");
+ AddResourceToBeRemoved(devirtualizer.Resource, "VM resource");
+ }
+
FixEntryPoint();
base.DeobfuscateEnd();
diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/DotNetReactorCflowDeobfuscator.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/DotNetReactorCflowDeobfuscator.cs
index 66b858df..aef4a6a7 100644
--- a/de4dot.code/deobfuscators/dotNET_Reactor/v4/DotNetReactorCflowDeobfuscator.cs
+++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/DotNetReactorCflowDeobfuscator.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Linq;
using de4dot.blocks;
using de4dot.blocks.cflow;
using dnlib.DotNet;
@@ -6,24 +7,16 @@
namespace de4dot.code.deobfuscators.dotNET_Reactor.v4 {
class DotNetReactorCflowDeobfuscator : IBlocksDeobfuscator {
- bool isContainsSwitch;
+ bool _hasSwitch;
public bool ExecuteIfNotModified { get; }
public void DeobfuscateBegin(Blocks blocks) {
- var contains = false;
- foreach (var instr in blocks.Method.Body.Instructions) {
- if (instr.OpCode == OpCodes.Switch) {
- contains = true;
- break;
- }
- }
-
- isContainsSwitch = contains;
+ _hasSwitch = blocks.Method.Body.Instructions.Any(instr => instr.OpCode == OpCodes.Switch);
}
public bool Deobfuscate(List allBlocks) {
- if (!isContainsSwitch)
+ if (!_hasSwitch)
return false;
var modified = false;
diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/EncryptedResource.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/EncryptedResource.cs
index 6a091c05..2689bbc1 100644
--- a/de4dot.code/deobfuscators/dotNET_Reactor/v4/EncryptedResource.cs
+++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/EncryptedResource.cs
@@ -555,8 +555,10 @@ bool Initialize() {
int count = emuEndIndex - emuStartIndex + 1;
instructions = new List(count);
- for (int i = 0; i < count; i++)
- instructions.Add(origInstrs[emuStartIndex + i].Clone());
+ for (int i = 0; i < count; i++) {
+ var ins = origInstrs[emuStartIndex + i];
+ instructions.Add(ins.Clone());
+ }
return true;
}
@@ -702,8 +704,30 @@ uint CalculateMagic(uint input) {
instrEmulator.Initialize(method, method.Parameters, locals, method.Body.InitLocals, false);
instrEmulator.SetLocal(emuLocal, new Int32Value((int)input));
- foreach (var instr in instructions)
- instrEmulator.Emulate(instr);
+ foreach (var instr in instructions) {
+ if (instr.OpCode == OpCodes.Bne_Un || instr.OpCode == OpCodes.Bne_Un_S) {
+ /* The emulator doesn't handle branches, and some DNR builds have a zero-check branch gating a division
+ // if (num12 == 0U)
+ /* 0x00002E98 FE0C2900 * / IL_01E4: ldloc V_41
+ /* 0x00002E9C 16 * / IL_01E8: ldc.i4.0
+ /* 0x00002E9D 400A000000 * / IL_01E9: bne.un IL_01F8
+ // num12 -= 1U;
+ /* 0x00002EA2 FE0C2900 * / IL_01EE: ldloc V_41
+ /* 0x00002EA6 17 * / IL_01F2: ldc.i4.1
+ /* 0x00002EA7 59 * / IL_01F3: sub
+ /* 0x00002EA8 FE0E2900 * / IL_01F4: stloc V_41
+ */
+ var rhs = instrEmulator.Pop();
+ var lhs = instrEmulator.Pop();
+ if (rhs is Int32Value rhsInt && lhs is Int32Value lhsInt && rhsInt.IsZero() && lhsInt.IsZero()) {
+ var local = (Local)instructions[instructions.IndexOf(instr) - 2].Operand;
+ instrEmulator.SetLocal(local, new Int32Value(-1));
+ }
+ }
+ else {
+ instrEmulator.Emulate(instr);
+ }
+ }
var tos = instrEmulator.Pop() as Int32Value;
if (tos == null || !tos.AllBitsValid())
diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/StringDecrypter.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/StringDecrypter.cs
index 610468fb..70c6b194 100644
--- a/de4dot.code/deobfuscators/dotNET_Reactor/v4/StringDecrypter.cs
+++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/StringDecrypter.cs
@@ -151,7 +151,8 @@ public void Initialize(MyPEImage peImage, byte[] fileData, ISimpleDeobfuscator s
Logger.v("Adding string decrypter. Resource: {0}", Utils.ToCsharpString(encryptedResource.Resource.Name));
decryptedData = encryptedResource.Decrypt();
}
- catch {
+ catch (Exception ex) {
+ Logger.w($"Initializing StringDecrypter failed: {ex.Message}");
encryptedResource.Method = null;
decryptStrings = false;
}
@@ -245,88 +246,5 @@ public string Decrypt(MethodDef method, int offset) {
}
public string Decrypt(string s) => Encoding.Unicode.GetString(Convert.FromBase64String(s));
-
- /**
- * Determines whether a method has many fields that receive a constant int
- * by checking the ratio between ldc.i4 and stfld.
- */
- private bool IsConstantsInitializer(MethodDef method) {
- int numLdcI4 = 0, numStfld = 0;
- foreach (var ins in method.Body.Instructions) {
- if (ins.IsLdcI4()) {
- numLdcI4++;
- } else if (ins.OpCode.Code == Code.Stfld) {
- numStfld++;
- }
- }
-
- return numStfld > 5 && numLdcI4 / (double)numStfld > 0.9;
- }
-
- /**
- * Creates a mapping of field name -> value for int assignments.
- */
- private Dictionary GetConstantFields(MethodDef method) {
- var result = new Dictionary();
-
- int val = 0;
- foreach (var ins in method.Body.Instructions) {
- if (ins.IsLdcI4()) {
- val = ins.GetLdcI4Value();
- } else if (ins.OpCode.Code == Code.Stfld) {
- result[((IField)ins.Operand).Name] = val;
- }
- }
-
- return result;
- }
-
- private Dictionary LoadConstantFields(TypeDef type) {
- foreach (var method in type.Methods) {
- if (IsConstantsInitializer(method)) {
- return GetConstantFields(method);
- }
- }
-
- return null;
- }
-
- /**
- * Replaces all references to the string decryption function by a load of the respective decrypted string.
- * This is for special cases where de4dot's normal static decryptor is unable to obtain the offset constants.
- */
- public void DeobfuscateXored(Blocks blocks) {
- if (decrypterInfos.Count != 1) return;
-
- foreach (var block in blocks.MethodBlocks.GetAllBlocks()) {
- var instrs = block.Instructions;
- for (int i = 4; i < instrs.Count; i++) {
- var instr = instrs[i];
-
- if (instr.OpCode != OpCodes.Call || instr.Operand != decrypterInfos[0].method)
- continue;
-
- if (instrs[i - 1].OpCode != OpCodes.Xor
- || instrs[i - 2].OpCode != OpCodes.Ldfld
- || instrs[i - 3].OpCode != OpCodes.Ldsfld
- || instrs[i - 4].OpCode != OpCodes.Ldc_I4)
- continue;
-
- int xorConst = instrs[i - 4].GetLdcI4Value();
- if (constantFields == null) {
- constantFields = LoadConstantFields(((FieldDef)instrs[i - 3].Operand).DeclaringType);
- if (constantFields == null) {
- Logger.w("Failed to load constant fields");
- return;
- }
- }
- int xorField = constantFields[((FieldDef)instrs[i - 2].Operand).Name];
- int offset = xorConst ^ xorField;
-
- var decryptedString = Decrypt(decrypterInfos[0].method, offset);
- block.Replace(i - 4, 5, OpCodes.Ldstr.ToInstruction(decryptedString));
- }
- }
- }
}
}
diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/Devirtualizer.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/Devirtualizer.cs
new file mode 100644
index 00000000..7336c2c5
--- /dev/null
+++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/Devirtualizer.cs
@@ -0,0 +1,185 @@
+/*
+ Copyright (C) 2011-2020 de4dot@gmail.com
+ 2025-2026 G DATA Advanced Analytics GmbH
+
+ This file is part of de4dotEx.
+
+ de4dotEx 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.
+
+ de4dotEx 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 de4dotEx. If not, see .
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using de4dot.blocks;
+using dnlib.DotNet;
+using dnlib.DotNet.Emit;
+
+namespace de4dot.code.deobfuscators.dotNET_Reactor.v4.vm;
+
+public class Devirtualizer {
+ readonly ISimpleDeobfuscator _deobfuscator;
+ readonly ModuleDefMD _module;
+ TypeDef _vmType;
+ EmbeddedResource _resource;
+
+ public bool Detected => _vmType != null && _resource != null;
+ public bool StreamHasPrependedByte { get; private set; }
+
+ public bool CanRemoveType { get; private set; }
+ public EmbeddedResource Resource => _resource;
+ public TypeDef VMType => _vmType;
+
+ public List DevirtualizedMethods { get; } = new();
+
+ public Devirtualizer(ISimpleDeobfuscator deobfuscator, ModuleDefMD module) {
+ _deobfuscator = deobfuscator;
+ _module = module;
+ }
+
+ public Devirtualizer(ModuleDefMD module, Devirtualizer oldOne) {
+ _module = module;
+ _deobfuscator = oldOne._deobfuscator;
+ _vmType = DeobUtils.Lookup(module, oldOne._vmType, "Could not find VM type");
+ if (oldOne._resource != null)
+ _resource = DotNetUtils.GetResource(module, oldOne._resource.Name.String) as EmbeddedResource;
+ StreamHasPrependedByte = oldOne.StreamHasPrependedByte;
+ }
+
+ public void Find() {
+ _vmType = _module.Types.FirstOrDefault(type =>
+ type.Methods.Any(method =>
+ method.IsStatic
+ && method.HasGenericParameters
+ && method.Parameters.Count == 4
+ && method.Parameters[0].Type.FullName == "System.Int32"
+ && method.Parameters[3].Type.IsByRef
+ && method.ReturnType.FullName == "System.Object[]"));
+
+ if (_vmType == null)
+ return;
+
+ MethodDef resourceMethod = null, parseMethod = null;
+ foreach (var method in _vmType.Methods.Where(method =>
+ DotNetUtils.CallsMethod(method, "System.Void System.IO.BinaryReader::.ctor(System.IO.Stream)"))) {
+ if (DotNetUtils.CallsMethod(method, "System.Void System.IO.MemoryStream::.ctor(System.Byte[])"))
+ parseMethod = method;
+ else
+ resourceMethod = method;
+ }
+
+ if (resourceMethod == null || parseMethod == null)
+ return;
+
+ var strings = DotNetUtils.GetCodeStrings(resourceMethod);
+ if (strings.Count == 0) {
+ Logger.w("DRVM: No strings in resource method {0}", resourceMethod);
+ return;
+ }
+
+ _resource = DotNetUtils.GetResource(_module, strings) as EmbeddedResource;
+
+ var insns = parseMethod.Body.Instructions;
+ for (int i = 0; i < insns.Count - 2; i++) {
+ if (insns[i].IsLdcI4() && insns[i].GetLdcI4Value() == 0) {
+ if (insns[i + 1].OpCode == OpCodes.Cgt_Un && insns[i + 2].OpCode == OpCodes.Stsfld) {
+ StreamHasPrependedByte = true;
+ break;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Parses the VM resource and attempts to devirtualize all virtualized methods.
+ ///
+ public void Devirtualize() {
+ var parser = new ResourceParser(_resource.CreateReader(), StreamHasPrependedByte);
+
+ var mpr = new HandlerMapper(_vmType, _deobfuscator);
+ var matcher = new PatternMatcher();
+ matcher.MatchAll(mpr);
+
+ for (int i = 0; i < parser.MethodCount; i++) {
+ var vmMethod = parser.GetMethod(i);
+ var target = _module.ResolveMethod(new MDToken(vmMethod.Token).Rid);
+ Logger.n("Devirtualizing method: {0} ({1:X8})", target, vmMethod.Token);
+
+ try {
+ bool allKnown = true;
+ foreach (var ins in vmMethod.Instructions) {
+ var matched = matcher.GetOpcode(ins.VirtualOpcode);
+ if (matched != null)
+ ins.Opcode = matched.Opcode;
+ else {
+ allKnown = false;
+ Logger.w("Unknown virtual opcode {0}", ins.VirtualOpcode);
+ }
+ }
+
+ if (!allKnown)
+ continue;
+
+ vmMethod.ResolveTokens(_module);
+ vmMethod.ResolveStrings(parser.Strings, _module);
+ vmMethod.ConcretizeStelem();
+ //foreach (var ins in vmMethod.Instructions) Console.WriteLine(ins);
+
+ target.Body = GenCilBody(vmMethod, target.Parameters);
+ DevirtualizedMethods.Add(target);
+ }
+ catch (Exception ex) {
+ Logger.w("Error devirtualizing {0}: {1}", target, ex.Message);
+ }
+ }
+
+ CanRemoveType = DevirtualizedMethods.Count == parser.MethodCount;
+ }
+
+ private CilBody GenCilBody(VMMethod method, ParameterList parameters) {
+ // Map VM instructions to dnlib instructions 1:1 at first.
+ var dnList = method.Instructions.Select(ins => new Instruction(ins.Opcode!, ins.Operand)).ToList();
+ var dnLocals = method.Locals.Select(elemType => new Local(elemType.ToTypeSig(_module.CorLibTypes))).ToList();
+
+ // Resolve operands that reference other instructions or locals.
+ foreach (var ins in dnList) {
+ if (ins.IsBr() || ins.IsConditionalBranch() || ins.IsLeave()) {
+ ins.Operand = dnList[(int)ins.Operand];
+ } else if (ins.OpCode == OpCodes.Switch) {
+ ins.Operand = ((int[])ins.Operand).Select(index => dnList[index]).ToArray();
+ } else if (ins.IsLdloc() || ins.IsStloc()) {
+ ins.Operand = dnLocals[(int)ins.Operand];
+ } else if (ins.IsLdarg() || ins.IsStarg()) {
+ ins.Operand = parameters[(int)ins.Operand];
+ }
+ }
+
+ // Resolve instructions and types in exception handlers.
+ var dnEh = method.ExceptionHandlers.Select(eh => {
+ var handler = new ExceptionHandler(eh.HandlerType) {
+ TryStart = dnList[eh.TryStart],
+ TryEnd = dnList[eh.TryEnd + 1],
+ HandlerStart = dnList[eh.HandlerStart],
+ HandlerEnd = dnList[eh.HandlerEnd + 1]
+ };
+ if (handler.HandlerType == ExceptionHandlerType.Filter)
+ handler.FilterStart = dnList[eh.FilterStart];
+ if (handler.HandlerType == ExceptionHandlerType.Catch)
+ handler.CatchType = (ITypeDefOrRef)_module.ResolveToken(new MDToken(eh.CatchType));
+
+ return handler;
+ }).ToList();
+
+ return new CilBody(true, dnList, dnEh, dnLocals);
+ }
+}
diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/HandlerMapper.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/HandlerMapper.cs
new file mode 100644
index 00000000..13288498
--- /dev/null
+++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/HandlerMapper.cs
@@ -0,0 +1,178 @@
+/*
+ Copyright (C) 2011-2020 de4dot@gmail.com
+ 2025-2026 G DATA Advanced Analytics GmbH
+
+ This file is part of de4dotEx.
+
+ de4dotEx 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.
+
+ de4dotEx 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 de4dotEx. If not, see .
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using de4dot.blocks;
+using dnlib.DotNet;
+using dnlib.DotNet.Emit;
+
+namespace de4dot.code.deobfuscators.dotNET_Reactor.v4.vm;
+
+#nullable enable
+
+///
+/// This class analyzes the VM logic and finds all opcode handlers.
+/// In other words: It collects the code of each switch case in the opcode dispatcher.
+///
+internal class HandlerMapper {
+ public Dictionary Handlers { get; } = new();
+
+ public HandlerMapper(TypeDef vmType, ISimpleDeobfuscator deobfuscator) {
+ var interpreterMethod = FindInterpreterMethod(vmType, deobfuscator);
+ if (interpreterMethod == null) {
+ throw new Exception("Interpreter method not found in " + vmType);
+ }
+
+ FindHandlers(interpreterMethod);
+ }
+
+ /**
+ * Finds and deobfuscates the method containing the huge opcode switch.
+ */
+ private static MethodDef? FindInterpreterMethod(TypeDef vmType, ISimpleDeobfuscator deobfuscator) {
+ var interpreterMethod = vmType.NestedTypes
+ .Where(type => type.Methods.Count > 15 && type.Fields.Count > 10)
+ .SelectMany(type => type.Methods)
+ .FirstOrDefault(method =>
+ !method.IsStatic
+ && method.GetParamCount() == 1
+ && method.Parameters[1].Type.DefinitionAssembly == vmType.DefinitionAssembly
+ && method.HasBody
+ && method.Body.Instructions.Count > 1500);
+
+ if (interpreterMethod == null) {
+ return null;
+ }
+
+ deobfuscator.Deobfuscate(interpreterMethod);
+
+ return interpreterMethod;
+ }
+
+ /**
+ * Finds the opcode switch in interpreterMethod and collects the handling instructions for each opcode.
+ */
+ private void FindHandlers(MethodDef interpreterMethod) {
+ interpreterMethod = DotNetUtils.Clone(interpreterMethod);
+ var insns = interpreterMethod.Body.Instructions;
+ Instruction? switchIns = null;
+ List trashLocals = new();
+ for (int i = 0; i < insns.Count; i++) {
+ if (insns[i].OpCode == OpCodes.Switch) {
+ var ldloc = insns[i - 1];
+ if (!ldloc.IsLdloc())
+ continue;
+
+ var switchedLocal = ldloc.GetLocal(interpreterMethod.Body.Variables);
+ if (switchedLocal.Type.DefinitionAssembly == interpreterMethod.DeclaringType.DefinitionAssembly)
+ switchIns = insns[i];
+ else if (switchedLocal.Type.FullName == "System.Int32") {
+ trashLocals.Add(switchedLocal);
+ }
+ }
+ }
+ if (switchIns == null) {
+ throw new Exception("Switch instruction not found in " + interpreterMethod);
+ }
+
+ var blocks = new Blocks(interpreterMethod);
+ var allBlocks = blocks.MethodBlocks.GetAllBlocks();
+
+ // Do some cleaning and normalization.
+ foreach (var target in (Instruction[])switchIns.Operand) {
+ var targetBlock = allBlocks.First(b => b.Instructions.Count > 0 && b.FirstInstr.Instruction == target);
+ foreach (var block in Reachable(targetBlock)) {
+ CleanSpuriousCffAssignments(block, trashLocals, interpreterMethod.Body.Variables);
+ if (block.IsConditionalBranch() && ShouldNormalizeBranch(block.LastInstr.OpCode)) {
+ block.FlipConditionalBranch(); // NOTE: This doesn't update operands, so instruction printings can be confsuing.
+ }
+ }
+ }
+ blocks.RepartitionBlocks(); // Removes empty blocks.
+
+ int c = 0;
+ foreach (var target in (Instruction[])switchIns.Operand) {
+ var targetBlock = allBlocks.First(b => b.Instructions.Count > 0 && b.FirstInstr.Instruction == target);
+ //Console.WriteLine("--- case " + (c) + " ---");
+ var list = new List();
+ foreach (var block in Reachable(targetBlock)) {
+ /*Console.WriteLine("{");
+ foreach (var ins in block.Instructions) Console.WriteLine(ins);
+ Console.WriteLine("}");*/
+ list.AddRange(block.Instructions.Select(i => i.Instruction));
+ }
+
+ Handlers.Add(c++, new VMHandler(list));
+ //Console.WriteLine(Handlers[c - 1]);
+ }
+ }
+
+ /**
+ * Removes any control flow key assignments that are left after unflattening.
+ */
+ private static void CleanSpuriousCffAssignments(Block block, List cffVars, LocalList allLocals) {
+ // IL_16DE: ldc.i4 330
+ // IL_16E3: stloc V_2
+ for (int i = 1; i < block.Instructions.Count; i++) {
+ if (block.Instructions[i].IsStloc()
+ && cffVars.Contains(block.Instructions[i].Instruction.GetLocal(allLocals))
+ && block.Instructions[i - 1].IsLdcI4()) {
+ block.Remove(i - 1, 2);
+ return;
+ }
+ }
+ }
+
+ private static List Reachable(Block block) {
+ List result = new();
+ TraverseBlocks(block, result);
+ return result;
+ }
+
+ private static void TraverseBlocks(Block block, List found) {
+ found.Add(block);
+ if (block.Targets is { Count: > 1 }) {
+ return; // Skip switches (remnants of flattening)
+ }
+ foreach (var target in block.GetTargets()) {
+ if (!found.Contains(target))
+ TraverseBlocks(target, found);
+ }
+ }
+
+ private static bool ShouldNormalizeBranch(OpCode opcode) {
+ switch (opcode.Code) {
+ case Code.Bge:
+ case Code.Bge_S:
+ case Code.Bge_Un:
+ case Code.Bge_Un_S:
+ case Code.Bgt:
+ case Code.Bgt_S:
+ case Code.Bgt_Un:
+ case Code.Bgt_Un_S:
+ case Code.Brtrue:
+ case Code.Brtrue_S:
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/IPattern.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/IPattern.cs
new file mode 100644
index 00000000..687c2354
--- /dev/null
+++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/IPattern.cs
@@ -0,0 +1,53 @@
+/*
+ Copyright (C) 2011-2020 de4dot@gmail.com
+ 2025-2026 G DATA Advanced Analytics GmbH
+
+ This file is part of de4dotEx.
+
+ de4dotEx 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.
+
+ de4dotEx 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 de4dotEx. If not, see .
+*/
+
+// Pattern code inspired by https://github.com/void-stack/VMAttack and https://github.com/puff/EazyDevirt.
+
+using System.Collections.Generic;
+using dnlib.DotNet.Emit;
+
+namespace de4dot.code.deobfuscators.dotNET_Reactor.v4.vm;
+
+public interface IPattern {
+ ///
+ /// Contiguous list of opcodes that should match. Nops are wildcards.
+ /// The pattern does not need to overlap the input completely, i.e., it can be a prefix of the input.
+ ///
+ IList Pattern { get; }
+
+ ///
+ /// If true, the pattern doesn't need to match from the start.
+ ///
+ bool MatchAnywhere => false;
+
+ ///
+ /// Does (optional) additional verification on the input if the pattern would not be unique otherwise.
+ ///
+ /// Matched instructions from the input.
+ /// True if this should indeed be a match.
+ bool Verify(IList instructions) => true;
+}
+
+public interface IOpcodePattern : IPattern {
+ ///
+ /// Resulting opcode assigned to the match.
+ ///
+ OpCode Opcode { get; }
+}
diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/PatternHelper.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/PatternHelper.cs
new file mode 100644
index 00000000..2e11b5d0
--- /dev/null
+++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/PatternHelper.cs
@@ -0,0 +1,78 @@
+/*
+ Copyright (C) 2011-2020 de4dot@gmail.com
+ 2025-2026 G DATA Advanced Analytics GmbH
+
+ This file is part of de4dotEx.
+
+ de4dotEx 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.
+
+ de4dotEx 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 de4dotEx. If not, see .
+*/
+
+// Some code in this file is loosely based on https://github.com/void-stack/VMAttack.
+
+using System.Linq;
+using dnlib.DotNet;
+using dnlib.DotNet.Emit;
+
+namespace de4dot.code.deobfuscators.dotNET_Reactor.v4.vm;
+
+#nullable enable
+
+internal static class PatternHelper {
+ public static bool FindPatternInOverrides(this MethodDef? virtualMethod, IPattern pattern) {
+ if (virtualMethod is not { IsVirtual: true, IsAbstract: true })
+ return false;
+
+ foreach (var t in virtualMethod.Module.GetTypes()) {
+ foreach (var method in t.Methods.Where(m => m.IsVirtual && m.HasBody && m.Name == virtualMethod.Name)) {
+ if (PatternMatcher.Match(pattern, method.Body.Instructions))
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Used for special situations where the overridden method calls another abstract method.
+ */
+ public static bool FindPatternInOverridesCall(this MethodDef? virtualMethod, IPattern pattern) {
+ if (virtualMethod is not { IsVirtual: true, IsAbstract: true })
+ return false;
+
+ foreach (var t in virtualMethod.Module.GetTypes()) {
+ // (...sometimes there is some nop/br trash in front of here)
+ // ldarg.0
+ // callvirt this.AsByte()
+ // ret
+ var firstOverride = t.Methods.FirstOrDefault(m => m.IsVirtual && m.HasBody
+ && m.Name == virtualMethod.Name
+ && m.Body.Instructions.Count >= 3
+ && m.Body.Instructions[^2].OpCode == OpCodes.Callvirt);
+ if (firstOverride == null)
+ continue;
+
+ var calledInOverride = ((MethodDef)firstOverride.Body.Instructions[^2].Operand).Name;
+ foreach (var t2 in virtualMethod.Module.GetTypes()) {
+ foreach (var callee in t2.Methods.Where(m => m.IsVirtual && m.HasBody && m.Name == calledInOverride)) {
+ if (PatternMatcher.Match(pattern, callee.Body.Instructions))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // In samples like 133fc00de41f4d14caf8a9473dfa14d72588c48db4bf4a7f9b1978865888c7df, the intermediate callvirt is sometimes skipped.
+ return FindPatternInOverrides(virtualMethod, pattern);
+ }
+}
diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/PatternMatcher.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/PatternMatcher.cs
new file mode 100644
index 00000000..b049862a
--- /dev/null
+++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/PatternMatcher.cs
@@ -0,0 +1,115 @@
+/*
+ Copyright (C) 2011-2020 de4dot@gmail.com
+ 2025-2026 G DATA Advanced Analytics GmbH
+
+ This file is part of de4dotEx.
+
+ de4dotEx 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.
+
+ de4dotEx 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 de4dotEx. If not, see .
+*/
+
+// Some code in this file is loosely based on https://github.com/void-stack/VMAttack / https://github.com/puff/EazyDevirt.
+
+using System;
+using System.Collections.Generic;
+using dnlib.DotNet.Emit;
+
+namespace de4dot.code.deobfuscators.dotNET_Reactor.v4.vm;
+
+#nullable enable
+
+internal class PatternMatcher
+{
+ private readonly List _opcodePatterns;
+
+ private readonly Dictionary _opcodes;
+
+ public PatternMatcher()
+ {
+ _opcodes = new Dictionary();
+ _opcodePatterns = new List();
+ foreach (var type in typeof(PatternMatcher).Assembly.GetTypes())
+ if (type.GetInterface(nameof(IOpcodePattern)) != null)
+ if (Activator.CreateInstance(type) is IOpcodePattern instance)
+ _opcodePatterns.Add(instance);
+ }
+
+ public IOpcodePattern? GetOpcode(int vmOpcode) => _opcodes.GetValueOrDefault(vmOpcode);
+
+ public void MatchAll(HandlerMapper mapper) {
+ int numMatched = 0;
+ foreach (var entry in mapper.Handlers) {
+ foreach (var pat in _opcodePatterns) {
+ if (Match(pat, entry.Value.Instructions)) {
+ Logger.v($"Opcode match: {entry.Key} -> {pat.Opcode}");
+ _opcodes[entry.Key] = pat;
+ numMatched++;
+ }
+ }
+ }
+ Logger.v($"Matched opcodes: {numMatched}");
+ }
+
+ public static bool Match(IPattern pattern, IList instructions) {
+ var match = pattern.MatchAnywhere ? MatchAnywhere(pattern, instructions) : MatchesFromStart(pattern, instructions);
+ return match && pattern.Verify(instructions);
+ }
+
+ private static bool MatchAnywhere(IPattern pattern, IList instructions)
+ {
+ var pat = pattern.Pattern;
+ if (pat.Count > instructions.Count) return false;
+
+ for (int i = 0; i < instructions.Count; i++) {
+ var currentCount = 0;
+
+ for (int j = i, k = 0; j < instructions.Count && k < pat.Count; j++, k++) {
+ var instruction = instructions[j];
+ if (instruction.OpCode != pat[k] && pat[k] != OpCodes.Nop && !CanInterchange(instruction, pat[k]))
+ break;
+ currentCount++;
+ }
+
+ if (currentCount == pat.Count)
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool MatchesFromStart(IPattern pattern, IList instructions)
+ {
+ var pat = pattern.Pattern;
+ if (pat.Count > instructions.Count) return false;
+
+ for (int i = 0; i < pat.Count; i++) {
+ if (pat[i] == OpCodes.Nop)
+ continue;
+
+ var instruction = instructions[i];
+ if (instructions[i].OpCode != pat[i] && !CanInterchange(instruction, pat[i]))
+ return false;
+ }
+
+ return true;
+ }
+
+ private static bool CanInterchange(Instruction ins, OpCode patOpCode)
+ {
+ var patIns = new Instruction(patOpCode);
+
+ return (ins.IsLdloc() && patIns.IsLdloc())
+ || (ins.IsStloc() && patIns.IsStloc())
+ || (ins.IsConditionalBranch() && patOpCode.Name.Replace(".s", "") == ins.OpCode.Name.Replace(".s", ""));
+ }
+}
diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/Patterns.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/Patterns.cs
new file mode 100644
index 00000000..33f59df3
--- /dev/null
+++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/Patterns.cs
@@ -0,0 +1,718 @@
+/*
+ Copyright (C) 2011-2020 de4dot@gmail.com
+ 2025-2026 G DATA Advanced Analytics GmbH
+
+ This file is part of de4dotEx.
+
+ de4dotEx 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.
+
+ de4dotEx 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 de4dotEx. If not, see .
+*/
+
+using System.Collections.Generic;
+using dnlib.DotNet;
+using dnlib.DotNet.Emit;
+
+namespace de4dot.code.deobfuscators.dotNET_Reactor.v4.vm;
+
+// Note when reading these patterns: They are often not 1:1 what you find in the assembly.
+// HandlerMapper applies normalization to guarantee matches even if blocks are laid out differently.
+
+internal record Callvirt : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldc_I4_0,
+ OpCodes.Call, // call System.Void VM/VMExecution::CallOperandMethod(System.Boolean)
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Callvirt;
+}
+
+internal record Call : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldc_I4_1,
+ OpCodes.Call, // call System.Void VM/VMExecution::CallOperandMethod(System.Boolean)
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Call;
+}
+
+internal record Ceq : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt,
+ OpCodes.Call,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt,
+ OpCodes.Call,
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt,
+ OpCodes.Brfalse,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldc_I4_1,
+ OpCodes.Newobj,
+ OpCodes.Callvirt,
+ OpCodes.Ret,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldc_I4_0,
+ OpCodes.Newobj,
+ OpCodes.Callvirt,
+ OpCodes.Ret
+ };
+ public OpCode Opcode => OpCodes.Ceq;
+}
+
+internal record Dup : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt,
+ OpCodes.Callvirt,
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Dup;
+}
+
+internal record Ldarg : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any,
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldloc_S,
+ OpCodes.Ldelem_Ref,
+ OpCodes.Callvirt,
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Ldarg;
+}
+
+// Before 7.0?
+internal record LdargOld : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any,
+ OpCodes.Ldelem_Ref,
+ OpCodes.Callvirt,
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Ldarg;
+}
+
+internal abstract record Ldc {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any, // [4]
+ OpCodes.Newobj,
+ OpCodes.Callvirt,
+ OpCodes.Ret
+ };
+
+ protected abstract string TypeName { get; }
+
+ public bool Verify(IList instructions) => ((TypeRef)instructions[4].Operand).FullName == TypeName;
+}
+
+internal record LdcI4 : Ldc, IOpcodePattern {
+ protected override string TypeName => "System.Int32";
+ public OpCode Opcode => OpCodes.Ldc_I4;
+}
+
+internal record LdcI8 : Ldc, IOpcodePattern {
+ protected override string TypeName => "System.Int64";
+ public OpCode Opcode => OpCodes.Ldc_I8;
+}
+
+internal record LdcR4 : Ldc, IOpcodePattern {
+ protected override string TypeName => "System.Single";
+ public OpCode Opcode => OpCodes.Ldc_R4;
+}
+
+internal record LdcR8 : Ldc, IOpcodePattern {
+ protected override string TypeName => "System.Double";
+ public OpCode Opcode => OpCodes.Ldc_R8;
+}
+
+internal record Ldlen : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt,
+ OpCodes.Ldnull,
+ OpCodes.Callvirt,
+ OpCodes.Castclass,
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt,
+ OpCodes.Ldc_I4_5,
+ OpCodes.Newobj,
+ OpCodes.Callvirt,
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Ldlen;
+}
+
+internal record Ldloc : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any,
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldloc_S,
+ OpCodes.Ldelem_Ref,
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt,
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Ldloc;
+}
+
+internal record LdlocOld : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any,
+ OpCodes.Ldelem_Ref,
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt,
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Ldloc;
+}
+
+internal record Ldnull : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldnull,
+ OpCodes.Newobj,
+ OpCodes.Callvirt,
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Ldnull;
+}
+
+internal record Ldsfld : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any,
+ OpCodes.Call,
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // callvirt System.Type System.Reflection.FieldInfo::get_FieldType()
+ OpCodes.Ldloc_S,
+ OpCodes.Ldnull,
+ OpCodes.Callvirt, // callvirt System.Object System.Reflection.FieldInfo::GetValue(System.Object)
+ OpCodes.Call,
+ OpCodes.Callvirt,
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Ldsfld;
+}
+
+internal record LdsfldOld : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any,
+ OpCodes.Stloc_S,
+ OpCodes.Ldtoken,
+ OpCodes.Call, // call System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle)
+ OpCodes.Callvirt, // callvirt System.Reflection.Module System.Type::get_Module()
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // callvirt System.Reflection.FieldInfo System.Reflection.Module::ResolveField(System.Int32)
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // callvirt System.Type System.Reflection.FieldInfo::get_FieldType()
+ OpCodes.Ldloc_S,
+ OpCodes.Ldnull,
+ OpCodes.Callvirt, // callvirt System.Object System.Reflection.FieldInfo::GetValue(System.Object)
+ OpCodes.Call,
+ OpCodes.Callvirt,
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Ldsfld;
+}
+
+internal record Ldstr : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldsfld,
+ OpCodes.Callvirt, // callvirt System.Int32 System.Collections.Generic.List`1::get_Count()
+ OpCodes.Brfalse,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldsfld,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any, // System.Int32
+ OpCodes.Callvirt, // System.String System.Collections.Generic.List`1::get_Item(System.Int32)
+ OpCodes.Newobj,
+ OpCodes.Callvirt,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true; // lower half of pattern differs between versions
+ public OpCode Opcode => OpCodes.Ldstr;
+}
+
+internal record Ldtoken : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldloc_S,
+ OpCodes.Nop, // callvirt ResolveType() - new builds call an internal helper method
+ OpCodes.Stloc_S,
+ OpCodes.Leave_S,
+ // (catch handler not captured by HandlerMapper)
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldloc_S,
+ OpCodes.Newobj,
+ OpCodes.Callvirt, // Push()
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ public OpCode Opcode => OpCodes.Ldtoken;
+}
+
+internal record Leave : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any,
+ OpCodes.Ldc_I4_1,
+ OpCodes.Sub,
+ OpCodes.Stfld,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldc_I4_1,
+ OpCodes.Stfld,
+ OpCodes.Ret
+ };
+ public OpCode Opcode => OpCodes.Leave;
+}
+
+internal record Newarr : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any, // unbox.any System.Int32
+ OpCodes.Call,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt,
+ OpCodes.Call,
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Call, // call System.Array System.Array::CreateInstance(System.Type,System.Int32)
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldloc_S,
+ OpCodes.Newobj,
+ OpCodes.Callvirt,
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Newarr;
+}
+
+internal record NewarrOld : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any,
+ OpCodes.Stloc_S,
+ OpCodes.Ldtoken,
+ OpCodes.Call,
+ OpCodes.Callvirt,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt,
+ OpCodes.Call,
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Call, // call System.Array System.Array::CreateInstance(System.Type,System.Int32)
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldloc_S,
+ OpCodes.Newobj,
+ OpCodes.Callvirt,
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Newarr;
+}
+
+internal record Newobj : IOpcodePattern {
+ // Partial pattern
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any,
+ OpCodes.Call,
+ OpCodes.Castclass, // castclass System.Reflection.ConstructorInfo
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // callvirt System.Reflection.ParameterInfo[] System.Reflection.MethodBase::GetParameters()
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Ldlen,
+ OpCodes.Conv_I4,
+ OpCodes.Newarr
+ };
+
+ public OpCode Opcode => OpCodes.Newobj;
+}
+
+internal record NewobjOld : IOpcodePattern {
+ // Partial pattern
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any,
+ OpCodes.Stloc_S,
+ OpCodes.Ldtoken,
+ OpCodes.Call, // call System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle)
+ OpCodes.Callvirt, // callvirt System.Reflection.Module System.Type::get_Module()
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // callvirt System.Reflection.MethodBase System.Reflection.Module::ResolveMethod(System.Int32)
+ OpCodes.Castclass, // castclass System.Reflection.ConstructorInfo
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // callvirt System.Reflection.ParameterInfo[] System.Reflection.MethodBase::GetParameters()
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Ldlen,
+ OpCodes.Conv_I4,
+ OpCodes.Newarr
+ };
+
+ public OpCode Opcode => OpCodes.Newobj;
+}
+
+internal record Nop : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ret
+ };
+ public OpCode Opcode => OpCodes.Nop;
+}
+
+internal record Pop : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // ldfld System.Object VM/VMExecution::Stack
+ OpCodes.Callvirt, // callvirt VM/VMObject VM/VMStack::Pop()
+ OpCodes.Pop,
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Pop;
+}
+
+internal record Ret : IOpcodePattern
+{
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldc_I4_S, // ldc.i4.s -3
+ OpCodes.Stfld, // stfld InstrPointer
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // ldfld Stack
+ OpCodes.Callvirt, // callvirt VMStack::Count()
+ OpCodes.Ldc_I4_0,
+ OpCodes.Ble, // ble -> ret
+ OpCodes.Ldarg_0,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // ldfld Stack
+ OpCodes.Callvirt, // callvirt VMStack::Pop()
+ OpCodes.Stfld, // stfld ReturnValue
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Ret;
+}
+
+internal record Stelem : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt,
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt,
+ OpCodes.Call,
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt,
+ OpCodes.Ldnull,
+ OpCodes.Callvirt,
+ OpCodes.Castclass, // System.Array
+ OpCodes.Dup,
+ OpCodes.Callvirt, // System.Type System.Object::GetType()
+ OpCodes.Callvirt, // System.Type System.Type::GetElementType()
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt, // System.Void System.Array::SetValue(System.Object,System.Int32)
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Stelem;
+}
+
+internal record Stfld : IOpcodePattern {
+ public IList Pattern => new List {
+ OpCodes.Callvirt, // callvirt System.Void System.Reflection.FieldInfo::SetValue(System.Object,System.Object)
+ OpCodes.Ret,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // callvirt System.Type System.Reflection.MemberInfo::get_DeclaringType()
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // callvirt System.Boolean System.Type::get_IsByRef()
+ OpCodes.Brfalse,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // callvirt System.Type System.Type::GetElementType()
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // callvirt System.Boolean System.Type::get_IsValueType()
+ OpCodes.Brfalse_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Call, // call System.Object System.Activator::CreateInstance(System.Type)
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Isinst,
+ OpCodes.Brfalse_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Castclass,
+ OpCodes.Ldloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Call,
+ OpCodes.Callvirt,
+ OpCodes.Newobj,
+ OpCodes.Throw
+ };
+ public bool MatchAnywhere => true;
+ public OpCode Opcode => OpCodes.Stfld;
+}
+
+internal record Stloc : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any,
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldfld,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldfld,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt,
+ OpCodes.Ldfld,
+ OpCodes.Call,
+ OpCodes.Stelem_Ref,
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Stloc;
+}
+
+internal record Stsfld : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any, // unbox.any System.Int32
+ OpCodes.Call,
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // callvirt System.Type System.Reflection.FieldInfo::get_FieldType()
+ OpCodes.Callvirt,
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Ldnull,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // callvirt System.Void System.Reflection.FieldInfo::SetValue(System.Object,System.Object)
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Stsfld;
+}
+
+internal record StsfldOld : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any, // unbox.any System.Int32
+ OpCodes.Stloc_S,
+ OpCodes.Ldtoken,
+ OpCodes.Call, // call System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle)
+ OpCodes.Callvirt, // callvirt System.Reflection.Module System.Type::get_Module()
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // callvirt System.Reflection.FieldInfo System.Reflection.Module::ResolveField(System.Int32)
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // callvirt System.Type System.Reflection.FieldInfo::get_FieldType()
+ OpCodes.Callvirt,
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Ldnull,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // callvirt System.Void System.Reflection.FieldInfo::SetValue(System.Object,System.Object)
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Stsfld;
+}
+
+internal record Switch : IOpcodePattern {
+ // Partial pattern
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Castclass, // System.Int32[]
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt,
+ OpCodes.Call,
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ };
+
+ public OpCode Opcode => OpCodes.Switch;
+}
+
+internal record Throw : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // ldfld System.Object VM/VMExecution::Stack
+ OpCodes.Callvirt, // callvirt VM/VMObject VM/VMStack::Pop()
+ OpCodes.Ldnull,
+ OpCodes.Callvirt,
+ OpCodes.Castclass,
+ OpCodes.Throw
+ };
+
+ public OpCode Opcode => OpCodes.Throw;
+}
diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/PatternsArithmetic.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/PatternsArithmetic.cs
new file mode 100644
index 00000000..03967b62
--- /dev/null
+++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/PatternsArithmetic.cs
@@ -0,0 +1,621 @@
+/*
+ Copyright (C) 2011-2020 de4dot@gmail.com
+ 2025-2026 G DATA Advanced Analytics GmbH
+
+ This file is part of de4dotEx.
+
+ de4dotEx 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.
+
+ de4dotEx 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 de4dotEx. If not, see .
+*/
+
+using System.Collections.Generic;
+using dnlib.DotNet;
+using dnlib.DotNet.Emit;
+
+namespace de4dot.code.deobfuscators.dotNET_Reactor.v4.vm;
+
+record BaseArithPattern : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // Stack
+ OpCodes.Callvirt, // Pop()
+ OpCodes.Call,
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // Stack
+ OpCodes.Callvirt, // Pop()
+ OpCodes.Call,
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Brfalse,
+ OpCodes.Ldloc_S,
+ OpCodes.Brfalse,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // Stack
+ OpCodes.Ldloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // [18] Add(), Sub(), etc.
+ OpCodes.Callvirt, // Push()
+ OpCodes.Ret,
+ OpCodes.Newobj, // VMException ctor
+ OpCodes.Throw
+ };
+ // ReSharper disable once InconsistentNaming
+ internal const int CallIndex = 18;
+}
+
+internal record Add : BaseArithPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Add,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Add;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record AddOvf : BaseArithPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Add_Ovf,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Add_Ovf;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record AddOvfUn : BaseArithPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Add_Ovf_Un,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Add_Ovf_Un;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record Sub : BaseArithPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Sub,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Sub;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record SubOvf : IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Sub_Ovf,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // Stack
+ OpCodes.Callvirt, // Pop()
+ OpCodes.Call,
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // Stack
+ OpCodes.Callvirt, // Pop()
+ OpCodes.Castclass, // not call unlike the others
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Brfalse,
+ OpCodes.Ldloc_S,
+ OpCodes.Brfalse,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // Stack
+ OpCodes.Ldloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // [18] SubOvf()
+ OpCodes.Callvirt, // Push()
+ OpCodes.Ret,
+ OpCodes.Newobj, // VMException ctor
+ OpCodes.Throw
+ };
+
+ public OpCode Opcode => OpCodes.Sub_Ovf;
+
+ public bool Verify(IList instructions)
+ => (instructions[18].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record SubOvfUn : BaseArithPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Sub_Ovf_Un,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Sub_Ovf_Un;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record Mul : BaseArithPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Mul,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Mul;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record MulOvf : BaseArithPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Mul_Ovf,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Mul_Ovf;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record MulOvfUn : BaseArithPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Mul_Ovf_Un,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Mul_Ovf_Un;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record Div : BaseArithPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Div,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Div;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record DivUn : BaseArithPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Div_Un,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Div_Un;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record Rem : BaseArithPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Rem,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Rem;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record RemUn : BaseArithPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Rem_Un,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Rem_Un;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record And : BaseArithPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.And,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.And;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record Or : BaseArithPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Or,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Or;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record Xor : BaseArithPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Xor,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Xor;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record Shl : BaseArithPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldc_I4_S, // ldc.i4.s 0x1F
+ OpCodes.And,
+ OpCodes.Shl,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Shl;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record Shr : BaseArithPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldc_I4_S, // ldc.i4.s 0x1F
+ OpCodes.And,
+ OpCodes.Shr,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Shr;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record ShrUn : BaseArithPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldc_I4_S, // ldc.i4.s 0x1F
+ OpCodes.And,
+ OpCodes.Shr_Un,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Shr_Un;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+record BaseUnaryPattern : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt, // Pop()
+ OpCodes.Call,
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Brfalse,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // [10] Operator()
+ OpCodes.Callvirt, // Push()
+ OpCodes.Ret,
+ OpCodes.Newobj,
+ OpCodes.Throw
+ };
+ // ReSharper disable once InconsistentNaming
+ internal const int CallIndex = 10;
+}
+
+internal record Neg : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt, // Pop()
+ OpCodes.Castclass,
+ OpCodes.Callvirt, // Neg()
+ OpCodes.Callvirt, // Push()
+ OpCodes.Ret
+ };
+
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Neg,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Neg;
+
+ public bool Verify(IList instructions)
+ => (instructions[6].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record Not : BaseUnaryPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Not,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Not;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/PatternsBranch.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/PatternsBranch.cs
new file mode 100644
index 00000000..4a67ffe3
--- /dev/null
+++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/PatternsBranch.cs
@@ -0,0 +1,411 @@
+/*
+ Copyright (C) 2011-2020 de4dot@gmail.com
+ 2025-2026 G DATA Advanced Analytics GmbH
+
+ This file is part of de4dotEx.
+
+ de4dotEx 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.
+
+ de4dotEx 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 de4dotEx. If not, see .
+*/
+
+using System.Collections.Generic;
+using dnlib.DotNet;
+using dnlib.DotNet.Emit;
+
+namespace de4dot.code.deobfuscators.dotNET_Reactor.v4.vm;
+
+internal record Br : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // ldfld Operand
+ OpCodes.Unbox_Any,
+ OpCodes.Ldc_I4_1,
+ OpCodes.Sub,
+ OpCodes.Stfld, // stfld InstrPointer
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Br;
+}
+
+internal record Brtrue : IOpcodePattern
+{
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt,
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Brfalse,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt,
+ OpCodes.Brfalse,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any,
+ OpCodes.Ldc_I4_1,
+ OpCodes.Sub,
+ OpCodes.Stfld, // stfld InstrPointer
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Brtrue;
+}
+
+internal record Brfalse : IOpcodePattern
+{
+ public IList Pattern => new List
+ {
+ OpCodes.Ldc_I4_0,
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // ldfld Stack
+ OpCodes.Callvirt, // callvirt VMStack::Pop()
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Brfalse,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt,
+ OpCodes.Ldc_I4_0,
+ OpCodes.Ceq,
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Brfalse,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any,
+ OpCodes.Ldc_I4_1,
+ OpCodes.Sub,
+ OpCodes.Stfld,
+ OpCodes.Ret,
+ OpCodes.Ldc_I4_1,
+ OpCodes.Stloc_S
+ };
+
+ public OpCode Opcode => OpCodes.Brfalse;
+}
+
+// Used for all except beq, bne.un, bgt.un, blt.un
+record CondBranchPattern : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // Stack
+ OpCodes.Callvirt, // Pop()
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // Stack
+ OpCodes.Callvirt, // Pop()
+ OpCodes.Call,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // [9] Comparison()
+ OpCodes.Brfalse,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any, // System.Int32
+ OpCodes.Ldc_I4_1,
+ OpCodes.Sub,
+ OpCodes.Stfld, // InstrPointer
+ OpCodes.Ret
+ };
+ // ReSharper disable once InconsistentNaming
+ internal const int CallIndex = 9;
+}
+
+internal record Beq : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // Stack
+ OpCodes.Callvirt, // Pop()
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // Stack
+ OpCodes.Callvirt, // Pop()
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // [8] Equals()
+ OpCodes.Brfalse,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any, // System.Int32
+ OpCodes.Ldc_I4_1,
+ OpCodes.Sub,
+ OpCodes.Stfld, // InstrPointer
+ OpCodes.Ret
+ };
+
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldloc_S,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ceq,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Beq;
+
+ public bool Verify(IList instructions) =>
+ (instructions[8].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record BneUn : IOpcodePattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // Stack
+ OpCodes.Callvirt, // Pop()
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // Stack
+ OpCodes.Callvirt, // Pop()
+ OpCodes.Callvirt, // [6] NotEquals()
+ OpCodes.Brfalse,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any, // System.Int32
+ OpCodes.Ldc_I4_1,
+ OpCodes.Sub,
+ OpCodes.Stfld, // InstrPointer
+ OpCodes.Ret
+ };
+
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldloc_S,
+ OpCodes.Ceq,
+ OpCodes.Ldc_I4_0,
+ OpCodes.Ceq,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Bne_Un;
+
+ public bool Verify(IList instructions) =>
+ (instructions[6].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record Bge : CondBranchPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Clt,
+ OpCodes.Ldc_I4_0,
+ OpCodes.Ceq,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Bge;
+
+ public bool Verify(IList instructions) =>
+ (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record BgeUn : CondBranchPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Clt_Un,
+ OpCodes.Ldc_I4_0,
+ OpCodes.Ceq,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Bge_Un;
+
+ public bool Verify(IList instructions) =>
+ (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record Bgt : CondBranchPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Cgt,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Bgt;
+
+ public bool Verify(IList instructions) =>
+ (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record BgtUn : IOpcodePattern { // UNSURE
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt,
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Call,
+ OpCodes.Stloc_S,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Callvirt,
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Call,
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Brfalse_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Brfalse,
+ OpCodes.Ldloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // Greater()
+ OpCodes.Brfalse,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any,
+ OpCodes.Ldc_I4_1,
+ OpCodes.Sub,
+ OpCodes.Stfld, // InstrPointer
+ OpCodes.Ret,
+ OpCodes.Ldloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // NotEquals()
+ OpCodes.Brfalse,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Unbox_Any,
+ OpCodes.Ldc_I4_1,
+ OpCodes.Sub,
+ OpCodes.Stfld, // InstrPointer
+ OpCodes.Ret
+ };
+
+ public OpCode Opcode => OpCodes.Bgt_Un;
+}
+
+internal record Ble : CondBranchPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Cgt,
+ OpCodes.Ldc_I4_0,
+ OpCodes.Ceq,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Ble;
+
+ public bool Verify(IList instructions) =>
+ (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record BleUn : CondBranchPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Cgt_Un,
+ OpCodes.Ldc_I4_0,
+ OpCodes.Ceq,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Ble_Un;
+
+ public bool Verify(IList instructions) =>
+ (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+internal record Blt : CondBranchPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Ldarg_1,
+ OpCodes.Castclass,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Clt,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Blt;
+
+ public bool Verify(IList instructions) =>
+ (instructions[CallIndex].Operand as MethodDef).FindPatternInOverrides(new Inner());
+}
+
+// blt.un???
diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/PatternsConv.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/PatternsConv.cs
new file mode 100644
index 00000000..3eaa29eb
--- /dev/null
+++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/vm/PatternsConv.cs
@@ -0,0 +1,546 @@
+/*
+ Copyright (C) 2011-2020 de4dot@gmail.com
+ 2025-2026 G DATA Advanced Analytics GmbH
+
+ This file is part of de4dotEx.
+
+ de4dotEx 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.
+
+ de4dotEx 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 de4dotEx. If not, see .
+*/
+
+using System.Collections.Generic;
+using dnlib.DotNet;
+using dnlib.DotNet.Emit;
+
+namespace de4dot.code.deobfuscators.dotNET_Reactor.v4.vm;
+
+record ConvPattern : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // Stack
+ OpCodes.Callvirt, // Pop()
+ OpCodes.Call,
+ OpCodes.Stloc_S,
+ OpCodes.Ldloc_S,
+ OpCodes.Brfalse,
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld, // Stack
+ OpCodes.Ldloc_S,
+ OpCodes.Callvirt, // [10] Convert()
+ OpCodes.Callvirt, // Push()
+ OpCodes.Ret,
+ OpCodes.Newobj, // VMException ctor
+ OpCodes.Throw
+ };
+ // ReSharper disable once InconsistentNaming
+ internal const int CallIndex = 10;
+}
+
+internal record ConvI1 : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Conv_I1,
+ OpCodes.Ldc_I4_1,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_I1;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverridesCall(new Inner());
+}
+
+internal record ConvU1 : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Conv_U1,
+ OpCodes.Ldc_I4_2,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_U1;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverridesCall(new Inner());
+}
+
+internal record ConvI2 : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Conv_I2,
+ OpCodes.Ldc_I4_3,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_I2;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverridesCall(new Inner());
+}
+
+internal record ConvU2 : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Conv_U2,
+ OpCodes.Ldc_I4_4,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_U2;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverridesCall(new Inner());
+}
+
+internal record ConvI4 : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Conv_I4,
+ OpCodes.Ldc_I4_5,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_I4;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverridesCall(new Inner());
+}
+
+internal record ConvU4 : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Conv_U4,
+ OpCodes.Ldc_I4_6,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_U4;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverridesCall(new Inner());
+}
+
+internal record ConvI8 : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Conv_I8,
+ OpCodes.Ldc_I4_7,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_I8;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverridesCall(new Inner());
+}
+
+internal record ConvU8 : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldfld,
+ OpCodes.Conv_U8,
+ OpCodes.Ldc_I4_8,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_U8;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverridesCall(new Inner());
+}
+
+internal record ConvR4 : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Conv_R4,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_R4;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverridesCall(new Inner());
+}
+
+internal record ConvR8 : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Conv_R8,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_R8;
+
+ public bool Verify(IList instructions) {
+
+ return (instructions[CallIndex].Operand as MethodDef).FindPatternInOverridesCall(new Inner());
+ }
+}
+
+internal record ConvRUn : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Conv_R_Un,
+ OpCodes.Conv_R8,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_R_Un;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverridesCall(new Inner());
+}
+
+internal record ConvOvfI1 : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Conv_Ovf_I1,
+ OpCodes.Ldc_I4_1,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_Ovf_I1;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverridesCall(new Inner());
+}
+
+internal record ConvOvfI1Un : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Conv_Ovf_I1_Un,
+ OpCodes.Ldc_I4_1,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_Ovf_I1_Un;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverridesCall(new Inner());
+}
+
+internal record ConvOvfU1 : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Conv_Ovf_U1,
+ OpCodes.Ldc_I4_2,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_Ovf_U1;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverridesCall(new Inner());
+}
+
+internal record ConvOvfU1Un : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Conv_Ovf_U1_Un,
+ OpCodes.Ldc_I4_2,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_Ovf_U1_Un;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverridesCall(new Inner());
+}
+
+internal record ConvOvfI2 : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Conv_Ovf_I2,
+ OpCodes.Ldc_I4_3,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_Ovf_I2;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverridesCall(new Inner());
+}
+
+internal record ConvOvfI2Un : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Conv_Ovf_I2_Un,
+ OpCodes.Ldc_I4_3,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_Ovf_I2_Un;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverridesCall(new Inner());
+}
+
+internal record ConvOvfU2 : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Conv_Ovf_U2,
+ OpCodes.Ldc_I4_4,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_Ovf_U2;
+
+ public bool Verify(IList instructions)
+ => (instructions[CallIndex].Operand as MethodDef).FindPatternInOverridesCall(new Inner());
+}
+
+internal record ConvOvfU2Un : ConvPattern, IOpcodePattern {
+ record Inner : IPattern {
+ public IList Pattern => new List
+ {
+ OpCodes.Ldarg_0,
+ OpCodes.Ldflda,
+ OpCodes.Ldfld,
+ OpCodes.Conv_Ovf_U2_Un,
+ OpCodes.Ldc_I4_4,
+ OpCodes.Newobj,
+ OpCodes.Ret
+ };
+ public bool MatchAnywhere => true;
+ }
+
+ public OpCode Opcode => OpCodes.Conv_Ovf_U2_Un;
+
+ public bool Verify(IList