/*
Copyright (C) 2011-2015 de4dot@gmail.com
This file is part of de4dot.
de4dot 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.
de4dot 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 de4dot. If not, see .
*/
using System.Collections.Generic;
using de4dot.blocks;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
namespace de4dot.code.deobfuscators {
public class PushedArgs {
List args;
int nextIndex;
public bool CanAddMore => nextIndex >= 0;
public int NumValidArgs => args.Count - (nextIndex + 1);
public PushedArgs(int numArgs) {
nextIndex = numArgs - 1;
args = new List(numArgs);
for (int i = 0; i < numArgs; i++)
args.Add(null);
}
public void Add(Instruction instr) => args[nextIndex--] = instr;
public void Set(int i, Instruction instr) => args[i] = instr;
internal void Pop() => args[++nextIndex] = null;
public Instruction Get(int i) {
if (0 <= i && i < args.Count)
return args[i];
return null;
}
public Instruction GetEnd(int i) => Get(args.Count - 1 - i);
}
public static class MethodStack {
// May not return all args. The args are returned in reverse order.
public static PushedArgs GetPushedArgInstructions(IList instructions, int index) {
try {
instructions[index].CalculateStackUsage(false, out int pushes, out int pops);
if (pops != -1)
return GetPushedArgInstructions(instructions, index, pops);
}
catch (System.NullReferenceException) {
// Here if eg. invalid metadata token in a call instruction (operand is null)
}
return new PushedArgs(0);
}
// May not return all args. The args are returned in reverse order.
static PushedArgs GetPushedArgInstructions(IList instructions, int index, int numArgs) {
var pushedArgs = new PushedArgs(numArgs);
if (!pushedArgs.CanAddMore) return pushedArgs;
Dictionary branches = null;
var states = new Stack();
var state = new State(index, null, 0, 0, 1, new HashSet());
var isBacktrack = false;
states.Push(state.Clone());
while (true) {
while (state.index >= 0) {
if (branches != null && branches.TryGetValue(state.index, out var branch) && state.visited.Add(state.index)) {
branch.current = 0;
var brState = state.Clone();
brState.branch = branch;
states.Push(brState);
}
if (!isBacktrack)
state.index--;
isBacktrack = false;
var update = UpdateState(instructions, state, pushedArgs);
if (update == Update.Finish)
return pushedArgs;
if (update == Update.Fail)
break;
}
if (states.Count == 0)
return pushedArgs;
var prevValidArgs = state.validArgs;
state = states.Pop();
if (state.validArgs < prevValidArgs)
for (int i = state.validArgs + 1; i <= prevValidArgs; i++)
pushedArgs.Pop();
if (branches == null)
branches = GetBranches(instructions);
else {
isBacktrack = true;
state.index = state.branch.Variants[state.branch.current++];
if (state.branch.current < state.branch.Variants.Count)
states.Push(state.Clone());
else
state.branch = null;
}
}
}
class Branch {
public int current;
public List Variants { get; }
public Branch() => Variants = new List();
}
class State {
public int index;
public Branch branch;
public int validArgs;
public int skipPushes;
public int addPushes;
public HashSet visited;
public State(int index, Branch branch, int validArgs, int skipPushes, int addPushes, HashSet visited) {
this.index = index;
this.branch = branch;
this.validArgs = validArgs;
this.skipPushes = skipPushes;
this.addPushes = addPushes;
this.visited = visited;
}
public State Clone() => new State(index, branch, validArgs, skipPushes, addPushes, new HashSet(visited));
}
enum Update { Ok, Fail, Finish };
private static Update UpdateState(IList instructions, State state, PushedArgs pushedArgs) {
if (state.index < 0 || state.index >= instructions.Count)
return Update.Fail;
var instr = instructions[state.index];
if (!Instr.IsFallThrough(instr.OpCode))
return Update.Fail;
instr.CalculateStackUsage(false, out int pushes, out int pops);
if (pops == -1)
return Update.Fail;
var isDup = instr.OpCode.Code == Code.Dup;
if (isDup) {
pushes = 1;
pops = 0;
}
if (pushes > 1)
return Update.Fail;
if (state.skipPushes > 0) {
state.skipPushes -= pushes;
if (state.skipPushes < 0)
return Update.Fail;
state.skipPushes += pops;
}
else {
if (pushes == 1) {
if (isDup)
state.addPushes++;
else {
for (; state.addPushes > 0; state.addPushes--) {
pushedArgs.Add(instr);
state.validArgs++;
if (!pushedArgs.CanAddMore)
return Update.Finish;
}
state.addPushes = 1;
}
}
state.skipPushes += pops;
}
return Update.Ok;
}
private static IList CacheInstructions = null;
private static Dictionary CacheBranches = null;
// cache last branches based on instructions object
private static Dictionary GetBranches(IList instructions) {
if (CacheInstructions == instructions) return CacheBranches;
CacheInstructions = instructions;
CacheBranches = new Dictionary();
for (int b = 0; b < instructions.Count; b++) {
var br = instructions[b];
if (br.Operand is Instruction target) {
var t = instructions.IndexOf(target);
if (!CacheBranches.TryGetValue(t, out var branch)) {
branch = new Branch();
CacheBranches.Add(t, branch);
}
branch.Variants.Add(b);
}
}
return CacheBranches;
}
public static TypeSig GetLoadedType(MethodDef method, IList instructions, int instrIndex) =>
GetLoadedType(method, instructions, instrIndex, 0, out bool wasNewobj);
public static TypeSig GetLoadedType(MethodDef method, IList instructions, int instrIndex, int argIndexFromEnd) =>
GetLoadedType(method, instructions, instrIndex, argIndexFromEnd, out bool wasNewobj);
public static TypeSig GetLoadedType(MethodDef method, IList instructions, int instrIndex, out bool wasNewobj) =>
GetLoadedType(method, instructions, instrIndex, 0, out wasNewobj);
public static TypeSig GetLoadedType(MethodDef method, IList instructions, int instrIndex, int argIndexFromEnd, out bool wasNewobj) {
wasNewobj = false;
var pushedArgs = MethodStack.GetPushedArgInstructions(instructions, instrIndex);
var pushInstr = pushedArgs.GetEnd(argIndexFromEnd);
if (pushInstr == null)
return null;
TypeSig type;
Local local;
var corLibTypes = method.DeclaringType.Module.CorLibTypes;
switch (pushInstr.OpCode.Code) {
case Code.Ldstr:
type = corLibTypes.String;
break;
case Code.Conv_I:
case Code.Conv_Ovf_I:
case Code.Conv_Ovf_I_Un:
type = corLibTypes.IntPtr;
break;
case Code.Conv_U:
case Code.Conv_Ovf_U:
case Code.Conv_Ovf_U_Un:
type = corLibTypes.UIntPtr;
break;
case Code.Conv_I8:
case Code.Conv_Ovf_I8:
case Code.Conv_Ovf_I8_Un:
type = corLibTypes.Int64;
break;
case Code.Conv_U8:
case Code.Conv_Ovf_U8:
case Code.Conv_Ovf_U8_Un:
type = corLibTypes.UInt64;
break;
case Code.Conv_R8:
case Code.Ldc_R8:
case Code.Ldelem_R8:
case Code.Ldind_R8:
type = corLibTypes.Double;
break;
case Code.Call:
case Code.Calli:
case Code.Callvirt:
var calledMethod = pushInstr.Operand as IMethod;
if (calledMethod == null)
return null;
type = calledMethod.MethodSig.GetRetType();
break;
case Code.Newarr:
var type2 = pushInstr.Operand as ITypeDefOrRef;
if (type2 == null)
return null;
type = new SZArraySig(type2.ToTypeSig());
wasNewobj = true;
break;
case Code.Newobj:
var ctor = pushInstr.Operand as IMethod;
if (ctor == null)
return null;
type = ctor.DeclaringType.ToTypeSig();
wasNewobj = true;
break;
case Code.Castclass:
case Code.Isinst:
case Code.Unbox_Any:
case Code.Ldelem:
case Code.Ldobj:
type = (pushInstr.Operand as ITypeDefOrRef).ToTypeSig();
break;
case Code.Ldarg:
case Code.Ldarg_S:
case Code.Ldarg_0:
case Code.Ldarg_1:
case Code.Ldarg_2:
case Code.Ldarg_3:
type = pushInstr.GetArgumentType(method.MethodSig, method.DeclaringType);
break;
case Code.Ldloc:
case Code.Ldloc_S:
case Code.Ldloc_0:
case Code.Ldloc_1:
case Code.Ldloc_2:
case Code.Ldloc_3:
local = pushInstr.GetLocal(method.Body.Variables);
if (local == null)
return null;
type = local.Type.RemovePinned();
break;
case Code.Ldloca:
case Code.Ldloca_S:
local = pushInstr.Operand as Local;
if (local == null)
return null;
type = CreateByRefType(local.Type.RemovePinned());
break;
case Code.Ldarga:
case Code.Ldarga_S:
type = CreateByRefType(pushInstr.GetArgumentType(method.MethodSig, method.DeclaringType));
break;
case Code.Ldfld:
case Code.Ldsfld:
var field = pushInstr.Operand as IField;
if (field == null || field.FieldSig == null)
return null;
type = field.FieldSig.GetFieldType();
break;
case Code.Ldflda:
case Code.Ldsflda:
var field2 = pushInstr.Operand as IField;
if (field2 == null || field2.FieldSig == null)
return null;
type = CreateByRefType(field2.FieldSig.GetFieldType());
break;
case Code.Ldelema:
case Code.Unbox:
type = CreateByRefType(pushInstr.Operand as ITypeDefOrRef);
break;
default:
return null;
}
return type;
}
static ByRefSig CreateByRefType(ITypeDefOrRef elementType) {
if (elementType == null)
return null;
return new ByRefSig(elementType.ToTypeSig());
}
static ByRefSig CreateByRefType(TypeSig elementType) {
if (elementType == null)
return null;
return new ByRefSig(elementType);
}
}
}