`pagewalk` refactoring (#3317)

* refactoring pagewalk + printing pagetable idxes

* added idx mask

* docs

* added caching

* idxlen

* linting

* improved error handling
pull/3330/head^2
jxuanli 2 months ago committed by GitHub
parent b7e53cee36
commit 9862461ee4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -2,7 +2,7 @@
# kbase
```text
usage: kbase [-h] [-r]
usage: kbase [-h] [-r] [-v]
```
@ -13,6 +13,7 @@ Finds the kernel virtual base address.
| :--- | :--- | :--- |
|-h|--help|show this help message and exit|
|-r|--rebase|rebase loaded symbol file|
|-v|--verbose|show more information relevant to the kbase (e.g. phys addr)|
<!-- END OF AUTOGENERATED PART. Do not modify this line or the line below, they mark the end of the auto-generated part of the file. If you want to extend the documentation in a way which cannot easily be done by adding to the command help description, write below the following line. -->
<!-- ------------\>8---- ----\>8---- ----\>8------------ -->

@ -28,6 +28,7 @@ import pwndbg.lib.kernel.structs
import pwndbg.lib.memory
import pwndbg.search
from pwndbg.aglib.kernel.paging import ArchPagingInfo
from pwndbg.aglib.kernel.paging import PageTableLevel
from pwndbg.lib.regs import BitFlags
_kconfig: pwndbg.lib.kernel.kconfig.Kconfig | None = None
@ -366,12 +367,12 @@ class x86_64Ops(x86Ops):
return pwndbg.dbg.selected_inferior().create_value(per_cpu_addr)
def virt_to_phys(self, virt: int) -> int:
if pwndbg.aglib.memory.is_kernel(virt) and virt < arch_paginginfo().vmalloc:
return virt - self.page_offset
res = pagewalk(virt)[0][1]
if res is None:
if not (pwndbg.aglib.memory.is_kernel(virt) and virt < arch_paginginfo().vmalloc):
# if not within physmap range, first find the physmap address
virt = pagewalk(virt)[0].virt
if virt is None:
return None
return res - self.page_offset
return virt - self.page_offset
def pfn_to_page(self, pfn: int) -> int:
# assumption: SPARSEMEM_VMEMMAP memory model used
@ -405,12 +406,12 @@ class Aarch64Ops(ArchOps):
return pwndbg.dbg.selected_inferior().create_value(per_cpu_addr)
def virt_to_phys(self, virt: int) -> int:
if pwndbg.aglib.memory.is_kernel(virt) and virt < arch_paginginfo().vmalloc:
return virt - self.page_offset + self.phys_offset
res = pagewalk(virt)[0][1]
if res is None:
if not (pwndbg.aglib.memory.is_kernel(virt) and virt < arch_paginginfo().vmalloc):
# if not within physmap range, first find the physmap address
virt = pagewalk(virt)[0].virt
if virt is None:
return None
return res - self.page_offset
return virt - self.page_offset + self.phys_offset
def phys_to_virt(self, phys: int) -> int:
# https://elixir.bootlin.com/linux/v6.16.4/source/arch/arm64/include/asm/memory.h#L356
@ -595,7 +596,8 @@ def kbase() -> int | None:
raise NotImplementedError()
def pagewalk(addr, entry=None):
@pwndbg.lib.cache.cache_until("stop")
def pagewalk(addr, entry=None) -> Tuple[PageTableLevel, ...]:
pi = arch_paginginfo()
if pi:
return pi.pagewalk(addr, entry)

@ -2,8 +2,8 @@ from __future__ import annotations
import math
import re
from dataclasses import dataclass
from typing import Dict
from typing import List
from typing import Tuple
import pwndbg
@ -31,6 +31,14 @@ def first_kernel_page_start():
return INVALID_ADDR
@dataclass
class PageTableLevel:
name: str
entry: int
virt: int # within physmap
idx: int
class ArchPagingInfo:
USERLAND = "userland"
KERNELLAND = "kernel [.text]"
@ -50,6 +58,7 @@ class ArchPagingInfo:
va_bits: int
pagetable_cache: Dict[pwndbg.dbg_mod.Value, Dict[int, int]] = {}
pagetableptr_cache: Dict[int, pwndbg.dbg_mod.Value] = {}
pagetable_level_names: Tuple[str, ...]
@property
@pwndbg.lib.cache.cache_until("objfile")
@ -99,27 +108,26 @@ class ArchPagingInfo:
return None
def pagewalk(
self, target, entry
) -> Tuple[Tuple[str, ...], List[Tuple[int | None, int | None]]]:
def pagewalk(self, target, entry) -> Tuple[PageTableLevel, ...]:
raise NotImplementedError()
def pagewalk_helper(self, target, entry) -> List[Tuple[int | None, int | None]]:
def pagewalk_helper(self, target, entry) -> Tuple[PageTableLevel, ...]:
base = self.physmap
if entry > base:
# user inputted a physmap address as pointer to pgd
entry -= base
level = self.paging_level
result: List[Tuple[int | None, int | None]] = [(None, None)] * (level + 1)
result = [PageTableLevel(None, None, None, None)] * (level + 1)
page_shift = self.page_shift
ENTRYMASK = ~((1 << page_shift) - 1) & ((1 << self.va_bits) - 1)
IDXMASK = (1 << (page_shift - math.ceil(math.log2(pwndbg.aglib.arch.ptrsize)))) - 1
for i in range(level, 0, -1):
vaddr = (entry & ENTRYMASK) + base - self.phys_offset
if self.should_stop_pagewalk(entry):
break
shift = (i - 1) * (page_shift - 3) + page_shift
offset = target & ((1 << shift) - 1)
idx = (target & (0x1FF << shift)) >> shift
idx = (target & (IDXMASK << shift)) >> shift
entry = 0
try:
# with this optimization, roughly x2 as fast on average
@ -142,10 +150,15 @@ class ArchPagingInfo:
print(M.warn(f"Exception while page walking: {e}"))
entry = 0
if entry == 0:
return result
result[i] = (entry, vaddr)
result[0] = (entry, (entry & ENTRYMASK) + base + offset - self.phys_offset)
return result
return tuple(result)
result[i] = PageTableLevel(self.pagetable_level_names[i], entry, vaddr, idx)
result[0] = PageTableLevel(
self.pagetable_level_names[0],
entry,
(entry & ENTRYMASK) + base + offset - self.phys_offset,
None,
)
return tuple(result)
def pageentry_flags(self, level) -> BitFlags:
raise NotImplementedError()
@ -313,12 +326,10 @@ class x86_64PagingInfo(ArchPagingInfo):
if pwndbg.aglib.regs[pwndbg.aglib.regs.stack] in page:
page.objfile = "kernel [stack]"
def pagewalk(
self, target, entry
) -> Tuple[Tuple[str, ...], List[Tuple[int | None, int | None]]]:
def pagewalk(self, target, entry) -> Tuple[PageTableLevel, ...]:
if entry is None:
entry = pwndbg.aglib.regs["cr3"]
return self.pagetable_level_names, self.pagewalk_helper(target, entry)
return self.pagewalk_helper(target, entry)
def pageentry_flags(self, is_last) -> BitFlags:
return BitFlags([("NX", 63), ("PS", 7), ("A", 5), ("U", 2), ("W", 1), ("P", 0)])
@ -606,16 +617,14 @@ class Aarch64PagingInfo(ArchPagingInfo):
pass
return 0x40000000 # default
def pagewalk(
self, target, entry
) -> Tuple[Tuple[str, ...], List[Tuple[int | None, int | None]]]:
def pagewalk(self, target, entry) -> Tuple[PageTableLevel, ...]:
if entry is None:
if pwndbg.aglib.memory.is_kernel(target):
entry = pwndbg.aglib.regs.TTBR1_EL1
else:
entry = pwndbg.aglib.regs.TTBR0_EL1
self.entry = entry
return self.pagetable_level_names, self.pagewalk_helper(target, entry)
return self.pagewalk_helper(target, entry)
def pageentry_flags(self, is_last) -> BitFlags:
if is_last:

@ -424,8 +424,7 @@ def kernel_vmmap(process_pages=True) -> Tuple[pwndbg.lib.memory.Page, ...]:
for page in pages:
if page.objfile == kv.pi.ESPSTACK:
continue
_, pgwalk_res = pwndbg.aglib.kernel.pagewalk(page.start)
entry, _ = pgwalk_res[0]
entry = pwndbg.aglib.kernel.pagewalk(page.start)[0].entry
if entry and entry >> 63 == 0:
page.flags |= 1

@ -12,12 +12,18 @@ from pwndbg.commands import CommandCategory
parser = argparse.ArgumentParser(description="Finds the kernel virtual base address.")
parser.add_argument("-r", "--rebase", action="store_true", help="rebase loaded symbol file")
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="show more information relevant to the kbase (e.g. phys addr)",
)
@pwndbg.commands.Command(parser, category=CommandCategory.KERNEL)
@pwndbg.commands.OnlyWhenQemuKernel
@pwndbg.commands.OnlyWhenPagingEnabled
def kbase(rebase=False) -> None:
def kbase(rebase=False, verbose=False) -> None:
if config.kernel_vmmap == "none":
print(M.error("kbase does not work when kernel-vmmap is set to none"))
return
@ -30,6 +36,11 @@ def kbase(rebase=False) -> None:
print(M.success(f"Found virtual text base address: {hex(base)}"))
if verbose:
phys = pwndbg.aglib.kernel.virt_to_phys(base)
if phys is not None:
print(M.success(f"corresponding physical address: {hex(phys)}"))
if not rebase:
return

