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). - [kversion](kernel/kversion.md) - Outputs the kernel version (/proc/version).
- [msr](kernel/msr.md) - Read or write to Model Specific Register (MSR) - [msr](kernel/msr.md) - Read or write to Model Specific Register (MSR)
- [p2v](kernel/p2v.md) - Translate physical address to its corresponding virtual address. - [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. - [pagewalk](kernel/pagewalk.md) - Performs pagewalk.
- [slab](kernel/slab.md) - Prints information about the linux kernel's slab allocator SLUB. - [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 ## 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 arguments
|Positional Argument|Help| |Positional Argument|Help|

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

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import math import math
from typing import Dict
from typing import List from typing import List
from typing import Tuple from typing import Tuple
@ -9,8 +10,8 @@ import pwndbg.aglib.vmmap_custom
import pwndbg.color.message as M import pwndbg.color.message as M
import pwndbg.lib.cache import pwndbg.lib.cache
import pwndbg.lib.memory 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 # don't return None but rather an invalid value for address markers
# this way arithmetic ops do not panic if physmap is not found # this way arithmetic ops do not panic if physmap is not found
INVALID_ADDR = 1 << 64 INVALID_ADDR = 1 << 64
@ -46,6 +47,9 @@ class ArchPagingInfo:
vmemmap: int vmemmap: int
kbase: int kbase: int
addr_marker_sz: 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 @property
@pwndbg.lib.cache.cache_until("start") @pwndbg.lib.cache.cache_until("start")
@ -93,8 +97,86 @@ class ArchPagingInfo:
return None 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): 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 @property
@pwndbg.lib.cache.cache_until("stop") @pwndbg.lib.cache.cache_until("stop")
def physmap(self): def physmap(self):
@ -222,6 +304,19 @@ 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(
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): class Aarch64PagingInfo(ArchPagingInfo):
def __init__(self): def __init__(self):
@ -236,6 +331,29 @@ class Aarch64PagingInfo(ArchPagingInfo):
self.vmalloc = module_start_wo_kaslr + 0x80000000 self.vmalloc = module_start_wo_kaslr + 0x80000000
shift = self.page_shift - self.STRUCT_PAGE_SHIFT shift = self.page_shift - self.STRUCT_PAGE_SHIFT
self.VMEMMAP_SIZE = (module_start_wo_kaslr - ((-1 << self.va_bits) + 2**64)) >> 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 @property
@pwndbg.lib.cache.cache_until("stop") @pwndbg.lib.cache.cache_until("stop")
@ -336,13 +454,33 @@ class Aarch64PagingInfo(ArchPagingInfo):
@property @property
@pwndbg.lib.cache.cache_until("stop") @pwndbg.lib.cache.cache_until("stop")
def page_shift(self) -> int: def page_shift(self) -> int:
# TODO: this might be arm version dependent if self.tcr_el1["TG1"] == 0b01:
if self.tcr_el1["TG1"] == 1:
return 14 return 14
elif self.tcr_el1["TG1"] == 0: elif self.tcr_el1["TG1"] == 0b10:
return 12 return 12
else: elif self.tcr_el1["TG1"] == 0b11:
return 16 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") @pwndbg.lib.cache.cache_until("stop")
def markers(self) -> Tuple[Tuple[str, int], ...]: def markers(self) -> Tuple[Tuple[str, int], ...]:
@ -413,35 +551,41 @@ class Aarch64PagingInfo(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]"
@property
@pwndbg.aglib.proc.OnlyWithArch(["x86-64"]) @pwndbg.lib.cache.cache_until("start")
def pagewalk(target, entry=None) -> List[Tuple[int | None, int | None]]: def kernel_phys_start(self):
level = pwndbg.aglib.kernel.arch_paginginfo().paging_level found_system = False
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
try: try:
table = pwndbg.aglib.memory.get_typed_pointer("unsigned long", vaddr) for line in pwndbg.dbg.selected_inferior().send_monitor("info mtree -f").splitlines():
entry = int(table[idx]) line = line.strip()
except Exception as e: if "Root memory region: system" in line:
print(M.warn(f"Exception while page walking: {e}")) found_system = True
entry = 0 if found_system:
if entry == 0: split = line.split("-")
return result if "ram" in line and len(split) > 1:
result[i] = (entry, vaddr) return int(split[0], 16)
result[0] = (entry, (entry & ENTRYMASK) + base + offset) except Exception:
return result 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: for page in pages:
if page.objfile == kv.pi.ESPSTACK: if page.objfile == kv.pi.ESPSTACK:
continue continue
pgwalk_res = pwndbg.aglib.kernel.paging.pagewalk(page.start) _, pgwalk_res = pwndbg.aglib.kernel.pagewalk(page.start)
entry, _ = pgwalk_res[0] entry, _ = pgwalk_res[0]
if entry and entry >> 63 == 0: if entry and entry >> 63 == 0:
page.flags |= 1 page.flags |= 1

