Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion De4DotCommon.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<TargetFrameworks Condition=" '$(De4DotNetFramework)' == 'true' ">net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(De4DotNetFramework)' != 'true' ">net8.0</TargetFrameworks>
<Features>strict</Features>
<LangVersion>latest</LangVersion>
<LangVersion>12.0</LangVersion>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)\de4dot.snk</AssemblyOriginatorKeyFile>
<VersionPrefix>3.4.0</VersionPrefix>
Expand Down
32 changes: 31 additions & 1 deletion de4dot.blocks/cflow/CflowUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
}
}
}
}
8 changes: 7 additions & 1 deletion de4dot.code/ObfuscatedFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
}
}
}
}
Expand Down
29 changes: 27 additions & 2 deletions de4dot.code/deobfuscators/dotNET_Reactor/v4/Deobfuscator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -42,6 +43,7 @@ public class DeobfuscatorInfo : DeobfuscatorInfoBase {
BoolOption removeNamespaces;
BoolOption removeAntiStrongName;
BoolOption renameShort;
BoolOption devirtualize;

public DeobfuscatorInfo()
: base(DEFAULT_REGEX) {
Expand All @@ -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;
Expand All @@ -73,6 +76,7 @@ public override IDeobfuscator CreateDeobfuscator() =>
RemoveNamespaces = removeNamespaces.Get(),
RemoveAntiStrongName = removeAntiStrongName.Get(),
RenameShort = renameShort.Get(),
Devirtualize = devirtualize.Get(),
});

protected override IEnumerable<Option> GetOptionsInternal() =>
Expand All @@ -87,6 +91,7 @@ protected override IEnumerable<Option> GetOptionsInternal() =>
removeNamespaces,
removeAntiStrongName,
renameShort,
devirtualize,
};
}

Expand All @@ -106,6 +111,7 @@ class Deobfuscator : DeobfuscatorBase {
AntiStrongName antiStrongname;
EmptyClass emptyClass;
ProxyCallFixer proxyCallFixer;
Devirtualizer devirtualizer;

bool unpackedNativeFile = false;
bool canRemoveDecrypterType = true;
Expand All @@ -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;
Expand Down Expand Up @@ -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);

Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -510,6 +527,10 @@ public override void DeobfuscateBegin() {
proxyCallFixer.Find();
proxyCallFixer.DeobfuscateAll();

if (devirtualizer.Detected && options.Devirtualize)
devirtualizer.Devirtualize();

// Inlines '<Module>{7212c6df-0f39-43d5-b7b8-3f24c0ebccff}'::m_1b8cd98d5e234215af7340e19a570660 references.
var cflowInliner = new CflowConstantsInliner(module, DeobfuscatedFile);
cflowInliner.InlineAllConstants();
AddTypeToBeRemoved(cflowInliner.Type, "Cflow constants type");
Expand Down Expand Up @@ -640,7 +661,6 @@ public override void DeobfuscateMethodEnd(Blocks blocks) {
metadataTokenObfuscator.Deobfuscate(blocks);
FixTypeofDecrypterInstructions(blocks);
RemoveAntiStrongNameCode(blocks);
stringDecrypter.DeobfuscateXored(blocks);
base.DeobfuscateMethodEnd(blocks);
}

Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,22 @@
using System.Collections.Generic;
using System.Linq;
using de4dot.blocks;
using de4dot.blocks.cflow;
using dnlib.DotNet;
using dnlib.DotNet.Emit;

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<Block> allBlocks) {
if (!isContainsSwitch)
if (!_hasSwitch)
return false;

var modified = false;
Expand Down
32 changes: 28 additions & 4 deletions de4dot.code/deobfuscators/dotNET_Reactor/v4/EncryptedResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -555,8 +555,10 @@ bool Initialize() {

int count = emuEndIndex - emuStartIndex + 1;
instructions = new List<Instruction>(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;
}
Expand Down Expand Up @@ -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())
Expand Down
Loading
Loading