Skip to content

Commit 19902b9

Browse files
authored
Merge pull request #23 from GDATAAdvancedAnalytics/netreactor7
Add support for .NET Reactor 7.0+
2 parents b2a0a08 + 14f8417 commit 19902b9

24 files changed

Lines changed: 3694 additions & 118 deletions

De4DotCommon.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<TargetFrameworks Condition=" '$(De4DotNetFramework)' == 'true' ">net48</TargetFrameworks>
88
<TargetFrameworks Condition=" '$(De4DotNetFramework)' != 'true' ">net8.0</TargetFrameworks>
99
<Features>strict</Features>
10-
<LangVersion>latest</LangVersion>
10+
<LangVersion>12.0</LangVersion>
1111
<SignAssembly>true</SignAssembly>
1212
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)\de4dot.snk</AssemblyOriginatorKeyFile>
1313
<VersionPrefix>3.4.0</VersionPrefix>

de4dot.blocks/cflow/CflowUtils.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License
1818
*/
1919

2020
using System.Collections.Generic;
21+
using dnlib.DotNet.Emit;
2122

2223
namespace de4dot.blocks.cflow {
2324
static class CflowUtils {
@@ -27,9 +28,38 @@ static class CflowUtils {
2728

2829
int index = intValue.Value;
2930
if (targets == null || index < 0 || index >= targets.Count)
30-
return fallThrough;
31+
return TryResolveConditionalFallthrough(fallThrough, intValue);
3132
else
3233
return targets[index];
3334
}
35+
36+
/**
37+
* Some obfuscators have default blocks that dispatch additional cases.
38+
* Example: 4e86d71a19f7f69471776817dc67585064b4b60542bc60e9450739bca63226ee
39+
*/
40+
private static Block? TryResolveConditionalFallthrough(Block? block, Int32Value value) {
41+
while (true) {
42+
if (block == null) return null;
43+
44+
var instrs = block.Instructions;
45+
if (instrs.Count < 3) return block;
46+
if (!instrs[0].IsLdloc()) return block;
47+
if (!instrs[1].IsLdcI4()) return block;
48+
if (instrs[2].OpCode.Code is not (Code.Beq or Code.Beq_S or Code.Bne_Un or Code.Bne_Un_S)) return block;
49+
50+
int constant = instrs[1].GetLdcI4Value();
51+
var branch = instrs[2];
52+
53+
int v = value.Value;
54+
55+
bool taken =
56+
branch.OpCode.Code is Code.Beq or Code.Beq_S ? (v == constant) :
57+
branch.OpCode.Code is Code.Bne_Un or Code.Bne_Un_S && (v != constant);
58+
59+
if (taken)
60+
return block.Targets![0];
61+
block = block.FallThrough;
62+
}
63+
}
3464
}
3565
}

