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 de4dot.blocks/DotNetUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ public static IEnumerable<MethodDef> GetNormalMethods(TypeDef type) {
return null;
}

public static IEnumerable<IMethod> GetMethodCalls(MethodDef method) {
public static List<IMethod> GetMethodCalls(MethodDef method) {
var list = new List<IMethod>();
if (method.HasBody) {
foreach (var instr in method.Body.Instructions) {
Expand Down
79 changes: 79 additions & 0 deletions de4dot.blocks/cflow/InstructionAndBranchEmulator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System.Collections.Generic;
using dnlib.DotNet;
using dnlib.DotNet.Emit;

namespace de4dot.blocks.cflow;

public class InstructionAndBranchEmulator : IBranchHandler {
public InstructionEmulator Emulator { get; }
readonly BranchEmulator _branchEmulator;
readonly IList<Instruction> _instructions;

protected int InsnIndex;

public InstructionAndBranchEmulator(MethodDef method) {
Emulator = new InstructionEmulator(method);
_branchEmulator = new BranchEmulator(Emulator, this);
_instructions = method.Body.Instructions;
}

public Value? Run(int startIndex = 0, params int[] @params) {
int x = 0;
foreach (int param in @params)
Emulator.SetArg(new Parameter(x++), new Int32Value(param));

for (InsnIndex = startIndex; InsnIndex < _instructions.Count; InsnIndex++) {
var instr = _instructions[InsnIndex];

if (!OnInstruction(instr, out var shouldSkip))
return null;
if (shouldSkip)
continue;

if (instr.OpCode == OpCodes.Ret)
return Emulator.Pop();

if (instr.OpCode.FlowControl is FlowControl.Branch or FlowControl.Cond_Branch) {
if (_branchEmulator.Emulate(instr))
InsnIndex--; // Loop update will do ++, which is bad
else
return null;
}
else
Emulator.Emulate(instr);
}

return null;
}

/// <summary>
/// Called before an instruction is processed. If false is returned, emulation is stopped.
/// </summary>
/// <param name="instr">The next instruction to be emulated.</param>
/// <param name="shouldSkip">Whether to continue with the next instruction without passing this one to
/// the emulator.</param>
/// <returns>Whether to continue emulation.</returns>
protected virtual bool OnInstruction(Instruction instr, out bool shouldSkip) {
shouldSkip = false;
return true;
}

void IBranchHandler.HandleNormal(int stackArgs, bool isTaken) {
if (!isTaken)
InsnIndex++;
else
InsnIndex = _instructions.IndexOf((Instruction)_instructions[InsnIndex].Operand);
}

bool IBranchHandler.HandleSwitch(Int32Value switchIndex) {
if (!switchIndex.AllBitsValid())
return false;
var instr = _instructions[InsnIndex];
var targets = (Instruction[])instr.Operand;
if (switchIndex.Value >= 0 && switchIndex.Value < targets.Length)
InsnIndex = _instructions.IndexOf(targets[switchIndex.Value]);
else
InsnIndex++;
return true;
}
}
59 changes: 37 additions & 22 deletions de4dot.blocks/cflow/InstructionEmulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -367,12 +367,7 @@ public void Emulate(Instruction instr) {
case Code.Conv_Ovf_U8: Emulate_Conv_Ovf_U8(instr); break;
case Code.Conv_Ovf_U8_Un: Emulate_Conv_Ovf_U8_Un(instr); break;

case Code.Ldelem_I1: valueStack.Pop(2); valueStack.Push(Int32Value.CreateUnknown()); break;
case Code.Ldelem_I2: valueStack.Pop(2); valueStack.Push(Int32Value.CreateUnknown()); break;
case Code.Ldelem_I8: valueStack.Pop(2); valueStack.Push(Int64Value.CreateUnknown()); break;
case Code.Ldelem_U1: valueStack.Pop(2); valueStack.Push(Int32Value.CreateUnknownUInt8()); break;
case Code.Ldelem_U2: valueStack.Pop(2); valueStack.Push(Int32Value.CreateUnknownUInt16()); break;
case Code.Ldelem_U4: valueStack.Pop(2); valueStack.Push(Int32Value.CreateUnknown()); break;
case Code.Ldelem: valueStack.Pop(2); valueStack.Push(GetUnknownValue(instr.Operand as ITypeDefOrRef)); break;

case Code.Ldind_I1: valueStack.Pop(); valueStack.Push(Int32Value.CreateUnknown()); break;
Expand All @@ -383,7 +378,7 @@ public void Emulate(Instruction instr) {
case Code.Ldind_U2: valueStack.Pop(); valueStack.Push(Int32Value.CreateUnknownUInt16()); break;
case Code.Ldind_U4: valueStack.Pop(); valueStack.Push(Int32Value.CreateUnknown()); break;

case Code.Ldlen: valueStack.Pop(); valueStack.Push(Int32Value.CreateUnknown()); break;
case Code.Ldlen: Emulate_Ldlen(instr); break;
case Code.Sizeof: Emulate_Sizeof(instr); break;

case Code.Ldfld: Emulate_Ldfld(instr); break;
Expand All @@ -402,8 +397,15 @@ public void Emulate(Instruction instr) {
case Code.Conv_R8: Emulate_Conv_R8(instr); break;

case Code.Newarr: Emulate_Newarr(instr); break;
case Code.Stelem_I4: Emulate_Stelem_I4(instr); break;
case Code.Ldelem_I4: Emulate_Ldelem_I4(instr); break;
case Code.Stelem_I1:
case Code.Stelem_I2:
case Code.Stelem_I4: Emulate_Stelem_I1I2I4(instr); break;
case Code.Ldelem_I1:
case Code.Ldelem_U1:
case Code.Ldelem_I2:
case Code.Ldelem_U2:
case Code.Ldelem_I4:
case Code.Ldelem_U4: Emulate_Ldelem_IU1IU2IU4(instr); break;

case Code.Arglist:
case Code.Beq:
Expand Down Expand Up @@ -473,8 +475,6 @@ public void Emulate(Instruction instr) {
case Code.Rethrow:
case Code.Stelem:
case Code.Stelem_I:
case Code.Stelem_I1:
case Code.Stelem_I2:
case Code.Stelem_I8:
case Code.Stelem_R4:
case Code.Stelem_R8:
Expand Down Expand Up @@ -532,9 +532,9 @@ void Emulate_Sizeof(Instruction instr){
void Emulate_Newarr(Instruction instr)
{
var val = valueStack.Pop();
if (val.IsInt32() && val is Int32Value { Value: < 500000 } arrSize)
if (val.IsInt32() && val is Int32Value { Value: < 500000 and >= 0 } arrSize)
{
List<Value> arr = new List<Value>(arrSize.Value);
var arr = new List<Value>(arrSize.Value);
for (int i = 0; i < arrSize.Value; i++) {
arr.Add(new UnknownValue());
}
Expand All @@ -546,27 +546,42 @@ void Emulate_Newarr(Instruction instr)
}
}

void Emulate_Stelem_I4(Instruction instr)
void Emulate_Ldlen(Instruction instr) {
var obj = valueStack.Pop();
valueStack.Push(obj is ObjectValue { obj: List<Value> arr }
? new Int32Value(arr.Count)
: Int32Value.CreateUnknown());
}

void Emulate_Stelem_I1I2I4(Instruction instr)
{
var val = valueStack.Pop();
var idxValue = valueStack.Pop();
var obj = valueStack.Pop();
if (val is Int32Value &&
idxValue is Int32Value idx && idx.AllBitsValid() &&
obj is ObjectValue { obj: List<Value> arr } && arr.Count > idx.Value) {
arr[idx.Value] = val;
if (val is Int32Value valI32
&& idxValue is Int32Value idx && idx.AllBitsValid()
&& obj is ObjectValue { obj: List<Value> arr } && idx.Value >= 0 && arr.Count > idx.Value) {
arr[idx.Value] = instr.OpCode.Code switch {
Code.Stelem_I1 => valI32.ToUInt8(),
Code.Stelem_I2 => valI32.ToUInt16(),
_ => val
};
}
}

void Emulate_Ldelem_I4(Instruction instr) {
void Emulate_Ldelem_IU1IU2IU4(Instruction instr) {
var idxValue = valueStack.Pop();
var obj = valueStack.Pop();
if (idxValue is Int32Value idx && idx.AllBitsValid() &&
obj is ObjectValue { obj: List<Value> arr } && arr.Count > idx.Value) {
valueStack.Push(arr[idx.Value]);
if (idxValue is Int32Value idx && idx.AllBitsValid()
&& obj is ObjectValue { obj: List<Value> arr } && idx.Value >= 0 && arr.Count > idx.Value) {
valueStack.Push(arr[idx.Value]); // We assume stelem or whatever populated the array put in the correct size
}
else {
valueStack.Push(Int32Value.CreateUnknown());
valueStack.Push(instr.OpCode.Code switch {
Code.Ldelem_U1 => Int32Value.CreateUnknownUInt8(),
Code.Ldelem_U2 => Int32Value.CreateUnknownUInt16(),
_ => Int32Value.CreateUnknown()
});
}
}

Expand Down
2 changes: 2 additions & 0 deletions de4dot.code/StringInliner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ protected override void InlineAllCalls() {
}

protected override CallResult CreateCallResult(IMethod method, MethodSpec gim, Block block, int callInstrIndex) {
if (method is MemberRef memberRef)
method = memberRef.ResolveMethod();
if (stringDecrypters.Find(method) == null)
return null;
return new MyCallResult(block, callInstrIndex, method, gim);
Expand Down
17 changes: 15 additions & 2 deletions de4dot.code/deobfuscators/ArrayFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ public static Value[] GetInitializedArray(int arraySize, MethodDef method, ref i
emulator.Push(theArray);

var instructions = method.Body.Instructions;
FieldDef arrayField = null;
int i;
for (i = newarrIndex + 1; i < instructions.Count; i++) {
var instr = instructions[i];
Expand All @@ -154,8 +155,20 @@ public static Value[] GetInitializedArray(int arraySize, MethodDef method, ref i
case Code.Starg_S:
case Code.Stsfld:
case Code.Stfld:
if (emulator.Peek() == theArray && i != newarrIndex + 1 && i != newarrIndex + 2)
goto done;
if (emulator.Peek() == theArray) {
if (i != newarrIndex + 1 && i != newarrIndex + 2)
goto done;
if (instr.OpCode.Code is Code.Stsfld or Code.Stfld)
arrayField = (FieldDef)instr.Operand;
}
break;

case Code.Ldsfld:
case Code.Ldfld:
if (arrayField != null && instr.Operand == arrayField) {
emulator.Push(theArray);
continue;
}
break;
}

Expand Down
5 changes: 3 additions & 2 deletions de4dot.code/deobfuscators/DeobfuscatorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ public virtual void DeobfuscateEnd() {
RestoreBaseType();
FixMDHeaderVersion();

module.Mvid = Guid.NewGuid();
// NOTE: Changing Mvid breaks samples where Mason Protector is used as a second layer.
//module.Mvid = Guid.NewGuid();
module.EnableTypeDefFindCache = cacheState;
}

Expand Down Expand Up @@ -677,7 +678,7 @@ public virtual bool IsValidNamespaceName(string ns) {
public virtual bool IsValidResourceKeyName(string name) => name != null && CheckValidName(name);
public virtual void OnWriterEvent(ModuleWriterBase writer, ModuleWriterEvent evt) { }
protected void FindAndRemoveInlinedMethods() => RemoveInlinedMethods(InlinedMethodsFinder.Find(module));
protected void RemoveInlinedMethods(List<MethodDef> inlinedMethods) =>
protected void RemoveInlinedMethods(IEnumerable<MethodDef> inlinedMethods) =>
AddMethodsToBeRemoved(new UnusedMethodsFinder(module, inlinedMethods, GetRemovedMethods()).Find(), "Inlined method");

protected MethodCollection GetRemovedMethods() {
Expand Down
54 changes: 54 additions & 0 deletions de4dot.code/deobfuscators/MasonProtector/AntiDebugKiller.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Collections.Generic;
using de4dot.blocks;
using de4dot.blocks.cflow;
using dnlib.DotNet;
using dnlib.DotNet.Emit;

namespace de4dot.code.deobfuscators.MasonProtector;

/// <summary>
/// (Applies to older versions of the protector only)<br/>
/// Removes "if (Debugger.IsAttached) Environment.Exit(-1) or Environment.FailFast("")" sequences.
/// This pass is mandatory because the presence of those instructions can otherwise hinder constant inlining when
/// they were placed in the middle of other code.
/// </summary>
class AntiDebugKiller : IBlocksDeobfuscator {
public bool ExecuteIfNotModified => true;

public void DeobfuscateBegin(Blocks blocks) {
}

public bool Deobfuscate(List<Block> allBlocks) {
foreach (var block in allBlocks) {
if (!block.IsConditionalBranch() || block.Instructions.Count < 2)
continue;
var instr = block.Instructions[block.Instructions.Count - 2];
if (instr.OpCode != OpCodes.Call)
continue;
if (instr.Operand is not IMethod { FullName: "System.Boolean System.Diagnostics.Debugger::get_IsAttached()" })
continue;

foreach (var tBlock in block.GetTargets()) {
if (tBlock.Instructions.Count < 2)
continue;
var exitCall = tBlock.Instructions[1];
if (exitCall.OpCode != OpCodes.Call)
continue;
if (exitCall.Operand is not IMethod method
|| (method.FullName != "System.Void System.Environment::Exit(System.Int32)"
&& method.FullName != "System.Void System.Environment::FailFast(System.String)"))
continue;
var firstInstr = tBlock.Instructions[0];
if (firstInstr.OpCode != OpCodes.Ldstr || (string)firstInstr.Operand != "")
if (!firstInstr.IsLdcI4() || firstInstr.GetLdcI4Value() != -1)
continue;

instr.Instruction.OpCode = OpCodes.Nop; // Nop IsAttached()
block.ReplaceBccWithBranch(tBlock == block.FallThrough);
return true;
}
}

return false;
}
}
39 changes: 39 additions & 0 deletions de4dot.code/deobfuscators/MasonProtector/AntiMiscKiller.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Linq;
using de4dot.blocks;
using dnlib.DotNet;
using dnlib.DotNet.Emit;

namespace de4dot.code.deobfuscators.MasonProtector;

public static class AntiMiscKiller {
static readonly string[] MasonCheck = new[] {
"System.Boolean System.Diagnostics.Debugger::get_IsAttached()",
"System.String System.Reflection.Assembly::get_Location()",
"System.Byte[] System.IO.File::ReadAllBytes(System.String)",
};

public static bool ShouldKill(MethodDef method) {
if (!method.IsStatic || !method.HasBody || method.Parameters.Count != 0)
return false;

var called = method.Body.Instructions
.Where(ins => ins.OpCode.Code is Code.Call or Code.Callvirt && ins.Operand is IMethod)
.Select(ins => ((IMethod)ins.Operand).FullName).ToHashSet();

if (called.IsSupersetOf(MasonCheck) && HasNativeCall(method)) {
return true;
}

if (DotNetUtils.CallsMethod(method, "System.Diagnostics.Process[] System.Diagnostics.Process::GetProcesses()")
&& DotNetUtils.HasString(method, "extremedumper")
&& HasNativeCall(method)) {
return true;
}

return false;
}

private static bool HasNativeCall(MethodDef method) => method.Body.Instructions.Any(ins =>
ins.OpCode == OpCodes.Call && ins.Operand is MethodDef { HasBody: true, IsStatic: true } md
&& md.Body.Instructions.Any(ins2 => ins2.OpCode == OpCodes.Calli));
}
Loading
Loading