/*
Copyright (C) 2011-2012 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 Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Metadata;
using de4dot.blocks;
namespace de4dot.code.deobfuscators {
// Restore the type of all fields / parameters that have had their type turned into object.
// This thing requires a lot more code than I have time to do now (similar to symbol renaming)
// so it will be a basic implementation only.
class TypesRestorer {
ModuleDefinition module;
List allMethods;
Dictionary> argInfos = new Dictionary>();
TypeInfo methodReturnInfo;
Dictionary> fieldWrites = new Dictionary>();
Dictionary updatedMethods = new Dictionary();
Dictionary updatedFields = new Dictionary();
class UpdatedMethod {
public int token;
public TypeReference[] newArgTypes;
public TypeReference newReturnType;
public UpdatedMethod(MethodDefinition method) {
token = method.MetadataToken.ToInt32();
newArgTypes = new TypeReference[DotNetUtils.getArgsCount(method)];
}
}
class UpdatedField {
public int token;
public TypeReference newFieldType;
public UpdatedField(FieldDefinition field) {
token = field.MetadataToken.ToInt32();
}
}
class TypeInfo {
Dictionary types = new Dictionary();
public TypeReference newType = null;
public T arg;
bool newobjTypes;
public Dictionary Types {
get { return types; }
}
public TypeInfo(T arg) {
this.arg = arg;
}
public void add(TypeReference type, bool wasNewobj = false) {
if (wasNewobj) {
if (!newobjTypes)
clear();
newobjTypes = true;
}
else if (newobjTypes)
return;
types[new TypeReferenceKey(type)] = true;
}
public void clear() {
types.Clear();
}
public bool updateNewType(ModuleDefinition module) {
if (types.Count == 0)
return false;
TypeReference theNewType = null;
foreach (var key in types.Keys) {
if (theNewType == null) {
theNewType = key.TypeReference;
continue;
}
theNewType = getCommonBaseClass(module, theNewType, key.TypeReference);
if (theNewType == null)
break;
}
if (theNewType == null)
return false;
if (MemberReferenceHelper.compareTypes(theNewType, newType))
return false;
newType = theNewType;
return true;
}
}
public TypesRestorer(ModuleDefinition module) {
this.module = module;
}
UpdatedMethod getUpdatedMethod(MethodDefinition method) {
int token = method.MetadataToken.ToInt32();
UpdatedMethod updatedMethod;
if (updatedMethods.TryGetValue(token, out updatedMethod))
return updatedMethod;
return updatedMethods[token] = new UpdatedMethod(method);
}
UpdatedField getUpdatedField(FieldDefinition field) {
int token = field.MetadataToken.ToInt32();
UpdatedField updatedField;
if (updatedFields.TryGetValue(token, out updatedField))
return updatedField;
return updatedFields[token] = new UpdatedField(field);
}
public void deobfuscate() {
allMethods = new List();
foreach (var type in module.GetTypes())
allMethods.AddRange(type.Methods);
foreach (var type in module.GetTypes()) {
foreach (var field in type.Fields) {
if (!MemberReferenceHelper.isSystemObject(field.FieldType))
continue;
var key = new FieldReferenceAndDeclaringTypeKey(field);
fieldWrites[key] = new TypeInfo(field);
}
}
for (int i = 0; i < 10; i++) {
bool changed = false;
changed |= deobfuscateFields();
changed |= deobfuscateMethods();
if (!changed)
break;
}
var fields = new List(updatedFields.Values);
if (fields.Count > 0) {
Log.v("Changing field types from object -> real type");
fields.Sort((a, b) => Utils.compareInt32(a.token, b.token));
Log.indent();
foreach (var updatedField in fields)
Log.v("Field {0:X8}: type {1} ({2:X8})", updatedField.token, Utils.removeNewlines(updatedField.newFieldType.FullName), updatedField.newFieldType.MetadataToken.ToInt32());
Log.deIndent();
}
var methods = new List(updatedMethods.Values);
if (methods.Count > 0) {
Log.v("Changing method args and return types from object -> real type");
methods.Sort((a, b) => Utils.compareInt32(a.token, b.token));
Log.indent();
foreach (var updatedMethod in methods) {
Log.v("Method {0:X8}", updatedMethod.token);
Log.indent();
if (updatedMethod.newReturnType != null)
Log.v("ret: {0} ({1:X8})", Utils.removeNewlines(updatedMethod.newReturnType.FullName), updatedMethod.newReturnType.MetadataToken.ToInt32());
for (int i = 0; i < updatedMethod.newArgTypes.Length; i++) {
var updatedArg = updatedMethod.newArgTypes[i];
if (updatedArg == null)
continue;
Log.v("arg {0}: {1} ({2:X8})", i, Utils.removeNewlines(updatedArg.FullName), updatedArg.MetadataToken.ToInt32());
}
Log.deIndent();
}
Log.deIndent();
}
}
bool deobfuscateMethods() {
bool changed = false;
foreach (var method in allMethods) {
methodReturnInfo = new TypeInfo(method.MethodReturnType.Parameter2);
deobfuscateMethod(method);
if (methodReturnInfo.updateNewType(module)) {
getUpdatedMethod(method).newReturnType = methodReturnInfo.newType;
method.MethodReturnType.ReturnType = methodReturnInfo.newType;
changed = true;
}
foreach (var info in argInfos.Values) {
if (info.updateNewType(module)) {
getUpdatedMethod(method).newArgTypes[DotNetUtils.getArgIndex(info.arg)] = info.newType;
info.arg.ParameterType = info.newType;
changed = true;
}
}
}
return changed;
}
static int sortTypeInfos(TypeInfo a, TypeInfo b) {
if (a.arg.Method.MetadataToken.ToInt32() < b.arg.Method.MetadataToken.ToInt32()) return -1;
if (a.arg.Method.MetadataToken.ToInt32() > b.arg.Method.MetadataToken.ToInt32()) return 1;
return Utils.compareInt32(a.arg.Sequence, b.arg.Sequence);
}
class PushedArgs {
List args;
int nextIndex;
public bool CanAddMore {
get { return nextIndex >= 0; }
}
public int NumValidArgs {
get { return 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;
}
public Instruction get(int i) {
if (0 <= i && i < args.Count)
return args[i];
return null;
}
public Instruction getEnd(int i) {
return get(args.Count - 1 - i);
}
public void fixDups() {
Instruction prev = null, instr;
for (int i = 0; i < NumValidArgs; i++, prev = instr) {
instr = args[i];
if (instr == null || prev == null)
continue;
if (instr.OpCode.Code != Code.Dup)
continue;
args[i] = prev;
instr = prev;
}
}
}
void deobfuscateMethod(MethodDefinition method) {
if (!method.IsStatic || method.Body == null)
return;
bool fixReturnType = MemberReferenceHelper.isSystemObject(method.MethodReturnType.ReturnType);
argInfos.Clear();
foreach (var arg in method.Parameters) {
if (arg.ParameterType == null || arg.ParameterType.IsValueType)
continue;
if (!MemberReferenceHelper.isSystemObject(arg.ParameterType))
continue;
argInfos[arg] = new TypeInfo(arg);
}
if (argInfos.Count == 0 && !fixReturnType)
return;
var methodParams = DotNetUtils.getParameters(method);
PushedArgs pushedArgs;
var instructions = method.Body.Instructions;
for (int i = 0; i < instructions.Count; i++) {
var instr = instructions[i];
switch (instr.OpCode.Code) {
case Code.Ret:
if (!fixReturnType)
break;
bool wasNewobj;
var type = getLoadedType(method, instructions, i, out wasNewobj);
if (type == null)
break;
methodReturnInfo.add(type);
break;
case Code.Call:
case Code.Calli:
case Code.Callvirt:
case Code.Newobj:
pushedArgs = getPushedArgInstructions(instructions, i);
var calledMethod = instr.Operand as MethodReference;
if (calledMethod == null)
break;
var calledMethodParams = DotNetUtils.getParameters(calledMethod);
for (int j = 0; j < pushedArgs.NumValidArgs; j++) {
int calledMethodParamIndex = calledMethodParams.Count - j - 1;
var ldInstr = pushedArgs.getEnd(j);
switch (ldInstr.OpCode.Code) {
case Code.Ldarg:
case Code.Ldarg_S:
case Code.Ldarg_0:
case Code.Ldarg_1:
case Code.Ldarg_2:
case Code.Ldarg_3:
addMethodArgType(getParameter(methodParams, ldInstr), DotNetUtils.getParameter(calledMethodParams, calledMethodParamIndex));
break;
default:
break;
}
}
break;
case Code.Castclass:
pushedArgs = getPushedArgInstructions(instructions, i);
if (pushedArgs.NumValidArgs < 1)
break;
addMethodArgType(getParameter(methodParams, pushedArgs.getEnd(0)), instr.Operand as TypeReference);
break;
case Code.Stloc:
case Code.Stloc_S:
case Code.Stloc_0:
case Code.Stloc_1:
case Code.Stloc_2:
case Code.Stloc_3:
pushedArgs = getPushedArgInstructions(instructions, i);
if (pushedArgs.NumValidArgs < 1)
break;
addMethodArgType(getParameter(methodParams, pushedArgs.getEnd(0)), DotNetUtils.getLocalVar(method.Body.Variables, instr));
break;
case Code.Stsfld:
pushedArgs = getPushedArgInstructions(instructions, i);
if (pushedArgs.NumValidArgs < 1)
break;
addMethodArgType(getParameter(methodParams, pushedArgs.getEnd(0)), instr.Operand as FieldReference);
break;
case Code.Stfld:
pushedArgs = getPushedArgInstructions(instructions, i);
if (pushedArgs.NumValidArgs >= 1) {
var field = instr.Operand as FieldReference;
addMethodArgType(getParameter(methodParams, pushedArgs.getEnd(0)), field);
if (pushedArgs.NumValidArgs >= 2 && field != null)
addMethodArgType(getParameter(methodParams, pushedArgs.getEnd(1)), field.DeclaringType);
}
break;
case Code.Ldfld:
case Code.Ldflda:
pushedArgs = getPushedArgInstructions(instructions, i);
if (pushedArgs.NumValidArgs < 1)
break;
addMethodArgType(getParameter(methodParams, pushedArgs.getEnd(0)), instr.Operand as FieldReference);
break;
//TODO: For better results, these should be checked:
case Code.Starg:
case Code.Starg_S:
case Code.Ldelema:
case Code.Ldelem_Any:
case Code.Ldelem_I:
case Code.Ldelem_I1:
case Code.Ldelem_I2:
case Code.Ldelem_I4:
case Code.Ldelem_I8:
case Code.Ldelem_R4:
case Code.Ldelem_R8:
case Code.Ldelem_Ref:
case Code.Ldelem_U1:
case Code.Ldelem_U2:
case Code.Ldelem_U4:
case Code.Ldind_I:
case Code.Ldind_I1:
case Code.Ldind_I2:
case Code.Ldind_I4:
case Code.Ldind_I8:
case Code.Ldind_R4:
case Code.Ldind_R8:
case Code.Ldind_Ref:
case Code.Ldind_U1:
case Code.Ldind_U2:
case Code.Ldind_U4:
case Code.Ldobj:
case Code.Stelem_Any:
case Code.Stelem_I:
case Code.Stelem_I1:
case Code.Stelem_I2:
case Code.Stelem_I4:
case Code.Stelem_I8:
case Code.Stelem_R4:
case Code.Stelem_R8:
case Code.Stelem_Ref:
case Code.Stind_I:
case Code.Stind_I1:
case Code.Stind_I2:
case Code.Stind_I4:
case Code.Stind_I8:
case Code.Stind_R4:
case Code.Stind_R8:
case Code.Stind_Ref:
case Code.Stobj:
default:
break;
}
}
}
static ParameterDefinition getParameter(IList parameters, Instruction instr) {
switch (instr.OpCode.Code) {
case Code.Ldarg:
case Code.Ldarg_S:
case Code.Ldarg_0:
case Code.Ldarg_1:
case Code.Ldarg_2:
case Code.Ldarg_3:
return DotNetUtils.getParameter(parameters, instr);
default:
return null;
}
}
bool addMethodArgType(ParameterDefinition methodParam, FieldReference field) {
if (field == null)
return false;
return addMethodArgType(methodParam, field.FieldType);
}
bool addMethodArgType(ParameterDefinition methodParam, VariableDefinition otherLocal) {
if (otherLocal == null)
return false;
return addMethodArgType(methodParam, otherLocal.VariableType);
}
bool addMethodArgType(ParameterDefinition methodParam, ParameterDefinition otherParam) {
if (otherParam == null)
return false;
return addMethodArgType(methodParam, otherParam.ParameterType);
}
bool addMethodArgType(ParameterDefinition methodParam, TypeReference type) {
if (methodParam == null || type == null)
return false;
if (!isValidType(type))
return false;
TypeInfo info;
if (!argInfos.TryGetValue(methodParam, out info))
return false;
if (info.Types.ContainsKey(new TypeReferenceKey(type)))
return false;
info.add(type);
return true;
}
// May not return all args. The args are returned in reverse order.
PushedArgs getPushedArgInstructions(IList instructions, int index) {
try {
int pushes, pops;
DotNetUtils.calculateStackUsage(instructions[index], false, out pushes, out 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.
PushedArgs getPushedArgInstructions(IList instructions, int index, int numArgs) {
var pushedArgs = new PushedArgs(numArgs);
Instruction instr;
int skipPushes = 0;
while (index >= 0 && pushedArgs.CanAddMore) {
instr = getPreviousInstruction(instructions, ref index);
if (instr == null)
break;
int pushes, pops;
DotNetUtils.calculateStackUsage(instr, false, out pushes, out pops);
if (pops == -1)
break;
if (instr.OpCode.Code == Code.Dup) {
pushes = 1;
pops = 0;
}
if (pushes > 1)
break;
if (skipPushes > 0) {
skipPushes -= pushes;
if (skipPushes < 0)
break;
skipPushes += pops;
}
else {
if (pushes == 1)
pushedArgs.add(instr);
skipPushes += pops;
}
}
instr = pushedArgs.get(0);
if (instr != null && instr.OpCode.Code == Code.Dup) {
instr = getPreviousInstruction(instructions, ref index);
if (instr != null) {
int pushes, pops;
DotNetUtils.calculateStackUsage(instr, false, out pushes, out pops);
if (pushes == 1 && pops == 0)
pushedArgs.set(0, instr);
}
}
pushedArgs.fixDups();
return pushedArgs;
}
bool deobfuscateFields() {
foreach (var info in fieldWrites.Values)
info.clear();
foreach (var method in allMethods) {
if (method.Body == null)
continue;
var instructions = method.Body.Instructions;
for (int i = 0; i < instructions.Count; i++) {
var instr = instructions[i];
TypeReference fieldType = null;
TypeInfo info = null;
FieldReference field;
switch (instr.OpCode.Code) {
case Code.Stfld:
case Code.Stsfld:
field = instr.Operand as FieldReference;
if (field == null)
continue;
if (!fieldWrites.TryGetValue(new FieldReferenceAndDeclaringTypeKey(field), out info))
continue;
bool wasNewobj;
fieldType = getLoadedType(method, instructions, i, out wasNewobj);
if (fieldType == null)
continue;
info.add(fieldType, wasNewobj);
break;
case Code.Call:
case Code.Calli:
case Code.Callvirt:
case Code.Newobj:
var pushedArgs = getPushedArgInstructions(instructions, i);
var calledMethod = instr.Operand as MethodReference;
if (calledMethod == null)
continue;
IList calledMethodArgs = DotNetUtils.getArgs(calledMethod);
calledMethodArgs = DotNetUtils.replaceGenericParameters(calledMethod.DeclaringType as GenericInstanceType, calledMethod as GenericInstanceMethod, calledMethodArgs);
for (int j = 0; j < pushedArgs.NumValidArgs; j++) {
var pushInstr = pushedArgs.getEnd(j);
if (pushInstr.OpCode.Code != Code.Ldfld && pushInstr.OpCode.Code != Code.Ldsfld)
continue;
field = pushInstr.Operand as FieldReference;
if (field == null)
continue;
if (!fieldWrites.TryGetValue(new FieldReferenceAndDeclaringTypeKey(field), out info))
continue;
fieldType = calledMethodArgs[calledMethodArgs.Count - 1 - j];
if (!isValidType(fieldType))
continue;
info.add(fieldType);
}
break;
default:
continue;
}
}
}
bool changed = false;
var removeThese = new List();
foreach (var info in fieldWrites.Values) {
if (info.updateNewType(module)) {
removeThese.Add(info.arg);
getUpdatedField(info.arg).newFieldType = info.newType;
info.arg.FieldType = info.newType;
changed = true;
}
}
foreach (var field in removeThese)
fieldWrites.Remove(new FieldReferenceAndDeclaringTypeKey(field));
return changed;
}
TypeReference getLoadedType(MethodDefinition method, IList instructions, int instrIndex, out bool wasNewobj) {
wasNewobj = false;
var pushedArgs = getPushedArgInstructions(instructions, instrIndex);
var pushInstr = pushedArgs.getEnd(0);
if (pushInstr == null)
return null;
TypeReference fieldType;
switch (pushInstr.OpCode.Code) {
case Code.Ldstr:
fieldType = module.TypeSystem.String;
break;
case Code.Call:
case Code.Calli:
case Code.Callvirt:
var calledMethod = pushInstr.Operand as MethodReference;
if (calledMethod == null)
return null;
fieldType = calledMethod.MethodReturnType.ReturnType;
break;
case Code.Newarr:
fieldType = pushInstr.Operand as TypeReference;
if (fieldType == null)
return null;
fieldType = new ArrayType(fieldType);
wasNewobj = true;
break;
case Code.Newobj:
var ctor = pushInstr.Operand as MethodReference;
if (ctor == null)
return null;
fieldType = ctor.DeclaringType;
wasNewobj = true;
break;
case Code.Castclass:
case Code.Isinst:
fieldType = pushInstr.Operand as TypeReference;
break;
case Code.Ldarg:
case Code.Ldarg_S:
case Code.Ldarg_0:
case Code.Ldarg_1:
case Code.Ldarg_2:
case Code.Ldarg_3:
fieldType = DotNetUtils.getArgType(method, pushInstr);
break;
case Code.Ldloc:
case Code.Ldloc_S:
case Code.Ldloc_0:
case Code.Ldloc_1:
case Code.Ldloc_2:
case Code.Ldloc_3:
var local = DotNetUtils.getLocalVar(method.Body.Variables, pushInstr);
if (local == null)
return null;
fieldType = local.VariableType;
break;
case Code.Ldfld:
case Code.Ldsfld:
var field2 = pushInstr.Operand as FieldReference;
if (field2 == null)
return null;
fieldType = field2.FieldType;
break;
default:
return null;
}
if (!isValidType(fieldType))
return null;
return fieldType;
}
static bool isValidType(TypeReference type) {
if (type == null)
return false;
if (type.IsValueType)
return false;
if (MemberReferenceHelper.isSystemObject(type))
return false;
if (type.EType == ElementType.Void)
return false;
while (type != null) {
switch (MemberReferenceHelper.getMemberReferenceType(type)) {
case CecilType.ArrayType:
case CecilType.GenericInstanceType:
case CecilType.PointerType:
case CecilType.TypeDefinition:
case CecilType.TypeReference:
break;
case CecilType.ByReferenceType:
case CecilType.FunctionPointerType:
case CecilType.GenericParameter:
case CecilType.OptionalModifierType:
case CecilType.PinnedType:
case CecilType.RequiredModifierType:
case CecilType.SentinelType:
default:
return false;
}
if (!(type is TypeSpecification))
break;
type = ((TypeSpecification)type).ElementType;
}
return type != null;
}
static TypeReference getCommonBaseClass(ModuleDefinition module, TypeReference a, TypeReference b) {
if (DotNetUtils.isDelegate(a) && DotNetUtils.derivesFromDelegate(DotNetUtils.getType(module, b)))
return b;
if (DotNetUtils.isDelegate(b) && DotNetUtils.derivesFromDelegate(DotNetUtils.getType(module, a)))
return a;
return null; //TODO:
}
static Instruction getPreviousInstruction(IList instructions, ref int instrIndex) {
while (true) {
instrIndex--;
if (instrIndex < 0)
return null;
var instr = instructions[instrIndex];
if (instr.OpCode.Code == Code.Nop)
continue;
if (instr.OpCode.OpCodeType == OpCodeType.Prefix)
continue;
switch (instr.OpCode.FlowControl) {
case FlowControl.Next:
case FlowControl.Call:
return instr;
default:
return null;
}
}
}
}
}