@ -1,12 +1,14 @@
from __future__ import annotations
import argparse
import math
import pwndbg.aglib.kernel
import pwndbg.aglib.kernel.paging
import pwndbg.aglib.regs
import pwndbg.color as C
import pwndbg.color.message as M
from pwndbg.aglib.kernel.paging import PageTableLevel
from pwndbg.commands import CommandCategory
parser = argparse.ArgumentParser(description="Performs pagewalk.")
@ -25,12 +27,19 @@ PAGETYPES = (
)
def print_pagetable_entry(name: str, paddr: int | None, vaddr: int, level: int, is_last: bool):
def print_pagetable_entry(ptl: PageTableLevel, level: int, is_last: bool):
pageflags = pwndbg.aglib.kernel.arch_paginginfo().pageentry_flags(is_last)
flags = ""
arrow_right = pwndbg.chain.c.arrow(f"{pwndbg.chain.config_arrow_right}")
if paddr is not None:
flags = f"{arrow_right} {name + 'e'}: {C.context.format_flags(paddr, pageflags, paddr)}"
name, entry, vaddr, idx = ptl.name, ptl.entry, ptl.virt, ptl.idx
if pwndbg.aglib.arch.name == "x86-64":
name = name.ljust(3, " ")
nbits = pwndbg.aglib.kernel.arch_ops().page_shift - math.ceil(
math.log2(pwndbg.aglib.arch.ptrsize)
) # each idx has that many bits
idxlen = len(str((1 << nbits) - 1))
if entry is not None:
flags = f"[{idx:0{idxlen}}] {arrow_right} {name + 'e'}: {C.context.format_flags(entry, pageflags, entry)}"
print(f"{C.blue(name)} @ {C.yellow(hex(vaddr))}{flags}")
@ -82,14 +91,14 @@ def pagewalk(vaddr, entry=None):
if entry is not None:
entry = int(pwndbg.dbg.selected_frame().evaluate_expression(entry))
vaddr = int(pwndbg.dbg.selected_frame().evaluate_expression(vaddr))
names, entries = pwndbg.aglib.kernel.pagewalk(vaddr, entry)
for i in range(len(names) - 1, 0, -1):
entry, vaddr = entries[i]
next, _ = entries[i - 1]
if entry is None:
levels = pwndbg.aglib.kernel.pagewalk(vaddr, entry)
for i in range(len(levels) - 1, 0, -1):
curr = levels[i]
next = levels[i - 1]
if curr.entry is None:
break
print_pagetable_entry(names[i], entry, vaddr, i, next is None or i == 1)
_, vaddr = entries[0]
print_pagetable_entry(curr, i, next.entry is None or i == 1)
vaddr = levels[0].virt
if vaddr is None:
print(M.warn("address is not mapped"))
return
@ -117,10 +126,13 @@ p2v_parser.add_argument("paddr", type=str, help="")
@pwndbg.aglib.proc.OnlyWithArch(["x86-64", "aarch64"])
def p2v(paddr):
paddr = int(pwndbg.dbg.selected_frame().evaluate_expression(paddr))
try:
vaddr = pwndbg.aglib.kernel.phys_to_virt(paddr)
paging_print_helper("Virtual address", vaddr)
page = pwndbg.aglib.kernel.virt_to_page(vaddr)
page_info(page)
except Exception:
print(M.warn("physical to virtual address failed, invalid physical address?"))
v2p_parser = argparse.ArgumentParser(
@ -136,9 +148,10 @@ v2p_parser.add_argument("vaddr", type=str, help="")
@pwndbg.aglib.proc.OnlyWithArch(["x86-64", "aarch64"])
def v2p(vaddr):
vaddr = int(pwndbg.dbg.selected_frame().evaluate_expression(vaddr))
entry, paddr = pwndbg.aglib.kernel.pagewalk(vaddr)[1][0] # more accurate
level = pwndbg.aglib.kernel.pagewalk(vaddr)[0] # more accurate
entry, paddr = level.entry, level.virt
if not entry:
print(M.warn("virtual to page failed, unmapped virtual address?"))
print(M.warn("virtual to physical address failed, unmapped virtual address?"))
return
paging_print_helper("Physmap address", paddr)
# paddr is the physmap address which is a virtual address
@ -159,6 +172,9 @@ page_parser.add_argument("page", type=str, help="")
@pwndbg.aglib.proc.OnlyWithArch(["x86-64", "aarch64"])
def pageinfo(page):
page = int(pwndbg.dbg.selected_frame().evaluate_expression(page))
try:
vaddr = pwndbg.aglib.kernel.page_to_virt(page)
paging_print_helper("Virtual address", vaddr)
page_info(page)
except Exception:
print(M.warn("invalid page struct pointer"))

Loading…
Cancel
Save