public static PageTable AddProcess(DetectedProc dp, Mem mem) { long Address = 0; int AddressIndex = 0; // dump Page table high to low var va = new VIRTUAL_ADDRESS(long.MaxValue - 0xfff); var rv = new PageTable { Failed = new List<HARDWARE_ADDRESS_ENTRY>(), DP = dp, mem = mem }; // TODO: encode VA's for self/recursive physical addr's if (dp.PageTableType == PTType.Windows) { Address = MagicNumbers.Windows_SelfAsVA; AddressIndex = MagicNumbers.Windows_SelfPtr; } // any output is error/warning output var cnt = rv.FillTable(new VIRTUAL_ADDRESS(Address), AddressIndex, dp.CR3Value); Debug.WriteLine($"extracted {cnt} PTE from process {dp.vmcs.EPTP:X16}:{dp.CR3Value:X16}"); dp.PT = rv; return rv; }
long FillTable(VIRTUAL_ADDRESS VA, int PageIndex, long CR3, bool OnlyUserSpace = false) { var entries = 0L; var PageTable = new Dictionary <VIRTUAL_ADDRESS, PFN>(); // We can just pick up the top level page to speed things up // we've already visited this page so were not going to waste time looking up the VA->PA //var page = new long[512]; //mem.GetPageFromFileOffset(FileOffset, ref page); // clear out the VA for the other indexes since were looking at the top level VA.Address = 0; foreach (var kvp in DP.TopPageTablePage) { // Only extract user portion, kernel will be mostly redundant if (OnlyUserSpace && kvp.Key >= 256) { continue; } // were at the top level (4th) VA.PML4 = kvp.Key; var pfn = new PFN(kvp.Value, VA.Address, CR3, DP.vmcs == null ? 0 : DP.vmcs.EPTP); PageTable.Add(VA, pfn); entries++; } // simulated top entry RootPageTable = new PFN(DP.TopPageTablePage[PageIndex], VA.Address, CR3, DP.vmcs == null ? 0 : DP.vmcs.EPTP) { SubTables = PageTable }; // descend the remaining levels // if we find nothing, we can be sure the value were using for EPTP or CR3 is bogus var new_entries = InlineExtract(RootPageTable, 3); if (new_entries == 0) { return(0); } entries += new_entries; // a hint for the full count of entries extracted RootPageTable.PFNCount = entries; return(entries); }
public static PageTable AddProcess(DetectedProc dp, Mem mem, bool OnlyUserSpace = false) { long Address = 0; int AddressIndex = 0; // dump Page table high to low var va = new VIRTUAL_ADDRESS(long.MaxValue - 0xfff); var rv = new PageTable { Failed = new List <HARDWARE_ADDRESS_ENTRY>(), DP = dp, mem = mem }; // TODO: encode VA's for self/recursive physical addr's if (dp.PageTableType == PTType.Windows) { Address = MagicNumbers.Windows_SelfAsVA; AddressIndex = MagicNumbers.Windows_SelfPtr; } // any output is error/warning output var cnt = rv.FillTable(new VIRTUAL_ADDRESS(Address), AddressIndex, dp.CR3Value, OnlyUserSpace); if (cnt == 0) { if (dp.vmcs != null) { WriteLine($"BAD EPTP/DirectoryTable Base {dp.vmcs.EPTP:X16}, try a different candidate or this dump may lack a hypervisor. Attempting PT walk W/O SLAT"); } else { WriteLine($"Decoding failed for {dp.CR3Value:X16}"); } /*cnt = rv.FillTable(new VIRTUAL_ADDRESS(Address), AddressIndex, dp.CR3Value, OnlyUserSpace); * WriteLine($"Physical walk w/o SLAT yielded {cnt} entries");*/ } dp.PT = rv; return(rv); }
public static PageTable AddProcess(DetectedProc dp, Mem mem, bool OnlyUserSpace = false) { long Address = 0; int AddressIndex = 0; // dump Page table high to low var va = new VIRTUAL_ADDRESS(long.MaxValue - 0xfff); var rv = new PageTable { Failed = new List<HARDWARE_ADDRESS_ENTRY>(), DP = dp, mem = mem }; // TODO: encode VA's for self/recursive physical addr's if (dp.PageTableType == PTType.Windows) { Address = MagicNumbers.Windows_SelfAsVA; AddressIndex = MagicNumbers.Windows_SelfPtr; } // any output is error/warning output var cnt = rv.FillTable(new VIRTUAL_ADDRESS(Address), AddressIndex, dp.CR3Value, OnlyUserSpace); if (cnt == 0) { if (dp.vmcs != null) WriteLine($"BAD EPTP/DirectoryTable Base {dp.vmcs.EPTP:X16}, try a different candidate or this dump may lack a hypervisor. Attempting PT walk W/O SLAT"); else WriteLine($"Decoding failed for {dp.CR3Value:X16}"); /*cnt = rv.FillTable(new VIRTUAL_ADDRESS(Address), AddressIndex, dp.CR3Value, OnlyUserSpace); WriteLine($"Physical walk w/o SLAT yielded {cnt} entries");*/ } dp.PT = rv; return rv; }
// Translates virtual address to physical address by way of CR3->EPTP double dereferencing (up to 24 loads) public HARDWARE_ADDRESS_ENTRY VirtualToPhysical(HARDWARE_ADDRESS_ENTRY eptp, HARDWARE_ADDRESS_ENTRY aCR3, long Addr) { var rv = HARDWARE_ADDRESS_ENTRY.MinAddr; var va = new VIRTUAL_ADDRESS(Addr); var gVa = new VIRTUAL_ADDRESS(aCR3.PTE); var Attempted = HARDWARE_ADDRESS_ENTRY.MinAddr; var ConvertedV2hP = new List <HARDWARE_ADDRESS_ENTRY>(); try { Attempted = gVa.Address; //convert Guest CR3 gPA into Host CR3 pPA var gpaCR3 = VirtualToPhysical(eptp, gVa.Address); //Console.WriteLine($"In V2P2P, using CR3 {aCR3.PTE:X16}, found guest phys CR3 {gpaCR3.PTE:X16}, attempting load of PML4E from {(gpaCR3 | va.PML4):X16}"); // gPML4E - as we go were getting gPA's which need to pPA Attempted = gpaCR3.NextTableAddress | va.PML4; var gPML4E = (HARDWARE_ADDRESS_ENTRY)GetValueAtPhysicalAddr(Attempted); ConvertedV2hP.Add(gPML4E); //Console.WriteLine($"guest PML4E = {gPML4E}"); // take CR3 and extract gPhys for VA we want to query var hPML4E = VirtualToPhysical(eptp, gPML4E.NextTableAddress); if (EPTP.IsValid(hPML4E.PTE) && EPTP.IsValid2(hPML4E.PTE) && HARDWARE_ADDRESS_ENTRY.IsBadEntry(hPML4E)) { Attempted = hPML4E.NextTableAddress | (va.DirectoryPointerOffset << 3); var gPDPTE = (HARDWARE_ADDRESS_ENTRY)GetValueAtPhysicalAddr(Attempted); ConvertedV2hP.Add(gPDPTE); var hPDPTE = VirtualToPhysical(eptp, gPDPTE.NextTableAddress); if (EPTP.IsValid(hPDPTE.PTE)) { if (!EPTP.IsLargePDPTE(hPDPTE.PTE)) { if (EPTP.IsValid2(hPDPTE.PTE)) { Attempted = hPDPTE.NextTableAddress | (va.DirectoryOffset << 3); var gPDE = (HARDWARE_ADDRESS_ENTRY)GetValueAtPhysicalAddr(Attempted); ConvertedV2hP.Add(gPDE); var hPDE = VirtualToPhysical(eptp, gPDE.NextTableAddress); if (EPTP.IsValid(hPDE.PTE)) { if (!EPTP.IsLargePDE(hPDE.PTE)) { if (EPTP.IsValid2(hPDE.PTE)) { Attempted = hPDE.NextTableAddress | (va.TableOffset << 3); var gPTE = (HARDWARE_ADDRESS_ENTRY)GetValueAtPhysicalAddr(Attempted); ConvertedV2hP.Add(gPTE); var hPTE = VirtualToPhysical(eptp, gPTE.NextTableAddress); if (EPTP.IsValidEntry(hPTE.PTE)) { rv = hPTE.NextTableAddress | va.Offset; } } } else { rv = (hPDE.PTE & 0xFFFFFFE00000) | va.TableOffset; //(Addr & 0x1FFFFF); } } } } else { rv = (hPDPTE.PTE & 0xFFFFC0000000) | va.DirectoryOffset; //(Addr & 0x3FFFFFFF); } } } } catch (PageNotFoundException ex) { throw new ExtendedPageNotFoundException( $"V2gP2hP conversion error. EPTP:{eptp}, CR3:{aCR3}, Requesting:{Attempted} Step:{ConvertedV2hP.Count()}. Step of 0 may indicate invalid EPTP.{Environment.NewLine}" , eptp, aCR3, Attempted, ConvertedV2hP, ex); } return(rv); }
// Translates virtual address to physical address (normal CR3 path) // Since Valid & Read access overlap each other for EPT and normal PT go through this path for both public HARDWARE_ADDRESS_ENTRY VirtualToPhysical(HARDWARE_ADDRESS_ENTRY aCR3, long Addr) { var rv = HARDWARE_ADDRESS_ENTRY.MaxAddr; var va = new VIRTUAL_ADDRESS(Addr); var ConvertedV2P = new List <HARDWARE_ADDRESS_ENTRY>(); var Attempted = HARDWARE_ADDRESS_ENTRY.MinAddr; //Console.WriteLine($"V2P CR3 = {aCR3.PTE:X16} VA = {va}"); // PML4E try { Attempted = (HARDWARE_ADDRESS_ENTRY)aCR3.NextTableAddress | (va.PML4 << 3); var PML4E = (HARDWARE_ADDRESS_ENTRY)GetValueAtPhysicalAddr(Attempted); //Console.WriteLine($"PML4E = {PML4E.PTE:X16}"); ConvertedV2P.Add(PML4E); if (PML4E.Valid) { Attempted = PML4E.NextTableAddress | (va.DirectoryPointerOffset << 3); var PDPTE = (HARDWARE_ADDRESS_ENTRY)GetValueAtPhysicalAddr(Attempted); ConvertedV2P.Add(PDPTE); //Console.WriteLine($"PDPTE = {PDPTE.PTE:X16}"); if (PDPTE.Valid) { if (!PDPTE.LargePage) { Attempted = PDPTE.NextTableAddress | (va.DirectoryOffset << 3); var PDE = (HARDWARE_ADDRESS_ENTRY)GetValueAtPhysicalAddr(Attempted); ConvertedV2P.Add(PDE); //Console.WriteLine($"PDE = {PDE.PTE:X16}"); if (PDE.Valid) { if (!PDE.LargePage) { Attempted = PDE.NextTableAddress | (va.TableOffset << 3); var PTE = (HARDWARE_ADDRESS_ENTRY)GetValueAtPhysicalAddr(Attempted); ConvertedV2P.Add(PTE); //Console.WriteLine($"PTE = {PTE.PTE:X16}"); //rv = PTE; // page is normal 4kb if (PTE.Valid) { rv = PTE.PTE | (PTE.NextTableAddress | va.Offset); } else { rv.Valid = false; } } else { // we have a 2MB page rv = PDE.PTE | ((PDE.PTE & 0xFFFFFFE00000) | va.TableOffset << 12); } } else { rv.Valid = false; } } else { // we have a 1GB page rv = PDPTE.PTE | ((PDPTE.PTE & 0xFFFFC0000000) | va.DirectoryOffset << 12 << 9); //rv = PDPTE; } } else { rv.Valid = false; } } else { rv.Valid = false; } } catch (Exception ex) { rv.Valid = false; } finally { //foreach(var paddr in ConvertedV2P) //{ //} } //Console.WriteLine($"return from V2P {rv:X16}"); // serialize the dictionary out return(rv); }
/// <summary> /// An Inline extraction for the page table hierarchy. /// Why not let the compiler do it? I have code clones here? /// /// I guess so, but we can see/deal with the subtle differences at each level here as we implement them. /// e.g. some levels have LargePage bits and we may also lay over other CPU modes here like 32 in 64 etc.. /// </summary> /// <param name="top"></param> /// <param name="Level"></param> /// <returns></returns> long InlineExtract(PFN top, int Level) { if (Level == 0) { return(0); } var entries = 0L; var VA = new VIRTUAL_ADDRESS(top.VA); //WriteLine($"4: Scanning {top.PageTable:X16}"); var hPA = HARDWARE_ADDRESS_ENTRY.MinAddr; var SLAT = top.SLAT; var CR3 = top.PageTable; // pull level 4 entries attach level 3 foreach (var top_sub in top.SubTables) { // scan each page for the level 4 entry var PTE = top_sub.Value.PTE; // we don't need to | in the PML4 AO (address offset) since were pulling down the whole page not just the one value // and were going to brute force our way through the entire table so this was just confusing things. var l3HW_Addr = PTE.NextTableAddress; // | top_sub.Key.PML4; // if we have an EPTP use it and request resolution of the HW_Addr if (SLAT != 0) { var hl3HW_Addr = HARDWARE_ADDRESS_ENTRY.MaxAddr; try { hl3HW_Addr = mem.VirtualToPhysical(SLAT, l3HW_Addr); } catch (Exception) { if (Vtero.DiagOutput) { WriteLine($"level3: Failed lookup {l3HW_Addr:X16}"); } } l3HW_Addr = hl3HW_Addr; } if (SLAT != 0 && (l3HW_Addr == long.MaxValue || l3HW_Addr == long.MaxValue - 1)) { continue; } // copy VA since were going to be making changes var s3va = new VIRTUAL_ADDRESS(top_sub.Key.Address); //WriteLine($"3: Scanning {s3va.Address:X16}"); top_sub.Value.hostPTE = l3HW_Addr; // cache translated value var lvl3_page = new long[512]; // extract the l3 page for each PTEEntry we had in l4 try { mem.GetPageForPhysAddr(l3HW_Addr, ref lvl3_page); } catch (Exception) { if (Vtero.DiagOutput) { WriteLine($"level3: Failed lookup {l3HW_Addr:X16}"); } } if (lvl3_page == null) { continue; } for (uint i3 = 0; i3 < 512; i3++) { if (lvl3_page[i3] == 0) { continue; } var l3PTE = new HARDWARE_ADDRESS_ENTRY(lvl3_page[i3]); // adjust VA to match extracted page entries s3va.DirectoryPointerOffset = i3; // save 'PFN' entry into sub-table I should really revisit all these names var l3PFN = new PFN(l3PTE, s3va.Address, CR3, SLAT); top_sub.Value.SubTables.Add(s3va, l3PFN); entries++; /// TODO: Double check if this is a real bit... /// I added it to help weed out some failure cases if (!l3PTE.LargePage) { // get the page that the current l3PFN describes var l2HW_Addr = l3PTE.NextTableAddress; if (SLAT != 0) { var hl2HW_Addr = HARDWARE_ADDRESS_ENTRY.MaxAddr; try { hl2HW_Addr = mem.VirtualToPhysical(SLAT, l2HW_Addr); } catch (Exception ex) { if (Vtero.DiagOutput) { WriteLine($"level2: Unable to V2P {l3PTE}"); } } l2HW_Addr = hl2HW_Addr; } // TODO: more error handling of exceptions & bad returns // TODO: support software PTE types if (l2HW_Addr == HARDWARE_ADDRESS_ENTRY.MaxAddr) { continue; } l3PFN.hostPTE = l2HW_Addr; var lvl2_page = new long[512]; try { mem.GetPageForPhysAddr(l2HW_Addr, ref lvl2_page); } catch (Exception ex) { if (Vtero.DiagOutput) { WriteLine($"level2: Failed lookup {l2HW_Addr:X16}"); } } if (lvl2_page == null) { continue; } // copy VA var s2va = new VIRTUAL_ADDRESS(s3va.Address); //WriteLine($"2: Scanning {s2va.Address:X16}"); // extract PTE's for each set entry for (uint i2 = 0; i2 < 512; i2++) { if (lvl2_page[i2] == 0) { continue; } var l2PTE = new HARDWARE_ADDRESS_ENTRY(lvl2_page[i2]); s2va.DirectoryOffset = i2; var l2PFN = new PFN(l2PTE, s2va.Address, CR3, SLAT) { Type = PFNType.Data }; l3PFN.SubTables.Add(s2va, l2PFN); entries++; if (!l2PTE.LargePage) { var l1HW_Addr = l2PTE.NextTableAddress; if (SLAT != 0) { var hl1HW_Addr = HARDWARE_ADDRESS_ENTRY.MaxAddr; try { hl1HW_Addr = mem.VirtualToPhysical(SLAT, l1HW_Addr); } catch (Exception ex) { if (Vtero.DiagOutput) { WriteLine($"level1: Unable to V2P {l2PTE}"); } } l1HW_Addr = hl1HW_Addr; } if (l1HW_Addr == HARDWARE_ADDRESS_ENTRY.MaxAddr) { continue; } l2PFN.hostPTE = l1HW_Addr; var lvl1_page = new long[512]; try { mem.GetPageForPhysAddr(l1HW_Addr, ref lvl1_page); } catch (Exception ex) { if (Vtero.DiagOutput) { WriteLine($"level1: Failed lookup {l1HW_Addr:X16}"); } } if (lvl1_page == null) { continue; } var s1va = new VIRTUAL_ADDRESS(s2va.Address); //WriteLine($"1: Scanning {s1va.Address:X16}"); for (uint i1 = 0; i1 < 512; i1++) { if (lvl1_page[i1] == 0) { continue; } var l1PTE = new HARDWARE_ADDRESS_ENTRY(lvl1_page[i1]); s1va.TableOffset = i1; var l1PFN = new PFN(l1PTE, s1va.Address, CR3, SLAT) { Type = PFNType.Data }; l2PFN.SubTables.Add(s1va, l1PFN); entries++; } } } } } } //top.PFNCount += entries; return(entries); }
long FillTable(VIRTUAL_ADDRESS VA, int PageIndex, long CR3, bool OnlyUserSpace = false) { var entries = 0L; var PageTable = new Dictionary<VIRTUAL_ADDRESS, PFN>(); // We can just pick up the top level page to speed things up // we've already visited this page so were not going to waste time looking up the VA->PA //var page = new long[512]; //mem.GetPageFromFileOffset(FileOffset, ref page); // clear out the VA for the other indexes since were looking at the top level VA.Address = 0; foreach (var kvp in DP.TopPageTablePage) { // Only extract user portion, kernel will be mostly redundant if(OnlyUserSpace && kvp.Key >= 256) continue; // were at the top level (4th) VA.PML4 = kvp.Key; var pfn = new PFN(kvp.Value, VA.Address, CR3, DP.vmcs == null ? 0 : DP.vmcs.EPTP); PageTable.Add(VA, pfn); entries++; } // simulated top entry RootPageTable = new PFN(DP.TopPageTablePage[PageIndex], VA.Address, CR3, DP.vmcs == null ? 0 : DP.vmcs.EPTP) { SubTables = PageTable }; // descend the remaining levels // if we find nothing, we can be sure the value were using for EPTP or CR3 is bogus var new_entries = InlineExtract(RootPageTable, 3); if (new_entries == 0) return 0; entries += new_entries; // a hint for the full count of entries extracted RootPageTable.PFNCount = entries; return entries; }
/// <summary> /// An Inline extraction for the page table hierarchy. /// Why not let the compiler do it? I have code clones here? /// /// I guess so, but we can see/deal with the subtle differences at each level here as we implement them. /// e.g. some levels have LargePage bits and we may also lay over other CPU modes here like 32 in 64 etc.. /// </summary> /// <param name="top"></param> /// <param name="Level"></param> /// <returns></returns> long InlineExtract(PFN top, int Level) { if (Level == 0) return 0; var entries = 0L; var VA = new VIRTUAL_ADDRESS(top.VA); //WriteLine($"4: Scanning {top.PageTable:X16}"); var hPA = HARDWARE_ADDRESS_ENTRY.MinAddr; var SLAT = top.SLAT; var CR3 = top.PageTable; // pull level 4 entries attach level 3 foreach (var top_sub in top.SubTables) { // scan each page for the level 4 entry var PTE = top_sub.Value.PTE; // we don't need to | in the PML4 AO (address offset) since were pulling down the whole page not just the one value // and were going to brute force our way through the entire table so this was just confusing things. var l3HW_Addr = PTE.NextTableAddress ;// | top_sub.Key.PML4; // if we have an EPTP use it and request resolution of the HW_Addr if (SLAT != 0) { var hl3HW_Addr = HARDWARE_ADDRESS_ENTRY.MaxAddr; try { hl3HW_Addr = mem.VirtualToPhysical(SLAT, l3HW_Addr); } catch (Exception) { if (Vtero.DiagOutput) WriteLine($"level3: Failed lookup {l3HW_Addr:X16}"); } l3HW_Addr = hl3HW_Addr; } if (SLAT != 0 && (l3HW_Addr == long.MaxValue || l3HW_Addr == long.MaxValue-1)) continue; // copy VA since were going to be making changes var s3va = new VIRTUAL_ADDRESS(top_sub.Key.Address); //WriteLine($"3: Scanning {s3va.Address:X16}"); top_sub.Value.hostPTE = l3HW_Addr; // cache translated value var lvl3_page = new long[512]; // extract the l3 page for each PTEEntry we had in l4 try { mem.GetPageForPhysAddr(l3HW_Addr, ref lvl3_page); } catch (Exception) { if (Vtero.DiagOutput) WriteLine($"level3: Failed lookup {l3HW_Addr:X16}"); } if (lvl3_page == null) continue; for (uint i3 = 0; i3 < 512; i3++) { if (lvl3_page[i3] == 0) continue; var l3PTE = new HARDWARE_ADDRESS_ENTRY(lvl3_page[i3]); // adjust VA to match extracted page entries s3va.DirectoryPointerOffset = i3; // save 'PFN' entry into sub-table I should really revisit all these names var l3PFN = new PFN(l3PTE, s3va.Address, CR3, SLAT); top_sub.Value.SubTables.Add(s3va, l3PFN); entries++; /// TODO: Double check if this is a real bit... /// I added it to help weed out some failure cases if (!l3PTE.LargePage) { // get the page that the current l3PFN describes var l2HW_Addr = l3PTE.NextTableAddress; if (SLAT != 0) { var hl2HW_Addr = HARDWARE_ADDRESS_ENTRY.MaxAddr; try { hl2HW_Addr = mem.VirtualToPhysical(SLAT, l2HW_Addr); } catch (Exception ex) { if (Vtero.DiagOutput) WriteLine($"level2: Unable to V2P {l3PTE}"); } l2HW_Addr = hl2HW_Addr; } // TODO: more error handling of exceptions & bad returns // TODO: support software PTE types if (l2HW_Addr == HARDWARE_ADDRESS_ENTRY.MaxAddr) continue; l3PFN.hostPTE = l2HW_Addr; var lvl2_page = new long[512]; try { mem.GetPageForPhysAddr(l2HW_Addr, ref lvl2_page); } catch (Exception ex) { if (Vtero.DiagOutput) WriteLine($"level2: Failed lookup {l2HW_Addr:X16}"); } if (lvl2_page == null) continue; // copy VA var s2va = new VIRTUAL_ADDRESS(s3va.Address); //WriteLine($"2: Scanning {s2va.Address:X16}"); // extract PTE's for each set entry for (uint i2 = 0; i2 < 512; i2++) { if (lvl2_page[i2] == 0) continue; var l2PTE = new HARDWARE_ADDRESS_ENTRY(lvl2_page[i2]); s2va.DirectoryOffset = i2; var l2PFN = new PFN(l2PTE, s2va.Address, CR3, SLAT) { Type = PFNType.Data }; l3PFN.SubTables.Add(s2va, l2PFN); entries++; if (!l2PTE.LargePage) { var l1HW_Addr = l2PTE.NextTableAddress; if (SLAT != 0) { var hl1HW_Addr = HARDWARE_ADDRESS_ENTRY.MaxAddr; try { hl1HW_Addr = mem.VirtualToPhysical(SLAT, l1HW_Addr); } catch (Exception ex) { if (Vtero.DiagOutput) WriteLine($"level1: Unable to V2P {l2PTE}"); } l1HW_Addr = hl1HW_Addr; } if (l1HW_Addr == HARDWARE_ADDRESS_ENTRY.MaxAddr) continue; l2PFN.hostPTE = l1HW_Addr; var lvl1_page = new long[512]; try { mem.GetPageForPhysAddr(l1HW_Addr, ref lvl1_page); } catch (Exception ex) { if(Vtero.DiagOutput) WriteLine($"level1: Failed lookup {l1HW_Addr:X16}"); } if (lvl1_page == null) continue; var s1va = new VIRTUAL_ADDRESS(s2va.Address); //WriteLine($"1: Scanning {s1va.Address:X16}"); for (uint i1 = 0; i1 < 512; i1++) { if (lvl1_page[i1] == 0) continue; var l1PTE = new HARDWARE_ADDRESS_ENTRY(lvl1_page[i1]); s1va.TableOffset = i1; var l1PFN = new PFN(l1PTE, s1va.Address, CR3, SLAT) { Type = PFNType.Data }; l2PFN.SubTables.Add(s1va, l1PFN); entries++; } } } } } } //top.PFNCount += entries; return entries; }
public IEnumerable <PFN> ExtractNextLevel(PFN PageContext, int Level = 4, bool RedundantKernelSpaces = false) { if (PageContext == null) { yield break; } var SLAT = Root.SLAT; var CR3 = Root.CR3; var top = Root.Entries; VIRTUAL_ADDRESS SubVA = PageContext.VA; HARDWARE_ADDRESS_ENTRY PA = PageContext.PTE; // get the page that the current PFN describes var HW_Addr = PA.NextTableAddress; if (SLAT != 0) { var hHW_Addr = HARDWARE_ADDRESS_ENTRY.MaxAddr; try { hHW_Addr = mem.VirtualToPhysical(SLAT, HW_Addr); } catch (Exception ex) { if (Vtero.DiagOutput) { WriteLine($"level{Level}: Unable to V2P {HW_Addr}"); } } HW_Addr = hHW_Addr; if (HW_Addr == long.MaxValue || HW_Addr == long.MaxValue - 1) { yield break; } } if (PageContext.PTE.LargePage && Level <= 1) { // cyclic if (MemorizeTables) { PageContext.SubTables.Add(PageContext.VA, PageContext); } yield break; } long[] page = new long[512]; bool ReadData = false; // copy VA since were going to be making changes var valueRead = mem.GetPageForPhysAddr(HW_Addr, ref page, ref ReadData); if (!ReadData || page == null) { yield break; } // if every entry is exactially the same bail out var check1 = page[0]; if (Array.TrueForAll <long>(page, (entry) => entry == check1)) { yield break; } var dupVA = new VIRTUAL_ADDRESS(SubVA.Address); for (int i = 0; i < 512; i++) { // kernel indexes are only relevant on the top level if (Level == 4 && (!RedundantKernelSpaces && i >= MagicNumbers.KERNEL_PT_INDEX_START_USUALLY)) { continue; } if (page[i] == 0) { continue; } switch (Level) { case 4: dupVA.PML4 = i; break; case 3: dupVA.DirectoryPointerOffset = i; break; case 2: dupVA.DirectoryOffset = i; break; case 1: dupVA.TableOffset = i; break; default: break; } var pfn = new PFN { VA = new VIRTUAL_ADDRESS(dupVA.Address), PTE = new HARDWARE_ADDRESS_ENTRY(page[i]) }; if (MemorizeTables) { PageContext.SubTables.Add( pfn.VA, pfn); } EntriesParsed++; yield return(pfn); } yield break; }
long InlineExtract(PFN top, int Level) { if (Level == 0) return 0; var entries = 0L; var VA = new VIRTUAL_ADDRESS(top.VA); var hPA = HARDWARE_ADDRESS_ENTRY.MinAddr; var CR3 = top.PageTable; var SLAT = top.SLAT; // pull level 4 entries attach level 3 foreach (var top_sub in top.SubTables) { // scan each page for the level 4 entry var PTE = top_sub.Value.PTE; var l3HW_Addr = PTE.NextTableAddress | top_sub.Key.PML4; // if we have an EPTP use it and request resolution of the HW_Addr if (SLAT != 0) { var hl3HW_Addr = HARDWARE_ADDRESS_ENTRY.MaxAddr; hl3HW_Addr = mem.VirtualToPhysical(SLAT, l3HW_Addr); l3HW_Addr = hl3HW_Addr; } if (l3HW_Addr == long.MaxValue) continue; // copy VA since were going to be making changes var s3va = new VIRTUAL_ADDRESS(top_sub.Key.Address); top_sub.Value.hostPTE = l3HW_Addr; // cache translated value var lvl3_page = new long[512]; // extract the l3 page for each PTEEntry we had in l4 try { mem.GetPageForPhysAddr(l3HW_Addr, ref lvl3_page); } catch (Exception) { WriteLine($"level3: Failed lookup {l3HW_Addr:X16}"); } if (lvl3_page == null) continue; for (uint i3 = 0; i3 < 512; i3++) { if (lvl3_page[i3] == 0) continue; var l3PTE = new HARDWARE_ADDRESS_ENTRY(lvl3_page[i3]); // adjust VA to match extracted page entries s3va.DirectoryPointerOffset = i3; // save 'PFN' entry into sub-table I should really revisit all these names var l3PFN = new PFN(l3PTE, s3va.Address, CR3, SLAT); top_sub.Value.SubTables.Add(s3va, l3PFN); entries++; // get the page that the current l3PFN describes var l2HW_Addr = l3PTE.NextTableAddress; if (SLAT != 0) { var hl2HW_Addr = HARDWARE_ADDRESS_ENTRY.MaxAddr; try { hl2HW_Addr = mem.VirtualToPhysical(SLAT, l2HW_Addr); } catch(Exception ex) { WriteLine($"level2: Unable to V2P {l3PTE}"); } l2HW_Addr = hl2HW_Addr; } // TODO: more error handlng of exceptions & bad return's // TODO: support software PTE types if (l2HW_Addr == HARDWARE_ADDRESS_ENTRY.MaxAddr) continue; l3PFN.hostPTE = l2HW_Addr; var lvl2_page = new long[512]; try { mem.GetPageForPhysAddr(l2HW_Addr, ref lvl2_page); } catch (Exception ex) { WriteLine($"level2: Failed lookup {l2HW_Addr:X16}"); } if (lvl2_page == null) continue; // copy VA var s2va = new VIRTUAL_ADDRESS(s3va.Address); // extract PTE's for each set entry for (uint i2 = 0; i2 < 512; i2++) { if (lvl2_page[i2] == 0) continue; var l2PTE = new HARDWARE_ADDRESS_ENTRY(lvl2_page[i2]); s2va.DirectoryOffset = i2; var l2PFN = new PFN(l2PTE, s2va.Address, CR3, SLAT); l3PFN.SubTables.Add(s2va, l2PFN); entries++; if (!l2PTE.LargePage) { var l1HW_Addr = l2PTE.NextTableAddress; if (SLAT != 0) { var hl1HW_Addr = HARDWARE_ADDRESS_ENTRY.MaxAddr; try { hl1HW_Addr = mem.VirtualToPhysical(SLAT, l1HW_Addr); } catch (Exception ex) { WriteLine($"level1: Unable to V2P {l2PTE}"); } l1HW_Addr = hl1HW_Addr; } if (l1HW_Addr == HARDWARE_ADDRESS_ENTRY.MaxAddr) continue; l2PFN.hostPTE = l1HW_Addr; var lvl1_page = new long[512]; try { mem.GetPageForPhysAddr(l1HW_Addr, ref lvl1_page); } catch (Exception ex) { WriteLine($"level1: Failed lookup {l1HW_Addr:X16}"); } if (lvl1_page == null) continue; var s1va = new VIRTUAL_ADDRESS(s2va.Address); for (uint i1 = 0; i1 < 512; i1++) { if (lvl1_page[i1] == 0) continue; var l1PTE = new HARDWARE_ADDRESS_ENTRY(lvl1_page[i1]); s1va.TableOffset = i1; var l1PFN = new PFN(l1PTE, s1va.Address, CR3, SLAT); l2PFN.SubTables.Add(s1va, l1PFN); entries++; } } } } } top.PFNCount += entries; return entries; }
// Translates virtual address to physical address by way of CR3->EPTP double dereferencing (up to 24 loads) public HARDWARE_ADDRESS_ENTRY VirtualToPhysical(HARDWARE_ADDRESS_ENTRY eptp, HARDWARE_ADDRESS_ENTRY aCR3, long Addr) { var rv = HARDWARE_ADDRESS_ENTRY.MinAddr; var va = new VIRTUAL_ADDRESS(Addr); var gVa = new VIRTUAL_ADDRESS(aCR3.PTE); var Attempted = HARDWARE_ADDRESS_ENTRY.MinAddr; var ConvertedV2hP = new List<HARDWARE_ADDRESS_ENTRY>(); try { Attempted = gVa.Address; //convert Guest CR3 gPA into Host CR3 pPA var gpaCR3 = VirtualToPhysical(eptp, gVa.Address); // Validate page table. Possibly adjust for run gaps //var GapSize = ValidateAndGetGap(gpaCR3, aCR3); // let's just ignore failures for now //if (GapSize == long.MaxValue) //Debug.Print("Table verification error. YMMV."); long GapSize = 0; //} //Console.WriteLine($"In V2P2P, using CR3 {aCR3.PTE:X16}, found guest phys CR3 {gpaCR3.PTE:X16}, attemptng load of PML4E from {(gpaCR3 | va.PML4):X16}"); // gPML4E - as we go were getting gPA's which need to pPA Attempted = (gpaCR3.NextTableAddress - GapSize) | (va.PML4 << 3); var gPML4E = (HARDWARE_ADDRESS_ENTRY) GetValueAtPhysicalAddr(Attempted); ConvertedV2hP.Add(gPML4E); //Console.WriteLine($"guest PML4E = {gPML4E}"); // take CR3 and extract gPhys for VA we want to query var hPML4E = VirtualToPhysical(eptp, gPML4E.NextTableAddress); if (EPTP.IsValid(hPML4E.PTE) && EPTP.IsValid2(hPML4E.PTE)) { Attempted = (hPML4E.NextTableAddress - GapSize) | (va.DirectoryPointerOffset << 3); var gPDPTE = (HARDWARE_ADDRESS_ENTRY) GetValueAtPhysicalAddr(Attempted); ConvertedV2hP.Add(gPDPTE); var hPDPTE = VirtualToPhysical(eptp, gPDPTE.NextTableAddress); if (EPTP.IsValid(hPDPTE.PTE)) { if(!EPTP.IsLargePDPTE(hPDPTE.PTE)) { if (EPTP.IsValid2(hPDPTE.PTE)) { Attempted = (hPDPTE.NextTableAddress - GapSize) | (va.DirectoryOffset << 3); var gPDE = (HARDWARE_ADDRESS_ENTRY) GetValueAtPhysicalAddr(Attempted); ConvertedV2hP.Add(gPDE); var hPDE = VirtualToPhysical(eptp, gPDE.NextTableAddress); if (EPTP.IsValid(hPDE.PTE)) { if (!EPTP.IsLargePDE(hPDE.PTE)) { if (EPTP.IsValid2(hPDE.PTE)) { Attempted = (hPDE.NextTableAddress - GapSize) | (va.TableOffset << 3); var gPTE = (HARDWARE_ADDRESS_ENTRY)GetValueAtPhysicalAddr(Attempted); ConvertedV2hP.Add(gPTE); var hPTE = VirtualToPhysical(eptp, gPTE.NextTableAddress); if (EPTP.IsValidEntry(hPTE.PTE)) rv = (hPTE.NextTableAddress - GapSize) | va.Offset; } } else rv = (hPDE.PTE & 0xFFFFFFE00000) | (Addr & 0x1FFFFF); } } } else rv = (hPDPTE.PTE & 0xFFFFC0000000) | (Addr & 0x3FFFFFFF); } } } catch(PageNotFoundException ex) { throw new ExtendedPageNotFoundException( $"V2gP2hP conversion error. EPTP:{eptp}, CR3:{aCR3}, Requesting:{Attempted} Step:{ConvertedV2hP.Count()}. Step of 0 may indicate invalid EPTP.{Environment.NewLine}" , eptp, aCR3, Attempted, ConvertedV2hP, ex); } return rv; }
// Translates virtual address to physical address (normal CR3 path) // Since Valid & Read access overlap each other for EPT and normal PT go through this path for both public HARDWARE_ADDRESS_ENTRY VirtualToPhysical(HARDWARE_ADDRESS_ENTRY aCR3, long Addr) { var rv = HARDWARE_ADDRESS_ENTRY.MaxAddr; var va = new VIRTUAL_ADDRESS(Addr); var ConvertedV2P = new List<HARDWARE_ADDRESS_ENTRY>(); var Attempted = HARDWARE_ADDRESS_ENTRY.MinAddr; //Console.WriteLine($"V2P CR3 = {aCR3.PTE:X16} VA = {va}"); // PML4E try { Attempted = (HARDWARE_ADDRESS_ENTRY) aCR3.NextTableAddress | (va.PML4 << 3); var PML4E = (HARDWARE_ADDRESS_ENTRY) GetValueAtPhysicalAddr(Attempted); //Console.WriteLine($"PML4E = {PML4E.PTE:X16}"); ConvertedV2P.Add(PML4E); if (PML4E.Valid) { Attempted = PML4E.NextTableAddress | (va.DirectoryPointerOffset << 3); var PDPTE = (HARDWARE_ADDRESS_ENTRY) GetValueAtPhysicalAddr(Attempted); ConvertedV2P.Add(PDPTE); //Console.WriteLine($"PDPTE = {PDPTE.PTE:X16}"); if (PDPTE.Valid) { if (!PDPTE.LargePage) { Attempted = PDPTE.NextTableAddress | (va.DirectoryOffset << 3); var PDE = (HARDWARE_ADDRESS_ENTRY)GetValueAtPhysicalAddr(Attempted); ConvertedV2P.Add(PDE); //Console.WriteLine($"PDE = {PDE.PTE:X16}"); if (PDE.Valid) { if (!PDE.LargePage) { Attempted = PDE.NextTableAddress | (va.TableOffset << 3); var PTE = (HARDWARE_ADDRESS_ENTRY)GetValueAtPhysicalAddr(Attempted); ConvertedV2P.Add(PTE); //Console.WriteLine($"PTE = {PTE.PTE:X16}"); // page is normal 4kb if (PTE.Valid) rv = PTE.NextTableAddress | va.Offset; else rv.Valid = false; } else { // we have a 2MB page rv = (PDE.PTE & 0xFFFFFFE00000) | (Addr & 0x1FFFFF); } } else rv.Valid = false; } else { // we have a 1GB page rv = (PDPTE.PTE & 0xFFFFC0000000) | (Addr & 0x3FFFFFFF); } } else rv.Valid = false; } else rv.Valid = false; } catch (Exception ex) { throw new PageNotFoundException("V2P conversion error page not found", Attempted, ConvertedV2P, ex); } finally { foreach(var paddr in ConvertedV2P) { } } //Console.WriteLine($"return from V2P {rv:X16}"); // serialize the dictionary out return rv; }
public byte[] HashRange(VIRTUAL_ADDRESS KEY, PFN VALUE) { var rv = new byte[1]; var block = new long[0x200]; // 0x200 * 8 = 4k var bpage = new byte[0x1000]; var tigger = new Tiger(); tigger.Initialize(); //fixed (void* lp = block, bp = bpage) //{ if (DiagOutput) WriteColor(VALUE.PTE.Valid ? ConsoleColor.Cyan : ConsoleColor.Red, $"VA: {KEY:X12} \t PFN: {VALUE.PTE}"); // if we have invalid (software managed) page table entries // the data may be present, or a prototype or actually in swap. // for the moment were only going to dump hardware managed data // or feel free to patch this up ;) if (!VALUE.PTE.Valid) return rv; if (VALUE.PTE.LargePage) { bool GoodRead = false; // 0x200 * 4kb = 2MB for (int i = 0; i < 0x200; i++) { MemAccess.GetPageForPhysAddr(VALUE.PTE, ref block, ref GoodRead); VALUE.PTE.PTE += 0x1000; if (!GoodRead) block = new long[0x200]; Buffer.BlockCopy(block, 0, bpage, 0, 4096); rv = tigger.ComputeHash(bpage); } return rv; } else { try { MemAccess.GetPageForPhysAddr(VALUE.PTE, ref block); } catch (Exception ex) { } if (block != null) { Buffer.BlockCopy(block, 0, bpage, 0, 4096); rv = tigger.ComputeHash(bpage); } } return rv; }
// TODO: Figure out if MemoryCopy or BlockCopy ... public string WriteRange(VIRTUAL_ADDRESS KEY, PFN VALUE, string BaseFileName, Mem PhysMemReader = null, bool SinglePFNStore = false, bool DumpNULL = false) { if (SinglePFNStore && SISmap == null) SISmap = new WAHBitArray(); if(SinglePFNStore) { if (SISmap.Get((int)VALUE.PTE.PFN)) return string.Empty; SISmap.Set((int)VALUE.PTE.PFN, true); } bool canAppend = false; var saveLoc = BaseFileName + KEY.Address.ToString("X") + ".bin"; var lastLoc = BaseFileName + (KEY.Address - ContigSize).ToString("X") + ".bin"; if (File.Exists(lastLoc)) { canAppend = true; ContigSize += 0x1000; saveLoc = lastLoc; } else ContigSize = 0x1000; //unsafe //{ var block = new long[0x200]; // 0x200 * 8 = 4k var bpage = new byte[0x1000]; //fixed (void* lp = block, bp = bpage) //{ if (DiagOutput) WriteColor(VALUE.PTE.Valid ? ConsoleColor.Cyan : ConsoleColor.Red, $"VA: {KEY:X12} \t PFN: {VALUE.PTE}"); // if we have invalid (software managed) page table entries // the data may be present, or a prototype or actually in swap. // for the moment were only going to dump hardware managed data // or feel free to patch this up ;) if (!VALUE.PTE.Valid) return string.Empty; if (VALUE.PTE.LargePage) { bool GoodRead = false; using (var lsavefile = File.OpenWrite(saveLoc)) { // 0x200 * 4kb = 2MB // TODO: Large pages properly? // TODO: PageCache is still broken in some cases... disable for now here for (int i = 0; i < 0x200; i++) { PhysMemReader.GetPageForPhysAddr(VALUE.PTE, ref block, ref GoodRead); VALUE.PTE.PTE += 0x1000; if(!GoodRead) block = new long[0x200]; Buffer.BlockCopy(block, 0, bpage, 0, 4096); //Buffer.MemoryCopy(lp, bp, 4096, 4096); lsavefile.Write(bpage, 0, 4096); //lsavefile.Write(bpage, 0, 4096); } return lastLoc; } } else { try { PhysMemReader.GetPageForPhysAddr(VALUE.PTE, ref block); } catch (Exception ex) { } if (block != null) { if (DumpNULL || !UnsafeHelp.IsZero(block)) { Buffer.BlockCopy(block, 0, bpage, 0, 4096); //Buffer.MemoryCopy(lp, bp, 4096, 4096); using (var savefile = (canAppend ? File.Open(lastLoc, FileMode.Append, FileAccess.Write, FileShare.ReadWrite) : File.OpenWrite(saveLoc))) savefile.Write(bpage, 0, 4096); //savefile.Write(bpage, 0, 4096); return lastLoc; } } } ContigSize = 0; return string.Empty; //} //} }
public IEnumerable<PFN> ExtractNextLevel(PFN PageContext, bool RedundantKernelSpaces, int Level = 4) { if (PageContext == null) yield break; RedundantKernelSpaces = true; var SLAT = Root.SLAT; var CR3 = Root.CR3; var top = Root.Entries; VIRTUAL_ADDRESS SubVA = PageContext.VA; HARDWARE_ADDRESS_ENTRY PA = PageContext.PTE; // get the page that the current PFN describes var HW_Addr = PA.NextTableAddress; if (SLAT != 0) { var hHW_Addr = HARDWARE_ADDRESS_ENTRY.MaxAddr; try { hHW_Addr = mem.VirtualToPhysical(SLAT, HW_Addr); } catch (Exception ex) { if (Vtero.DiagOutput) WriteLine($"level{Level}: Unable to V2P {HW_Addr}"); } HW_Addr = hHW_Addr; if (HW_Addr == long.MaxValue || HW_Addr == long.MaxValue - 1) yield break; } if (PageContext.PTE.LargePage && Level <= 1) { // cyclic PageContext.SubTables.Add(PageContext.VA, PageContext); yield break; } long[] page = new long[512]; bool ReadData = false; // copy VA since were going to be making changes var valueRead = mem.GetPageForPhysAddr(HW_Addr, ref page, ref ReadData); if (!ReadData || page == null) yield break; var dupVA = new VIRTUAL_ADDRESS(SubVA.Address); for (int i = 0; i < 512; i++) { if (!RedundantKernelSpaces && i >= MagicNumbers.KERNEL_PT_INDEX_START_USUALLY) continue; if (page[i] == 0) continue; switch (Level) { case 4: dupVA.PML4 = i; break; case 3: dupVA.DirectoryPointerOffset = i; break; case 2: dupVA.DirectoryOffset = i; break; case 1: dupVA.TableOffset = i; break; default: break; } var pfn = new PFN { VA = new VIRTUAL_ADDRESS(dupVA.Address), PTE = new HARDWARE_ADDRESS_ENTRY(page[i]) }; PageContext.SubTables.Add( pfn.VA, pfn); EntriesParsed++; yield return pfn; } yield break; }