Improving commands relating to paging (#3146)

* improving paging related commands

* adding command docs

* refactored x64 pagewalk

* implemented pagewalking for aarch64

* updated tests

* improved control register accesses

* detecting kernel phys start

* using pagewalk to resolve phys address for more accurate result

* updated tests

* pagewalk optimization

* fixed tests

* fixed based on comments

* improvements
pull/3149/head
jxuanli 5 months ago committed by GitHub
parent 040636ef2a
commit 7cec118771
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -90,9 +90,10 @@
- [kversion](kernel/kversion.md) - Outputs the kernel version (/proc/version).
- [msr](kernel/msr.md) - Read or write to Model Specific Register (MSR)
- [p2v](kernel/p2v.md) - Translate physical address to its corresponding virtual address.
- [pageinfo](kernel/pageinfo.md) - Convert a pointer to a `struct page` to its corresponding virtual address.
- [pagewalk](kernel/pagewalk.md) - Performs pagewalk.
- [slab](kernel/slab.md) - Prints information about the linux kernel's slab allocator SLUB.
- [v2p](kernel/v2p.md) - Translate virtual address to its corresponding physical address.
- [v2p](kernel/v2p.md) - Translate virtual address to its corresponding physmap address.
## Linux/libc/ELF

@ -0,0 +1,23 @@
<!-- THIS PART OF THIS FILE IS AUTOGENERATED. DO NOT MODIFY IT. See scripts/generate-docs.sh -->
# pageinfo
```text
usage: pageinfo [-h] page
```
Convert a pointer to a `struct page` to its corresponding virtual address.
### Positional arguments
|Positional Argument|Help|
| :--- | :--- |
|page||
### Optional arguments
|Short|Long|Help|
| :--- | :--- | :--- |
|-h|--help|show this help message and exit|
<!-- 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------------ -->

@ -6,7 +6,7 @@ usage: v2p [-h] vaddr
```
Translate virtual address to its corresponding physical address.
Translate virtual address to its corresponding physmap address.
### Positional arguments
|Positional Argument|Help|

@ -564,6 +564,14 @@ def kbase() -> int | None:
raise NotImplementedError()
def pagewalk(addr, entry=None):
pi = arch_paginginfo()
if pi:
return pi.pagewalk(addr, entry)
else:
raise NotImplementedError()
def paging_enabled() -> bool:
arch_name = pwndbg.aglib.arch.name
if arch_name == "i386":

@ -1,6 +1,7 @@
from __future__ import annotations
import math
from typing import Dict
from typing import List
from typing import Tuple
@ -9,8 +10,8 @@ import pwndbg.aglib.vmmap_custom
import pwndbg.color.message as M
import pwndbg.lib.cache
import pwndbg.lib.memory
from pwndbg.lib.regs import BitFlags
ENTRYMASK = ~((1 << 12) - 1) & ((1 << 51) - 1)
# don't return None but rather an invalid value for address markers
# this way arithmetic ops do not panic if physmap is not found
INVALID_ADDR = 1 << 64
@ -46,6 +47,9 @@ class ArchPagingInfo:
vmemmap: int
kbase: int
addr_marker_sz: int
va_bits: int
pagetable_cache: Dict[pwndbg.dbg_mod.Value, Dict[int, int]] = {}
pagetableptr_cache: Dict[int, pwndbg.dbg_mod.Value] = {}
@property
@pwndbg.lib.cache.cache_until("start")
@ -93,8 +97,86 @@ class ArchPagingInfo:
return None
def pagewalk(
self, target, entry
) -> Tuple[Tuple[str, ...], List[Tuple[int | None, int | None]]]:
raise NotImplementedError()
def pagewalk_helper(
self, target, entry, kernel_phys_base=0
) -> List[Tuple[int | None, int | None]]:
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)
page_shift = self.page_shift
ENTRYMASK = ~((1 << page_shift) - 1) & ((1 << self.va_bits) - 1)
for i in range(level, 0, -1):
vaddr = (entry & ENTRYMASK) + base - kernel_phys_base
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
entry = 0
try:
# with this optimization, roughly x2 as fast on average
# especially useful when parsing a large number of pages, e.g. set kernel-vmmap monitor
if vaddr not in self.pagetableptr_cache:
self.pagetableptr_cache[vaddr] = pwndbg.aglib.memory.get_typed_pointer(
"unsigned long", vaddr
)
table = self.pagetableptr_cache[vaddr]
if table not in self.pagetable_cache:
self.pagetable_cache[table] = {}
table_cache = self.pagetable_cache[table]
if idx not in table_cache:
table_cache[idx] = int(table[idx])
entry = table_cache[idx]
# Prior to optimization:
# table = pwndbg.aglib.memory.get_typed_pointer("unsigned long", vaddr)
# entry = int(table[idx])
except Exception as e:
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 - kernel_phys_base)
return result
def pageentry_flags(self, level) -> BitFlags:
raise NotImplementedError()
def should_stop_pagewalk(self, is_last):
raise NotImplementedError()
class x86_64PagingInfo(ArchPagingInfo):
def __init__(self):
self.va_bits = 48 if self.paging_level == 4 else 51
# https://blog.zolutal.io/understanding-paging/
self.pagetable_level_names = (
(
"Page",
"PT",
"PMD",
"PUD",
"PGD",
)
if self.paging_level == 4
else (
"Page",
"PT",
"PMD",
"P4D",
"PUD",
"PGD",
)
)
@property
@pwndbg.lib.cache.cache_until("stop")
def physmap(self):
@ -222,6 +304,19 @@ 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]]]:
if entry is None:
entry = pwndbg.aglib.regs["cr3"]
return self.pagetable_level_names, 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)])
def should_stop_pagewalk(self, entry):
return entry & (1 << 7) > 0
class Aarch64PagingInfo(ArchPagingInfo):
def __init__(self):
@ -236,6 +331,29 @@ class Aarch64PagingInfo(ArchPagingInfo):
self.vmalloc = module_start_wo_kaslr + 0x80000000
shift = self.page_shift - self.STRUCT_PAGE_SHIFT
self.VMEMMAP_SIZE = (module_start_wo_kaslr - ((-1 << self.va_bits) + 2**64)) >> shift
# correct for linux
if self.paging_level == 4:
self.pagetable_level_names = (
"Page",
"L3",
"L2",
"L1",
"L0",
)
elif self.paging_level == 3:
self.pagetable_level_names = (
"Page",
"L3",
"L2",
"L1",
)
elif self.paging_level == 2:
self.pagetable_level_names = (
"Page",
"L3",
"L2",
)
@property
@pwndbg.lib.cache.cache_until("stop")
@ -336,13 +454,33 @@ class Aarch64PagingInfo(ArchPagingInfo):
@property
@pwndbg.lib.cache.cache_until("stop")
def page_shift(self) -> int:
# TODO: this might be arm version dependent
if self.tcr_el1["TG1"] == 1:
if self.tcr_el1["TG1"] == 0b01:
return 14
elif self.tcr_el1["TG1"] == 0:
elif self.tcr_el1["TG1"] == 0b10:
return 12
else:
elif self.tcr_el1["TG1"] == 0b11:
return 16
raise NotImplementedError()
@property
@pwndbg.lib.cache.cache_until("stop")
def page_shift_user(self) -> int:
if self.tcr_el1["TG0"] == 0b00:
return 12
elif self.tcr_el1["TG0"] == 0b01:
return 16
elif self.tcr_el1["TG0"] == 0b10:
return 14
raise NotImplementedError()
@property
@pwndbg.lib.cache.cache_until("stop")
def paging_level(self):
# https://www.kernel.org/doc/html/v5.3/arm64/memory.html
if self.page_shift == 16:
return 2
# in some cases, not all addressing bits are used
return (self.va_bits - self.page_shift + (self.page_shift - 4)) // (self.page_shift - 3)
@pwndbg.lib.cache.cache_until("stop")
def markers(self) -> Tuple[Tuple[str, int], ...]:
@ -413,35 +551,41 @@ class Aarch64PagingInfo(ArchPagingInfo):
if pwndbg.aglib.regs[pwndbg.aglib.regs.stack] in page:
page.objfile = "kernel [stack]"
@pwndbg.aglib.proc.OnlyWithArch(["x86-64"])
def pagewalk(target, entry=None) -> List[Tuple[int | None, int | None]]:
level = pwndbg.aglib.kernel.arch_paginginfo().paging_level
base = pwndbg.aglib.kernel.arch_paginginfo().physmap
if entry is None:
entry = pwndbg.aglib.regs["cr3"]
else:
entry = int(pwndbg.dbg.selected_frame().evaluate_expression(entry))
if entry > base:
# user inputted a physmap address as pointer to pgd
entry -= base
result: List[Tuple[int | None, int | None]] = [(None, None)] * (level + 1)
for i in range(level, 0, -1):
vaddr = (entry & ENTRYMASK) + base
if entry & (1 << 7) > 0:
break
shift = (i - 1) * 9 + 12
offset = target & ((1 << shift) - 1)
idx = (target & (0x1FF << shift)) >> shift
entry = 0
@property
@pwndbg.lib.cache.cache_until("start")
def kernel_phys_start(self):
found_system = False
try:
table = pwndbg.aglib.memory.get_typed_pointer("unsigned long", vaddr)
entry = int(table[idx])
except Exception as e:
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)
return result
for line in pwndbg.dbg.selected_inferior().send_monitor("info mtree -f").splitlines():
line = line.strip()
if "Root memory region: system" in line:
found_system = True
if found_system:
split = line.split("-")
if "ram" in line and len(split) > 1:
return int(split[0], 16)
except Exception:
pass
return 0x40000000 # default
def pagewalk(
self, target, entry
) -> Tuple[Tuple[str, ...], List[Tuple[int | None, int | None]]]:
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, self.kernel_phys_start
)
def pageentry_flags(self, is_last) -> BitFlags:
if is_last:
return BitFlags([("UNX", 54), ("PNX", 53), ("AP", (6, 7))])
return BitFlags([("UNX", 60), ("PNX", 59), ("AP", (6, 7))])
def should_stop_pagewalk(self, entry):
# self.entry is set because the call chain
return (((entry & 1) == 0) or ((entry & 3) == 1)) and entry != self.entry

@ -388,7 +388,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.paging.pagewalk(page.start)
_, pgwalk_res = pwndbg.aglib.kernel.pagewalk(page.start)
entry, _ = pgwalk_res[0]
if entry and entry >> 63 == 0:
page.flags |= 1

@ -103,7 +103,7 @@ def format_flags(value: int | None, flags: BitFlags, last: int | None = None):
size = 1
else:
assert len(bit) == 2
size = bit[1]
size = bit[1] - bit[0] + 1
bit = bit[0]
mask = (1 << size) - 1
@ -112,7 +112,10 @@ def format_flags(value: int | None, flags: BitFlags, last: int | None = None):
# If the bitfield is larger than a single bit, we can't communicate the value
# with just the case of the name, so append the actual value
if size > 1:
name = f"{name}:{flag_val}"
if size == 2:
name = f"{name}:{flag_val:0{size}b}"
else:
name = f"{name}:{flag_val:#x}"
if flag_val == 0:
name = flag_unset(name.lower())

@ -8,17 +8,25 @@ import pwndbg.aglib.regs
import pwndbg.color as C
import pwndbg.color.message as M
from pwndbg.commands import CommandCategory
from pwndbg.lib.regs import BitFlags
parser = argparse.ArgumentParser(description="Performs pagewalk.")
parser.add_argument("vaddr", type=str, help="virtual address to walk")
parser.add_argument("--pgd", dest="entry", type=str, default=None, help="")
pageflags = BitFlags([("NX", 63), ("PS", 7), ("A", 5), ("W", 1), ("P", 0)])
PAGETYPES = (
"buddy",
"offline",
"table",
"guard",
"hugetlb",
"slab",
"zsmalloc",
"unaccepted",
)
def print_pagetable_entry(name: str, paddr: int | None, vaddr: int):
def print_pagetable_entry(name: str, paddr: int | None, vaddr: int, 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:
@ -26,45 +34,56 @@ def print_pagetable_entry(name: str, paddr: int | None, vaddr: int):
print(f"{C.blue(name)} @ {C.yellow(hex(vaddr))} {flags}")
def pg_indices(vaddr, nr_level):
result = [vaddr & (0x1000 - 1)]
vaddr >>= 12
for _ in range(nr_level):
result.append(vaddr & (0x1FF))
vaddr >>= 9
return result
def page_type(page):
names = PAGETYPES
page_type_val = pwndbg.aglib.memory.s32(page + 0x30)
if page_type_val == -1:
return "initialized"
if page_type_val >= 0:
return f"mapcount: {page_type_val}"
page_type_val = pwndbg.aglib.memory.u32(page + 0x30)
if pwndbg.aglib.kernel.krelease() >= (6, 12):
idx = (page_type_val >> 24) - 0xF0
if idx < len(names):
return names[idx]
if pwndbg.aglib.kernel.krelease() >= (6, 11):
names = names[:-1][::-1]
for i in range(len(names)):
if page_type_val & (1 << (i + 24)) == 0:
return names[i]
if pwndbg.aglib.kernel.krelease() >= (5, 0):
names = names[:5]
for i in range(len(names)):
if page_type_val & (1 << (7 + i)) == 0:
return names[i]
return "unknown"
def page_info(page):
try:
refcount = pwndbg.aglib.memory.u32(page + 0x34)
print(
f"{C.green('page')} @ {C.yellow(hex(page))} [{page_type(page)}, refcount: {refcount}]"
)
except (ValueError, TypeError):
print(M.warn("invalid page address"))
@pwndbg.commands.Command(parser, category=CommandCategory.KERNEL)
@pwndbg.commands.OnlyWhenQemuKernel
@pwndbg.commands.OnlyWhenPagingEnabled
@pwndbg.aglib.proc.OnlyWithArch(["x86-64"])
@pwndbg.aglib.proc.OnlyWithArch(["x86-64", "aarch64"])
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))
# https://blog.zolutal.io/understanding-paging/
level = pwndbg.aglib.kernel.arch_paginginfo().paging_level
names = (
"Page",
"PT",
"PMD",
"PUD",
"PGD",
)
if level == 5:
names = (
"Page",
"PT",
"PMD",
"P4D",
"PUD",
"PGD",
)
entries = pwndbg.aglib.kernel.paging.pagewalk(vaddr, entry)
for i in range(level, 0, -1):
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:
break
print_pagetable_entry(names[i], entry, vaddr)
print_pagetable_entry(names[i], entry, vaddr, i, next is None or i == 1)
_, vaddr = entries[0]
if vaddr is None:
print(M.warn("address is not mapped"))
@ -74,7 +93,7 @@ def pagewalk(vaddr, entry=None):
def paging_print_helper(name, addr):
print(f"{C.green(name)}: {C.yellow(hex(pwndbg.aglib.kernel.phys_to_virt(int(addr))))}")
print(f"{C.green(name)}: {C.yellow(hex(addr))}")
p2v_parser = argparse.ArgumentParser(
@ -87,14 +106,17 @@ p2v_parser.add_argument("paddr", type=str, help="")
@pwndbg.commands.OnlyWhenQemuKernel
@pwndbg.commands.OnlyWithKernelDebugSyms
@pwndbg.commands.OnlyWhenPagingEnabled
@pwndbg.aglib.proc.OnlyWithArch(["x86-64", "aarch64"])
def p2v(paddr):
paddr = pwndbg.dbg.selected_frame().evaluate_expression(paddr)
vaddr = pwndbg.aglib.kernel.phys_to_virt(int(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)
v2p_parser = argparse.ArgumentParser(
description="Translate virtual address to its corresponding physical address."
description="Translate virtual address to its corresponding physmap address."
)
v2p_parser.add_argument("vaddr", type=str, help="")
@ -103,7 +125,31 @@ v2p_parser.add_argument("vaddr", type=str, help="")
@pwndbg.commands.OnlyWhenQemuKernel
@pwndbg.commands.OnlyWithKernelDebugSyms
@pwndbg.commands.OnlyWhenPagingEnabled
@pwndbg.aglib.proc.OnlyWithArch(["x86-64", "aarch64"])
def v2p(vaddr):
vaddr = pwndbg.dbg.selected_frame().evaluate_expression(vaddr)
paddr = pwndbg.aglib.kernel.virt_to_phys(int(vaddr))
paging_print_helper("Physical address", paddr)
vaddr = int(pwndbg.dbg.selected_frame().evaluate_expression(vaddr))
entry, paddr = pwndbg.aglib.kernel.pagewalk(vaddr)[1][0] # more accurate
if not entry:
print(M.warn("virtual to page failed"))
paging_print_helper("Physmap address", paddr)
# paddr is the physmap address which is a virtual address
page = pwndbg.aglib.kernel.virt_to_page(paddr)
page_info(page)
page_parser = argparse.ArgumentParser(
description="Convert a pointer to a `struct page` to its corresponding virtual address."
)
page_parser.add_argument("page", type=str, help="")
@pwndbg.commands.Command(page_parser, category=CommandCategory.KERNEL)
@pwndbg.commands.OnlyWhenQemuKernel
@pwndbg.commands.OnlyWithKernelDebugSyms
@pwndbg.commands.OnlyWhenPagingEnabled
@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)

@ -46,7 +46,7 @@ class BitFlags:
if isinstance(r, int):
return (self.value >> r) & 1
s, e = r
return ((~((1 << s) - 1) & ((1 << e) - 1)) & self.value) >> s
return ((~((1 << s) - 1) & ((1 << (e + 1)) - 1)) & self.value) >> s
def __setitem__(self, key, value):
self.flags[key] = value
@ -485,7 +485,7 @@ aarch64_cpsr_flags = BitFlags(
("A", 8),
("I", 7),
("F", 6),
("EL", (2, 2)),
("EL", 2),
("SP", 0),
]
)
@ -535,6 +535,7 @@ aarch64_tcr_flags = BitFlags(
[
("TG1", (30, 31)),
("T1SZ", (16, 21)),
("TG0", (14, 15)),
("T0SZ", (0, 5)),
]
)
@ -608,6 +609,8 @@ aarch64 = RegisterSet(
"spsr_el2": aarch64_cpsr_flags,
"spsr_el3": aarch64_cpsr_flags,
"tcr_el1": aarch64_tcr_flags,
"ttbr0_el1": BitFlags(),
"ttbr1_el1": BitFlags(),
},
# X29 is the frame pointer register (FP) but setting it
# as frame here messes up the register order to the point

@ -127,6 +127,11 @@ def test_x64_extra_registers_under_kernel_mode():
assert flag in res or flag.upper() in res
def get_slab_freelist_elements(out):
out = pwndbg.color.strip(out)
return re.findall(r"- \[0x[0-9a-fA-F\-]{2}\] (0x[0-9a-fA-F]+)", out)
def get_slab_object_address():
"""helper function to get the address of some kmalloc slab object
and the associated slab cache name"""
@ -134,9 +139,7 @@ def get_slab_object_address():
for cache in caches:
cache_name = cache.name
info = gdb.execute(f"slab info -v {cache_name}", to_string=True)
ansi_escape = re.compile(r"\x1b\[[0-9;]*m")
info = ansi_escape.sub("", info)
matches = re.findall(r"- \[0x[0-9a-fA-F\-]{2}\] (0x[0-9a-fA-F]+)", info)
matches = get_slab_freelist_elements(info)
if len(matches) > 0:
return (matches[0], cache_name)
raise ValueError("Could not find any slab objects")
@ -192,6 +195,11 @@ def test_command_kernel_vmmap():
)
def get_buddy_freelist_elements(out):
out = pwndbg.color.strip(out)
return re.findall(r"\[0x[0-9a-fA-F\-]{2}\] (0x[0-9a-fA-F]{16})", out)
@pytest.mark.skipif(not pwndbg.aglib.kernel.has_debug_syms(), reason="test requires debug symbols")
def test_command_buddydump():
res = gdb.execute("buddydump", to_string=True)
@ -201,16 +209,13 @@ def test_command_buddydump():
# this indicates the buddy allocator contains at least one entry
assert "Order" in res and "Zone" in res and ("per_cpu_pageset" in res or "free_area" in res)
ansi_escape = re.compile(r"\x1b\[[0-9;]*m")
res = ansi_escape.sub("", res)
# find the starting addresses of all entries within the freelists
matches = re.findall(r"\[0x[0-9a-fA-F\-]{2}\] (0x[0-9a-fA-F]{16})", res)
matches = get_buddy_freelist_elements(res)
for i in range(0, len(matches), 20):
# check every 20 elements so tests do not take too long
match = int(matches[i], 16)
res = gdb.execute(f"bud -f {hex(match + random.randint(0, 0x1000 - 1))}", to_string=True)
res = ansi_escape.sub("", res)
_matches = re.findall(r"\[0x[0-9a-fA-F\-]{2}\] (0x[0-9a-fA-F]{16})", res)
_matches = get_buddy_freelist_elements(res)
# asserting `bud -f` behaviour -- should be able to find the corresponding entry to an address
# even if the address is not aligned
assert len(_matches) == 1 and int(_matches[0], 16) == match
@ -236,28 +241,81 @@ def test_command_buddydump():
assert "free_area" not in filter_res
@pytest.mark.skipif(
pwndbg.aglib.arch.name not in ["i386", "x86-64"],
reason="pagewalk is only fully implemented for x86 (partially relies on cr3)",
)
def check_0x100_bytes(address, physmap_addr):
# compare the first 0x100 bytes of the page (e.g. first kernel image page) with its physmap conterpart
expected = pwndbg.aglib.memory.read(address, 0x100)
actual = pwndbg.aglib.memory.read(physmap_addr, 0x100)
assert all(expected[i] == actual[i] for i in range(0x100))
def test_command_pagewalk():
address = pwndbg.aglib.kernel.kbase()
if address is None:
# no kbase? fine
pages = pwndbg.aglib.vmmap.get()
address = pages[0].start
res = gdb.execute(f"pagewalk {hex(address)}", to_string=True)
assert "PMD" in res # Page Size is only set for PMDe or PTe
assert any(
name in res
for name in (
"PMD", # Page Size is only set for PMDe or PTe
"L1",
"L3",
)
)
res = res.splitlines()[-1]
match = re.findall(r"0x[0-9a-fA-F]{16}", res)[0]
physmap_addr = int(match, 16)
# compare the first 0x100 bytes of the page (e.g. first kernel image page) with its physmap conterpart
expected = pwndbg.aglib.memory.read(address, 0x100)
actual = pwndbg.aglib.memory.read(physmap_addr, 0x100)
assert all(expected[i] == actual[i] for i in range(0x100))
check_0x100_bytes(address, physmap_addr)
# make sure that when using cr3 for pgd, it still works
res2 = gdb.execute(f"pagewalk {hex(address)} --pgd $cr3", to_string=True).splitlines()[-1]
pgd_ptr = "$cr3"
if pwndbg.aglib.arch.name == "aarch64":
if pwndbg.aglib.memory.is_kernel(address):
pgd_ptr = pwndbg.aglib.regs.TTBR1_EL1
else:
pgd_ptr = pwndbg.aglib.regs.TTBR0_EL1
res2 = gdb.execute(f"pagewalk {hex(address)} --pgd {pgd_ptr}", to_string=True).splitlines()[-1]
assert res == res2
# test non nonexistent address
res = gdb.execute("pagewalk 0", to_string=True)
assert res.splitlines()[-1] == "address is not mapped"
@pytest.mark.skipif(not pwndbg.aglib.kernel.has_debug_syms(), reason="test requires debug symbols")
def test_command_paging():
def test_command_paging_helper(pagetype, addr):
out = gdb.execute(f"v2p {addr}", to_string=True)
out = pwndbg.color.strip(out)
# pagetype should be correct
assert pagetype in out
page = int(out.splitlines()[1].split()[2], 16)
physmap_addr = int(out.splitlines()[0].split()[-1], 16)
# the first 0x100 bytes of the resolved address should match the original
check_0x100_bytes(addr, physmap_addr)
phys_addr = pwndbg.aglib.kernel.virt_to_phys(physmap_addr)
out = gdb.execute(f"p2v {phys_addr}", to_string=True)
out = pwndbg.color.strip(out)
# the virtual address should be the physmap address
assert physmap_addr == int(out.splitlines()[0].split()[-1], 16)
out = gdb.execute(f"pageinfo {page}", to_string=True)
out = pwndbg.color.strip(out)
# the virtual address should be the physmap address
assert physmap_addr == int(out.splitlines()[0].split()[-1], 16)
# kbase, slab, buddy, vmemmap
kbase = pwndbg.aglib.kernel.kbase()
test_command_paging_helper("initialized", kbase)
vmemmap = pwndbg.aglib.kernel.arch_paginginfo().vmemmap
test_command_paging_helper("initialized", vmemmap)
res = gdb.execute("buddydump", to_string=True)
matches = get_buddy_freelist_elements(res)
if len(matches) > 0 and "free_area" in res: # only pages in free_area is marked "buddy"
buddy = int(matches[-1], 16)
test_command_paging_helper("buddy", buddy)
if pwndbg.aglib.kernel.krelease() >= (6, 11):
# the slab marker is only added after v6.11
res = gdb.execute("slab info -v -p kmalloc-32", to_string=True)
matches = get_slab_freelist_elements(res)
if len(matches) > 0:
slab = int(matches[-1].split()[-1], 16)
test_command_paging_helper("slab", slab)
res = gdb.execute(f"pagewalk {kbase}")

@ -218,7 +218,7 @@ def test_aarch64_conditional_jump_output(qemu_assembly_run):
"\n"
" 0x1010140 <C> ✔ tbnz w2, #3, D <D>\n"
"\n"
" 0x1010148 <D> cmp x2, x3 0xa - 0x0 CPSR => 0x20000000 [ n z C v q pan il d a i f el:0 sp ]\n"
" 0x1010148 <D> cmp x2, x3 0xa - 0x0 CPSR => 0x20000000 [ n z C v q pan il d a i f el sp ]\n"
" 0x101014c <D+4> ✘ b.eq E <E>\n"
" \n"
" 0x1010150 <D+8> nop \n"
@ -510,7 +510,7 @@ def test_aarch64_write_cpsr_when_zero(qemu_assembly_run):
"LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n"
"─────────────────────[ DISASM / aarch64 / set emulate on ]──────────────────────\n"
" 0x1010120 <_start> mov x19, #8 X19 => 8\n"
" 0x1010124 <_start+4> cmn x19, #8 8 + 8 CPSR => 0x0 [ n z c v q pan il d a i f el:0 sp ]\n"
" 0x1010124 <_start+4> cmn x19, #8 8 + 8 CPSR => 0x0 [ n z c v q pan il d a i f el sp ]\n"
" ► 0x1010128 <_start+8> ✔ b.ne exit <exit>\n"
"\n"
" 0x1010140 <exit> mov x0, #0 X0 => 0\n"
@ -735,7 +735,7 @@ def test_aarch64_reference(qemu_start_binary):
gdb.execute("auxv", to_string=True)
assert (
gdb.execute("cpsr", to_string=True, from_tty=False).strip()
== "cpsr 0x0 [ n z c v q pan il d a i f el:0 sp ]"
== "cpsr 0x0 [ n z c v q pan il d a i f el sp ]"
)
gdb.execute("context", to_string=True)
gdb.execute("hexdump", to_string=True)

Loading…
Cancel
Save