x86-64 4-level page-table walk
hyptcn3/guestparse/view.py
Why it matters
A custom page-table walk (no libvmi) that translates guest-kernel virtual addresses to physical ones straight from the RAM snapshot, including 2 MiB / 1 GiB huge pages (PSE bit). Instead of a bare None it returns the failing level and a reason code, so it can tell “table was not collected” from “the kernel does not map this address” — the profile diagnosis depends on that. Known bug: for a huge page the mask keeps bit 12 (PAT) in the address, so the physical address is wrong whenever PAT is set.
More excerpts from this repo:
- Finding the kernel’s KASLR shift in a snapshot view.py:76–112
- Cross-view process check (rootkit invariant) checks.py:231–278
- SHA-NI with CPUID runtime dispatch sha256.c:304–342
- eBPF: bpf_loop() instead of a loop the verifier rejected vmic_kvm.bpf.c:138–159
Git blame:ab574b8d 19 September 2026, commit from my account, 34 linesb18d4eaf 18 September 2026, commit from my account, 15 lineshighlighted lines
204 def walk_pa_detail(self, va):205 """206 Preklad virtualnej adresy hosta prechodom tabuliek stranok207 (4 urovne, x86_64). Pokryva aj oblasti, ktore linearne mapovane nie su -208 moduly a vmalloc.209210 Vracia {'pa', 'level', 'index', 'reason', 'reason_code'}; 'pa' je None,211 ked sa prechod nedokoncil. Velke stranky (1 GiB, 2 MiB) sa rozpoznaju212 podla bitu PSE.213214 Kody dovodov (na nich stoji diagnoza, nie na samotnom None):215 bez_korena - nepozname posun jadra alebo init_top_pgt216 chyba_tabulka - tabulku na tej urovni snimka neobsahuje (nic to217 nehovori o profile, iba o tom, co sa zozbieralo)218 chyba_polozka - tabulka V SNIMKE JE, ale polozka pre tuto adresu219 v nej nie je pritomna, teda jadro tuto virtualnu220 adresu nemapuje221 """222 out = {"pa": None, "level": None, "index": None,223 "reason": None, "reason_code": None}224 table = self._pgt_root()225 if table is None:226 out["reason_code"] = "bez_korena"227 out["reason"] = ("neznamy posun jadra alebo profil nema "228 "init_top_pgt")229 return out230 # bity 47:39 / 38:30 / 29:21 / 20:12231 for level, shift in enumerate((39, 30, 21, 12)):232 idx = (va >> shift) & 0x1FF233 name = self.PGT_LEVELS[level]234 ent = self.u64(table + idx * 8) if self._page_present(table) else None235 if ent is None:236 out.update(level=name, index=idx, reason_code="chyba_tabulka",237 reason="tabulka %s na 0x%x nie je v snimke"238 % (name, table))239 return out240 if not (ent & self.PTE_PRESENT):241 out.update(level=name, index=idx, reason_code="chyba_polozka",242 reason="polozka %s[%d] nie je pritomna"243 % (name, idx))244 return out245 phys = ent & 0x000FFFFFFFFFF000246 if level in (1, 2) and (ent & self.PTE_PSE):247 size = 1 << shift248 out["pa"] = phys + (va & (size - 1))249 return out250 table = phys251 out["pa"] = table + (va & 0xFFF)252 return out