public static void DumpDetected(Vtero vtero, DetectedProc p, long VAStart = 0, long VAEnd = 0xffffffff0000) { var mods = vtero.ModuleScan(p, 3, VAStart, VAEnd); //Parallel.ForEach(mods, (detected) => //{ foreach (var detected in p.Sections) { var cv_data = vtero.ExtractCVDebug(p, detected.Value); if (cv_data != null) { var sympath = Environment.GetEnvironmentVariable("_NT_SYMBOL_PATH"); if (string.IsNullOrWhiteSpace(sympath)) { sympath = "SRV*http://msdl.microsoft.com/download/symbols"; } // TODO: fix this or not? //if(Vtero.TryLoadSymbols(p.ID.GetHashCode(), cv_data, detected.Key)) // vtero.KernelProc = p; } } //}); }
/// <summary> /// Initial testing/prototype /// Detect/download all binaries in all AS /// </summary> /// <param name="ops"></param> /// <param name="vtero"></param> public void StartAnalyze(AnalyzeOptions ops, Vtero vtero) { long VAStart = 0; long VAEnd = VAStart + (0x8000000000 - 0x1000); string input = string.Empty; var GloalView = new ConcurrentDictionary<DetectedProc, ConcurrentDictionary<long, Extract>>(); vtero.MemAccess = Mem.InitMem(vtero.MemFile, vtero.MRD); if (vtero.VMCSs.Count < 1) { foreach (var p in vtero.FlattenASGroups) { DumpDetected(vtero, p); } // scan bare metal // Parallel.ForEach(vtero.Processes, (p) => //{ // WriteColor(ConsoleColor.Cyan, $"Scanning for modules addressable by: {p}"); // DumpDetected(vtero, p); //}); } else foreach (var grpz in vtero.ASGroups) { foreach (var vm in vtero.VMCSs.Values) { WriteColor(ConsoleColor.White, $"Group ID: {grpz.Key}"); foreach (var p in grpz.Value) { DumpDetected(vtero, p); } } } }
public dynamic xStructInfo(string Struct, long[] memRead = null, string Module = "ntkrnlmp") { MemSection pdb = null; if (Module == "ntkrnlmp" && KernelSection != null) { pdb = KernelSection; } else { var pdbPaths = from files in Sections.Values where files.DebugDetails != null && !string.IsNullOrWhiteSpace(files.DebugDetails.PDBFullPath) && files.DebugDetails.PDBFullPath.ToLower().Contains(Module.ToLower()) select files; pdb = pdbPaths.FirstOrDefault(); KernelSection = pdb; } if (sym == null) { sym = Vtero.TryLoadSymbols(ID.GetHashCode(), pdb.DebugDetails, pdb.VA.Address); } return(sym.xStructInfo(pdb.DebugDetails.PDBFullPath, Struct, memRead, GetVirtualByte, GetVirtualLong)); }
public void LoadModulesInRange(long VA, long length, string OnlyModule = null) { var KVS = new VirtualScanner(this, new Mem(MemAccess)); foreach (var artifact in KVS.Run(VA, VA + length)) { var ms = new MemSection() { IsExec = true, Module = artifact, VA = new VIRTUAL_ADDRESS(artifact.VA) }; var extracted = ExtractCVDebug(ms); if (extracted == null) { continue; } if (!string.IsNullOrWhiteSpace(OnlyModule) && OnlyModule != ms.Name) { continue; } if (!Sections.ContainsKey(artifact.VA)) { Sections.TryAdd(artifact.VA, ms); } // we can clobber this guy all the time I guess since everything is stateless in Sym and managed // entirely by the handle ID really which is local to our GUID so.... sym = Vtero.TryLoadSymbols(ID.GetHashCode(), ms.DebugDetails, ms.VA.Address); if (Vtero.VerboseOutput) { WriteColor(ConsoleColor.Green, $"symbol loaded [{sym != null}] from file [{ms.DebugDetails.PDBFullPath}]"); } } }
public void DumpProc(string Folder, bool IncludeData = false, bool KernelSpace = true, bool OnlyExec = true) { //// TODO: BOILER PLATE check perf of using callbacks PageTable.AddProcess(this, new Mem(MemAccess)); var cnt = PT.FillPageQueue(false, KernelSpace); Folder = Folder + Path.DirectorySeparatorChar.ToString(); Directory.CreateDirectory(Folder); long ContigSizeState = 0, curr = 0; // single threaded worked best so far //Parallel.For(0, cnt, (i, loopState) => x foreach (var range in PT.FillPageQueue(false, KernelSpace)) { curr++; if (Vtero.VerboseLevel > 1) { //var curr = cnt - PT.PageQueue.Count; //var done = Convert.ToDouble(curr) / Convert.ToDouble(cnt) * 100.0; Console.CursorLeft = 0; Console.Write($"{curr} scanned"); } if (range.PTE.Valid) { // skip data as requested if (!IncludeData && range.PTE.NoExecute) { continue; } Vtero.WriteRange(range.VA, range, Folder, ref ContigSizeState, MemAccess); } } }
public static void DumpDetected(Vtero vtero, DetectedProc p, long VAStart = 0, long VAEnd = 0xffffffff0000) { var mods = vtero.ModuleScan(p, VAStart, VAEnd); // BUGBUG: TODO: Refactor the threadlocal stuff seems were re-entrant unsafe :( //Parallel.ForEach(mods, (detected) => //{ foreach (var detected in mods) { var cv_data = vtero.ExtractCVDebug(p, detected.Value, detected.Key); if (cv_data != null) { var sympath = Environment.GetEnvironmentVariable("_NT_SYMBOL_PATH"); if (string.IsNullOrWhiteSpace(sympath)) { sympath = "SRV*http://msdl.microsoft.com/download/symbols"; } if (vtero.TryLoadSymbols(p, detected.Value, cv_data, detected.Key, sympath)) { vtero.GetKernelDebuggerData(p, detected.Value, cv_data, sympath); } } } //}); }
public Dumper(Vtero vtero, string outDir, DetectedProc dp, MemRangeArgs args) { Vtero = vtero; DP = dp; OutDir = outDir; SelectedRegions = args.Regions; }
public static Vtero CheckpointRestoreState(string SaveFile) { Vtero ThisInstance = null; using (var SerData = File.OpenRead(SaveFile)) ThisInstance = Serializer.Deserialize <inVtero.net.Vtero>(SerData); return(ThisInstance); }
public void LoadSymbols(MemSection OnlyMS = null) { foreach (var ms in Sections) { if (OnlyMS == null || (OnlyMS != null && OnlyMS.VA.Address == ms.Key)) { sym = Vtero.TryLoadSymbols(ID.GetHashCode(), ms.Value.DebugDetails, ms.Value.VA.Address); if (Vtero.VerboseOutput) { WriteColor(ConsoleColor.Green, $"symbol loaded [{sym != null}] from file [{ms.Value.DebugDetails.PDBFullPath}]"); } } } }
public static void DumpDetected(Vtero vtero, DetectedProc p, long VAStart = 0, long VAEnd = 0xffffffff0000) { var mods = vtero.ModuleScan(p, VAStart, VAEnd); //Parallel.ForEach(mods, (detected) => //{ foreach (var detected in p.Sections) { var cv_data = vtero.ExtractCVDebug(p, detected); if (cv_data != null) { var sympath = Environment.GetEnvironmentVariable("_NT_SYMBOL_PATH"); if (string.IsNullOrWhiteSpace(sympath)) sympath = "SRV*http://msdl.microsoft.com/download/symbols"; if(vtero.TryLoadSymbols(cv_data, detected.VA.Address, sympath)) vtero.KernelProc = p; } } //}); }
public void dump(DumpOptions argz) { DumpOptions dOptions = argz; if(argz == null) { ArgUsage.GetStyledUsage<DumpOptions>().Write(); return; } if (option.IgnoreSaveData) { ConsoleString.WriteLine("No save state available or requested to ignore, scan first before dumping.", ConsoleColor.Yellow, ConsoleColor.Black); return; } if(vtero == null) vtero = Scan.Scanit(option); Dump.DumpIt(vtero, option, dOptions); }
/// <summary> /// Initial testing/prototype /// Detect/download all binaries in all AS /// </summary> /// <param name="ops"></param> /// <param name="vtero"></param> public void StartAnalyze(AnalyzeOptions ops, Vtero vtero) { long VAStart = 0; long VAEnd = VAStart + (0x8000000000 - 0x1000); string input = string.Empty; var GloalView = new ConcurrentDictionary <DetectedProc, ConcurrentDictionary <long, Extract> >(); vtero.MemAccess = Mem.InitMem(vtero.MemFile, vtero.MRD); if (vtero.VMCSs.Count < 1) { foreach (var p in vtero.FlattenASGroups) { DumpDetected(vtero, p); } // scan bare metal // Parallel.ForEach(vtero.Processes, (p) => //{ // WriteColor(ConsoleColor.Cyan, $"Scanning for modules addressable by: {p}"); // DumpDetected(vtero, p); //}); } else { foreach (var grpz in vtero.ASGroups) { foreach (var vm in vtero.VMCSs.Values) { WriteColor(ConsoleColor.White, $"Group ID: {grpz.Key}"); foreach (var p in grpz.Value) { DumpDetected(vtero, p); } } } } }
/// <summary> /// Currently we scan hard for only kernel regions (2MB pages + ExEC) /// If there are kernel modules named the OnlyModule it may cause us to ignore the real one in that case /// you can still scan for * by passing null or empty string /// </summary> /// <param name="OnlyModule">Stop when the first module named this is found</param> public VirtualScanner ScanAndLoadModules(string OnlyModule = "ntkrnlmp.pdb", bool OnlyLarge = true, bool IncludeKernelSpace = true, bool OnlyValid = true, bool IncludeData = false, bool DoExtraHeaderScan = true) { const int LARGE_PAGE_SIZE = 1024 * 1024 * 2; var curr = 0; PageTable.AddProcess(this, new Mem(MemAccess)); //var cnt = PT.FillPageQueue(OnlyLarge, IncludeKernelSpace); var KVS = new VirtualScanner(this, new Mem(MemAccess), DoExtraHeaderScan); // single threaded worked best so far //Parallel.For(0, cnt, (i, loopState) => x foreach (var range in PT.FillPageQueue(OnlyLarge, IncludeKernelSpace, OnlyValid, IncludeData)) //for (int i = 0; i < cnt; i++) { curr++; bool stop = false; if (Vtero.VerboseLevel > 1) { //var curr = cnt - PT.PageQueue.Count; //var done = Convert.ToDouble(curr) / Convert.ToDouble(cnt) * 100.0; Console.CursorLeft = 0; Console.Write($"{curr} scanned"); } if (range.PTE.Valid && !range.PTE.NoExecute) { foreach (var artifact in KVS.Run(range.VA.Address, range.VA.Address + (range.PTE.LargePage ? LARGE_PAGE_SIZE : MagicNumbers.PAGE_SIZE), range)) { var ms = new MemSection() { IsExec = true, Module = artifact, VA = new VIRTUAL_ADDRESS(artifact.VA), Source = range }; var extracted = ExtractCVDebug(ms); if (extracted == null) { if (Vtero.VerboseLevel > 1) { WriteColor(ConsoleColor.Yellow, $"failed debug info for PE @address {range.VA.Address:X}, extracted headers: {artifact}"); } continue; } if (!string.IsNullOrWhiteSpace(OnlyModule) && OnlyModule != ms.Name) { continue; } if (!Sections.ContainsKey(artifact.VA)) { Sections.TryAdd(artifact.VA, ms); } // we can clobber this guy all the time I guess since everything is stateless in Sym and managed // entirely by the handle ID really which is local to our GUID so.... sym = Vtero.TryLoadSymbols(ID.GetHashCode(), ms.DebugDetails, ms.VA.Address); if (Vtero.VerboseOutput) { WriteColor((sym != null) ? ConsoleColor.Green : ConsoleColor.Yellow, $" symbol loaded = [{sym != null}] PDB [{ms.DebugDetails.PDBFullPath}] @ {range.VA.Address:X}, {ms.Name}"); if (Vtero.VerboseLevel > 1) { WriteColor((sym != null) ? ConsoleColor.Green : ConsoleColor.Yellow, $"headers: { artifact} "); } } if (!string.IsNullOrWhiteSpace(OnlyModule)) { if (!string.IsNullOrWhiteSpace(ms.Name) && ms.Name == OnlyModule) { stop = true; //loopState.Stop(); break; } } //if (loopState.IsStopped) //return; if (stop) { break; } } } //if (loopState.IsStopped) // return;e //}); if (stop) { break; } } return(KVS); }
public static void DumpIt(Vtero vtero, ConfigOptions co, DumpOptions dmpo) { var Version = vtero.Version; Mem.InitMem(co.FileName, vtero.MRD); // Extract Address Spaces verifies the linkages between // process<->CR3<->EPTP(if there is one) // and that they are functional var vetted = vtero.ExtrtactAddressSpaces(null, null, Version); // leaving this in as an example maybe? ;) //WriteLine("enter a group ID: "); //input = ReadLine(); //int Grp = int.Parse(input); //WriteLine("enter a process ID: "); //input = ReadLine(); //long procID = long.Parse(input, NumberStyles.HexNumber); //var proc = (from procz in vtero.ASGroups[Grp] // where procz.CR3Value == procID // select procz).First(); //int i = 1; //DetectedProc dp = proc; //while(dp == null) // dp = vtero.GetKernelRangeFromGroup(i++); // Scan for kernel // NT kernel may be in 0xFFFFF80000000 to 0xFFFFF8800000 range long KernVAStart = 0xF80000000000; long KernVAEnd = KernVAStart + (0x8000000000 - 0x1000); string input = string.Empty; var Detections = new Dictionary<long, Extract>(); DetectedProc LikelyKernel = null; bool Decoded = false; // were doing this in nested loops to brute force our way past any errors // but only need the first set of detections per group foreach (var grpz in vtero.ASGroups) { foreach (var vm in vtero.VMCSs.Values) { WriteColor(ConsoleColor.White, $"Group ID: {grpz.Key}"); foreach (var p in grpz.Value) { WriteLine($"Proc: {p.CR3Value:X}"); Detections = Detections.Concat( vtero.ModuleScan(p, KernVAStart, KernVAEnd).Where(x => !Detections.ContainsKey(x.Key))) .ToDictionary(x => x.Key, x => x.Value); if (Detections.Count() > 0) { LikelyKernel = p; if (vm.EPTP == 0) p.vmcs = null; else p.vmcs = vm; // scan for kernel foreach (var detected in Detections) { WriteColor(ConsoleColor.Green, $"Attempting to parse detected PE module loaded @ {detected.Key:X}"); WriteColor(ConsoleColor.Cyan, detected.Value.ToString()); if (detected.Value.ToString().Contains("POOLCODE")) { WriteColor(ConsoleColor.White, "Likely Kernel analyzing for CV data"); /* * var cv_data = vtero.ExtractCVDebug(LikelyKernel, detected.Value, detected.Key); if (cv_data != null) { var sympath = Environment.GetEnvironmentVariable("_NT_SYMBOL_PATH"); if (string.IsNullOrWhiteSpace(sympath)) sympath = "SRV*http://msdl.microsoft.com/download/symbols"; if (vtero.TryLoadSymbols(cv_data, detected.Key, sympath)) Decoded = vtero.GetKernelDebuggerData(LikelyKernel, detected.Value, cv_data, sympath); } */ } } } if (Decoded) break; } if (Decoded) break; } if (Decoded) break; } ForegroundColor = ConsoleColor.Green; WriteLine($"{Environment.NewLine}Final analysis completed, address spaces extracted. {QuickOptions.Timer.Elapsed} {QuickOptions.FormatRate(vtero.FileSize * 3, QuickOptions.Timer.Elapsed)}"); // do a test dump // extract & dump could be done at the same time if(!dmpo.ListOnly) vtero.DumpASToFile(); //if (Vtero.VerboseOutput) //vtero.DumpFailList(); return; }
public override bool IsSupportedFormat(Vtero vtero) { bool rv = false; if (!File.Exists(DumpFile)) return rv; // use abstract implementation & scan for internal LogicalPhysMemDesc = ExtractMemDesc(vtero); using (var dstream = File.OpenRead(DumpFile)) { MemSize = dstream.Length; using (var dbin = new BinaryReader(dstream)) { // start with a easy to handle format of DMP if (ASCIIEncoding.ASCII.GetString(dbin.ReadBytes(8)) != "PAGEDU64") return rv; dbin.BaseStream.Position = 0x2020; StartOfMem = dbin.ReadUInt32(); // Find the RUN info dbin.BaseStream.Position = 0x88; var MemRunDescriptor = new MemoryDescriptor(); MemRunDescriptor.StartOfMemmory = StartOfMem; MemRunDescriptor.NumberOfRuns = dbin.ReadInt64(); MemRunDescriptor.NumberOfPages = dbin.ReadInt64(); // this struct has to fit in the header which is only 0x2000 in total size if (MemRunDescriptor.NumberOfRuns > 32 || MemRunDescriptor.NumberOfRuns < 0) { // TODO: in this case we have to de-patchguard the KDDEBUGGER_DATA block // before resulting to that... implemented a memory scanning mode to extract the runs out via struct detection PhysMemDesc = LogicalPhysMemDesc; PhysMemDesc.StartOfMemmory = StartOfMem; // physmem is preferred place to load from so if we have only 1 run move it to phys. LogicalPhysMemDesc = null; } else { // in this case StartOfMem is 0x2000 MemRunDescriptor.StartOfMemmory = 0x2000; // we have an embedded RUN in the DMP file that appears to conform to the rules we know for (int i = 0; i < MemRunDescriptor.NumberOfRuns; i++) { var basePage = dbin.ReadInt64(); var pageCount = dbin.ReadInt64(); MemRunDescriptor.Run.Add(new MemoryRun() { BasePage = basePage, PageCount = pageCount }); } PhysMemDesc = MemRunDescriptor; } rv = true; } } #if OLD_CODE long aSkipCount = 0; for (int i = 0; i < PhysMemDesc.NumberOfRuns; i++) { var RunSkip = PhysMemDesc.Run[i].BasePage - aSkipCount; PhysMemDesc.Run[i].SkipCount = RunSkip; aSkipCount = PhysMemDesc.Run[i].BasePage + PhysMemDesc.Run[i].PageCount; } #endif return rv; }
public Vtero CheckpointRestoreState(string SaveFile) { Vtero ThisInstance = new Vtero(); var siz = new FileInfo(SaveFile).Length; if (siz == 0) return null; using (var SerData = File.OpenRead(SaveFile)) ThisInstance = Serializer.Deserialize<inVtero.net.Vtero>(SerData); return ThisInstance; }
public Scanner(string InputFile, Vtero vTero) : this() { Filename = InputFile; vtero = vTero; }
public Vtero Scanit(ScanOptions op) { bool SkipVMCS = false; #if TESTING foreach (var sx in op.DisabledScans) { var spec = sx.ToLower(); if (spec.Contains("vmcs")) { SkipVMCS = true; } if (spec.Contains("obsd")) { Version = Version & ~PTType.OpenBSD; } if (spec.Contains("nbsd")) { Version = Version & ~PTType.NetBSD; } if (spec.Contains("fbsd")) { Version = Version & ~PTType.FreeBSD; } if (spec.Contains("lin")) { Version = Version & ~PTType.LinuxS; } if (spec.Contains("hv")) { Version = Version & ~PTType.HyperV; } if (spec.Contains("gen")) { Version = Version & ~PTType.GENERIC; } if (spec.Contains("win")) { Version = Version & ~PTType.Windows; } } if ((Version & PTType.VALUE) == PTType.VALUE) { bool Parsed = false; do { if (args.Length < 2) { WriteLine($"Specify value"); return; } Parsed = uint.TryParse(args[2], NumberStyles.HexNumber, CultureInfo.CurrentCulture, out valuI); if (!Parsed) { Parsed = ulong.TryParse(args[2], NumberStyles.HexNumber, CultureInfo.CurrentCulture, out valuL); if (Parsed) { Is64Scan = true; } else { WriteLine($"Unable to parse input {args[2]}"); return; } } else { valuL = (ulong)valuI; } } while (!Parsed); } #endif string Filename = null; Vtero vtero = null; PTType Version = PTType.Windows; // this instance is temporally used for loading state // i.e. don't set properties or fields here var saveStateFile = $"{Filename}.inVtero.net"; if (File.Exists(saveStateFile)) { if (todo.Key != ConsoleKey.D) { vtero = vtero.CheckpointRestoreState(saveStateFile); vtero.OverRidePhase = true; } else { File.Delete(saveStateFile); } } if (vtero.Phase < 2) { vtero = new Vtero(Filename); } //Mem.InitMem(parsed.FileName, null, vtero.DetectedDesc); ProgressBarz.Bar.Message = "First pass, looking for processes"; ForegroundColor = ConsoleColor.Cyan; #if TESTING Timer = Stopwatch.StartNew(); if ((Version & PTType.VALUE) == PTType.VALUE) { var off = vtero.ScanValue(Is64Scan, valuL, 0); WriteLine(FormatRate(vtero.FileSize, Timer.Elapsed)); using (var dstream = File.OpenRead(vtero.MemFile)) { using (var dbin = new BinaryReader(dstream)) { foreach (var xoff in off) { WriteLine($"Checking Memory Descriptor @{(xoff + 28):X}"); if (xoff > vtero.FileSize) { WriteLine($"offset {xoff:X} > FileSize {vtero.FileSize:X}"); continue; } dstream.Position = xoff + 28; var MemRunDescriptor = new MemoryDescriptor(); MemRunDescriptor.NumberOfRuns = dbin.ReadInt64(); MemRunDescriptor.NumberOfPages = dbin.ReadInt64(); Console.WriteLine($"Runs: {MemRunDescriptor.NumberOfRuns}, Pages: {MemRunDescriptor.NumberOfPages} "); if (MemRunDescriptor.NumberOfRuns < 0 || MemRunDescriptor.NumberOfRuns > 32) { continue; } for (int i = 0; i < MemRunDescriptor.NumberOfRuns; i++) { var basePage = dbin.ReadInt64(); var pageCount = dbin.ReadInt64(); MemRunDescriptor.Run.Add(new MemoryRun() { BasePage = basePage, PageCount = pageCount }); } WriteLine($"MemoryDescriptor {MemRunDescriptor}"); } } } WriteLine("Finished VALUE scan."); return; } if ((Version & PTType.VALUE) == PTType.VALUE) { return; } #endif // basic perf checking QuickOptions.Timer = Stopwatch.StartNew(); var procCount = vtero.ProcDetectScan(Version); WriteColor(ConsoleColor.Blue, ConsoleColor.Yellow, $"{procCount} candidate process page tables. Time so far: {QuickOptions.Timer.Elapsed}, second pass starting. {QuickOptions.FormatRate(vtero.FileSize, QuickOptions.Timer.Elapsed)}"); //BackgroundColor = ConsoleColor.Black; //ForegroundColor = ConsoleColor.Cyan; if (procCount < 3) { WriteColor(ConsoleColor.Red, "Seems like a fail. Try generic scanning or implement a state scan like LinuxS"); return(null); } // second pass // with the page tables we acquired, locate candidate VMCS pages in the format // [31-bit revision id][abort indicator] // the page must also have at least 1 64bit value which is all set (-1) // Root-HOST CR3 will have uniform diff // unless an extent based dump image is input, some .DMP variations // TODO: Add support for extent based inputs // Guest VMCS will contain host CR3 & guest CR3 (hCR3 & gCR3) // sometimes CR3 will be found in multiple page tables, e.g. system process or SMP // if I have more than 1 CR3 from different file_offset, just trim them out for now // future may have a reason to isolate based on original locationAG if (SkipVMCS) { return(vtero); } ProgressBarz.Bar.Message = "Second pass, correlating for VMCS pages"; var VMCSCount = vtero.VMCSScan(); //Timer.Stop(); WriteColor(ConsoleColor.Blue, ConsoleColor.Yellow, $"{VMCSCount} candidate VMCS pages. Time to process: {QuickOptions.Timer.Elapsed}, Data scanned: {vtero.FileSize:N}"); // second time WriteColor(ConsoleColor.Blue, ConsoleColor.Yellow, $"Second pass done. {QuickOptions.FormatRate(vtero.FileSize * 2, QuickOptions.Timer.Elapsed)}"); // each of these depends on a VMCS scan/pass having been done at the moment WriteColor(ConsoleColor.Cyan, ConsoleColor.Black, "grouping and joining all memory"); // After this point were fairly functional vtero.GroupAS(); // sync-save state so restarting is faster if (!File.Exists(saveStateFile)) { Write($"Saving checkpoint... "); saveStateFile = vtero.CheckpointSaveState(); WriteColor(ConsoleColor.White, saveStateFile); } return(vtero); }
public static void Main(string[] args) { #region fluff var Version = PTType.UNCONFIGURED; string Filename = null; if (args.Length == 0 || args.Length > 2) { WriteLine("inVtero FileName [win|fbsd|obsd|nbsd|!]"); WriteLine("\"inVtero FileName winfbsd\" (e.g. Run FreeBSD and Windows together)"); return; } try { Filename = args[0]; if (args.Length > 1) { var spec = args[1].ToLower(); if (spec.Contains("gen")) Version |= PTType.GENERIC; if (spec.Contains("win")) Version |= PTType.Windows; if (spec.Contains("fbsd")) Version |= PTType.FreeBSD; if (spec.Contains("obsd")) Version |= PTType.OpenBSD; if (spec.Contains("nbsd")) Version |= PTType.NetBSD; if (spec.Contains("!")) Version |= PTType.ALL; } else Version = PTType.ALL; var vtero = new Vtero(Filename); Vtero.VerboseOutput = true; CancelKeyPress += Console_CancelKeyPress; AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; ForegroundColor = ConsoleColor.Cyan; #endregion // basic perf checking Timer = Stopwatch.StartNew(); var procCount = vtero.ProcDetectScan(Version); #region page table/CR3 progress report ForegroundColor = ConsoleColor.Blue; BackgroundColor = ConsoleColor.Yellow; var msg = $"{procCount} candiate process page tables. Time so far: {Timer.Elapsed}, second pass starting."; Write(msg); WriteLine(PrintRate(vtero.FileSize, Timer.Elapsed)); BackgroundColor = ConsoleColor.Black; ForegroundColor = ConsoleColor.Cyan; if (procCount < 3) { WriteLine("Seems like a fail. See if this is Linux or something that a different detection technique is needed? :("); return; } //BackgroundColor = ConsoleColor.White; #endregion #region blighering // second pass // with the page tables we aquired, locate candidate VMCS pages in the format // [31-bit revision id][abort indicator] // the page must also have at least 1 64bit value which is all set (-1) // Root-HOST CR3 will have uniform diff // unless an extent based dump image is input, some .DMP variations // TODO: Add support for extent based inputs // Guest VMCS will contain host CR3 & guest CR3 (hCR3 & gCR3) // sometimes CR3 will be found in multiple page tables, e.g. system process or SMP // if I have more than 1 CR3 from different file_offset, just trim them out for now // future may have a reason to isolate based on original locationAG #endregion var VMCSCount = vtero.VMCSScan(); //Timer.Stop(); #region VMCS page detection ForegroundColor = ConsoleColor.Blue; BackgroundColor = ConsoleColor.Yellow; WriteLine($"{VMCSCount} candiate VMCS pages. Time to process: {Timer.Elapsed}"); Write($"Data scanned: {vtero.FileSize:N}"); // second time WriteLine("Second pass done. " + PrintRate(vtero.FileSize * 2, Timer.Elapsed)); BackgroundColor = ConsoleColor.Black; ForegroundColor = ConsoleColor.Cyan; #region TEST WriteLine("grouping and joinging all memory"); vtero.GroupAS(); //vtero.ExtrtactAddressSpaces(); vtero.DumpFailList(); WriteLine($"Final analysis compleated, address spaces extracted. {Timer.Elapsed} {PrintRate(vtero.FileSize * 3, Timer.Elapsed)}"); #endregion #endregion } catch (Exception ex) { Write("Error in processing, likely need to adjust run/gaps. "); Write(ex.ToString()); WriteLine((ex.InnerException == null ? "." : ex.InnerException.ToString())); } finally { ResetColor(); } return; }
public static Vtero Scanit(ConfigOptions co) { bool SkipVMCS = (co.VersionsToEnable & PTType.VMCS) != PTType.VMCS; var Filename = co.FileName; co.VersionsToEnable = co.VersionsToEnable & ~PTType.VMCS; // allocate now so that we can un-serialize or keep an instance Vtero vtero = new Vtero(); // this instance is temporally used for loading state // i.e. don't set properties or fields here var saveStateFile = $"{Filename}.inVtero.net"; if (File.Exists(saveStateFile)) { if (!co.IgnoreSaveData) { vtero = vtero.CheckpointRestoreState(saveStateFile); if(vtero == null) { WriteColor(ConsoleColor.Red, "ERROR IN SERIALIZER"); return null; } vtero.OverRidePhase = true; } else File.Delete(saveStateFile); } if (vtero.Phase < 2) vtero = new Vtero(Filename); if (!vtero.OverRidePhase) { Mem.InitMem(co.FileName, vtero.MRD); ProgressBarz.BaseMessage = new ConsoleString("First pass, looking for processes"); ForegroundColor = ConsoleColor.Cyan; #if TESTING Timer = Stopwatch.StartNew(); if ((Version & PTType.VALUE) == PTType.VALUE) { var off = vtero.ScanValue(Is64Scan, valuL, 0); WriteLine(FormatRate(vtero.FileSize, Timer.Elapsed)); using (var dstream = File.OpenRead(vtero.MemFile)) { using (var dbin = new BinaryReader(dstream)) { foreach (var xoff in off) { WriteLine($"Checking Memory Descriptor @{(xoff + 28):X}"); if (xoff > vtero.FileSize) { WriteLine($"offset {xoff:X} > FileSize {vtero.FileSize:X}"); continue; } dstream.Position = xoff + 28; var MemRunDescriptor = new MemoryDescriptor(); MemRunDescriptor.NumberOfRuns = dbin.ReadInt64(); MemRunDescriptor.NumberOfPages = dbin.ReadInt64(); Console.WriteLine($"Runs: {MemRunDescriptor.NumberOfRuns}, Pages: {MemRunDescriptor.NumberOfPages} "); if (MemRunDescriptor.NumberOfRuns < 0 || MemRunDescriptor.NumberOfRuns > 32) { continue; } for (int i = 0; i < MemRunDescriptor.NumberOfRuns; i++) { var basePage = dbin.ReadInt64(); var pageCount = dbin.ReadInt64(); MemRunDescriptor.Run.Add(new MemoryRun() { BasePage = basePage, PageCount = pageCount }); } WriteLine($"MemoryDescriptor {MemRunDescriptor}"); } } } WriteLine("Finished VALUE scan."); return; } if ((Version & PTType.VALUE) == PTType.VALUE) return; #endif } // basic perf checking QuickOptions.Timer = Stopwatch.StartNew(); var procCount = vtero.ProcDetectScan(co.VersionsToEnable); if (!vtero.OverRidePhase) { WriteColor(ConsoleColor.Blue, ConsoleColor.Yellow, $"{procCount} candidate process page tables. Time so far: {QuickOptions.Timer.Elapsed}, second pass starting. {QuickOptions.FormatRate(vtero.FileSize, QuickOptions.Timer.Elapsed)}"); if (procCount < 3) { WriteColor(ConsoleColor.Red, "Seems like a fail. Try generic scanning or implement a state scan like LinuxS"); return null; } } // second pass // with the page tables we acquired, locate candidate VMCS pages in the format // [31-bit revision id][abort indicator] // the page must also have at least 1 64bit value which is all set (-1) // Root-HOST CR3 will have uniform diff // unless an extent based dump image is input, some .DMP variations // TODO: Add support for extent based inputs // Guest VMCS will contain host CR3 & guest CR3 (hCR3 & gCR3) // sometimes CR3 will be found in multiple page tables, e.g. system process or SMP // if I have more than 1 CR3 from different file_offset, just trim them out for now // future may have a reason to isolate based on original locationAG if (SkipVMCS) { if (!vtero.OverRidePhase) vtero.GroupAS(); if (co.VerboseLevel > 1) WriteColor(ConsoleColor.Yellow, "Skipping VMCS scan (as requested)."); } else { ProgressBarz.BaseMessage = new ConsoleString("Second pass, correlating for VMCS pages"); var VMCSCount = vtero.VMCSScan(); //Timer.Stop(); if (!vtero.OverRidePhase) { WriteColor(ConsoleColor.Blue, ConsoleColor.Yellow, $"{VMCSCount} candidate VMCS pages. Time to process: {QuickOptions.Timer.Elapsed}, Data scanned: {vtero.FileSize:N}"); // second time WriteColor(ConsoleColor.Blue, ConsoleColor.Yellow, $"Second pass done. {QuickOptions.FormatRate(vtero.FileSize * 2, QuickOptions.Timer.Elapsed)}"); // each of these depends on a VMCS scan/pass having been done at the moment WriteColor(ConsoleColor.Cyan, ConsoleColor.Black, "grouping and joining all memory"); } // After this point were fairly functional vtero.GroupAS(); } // sync-save state so restarting is faster if (!File.Exists(saveStateFile)) { Write($"Saving checkpoint... "); saveStateFile = vtero.CheckpointSaveState(); WriteColor(ConsoleColor.White, saveStateFile); } Console.CursorVisible = true; return vtero; }
public static void DumpIt(Vtero vtero, ConfigOptions co, DumpOptions dmpo) { var Version = vtero.Version; Mem.InitMem(co.FileName, vtero.MRD); // Extract Address Spaces verifies the linkages between // process<->CR3<->EPTP(if there is one) // and that they are functional var vetted = vtero.ExtrtactAddressSpaces(null, null, Version); // leaving this in as an example maybe? ;) //WriteLine("enter a group ID: "); //input = ReadLine(); //int Grp = int.Parse(input); //WriteLine("enter a process ID: "); //input = ReadLine(); //long procID = long.Parse(input, NumberStyles.HexNumber); //var proc = (from procz in vtero.ASGroups[Grp] // where procz.CR3Value == procID // select procz).First(); //int i = 1; //DetectedProc dp = proc; //while(dp == null) // dp = vtero.GetKernelRangeFromGroup(i++); // Scan for kernel // NT kernel may be in 0xFFFFF80000000 to 0xFFFFF8800000 range long KernVAStart = 0xF80000000000; long KernVAEnd = KernVAStart + (0x8000000000 - 0x1000); string input = string.Empty; var Detections = new Dictionary <long, Extract>(); DetectedProc LikelyKernel = null; bool Decoded = false; // were doing this in nested loops to brute force our way past any errors // but only need the first set of detections per group foreach (var grpz in vtero.ASGroups) { foreach (var vm in vtero.VMCSs.Values) { WriteColor(ConsoleColor.White, $"Group ID: {grpz.Key}"); foreach (var p in grpz.Value) { WriteLine($"Proc: {p.CR3Value:X}"); Detections = Detections.Concat( vtero.ModuleScan(p, 3, KernVAStart, KernVAEnd).Where(x => !Detections.ContainsKey(x.Key))) .ToDictionary(x => x.Key, x => x.Value); if (Detections.Count() > 0) { LikelyKernel = p; if (vm.EPTP == 0) { p.vmcs = null; } else { p.vmcs = vm; } // scan for kernel foreach (var detected in Detections) { WriteColor(ConsoleColor.Green, $"Attempting to parse detected PE module loaded @ {detected.Key:X}"); WriteColor(ConsoleColor.Cyan, detected.Value.ToString()); if (detected.Value.ToString().Contains("POOLCODE")) { WriteColor(ConsoleColor.White, "Likely Kernel analyzing for CV data"); /* * var cv_data = vtero.ExtractCVDebug(LikelyKernel, detected.Value, detected.Key); * * if (cv_data != null) * { * var sympath = Environment.GetEnvironmentVariable("_NT_SYMBOL_PATH"); * if (string.IsNullOrWhiteSpace(sympath)) * sympath = "SRV*http://msdl.microsoft.com/download/symbols"; * * if (Vtero.TryLoadSymbols(cv_data, detected.Key, sympath)) * Decoded = vtero.GetKernelDebuggerData(LikelyKernel, detected.Value, cv_data, sympath); * } */ } } } if (Decoded) { break; } } if (Decoded) { break; } } if (Decoded) { break; } } ForegroundColor = ConsoleColor.Green; WriteLine($"{Environment.NewLine}Final analysis completed, address spaces extracted. {QuickOptions.Timer.Elapsed} {QuickOptions.FormatRate(vtero.FileSize * 3, QuickOptions.Timer.Elapsed)}"); // do a test dump // extract & dump could be done at the same time if (!dmpo.ListOnly) { vtero.DumpASToFile(); } //if (Vtero.VerboseOutput) //vtero.DumpFailList(); return; }
public void scan() { vtero = Scan.Scanit(option); }
public static void Main(string[] args) { #region fluff var Version = PTType.UNCONFIGURED; var Filename = string.Empty; var SkipVMCS = false; if (args.Length < 1) { PrintHelp(); return; } try { Filename = args[0]; if (!File.Exists(Filename)) { PrintHelp(); return; } if (args.Length > 1) { var spec = args[1].ToLower(); if (spec.Contains("win")) Version |= PTType.Windows; if (spec.Contains("lin")) Version |= PTType.LinuxS; if (spec.Contains("fbsd")) Version |= PTType.FreeBSD; if (spec.Contains("obsd")) Version |= PTType.OpenBSD; if (spec.Contains("nbsd")) Version |= PTType.NetBSD; if (spec.Contains("gen")) Version |= PTType.GENERIC; if (spec.Contains("!")) Version |= PTType.ALL; if (spec.Contains("-vmcs")) SkipVMCS = true; if (spec.Contains("-obsd")) Version = Version & ~PTType.OpenBSD; if (spec.Contains("-nbsd")) Version = Version & ~PTType.NetBSD; if (spec.Contains("-fbsd")) Version = Version & ~PTType.FreeBSD; if (spec.Contains("-lin")) Version = Version & ~PTType.LinuxS; if (spec.Contains("-win")) Version = Version & ~PTType.Windows; } else Version = PTType.ALL; Vtero vtero = null; var saveStateFile = $"{Filename}.inVtero.net"; if (File.Exists(saveStateFile)) { WriteLine("Found save state, (l)oad or (d)iscard?"); var todo = ReadKey(); if (todo.Key == ConsoleKey.L) { vtero = Vtero.CheckpointRestoreState(saveStateFile); vtero.OverRidePhase = true; } else File.Delete(saveStateFile); } if(vtero == null) vtero = new Vtero(Filename); Vtero.VerboseOutput = true; CancelKeyPress += Console_CancelKeyPress; AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; ForegroundColor = ConsoleColor.Cyan; #endregion // basic perf checking Timer = Stopwatch.StartNew(); var procCount = vtero.ProcDetectScan(Version); #region page table/CR3 progress report ForegroundColor = ConsoleColor.Blue; BackgroundColor = ConsoleColor.Yellow; var msg = $"{procCount} candiate process page tables. Time so far: {Timer.Elapsed}, second pass starting."; Write(msg); WriteLine(FormatRate(vtero.FileSize, Timer.Elapsed)); BackgroundColor = ConsoleColor.Black; ForegroundColor = ConsoleColor.Cyan; if (procCount < 3) { WriteLine("Seems like a fail. Try generic scanning or implement a state scan like LinuxS"); return; } //BackgroundColor = ConsoleColor.White; #endregion #region blighering // second pass // with the page tables we acquired, locate candidate VMCS pages in the format // [31-bit revision id][abort indicator] // the page must also have at least 1 64bit value which is all set (-1) // Root-HOST CR3 will have uniform diff // unless an extent based dump image is input, some .DMP variations // TODO: Add support for extent based inputs // Guest VMCS will contain host CR3 & guest CR3 (hCR3 & gCR3) // sometimes CR3 will be found in multiple page tables, e.g. system process or SMP // if I have more than 1 CR3 from different file_offset, just trim them out for now // future may have a reason to isolate based on original locationAG #endregion if (!SkipVMCS) { var VMCSCount = vtero.VMCSScan(); //Timer.Stop(); #region VMCS page detection ForegroundColor = ConsoleColor.Blue; BackgroundColor = ConsoleColor.Yellow; WriteLine($"{VMCSCount} candiate VMCS pages. Time to process: {Timer.Elapsed}"); Write($"Data scanned: {vtero.FileSize:N}"); // second time WriteLine($"Second pass done. {FormatRate(vtero.FileSize * 2, Timer.Elapsed)}"); BackgroundColor = ConsoleColor.Black; ForegroundColor = ConsoleColor.Cyan; #region TEST // each of these depends on a VMCS scan/pass having been done at the moment WriteLine("grouping and joining all memory"); vtero.GroupAS(); // sync-save state so restarting is faster if (!File.Exists(saveStateFile)) { Write($"Saving checkpoint... "); saveStateFile = vtero.CheckpointSaveState(); WriteLine(saveStateFile); } // Extract Address Spaces verifies the linkages between // process<->CR3<->EPTP(if there is one) // and that they are functional var vetted = vtero.ExtrtactAddressSpaces(); // do a test dump // extract & dump could be done at the same time vtero.DumpASToFile(vetted); if (Vtero.VerboseOutput) vtero.DumpFailList(); WriteLine($"Final analysis compleated, address spaces extracted. {Timer.Elapsed} {FormatRate(vtero.FileSize * 3, Timer.Elapsed)}"); } #endregion #endregion } catch (Exception ex) { Write("Error in processing, likely need to adjust run/gaps. "); Write(ex.ToString()); WriteLine((ex.InnerException == null ? "." : ex.InnerException.ToString())); } finally { ResetColor(); } return; }
public Vtero Scanit(ScanOptions op) { bool SkipVMCS = false; #if TESTING foreach (var sx in op.DisabledScans) { var spec = sx.ToLower(); if (spec.Contains("vmcs")) SkipVMCS = true; if (spec.Contains("obsd")) Version = Version & ~PTType.OpenBSD; if (spec.Contains("nbsd")) Version = Version & ~PTType.NetBSD; if (spec.Contains("fbsd")) Version = Version & ~PTType.FreeBSD; if (spec.Contains("lin")) Version = Version & ~PTType.LinuxS; if (spec.Contains("hv")) Version = Version & ~PTType.HyperV; if (spec.Contains("gen")) Version = Version & ~PTType.GENERIC; if (spec.Contains("win")) Version = Version & ~PTType.Windows; } if((Version & PTType.VALUE) == PTType.VALUE) { bool Parsed = false; do { if(args.Length < 2) { WriteLine($"Specify value"); return; } Parsed = uint.TryParse(args[2],NumberStyles.HexNumber, CultureInfo.CurrentCulture, out valuI); if (!Parsed) { Parsed = ulong.TryParse(args[2], NumberStyles.HexNumber, CultureInfo.CurrentCulture, out valuL); if (Parsed) Is64Scan = true; else { WriteLine($"Unable to parse input {args[2]}"); return; } } else valuL = (ulong)valuI; } while (!Parsed); } #endif string Filename = null; Vtero vtero = null; PTType Version = PTType.Windows; // this instance is temporally used for loading state // i.e. don't set properties or fields here var saveStateFile = $"{Filename}.inVtero.net"; if (File.Exists(saveStateFile)) { if (todo.Key != ConsoleKey.D ) { vtero = vtero.CheckpointRestoreState(saveStateFile); vtero.OverRidePhase = true; } else File.Delete(saveStateFile); } if (vtero.Phase < 2) vtero = new Vtero(Filename); //Mem.InitMem(parsed.FileName, null, vtero.DetectedDesc); ProgressBarz.Bar.Message = "First pass, looking for processes"; ForegroundColor = ConsoleColor.Cyan; #if TESTING Timer = Stopwatch.StartNew(); if ((Version & PTType.VALUE) == PTType.VALUE) { var off = vtero.ScanValue(Is64Scan, valuL, 0); WriteLine(FormatRate(vtero.FileSize, Timer.Elapsed)); using (var dstream = File.OpenRead(vtero.MemFile)) { using (var dbin = new BinaryReader(dstream)) { foreach (var xoff in off) { WriteLine($"Checking Memory Descriptor @{(xoff + 28):X}"); if (xoff > vtero.FileSize) { WriteLine($"offset {xoff:X} > FileSize {vtero.FileSize:X}"); continue; } dstream.Position = xoff + 28; var MemRunDescriptor = new MemoryDescriptor(); MemRunDescriptor.NumberOfRuns = dbin.ReadInt64(); MemRunDescriptor.NumberOfPages = dbin.ReadInt64(); Console.WriteLine($"Runs: {MemRunDescriptor.NumberOfRuns}, Pages: {MemRunDescriptor.NumberOfPages} "); if (MemRunDescriptor.NumberOfRuns < 0 || MemRunDescriptor.NumberOfRuns > 32) { continue; } for (int i = 0; i < MemRunDescriptor.NumberOfRuns; i++) { var basePage = dbin.ReadInt64(); var pageCount = dbin.ReadInt64(); MemRunDescriptor.Run.Add(new MemoryRun() { BasePage = basePage, PageCount = pageCount }); } WriteLine($"MemoryDescriptor {MemRunDescriptor}"); } } } WriteLine("Finished VALUE scan."); return; } if ((Version & PTType.VALUE) == PTType.VALUE) return; #endif // basic perf checking QuickOptions.Timer = Stopwatch.StartNew(); var procCount = vtero.ProcDetectScan(Version); WriteColor(ConsoleColor.Blue, ConsoleColor.Yellow, $"{procCount} candidate process page tables. Time so far: {QuickOptions.Timer.Elapsed}, second pass starting. {QuickOptions.FormatRate(vtero.FileSize, QuickOptions.Timer.Elapsed)}"); //BackgroundColor = ConsoleColor.Black; //ForegroundColor = ConsoleColor.Cyan; if (procCount < 3) { WriteColor(ConsoleColor.Red,"Seems like a fail. Try generic scanning or implement a state scan like LinuxS"); return null; } // second pass // with the page tables we acquired, locate candidate VMCS pages in the format // [31-bit revision id][abort indicator] // the page must also have at least 1 64bit value which is all set (-1) // Root-HOST CR3 will have uniform diff // unless an extent based dump image is input, some .DMP variations // TODO: Add support for extent based inputs // Guest VMCS will contain host CR3 & guest CR3 (hCR3 & gCR3) // sometimes CR3 will be found in multiple page tables, e.g. system process or SMP // if I have more than 1 CR3 from different file_offset, just trim them out for now // future may have a reason to isolate based on original locationAG if (SkipVMCS) return vtero; ProgressBarz.Bar.Message = "Second pass, correlating for VMCS pages"; var VMCSCount = vtero.VMCSScan(); //Timer.Stop(); WriteColor(ConsoleColor.Blue, ConsoleColor.Yellow, $"{VMCSCount} candidate VMCS pages. Time to process: {QuickOptions.Timer.Elapsed}, Data scanned: {vtero.FileSize:N}"); // second time WriteColor(ConsoleColor.Blue, ConsoleColor.Yellow, $"Second pass done. {QuickOptions.FormatRate(vtero.FileSize * 2, QuickOptions.Timer.Elapsed)}"); // each of these depends on a VMCS scan/pass having been done at the moment WriteColor(ConsoleColor.Cyan, ConsoleColor.Black, "grouping and joining all memory"); // After this point were fairly functional vtero.GroupAS(); // sync-save state so restarting is faster if (!File.Exists(saveStateFile)) { Write($"Saving checkpoint... "); saveStateFile = vtero.CheckpointSaveState(); WriteColor(ConsoleColor.White, saveStateFile); } return vtero; }
/// <summary> /// Currently we scan hard for only kernel regions (2MB pages + ExEC) /// If there are kernel modules named the OnlyModule it may cause us to ignore the real one in that case /// you can still scan for * by passing null or empty string /// </summary> /// <param name="OnlyModule">Stop when the first module named this is found</param> public VirtualScanner ScanAndLoadModules(string OnlyModule = "ntkrnlmp") { PageTable.AddProcess(this, new Mem(MemAccess)); var cnt = PT.FillPageQueue(true); var KVS = new VirtualScanner(this, new Mem(MemAccess)); KVS.ScanMode = VAScanType.PE_FAST; Parallel.For(0, cnt, (i, loopState) => { PFN range; var curr = cnt - PT.PageQueue.Count; var done = (int)(Convert.ToDouble(curr) / Convert.ToDouble(cnt) * 100.0) + 0.5; if (PT.PageQueue.TryDequeue(out range) && range.PTE.Valid) { var found = KVS.Run(range.VA.Address, range.VA.Address + (range.PTE.LargePage ? (1024 * 1024 * 2) : 0x1000), loopState); // Attempt load foreach (var artifact in found) { var ms = new MemSection() { IsExec = true, Module = artifact.Value, VA = new VIRTUAL_ADDRESS(artifact.Key) }; var extracted = ExtractCVDebug(ms); if (extracted == null) { continue; } if (!string.IsNullOrWhiteSpace(OnlyModule) && OnlyModule != ms.Name) { continue; } if (!Sections.ContainsKey(artifact.Key)) { Sections.TryAdd(artifact.Key, ms); } // we can clobber this guy all the time I guess since everything is stateless in Sym and managed // entirely by the handle ID really which is local to our GUID so.... sym = Vtero.TryLoadSymbols(ID.GetHashCode(), ms.DebugDetails, ms.VA.Address); if (Vtero.VerboseOutput) { WriteColor(ConsoleColor.Green, $"symbol loaded [{sym != null}] from file [{ms.DebugDetails.PDBFullPath}]"); } if (!string.IsNullOrWhiteSpace(OnlyModule)) { if (!string.IsNullOrWhiteSpace(ms.Name) && ms.Name == OnlyModule) { loopState.Stop(); return; } } if (loopState.IsStopped) { return; } } } if (loopState.IsStopped) { return; } }); return(KVS); }