Beispiel #1
0
        private DevirtualisationContext CreateDevirtualisationContext(DevirtualisationOptions options)
        {
            // Open target file.
            Logger.Log(Tag, $"Opening target file {options.InputFile}...");
            var assembly = WindowsAssembly.FromFile(options.InputFile);
            var header   = assembly.NetDirectory?.MetadataHeader;

            if (header == null)
            {
                throw new BadImageFormatException("Assembly does not contain a valid .NET header.");
            }

            // Hook into md stream parser.
            var parser = new KoiVmAwareStreamParser(options.KoiStreamName, Logger);

            if (options.OverrideKoiStreamData)
            {
                string path = Path.IsPathRooted(options.KoiStreamDataFile)
                    ? options.KoiStreamDataFile
                    : Path.Combine(Path.GetDirectoryName(options.InputFile), options.KoiStreamDataFile);

                Logger.Log(Tag, $"Opening external Koi stream data file {path}...");
                parser.ReplacementData = File.ReadAllBytes(path);

                var streamHeader = header.StreamHeaders.FirstOrDefault(h => h.Name == options.KoiStreamName);
                if (streamHeader == null)
                {
                    streamHeader = new MetadataStreamHeader(options.KoiStreamName)
                    {
                        Stream = KoiStream.FromReadingContext(
                            new ReadingContext()
                        {
                            Reader = new MemoryStreamReader(parser.ReplacementData)
                        }, Logger)
                    };
                    header.StreamHeaders.Add(streamHeader);
                }
            }

            header.StreamParser = parser;

            // Ignore invalid / encrypted method bodies when specified.
            if (options.IgnoreInvalidMethodBodies)
            {
                var table = header.GetStream <TableStream>().GetTable <MethodDefinitionTable>();
                table.MethodBodyReader = new DefaultMethodBodyReader {
                    ThrowOnInvalidMethodBody = false
                };
            }

            // Lock image and set custom md resolver.
            var image = header.LockMetadata();

            image.MetadataResolver = new DefaultMetadataResolver(
                new DefaultNetAssemblyResolver(Path.GetDirectoryName(options.InputFile)));

            var runtimeImage = ResolveRuntimeImage(options, image);

            return(new DevirtualisationContext(options, image, runtimeImage, Logger));
        }
Beispiel #2
0
 public InferenceDisassembler(VMConstants constants, KoiStream koiStream)
 {
     Constants   = constants ?? throw new ArgumentNullException(nameof(constants));
     KoiStream   = koiStream ?? throw new ArgumentNullException(nameof(koiStream));
     _decoder    = new InstructionDecoder(constants, KoiStream.Contents.CreateReader());
     _processor  = new InstructionProcessor(this);
     _cfgBuilder = new ControlFlowGraphBuilder();
 }
Beispiel #3
0
        private DevirtualisationContext CreateDevirtualisationContext(DevirtualisationOptions options)
        {
            string workingDirectory = Path.GetDirectoryName(options.InputFile);

            // Open target PE.
            Logger.Log(Tag, $"Opening target file {options.InputFile}...");
            var peFile = PEFile.FromFile(options.InputFile);

            // Create #Koi stream aware metadata reader.
            var streamReader = options.OverrideKoiStreamData
                ? new DefaultMetadataStreamReader()
                : (IMetadataStreamReader) new KoiVmAwareStreamReader(options.KoiStreamName, Logger);

            var peImage = PEImage.FromFile(peFile, new PEReaderParameters
            {
                MetadataStreamReader = streamReader
            });

            var metadata = peImage.DotNetDirectory?.Metadata;

            if (metadata is null)
            {
                throw new BadImageFormatException("Assembly does not contain a valid .NET header.");
            }

            // If custom koi stream data was provided, inject it.
            KoiStream koiStream;

            if (!options.OverrideKoiStreamData)
            {
                koiStream = metadata.GetStream <KoiStream>() ?? throw new DevirtualisationException(
                                      "Koi stream was not found in the target PE. This could be because the input file is " +
                                      "not protected with KoiVM, or the metadata stream uses a name that is different " +
                                      "from the one specified in the input parameters.");
            }
            else
            {
                string path = Path.IsPathRooted(options.KoiStreamDataFile)
                    ? options.KoiStreamDataFile
                    : Path.Combine(workingDirectory, options.KoiStreamDataFile);

                Logger.Log(Tag, $"Opening external Koi stream data file {path}...");
                var contents = File.ReadAllBytes(path);

                // Replace original koi stream if it existed.
                koiStream = new KoiStream(options.KoiStreamName, new DataSegment(contents), Logger);
            }

            // Ignore invalid / encrypted method bodies when specified.
            var moduleReadParameters = new ModuleReaderParameters(workingDirectory,
                                                                  options.IgnoreInvalidMD ? (IErrorListener)ThrowErrorListener.Instance : EmptyErrorListener.Instance);

            var module        = ModuleDefinition.FromImage(peImage, moduleReadParameters);
            var runtimeModule = ResolveRuntimeModule(options, module);

            koiStream.ResolutionContext = module;
            return(new DevirtualisationContext(options, module, runtimeModule, koiStream, Logger));
        }