@ -103,7 +103,7 @@ def format_flags(value: int | None, flags: BitFlags, last: int | None = None):
size = 1 size = 1
else: else:
assert len(bit) == 2 assert len(bit) == 2
size = bit[1] size = bit[1] - bit[0] + 1
bit = bit[0] bit = bit[0]
mask = (1 << size) - 1 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 # 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 # with just the case of the name, so append the actual value
if size > 1: 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: if flag_val == 0:
name = flag_unset(name.lower()) name = flag_unset(name.lower())

@ -8,17 +8,25 @@ 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.commands import CommandCategory from pwndbg.commands import CommandCategory
from pwndbg.lib.regs import BitFlags
parser = argparse.ArgumentParser(description="Performs pagewalk.") parser = argparse.ArgumentParser(description="Performs pagewalk.")
parser.add_argument("vaddr", type=str, help="virtual address to walk") parser.add_argument("vaddr", type=str, help="virtual address to walk")
parser.add_argument("--pgd", dest="entry", type=str, default=None, help="") parser.add_argument("--pgd", dest="entry", type=str, default=None, help="")
PAGETYPES = (
pageflags = BitFlags([("NX", 63), ("PS", 7), ("A", 5), ("W", 1), ("P", 0)]) "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 = "" 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: 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}") print(f"{C.blue(name)} @ {C.yellow(hex(vaddr))} {flags}")
def pg_indices(vaddr, nr_level): def page_type(page):
result = [vaddr & (0x1000 - 1)] names = PAGETYPES
vaddr >>= 12 page_type_val = pwndbg.aglib.memory.s32(page + 0x30)
for _ in range(nr_level): if page_type_val == -1:
result.append(vaddr & (0x1FF)) return "initialized"
vaddr >>= 9 if page_type_val >= 0:
return result 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.Command(parser, category=CommandCategory.KERNEL)
@pwndbg.commands.OnlyWhenQemuKernel @pwndbg.commands.OnlyWhenQemuKernel
@pwndbg.commands.OnlyWhenPagingEnabled @pwndbg.commands.OnlyWhenPagingEnabled
@pwndbg.aglib.proc.OnlyWithArch(["x86-64"]) @pwndbg.aglib.proc.OnlyWithArch(["x86-64", "aarch64"])
def pagewalk(vaddr, entry=None): 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)) vaddr = int(pwndbg.dbg.selected_frame().evaluate_expression(vaddr))
# https://blog.zolutal.io/understanding-paging/ names, entries = pwndbg.aglib.kernel.pagewalk(vaddr, entry)
level = pwndbg.aglib.kernel.arch_paginginfo().paging_level for i in range(len(names) - 1, 0, -1):
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):
entry, vaddr = entries[i] entry, vaddr = entries[i]
next, _ = entries[i - 1]
if entry is None: if entry is None:
break 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] _, vaddr = entries[0]
if vaddr is None: if vaddr is None:
print(M.warn("address is not mapped")) print(M.warn("address is not mapped"))
@ -74,7 +93,7 @@ def pagewalk(vaddr, entry=None):
def paging_print_helper(name, addr): 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( p2v_parser = argparse.ArgumentParser(
@ -87,14 +106,17 @@ p2v_parser.add_argument("paddr", type=str, help="")
@pwndbg.commands.OnlyWhenQemuKernel @pwndbg.commands.OnlyWhenQemuKernel
@pwndbg.commands.OnlyWithKernelDebugSyms @pwndbg.commands.OnlyWithKernelDebugSyms
@pwndbg.commands.OnlyWhenPagingEnabled @pwndbg.commands.OnlyWhenPagingEnabled
@pwndbg.aglib.proc.OnlyWithArch(["x86-64", "aarch64"])
def p2v(paddr): def p2v(paddr):
paddr = pwndbg.dbg.selected_frame().evaluate_expression(paddr) paddr = int(pwndbg.dbg.selected_frame().evaluate_expression(paddr))
vaddr = pwndbg.aglib.kernel.phys_to_virt(int(paddr)) vaddr = pwndbg.aglib.kernel.phys_to_virt(paddr)
paging_print_helper("Virtual address", vaddr) paging_print_helper("Virtual address", vaddr)
page = pwndbg.aglib.kernel.virt_to_page(vaddr)
page_info(page)
v2p_parser = argparse.ArgumentParser( 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="") 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.OnlyWhenQemuKernel
@pwndbg.commands.OnlyWithKernelDebugSyms @pwndbg.commands.OnlyWithKernelDebugSyms
@pwndbg.commands.OnlyWhenPagingEnabled @pwndbg.commands.OnlyWhenPagingEnabled
@pwndbg.aglib.proc.OnlyWithArch(["x86-64", "aarch64"])
def v2p(vaddr): def v2p(vaddr):
vaddr = pwndbg.dbg.selected_frame().evaluate_expression(vaddr) vaddr = int(pwndbg.dbg.selected_frame().evaluate_expression(vaddr))
paddr = pwndbg.aglib.kernel.virt_to_phys(int(vaddr)) entry, paddr = pwndbg.aglib.kernel.pagewalk(vaddr)[1][0] # more accurate
paging_print_helper("Physical address", paddr) 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): if isinstance(r, int):
return (self.value >> r) & 1 return (self.value >> r) & 1
s, e = r 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): def __setitem__(self, key, value):
self.flags[key] = value self.flags[key] = value
@ -485,7 +485,7 @@ aarch64_cpsr_flags = BitFlags(
("A", 8), ("A", 8),
("I", 7), ("I", 7),
("F", 6), ("F", 6),
("EL", (2, 2)), ("EL", 2),
("SP", 0), ("SP", 0),
] ]
) )
@ -535,6 +535,7 @@ aarch64_tcr_flags = BitFlags(
[ [
("TG1", (30, 31)), ("TG1", (30, 31)),
("T1SZ", (16, 21)), ("T1SZ", (16, 21)),
("TG0", (14, 15)),
("T0SZ", (0, 5)), ("T0SZ", (0, 5)),
] ]
) )
@ -608,6 +609,8 @@ aarch64 = RegisterSet(
"spsr_el2": aarch64_cpsr_flags, "spsr_el2": aarch64_cpsr_flags,
"spsr_el3": aarch64_cpsr_flags, "spsr_el3": aarch64_cpsr_flags,
"tcr_el1": aarch64_tcr_flags, "tcr_el1": aarch64_tcr_flags,
"ttbr0_el1": BitFlags(),
"ttbr1_el1": BitFlags(),
}, },
# X29 is the frame pointer register (FP) but setting it # X29 is the frame pointer register (FP) but setting it
# as frame here messes up the register order to the point # 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 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(): def get_slab_object_address():
"""helper function to get the address of some kmalloc slab object """helper function to get the address of some kmalloc slab object
and the associated slab cache name""" and the associated slab cache name"""
@ -134,9 +139,7 @@ def get_slab_object_address():
for cache in caches: for cache in caches:
cache_name = cache.name cache_name = cache.name
info = gdb.execute(f"slab info -v {cache_name}", to_string=True) info = gdb.execute(f"slab info -v {cache_name}", to_string=True)
ansi_escape = re.compile(r"\x1b\[[0-9;]*m") matches = get_slab_freelist_elements(info)
info = ansi_escape.sub("", info)
matches = re.findall(r"- \[0x[0-9a-fA-F\-]{2}\] (0x[0-9a-fA-F]+)", info)
if len(matches) > 0: if len(matches) > 0:
return (matches[0], cache_name) return (matches[0], cache_name)
raise ValueError("Could not find any slab objects") 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") @pytest.mark.skipif(not pwndbg.aglib.kernel.has_debug_syms(), reason="test requires debug symbols")
def test_command_buddydump(): def test_command_buddydump():
res = gdb.execute("buddydump", to_string=True) 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 # 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) 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 # 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): for i in range(0, len(matches), 20):
# check every 20 elements so tests do not take too long # check every 20 elements so tests do not take too long
match = int(matches[i], 16) match = int(matches[i], 16)
res = gdb.execute(f"bud -f {hex(match + random.randint(0, 0x1000 - 1))}", to_string=True) res = gdb.execute(f"bud -f {hex(match + random.randint(0, 0x1000 - 1))}", to_string=True)
res = ansi_escape.sub("", res) _matches = get_buddy_freelist_elements(res)
_matches = re.findall(r"\[0x[0-9a-fA-F\-]{2}\] (0x[0-9a-fA-F]{16})", res)
# asserting `bud -f` behaviour -- should be able to find the corresponding entry to an address # asserting `bud -f` behaviour -- should be able to find the corresponding entry to an address
# even if the address is not aligned # even if the address is not aligned
assert len(_matches) == 1 and int(_matches[0], 16) == match 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 assert "free_area" not in filter_res
@pytest.mark.skipif( def check_0x100_bytes(address, physmap_addr):
pwndbg.aglib.arch.name not in ["i386", "x86-64"], # compare the first 0x100 bytes of the page (e.g. first kernel image page) with its physmap conterpart
reason="pagewalk is only fully implemented for x86 (partially relies on cr3)", 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(): def test_command_pagewalk():
address = pwndbg.aglib.kernel.kbase() address = pwndbg.aglib.kernel.kbase()
if address is None: if address is None:
# no kbase? fine
pages = pwndbg.aglib.vmmap.get() pages = pwndbg.aglib.vmmap.get()
address = pages[0].start address = pages[0].start
res = gdb.execute(f"pagewalk {hex(address)}", to_string=True) 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] res = res.splitlines()[-1]
match = re.findall(r"0x[0-9a-fA-F]{16}", res)[0] match = re.findall(r"0x[0-9a-fA-F]{16}", res)[0]
physmap_addr = int(match, 16) physmap_addr = int(match, 16)
# compare the first 0x100 bytes of the page (e.g. first kernel image page) with its physmap conterpart check_0x100_bytes(address, physmap_addr)
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))
# make sure that when using cr3 for pgd, it still works # 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 assert res == res2
# test non nonexistent address # test non nonexistent address
res = gdb.execute("pagewalk 0", to_string=True) res = gdb.execute("pagewalk 0", to_string=True)
assert res.splitlines()[-1] == "address is not mapped" 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" "\n"
" 0x1010140 <C> ✔ tbnz w2, #3, D <D>\n" " 0x1010140 <C> ✔ tbnz w2, #3, D <D>\n"
"\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" " 0x101014c <D+4> ✘ b.eq E <E>\n"
" \n" " \n"
" 0x1010150 <D+8> nop \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" "LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n"
"─────────────────────[ DISASM / aarch64 / set emulate on ]──────────────────────\n" "─────────────────────[ DISASM / aarch64 / set emulate on ]──────────────────────\n"
" 0x1010120 <_start> mov x19, #8 X19 => 8\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" " ► 0x1010128 <_start+8> ✔ b.ne exit <exit>\n"
"\n" "\n"
" 0x1010140 <exit> mov x0, #0 X0 => 0\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) gdb.execute("auxv", to_string=True)
assert ( assert (
gdb.execute("cpsr", to_string=True, from_tty=False).strip() 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("context", to_string=True)
gdb.execute("hexdump", to_string=True) gdb.execute("hexdump", to_string=True)

Loading…
Cancel
Save