`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 # kbase
```text ```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| |-h|--help|show this help message and exit|
|-r|--rebase|rebase loaded symbol file| |-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. --> <!-- 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------------ --> <!-- ------------\>8---- ----\>8---- ----\>8------------ -->

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

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

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

@ -12,12 +12,18 @@ from pwndbg.commands import CommandCategory
parser = argparse.ArgumentParser(description="Finds the kernel virtual base address.") 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("-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.Command(parser, category=CommandCategory.KERNEL)
@pwndbg.commands.OnlyWhenQemuKernel @pwndbg.commands.OnlyWhenQemuKernel
@pwndbg.commands.OnlyWhenPagingEnabled @pwndbg.commands.OnlyWhenPagingEnabled
def kbase(rebase=False) -> None: def kbase(rebase=False, verbose=False) -> None:
if config.kernel_vmmap == "none": if config.kernel_vmmap == "none":
print(M.error("kbase does not work when kernel-vmmap is set to none")) print(M.error("kbase does not work when kernel-vmmap is set to none"))
return return
@ -30,6 +36,11 @@ def kbase(rebase=False) -> None:
print(M.success(f"Found virtual text base address: {hex(base)}")) 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: if not rebase:
return return

@ -1,12 +1,14 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import math
import pwndbg.aglib.kernel import pwndbg.aglib.kernel
import pwndbg.aglib.kernel.paging import pwndbg.aglib.kernel.paging
import pwndbg.aglib.regs import pwndbg.aglib.regs
import pwndbg.color as C import pwndbg.color as C
import pwndbg.color.message as M import pwndbg.color.message as M
from pwndbg.aglib.kernel.paging import PageTableLevel
from pwndbg.commands import CommandCategory from pwndbg.commands import CommandCategory
parser = argparse.ArgumentParser(description="Performs pagewalk.") parser = argparse.ArgumentParser(description="Performs pagewalk.")
@ -25,13 +27,20 @@ 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) pageflags = pwndbg.aglib.kernel.arch_paginginfo().pageentry_flags(is_last)
flags = "" flags = ""
arrow_right = pwndbg.chain.c.arrow(f"{pwndbg.chain.config_arrow_right}") arrow_right = pwndbg.chain.c.arrow(f"{pwndbg.chain.config_arrow_right}")
if paddr is not None: name, entry, vaddr, idx = ptl.name, ptl.entry, ptl.virt, ptl.idx
flags = f"{arrow_right} {name + 'e'}: {C.context.format_flags(paddr, pageflags, paddr)}" if pwndbg.aglib.arch.name == "x86-64":
print(f"{C.blue(name)} @ {C.yellow(hex(vaddr))} {flags}") 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}")
def page_type(page): def page_type(page):
@ -82,14 +91,14 @@ def pagewalk(vaddr, entry=None):
if entry is not None: if entry is not None:
entry = int(pwndbg.dbg.selected_frame().evaluate_expression(entry)) entry = int(pwndbg.dbg.selected_frame().evaluate_expression(entry))
vaddr = int(pwndbg.dbg.selected_frame().evaluate_expression(vaddr)) vaddr = int(pwndbg.dbg.selected_frame().evaluate_expression(vaddr))
names, entries = pwndbg.aglib.kernel.pagewalk(vaddr, entry) levels = pwndbg.aglib.kernel.pagewalk(vaddr, entry)
for i in range(len(names) - 1, 0, -1): for i in range(len(levels) - 1, 0, -1):
entry, vaddr = entries[i] curr = levels[i]
next, _ = entries[i - 1] next = levels[i - 1]
if entry is None: if curr.entry is None:
break break
print_pagetable_entry(names[i], entry, vaddr, i, next is None or i == 1) print_pagetable_entry(curr, i, next.entry is None or i == 1)
_, vaddr = entries[0] vaddr = levels[0].virt
if vaddr is None: if vaddr is None:
print(M.warn("address is not mapped")) print(M.warn("address is not mapped"))
return return
@ -117,10 +126,13 @@ p2v_parser.add_argument("paddr", type=str, help="")
@pwndbg.aglib.proc.OnlyWithArch(["x86-64", "aarch64"]) @pwndbg.aglib.proc.OnlyWithArch(["x86-64", "aarch64"])
def p2v(paddr): def p2v(paddr):
paddr = int(pwndbg.dbg.selected_frame().evaluate_expression(paddr)) paddr = int(pwndbg.dbg.selected_frame().evaluate_expression(paddr))
vaddr = pwndbg.aglib.kernel.phys_to_virt(paddr) try:
paging_print_helper("Virtual address", vaddr) vaddr = pwndbg.aglib.kernel.phys_to_virt(paddr)
page = pwndbg.aglib.kernel.virt_to_page(vaddr) paging_print_helper("Virtual address", vaddr)
page_info(page) 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( v2p_parser = argparse.ArgumentParser(
@ -136,9 +148,10 @@ v2p_parser.add_argument("vaddr", type=str, help="")
@pwndbg.aglib.proc.OnlyWithArch(["x86-64", "aarch64"]) @pwndbg.aglib.proc.OnlyWithArch(["x86-64", "aarch64"])
def v2p(vaddr): def v2p(vaddr):
vaddr = int(pwndbg.dbg.selected_frame().evaluate_expression(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: 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 return
paging_print_helper("Physmap address", paddr) paging_print_helper("Physmap address", paddr)
# paddr is the physmap address which is a virtual address # 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"]) @pwndbg.aglib.proc.OnlyWithArch(["x86-64", "aarch64"])
def pageinfo(page): def pageinfo(page):
page = int(pwndbg.dbg.selected_frame().evaluate_expression(page)) page = int(pwndbg.dbg.selected_frame().evaluate_expression(page))
vaddr = pwndbg.aglib.kernel.page_to_virt(page) try:
paging_print_helper("Virtual address", vaddr) vaddr = pwndbg.aglib.kernel.page_to_virt(page)
page_info(page) paging_print_helper("Virtual address", vaddr)
page_info(page)
except Exception:
print(M.warn("invalid page struct pointer"))

Loading…
Cancel
Save