public int ProcDetectScan(PTType Modes, int DetectOnly = 0) { scan.ScanMode = Modes; var rv = scan.Analyze(DetectOnly); foreach (var p in scan.DetectedProcesses.Values) Processes.Add(p); return rv; }
public int ProcDetectScan(PTType Modes, int DetectOnly = 0) { if (Phase >= 1 && OverRidePhase) { return(Processes.Count()); } scan.ScanMode = Modes; var rv = scan.Analyze(DetectOnly); foreach (var p in scan.DetectedProcesses.Values) { Processes.Add(p); } Phase = 2; return(rv); }
/// <summary> /// This routine is fairly expensive, maybe unnecessary as well but it demo's walking the page table + EPT. /// You can connect an address space dumper really easily /// </summary> /// <param name="MemSpace">The list of VMCS/EPTP configurations which will alter the page table use</param> /// <param name="Procs">Detected procs to query</param> /// <param name="pTypes">Type bitmas to interpreate</param> public List<DetectedProc> ExtrtactAddressSpaces(IOrderedEnumerable<VMCS> MemSpace = null, ConcurrentBag<DetectedProc> Procs = null, PTType pTypes = PTType.UNCONFIGURED) { List<DetectedProc> rvList = new List<DetectedProc>(); var PT2Scan = pTypes == PTType.UNCONFIGURED ? (PTType.Windows | PTType.HyperV | PTType.GENERIC) : pTypes; var procList = Procs == null ? Processes : Procs; //var memSpace = MemSpace == null ? VMCSs.First() : MemSpace.First(); var memSpace = MemSpace == null ? VMCSs.GroupBy(x => x.EPTP).Select(xg => xg.First()) : MemSpace; var p = from proc in Processes where (((proc.PageTableType & PT2Scan) == proc.PageTableType)) orderby proc.CR3Value ascending select proc; int pcnt = Processes.Count(); int vmcnt = memSpace.Count(); var tot = pcnt * vmcnt; var curr = 0; var AlikelyKernelSet = from ptes in p.First().TopPageTablePage where ptes.Key > 255 && MagicNumbers.Each.All(ppx => ppx != ptes.Key) select ptes.Value; Console.ForegroundColor = ConsoleColor.Yellow; WriteLine($"assessing {tot} address spaces"); ProgressBarz.Progress = 0; var VMCSTriage = new Dictionary<VMCS, int>(); //Parallel.ForEach(memSpace, (space) => foreach (var space in memSpace) { // we do it this way so that parallelized tasks do not interfere with each other // overall it may blow the cache hit ratio but will tune a single task to see the larger/better cache // versus multicore, my suspicion is that multi-core is better using (var memAxs = new Mem(MemFile, null, DetectedDesc)) { var sx = 0; foreach (var px in p) //Parallel.ForEach(p, (proc) => { try { var proc = px.Clone<DetectedProc>(); proc.vmcs = space; var pt = PageTable.AddProcess(proc, memAxs, false); if (pt != null && VerboseOutput) { // If we used group detection correlation a valid EPTP should work for every process if (proc.vmcs != null && proc.PT.RootPageTable.PFNCount > proc.TopPageTablePage.Count()) { WriteLine($"Virtualized Process PT Entries [{proc.PT.RootPageTable.PFNCount}] Type [{proc.PageTableType}] PID [{proc.vmcs.EPTP:X}:{proc.CR3Value:X}]"); rvList.Add(proc); } else { WriteLine($"canceling evaluation of bad EPTP for this group"); foreach (var pxc in Processes) pxc.vmcs = null; break; } sx++; curr++; var progress = Convert.ToInt32((Convert.ToDouble(curr) / Convert.ToDouble(tot) * 100.0) + 0.5); ProgressBarz.RenderConsoleProgress(progress); } } catch (ExtendedPageNotFoundException eptpX) { WriteLine($"Bad EPTP selection;{Environment.NewLine}\tEPTP:{eptpX.RequestedEPTP}{Environment.NewLine}\t CR3:{eptpX.RequestedCR3}{Environment.NewLine} Attempting to skip to next proc."); memAxs.DumpPFNIndex(); } catch (MemoryRunMismatchException mrun) { WriteLine($"Error in accessing memory for PFN {mrun.PageRunNumber:X16}"); memAxs.DumpPFNIndex(); } catch (PageNotFoundException pnf) { WriteLine($"Error in selecting page, see {pnf}"); memAxs.DumpPFNIndex(); } catch (Exception ex) { WriteLine($"Error in memspace extraction: {ex.ToString()}"); memAxs.DumpPFNIndex(); } WriteLine($"{sx} VMCS dominated process address spaces and were decoded succsessfully."); //}); } } } //}); using (var memAxs = new Mem(MemFile, null, DetectedDesc)) { var nonVMCSprocs = from px in Processes where px.vmcs == null select px; foreach (var px in nonVMCSprocs) { var pmetal = px.Clone<DetectedProc>(); // this is a process on the bare metal var pt = PageTable.AddProcess(pmetal, memAxs, false); WriteLine($"Process {pmetal.CR3Value:X16} Physical walk w/o SLAT yielded {pmetal.PT.RootPageTable.PFNCount} entries"); rvList.Add(pmetal); } } return rvList; }
/// <summary> /// Group address spaces into related buckets /// /// We will assign an address space ID to each detected proc so we know what process belongs with who /// After AS grouping we will know what EPTP belongs to which AS since one of the DP's will have it's CR3 in the VMCS /// </summary> /// <param name="pTypes">Types to scan for, this is of the already detected processs list so it's already filtered really</param> public void GroupAS(PTType pTypes = PTType.UNCONFIGURED) { var PT2Scan = pTypes == PTType.UNCONFIGURED ? PTType.ALL : pTypes; if (Phase >=3 && OverRidePhase) return; // To join an AS group we want to see > 50% corelation which is a lot considering were only interperating roughly 10-20 values (more like 12) var p = from proc in Processes where (((proc.PageTableType & PT2Scan) == proc.PageTableType)) orderby proc.CR3Value ascending select proc; ASGroups = new Dictionary<int, List<DetectedProc>>(); // we trim out the known recursive/self entries since they will naturally not be equivalent var AlikelyKernelSet = from ptes in p.First().TopPageTablePage where ptes.Key > 255 && MagicNumbers.Each.All(ppx => ppx != ptes.Key) select ptes.Value; int totUngrouped = Processes.Count(); int CurrASID = 1; var grouped = new List<DetectedProc>(); ASGroups[CurrASID] = new List<DetectedProc>(); while (true) { ForegroundColor = ConsoleColor.Yellow; WriteLine("Scanning for group correlations"); ForegroundColor = ConsoleColor.Cyan; foreach (var proc in p) { var currKern = from ptes in proc.TopPageTablePage where ptes.Key > 255 && MagicNumbers.Each.All(ppx => ppx != ptes.Key) select ptes.Value; var interSection = currKern.Intersect(AlikelyKernelSet); var corralated = interSection.Count() * 1.00 / AlikelyKernelSet.Count(); if (corralated > 0.50 && !ASGroups[CurrASID].Contains(proc)) { WriteLine($"MemberProces: Group {CurrASID} Type [{proc.PageTableType}] GroupCorrelation [{corralated:P3}] PID [{proc.CR3Value:X}]"); proc.AddressSpaceID = CurrASID; ASGroups[CurrASID].Add(proc); // global list to quickly scan grouped.Add(proc); } } ForegroundColor = ConsoleColor.Yellow; var totGrouped = (from g in ASGroups.Values select g).Sum(x => x.Count()); Console.WriteLine($"Finished Group {CurrASID} collected size {ASGroups[CurrASID].Count()} next group"); // if there is more work todo, setup an entry for testing if (totGrouped < totUngrouped) { CurrASID++; ASGroups[CurrASID] = new List<DetectedProc>(); } else break; // we grouped them all! /// Isolate next un-grouped PageTable var UnGroupedProc = from nextProc in Processes where !grouped.Contains(nextProc) select nextProc; AlikelyKernelSet = from ptes in UnGroupedProc.First().TopPageTablePage where ptes.Key > 255 && MagicNumbers.Each.All(ppx => ppx != ptes.Key) select ptes.Value; } Console.WriteLine($"Done All process groups."); // after grouping link VMCS back to the group who 'discovered' the VMCS in the first place! var eptpz = VMCSs.GroupBy(eptz => eptz.EPTP).Select(ept => ept.First()).ToArray(); // find groups dominated by each vmcs var VMCSGroup = from aspace in ASGroups.Values.AsEnumerable() from ept in eptpz where aspace.Any(adpSpace => adpSpace == ept.dp) select new { AS = aspace, EPTctx = ept }; // link the proc back into the eptp foreach (var ctx in VMCSGroup) foreach (var dp in ctx.AS) dp.vmcs = ctx.EPTctx; Phase = 4; // were good, all Processes should have a VMCS if applicable and be identifiable by AS ID }
public int ProcDetectScan(PTType Modes, int DetectOnly = 0) { if (Phase >= 1 && OverRidePhase) return Processes.Count(); scan.ScanMode = Modes; var rv = scan.Analyze(DetectOnly); foreach (var p in scan.DetectedProcesses.Values) Processes.Add(p); Phase = 2; return rv; }
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> /// This routine is fairly expensive, maybe unnessisary as well but it demo's walking the page table + EPT. /// You can connect an address space dumper really easially /// </summary> /// <param name="MemSpace">The list of VMCS/EPTP configurations which will alter the page table use</param> /// <param name="Procs">Detected procs to query</param> /// <param name="pTypes">Type bitmas to interpreate</param> public void ExtrtactAddressSpaces(IOrderedEnumerable<VMCS> MemSpace = null, ConcurrentBag<DetectedProc> Procs = null, PTType pTypes = PTType.UNCONFIGURED) { var PT2Scan = pTypes == PTType.UNCONFIGURED ? (PTType.Windows | PTType.HyperV | PTType.GENERIC) : pTypes; var procList = Procs == null ? Processes : Procs; //var memSpace = MemSpace == null ? VMCSs.First() : MemSpace.First(); var memSpace = MemSpace == null ? VMCSs.GroupBy(x => x.EPTP).Select(xg => xg.First()) : MemSpace; //.Where(xw => xw.All(xz => xz.EPTP == 0x1138601E)) var p = from proc in Processes where (((proc.PageTableType & PT2Scan) == proc.PageTableType)) orderby proc.CR3Value ascending select proc; int pcnt = Processes.Count(); int vmcnt = memSpace.Count(); int tot = pcnt * vmcnt; int curr = 0; var AlikelyKernelSet = from ptes in p.First().TopPageTablePage where ptes.Key > 255 && MagicNumbers.Each.All(ppx => ppx != ptes.Key) select ptes.Value; Console.ForegroundColor = ConsoleColor.Yellow; WriteLine($"assessing {tot} address spaces"); ProgressBarz.Progress = 0; Parallel.ForEach(memSpace, (space) => //foreach (var space in memSpace) { using (var memAxs = new Mem(MemFile)) { var sx = 0; //foreach (var proc in p) Parallel.ForEach(p, (proc) => { try { proc.vmcs = space; var pt = PageTable.AddProcess(proc, memAxs); if (pt != null && VerboseOutput) { var currKern = from ptes in proc.TopPageTablePage where ptes.Key > 255 && MagicNumbers.Each.All(ppx => ppx != ptes.Key) select ptes.Value; var interSection = currKern.Intersect(AlikelyKernelSet); var corralated = interSection.Count() * 1.00 / AlikelyKernelSet.Count(); WriteLine($"PT Entries [{proc.PT.RootPageTable.PFNCount}] Type [{proc.PageTableType}] GroupCorrelation [{corralated:P3}] PID [{proc.vmcs.EPTP:X}:{proc.CR3Value:X}]"); sx++; curr++; var progress = Convert.ToInt32((Convert.ToDouble(curr) / Convert.ToDouble(tot) * 100.0) + 0.5); ProgressBarz.RenderConsoleProgress(progress); } } catch (ExtendedPageNotFoundException eptpX) { WriteLine($"Bad EPTP selection;{Environment.NewLine}\tEPTP:{eptpX.RequestedEPTP}{Environment.NewLine}\t CR3:{eptpX.RequestedCR3}{Environment.NewLine} Attempting to skip to next proc."); } catch (MemoryRunMismatchException mrun) { WriteLine($"Error in accessing memory for PFN {mrun.PageRunNumber:X16}"); } catch (PageNotFoundException pnf) { WriteLine($"Error in selecting page, see {pnf}"); } catch(Exception ex) { WriteLine($"Error in memspace extraction: {ex.ToString()}"); } WriteLine($"{sx} VMCS dominated process address spaces and were decoded succsessfully."); }); //} } //} }); }
/// <summary> /// This routine is fairly expensive, maybe unnecessary as well but it demo's walking the page table + EPT. /// You can connect an address space dumper really easily /// /// TODO: Remake this. Instead of just pre-buffering everything. Ensure the GroupAS detections are appropriate /// and if not, reassign the VMCS/EPTP page to bare metal or a different HVLayer item. /// </summary> /// <param name="MemSpace">The list of VMCS/EPTP configurations which will alter the page table use</param> /// <param name="Procs">Detected procs to query</param> /// <param name="pTypes">Type bitmask to interpret</param> public Dictionary<int, List<DetectedProc>> ExtrtactAddressSpaces(IOrderedEnumerable<VMCS> MemSpace = null, ConcurrentBag<DetectedProc> Procs = null, PTType pTypes = PTType.UNCONFIGURED) { Dictionary<int, List<DetectedProc>> rvList = new Dictionary<int, List<DetectedProc>>(); var PT2Scan = pTypes == PTType.UNCONFIGURED ? (PTType.Windows | PTType.HyperV | PTType.GENERIC) : pTypes; var procList = Procs == null ? Processes : Procs; //var memSpace = MemSpace == null ? VMCSs.First() : MemSpace.First(); var memSpace = MemSpace == null ? VMCSs.Values.GroupBy(x => x.EPTP).Select(xg => xg.First()) : MemSpace; var ms = from memx in memSpace orderby memx.Offset ascending select memx; int gcnt = ASGroups.Count(); int vmcnt = memSpace.Count(); var tot = gcnt * vmcnt; var curr = 0; bool CollectKernelAS = true; var CurrColor = ForegroundColor; WriteColor(ConsoleColor.White, ConsoleColor.Black, $"assessing {tot} address space combinations"); ProgressBarz.RenderConsoleProgress(0); var VMCSTriage = new Dictionary<VMCS, int>(); //Parallel.ForEach(memSpace, (space) => //foreach (var space in ms) //{ // we do it this way so that parallelized tasks do not interfere with each other // overall it may blow the cache hit ratio but will tune a single task to see the larger/better cache // versus multicore, my suspicion is that multi-core is better //using (var memAxs = new Mem(MemFile, null, DetectedDesc)) var memAxs = MemAccess; { var sx = 0; // assign address space by group foreach (var grp in ASGroups) { // if the group is configured fully, then we know we were successful // since we null out the list if we fail, so skip to next one //if ((rvList.ContainsKey(grp.Key) && rvList[grp.Key] != null) || grp.Value == null) // continue; rvList[grp.Key] = new List<DetectedProc>(); var orderedGroup = from px in grp.Value where ((px.PageTableType & PT2Scan) == px.PageTableType) && px.AddressSpaceID == grp.Key orderby px.CR3Value ascending select procList; //foreach (var proc in from proc in grp.Value // where (((proc.PageTableType & PT2Scan) == proc.PageTableType)) && (proc.AddressSpaceID == grp.Key) // orderby proc.CR3Value ascending // select proc) //foreach(var proc in orderedGroup.SelectMany(x => x)) //Parallel.ForEach(p, (proc) => if (orderedGroup.Count() < 1) continue; var proc = orderedGroup.First().First(); { int i = 0; List<long> tableCounts = new List<long>(); if (proc.CandidateList == null || proc.CandidateList.Count < 1) { if (proc.vmcs != null) proc.CandidateList = new List<VMCS>() { proc.vmcs }; // just set the one else proc.CandidateList = new List<VMCS>() { new VMCS() { EPTP = 0 } }; } // find the best space for this proc foreach (var space in proc.CandidateList) { try { // this is killing memory, probably not needed //var proc = px.Clone<DetectedProc>(); proc.vmcs = space; if (VerboseOutput) WriteLine($"Scanning PT from Type [{proc.PageTableType}] PID [{proc.vmcs.EPTP:X}:{proc.CR3Value:X}] ID{proc.AddressSpaceID} Key{grp.Key}"); var pt = PageTable.AddProcess(proc, memAxs, CollectKernelAS); CollectKernelAS = false; if (pt != null) { // If we used group detection correlation a valid EPTP should work for every process // so if it's bad we skip the entire evaluation if (proc.vmcs != null && proc.PT.Root.Count > proc.TopPageTablePage.Count()) { tableCounts[i++] = proc.PT.Root.Count; WriteLine($"TableCount for VMCS candidate is {proc.PT.Root.Count}"); if (VerboseOutput) WriteLine($"{rvList[grp.Key].Count()} of {grp.Value.Count} Virtualized Process PT Entries [{proc.PT.Root.Count}] Type [{proc.PageTableType}] PID [{proc.vmcs.EPTP:X}:{proc.CR3Value:X}]"); // collect process into now joined EPTP<->Proc rvList[grp.Key].Add(proc); if (rvList[grp.Key].Count() == grp.Value.Count && VerboseOutput) { ForegroundColor = ConsoleColor.Green; WriteLine($"Validated 100% {grp.Value.Count} of detected group {proc.AddressSpaceID}, continuing with next group."); ForegroundColor = CurrColor; break; } } else { // let's just cancel if we haven't done any decodes if (rvList[grp.Key].Count() < 1) { WriteColor(ConsoleColor.Yellow, $"Canceling evaluation of bad EPTP for this group/Address Space ({grp.Key}) a likely bare metal group"); foreach (var p in Processes) if (p.vmcs != null && p.vmcs.EPTP == space.EPTP && p.AddressSpaceID == proc.AddressSpaceID) p.vmcs = null; rvList[grp.Key] = null; } break; } sx++; curr++; } var progress = Convert.ToInt32((Convert.ToDouble(curr) / Convert.ToDouble(tot) * 100.0) + 0.5); ProgressBarz.RenderConsoleProgress(progress); } catch (ExtendedPageNotFoundException eptpX) { WriteLine($"Bad EPTP selection;{Environment.NewLine}\tEPTP:{eptpX.RequestedEPTP}{Environment.NewLine}\t CR3:{eptpX.RequestedCR3}{Environment.NewLine} Attempting to skip to next proc."); memAxs.DumpPFNIndex(); } catch (MemoryRunMismatchException mrun) { WriteLine($"Error in accessing memory for PFN {mrun.PageRunNumber:X12}"); memAxs.DumpPFNIndex(); } catch (PageNotFoundException pnf) { WriteLine($"Error in selecting page, see {pnf}"); memAxs.DumpPFNIndex(); } catch (Exception ex) { WriteLine($"Error in memspace extraction: {ex.ToString()}"); memAxs.DumpPFNIndex(); } WriteLine($"{sx} VMCS dominated process address spaces and were decoded successfully."); //}); } } } } //} //}); CollectKernelAS = true; // a backup to test a non-VMCS //using (var memAxs = new Mem(MemFile, null, DetectedDesc)) //var memAxs = Mem.Instance; { var nonVMCSprocs = from proc in Processes where (((proc.PageTableType & PT2Scan) == proc.PageTableType)) where proc.vmcs == null orderby proc.CR3Value ascending select proc; foreach (var pmetal in nonVMCSprocs) { // unassigned, give them a unique entry for now, we should rerun the grouping method if(pmetal.AddressSpaceID == 0) { var ASID = ASGroups.Count + 1; pmetal.AddressSpaceID = ASID; ASGroups.TryAdd(ASID, new ConcurrentBag<DetectedProc> { pmetal }); } // this is a process on the bare metal var pt = PageTable.AddProcess(pmetal, memAxs, CollectKernelAS); CollectKernelAS = false; WriteLine($"Process {pmetal.CR3Value:X12} Physical walk w/o SLAT yielded {pmetal.PT.Root.Count} entries, bare metal group is {pmetal.AddressSpaceID}"); if (rvList.ContainsKey(pmetal.AddressSpaceID) && rvList[pmetal.AddressSpaceID] == null) rvList[pmetal.AddressSpaceID] = new List<DetectedProc>(); if (rvList.ContainsKey(pmetal.AddressSpaceID)) rvList[pmetal.AddressSpaceID].Add(pmetal); else rvList.Add(pmetal.AddressSpaceID, new List<DetectedProc>()); } } return rvList; }
// TODO: Move above into DetectedProc class methods /// <summary> /// Group address spaces into related buckets /// /// We will assign an address space ID to each detected proc so we know what process belongs with who /// After AS grouping we will know what EPTP belongs to which AS since one of the DP's will have it's CR3 in the VMCS /// /// Yes it's a bit complicated. /// /// The overall procedure however is straight forward in that; /// /// * For every detected process /// Bucket into groups which are the "Address spaces" that initially are /// /// (a) based on kernel address space similarities /// and then /// (b) based on what VMCS value was found pointing to that group /// /// This ensures that if we have several hypervisors with a possibly identical kernel grouping (i.e. the PFN's /// were used by each kernel were identical), they are disambiguated by the VMCS. (Which can be validated later) /// /// The benefit here is that brute forcing at this stage is fairly expensive and can lead to significant overhead, there does /// tend to be some outliers for large systems that need to be looked at more to determine who they belong too. Nevertheless, it's /// inconsequential if they are grouped with the appropriate AS since even if they are isolated into their own 'AS' this is an artificial /// construct for our book keeping. The net result is that even if some process is grouped by itself due to some aggressive variation in /// kernel PFN' use (lots of dual mapped memory/MDL's or something), it's still able to be dumped and analyzed. /// /// /// </summary> /// <param name="pTypes">Types to scan for, this is of the already detected processes list so it's already filtered really</param> public void GroupAS(PTType pTypes = PTType.UNCONFIGURED) { var PT2Scan = pTypes == PTType.UNCONFIGURED ? PTType.ALL : pTypes; //if (Phase >=3 && OverRidePhase) // return; // To join an AS group we want to see > 50% correlation which is a lot considering were only interoperating roughly 10-20 values (more like 12) var p = from proc in Processes where (((proc.PageTableType & PT2Scan) == proc.PageTableType)) orderby proc.CR3Value ascending select proc; ASGroups = new ConcurrentDictionary<int, ConcurrentBag<DetectedProc>>(); // we trim out the known recursive/self entries since they will naturally not be equivalent var AlikelyKernelSet = from ptes in p.First().TopPageTablePage where ptes.Key > 255 && MagicNumbers.Each.All(ppx => ppx != ptes.Key) select ptes.Value; int totUngrouped = Processes.Count(); int CurrASID = 1; int LastGroupTotal = 0; var grouped = new ConcurrentBag<DetectedProc>(); WriteLine($"Scanning for group correlations total processes {totUngrouped}"); ASGroups[CurrASID] = new ConcurrentBag<DetectedProc>(); while (true) { ForegroundColor = ConsoleColor.Yellow; Parallel.ForEach(p, (proc) => { var currKern = from ptes in proc.TopPageTablePage where ptes.Key > 255 && MagicNumbers.Each.All(ppx => ppx != ptes.Key) select ptes.Value; var interSection = currKern.Intersect(AlikelyKernelSet); var correlated = interSection.Count() * 1.00 / AlikelyKernelSet.Count(); // add this detected CR3/process address space to an address space grouping when // the kernel range is above the acceptable threshold, the group does not contain this proc // and this proc is not already joined into another group if (correlated > GroupThreshold && !ASGroups[CurrASID].Contains(proc) && proc.AddressSpaceID == 0) { WriteColor(ConsoleColor.Cyan, $"MemberProces: Group {CurrASID} Type [{proc.PageTableType}] GroupCorrelation [{correlated:P3}] PID [{proc.CR3Value:X}]"); proc.AddressSpaceID = CurrASID; ASGroups[CurrASID].Add(proc); // global list to quickly scan grouped.Add(proc); } }); ForegroundColor = ConsoleColor.Yellow; var totGrouped = (from g in ASGroups.Values select g).Sum(x => x.Count()); WriteLine($"Finished Group {CurrASID} collected size {ASGroups[CurrASID].Count()}, continuing to group"); // if there is more work todo, setup an entry for testing if (totGrouped < totUngrouped) { // if we wind up here // there has been no forward progress in isolating further groups if(LastGroupTotal == totGrouped) { ForegroundColor = ConsoleColor.Red; WriteLine($"Terminating with non-grouped process candidates. GroupThreshold may be too high. {GroupThreshold}"); var pz = from px in Processes where px.AddressSpaceID == 0 select px; // just add the ungrouped processes as a single each bare metal // unless it has an existing VMCS pointer foreach (var px in pz) { WriteLine(px); CurrASID++; px.AddressSpaceID = CurrASID; ASGroups[CurrASID] = new ConcurrentBag<DetectedProc>() { px }; var isCandidate = from pvmcs in scan.HVLayer where pvmcs.gCR3 == px.CR3Value select pvmcs; if (isCandidate.Count() > 0) { px.CandidateList = new List<VMCS>(isCandidate); px.vmcs = px.CandidateList.First(); WriteColor( ConsoleColor.White, $"Detected ungrouped {px.CR3Value} as a candidate under {px.CandidateList.Count()} values (first){px.vmcs.EPTP}"); } } ForegroundColor = ConsoleColor.Yellow; break; } CurrASID++; ASGroups[CurrASID] = new ConcurrentBag<DetectedProc>(); WriteLine($"grouped count ({totGrouped}) is less than total process count ({totUngrouped}, rescanning...)"); LastGroupTotal = totGrouped; } else break; // we grouped them all! /// Isolate next un-grouped PageTable var UnGroupedProc = from nextProc in Processes where !grouped.Contains(nextProc) select nextProc; AlikelyKernelSet = from ptes in UnGroupedProc.First().TopPageTablePage where ptes.Key > 255 && MagicNumbers.Each.All(ppx => ppx != ptes.Key) select ptes.Value; } Console.WriteLine($"Done All process groups."); // after grouping link VMCS back to the group who 'discovered' the VMCS in the first place! var eptpz = VMCSs.Values.GroupBy(eptz => eptz.EPTP).OrderBy(eptx => eptx.Key).Select(ept => ept.First()).ToArray(); // find groups dominated by each vmcs var VMCSGroup = from aspace in ASGroups.AsEnumerable() from ept in eptpz where aspace.Value.Any(adpSpace => adpSpace == ept.dp) select new { AS = aspace, EPTctx = ept }; // link the proc back into the eptp foreach (var ctx in VMCSGroup) foreach (var dp in ctx.AS.Value) { if(dp.CandidateList == null) dp.CandidateList = new List<VMCS>(); dp.vmcs = ctx.EPTctx; dp.CandidateList.Add(ctx.EPTctx); } // resort by CR3 foreach (var ctx in ASGroups.Values) { var dpz = from d in ctx orderby d.CR3Value descending select d; if (dpz.Count() >= 1) { var aspace = dpz.First().AddressSpaceID; ASGroups[aspace] = new ConcurrentBag<DetectedProc>(dpz); } } Phase = 4; // were good, all Processes should have a VMCS if applicable and be identifiable by AS ID }
/// <summary> /// This routine is fairly expensive, maybe unnecessary as well but it demo's walking the page table + EPT. /// You can connect an address space dumper really easily /// </summary> /// <param name="MemSpace">The list of VMCS/EPTP configurations which will alter the page table use</param> /// <param name="Procs">Detected procs to query</param> /// <param name="pTypes">Type bitmas to interpreate</param> public List <DetectedProc> ExtrtactAddressSpaces(IOrderedEnumerable <VMCS> MemSpace = null, ConcurrentBag <DetectedProc> Procs = null, PTType pTypes = PTType.UNCONFIGURED) { List <DetectedProc> rvList = new List <DetectedProc>(); var PT2Scan = pTypes == PTType.UNCONFIGURED ? (PTType.Windows | PTType.HyperV | PTType.GENERIC) : pTypes; var procList = Procs == null ? Processes : Procs; //var memSpace = MemSpace == null ? VMCSs.First() : MemSpace.First(); var memSpace = MemSpace == null?VMCSs.GroupBy(x => x.EPTP).Select(xg => xg.First()) : MemSpace; var p = from proc in Processes where (((proc.PageTableType & PT2Scan) == proc.PageTableType)) orderby proc.CR3Value ascending select proc; int pcnt = Processes.Count(); int vmcnt = memSpace.Count(); var tot = pcnt * vmcnt; var curr = 0; var AlikelyKernelSet = from ptes in p.First().TopPageTablePage where ptes.Key > 255 && MagicNumbers.Each.All(ppx => ppx != ptes.Key) select ptes.Value; Console.ForegroundColor = ConsoleColor.Yellow; WriteLine($"assessing {tot} address spaces"); ProgressBarz.Progress = 0; var VMCSTriage = new Dictionary <VMCS, int>(); //Parallel.ForEach(memSpace, (space) => foreach (var space in memSpace) { // we do it this way so that parallelized tasks do not interfere with each other // overall it may blow the cache hit ratio but will tune a single task to see the larger/better cache // versus multicore, my suspicion is that multi-core is better using (var memAxs = new Mem(MemFile, null, DetectedDesc)) { var sx = 0; foreach (var px in p) //Parallel.ForEach(p, (proc) => { try { var proc = px.Clone <DetectedProc>(); proc.vmcs = space; var pt = PageTable.AddProcess(proc, memAxs, false); if (pt != null && VerboseOutput) { // If we used group detection correlation a valid EPTP should work for every process if (proc.vmcs != null && proc.PT.RootPageTable.PFNCount > proc.TopPageTablePage.Count()) { WriteLine($"Virtualized Process PT Entries [{proc.PT.RootPageTable.PFNCount}] Type [{proc.PageTableType}] PID [{proc.vmcs.EPTP:X}:{proc.CR3Value:X}]"); rvList.Add(proc); } else { WriteLine($"canceling evaluation of bad EPTP for this group"); foreach (var pxc in Processes) { pxc.vmcs = null; } break; } sx++; curr++; var progress = Convert.ToInt32((Convert.ToDouble(curr) / Convert.ToDouble(tot) * 100.0) + 0.5); ProgressBarz.RenderConsoleProgress(progress); } } catch (ExtendedPageNotFoundException eptpX) { WriteLine($"Bad EPTP selection;{Environment.NewLine}\tEPTP:{eptpX.RequestedEPTP}{Environment.NewLine}\t CR3:{eptpX.RequestedCR3}{Environment.NewLine} Attempting to skip to next proc."); memAxs.DumpPFNIndex(); } catch (MemoryRunMismatchException mrun) { WriteLine($"Error in accessing memory for PFN {mrun.PageRunNumber:X16}"); memAxs.DumpPFNIndex(); } catch (PageNotFoundException pnf) { WriteLine($"Error in selecting page, see {pnf}"); memAxs.DumpPFNIndex(); } catch (Exception ex) { WriteLine($"Error in memspace extraction: {ex.ToString()}"); memAxs.DumpPFNIndex(); } WriteLine($"{sx} VMCS dominated process address spaces and were decoded succsessfully."); //}); } } } //}); using (var memAxs = new Mem(MemFile, null, DetectedDesc)) { var nonVMCSprocs = from px in Processes where px.vmcs == null select px; foreach (var px in nonVMCSprocs) { var pmetal = px.Clone <DetectedProc>(); // this is a process on the bare metal var pt = PageTable.AddProcess(pmetal, memAxs, false); WriteLine($"Process {pmetal.CR3Value:X16} Physical walk w/o SLAT yielded {pmetal.PT.RootPageTable.PFNCount} entries"); rvList.Add(pmetal); } } return(rvList); }
/// <summary> /// Group address spaces into related buckets /// /// We will assign an address space ID to each detected proc so we know what process belongs with who /// After AS grouping we will know what EPTP belongs to which AS since one of the DP's will have it's CR3 in the VMCS /// </summary> /// <param name="pTypes">Types to scan for, this is of the already detected processs list so it's already filtered really</param> public void GroupAS(PTType pTypes = PTType.UNCONFIGURED) { var PT2Scan = pTypes == PTType.UNCONFIGURED ? PTType.ALL : pTypes; if (Phase >= 3 && OverRidePhase) { return; } // To join an AS group we want to see > 50% corelation which is a lot considering were only interperating roughly 10-20 values (more like 12) var p = from proc in Processes where (((proc.PageTableType & PT2Scan) == proc.PageTableType)) orderby proc.CR3Value ascending select proc; ASGroups = new Dictionary <int, List <DetectedProc> >(); // we trim out the known recursive/self entries since they will naturally not be equivalent var AlikelyKernelSet = from ptes in p.First().TopPageTablePage where ptes.Key > 255 && MagicNumbers.Each.All(ppx => ppx != ptes.Key) select ptes.Value; int totUngrouped = Processes.Count(); int CurrASID = 1; var grouped = new List <DetectedProc>(); ASGroups[CurrASID] = new List <DetectedProc>(); while (true) { ForegroundColor = ConsoleColor.Yellow; WriteLine("Scanning for group correlations"); ForegroundColor = ConsoleColor.Cyan; foreach (var proc in p) { var currKern = from ptes in proc.TopPageTablePage where ptes.Key > 255 && MagicNumbers.Each.All(ppx => ppx != ptes.Key) select ptes.Value; var interSection = currKern.Intersect(AlikelyKernelSet); var corralated = interSection.Count() * 1.00 / AlikelyKernelSet.Count(); if (corralated > 0.50 && !ASGroups[CurrASID].Contains(proc)) { WriteLine($"MemberProces: Group {CurrASID} Type [{proc.PageTableType}] GroupCorrelation [{corralated:P3}] PID [{proc.CR3Value:X}]"); proc.AddressSpaceID = CurrASID; ASGroups[CurrASID].Add(proc); // global list to quickly scan grouped.Add(proc); } } ForegroundColor = ConsoleColor.Yellow; var totGrouped = (from g in ASGroups.Values select g).Sum(x => x.Count()); Console.WriteLine($"Finished Group {CurrASID} collected size {ASGroups[CurrASID].Count()} next group"); // if there is more work todo, setup an entry for testing if (totGrouped < totUngrouped) { CurrASID++; ASGroups[CurrASID] = new List <DetectedProc>(); } else { break; // we grouped them all! } /// Isolate next un-grouped PageTable var UnGroupedProc = from nextProc in Processes where !grouped.Contains(nextProc) select nextProc; AlikelyKernelSet = from ptes in UnGroupedProc.First().TopPageTablePage where ptes.Key > 255 && MagicNumbers.Each.All(ppx => ppx != ptes.Key) select ptes.Value; } Console.WriteLine($"Done All process groups."); // after grouping link VMCS back to the group who 'discovered' the VMCS in the first place! var eptpz = VMCSs.GroupBy(eptz => eptz.EPTP).Select(ept => ept.First()).ToArray(); // find groups dominated by each vmcs var VMCSGroup = from aspace in ASGroups.Values.AsEnumerable() from ept in eptpz where aspace.Any(adpSpace => adpSpace == ept.dp) select new { AS = aspace, EPTctx = ept }; // link the proc back into the eptp foreach (var ctx in VMCSGroup) { foreach (var dp in ctx.AS) { dp.vmcs = ctx.EPTctx; } } Phase = 4; // were good, all Processes should have a VMCS if applicable and be identifiable by AS ID }