private static void WriteHeader(TextWriter writer, VirtualisedMethod method) { writer.WriteLine("// Export ID: " + (method.ExportId?.ToString() ?? "<none>")); var exportInfo = method.ExportInfo; if (exportInfo != null) { writer.WriteLine("// Raw function signature: "); writer.WriteLine("// Flags: 0x{0:X2} (0b{1})", exportInfo.Signature.Flags, Convert.ToString(exportInfo.Signature.Flags, 2).PadLeft(8, '0')); writer.WriteLine($"// Return Type: {exportInfo.Signature.ReturnToken}"); writer.WriteLine($"// Parameter Types: " + string.Join(", ", exportInfo.Signature.ParameterTokens)); } writer.WriteLine("// Inferred method signature: " + method.MethodSignature); writer.WriteLine("// Physical method: " + method.CallerMethod); writer.WriteLine("// Entrypoint Offset: " + method.Function.EntrypointAddress.ToString("X4")); writer.WriteLine("// Entrypoint Key: " + method.Function.EntryKey.ToString("X8")); writer.WriteLine(); writer.WriteLine("// Variables: "); foreach (var variable in method.ILCompilationUnit.Variables) { writer.WriteLine($"// {variable.Name}: {variable.VariableType} (assigned {variable.AssignedBy.Count}x, used {variable.UsedBy.Count}x)"); } writer.WriteLine(); }
private static void DumpCilAst(DevirtualisationContext context, VirtualisedMethod method, string suffix = null) { using (var fs = File.CreateText(Path.Combine(context.Options.OutputOptions.CilAstDumpsDirectory, $"function_{method.Function.EntrypointAddress:X4}{suffix}.dot"))) { WriteHeader(fs, method); var writer = new DotWriter(fs, new BasicBlockSerializer(method.CallerMethod.CilMethodBody)); writer.Write(method.ControlFlowGraph.ConvertToGraphViz(CilAstBlock.AstBlockProperty)); } }
private static void DumpILAstTree(DevirtualisationContext context, VirtualisedMethod method) { using (var fs = File.CreateText(Path.Combine(context.Options.OutputOptions.ILAstDumpsDirectory, $"function_{method.Function.EntrypointAddress:X4}_tree.dot"))) { WriteHeader(fs, method); var writer = new DotWriter(fs, new BasicBlockSerializer()); writer.Write(method.ILCompilationUnit.ConvertToGraphViz()); } }
private static void DumpControlFlowGraph(DevirtualisationContext context, VirtualisedMethod method) { using (var fs = File.CreateText(Path.Combine( context.Options.OutputOptions.ILDumpsDirectory, $"function_{method.Function.EntrypointAddress:X4}.dot"))) { var writer = new DotWriter(fs, new BasicBlockSerializer()); writer.Write(method.ControlFlowGraph.ConvertToGraphViz(ILBasicBlock.BasicBlockProperty)); } }
private static void DumpCil(DevirtualisationContext context, VirtualisedMethod method) { var methodBody = method.CallerMethod.CilMethodBody; var formatter = new CilInstructionFormatter(methodBody); using (var fs = File.CreateText(Path.Combine(context.Options.OutputOptions.CilDumpsDirectory, $"function_{method.Function.EntrypointAddress:X4}.il"))) { WriteBasicInfo(fs, method); // Dump variables. var variables = ((LocalVariableSignature)methodBody.Signature?.Signature)?.Variables ?? Array.Empty <VariableSignature>(); if (variables.Count > 0) { fs.WriteLine("// Variables: "); for (int i = 0; i < variables.Count; i++) { var variable = variables[i]; fs.WriteLine($"// {i}: {variable.VariableType}"); } fs.WriteLine(); } // Dump EHs. if (methodBody.ExceptionHandlers.Count > 0) { fs.WriteLine("// Exception handlers:"); for (int i = 0; i < methodBody.ExceptionHandlers.Count; i++) { var eh = methodBody.ExceptionHandlers[i]; fs.WriteLine( "// {0, 2}: EHType: {1, -10} TryStart: {2, -10} TryEnd: {3, -10} HandlerStart: {4, -10} HandlerEnd: {5, -10} FilterStart: {6, -10} CatchType: {7}", i.ToString(), eh.HandlerType, eh.TryStart != null ? $"IL_{eh.TryStart.Offset:X4}" : "<null>", eh.TryEnd != null ? $"IL_{eh.TryEnd.Offset:X4}" : "<null>", eh.HandlerStart != null ? $"IL_{eh.HandlerStart.Offset:X4}" : "<null>", eh.HandlerEnd != null ? $"IL_{eh.HandlerEnd.Offset:X4}" : "<null>", eh.FilterStart != null ? $"IL_{eh.FilterStart.Offset:X4}" : "<null>", eh.CatchType?.FullName ?? "<null>"); } fs.WriteLine(); } // Dump instructions. foreach (var instruction in methodBody.Instructions) { fs.WriteLine(formatter.FormatInstruction(instruction)); } } }
private static void WriteHeader(TextWriter writer, VirtualisedMethod method) { WriteBasicInfo(writer, method); writer.WriteLine("// Variables: "); foreach (var variable in method.CilCompilationUnit.Variables) { writer.WriteLine($"// {variable.Name}: {variable.VariableType} (assigned {variable.AssignedBy.Count}x, used {variable.UsedBy.Count}x)"); } writer.WriteLine(); }
private static void GenerateCil(DevirtualisationContext context, VirtualisedMethod method, TypeDefinition flagHelper) { // Generate final CIL code. context.Logger.Debug(Tag, $"Generating CIL for function_{method.Function.EntrypointAddress:X4}..."); var generator = new CilMethodBodyGenerator(context.Constants, flagHelper) { EnableStackVerification = !context.Options.EnableSalvageMode, EnableExceptionHandlerValidation = !context.Options.EnableSalvageMode }; method.CallerMethod.CilMethodBody = generator.Compile(method.CallerMethod, method.CilCompilationUnit); if (context.Options.OutputOptions.DumpRecompiledCil) { context.Logger.Log(Tag, $"Dumping CIL of function_{method.Function.EntrypointAddress:X4}..."); DumpCil(context, method); } }
private static void RecompileToCilAst(DevirtualisationContext context, VirtualisedMethod method) { context.Logger.Debug(Tag, $"Recompiling function_{method.Function.EntrypointAddress:X4} to CIL AST..."); var recompiler = new ILToCilRecompiler(method.CallerMethod.CilMethodBody, context.TargetModule, context) { Logger = context.Logger, InferParameterTypes = method.IsMethodSignatureInferred }; // Subscribe to progress events if specified in the options. if (context.Options.OutputOptions.DumpAllControlFlowGraphs) { int step = 1; recompiler.InitialAstBuilt += (sender, args) => { context.Logger.Debug2(Tag, $"Dumping initial CIL AST of function_{method.Function.EntrypointAddress:X4}..."); method.CilCompilationUnit = args; DumpCilAst(context, method, $" (0. Initial)"); }; recompiler.TransformEnd += (sender, args) => { context.Logger.Debug2(Tag, $"Dumping tentative CIL AST of function_{method.Function.EntrypointAddress:X4}..."); method.CilCompilationUnit = args.Unit; DumpCilAst(context, method, $" ({step++}. {args.Transform.Name})"); }; } // Recompile! method.CilCompilationUnit = recompiler.Recompile(method.ILCompilationUnit); // Dump AST if specified in the options. if (context.Options.OutputOptions.DumpControlFlowGraphs) { context.Logger.Log(Tag, $"Dumping CIL AST of function_{method.Function.EntrypointAddress:X4}..."); DumpCilAst(context, method); DumpCilAstTree(context, method); } }
private static void WriteBasicInfo(TextWriter writer, VirtualisedMethod method) { writer.WriteLine("// Export ID: " + (method.ExportId?.ToString() ?? "<none>")); var exportInfo = method.ExportInfo; if (exportInfo != null) { writer.WriteLine("// Raw function signature: "); writer.WriteLine("// Flags: 0x{0:X2} (0b{1})", exportInfo.Signature.Flags, Convert.ToString(exportInfo.Signature.Flags, 2).PadLeft(8, '0')); writer.WriteLine($"// Return Type: {exportInfo.Signature.ReturnToken}"); writer.WriteLine($"// Parameter Types: " + string.Join(", ", exportInfo.Signature.ParameterTokens)); } writer.WriteLine("// Inferred method signature: " + method.MethodSignature); writer.WriteLine("// Physical method: " + method.CallerMethod); writer.WriteLine("// Entrypoint Offset: " + method.Function.EntrypointAddress.ToString("X4")); writer.WriteLine("// Entrypoint Key: " + method.Function.EntryKey.ToString("X8")); writer.WriteLine(); }
private static void AddPhysicalMethod(DevirtualisationContext context, VirtualisedMethod method) { bool isHelperInit = method.ExportId == context.Constants.HelperInit; // Decide on a name of the new method. string name; if (!method.IsExport) { name = "__VMFUNCTION__" + method.Function.EntrypointAddress.ToString("X4"); } else if (isHelperInit) { name = "__VMHELPER_INIT__"; } else { name = "__VMEXPORT__" + method.ExportId; } // Create new physical method. var dummy = new MethodDefinition(name, MethodAttributes.Public, method.MethodSignature); dummy.IsStatic = !method.MethodSignature.HasThis; dummy.CilMethodBody = new CilMethodBody(dummy); method.CallerMethod = dummy; // We perform a heuristic analysis on all private members that are accessed in the code, as these are only // able to be accessed if the caller is in the same type. var inferredDeclaringType = TryInferDeclaringTypeFromMemberAccesses(context, method); // If that fails (e.g. there are no private member accesses), we check if the method is an instance member. // If it is, the this parameter is always the declaring type, EXCEPT for one scenario: // // When the method is intra-linked and only referenced through a LDFTN function, the this parameter type // can be inaccurate. KoiVM does not care for the type of the this object, as everything is done through // reflection, so it reuses the method signature for things like instance event handlers (object, EventArgs), // even though the hidden this parameter might have had a different type. if (inferredDeclaringType == null && !dummy.IsStatic) { inferredDeclaringType = TryInferDeclaringTypeFromThisParameter(context, dummy); } if (inferredDeclaringType != null) { // We found a declaring type! context.Logger.Debug(Tag, $"Inferred declaring type of function_{method.Function.EntrypointAddress:X4} ({inferredDeclaringType})."); // Remove this parameter from the method signature if necessary. if (!dummy.IsStatic) { dummy.Signature.Parameters.RemoveAt(0); } } else { // Fallback method: Add to <Module> and make static. context.Logger.Debug(Tag, isHelperInit ? $"Adding HELPER_INIT to <Module>." : $"Could not infer declaring type of function_{method.Function.EntrypointAddress:X4}. Adding to <Module> instead."); dummy.IsStatic = true; method.MethodSignature.HasThis = false; inferredDeclaringType = context.TargetImage.Assembly.Modules[0].TopLevelTypes[0]; } inferredDeclaringType.Methods.Add(dummy); }
private static TypeDefinition TryInferDeclaringTypeFromMemberAccesses( DevirtualisationContext context, VirtualisedMethod method) { // Get all private member accesses. var privateMemberRefs = new HashSet <IMemberReference>(); foreach (var instruction in method.Function.Instructions.Values) { IMemberProvider provider; var annotation = instruction.Annotation; switch (annotation) { case IMemberProvider p: provider = p; break; case CallAnnotation call: var resolvedMethod = context.ResolveMethod(call.Function.EntrypointAddress); if (resolvedMethod == null) { continue; } provider = new ECallAnnotation(resolvedMethod, VMECallOpCode.CALL); break; default: continue; } if (provider.Member.DeclaringType != null && provider.Member.DeclaringType.ResolutionScope.GetAssembly() == context.TargetImage.Assembly && provider.RequiresSpecialAccess) { privateMemberRefs.Add(provider.Member); } } var types = new List <TypeDefinition>(); foreach (var member in privateMemberRefs) { var memberDef = (IMemberDefinition)((IResolvable)member).Resolve(); var declaringTypes = GetDeclaringTypes(memberDef as TypeDefinition ?? memberDef.DeclaringType); types.Add(declaringTypes.First(t => memberDef.IsAccessibleFromType(t))); } if (types.Count == 0) { return(null); } types.Sort((a, b) => { if (a.IsAccessibleFromType(b)) { return(b.IsAccessibleFromType(a) ? 0 : 1); } else { return(b.IsAccessibleFromType(a) ? 0 : -1); } }); return(types[0]); }
private static TypeDefinition TryInferDeclaringTypeFromMemberAccesses( DevirtualisationContext context, VirtualisedMethod method) { // Get all private member accesses. var privateMemberRefs = new HashSet <IMemberReference>(); foreach (var instruction in method.Function.Instructions.Values) { IMemberProvider provider; var annotation = instruction.Annotation; switch (annotation) { case IMemberProvider p: provider = p; break; case CallAnnotation call: var resolvedMethod = context.ResolveMethod(call.Function.EntrypointAddress); if (resolvedMethod == null) { continue; } provider = new ECallAnnotation(resolvedMethod, VMECallOpCode.CALL); break; default: continue; } if (provider.Member.DeclaringType != null && provider.Member.DeclaringType.ResolutionScope.GetAssembly() == context.TargetImage.Assembly && provider.RequiresSpecialAccess) { privateMemberRefs.Add(provider.Member); } } var types = new List <TypeDefinition>(); foreach (var member in privateMemberRefs) { var memberDef = (IMemberDefinition)((IResolvable)member).Resolve(); var declaringTypes = GetDeclaringTypes(memberDef as TypeDefinition ?? memberDef.DeclaringType); types.Add(declaringTypes.First(t => memberDef.IsAccessibleFromType(t))); } if (types.Count == 0) { return(null); } types.Sort((a, b) => { if (a.IsAccessibleFromType(b)) { return(b.IsAccessibleFromType(a) ? 0 : 1); } else { return(b.IsAccessibleFromType(a) ? 0 : -1); } }); return(types[0]); // // Get all private member accesses. // var privateMemberRefs = method.Function.Instructions.Values // .Select(i => i.Annotation) // .OfType<IMemberProvider>() // .Where(a => a.Member.DeclaringType != null // && a.Member.DeclaringType.ResolutionScope == context.TargetImage.Assembly.Modules[0] // && a.RequiresSpecialAccess) // .Select(a => a.Member) // .Distinct() //#if DEBUG // .ToArray() //#endif // ; // // // Get all declaring type chains. // var declaringTypeChains = privateMemberRefs // .Select(m => GetDeclaringTypes((TypeDefinition) m.DeclaringType.Resolve())) // .ToArray(); // // TypeDefinition result = null; // // switch (declaringTypeChains.Length) // { // case 0: // break; // // case 1: // result = declaringTypeChains[0][0]; // break; // // default: // // Try find common declaring type. // result = GetCommonDeclaringType(declaringTypeChains); // if (result == null) // { // // If that does not work, try looking into base types instead. // var declaringTypes = privateMemberRefs // .Select(m => m.DeclaringType) // .ToArray(); // // var helper = new TypeHelper(context.ReferenceImporter); // var commonBaseType = helper.GetCommonBaseType(declaringTypes); // if (commonBaseType != null && // commonBaseType.ResolutionScope == context.TargetImage.Assembly.Modules[0]) // { // result = commonBaseType // .ToTypeDefOrRef() // .Resolve() as TypeDefinition; // } // } // // break; // } // // return result; }
private static void DumpDisassembledIL(DevirtualisationContext context, VirtualisedMethod method) { using (var fs = File.CreateText(Path.Combine( context.Options.OutputOptions.ILDumpsDirectory, $"function_{method.Function.EntrypointAddress:X4}.koi"))) { // Write basic information about export: fs.WriteLine("; Export ID: " + (method.ExportId?.ToString() ?? "<none>")); var exportInfo = method.ExportInfo; if (exportInfo != null) { fs.WriteLine("; Raw function signature: "); fs.WriteLine("; Flags: 0x{0:X2} (0b{1})", exportInfo.Signature.Flags, Convert.ToString(exportInfo.Signature.Flags, 2).PadLeft(8, '0')); fs.WriteLine($"; Return Type: {exportInfo.Signature.ReturnToken}"); fs.WriteLine($"; Parameter Types: " + string.Join(", ", exportInfo.Signature.ParameterTokens)); } fs.WriteLine("; Inferred method signature: " + method.MethodSignature); fs.WriteLine("; Physical method: " + method.CallerMethod); fs.WriteLine("; Entrypoint Offset: " + method.Function.EntrypointAddress.ToString("X4")); fs.WriteLine("; Entrypoint Key: " + method.Function.EntryKey.ToString("X8")); fs.WriteLine(); // Write contents of nodes. int instructionLength = -1; int annotationLength = -1; int stackLength = -1; int registersLength = -1; foreach (var node in method.ControlFlowGraph.Nodes) { node.UserData.TryGetValue(ILBasicBlock.BasicBlockProperty, out var b); if (b != null) { var block = (ILBasicBlock)b; foreach (var instruction in block.Instructions) { instructionLength = Math.Max(instruction.ToString().Length, instructionLength); annotationLength = Math.Max(instruction.Annotation?.ToString().Length ?? 0, annotationLength); stackLength = Math.Max(instruction.ProgramState.Stack.ToString().Length, stackLength); registersLength = Math.Max(instruction.ProgramState.Registers.ToString().Length, registersLength); } } } const int separatorLength = 3; instructionLength += separatorLength; annotationLength += separatorLength; stackLength += separatorLength; registersLength += separatorLength; fs.Write("; Instruction".PadRight(instructionLength)); fs.Write(" "); fs.Write("Annotation".PadRight(annotationLength)); fs.Write(" "); fs.Write("Stack".PadRight(stackLength)); fs.Write(" "); fs.Write("Registers".PadRight(registersLength)); fs.WriteLine(" EH stack"); foreach (var node in method.ControlFlowGraph.Nodes.OrderBy(x => x.Name)) { node.UserData.TryGetValue(ILBasicBlock.BasicBlockProperty, out var b); if (b == null) { fs.WriteLine("; <<< unknown >>> "); fs.WriteLine(); } else { var block = (ILBasicBlock)b; foreach (var instruction in block.Instructions) { fs.Write(instruction.ToString().PadRight(instructionLength)); fs.Write(" ; "); fs.Write((instruction.Annotation?.ToString() ?? string.Empty).PadRight(annotationLength)); fs.Write(" "); fs.Write(instruction.ProgramState.Stack.ToString().PadRight(stackLength)); fs.Write(" "); fs.Write(instruction.ProgramState.Registers.ToString().PadRight(registersLength)); fs.Write(" "); fs.WriteLine("{" + string.Join(", ", instruction.ProgramState.EHStack) + "}"); } fs.WriteLine(); } } } }
public void Run(DevirtualisationContext context) { var disassembler = new InferenceDisassembler(context.Constants, context.KoiStream) { Logger = context.Logger, FunctionFactory = new ExportsAwareFunctionFactory(context), SalvageCfgOnError = context.Options.EnableSalvageMode, ExitKeyResolver = new SimpleExitKeyBruteForce(), ResolveUnknownExitKeys = true }; // Register functions entry points. foreach (var method in context.VirtualisedMethods) { if (!method.ExportInfo.IsSignatureOnly && (!method.IsExport || context.Options.SelectedExports.Contains(method.ExportId.Value, method.ExportInfo))) { disassembler.AddFunction(method.Function); } } // Listen for new explored functions. var newFunctions = new Dictionary <uint, VMFunction>(); disassembler.FunctionInferred += (sender, args) => { var method = context.ResolveMethod(args.Function.EntrypointAddress); if (method == null) { newFunctions.Add(args.Function.EntrypointAddress, args.Function); } }; // Disassemble! var controlFlowGraphs = disassembler.DisassembleFunctions(); foreach (var entry in controlFlowGraphs) { VirtualisedMethod method; if (newFunctions.ContainsKey(entry.Key)) { context.Logger.Debug(Tag, $"Creating method for function_{entry.Key:X4}."); method = new VirtualisedMethod(newFunctions[entry.Key]); context.VirtualisedMethods.Add(method); } else { method = context.VirtualisedMethods.First(x => x.Function.EntrypointAddress == entry.Key); } method.ControlFlowGraph = entry.Value; if (context.Options.OutputOptions.DumpDisassembledIL) { context.Logger.Log(Tag, $"Dumping IL of function_{entry.Key:X4}..."); DumpDisassembledIL(context, method); } if (context.Options.OutputOptions.DumpControlFlowGraphs) { context.Logger.Log(Tag, $"Dumping CFG of function_{entry.Key:X4}..."); DumpControlFlowGraph(context, method); } } }