Esempio n. 1
0
        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();
        }
Esempio n. 2
0
 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));
     }
 }
Esempio n. 3
0
 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());
     }
 }
Esempio n. 4
0
 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));
     }
 }
Esempio n. 5
0
        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));
                }
            }
        }
Esempio n. 6
0
        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();
        }
Esempio n. 7
0
        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);
            }
        }
Esempio n. 8
0
        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);
            }
        }
Esempio n. 9
0
        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();
        }
Esempio n. 10
0
        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);
        }
Esempio n. 11
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]);
        }
Esempio n. 12
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;
        }
Esempio n. 13
0
        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();
                    }
                }
            }
        }
Esempio n. 14
0
        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);
                }
            }
        }