de4dot.code/ObfuscatedFile.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,13 @@ void Deobfuscate(MethodDef method, BlocksCflowDeobfuscator cflowDeobfuscator, Me
609609
deob.DeobfuscateMethodBegin(blocks);
610610
if (options.ControlFlowDeobfuscation) {
611611
cflowDeobfuscator.Initialize(blocks);
612-
cflowDeobfuscator.Deobfuscate();
612+
try {
613+
cflowDeobfuscator.Deobfuscate();
614+
}
615+
catch (Exception) {
616+
Logger.e("Error during cflow deobfuscation of {0} ({1:X8})", Utils.RemoveNewlines(method), method.MDToken.ToUInt32());
617+
throw;
618+
}
613619
}
614620

615621
if (deob.DeobfuscateOther(blocks) && options.ControlFlowDeobfuscation)

de4dot.code/deobfuscators/dotNET_Reactor/v4/CflowConstantsInliner.cs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,13 @@ void Find() {
3838
continue;
3939
if (i + 1 >= instrs.Count)
4040
continue;
41-
var stsfld = instrs[i + 1];
42-
if (stsfld.OpCode.Code != Code.Stsfld)
41+
var store = instrs[i + 1];
42+
if (store.OpCode.Code is not (Code.Stsfld or Code.Stfld))
4343
continue;
44-
var key = stsfld.Operand as FieldDef;
45-
if (key == null)
44+
if (store.Operand is not FieldDef key)
4645
continue;
4746

48-
var value = ldcI4.GetLdcI4Value();
49-
if (!dictionary.ContainsKey(key))
50-
dictionary.Add(key, value);
51-
else
52-
dictionary[key] = value;
47+
dictionary[key] = ldcI4.GetLdcI4Value();
5348
}
5449

5550
if (dictionary.Count < 100) {
@@ -76,14 +71,21 @@ public void InlineAllConstants() {
7671
var instrs = method.Body.Instructions;
7772

7873
for (var i = 0; i < instrs.Count; i++) {
79-
var ldsfld = instrs[i];
80-
if (ldsfld.OpCode.Code != Code.Ldsfld)
74+
bool nopNext = false;
75+
var load = instrs[i];
76+
if (load.OpCode.Code != Code.Ldsfld)
8177
continue;
82-
var ldsfldValue = ldsfld.Operand as FieldDef;
83-
if (ldsfldValue == null)
78+
if (i < instrs.Count - 1 && instrs[i + 1].OpCode.Code == Code.Ldfld) {
79+
load = instrs[i + 1];
80+
nopNext = true;
81+
}
82+
if (load.Operand is not FieldDef loadField)
8483
continue;
85-
if (dictionary.TryGetValue(ldsfldValue, out var value))
84+
if (dictionary.TryGetValue(loadField, out var value)) {
8685
instrs[i] = Instruction.CreateLdcI4(value);
86+
if (nopNext)
87+
instrs[i + 1] = Instruction.Create(OpCodes.Nop);
88+
}
8789
}
8890
}
8991
}

de4dot.code/deobfuscators/dotNET_Reactor/v4/Deobfuscator.cs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ You should have received a copy of the GNU General Public License
2525
using dnlib.DotNet.Writer;
2626
using de4dot.blocks;
2727
using de4dot.blocks.cflow;
28+
using de4dot.code.deobfuscators.dotNET_Reactor.v4.vm;
2829

2930
namespace de4dot.code.deobfuscators.dotNET_Reactor.v4 {
3031
public class DeobfuscatorInfo : DeobfuscatorInfoBase {
@@ -42,6 +43,7 @@ public class DeobfuscatorInfo : DeobfuscatorInfoBase {
4243
BoolOption removeNamespaces;
4344
BoolOption removeAntiStrongName;
4445
BoolOption renameShort;
46+
BoolOption devirtualize;
4547

4648
public DeobfuscatorInfo()
4749
: base(DEFAULT_REGEX) {
@@ -55,6 +57,7 @@ public DeobfuscatorInfo()
5557
removeNamespaces = new BoolOption(null, MakeArgName("ns1"), "Clear namespace if there's only one class in it", true);
5658
removeAntiStrongName = new BoolOption(null, MakeArgName("sn"), "Remove anti strong name code", true);
5759
renameShort = new BoolOption(null, MakeArgName("sname"), "Rename short names", false);
60+
devirtualize = new BoolOption(null, MakeArgName("devirtualize"), "Devirtualize methods", true);
5861
}
5962

6063
public override string Name => THE_NAME;
@@ -73,6 +76,7 @@ public override IDeobfuscator CreateDeobfuscator() =>
7376
RemoveNamespaces = removeNamespaces.Get(),
7477
RemoveAntiStrongName = removeAntiStrongName.Get(),
7578
RenameShort = renameShort.Get(),
79+
Devirtualize = devirtualize.Get(),
7680
});
7781

7882
protected override IEnumerable<Option> GetOptionsInternal() =>
@@ -87,6 +91,7 @@ protected override IEnumerable<Option> GetOptionsInternal() =>
8791
removeNamespaces,
8892
removeAntiStrongName,
8993
renameShort,
94+
devirtualize,
9095
};
9196
}
9297

