From e5a3a50a701c519fe8d00229b4d43945434ef88d Mon Sep 17 00:00:00 2001 From: Filip Grzywok Date: Thu, 28 Sep 2023 23:32:05 +0200 Subject: [PATCH] feat: Back vmmap with info proc mappings on newer qemu versions --- pwndbg/commands/vmmap.py | 10 +++++++- pwndbg/gdblib/qemu.py | 9 +++++++ pwndbg/gdblib/vmmap.py | 51 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/pwndbg/commands/vmmap.py b/pwndbg/commands/vmmap.py index 2fb985933..f583c8fc0 100644 --- a/pwndbg/commands/vmmap.py +++ b/pwndbg/commands/vmmap.py @@ -3,6 +3,7 @@ Command to print the virtual memory map a la /proc/self/maps. """ from __future__ import annotations +import re import argparse import gdb @@ -151,9 +152,16 @@ def vmmap( print(M.get(page.vaddr, text=display_text, prefix=backtrace_prefix)) - if pwndbg.gdblib.qemu.is_qemu(): + if pwndbg.gdblib.qemu.is_qemu() and not pwndbg.gdblib.qemu.exec_file_supported(): print("\n[QEMU target detected - vmmap result might not be accurate; see `help vmmap`]") + gdb_version = tuple(map(int, re.search(r"(\d+)[^\d]+(\d+)", gdb.VERSION).groups())) + + # Only GDB versions >=12 report permission info in info proc mappings. On older versions, we fallback on "rwx". + # * https://github.com/bminor/binutils-gdb/commit/29ef4c0699e1b46d41ade00ae07a54f979ea21cc + if pwndbg.gdblib.qemu.is_qemu_usermode() and gdb_version[0] < 12: + print("\n[GDB <=12.1 detected - vmmap cannot fetch permission information, defaulting to rwx]") + parser = argparse.ArgumentParser(description="Add virtual memory map page.") parser.add_argument("start", help="Starting virtual address") diff --git a/pwndbg/gdblib/qemu.py b/pwndbg/gdblib/qemu.py index fdbf70fa9..f87ae8491 100644 --- a/pwndbg/gdblib/qemu.py +++ b/pwndbg/gdblib/qemu.py @@ -58,6 +58,15 @@ def is_qemu_kernel() -> bool: return is_qemu() and not is_usermode() +@pwndbg.lib.cache.cache_until("stop") +def exec_file_supported() -> bool: + """Returns ``True`` if the qemu target supports exec file feature. + Used in `vmmap` to determine whether qemu supports `info proc mappings` + """ + response = gdb.execute("maintenance packet qSupported", to_string=True, from_tty=False) + + return "exec-file" in response + @start @pwndbg.lib.cache.cache_until("stop") def root() -> Any | None: diff --git a/pwndbg/gdblib/vmmap.py b/pwndbg/gdblib/vmmap.py index b124c1d85..209396a51 100644 --- a/pwndbg/gdblib/vmmap.py +++ b/pwndbg/gdblib/vmmap.py @@ -91,7 +91,10 @@ def get() -> tuple[pwndbg.lib.memory.Page, ...]: if is_corefile(): return tuple(coredump_maps()) - proc_maps = proc_pid_maps() + if pwndbg.gdblib.qemu.is_qemu_usermode(): + proc_maps = info_proc_maps() + else: + proc_maps = proc_pid_maps() # The `proc_maps` is usually a tuple of Page objects but it can also be: # None - when /proc/$pid/maps does not exist/is not available @@ -338,6 +341,52 @@ def coredump_maps(): return tuple(pages) +@pwndbg.lib.cache.cache_until("start", "stop") +def info_proc_maps(): + """ + Parse the result of info proc mappings. + Returns: + A tuple of pwndbg.lib.memory.Page objects or None if + info proc mapping is not supported on the target. + """ + + try: + info_proc_mappings = pwndbg.gdblib.info.proc_mappings().splitlines() + except gdb.error: + # On qemu user emulation, we may get: gdb.error: Not supported on this target. + info_proc_mappings = [] + + pages = [] + for line in info_proc_mappings: + # We look for lines like: + # ['0x555555555000', '0x555555556000', '0x1000', '0x1000', 'rw-p', '/home/user/a.out'] + try: + split_line = line.split() + + # Permission info is only available in GDB versions >=12 + # https://github.com/bminor/binutils-gdb/commit/29ef4c0699e1b46d41ade00ae07a54f979ea21cc + # Assume "rw-p" on older gdb versions + if len(split_line) < 6: + start, _end, size, offset, objfile = split_line + perm = 'rwxp' + else: + start, _end, size, offset, perm, objfile = split_line + start, size, offset = int(start, 16), int(size, 16), int(offset, 16) + except (IndexError, ValueError): + continue + + flags = 0 + if "r" in perm: + flags |= 4 + if "w" in perm: + flags |= 2 + if "x" in perm: + flags |= 1 + + pages.append(pwndbg.lib.memory.Page(start, size, flags, offset, objfile)) + + return tuple(pages) + @pwndbg.lib.cache.cache_until("start", "stop") def proc_pid_maps(): """