Beispiel #4
0
        public uint?ResolveExitKey(ILogger logger, KoiStream koiStream, VMConstants constants, VMFunction function)
        {
            // Strategy:
            //
            // Find CALL references to the function, and try every possible key in the key space that
            // decrypts the next instruction to either a PUSHR_xxxx R0 or a PUSHR_DWORD SP, as they
            // appear in the post-call to either store the return value, or adjust SP to clean up
            // the stack.

            // Since the actual decryption of the opcode only uses the 8 least significant bits, we can
            // make an optimisation that shaves off around 8 bits of the key space. By attempting to
            // decrypt the first byte using just the first 8 bits, and verifying whether this output
            // results to one of the instructions mentioned in the above, we can quickly rule out many
            // potential keys.

            var callReferences = function.References.Where(r => r.ReferenceType == FunctionReferenceType.Call).ToArray();

            if (callReferences.Length == 0)
            {
                logger.Warning(Tag, $"Cannot brute-force the exit key of function_{function.EntrypointAddress:X4} as it has no recorded call references.");
                return(null);
            }

            var data = koiStream.Data;

            // Find any call reference.
            foreach (var callReference in callReferences)
            {
                var  call         = callReference.Caller.Instructions[callReference.Offset];
                long targetOffset = call.Offset + call.Size;

                // Go over all possible LSBs.
                for (uint lsb = 0; lsb < byte.MaxValue; lsb++)
                {
                    // Check whether the LSB decodes to a PUSHR_xxxx.
                    if (IsPotentialLSB(constants, data[targetOffset], lsb))
                    {
                        // Go over all remaining 24 bits.
                        for (uint i = 0; i < 0x00FFFFFF; i++)
                        {
                            uint currentKey = (i << 8) | lsb;

                            // Try new key.
                            if (IsValidKey(constants, data, targetOffset, currentKey))
                            {
                                // We have found a key!
                                return(currentKey);
                            }
                        } // for all other 24 bits.
                    }     // if potential LSB
                }         // foreach LSB
            }             // foreach call reference

            return(null);
        }
        public DevirtualisationContext(DevirtualisationOptions options, ModuleDefinition targetModule,
                                       ModuleDefinition runtimeModule, KoiStream koiStream, ILogger logger)
        {
            Options       = options ?? throw new ArgumentNullException(nameof(options));
            TargetModule  = targetModule ?? throw new ArgumentNullException(nameof(targetModule));
            RuntimeModule = runtimeModule ?? throw new ArgumentNullException(nameof(runtimeModule));
            KoiStream     = koiStream ?? throw new ArgumentNullException(nameof(koiStream));
            Logger        = logger ?? throw new ArgumentNullException(nameof(logger));

            ReferenceImporter = new ReferenceImporter(targetModule);
        }
        public MetadataStream ReadStream(string streamName, ReadingContext context)
        {
            if (streamName != KoiStreamName)
            {
                return(_parser.ReadStream(streamName, context));
            }

            if (ReplacementData != null)
            {
                context = new ReadingContext()
                {
                    Reader = new MemoryStreamReader(ReplacementData)
                };
            }

            return(KoiStream.FromReadingContext(context, Logger));
        }
        public uint?ResolveExitKey(ILogger logger, KoiStream koiStream, VMConstants constants, VMFunction function)
        {
            // Strategy:
            //
            // Find CALL references to the function, and try every possible key in the key space that
            // decrypts the next instruction to either a PUSHR_xxxx R0 or a PUSHR_DWORD SP, as they
            // appear in the post-call to either store the return value, or adjust SP to clean up
            // the stack.

            // Since the actual decryption of the opcode only uses the 8 least significant bits, we can
            // make an optimisation that shaves off around 8 bits of the key space. By attempting to
            // decrypt the first byte using just the first 8 bits, and verifying whether this output
            // results to one of the instructions mentioned in the above, we can quickly rule out many
            // potential keys.

            var callReferences = function.References
                                 .Where(r => r.ReferenceType == FunctionReferenceType.Call)
                                 .ToArray();

            if (callReferences.Length == 0)
            {
                logger.Warning(Tag, $"Cannot brute-force the exit key of function_{function.EntrypointAddress:X4} as it has no recorded call references.");
                return(null);
            }

            var reader = koiStream.Contents.CreateReader();

            byte[] encryptedOpCodes = new byte[3];
            var    watch            = new Stopwatch();

            // Find any call reference.
            for (int i = 0; i < callReferences.Length; i++)
            {
                var callReference = callReferences[i];
                logger.Debug(Tag, $"Started bruteforcing key for call reference {i.ToString()} ({callReference.ToString()}).");
                watch.Restart();

                var  call         = callReference.Caller.Instructions[callReference.Offset];
                long targetOffset = call.Offset + call.Size;

                reader.FileOffset = (uint)targetOffset;
                reader.ReadBytes(encryptedOpCodes, 0, encryptedOpCodes.Length);

                // Go over all possible LSBs.
                for (uint lsb = 0; lsb < byte.MaxValue; lsb++)
                {
                    // Check whether the LSB decodes to a PUSHR_xxxx.
                    if (IsPotentialLSB(constants, encryptedOpCodes[0], lsb))
                    {
                        // Go over all remaining 24 bits.
                        for (uint j = 0; j < 0x00FFFFFF; j++)
                        {
                            uint currentKey = (j << 8) | lsb;

                            // Try new key.
                            if (IsValidKey(constants, encryptedOpCodes, currentKey))
                            {
                                // We have found a key!
                                watch.Stop();
                                logger.Debug(Tag, $"Found key after {watch.Elapsed.TotalSeconds:0.00}s.");

                                return(currentKey);
                            }
                        } // for all other 24 bits.
                    }     // if potential LSB
                }         // foreach LSB

                watch.Stop();
                logger.Debug(Tag, $"Exhausted key space after {watch.Elapsed.TotalSeconds:0.00}s without finding key.");
            }

            return(null);
        }