From b87c8d4b04d45a538f628d6da06fe4f0613fb3ca Mon Sep 17 00:00:00 2001 From: patryk4815 Date: Thu, 5 Jun 2025 21:03:31 +0200 Subject: [PATCH] Revert "Improving `vmmap` output when debugging kernel (#3020)" This reverts commit 16c75e6c24f1343496ae00e6a6ada559fbfc5883. --- pwndbg/aglib/kernel/__init__.py | 28 ++- pwndbg/aglib/kernel/paging.py | 8 +- pwndbg/aglib/kernel/vmmap.py | 177 +----------------- pwndbg/color/memory.py | 6 +- pwndbg/commands/vmmap.py | 3 +- pwndbg/lib/memory.py | 2 +- .../tests/system/test_commands_kernel.py | 26 --- 7 files changed, 44 insertions(+), 206 deletions(-) diff --git a/pwndbg/aglib/kernel/__init__.py b/pwndbg/aglib/kernel/__init__.py index adf199793..03b8f5cd0 100644 --- a/pwndbg/aglib/kernel/__init__.py +++ b/pwndbg/aglib/kernel/__init__.py @@ -202,7 +202,33 @@ def is_kaslr_enabled() -> bool: @pwndbg.lib.cache.cache_until("start") def kbase() -> int | None: - return pwndbg.aglib.kernel.paging.kbase() + arch_name = pwndbg.aglib.arch.name + + address = 0 + + if arch_name == "x86-64": + address = get_idt_entries()[0].offset + elif arch_name == "aarch64": + address = pwndbg.aglib.regs.vbar + else: + return None + + mappings = pwndbg.aglib.vmmap.get() + for mapping in mappings: + # TODO: Check alignment + + # only search in kernel mappings: + # https://www.kernel.org/doc/html/v5.3/arm64/memory.html + if mapping.vaddr & (0xFFFF << 48) == 0: + continue + + if not mapping.execute: + continue + + if address in mapping: + return mapping.vaddr + + return None def get_idt_entries() -> List[pwndbg.lib.kernel.structs.IDTEntry]: diff --git a/pwndbg/aglib/kernel/paging.py b/pwndbg/aglib/kernel/paging.py index 0941c0c74..16f03dc65 100644 --- a/pwndbg/aglib/kernel/paging.py +++ b/pwndbg/aglib/kernel/paging.py @@ -15,7 +15,7 @@ ENTRYMASK = ~((1 << 12) - 1) & ((1 << 51) - 1) @pwndbg.lib.cache.cache_until("start", "stop") def get_memory_map_raw() -> Tuple[pwndbg.lib.memory.Page, ...]: - return pwndbg.aglib.kernel.vmmap.kernel_vmmap(False) + return pwndbg.aglib.kernel.vmmap.kernel_vmmap() def find_kbase(pages) -> int | None: @@ -32,7 +32,7 @@ def find_kbase(pages) -> int | None: mappings = pages for mapping in mappings: - # should be page aligned -- either from pt-dump or info mem + # TODO: Check alignment # only search in kernel mappings: # https://www.kernel.org/doc/html/v5.3/arm64/memory.html @@ -68,11 +68,11 @@ guess_physmap = config.add_param( def physmap_base() -> int: - if pwndbg.aglib.kernel.has_debug_syms() and pwndbg.aglib.arch.name == "x86-64": + if pwndbg.aglib.kernel.has_debug_syms(): result = pwndbg.aglib.symbol.lookup_symbol_value("page_offset_base") if result is not None: return result - if guess_physmap or pwndbg.aglib.arch.name == "aarch64": + if guess_physmap: result = guess_physmap_base() if result is not None: return result diff --git a/pwndbg/aglib/kernel/vmmap.py b/pwndbg/aglib/kernel/vmmap.py index eb87d949e..018b1be5c 100644 --- a/pwndbg/aglib/kernel/vmmap.py +++ b/pwndbg/aglib/kernel/vmmap.py @@ -18,161 +18,13 @@ from pt.pt_x86_64_parse import PT_x86_64_Backend import pwndbg import pwndbg.aglib.arch import pwndbg.aglib.kernel -import pwndbg.aglib.kernel.paging import pwndbg.aglib.qemu import pwndbg.aglib.regs -import pwndbg.aglib.vmmap import pwndbg.color.message as M import pwndbg.lib.cache import pwndbg.lib.memory -class KernelVmmap: - USERLAND = "userland" - KERNELLAND = "kernel [.text]" - KERNELRO = "kernel [.rodata]" - KERNELBSS = "kernel [.bss]" - KERNELDRIVER = "kernel [.driver .bpf]" - ESPSTACK = "%esp fixup" - - def __init__(self, pages: Tuple[pwndbg.lib.memory.Page, ...]): - self.pages = pages - self.sections = None - if not pwndbg.aglib.kernel.has_debug_syms(): - return - self.kbase = kbase = pwndbg.aglib.kernel.paging.find_kbase(pages) - if pwndbg.aglib.arch.name == "x86-64": - # https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt - # works for v5.x and v6.x - physmap = pwndbg.aglib.kernel.paging.physmap_base() - vmalloc = pwndbg.aglib.symbol.lookup_symbol_value("vmalloc_base") - vmemmap = pwndbg.aglib.symbol.lookup_symbol_value("vmemmap_base") - self.sections = ( - (self.USERLAND, 0), - (None, 0x8000000000000000), - ("physmap", physmap), - ("vmalloc", vmalloc), - ("vmemmap", vmemmap), - # TODO: find better ways to handle the following constants - # I cound not find kernel symbols that reference their values - # the actual region base may differ but the region always falls within the below range - # even if KASLR is enabled - ("cpu entry", 0xFFFFFE0000000000), - ("rand cpu entry", 0xFFFFFE0000001000), - (self.ESPSTACK, 0xFFFFFF0000000000), - ("EFI", 0xFFFFFFEF00000000), - (self.KERNELLAND, kbase), - ("fixmap", 0xFFFFFFFFFF000000), - ("legacy abi", 0xFFFFFFFFFF600000), - (None, 0xFFFFFFFFFFFFFFFF), - ) - if pwndbg.aglib.arch.name == "aarch64": - # https://www.kernel.org/doc/html/v5.3/arm64/memory.html - # https://elixir.bootlin.com/linux/v6.15/source/arch/arm64/mm/ptdump.c#L351 - # TODO: I don't think those are necessarily accurate when KASLR is enabled - # but I'm not familiar with ARM enough quite yet to find better ways - sections = [(self.USERLAND, 0)] - address_markers = pwndbg.aglib.symbol.lookup_symbol_addr("address_markers") - value = 0 - name = None - for i in range(20): - value = pwndbg.aglib.memory.u64(address_markers + i * 0x10) - if value > 0: - name_ptr = pwndbg.aglib.memory.u64(address_markers + i * 0x10 + 8) - name = None - if name_ptr > 0: - name = pwndbg.aglib.memory.string(name_ptr).decode() - name = name.split(" ")[0].lower() - if "end" in name: - name = None - if name == "linear": - name = "physmap" - sections.append((name, value)) - if value == 0xFFFFFFFFFFFFFFFF: - break - self.sections = tuple(sections) - - def get_name(self, addr: int) -> str: - if addr is None or self.sections is None: - return None - for i in range(len(self.sections) - 1): - name, cur = self.sections[i] - _, next = self.sections[i + 1] - if cur is None or next is None: - continue - if addr >= cur and addr < next: - return name - return None - - def adjust(self): - for i, page in enumerate(self.pages): - name = self.get_name(page.start) - if name is not None: - page.objfile = name - user_idx, kernel_idx = None, None - for i, page in enumerate(self.pages): - if user_idx is None and page.objfile == self.USERLAND: - user_idx = i - if kernel_idx is None and page.objfile == self.KERNELLAND: - kernel_idx = i - self.handle_user_pages(user_idx) - self.handle_kernel_pages(kernel_idx) - self.handle_offsets() - - def handle_user_pages(self, user_idx): - if user_idx is None: - return - base_offset = self.pages[user_idx].start - for i in range(user_idx, len(self.pages)): - page = self.pages[i] - if page.objfile != self.USERLAND: - break - diff = page.start - base_offset - if diff > 0x100000: - if diff > 0x100000000000: - if page.execute: - page.objfile = "userland [library]" - elif page.rw: - page.objfile = "userland [stack]" - else: - page.objfile = "userland [heap]" - else: - # page.objfile += f"_{hex(i)[2:]}" - base_offset = page.start - - def handle_kernel_pages(self, kernel_idx): - if kernel_idx is None: - return - has_loadable_driver = False - for i in range(kernel_idx, len(self.pages)): - page = self.pages[i] - if page.objfile != self.KERNELLAND: - break - if not page.execute: - if page.write: - page.objfile = self.KERNELBSS - else: - page.objfile = self.KERNELRO - if has_loadable_driver: - page.objfile = self.KERNELDRIVER - if page.execute and page.start != self.kbase: - page.objfile = self.KERNELDRIVER - has_loadable_driver = True - if pwndbg.aglib.regs[pwndbg.aglib.regs.stack] in page: - page.objfile = "kernel [stack]" - - def handle_offsets(self): - prev_objfile, base = "", 0 - for page in self.pages: - # the check on KERNELRO is to make getting offsets for symbols such as `init_creds` more convinient - if page.objfile != self.KERNELRO and prev_objfile != page.objfile: - prev_objfile = page.objfile - base = page.start - page.offset = page.start - base - if len(hex(page.offset)) > 9: - page.offset = 0 - - # Most of QemuMachine code was inherited from gdb-pt-dump thanks to Martin Radev (@martinradev) # on the MIT license, see: # https://github.com/martinradev/gdb-pt-dump/blob/21158ac3f9b36d0e5e0c86193e0ef018fc628e74/pt_gdb/pt_gdb.py#L11-L80 @@ -325,6 +177,7 @@ def kernel_vmmap_via_page_tables() -> Tuple[pwndbg.lib.memory.Page, ...]: flags |= 1 objfile = f"[pt_{hex(start)[2:-3]}]" retpages.append(pwndbg.lib.memory.Page(start, size, flags, 0, objfile)) + return tuple(retpages) @@ -414,9 +267,10 @@ def kernel_vmmap_via_monitor_info_mem() -> Tuple[pwndbg.lib.memory.Page, ...]: flags |= 4 if "w" in perm: flags |= 2 - if len(perm) == 4: # if the qemu version displays if the page is executable - if "x" in perm: - flags |= 1 + # QEMU does not expose X/NX bit, see #685 + # if 'x' in perm: flags |= 1 + flags |= 1 + pages.append(pwndbg.lib.memory.Page(start, size, flags, 0, "")) return tuple(pages) @@ -440,7 +294,7 @@ Note that the page-tables method will require the QEMU kernel process to be on t ) -def kernel_vmmap(process_pages=True) -> Tuple[pwndbg.lib.memory.Page, ...]: +def kernel_vmmap() -> Tuple[pwndbg.lib.memory.Page, ...]: if not pwndbg.aglib.qemu.is_qemu_kernel(): return () @@ -453,22 +307,9 @@ def kernel_vmmap(process_pages=True) -> Tuple[pwndbg.lib.memory.Page, ...]: ): return () - pages = None if kernel_vmmap_mode == "page-tables": - pages = kernel_vmmap_via_page_tables() + return kernel_vmmap_via_page_tables() elif kernel_vmmap_mode == "monitor": - pages = kernel_vmmap_via_monitor_info_mem() - if process_pages and pwndbg.aglib.arch.name == "x86-64": - # TODO: check version here when QEMU displays the x bit for x64 - for page in pages: - pgwalk_res = pwndbg.aglib.kernel.paging.pagewalk(page.start) - entry, vaddr = pgwalk_res[0] - if entry and entry >> 63 == 0: - page.flags |= 1 - if pages is None: - return () - if process_pages: - kv = KernelVmmap(pages) - kv.adjust() + return kernel_vmmap_via_monitor_info_mem() - return tuple(pages) + return () diff --git a/pwndbg/color/memory.py b/pwndbg/color/memory.py index 012b93284..5e2e09a14 100644 --- a/pwndbg/color/memory.py +++ b/pwndbg/color/memory.py @@ -88,12 +88,10 @@ def get( color = normal elif "[stack" in page.objfile: color = c.stack + elif "[heap" in page.objfile: + color = c.heap elif page.execute: color = c.code - elif not page.write: - color = c.rodata - elif any(keyword in page.objfile for keyword in ("[heap", "physmap", "vmalloc")): - color = c.heap elif page.rw: color = c.data elif page.is_guard: diff --git a/pwndbg/commands/vmmap.py b/pwndbg/commands/vmmap.py index eab35aca8..939589887 100644 --- a/pwndbg/commands/vmmap.py +++ b/pwndbg/commands/vmmap.py @@ -14,7 +14,6 @@ from elftools.elf.elffile import ELFFile import pwndbg.aglib.arch import pwndbg.aglib.elf import pwndbg.aglib.file -import pwndbg.aglib.qemu import pwndbg.aglib.vmmap import pwndbg.aglib.vmmap_custom import pwndbg.color.memory as M @@ -50,7 +49,7 @@ def print_vmmap_table_header() -> None: prefer_relpaths = "on" if pwndbg.config.vmmap_prefer_relpaths else "off" width = 2 + 2 * pwndbg.aglib.arch.ptrsize print( - f"{'Start':>{width}} {'End':>{width}} {'Perm'} {'Size':>8} {'Offset':>7} " + f"{'Start':>{width}} {'End':>{width}} {'Perm'} {'Size':>8} {'Offset':>6} " f"{'File'} (set vmmap-prefer-relpaths {prefer_relpaths})" ) diff --git a/pwndbg/lib/memory.py b/pwndbg/lib/memory.py index ddcde3852..e1845c365 100644 --- a/pwndbg/lib/memory.py +++ b/pwndbg/lib/memory.py @@ -148,7 +148,7 @@ class Page: else: objfile = self.objfile width = 2 + 2 * pwndbg.aglib.arch.ptrsize - return f"{self.vaddr:#{width}x} {self.vaddr + self.memsz:#{width}x} {self.permstr} {self.memsz:8x} {self.offset:7x} {objfile or ''}" + return f"{self.vaddr:#{width}x} {self.vaddr + self.memsz:#{width}x} {self.permstr} {self.memsz:8x} {self.offset:6x} {objfile or ''}" def __repr__(self) -> str: return f"{self.__class__.__name__}({self.__str__()!r})" diff --git a/tests/qemu-tests/tests/system/test_commands_kernel.py b/tests/qemu-tests/tests/system/test_commands_kernel.py index 9b055d4b1..cdfd37437 100644 --- a/tests/qemu-tests/tests/system/test_commands_kernel.py +++ b/tests/qemu-tests/tests/system/test_commands_kernel.py @@ -129,32 +129,6 @@ def test_command_msr_write(): gdb.execute(f"msr MSR_LSTAR -w {prev_msr_lstar}") -@pytest.mark.skipif(not pwndbg.aglib.kernel.has_debug_syms(), reason="test requires debug symbols") -def test_command_kernel_vmmap(): - res = gdb.execute("vmmap", to_string=True) - assert all( - key in res - for key in ( - "vmalloc", - "fixmap", - "physmap", - "vmemmap", - ) - ) - if pwndbg.aglib.arch.name == "x86-64": - assert any( - key in res - # this needs to be `any` because kernel is not fully initialized - # when the test is run (qemu-system takes >3 seconds to fully setup for linux) - for key in ( - "kernel [.text]", - "kernel [.rodata]", - "kernel [.bss]", - "kernel [stack]", - ) - ) - - @pytest.mark.skipif(not pwndbg.aglib.kernel.has_debug_syms(), reason="test requires debug symbols") @pytest.mark.skipif( pwndbg.aglib.arch.name not in ["i386", "x86-64"],