diff --git a/docs/commands/kernel/kbase.md b/docs/commands/kernel/kbase.md index 5dd9c629a..9ca8a3519 100644 --- a/docs/commands/kernel/kbase.md +++ b/docs/commands/kernel/kbase.md @@ -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)| diff --git a/pwndbg/aglib/kernel/__init__.py b/pwndbg/aglib/kernel/__init__.py index df839ca6a..366e81d75 100644 --- a/pwndbg/aglib/kernel/__init__.py +++ b/pwndbg/aglib/kernel/__init__.py @@ -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) diff --git a/pwndbg/aglib/kernel/paging.py b/pwndbg/aglib/kernel/paging.py index 54482cae6..932ffc44a 100644 --- a/pwndbg/aglib/kernel/paging.py +++ b/pwndbg/aglib/kernel/paging.py @@ -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: diff --git a/pwndbg/aglib/kernel/vmmap.py b/pwndbg/aglib/kernel/vmmap.py index db2a2e987..b5af3caa0 100644 --- a/pwndbg/aglib/kernel/vmmap.py +++ b/pwndbg/aglib/kernel/vmmap.py @@ -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 diff --git a/pwndbg/commands/kbase.py b/pwndbg/commands/kbase.py index f92b92124..99fb48047 100644 --- a/pwndbg/commands/kbase.py +++ b/pwndbg/commands/kbase.py @@ -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 diff --git a/pwndbg/commands/paging.py b/pwndbg/commands/paging.py index 19ea6c03b..5cea39cef 100644 --- a/pwndbg/commands/paging.py +++ b/pwndbg/commands/paging.py @@ -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,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) 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)}" - print(f"{C.blue(name)} @ {C.yellow(hex(vaddr))} {flags}") + 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}") def page_type(page): @@ -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)) - 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) + 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)) - vaddr = pwndbg.aglib.kernel.page_to_virt(page) - paging_print_helper("Virtual address", vaddr) - page_info(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"))