@@ -106,6 +111,7 @@ class Deobfuscator : DeobfuscatorBase {
106111
AntiStrongName antiStrongname;
107112
EmptyClass emptyClass;
108113
ProxyCallFixer proxyCallFixer;
114+
Devirtualizer devirtualizer;
109115

110116
bool unpackedNativeFile = false;
111117
bool canRemoveDecrypterType = true;
@@ -122,6 +128,7 @@ internal class Options : OptionsBase {
122128
public bool RemoveNamespaces { get; set; }
123129
public bool RemoveAntiStrongName { get; set; }
124130
public bool RenameShort { get; set; }
131+
public bool Devirtualize { get; set; }
125132
}
126133

127134
public override string Type => DeobfuscatorInfo.THE_TYPE;
@@ -205,7 +212,8 @@ protected override int DetectInternal() {
205212
ToInt32(stringDecrypter.Detected) +
206213
ToInt32(booleanDecrypter.Detected) +
207214
ToInt32(assemblyResolver.Detected) +
208-
ToInt32(resourceResolver.Detected);
215+
ToInt32(resourceResolver.Detected) +
216+
ToInt32(devirtualizer.Detected);
209217
if (sum > 0)
210218
val += 100 + 10 * (sum - 1);
211219

@@ -222,6 +230,8 @@ protected override void ScanForObfuscator() {
222230
methodsDecrypter.Find();
223231
proxyCallFixer = new ProxyCallFixer(module, DeobfuscatedFile);
224232
proxyCallFixer.FindDelegateCreator(module);
233+
devirtualizer = new Devirtualizer(DeobfuscatedFile, module);
234+
devirtualizer.Find();
225235
stringDecrypter = new StringDecrypter(module);
226236
stringDecrypter.Find(DeobfuscatedFile);
227237
booleanDecrypter = new BooleanDecrypter(module);
@@ -277,6 +287,12 @@ Methods decrypter locals (not showing its own types):
277287
+ "System.Byte&"
278288
*/
279289

290+
if (devirtualizer.Detected) {
291+
if (devirtualizer.StreamHasPrependedByte)
292+
return DeobfuscatorInfo.THE_NAME + " >= 7.0"; // not sure when exactly this was introduced, might also be 7.3
293+
return DeobfuscatorInfo.THE_NAME + " >= 6.2";
294+
}
295+
280296
LocalTypes localTypes;
281297
int minVer = -1;
282298
foreach (var info in stringDecrypter.DecrypterInfos) {
@@ -430,6 +446,7 @@ public override IDeobfuscator ModuleReloaded(ModuleDefMD module) {
430446
newOne.peImage = new MyPEImage(fileData);
431447
newOne.methodsDecrypter = new MethodsDecrypter(module, methodsDecrypter);
432448
newOne.proxyCallFixer = new ProxyCallFixer(module, proxyCallFixer);
449+
newOne.devirtualizer = new Devirtualizer(module, devirtualizer);
433450
newOne.stringDecrypter = new StringDecrypter(module, stringDecrypter);
434451
newOne.booleanDecrypter = new BooleanDecrypter(module, booleanDecrypter);
435452
newOne.assemblyResolver = new AssemblyResolver(module, assemblyResolver);
@@ -510,6 +527,10 @@ public override void DeobfuscateBegin() {
510527
proxyCallFixer.Find();
511528
proxyCallFixer.DeobfuscateAll();
512529

530+
if (devirtualizer.Detected && options.Devirtualize)
531+
devirtualizer.Devirtualize();
532+
533+
// Inlines '<Module>{7212c6df-0f39-43d5-b7b8-3f24c0ebccff}'::m_1b8cd98d5e234215af7340e19a570660 references.
513534
var cflowInliner = new CflowConstantsInliner(module, DeobfuscatedFile);
514535
cflowInliner.InlineAllConstants();
515536
AddTypeToBeRemoved(cflowInliner.Type, "Cflow constants type");
@@ -640,7 +661,6 @@ public override void DeobfuscateMethodEnd(Blocks blocks) {
640661
metadataTokenObfuscator.Deobfuscate(blocks);
641662
FixTypeofDecrypterInstructions(blocks);
642663
RemoveAntiStrongNameCode(blocks);
643-
stringDecrypter.DeobfuscateXored(blocks);
644664
base.DeobfuscateMethodEnd(blocks);
645665
}
646666

@@ -687,6 +707,11 @@ public override void DeobfuscateEnd() {
687707
else
688708
Logger.v("Could not remove decrypter type");
689709

710+
if (devirtualizer.CanRemoveType) {
711+
AddTypeToBeRemoved(devirtualizer.VMType, "VM type");
712+
AddResourceToBeRemoved(devirtualizer.Resource, "VM resource");
713+
}
714+
690715
FixEntryPoint();
691716

692717
base.DeobfuscateEnd();

de4dot.code/deobfuscators/dotNET_Reactor/v4/DotNetReactorCflowDeobfuscator.cs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,22 @@
11
using System.Collections.Generic;
2+
using System.Linq;
23
using de4dot.blocks;
34
using de4dot.blocks.cflow;
45
using dnlib.DotNet;
56
using dnlib.DotNet.Emit;
67

78
namespace de4dot.code.deobfuscators.dotNET_Reactor.v4 {
89
class DotNetReactorCflowDeobfuscator : IBlocksDeobfuscator {
9-
bool isContainsSwitch;
10+
bool _hasSwitch;
1011

1112
public bool ExecuteIfNotModified { get; }
1213

1314
public void DeobfuscateBegin(Blocks blocks) {
14-
var contains = false;
15-
foreach (var instr in blocks.Method.Body.Instructions) {
16-
if (instr.OpCode == OpCodes.Switch) {
17-
contains = true;
18-
break;
19-
}
20-
}
21-
22-
isContainsSwitch = contains;
15+
_hasSwitch = blocks.Method.Body.Instructions.Any(instr => instr.OpCode == OpCodes.Switch);
2316
}
2417

2518
public bool Deobfuscate(List<Block> allBlocks) {
26-
if (!isContainsSwitch)
19+
if (!_hasSwitch)
2720
return false;
2821

2922
var modified = false;

de4dot.code/deobfuscators/dotNET_Reactor/v4/EncryptedResource.cs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -555,8 +555,10 @@ bool Initialize() {
555555

556556
int count = emuEndIndex - emuStartIndex + 1;
557557
instructions = new List<Instruction>(count);
558-
for (int i = 0; i < count; i++)
559-
instructions.Add(origInstrs[emuStartIndex + i].Clone());
558+
for (int i = 0; i < count; i++) {
559+
var ins = origInstrs[emuStartIndex + i];
560+
instructions.Add(ins.Clone());
561+
}
560562

561563
return true;
562564
}
@@ -702,8 +704,30 @@ uint CalculateMagic(uint input) {
702704
instrEmulator.Initialize(method, method.Parameters, locals, method.Body.InitLocals, false);
703705
instrEmulator.SetLocal(emuLocal, new Int32Value((int)input));
704706

705-
foreach (var instr in instructions)
706-
instrEmulator.Emulate(instr);
707+
foreach (var instr in instructions) {
708+
if (instr.OpCode == OpCodes.Bne_Un || instr.OpCode == OpCodes.Bne_Un_S) {
709+
/* The emulator doesn't handle branches, and some DNR builds have a zero-check branch gating a division
710+
// if (num12 == 0U)
711+
/* 0x00002E98 FE0C2900 * / IL_01E4: ldloc V_41
712+
/* 0x00002E9C 16 * / IL_01E8: ldc.i4.0
713+
/* 0x00002E9D 400A000000 * / IL_01E9: bne.un IL_01F8
714+
// num12 -= 1U;
715+
/* 0x00002EA2 FE0C2900 * / IL_01EE: ldloc V_41
716+
/* 0x00002EA6 17 * / IL_01F2: ldc.i4.1
717+
/* 0x00002EA7 59 * / IL_01F3: sub
718+
/* 0x00002EA8 FE0E2900 * / IL_01F4: stloc V_41
719+
*/
720+
var rhs = instrEmulator.Pop();
721+
var lhs = instrEmulator.Pop();
722+
if (rhs is Int32Value rhsInt && lhs is Int32Value lhsInt && rhsInt.IsZero() && lhsInt.IsZero()) {
723+
var local = (Local)instructions[instructions.IndexOf(instr) - 2].Operand;
724+
instrEmulator.SetLocal(local, new Int32Value(-1));
725+
}
726+
}
727+
else {
728+
instrEmulator.Emulate(instr);
729+
}
730+
}
707731

708732
var tos = instrEmulator.Pop() as Int32Value;
709733
if (tos == null || !tos.AllBitsValid())

0 commit comments

Comments
 (0)