From d28c4a3eefcbdedbb4cd6477c9c4807b07b11fce Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 22 Sep 2024 10:42:44 -0700 Subject: [PATCH 01/53] added ruff linter + fixes --- gef.py | 303 +++++++++++++++++++++++++++++------------------------- ruff.toml | 2 + 2 files changed, 164 insertions(+), 141 deletions(-) create mode 100644 ruff.toml diff --git a/gef.py b/gef.py index af6ee583e..621270212 100644 --- a/gef.py +++ b/gef.py @@ -353,7 +353,8 @@ def calling_function() -> Optional[str]: try: stack_info = traceback.extract_stack()[-3] return stack_info.name - except: + except Exception as e: + dbg(f"traceback failed with {str(e)}") return None @@ -743,7 +744,12 @@ def _search_for_realpath(self) -> Optional[str]: @property def realpath(self) -> str: # when in a `gef-remote` session, realpath returns the path to the binary on the local disk, not remote - return self.path if gef.session.remote is None else self._search_for_realpath() + if gef.session.remote is None: + return self.path + default = self._search_for_realpath() + if default: + return default + raise FileNotFoundError def __str__(self) -> str: return (f"Section(start={self.page_start:#x}, end={self.page_end:#x}, " @@ -1594,7 +1600,7 @@ class ChunkFlags(enum.IntFlag): NON_MAIN_ARENA = 4 def __str__(self) -> str: - return f" | ".join([ + return " | ".join([ Color.greenify("PREV_INUSE") if self.value & self.PREV_INUSE else Color.redify("PREV_INUSE"), Color.greenify("IS_MMAPPED") if self.value & self.IS_MMAPPED else Color.redify("IS_MMAPPED"), Color.greenify("NON_MAIN_ARENA") if self.value & self.NON_MAIN_ARENA else Color.redify("NON_MAIN_ARENA") @@ -1881,9 +1887,9 @@ def _show_code_line(fname: str, idx: int) -> str: gef_print("") exc_type, exc_value, exc_traceback = sys.exc_info() - + exc_name = exc_type.__name__ if exc_type else "Unknown" gef_print(" Exception raised ".center(80, HORIZONTAL_LINE)) - gef_print(f"{Color.colorify(exc_type.__name__, 'bold underline red')}: {exc_value}") + gef_print(f"{Color.colorify(exc_name, 'bold underline red')}: {exc_value}") gef_print(" Detailed stacktrace ".center(80, HORIZONTAL_LINE)) for fs in traceback.extract_tb(exc_traceback)[::-1]: @@ -3516,7 +3522,7 @@ def is_qemu() -> bool: if not is_remote_debug(): return False response = gdb.execute("maintenance packet Qqemu.sstepbits", to_string=True, from_tty=False) or "" - return "ENABLE=" in response + return "ENABLE=1" in response @lru_cache() @@ -3674,19 +3680,19 @@ def is_hex(pattern: str) -> bool: return len(pattern) % 2 == 0 and all(c in string.hexdigits for c in pattern[2:]) -def continue_handler(_: "gdb.events.ContinueEvent") -> None: +def continue_handler(_: "gdb.ContinueEvent") -> None: """GDB event handler for new object continue cases.""" return -def hook_stop_handler(_: "gdb.events.StopEvent") -> None: +def hook_stop_handler(_: "gdb.StopEvent") -> None: """GDB event handler for stop cases.""" reset_all_caches() gdb.execute("context") return -def new_objfile_handler(evt: Optional["gdb.events.NewObjFileEvent"]) -> None: +def new_objfile_handler(evt: Optional["gdb.NewObjFileEvent"]) -> None: """GDB event handler for new object file cases.""" reset_all_caches() progspace = gdb.current_progspace() @@ -3721,7 +3727,7 @@ def new_objfile_handler(evt: Optional["gdb.events.NewObjFileEvent"]) -> None: return -def exit_handler(_: "gdb.events.ExitedEvent") -> None: +def exit_handler(_: "gdb.ExitedEvent") -> None: """GDB event handler for exit cases.""" global gef # flush the caches @@ -3752,13 +3758,13 @@ def exit_handler(_: "gdb.events.ExitedEvent") -> None: return -def memchanged_handler(_: "gdb.events.MemoryChangedEvent") -> None: +def memchanged_handler(_: "gdb.MemoryChangedEvent") -> None: """GDB event handler for mem changes cases.""" reset_all_caches() return -def regchanged_handler(_: "gdb.events.RegisterChangedEvent") -> None: +def regchanged_handler(_: "gdb.RegisterChangedEvent") -> None: """GDB event handler for reg changes cases.""" reset_all_caches() return @@ -3888,7 +3894,7 @@ def get_memory_alignment(in_bits: bool = False) -> int: try: return gdb.parse_and_eval("$pc").type.sizeof - except: + except Exception: pass raise OSError("GEF is running under an unsupported mode") @@ -4123,72 +4129,72 @@ def set_arch(arch: Optional[str] = None, _: Optional[str] = None) -> None: # @only_if_events_supported("cont") -def gef_on_continue_hook(func: Callable[["gdb.events.ContinueEvent"], None]) -> None: +def gef_on_continue_hook(func: Callable[["gdb.ContinueEvent"], None]) -> None: gdb.events.cont.connect(func) @only_if_events_supported("cont") -def gef_on_continue_unhook(func: Callable[["gdb.events.ThreadEvent"], None]) -> None: +def gef_on_continue_unhook(func: Callable[["gdb.ThreadEvent"], None]) -> None: gdb.events.cont.disconnect(func) @only_if_events_supported("stop") -def gef_on_stop_hook(func: Callable[["gdb.events.StopEvent"], None]) -> None: +def gef_on_stop_hook(func: Callable[["gdb.StopEvent"], None]) -> None: gdb.events.stop.connect(func) @only_if_events_supported("stop") -def gef_on_stop_unhook(func: Callable[["gdb.events.StopEvent"], None]) -> None: +def gef_on_stop_unhook(func: Callable[["gdb.StopEvent"], None]) -> None: gdb.events.stop.disconnect(func) @only_if_events_supported("exited") -def gef_on_exit_hook(func: Callable[["gdb.events.ExitedEvent"], None]) -> None: +def gef_on_exit_hook(func: Callable[["gdb.ExitedEvent"], None]) -> None: gdb.events.exited.connect(func) @only_if_events_supported("exited") -def gef_on_exit_unhook(func: Callable[["gdb.events.ExitedEvent"], None]) -> None: +def gef_on_exit_unhook(func: Callable[["gdb.ExitedEvent"], None]) -> None: gdb.events.exited.disconnect(func) @only_if_events_supported("new_objfile") -def gef_on_new_hook(func: Callable[["gdb.events.NewObjFileEvent"], None]) -> None: +def gef_on_new_hook(func: Callable[["gdb.NewObjFileEvent"], None]) -> None: gdb.events.new_objfile.connect(func) @only_if_events_supported("new_objfile") -def gef_on_new_unhook(func: Callable[["gdb.events.NewObjFileEvent"], None]) -> None: +def gef_on_new_unhook(func: Callable[["gdb.NewObjFileEvent"], None]) -> None: gdb.events.new_objfile.disconnect(func) @only_if_events_supported("clear_objfiles") -def gef_on_unload_objfile_hook(func: Callable[["gdb.events.ClearObjFilesEvent"], None]) -> None: +def gef_on_unload_objfile_hook(func: Callable[["gdb.ClearObjFilesEvent"], None]) -> None: gdb.events.clear_objfiles.connect(func) @only_if_events_supported("clear_objfiles") -def gef_on_unload_objfile_unhook(func: Callable[["gdb.events.ClearObjFilesEvent"], None]) -> None: +def gef_on_unload_objfile_unhook(func: Callable[["gdb.ClearObjFilesEvent"], None]) -> None: gdb.events.clear_objfiles.disconnect(func) @only_if_events_supported("memory_changed") -def gef_on_memchanged_hook(func: Callable[["gdb.events.MemoryChangedEvent"], None]) -> None: +def gef_on_memchanged_hook(func: Callable[["gdb.MemoryChangedEvent"], None]) -> None: gdb.events.memory_changed.connect(func) @only_if_events_supported("memory_changed") -def gef_on_memchanged_unhook(func: Callable[["gdb.events.MemoryChangedEvent"], None]) -> None: +def gef_on_memchanged_unhook(func: Callable[["gdb.MemoryChangedEvent"], None]) -> None: gdb.events.memory_changed.disconnect(func) @only_if_events_supported("register_changed") -def gef_on_regchanged_hook(func: Callable[["gdb.events.RegisterChangedEvent"], None]) -> None: +def gef_on_regchanged_hook(func: Callable[["gdb.RegisterChangedEvent"], None]) -> None: gdb.events.register_changed.connect(func) @only_if_events_supported("register_changed") -def gef_on_regchanged_unhook(func: Callable[["gdb.events.RegisterChangedEvent"], None]) -> None: +def gef_on_regchanged_unhook(func: Callable[["gdb.RegisterChangedEvent"], None]) -> None: gdb.events.register_changed.disconnect(func) @@ -4757,7 +4763,7 @@ def settings(self) -> List[str]: """Return the list of settings for this command.""" return list(iter(self)) - @deprecated(f"Use `self[setting_name]` instead") + @deprecated("Use `self[setting_name]` instead") def get_setting(self, name: str) -> Any: return self.__getitem__(name) @@ -4765,14 +4771,14 @@ def __getitem__(self, name: str) -> Any: key = self.__get_setting_name(name) return gef.config[key] - @deprecated(f"Use `setting_name in self` instead") + @deprecated("Use `setting_name in self` instead") def has_setting(self, name: str) -> bool: return self.__contains__(name) def __contains__(self, name: str) -> bool: return self.__get_setting_name(name) in gef.config - @deprecated(f"Use `self[setting_name] = value` instead") + @deprecated("Use `self[setting_name] = value` instead") def add_setting(self, name: str, value: Tuple[Any, type, str], description: str = "") -> None: return self.__setitem__(name, (value, description)) @@ -4797,7 +4803,7 @@ def __setitem__(self, name: str, value: Union["GefSetting", Tuple[Any, str]]) -> gef.config[key] = GefSetting(value[0], description=value[1]) return - @deprecated(f"Use `del self[setting_name]` instead") + @deprecated("Use `del self[setting_name]` instead") def del_setting(self, name: str) -> None: return self.__delitem__(name) @@ -5239,7 +5245,7 @@ def comp2_b(x: Any) -> str: return f"{(x + (1 << off)) % (1 << off):b}" gef_print("0b" + comp2_b(res)) gef_print(f"{binascii.unhexlify(s_i)}") gef_print(f"{binascii.unhexlify(s_i)[::-1]}") - except: + except Exception: pass return @@ -5463,6 +5469,7 @@ class GefThemeCommand(GenericCommand): _cmdline_ = "theme" _syntax_ = f"{_cmdline_} [KEY [VALUE]]" + _examples_ = (f"{_cmdline_} address_stack green") def __init__(self) -> None: super().__init__(self._cmdline_) @@ -5497,7 +5504,7 @@ def do_invoke(self, args: List[str]) -> None: return setting_name = args[0] - if not setting_name in self: + if setting_name not in self: err("Invalid key") return @@ -5506,8 +5513,8 @@ def do_invoke(self, args: List[str]) -> None: gef_print(f"{setting_name:40s}: {Color.colorify(value, value)}") return - colors = [color for color in args[1:] if color in Color.colors] - self[setting_name] = " ".join(colors) + colors = (color for color in args[1:] if color in Color.colors) + self[setting_name][0] = " ".join(colors) return @@ -5574,6 +5581,7 @@ def apply_at(self, address: int, max_depth: int, depth: int = 0) -> None: ptrsize = gef.arch.ptrsize unpack = u32 if ptrsize == 4 else u64 for field in _structure._fields_: + assert len(field) == 2 _name, _type = field _value = getattr(_structure, _name) _offset = getattr(self.class_type, _name).offset @@ -6127,7 +6135,7 @@ def search_pattern(self, pattern: str, section_name: str) -> None: for section in gef.memory.maps: if not section.permission & Permission.READ: continue if section.path == "[vvar]": continue - if not section_name in section.path: continue + if section_name not in section.path: continue start = section.page_start end = section.page_end - 1 @@ -6264,7 +6272,7 @@ def __init__(self) -> None: super().__init__(prefix=False) return - @parse_arguments({"host": "", "port": 0}, {"--pid": -1, "--qemu-user": False, "--qemu-binary": ""}) + @parse_arguments({"host": "", "port": 0}, {"--pid": 0, "--qemu-user": ""}) def do_invoke(self, _: List[str], **kwargs: Any) -> None: if gef.session.remote is not None: err("You already are in remote session. Close it first before opening a new one...") @@ -6277,15 +6285,15 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: return # qemu-user support - qemu_binary: Optional[pathlib.Path] = None if args.qemu_user: + dbg("Setting up qemu-user session") try: - qemu_binary = pathlib.Path(args.qemu_binary).expanduser().absolute() if args.qemu_binary else gef.session.file + qemu_binary = pathlib.Path(args.qemu_user).expanduser().absolute() if args.qemu_user else gef.session.file if not qemu_binary or not qemu_binary.exists(): - raise FileNotFoundError(f"{qemu_binary} does not exist") + raise FileNotFoundError(f"qemu-user session was specified, but binary '{qemu_binary}' does not exist") except Exception as e: err(f"Failed to initialize qemu-user mode, reason: {str(e)}") - return + raise e # Try to establish the remote session, throw on error # Set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which @@ -6411,8 +6419,8 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: try: last_insn = gef_instruction_n(address, num_items-1) last_addr = last_insn.address - except: - err(f"Cannot patch instruction at {address:#x} reaching unmapped area") + except Exception as e: + err(f"Cannot patch instruction at {address:#x} reaching unmapped area, reason: {e}") return total_bytes = (last_addr - address) + gef_get_instruction_at(last_addr).size() @@ -7739,10 +7747,10 @@ def context_regs(self) -> None: gdb.execute(f"registers {printable_registers}") return - widest = l = max(map(len, gef.arch.all_registers)) - l += 5 - l += gef.arch.ptrsize * 2 - nb = get_terminal_size()[1] // l + widest = curlen = max(map(len, gef.arch.all_registers)) + curlen += 5 + curlen += gef.arch.ptrsize * 2 + nb = get_terminal_size()[1] // curlen i = 1 line = "" changed_color = gef.config["theme.registers_value_changed"] @@ -7831,7 +7839,7 @@ def context_code(self) -> None: breakpoints = gdb.breakpoints() or [] # breakpoint.locations was introduced in gdb 13.1 if len(breakpoints) and hasattr(breakpoints[-1], "locations"): - bp_locations = [hex(location.address) for b in breakpoints for location in b.locations if location is not None] + bp_locations = [hex(location.address) for b in breakpoints for location in b.locations if location is not None] # type: ignore else: # location relies on the user setting the breakpoints with "b *{hex(address)}" bp_locations = [b.location for b in breakpoints if b.location and b.location.startswith("*")] @@ -7939,6 +7947,8 @@ def print_arguments_from_symbol(self, function_name: str, symbol: "gdb.Symbol") args = [] fields = symbol.type.fields() if symbol.type else [] for i, f in enumerate(fields): + if not f.type: + continue _value = gef.arch.get_ith_parameter(i, in_func=False)[1] _value = RIGHT_ARROW.join(dereference_from(_value)) _name = f.name or f"var_{i}" @@ -8030,9 +8040,8 @@ def context_source(self) -> None: if not symtab.is_valid(): return - fpath = symtab.fullname() - with open(fpath, "r") as f: - lines = [l.rstrip() for l in f.readlines()] + fpath = pathlib.Path(symtab.fullname()) + lines = [curline.rstrip() for curline in fpath.read_text().splitlines()] except Exception: return @@ -8455,23 +8464,23 @@ def _hexdump(self, start_addr: int, length: int, arrange_as: str, offset: int = "word": ("H", 2), } - r, l = formats[arrange_as] - fmt_str = f"{{base}}{VERTICAL_LINE}+{{offset:#06x}} {{sym}}{{val:#0{l*2+2}x}} {{text}}" - fmt_pack = f"{endianness!s}{r}" + formatter, width = formats[arrange_as] + fmt_str = f"{{base}}{VERTICAL_LINE}+{{offset:#06x}} {{sym}}{{val:#0{width*2+2}x}} {{text}}" + fmt_pack = f"{endianness!s}{formatter}" lines = [] i = 0 text = "" while i < length: - cur_addr = start_addr + (i + offset) * l + cur_addr = start_addr + (i + offset) * width sym = gdb_get_location_from_symbol(cur_addr) sym = f"<{sym[0]:s}+{sym[1]:04x}> " if sym else "" - mem = gef.memory.read(cur_addr, l) + mem = gef.memory.read(cur_addr, width) val = struct.unpack(fmt_pack, mem)[0] if show_ascii: text = "".join([chr(b) if 0x20 <= b < 0x7F else "." for b in mem]) lines.append(fmt_str.format(base=Color.colorify(format_address(cur_addr), base_address_color), - offset=(i + offset) * l, sym=sym, val=val, text=text)) + offset=(i + offset) * width, sym=sym, val=val, text=text)) i += 1 return lines @@ -8573,9 +8582,9 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: var_name = values[0] try: values = str(gdb.parse_and_eval(var_name)).lstrip("{").rstrip("}").replace(",","").split(" ") - except: + except Exception as e: gef_print(f"Bad variable specified, check value with command: p {var_name}") - return + raise e d = str(gef.arch.endianness) for value in values: @@ -8761,7 +8770,7 @@ def pprint_dereferenced(addr: int, idx: int, base_offset: int = 0) -> str: addrs = dereference_from(current_address) addr_l = format_address(int(addrs[0], 16)) ma = (memalign*2 + 2) - l = ( + line = ( f"{Color.colorify(addr_l, base_address_color)}{VERTICAL_LINE}" f"{base_offset+offset:+#07x}: {sep.join(addrs[1:]):{ma}s}" ) @@ -8775,10 +8784,10 @@ def pprint_dereferenced(addr: int, idx: int, base_offset: int = 0) -> str: if register_hints: m = f"\t{LEFT_ARROW}{', '.join(list(register_hints))}" - l += Color.colorify(m, registers_color) + line += Color.colorify(m, registers_color) offset += memalign - return l + return line @only_if_gdb_running @parse_arguments({"address": "$sp"}, {("-r", "--reference"): "", ("-l", "--length"): 10}) @@ -8932,18 +8941,18 @@ def print_entry(self, entry: Section) -> None: elif entry.permission & Permission.READ and entry.permission & Permission.EXECUTE: line_color = gef.config["theme.address_code"] - l = [ + line_parts = [ Color.colorify(format_address(entry.page_start), line_color), Color.colorify(format_address(entry.page_end), line_color), Color.colorify(format_address(entry.offset), line_color), ] if entry.permission == Permission.ALL: - l.append(Color.colorify(str(entry.permission), "underline " + line_color)) + line_parts.append(Color.colorify(str(entry.permission), "underline " + line_color)) else: - l.append(Color.colorify(str(entry.permission), line_color)) + line_parts.append(Color.colorify(str(entry.permission), line_color)) - l.append(Color.colorify(entry.path, line_color)) - line = " ".join(l) + line_parts.append(Color.colorify(entry.path, line_color)) + line = " ".join(line_parts) gef_print(line) return @@ -8990,13 +8999,13 @@ def do_invoke(self, argv: List[str]) -> None: if filter_by_name and filter_by_name not in xfile.name: continue - l = [ + line_parts = [ format_address(xfile.zone_start), format_address(xfile.zone_end), f"{xfile.name:<21s}", xfile.filename, ] - gef_print(" ".join(l)) + gef_print(" ".join(line_parts)) return @@ -9680,7 +9689,7 @@ def dump_tracked_allocations(self) -> None: ok("No free() chunk tracked") return - def clean(self, _: "gdb.events.ExitedEvent") -> None: + def clean(self, _: "gdb.ExitedEvent") -> None: global gef ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - Cleaning up") @@ -9911,6 +9920,7 @@ def __init__(self) -> None: gef.config["gef.libc_version"] = GefSetting("", str, "Specify libc version when auto-detection fails") gef.config["gef.main_arena_offset"] = GefSetting("", str, "Offset from libc base address to main_arena symbol (int or hex). Set to empty string to disable.") gef.config["gef.propagate_debug_exception"] = GefSetting(False, bool, "If true, when debug mode is enabled, Python exceptions will be propagated all the way.") + gef.config["gef.extra_python_package_paths"] = GefSetting("", str, "Semi-colon separate list of extra paths to include for python packages") self.commands : Dict[str, GenericCommand] = collections.OrderedDict() self.functions : Dict[str, GenericFunction] = collections.OrderedDict() @@ -10232,7 +10242,7 @@ def set_setting(self, argv: List[str]) -> bool: _type = gef.config.raw_entry(key).type # Attempt to parse specific values for known types - if _type == bool: + if _type is bool: if new_value.upper() in ("TRUE", "T", "1"): _newval = True elif new_value.upper() in ("FALSE", "F", "0"): @@ -10340,7 +10350,7 @@ def reload(self, quiet: bool): except Exception: continue new_value = cfg.get(section, optname) - if setting.type == bool: + if setting.type is bool: new_value = True if new_value.upper() in ("TRUE", "T", "1") else False setting.value = setting.type(new_value) @@ -10694,7 +10704,7 @@ def reset_caches(self) -> None: if not hasattr(obj, "cache_clear"): continue obj.cache_clear() - except: # we're reseting the cache here, we don't care if (or which) exception triggers + except Exception: # we're reseting the cache here, we don't care if (or which) exception triggers continue return @@ -10764,7 +10774,10 @@ def read_ascii_string(self, address: int) -> Optional[str]: @property def maps(self) -> List[Section]: if not self.__maps: - self.__maps = self.__parse_maps() + maps = self.__parse_maps() + if not maps: + raise RuntimeError("Failed to determine memory layout") + self.__maps = maps return self.__maps def __parse_maps(self) -> Optional[List[Section]]: @@ -10777,17 +10790,17 @@ def __parse_maps(self) -> Optional[List[Section]]: try: return list(self.parse_gdb_info_proc_maps()) - except: + except Exception: pass try: return list(self.parse_procfs_maps()) - except: + except Exception: pass try: return list(self.parse_monitor_info_mem()) - except: + except Exception: pass raise RuntimeError("Failed to get memory layout") @@ -10964,7 +10977,7 @@ def main_arena(self) -> Optional[GlibcArena]: # the initialization of `main_arena` also defined `selected_arena`, so # by default, `main_arena` == `selected_arena` self.selected_arena = self.__libc_main_arena - except: + except Exception: # the search for arena can fail when the session is not started pass return self.__libc_main_arena @@ -11030,7 +11043,8 @@ def find_main_arena_addr() -> int: try: dbg("Trying to bruteforce main_arena address") # setup search_range for `main_arena` to `.data` of glibc - search_filter = lambda f: "libc" in pathlib.Path(f.filename).name and f.name == ".data" + def search_filter(zone: Zone) -> bool: + return "libc" in pathlib.Path(zone.filename).name and zone.name == ".data" for dotdata in list(filter(search_filter, get_info_files())): search_range = range(dotdata.zone_start, dotdata.zone_end, alignment) @@ -11113,9 +11127,10 @@ def tidx2size(self, idx: int) -> int: def malloc_align_address(self, address: int) -> int: """Align addresses according to glibc's MALLOC_ALIGNMENT. See also Issue #689 on Github""" + def ceil(n: int) -> int: + return int(-1 * n // 1 * -1) malloc_alignment = self.malloc_alignment - ceil = lambda n: int(-1 * n // 1 * -1) - return malloc_alignment * ceil((address / malloc_alignment)) + return malloc_alignment * ceil((address // malloc_alignment)) class GefSetting: @@ -11387,12 +11402,13 @@ def __repr__(self): return f"RemoteMode = {str(self)} ({int(self)})" def prompt_string(self) -> str: - if self == GefRemoteSessionManager.RemoteMode.QEMU: - return Color.boldify("(qemu) ") - if self == GefRemoteSessionManager.RemoteMode.RR: - return Color.boldify("(rr) ") - if self == GefRemoteSessionManager.RemoteMode.GDBSERVER: - return Color.boldify("(remote) ") + match self: + case GefRemoteSessionManager.RemoteMode.QEMU: + return Color.boldify("(qemu) ") + case GefRemoteSessionManager.RemoteMode.RR: + return Color.boldify("(rr) ") + case GefRemoteSessionManager.RemoteMode.GDBSERVER: + return Color.boldify("(remote) ") raise AttributeError("Unknown value") def __init__(self, host: str, port: int, pid: int =-1, qemu: Optional[pathlib.Path] = None) -> None: @@ -11402,6 +11418,8 @@ def __init__(self, host: str, port: int, pid: int =-1, qemu: Optional[pathlib.Pa self.__local_root_fd = tempfile.TemporaryDirectory() self.__local_root_path = pathlib.Path(self.__local_root_fd.name) self.__qemu = qemu + if pid > 0: + self._pid = pid if self.__qemu is not None: self._mode = GefRemoteSessionManager.RemoteMode.QEMU @@ -11417,7 +11435,7 @@ def close(self) -> None: gef_on_new_hook(new_objfile_handler) except Exception as e: warn(f"Exception while restoring local context: {str(e)}") - return + raise e def __str__(self) -> str: return f"RemoteSession(target='{self.target}', local='{self.root}', pid={self.pid}, mode={self.mode})" @@ -11478,7 +11496,7 @@ def sync(self, src: str, dst: Optional[str] = None) -> bool: def connect(self, pid: int) -> bool: """Connect to remote target. If in extended mode, also attach to the given PID.""" # before anything, register our new hook to download files from the remote target - dbg(f"[remote] Installing new objfile handlers") + dbg("[remote] Installing new objfile handlers") try: gef_on_new_unhook(new_objfile_handler) except SystemError: @@ -11506,17 +11524,19 @@ def connect(self, pid: int) -> bool: def setup(self) -> bool: # setup remote adequately depending on remote or qemu mode - if self.mode == GefRemoteSessionManager.RemoteMode.QEMU: - dbg(f"Setting up as qemu session, target={self.__qemu}") - self.__setup_qemu() - elif self.mode == GefRemoteSessionManager.RemoteMode.RR: - dbg(f"Setting up as rr session") - self.__setup_rr() - elif self.mode == GefRemoteSessionManager.RemoteMode.GDBSERVER: - dbg(f"Setting up as remote session") - self.__setup_remote() - else: - raise Exception + match self.mode: + case GefRemoteSessionManager.RemoteMode.QEMU: + dbg(f"Setting up as qemu session, target={self.__qemu}") + self.__setup_qemu() + case GefRemoteSessionManager.RemoteMode.RR: + dbg("Setting up as rr session") + self.__setup_rr() + case GefRemoteSessionManager.RemoteMode.GDBSERVER: + dbg("Setting up as remote session") + self.__setup_remote() + case _: + raise ValueError + # refresh gef to consider the binary reset_all_caches() gef.binary = Elf(self.lfile) @@ -11581,7 +11601,7 @@ def __setup_rr(self) -> bool: self.__local_root_path = pathlib.Path("/") return True - def remote_objfile_event_handler(self, evt: "gdb.events.NewObjFileEvent") -> None: + def remote_objfile_event_handler(self, evt: "gdb.NewObjFileEvent") -> None: dbg(f"[remote] in remote_objfile_handler({evt.new_objfile.filename if evt else 'None'}))") if not evt or not evt.new_objfile.filename: return @@ -11710,10 +11730,30 @@ def reset_caches(self) -> None: def target_remote_posthook(): - if gef.session.remote_initializing: - return + conn = gdb.selected_inferior().connection + # if here, the connection should be established + if not isinstance(conn, gdb.RemoteTargetConnection): + raise EnvironmentError("Failed to create a remote session") + + # if gef.session.remote_initializing: + # dbg(f"remote session already initializing: {gef.session}") + # return + + print(f"{conn.description=}") + print(f"{conn.details=}") + print(f"{conn.type=}") + + assert conn.details + host, port = conn.details.split(":") + pid = ( + gdb.selected_inferior().pid + if False + else gdb.selected_thread().ptid[1] + ) + print(host, port, pid) + gef.session.remote = GefRemoteSessionManager(host, int(port), pid) + gef.session.remote._mode = GefRemoteSessionManager.RemoteMode.GDBSERVER - gef.session.remote = GefRemoteSessionManager("", 0) if not gef.session.remote.setup(): raise EnvironmentError(f"Failed to create a proper environment for {gef.session.remote}") @@ -11729,17 +11769,6 @@ def target_remote_posthook(): f"(with Python {'.'.join(map(str, PYTHON_MIN_VERSION))} or higher).") exit(1) - # When using a Python virtual environment, GDB still loads the system-installed Python - # so GEF doesn't load site-packages dir from environment - # In order to fix it, from the shell with venv activated we run the python binary, - # take and parse its path, add the path to the current python process using sys.path.extend - PYTHONBIN = which("python3") - PREFIX = gef_pystring(subprocess.check_output([PYTHONBIN, '-c', 'import os, sys;print((sys.prefix))'])).strip("\\n") - if PREFIX != sys.base_prefix: - SITE_PACKAGES_DIRS = subprocess.check_output( - [PYTHONBIN, "-c", "import os, sys;print(os.linesep.join(sys.path).strip())"]).decode("utf-8").split() - sys.path.extend(SITE_PACKAGES_DIRS) - # setup config gdb_initial_settings = ( "set confirm off", @@ -11771,6 +11800,14 @@ def target_remote_posthook(): # reload settings gdb.execute("gef restore") + # extend `sys.path` for venv compat + if gef.config["gef.extra_python_package_paths"]: + for path in map(pathlib.Path, gef.config["gef.extra_python_package_paths"].split(";")): + if not path.exists(): + warn(f"Skipping invalid directory {path}") + continue + sys.path.extend(str(path.absolute())) + # setup gdb prompt gdb.prompt_hook = __gef_prompt__ @@ -11789,32 +11826,16 @@ def target_remote_posthook(): GefTmuxSetup() - if GDB_VERSION > (9, 0): - disable_tr_overwrite_setting = "gef.disable_target_remote_overwrite" - - if not gef.config[disable_tr_overwrite_setting]: - warnmsg = ("Using `target remote` with GEF should work in most cases, " - "but use `gef-remote` if you can. You can disable the " - "overwrite of the `target remote` command by toggling " - f"`{disable_tr_overwrite_setting}` in the config.") - hook = f""" - define target hookpost-{{}} - pi target_remote_posthook() - context - pi if calling_function() != "connect": warn("{warnmsg}") - end - """ - - # Register a post-hook for `target remote` that initialize the remote session - gdb.execute(hook.format("remote")) - gdb.execute(hook.format("extended-remote")) - else: - errmsg = ("Using `target remote` does not work, use `gef-remote` " - f"instead. You can toggle `{disable_tr_overwrite_setting}` " - "if this is not desired.") - hook = f"""pi if calling_function() != "connect": err("{errmsg}")""" - gdb.execute(f"define target hook-remote\n{hook}\nend") - gdb.execute(f"define target hook-extended-remote\n{hook}\nend") + # Register a post-hook for `target remote` that initialize the remote session + hook = """ + define target hookpost-{} + pi target_remote_posthook() + context + pi res = calling_function(); if res != "connect": err("Failed to initialize remote session: " + str(res)) + end + """ + gdb.execute(hook.format("remote")) + gdb.execute(hook.format("extended-remote")) # restore saved breakpoints (if any) bkp_fpath = pathlib.Path(gef.config["gef.autosave_breakpoints_file"]).expanduser().absolute() diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 000000000..6a3fc3001 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,2 @@ +[lint.per-file-ignores] +"gef.py" = ["E701"] \ No newline at end of file From cde95b309f124dc69cbc67646a5e3f9ea07746e1 Mon Sep 17 00:00:00 2001 From: hugsy Date: Tue, 24 Sep 2024 13:36:24 -0700 Subject: [PATCH 02/53] checkpoint --- gef.py | 338 +++++++++++++++++------------------ tests/base.py | 8 +- tests/commands/gef_remote.py | 2 +- tests/commands/theme.py | 2 +- 4 files changed, 173 insertions(+), 177 deletions(-) diff --git a/gef.py b/gef.py index 621270212..17d602c23 100644 --- a/gef.py +++ b/gef.py @@ -82,8 +82,8 @@ from functools import lru_cache from io import StringIO, TextIOWrapper from types import ModuleType -from typing import (Any, ByteString, Callable, Dict, Generator, Iterable, - Iterator, List, NoReturn, Optional, Sequence, Set, Tuple, Type, TypeVar, +from typing import (Any, ByteString, Callable, Generator, Iterable, + Iterator, NoReturn, Optional, Sequence, Set, Tuple, Type, TypeVar, Union, cast) from urllib.request import urlopen @@ -101,7 +101,7 @@ def http_get(url: str) -> Optional[bytes]: return None -def update_gef(argv: List[str]) -> int: +def update_gef(argv: list[str]) -> int: """Obsolete. Use `gef.sh`.""" return -1 @@ -146,7 +146,7 @@ def update_gef(argv: List[str]) -> int: __registered_commands__ : Set[Type["GenericCommand"]] = set() __registered_functions__ : Set[Type["GenericFunction"]] = set() -__registered_architectures__ : Dict[Union["Elf.Abi", str], Type["Architecture"]] = {} +__registered_architectures__ : dict[Union["Elf.Abi", str], Type["Architecture"]] = {} __registered_file_formats__ : Set[ Type["FileFormat"] ] = set() GefMemoryMapProvider = Callable[[], Generator["Section", None, None]] @@ -363,7 +363,6 @@ def calling_function() -> Optional[str]: # def only_if_gdb_running(f: Callable) -> Callable: """Decorator wrapper to check if GDB is running.""" - @functools.wraps(f) def wrapper(*args: Any, **kwargs: Any) -> Any: if is_alive(): @@ -447,8 +446,8 @@ def FakeExit(*args: Any, **kwargs: Any) -> NoReturn: sys.exit = FakeExit -def parse_arguments(required_arguments: Dict[Union[str, Tuple[str, str]], Any], - optional_arguments: Dict[Union[str, Tuple[str, str]], Any]) -> Callable: +def parse_arguments(required_arguments: dict[Union[str, Tuple[str, str]], Any], + optional_arguments: dict[Union[str, Tuple[str, str]], Any]) -> Callable: """Argument parsing decorator.""" def int_wrapper(x: str) -> int: return int(x, 0) @@ -797,8 +796,8 @@ class FileFormat: name: str path: pathlib.Path entry_point: int - checksec: Dict[str, bool] - sections: List[FileFormatSection] + checksec: dict[str, bool] + sections: list[FileFormatSection] def __init__(self, path: Union[str, pathlib.Path]) -> None: raise NotImplementedError @@ -887,11 +886,11 @@ class OsAbi(enum.Enum): e_shstrndx: int path: pathlib.Path - phdrs : List["Phdr"] - shdrs : List["Shdr"] + phdrs : list["Phdr"] + shdrs : list["Shdr"] name: str = "ELF" - __checksec : Dict[str, bool] + __checksec : dict[str, bool] def __init__(self, path: Union[str, pathlib.Path]) -> None: """Instantiate an ELF object. A valid ELF must be provided, or an exception will be thrown.""" @@ -977,7 +976,7 @@ def is_valid(cls, path: pathlib.Path) -> bool: return u32(path.open("rb").read(4), e = Endianness.BIG_ENDIAN) == Elf.ELF_MAGIC @property - def checksec(self) -> Dict[str, bool]: + def checksec(self) -> dict[str, bool]: """Check the security property of the ELF binary. The following properties are: - Canary - NX @@ -1244,7 +1243,7 @@ def __str__(self) -> str: class Instruction: """GEF representation of a CPU instruction.""" - def __init__(self, address: int, location: str, mnemo: str, operands: List[str], opcodes: bytes) -> None: + def __init__(self, address: int, location: str, mnemo: str, operands: list[str], opcodes: bytes) -> None: self.address, self.location, self.mnemonic, self.operands, self.opcodes = \ address, location, mnemo, operands, opcodes return @@ -1549,7 +1548,7 @@ def heap_addr(self, allow_unaligned: bool = False) -> Optional[int]: return _addr return gef.heap.malloc_align_address(_addr) - def get_heap_info_list(self) -> Optional[List[GlibcHeapInfo]]: + def get_heap_info_list(self) -> Optional[list[GlibcHeapInfo]]: if self.is_main_arena(): return None heap_addr = self.get_heap_for_ptr(self.top) @@ -2237,7 +2236,7 @@ def gef_disassemble(addr: int, nb_insn: int, nb_prev: int = 0) -> Generator[Inst yield insn -def gef_execute_external(command: Sequence[str], as_list: bool = False, **kwargs: Any) -> Union[str, List[str]]: +def gef_execute_external(command: Sequence[str], as_list: bool = False, **kwargs: Any) -> Union[str, list[str]]: """Execute an external command and return the result.""" res = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=kwargs.get("shell", False)) return [gef_pystring(_) for _ in res.splitlines()] if as_list else gef_pystring(res) @@ -2259,7 +2258,7 @@ def gef_execute_gdb_script(commands: str) -> None: @deprecated("Use Elf(fname).checksec()") -def checksec(filename: str) -> Dict[str, bool]: +def checksec(filename: str) -> dict[str, bool]: return Elf(filename).checksec @@ -2311,7 +2310,7 @@ def is_little_endian() -> bool: return gef.arch.endianness == Endianness.LITTLE_ENDIAN -def flags_to_human(reg_value: int, value_table: Dict[int, str]) -> str: +def flags_to_human(reg_value: int, value_table: dict[int, str]) -> str: """Return a human readable string showing the flag states.""" flags = [] for bit_index, name in value_table.items(): @@ -2366,7 +2365,7 @@ class Architecture(ArchitectureBase): return_register: str flag_register: Optional[str] instruction_length: Optional[int] - flags_table: Dict[int, str] + flags_table: dict[int, str] syscall_register: Optional[str] syscall_instructions: Union[Tuple[()], Tuple[str, ...]] function_parameters: Union[Tuple[()], Tuple[str, ...]] @@ -3559,7 +3558,7 @@ def get_function_length(sym: str) -> int: @lru_cache() -def get_info_files() -> List[Zone]: +def get_info_files() -> list[Zone]: """Retrieve all the files loaded by debuggee.""" lines = (gdb.execute("info files", to_string=True) or "").splitlines() infos = [] @@ -3611,7 +3610,7 @@ def process_lookup_path(name: str, perm: Permission = Permission.ALL) -> Optiona err("Process is not running") return None - matches: Dict[str, Section] = dict() + matches: dict[str, Section] = dict() for sect in gef.memory.maps: filename = pathlib.Path(sect.path).name @@ -4116,7 +4115,7 @@ def get_register(regname) -> Optional[int]: @deprecated("Use `gef.memory.maps`") -def get_process_maps() -> List[Section]: +def get_process_maps() -> list[Section]: return gef.memory.maps @@ -4691,8 +4690,8 @@ class GenericCommand(gdb.Command): _cmdline_: str _syntax_: str - _example_: Union[str, List[str]] = "" - _aliases_: List[str] = [] + _example_: Union[str, list[str]] = "" + _aliases_: list[str] = [] def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) @@ -4740,7 +4739,7 @@ def usage(self) -> None: err(f"Syntax\n{self._syntax_}") return - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: raise NotImplementedError def pre_load(self) -> None: @@ -4759,7 +4758,7 @@ def __iter__(self) -> Generator[str, None, None]: yield key.replace(f"{self._cmdline_}.", "", 1) @property - def settings(self) -> List[str]: + def settings(self) -> list[str]: """Return the list of settings for this command.""" return list(iter(self)) @@ -4811,7 +4810,7 @@ def __delitem__(self, name: str) -> None: del gef.config[self.__get_setting_name(name)] return - def __set_repeat_count(self, argv: List[str], from_tty: bool) -> None: + def __set_repeat_count(self, argv: list[str], from_tty: bool) -> None: if not from_tty: self.repeat = False self.repeat_count = 0 @@ -4836,7 +4835,7 @@ def __init__(self) -> None: super().__init__(prefix=True) return - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not argv: self.usage() return @@ -4849,7 +4848,7 @@ class ArchGetCommand(GenericCommand): _syntax_ = f"{_cmdline_}" _example_ = f"{_cmdline_}" - def do_invoke(self, args: List[str]) -> None: + def do_invoke(self, args: list[str]) -> None: gef_print(f"{Color.greenify('Arch')}: {gef.arch}") gef_print(f"{Color.greenify('Reason')}: {gef.arch_reason}") @@ -4862,10 +4861,10 @@ class ArchSetCommand(GenericCommand): _syntax_ = f"{_cmdline_} " _example_ = f"{_cmdline_} X86" - def do_invoke(self, args: List[str]) -> None: + def do_invoke(self, args: list[str]) -> None: reset_architecture(args[0].lower() if args else None) - def complete(self, text: str, word: str) -> List[str]: + def complete(self, text: str, word: str) -> list[str]: return sorted(x for x in __registered_architectures__.keys() if isinstance(x, str) and x.lower().startswith(text.lower().strip())) @@ -4877,7 +4876,7 @@ class ArchListCommand(GenericCommand): _syntax_ = f"{_cmdline_}" _example_ = f"{_cmdline_}" - def do_invoke(self, args: List[str]) -> None: + def do_invoke(self, args: list[str]) -> None: gef_print(Color.greenify("Available architectures:")) for arch in sorted(set(__registered_architectures__.values()), key=lambda x: x.arch): if arch is GenericArchitecture: @@ -4897,7 +4896,7 @@ class VersionCommand(GenericCommand): _syntax_ = f"{_cmdline_}" _example_ = f"{_cmdline_}" - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: gef_fpath = pathlib.Path(inspect.stack()[0][1]).expanduser().absolute() gef_dir = gef_fpath.parent with gef_fpath.open("rb") as f: @@ -4944,7 +4943,7 @@ def __init__(self) -> None: return @property - def format_matrix(self) -> Dict[int, Tuple[str, str, str]]: + def format_matrix(self) -> dict[int, Tuple[str, str, str]]: # `gef.arch.endianness` is a runtime property, should not be defined as a class property return { 8: (f"{gef.arch.endianness}B", "char", "db"), @@ -4955,7 +4954,7 @@ def format_matrix(self) -> Dict[int, Tuple[str, str, str]]: @only_if_gdb_running @parse_arguments({"location": "$pc", }, {("--length", "-l"): 256, "--bitlen": 0, "--lang": "py", "--clip": False,}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: """Default value for print-format command.""" args: argparse.Namespace = kwargs["arguments"] args.bitlen = args.bitlen or gef.arch.ptrsize * 2 @@ -5023,7 +5022,7 @@ def __init__(self) -> None: super().__init__(prefix=True) return - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not argv: self.usage() return @@ -5037,7 +5036,7 @@ class PieBreakpointCommand(GenericCommand): _syntax_ = f"{_cmdline_} OFFSET" @parse_arguments({"offset": ""}, {}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] if not args.offset: self.usage() @@ -5069,7 +5068,7 @@ class PieInfoCommand(GenericCommand): _syntax_ = f"{_cmdline_} BREAKPOINT" @parse_arguments({"breakpoints": [-1,]}, {}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] if args.breakpoints[0] == -1: # No breakpoint info needed @@ -5095,7 +5094,7 @@ class PieDeleteCommand(GenericCommand): _syntax_ = f"{_cmdline_} [BREAKPOINT]" @parse_arguments({"breakpoints": [-1,]}, {}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: global gef args : argparse.Namespace = kwargs["arguments"] if args.breakpoints[0] == -1: @@ -5110,7 +5109,7 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: @staticmethod - def delete_bp(breakpoints: List[PieVirtualBreakpoint]) -> None: + def delete_bp(breakpoints: list[PieVirtualBreakpoint]) -> None: global gef for bp in breakpoints: # delete current real breakpoints if exists @@ -5128,7 +5127,7 @@ class PieRunCommand(GenericCommand): _cmdline_ = "pie run" _syntax_ = _cmdline_ - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: global gef fpath = get_filepath() if not fpath: @@ -5171,7 +5170,7 @@ class PieAttachCommand(GenericCommand): _cmdline_ = "pie attach" _syntax_ = f"{_cmdline_} PID" - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: try: gdb.execute(f"attach {' '.join(argv)}", to_string=True) except gdb.error as e: @@ -5195,7 +5194,7 @@ class PieRemoteCommand(GenericCommand): _cmdline_ = "pie remote" _syntax_ = f"{_cmdline_} REMOTE" - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: try: gdb.execute(f"gef-remote {' '.join(argv)}") except gdb.error as e: @@ -5221,7 +5220,7 @@ class SmartEvalCommand(GenericCommand): _example_ = (f"\n{_cmdline_} $pc+1" f"\n{_cmdline_} 0x00007ffff7a10000 0x00007ffff7bce000") - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: argc = len(argv) if argc == 1: self.evaluate(argv) @@ -5231,7 +5230,7 @@ def do_invoke(self, argv: List[str]) -> None: self.distance(argv) return - def evaluate(self, expr: List[str]) -> None: + def evaluate(self, expr: list[str]) -> None: def show_as_int(i: int) -> None: off = gef.arch.ptrsize*8 def comp2_x(x: Any) -> str: return f"{(x + (1 << off)) % (1 << off):x}" @@ -5268,7 +5267,7 @@ def comp2_b(x: Any) -> str: return f"{(x + (1 << off)) % (1 << off):b}" gef_print(" ".join(parsed_expr)) return - def distance(self, args: List[str]) -> None: + def distance(self, args: list[str]) -> None: try: x = int(args[0], 16) if is_hex(args[0]) else int(args[0]) y = int(args[1], 16) if is_hex(args[1]) else int(args[1]) @@ -5286,7 +5285,7 @@ class CanaryCommand(GenericCommand): _syntax_ = _cmdline_ @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: self.dont_repeat() fname = get_filepath() @@ -5321,7 +5320,7 @@ def __init__(self) -> None: @only_if_gdb_running @only_if_gdb_target_local - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: self.show_info_proc() self.show_ancestor() self.show_descendants() @@ -5329,7 +5328,7 @@ def do_invoke(self, argv: List[str]) -> None: self.show_connections() return - def get_state_of(self, pid: int) -> Dict[str, str]: + def get_state_of(self, pid: int) -> dict[str, str]: res = {} with open(f"/proc/{pid}/status", "r") as f: file = f.readlines() @@ -5345,7 +5344,7 @@ def get_cmdline_of(self, pid: int) -> str: def get_process_path_of(self, pid: int) -> str: return os.readlink(f"/proc/{pid}/exe") - def get_children_pids(self, pid: int) -> List[int]: + def get_children_pids(self, pid: int) -> list[int]: cmd = [gef.session.constants["ps"], "-o", "pid", "--ppid", f"{pid}", "--noheaders"] try: return [int(x) for x in gef_execute_external(cmd, as_list=True)] @@ -5400,7 +5399,7 @@ def show_fds(self) -> None: gef_print(f"\t{fullpath} {RIGHT_ARROW} {os.readlink(fullpath)}") return - def list_sockets(self, pid: int) -> List[int]: + def list_sockets(self, pid: int) -> list[int]: sockets = [] path = f"/proc/{pid:d}/fd" items = os.listdir(path) @@ -5469,7 +5468,7 @@ class GefThemeCommand(GenericCommand): _cmdline_ = "theme" _syntax_ = f"{_cmdline_} [KEY [VALUE]]" - _examples_ = (f"{_cmdline_} address_stack green") + _example_ = (f"{_cmdline_} address_stack green") def __init__(self) -> None: super().__init__(self._cmdline_) @@ -5492,7 +5491,7 @@ def __init__(self) -> None: self["source_current_line"] = ("green", "Color to use for the current code line in the source window") return - def do_invoke(self, args: List[str]) -> None: + def do_invoke(self, args: list[str]) -> None: self.dont_repeat() argc = len(args) @@ -5514,8 +5513,7 @@ def do_invoke(self, args: List[str]) -> None: return colors = (color for color in args[1:] if color in Color.colors) - self[setting_name][0] = " ".join(colors) - return + self[setting_name] = " ".join(colors) # type: ignore // this is valid since we overwrote __setitem__() class ExternalStructureManager: @@ -5534,7 +5532,7 @@ def __str__(self) -> str: return self.name def pprint(self) -> None: - res: List[str] = [] + res: list[str] = [] for _name, _type in self.class_type._fields_: # type: ignore size = ctypes.sizeof(_type) name = Color.colorify(_name, gef.config["pcustom.structure_name"]) @@ -5751,7 +5749,7 @@ def __init__(self) -> None: return @parse_arguments({"type": "", "address": ""}, {}) - def do_invoke(self, *_: Any, **kwargs: Dict[str, Any]) -> None: + def do_invoke(self, *_: Any, **kwargs: dict[str, Any]) -> None: args = cast(argparse.Namespace, kwargs["arguments"]) if not args.type: gdb.execute("pcustom list") @@ -5795,7 +5793,7 @@ def __init__(self) -> None: super().__init__() return - def do_invoke(self, _: List) -> None: + def do_invoke(self, _: list) -> None: """Dump the list of all the structures and their respective.""" manager = ExternalStructureManager() info(f"Listing custom structures from '{manager.path}'") @@ -5820,7 +5818,7 @@ def __init__(self) -> None: super().__init__() return - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) == 0: self.usage() return @@ -5848,7 +5846,7 @@ def __init__(self) -> None: super().__init__() return - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) == 0: self.usage() return @@ -5892,7 +5890,7 @@ class ChangeFdCommand(GenericCommand): @only_if_gdb_running @only_if_gdb_target_local - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) != 2: self.usage() return @@ -5970,7 +5968,7 @@ class ScanSectionCommand(GenericCommand): _example_ = f"\n{_cmdline_} stack libc" @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) != 2: self.usage() return @@ -6067,7 +6065,7 @@ def print_loc(self, loc: Tuple[int, int, str]) -> None: gef_print(f""" {loc[0]:#x} - {loc[1]:#x} {RIGHT_ARROW} "{Color.pinkify(loc[2])}" """) return - def search_pattern_by_address(self, pattern: str, start_address: int, end_address: int) -> List[Tuple[int, int, str]]: + def search_pattern_by_address(self, pattern: str, start_address: int, end_address: int) -> list[Tuple[int, int, str]]: """Search a pattern within a range defined by arguments.""" _pattern = gef_pybytes(pattern) step = self["nr_pages_chunk"] * gef.session.pagesize @@ -6099,7 +6097,7 @@ def search_pattern_by_address(self, pattern: str, start_address: int, end_addres return locations - def search_binpattern_by_address(self, binpattern: bytes, start_address: int, end_address: int) -> List[Tuple[int, int, str]]: + def search_binpattern_by_address(self, binpattern: bytes, start_address: int, end_address: int) -> list[Tuple[int, int, str]]: """Search a binary pattern within a range defined by arguments.""" step = self["nr_pages_chunk"] * gef.session.pagesize @@ -6152,7 +6150,7 @@ def search_pattern(self, pattern: str, section_name: str) -> None: return @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: argc = len(argv) if argc < 1: self.usage() @@ -6217,7 +6215,7 @@ class FlagsCommand(GenericCommand): _example_ = (f"\n{_cmdline_}" f"\n{_cmdline_} +zero # sets ZERO flag") - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not gef.arch.flag_register: warn(f"The architecture {gef.arch.arch}:{gef.arch.mode} doesn't have flag register.") return @@ -6273,7 +6271,7 @@ def __init__(self) -> None: return @parse_arguments({"host": "", "port": 0}, {"--pid": 0, "--qemu-user": ""}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: if gef.session.remote is not None: err("You already are in remote session. Close it first before opening a new one...") return @@ -6335,7 +6333,7 @@ def __init__(self) -> None: @only_if_gdb_running @parse_arguments({"address": "$pc"}, {"--n": 1}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] address = parse_address(args.address) num_instructions = args.n @@ -6364,7 +6362,7 @@ def __init__(self) -> None: return @only_if_gdb_running - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: target_addr = gef_next_instruction(parse_address("$pc")).address JustSilentStopBreakpoint("".join(["*", str(target_addr)])) gdb.execute("continue") @@ -6397,7 +6395,7 @@ def __init__(self) -> None: @only_if_gdb_running @parse_arguments({"address": "$pc"}, {"--i": 1, "--b": False, "--f": False, "--n": False}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] address = parse_address(args.address) nop = gef.arch.nop_insn @@ -6480,7 +6478,7 @@ def __init__(self) -> None: @only_if_gdb_running @parse_arguments({"address": ""}, {("-r", "--retval"): 0}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] loc = args.address if args.address else f"*{gef.arch.pc:#x}" StubBreakpoint(loc, args.retval) @@ -6499,7 +6497,7 @@ def __init__(self) -> None: return @only_if_gdb_running - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: self.usage() return @@ -6518,7 +6516,7 @@ def __init__(self) -> None: @only_if_gdb_running @parse_arguments({"addr": ""}, {"--reset": False}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: global gef args: argparse.Namespace = kwargs["arguments"] @@ -6555,7 +6553,7 @@ class GlibcHeapArenaCommand(GenericCommand): _syntax_ = _cmdline_ @only_if_gdb_running - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: for arena in gef.heap.arenas: gef_print(str(arena)) return @@ -6575,7 +6573,7 @@ def __init__(self) -> None: @parse_arguments({"address": ""}, {"--allow-unaligned": False, "--number": 1}) @only_if_gdb_running - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] if not args.address: err("Missing chunk address") @@ -6680,7 +6678,7 @@ def __init__(self) -> None: @parse_arguments({"arena_address": ""}, {("--all", "-a"): False, "--allow-unaligned": False, "--min-size": 0, "--max-size": 0, ("--count", "-n"): -1, ("--summary", "-s"): False, "--resolve": False}) @only_if_gdb_running - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args = kwargs["arguments"] ctx = GlibcHeapWalkContext(print_arena=args.all, allow_unaligned=args.allow_unaligned, min_size=args.min_size, max_size=args.max_size, count=args.count, resolve_type=args.resolve, summary=args.summary) if args.all or not args.arena_address: @@ -6776,7 +6774,7 @@ def __init__(self) -> None: return @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not argv: for bin_t in self._bin_types_: gdb.execute(f"heap bins {bin_t}") @@ -6839,7 +6837,7 @@ def __init__(self) -> None: return @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: # Determine if we are using libc with tcache built in (2.26+) if gef.libc.version and gef.libc.version < (2, 26): info("No Tcache in this version of libc") @@ -6936,7 +6934,7 @@ def find_tcache(self) -> int: tcache_addr = heap_base + 0x10 return tcache_addr - def check_thread_ids(self, tids: List[int]) -> List[int]: + def check_thread_ids(self, tids: list[int]) -> list[int]: """Return the subset of tids that are currently valid.""" existing_tids = set(t.num for t in gdb.selected_inferior().threads()) return list(set(tids) & existing_tids) @@ -7093,7 +7091,7 @@ def do_invoke(self, *_: Any, **kwargs: Any) -> None: arena_address = args.arena_address or f"{gef.heap.selected_arena.address:#x}" gef_print(titlify(f"Small Bins for arena at {arena_address}")) - bins: Dict[int, int] = {} + bins: dict[int, int] = {} heap_bins_cmd = gef.gdb.commands["heap bins"] assert isinstance (heap_bins_cmd, GlibcHeapBinsCommand) for i in range(1, 63): @@ -7151,7 +7149,7 @@ class DetailRegistersCommand(GenericCommand): @only_if_gdb_running @parse_arguments({"registers": [""]}, {}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: unchanged_color = gef.config["theme.registers_register_name"] changed_color = gef.config["theme.registers_value_changed"] string_color = gef.config["theme.dereference_string"] @@ -7246,7 +7244,7 @@ def __init__(self) -> None: super().__init__(prefix=True) return - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: err("Missing sub-command (search|get)") self.usage() return @@ -7263,18 +7261,14 @@ class ShellcodeSearchCommand(GenericCommand): api_base = "http://shell-storm.org" search_url = f"{api_base}/api/?s=" - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not argv: err("Missing pattern to search") self.usage() return - self.search_shellcode(argv) - return - - def search_shellcode(self, search_options: List) -> None: # API : http://shell-storm.org/shellcode/ - args = "*".join(search_options) + args = "*".join(argv) res = http_get(self.search_url + args) if res is None: @@ -7312,7 +7306,7 @@ class ShellcodeGetCommand(GenericCommand): api_base = "http://shell-storm.org" get_url = f"{api_base}/shellcode/files/shellcode-{{:d}}.html" - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) != 1: err("Missing ID to download") self.usage() @@ -7358,7 +7352,7 @@ def __init__(self) -> None: return @parse_arguments({"pattern": ""}, {"--attach": False, "--smart-scan": False}) - def do_invoke(self, _: List, **kwargs: Any) -> None: + def do_invoke(self, _: list, **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] do_attach = args.attach smart_scan = args.smart_scan @@ -7388,7 +7382,7 @@ def do_invoke(self, _: List, **kwargs: Any) -> None: return None - def get_processes(self) -> Generator[Dict[str, str], None, None]: + def get_processes(self) -> Generator[dict[str, str], None, None]: output = gef_execute_external(self["ps_command"].split(), True) names = [x.lower().replace("%", "") for x in output[0].split()] @@ -7421,7 +7415,7 @@ def __init__(self) -> None: return @parse_arguments({}, {"--filename": ""}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] if is_qemu_system(): @@ -7500,7 +7494,7 @@ def __init__(self) -> None: self["entrypoint_symbols"] = ("main _main __libc_start_main __uClibc_main start _start", "Possible symbols for entry points") return - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: fpath = get_filepath() if fpath is None: warn("No executable to debug, use `file` to load a binary") @@ -7554,7 +7548,7 @@ def set_init_tbreak(self, addr: int) -> EntryBreakBreakpoint: bp = EntryBreakBreakpoint(f"*{addr:#x}") return bp - def set_init_tbreak_pie(self, addr: int, argv: List[str]) -> EntryBreakBreakpoint: + def set_init_tbreak_pie(self, addr: int, argv: list[str]) -> EntryBreakBreakpoint: warn("PIC binary detected, retrieving text base address") gdb.execute("set stop-on-solib-events 1") hide_context() @@ -7580,7 +7574,7 @@ def __init__(self) -> None: return @parse_arguments({"name": "", "address": "*$pc"}, {}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] if not args.name: err("Missing name for breakpoint") @@ -7602,7 +7596,7 @@ class ContextCommand(GenericCommand): _syntax_ = f"{_cmdline_} [legend|regs|stack|code|args|memory|source|trace|threads|extra]" _aliases_ = ["ctx",] - old_registers: Dict[str, Optional[int]] = {} + old_registers: dict[str, Optional[int]] = {} def __init__(self) -> None: super().__init__() @@ -7630,7 +7624,7 @@ def __init__(self) -> None: self["libc_args"] = (False, "[DEPRECATED - Unused] Show libc function call args description") self["libc_args_path"] = ("", "[DEPRECATED - Unused] Path to libc function call args json files, provided via gef-extras") - self.layout_mapping: Dict[str, Tuple[Callable, Optional[Callable], Optional[Callable]]] = { + self.layout_mapping: dict[str, Tuple[Callable, Optional[Callable], Optional[Callable]]] = { "legend": (self.show_legend, None, None), "regs": (self.context_regs, None, None), "stack": (self.context_stack, None, None), @@ -7663,7 +7657,7 @@ def show_legend(self) -> None: return @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not self["enable"] or gef.ui.context_hidden: return @@ -7826,7 +7820,7 @@ def context_stack(self) -> None: return - def addr_has_breakpoint(self, address: int, bp_locations: List[str]) -> bool: + def addr_has_breakpoint(self, address: int, bp_locations: list[str]) -> bool: return any(hex(address) in b for b in bp_locations) def context_code(self) -> None: @@ -8026,7 +8020,7 @@ def __get_current_block_start_address() -> Optional[int]: gef_print(")") return - def line_has_breakpoint(self, file_name: str, line_number: int, bp_locations: List[str]) -> bool: + def line_has_breakpoint(self, file_name: str, line_number: int, bp_locations: list[str]) -> bool: filename_line = f"{file_name}:{line_number}" return any(filename_line in loc for loc in bp_locations) @@ -8306,7 +8300,7 @@ def __init__(self) -> None: return @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: self.usage() return @@ -8324,7 +8318,7 @@ def __init__(self) -> None: return @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) not in (1, 2, 3): self.usage() return @@ -8363,7 +8357,7 @@ def __init__(self) -> None: return @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not argv: self.usage() return @@ -8384,7 +8378,7 @@ class MemoryWatchResetCommand(GenericCommand): _syntax_ = f"{_cmdline_}" @only_if_gdb_running - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: gef.ui.watches.clear() ok("Memory watches cleared") return @@ -8397,7 +8391,7 @@ class MemoryWatchListCommand(GenericCommand): _syntax_ = f"{_cmdline_}" @only_if_gdb_running - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: if not gef.ui.watches: info("No memory watches") return @@ -8425,7 +8419,7 @@ def __init__(self) -> None: @only_if_gdb_running @parse_arguments({"address": "",}, {("--reverse", "-r"): False, ("--size", "-s"): 0}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: valid_formats = ["byte", "word", "dword", "qword"] if not self.format or self.format not in valid_formats: err("Invalid command") @@ -8452,7 +8446,7 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: gef_print("\n".join(lines)) return - def _hexdump(self, start_addr: int, length: int, arrange_as: str, offset: int = 0) -> List[str]: + def _hexdump(self, start_addr: int, length: int, arrange_as: str, offset: int = 0) -> list[str]: endianness = gef.arch.endianness base_address_color = gef.config["theme.dereference_base_address"] @@ -8563,7 +8557,7 @@ def __init__(self) -> None: @only_if_gdb_running @parse_arguments({"location": "", "values": ["", ]}, {}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] if not self.format or self.format not in self.SUPPORTED_SIZES: self.usage() @@ -8663,7 +8657,7 @@ class PatchStringCommand(GenericCommand): ] @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: argc = len(argv) if argc != 2: self.usage() @@ -8681,7 +8675,7 @@ def do_invoke(self, argv: List[str]) -> None: @lru_cache() -def dereference_from(address: int) -> List[str]: +def dereference_from(address: int) -> list[str]: if not is_alive(): return [format_address(address),] @@ -8791,7 +8785,7 @@ def pprint_dereferenced(addr: int, idx: int, base_offset: int = 0) -> str: @only_if_gdb_running @parse_arguments({"address": "$sp"}, {("-r", "--reference"): "", ("-l", "--length"): 10}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] nb = args.length @@ -8845,7 +8839,7 @@ class ASLRCommand(GenericCommand): _cmdline_ = "aslr" _syntax_ = f"{_cmdline_} [(on|off)]" - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: argc = len(argv) if argc == 0: @@ -8887,7 +8881,7 @@ class ResetCacheCommand(GenericCommand): _cmdline_ = "reset-cache" _syntax_ = _cmdline_ - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: reset_all_caches() return @@ -8902,7 +8896,7 @@ class VMMapCommand(GenericCommand): _example_ = f"{_cmdline_} libc" @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: vmmap = gef.memory.maps if not vmmap: err("No address mapping information found") @@ -8984,7 +8978,7 @@ class XFilesCommand(GenericCommand): _example_ = f"\n{_cmdline_} libc\n{_cmdline_} libc IO_vtables" @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: color = gef.config["theme.table_heading"] headers = ["Start", "End", "Name", "File"] gef_print(Color.colorify("{:<{w}s}{:<{w}s}{:<21s} {:s}".format(*headers, w=gef.arch.ptrsize*2+3), color)) @@ -9022,7 +9016,7 @@ def __init__(self) -> None: return @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not argv: err("At least one valid address must be specified") self.usage() @@ -9083,7 +9077,7 @@ def __init__(self) -> None: super().__init__(prefix=True) return - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: self.usage() return @@ -9098,7 +9092,7 @@ class XorMemoryDisplayCommand(GenericCommand): _example_ = f"{_cmdline_} $sp 16 41414141" @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) != 3: self.usage() return @@ -9127,7 +9121,7 @@ class XorMemoryPatchCommand(GenericCommand): _example_ = f"{_cmdline_} $sp 16 41414141" @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) != 3: self.usage() return @@ -9159,7 +9153,7 @@ def __init__(self) -> None: return @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) not in (1, 2): self.usage() return @@ -9246,7 +9240,7 @@ def __init__(self) -> None: self["length"] = (1024, "Default length of a cyclic buffer to generate") return - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: self.usage() return @@ -9265,7 +9259,7 @@ class PatternCreateCommand(GenericCommand): ] @parse_arguments({"length": 0}, {"-n": 0,}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] length = args.length or gef.config["pattern.length"] n = args.n or gef.arch.ptrsize @@ -9292,7 +9286,7 @@ class PatternSearchCommand(GenericCommand): @only_if_gdb_running @parse_arguments({"pattern": ""}, {("--period", "-n"): 0, ("--max-length", "-l"): 0}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args = kwargs["arguments"] if not args.pattern: warn("No pattern provided") @@ -9361,7 +9355,7 @@ def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_FILENAME) return - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: argc = len(argv) if argc == 0: @@ -9426,7 +9420,7 @@ def build_line(self, name: str, color: str, address_val: int, got_address: int) @only_if_gdb_running @parse_arguments({"symbols": [""]}, {"--all": False}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] vmmap = gef.memory.maps mapfiles = [mapfile for mapfile in vmmap if @@ -9436,7 +9430,7 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: for mapfile in mapfiles: self.print_got_for(mapfile.path, mapfile.realpath, args.symbols) - def print_got_for(self, file: str, realpath: str, argv: List[str]) -> None: + def print_got_for(self, file: str, realpath: str, argv: list[str]) -> None: readelf = gef.session.constants["readelf"] elf_file = realpath @@ -9505,7 +9499,7 @@ def __init__(self) -> None: super().__init__(prefix=True) self["regex"] = (False, "Enable regex highlighting") - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: return self.usage() @@ -9527,7 +9521,7 @@ def print_highlight_table(self) -> None: f"{Color.colorify(color, color)}") return - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: return self.print_highlight_table() @@ -9538,7 +9532,7 @@ class HighlightClearCommand(GenericCommand): _aliases_ = ["hlc"] _syntax_ = _cmdline_ - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: return gef.ui.highlight_table.clear() @@ -9550,7 +9544,7 @@ class HighlightAddCommand(GenericCommand): _aliases_ = ["highlight set", "hla"] _example_ = f"{_cmdline_} 41414141 yellow" - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) < 2: return self.usage() @@ -9573,7 +9567,7 @@ class HighlightRemoveCommand(GenericCommand): ] _example_ = f"{_cmdline_} remove 41414141" - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not argv: return self.usage() @@ -9591,7 +9585,7 @@ class FormatStringSearchCommand(GenericCommand): _syntax_ = _cmdline_ _aliases_ = ["fmtstr-helper",] - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: dangerous_functions = { "printf": 0, "sprintf": 1, @@ -9641,7 +9635,7 @@ def __init__(self) -> None: @only_if_gdb_running @experimental_feature - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not argv: self.setup() return @@ -9742,7 +9736,7 @@ def invoke(self, *args: Any) -> int: raise gdb.GdbError("No debugging session active") return self.do_invoke(args) - def arg_to_long(self, args: List, index: int, default: int = 0) -> int: + def arg_to_long(self, args: Any, index: int, default: int = 0) -> int: try: addr = args[index] return int(addr) if addr.address is None else int(addr.address) @@ -9759,7 +9753,7 @@ class StackOffsetFunction(GenericFunction): _function_ = "_stack" _syntax_ = f"${_function_}()" - def do_invoke(self, args: List) -> int: + def do_invoke(self, args: list) -> int: base = get_section_base_address("[stack]") if not base: raise gdb.GdbError("Stack not found") @@ -9773,7 +9767,7 @@ class HeapBaseFunction(GenericFunction): _function_ = "_heap" _syntax_ = f"${_function_}()" - def do_invoke(self, args: List) -> int: + def do_invoke(self, args: list[str]) -> int: base = gef.heap.base_address if not base: base = get_section_base_address("[heap]") @@ -9790,7 +9784,7 @@ class SectionBaseFunction(GenericFunction): _syntax_ = "$_base([filepath])" _example_ = "p $_base(\\\"/usr/lib/ld-2.33.so\\\")" - def do_invoke(self, args: List) -> int: + def do_invoke(self, args: list) -> int: addr = 0 try: name = args[0].string() @@ -9818,7 +9812,7 @@ class BssBaseFunction(GenericFunction): _syntax_ = f"${_function_}([OFFSET])" _example_ = "deref $_bss(0x20)" - def do_invoke(self, args: List) -> int: + def do_invoke(self, args: list) -> int: base = get_zone_base_address(".bss") if not base: raise gdb.GdbError("BSS not found") @@ -9832,7 +9826,7 @@ class GotBaseFunction(GenericFunction): _syntax_ = f"${_function_}([OFFSET])" _example_ = "deref $_got(0x20)" - def do_invoke(self, args: List) -> int: + def do_invoke(self, args: list) -> int: base = get_zone_base_address(".got") if not base: raise gdb.GdbError("GOT not found") @@ -9922,24 +9916,24 @@ def __init__(self) -> None: gef.config["gef.propagate_debug_exception"] = GefSetting(False, bool, "If true, when debug mode is enabled, Python exceptions will be propagated all the way.") gef.config["gef.extra_python_package_paths"] = GefSetting("", str, "Semi-colon separate list of extra paths to include for python packages") - self.commands : Dict[str, GenericCommand] = collections.OrderedDict() - self.functions : Dict[str, GenericFunction] = collections.OrderedDict() - self.missing: Dict[str, Exception] = {} + self.commands : dict[str, GenericCommand] = collections.OrderedDict() + self.functions : dict[str, GenericFunction] = collections.OrderedDict() + self.missing: dict[str, Exception] = {} return @property @deprecated() - def loaded_commands(self) -> List[Tuple[str, Type[GenericCommand], Any]]: + def loaded_commands(self) -> list[Tuple[str, Type[GenericCommand], Any]]: raise ObsoleteException("Obsolete loaded_commands") @property @deprecated() - def loaded_functions(self) -> List[Type[GenericFunction]]: + def loaded_functions(self) -> list[Type[GenericFunction]]: raise ObsoleteException("Obsolete loaded_functions") @property @deprecated() - def missing_commands(self) -> Dict[str, Exception]: + def missing_commands(self) -> dict[str, Exception]: raise ObsoleteException("Obsolete missing_commands") def setup(self) -> None: @@ -10221,7 +10215,7 @@ def print_settings(self) -> None: self.print_setting(x) return - def set_setting(self, argv: List[str]) -> bool: + def set_setting(self, argv: list[str]) -> bool: global gef key, new_value = argv @@ -10257,7 +10251,7 @@ def set_setting(self, argv: List[str]) -> bool: reset_all_caches() return True - def complete(self, text: str, word: str) -> List[str]: + def complete(self, text: str, word: str) -> list[str]: settings = sorted(gef.config) if text == "": @@ -10373,7 +10367,7 @@ def __init__(self) -> None: def invoke(self, args: Any, from_tty: bool) -> None: self.dont_repeat() - missing_commands: Dict[str, Exception] = gef.gdb.missing + missing_commands: dict[str, Exception] = gef.gdb.missing if not missing_commands: ok("No missing command") return @@ -10486,7 +10480,7 @@ def __init__(self) -> None: super().__init__(prefix=True) return - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: self.usage() return @@ -10503,7 +10497,7 @@ def __init__(self) -> None: super().__init__() return - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) < 2: self.usage() return @@ -10522,7 +10516,7 @@ def __init__(self) -> None: super().__init__() return - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: global gef if len(argv) != 1: self.usage() @@ -10548,7 +10542,7 @@ def __init__(self) -> None: super().__init__() return - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: ok("Aliases defined:") for a in gef.session.aliases: gef_print(f"{a.alias:30s} {RIGHT_ARROW} {a.command}") @@ -10717,7 +10711,7 @@ def __init__(self) -> None: def reset_caches(self) -> None: super().reset_caches() - self.__maps: Optional[List[Section]] = None + self.__maps: Optional[list[Section]] = None return def write(self, address: int, buffer: ByteString, length: Optional[int] = None) -> None: @@ -10772,7 +10766,7 @@ def read_ascii_string(self, address: int) -> Optional[str]: return None @property - def maps(self) -> List[Section]: + def maps(self) -> list[Section]: if not self.__maps: maps = self.__parse_maps() if not maps: @@ -10780,7 +10774,7 @@ def maps(self) -> List[Section]: self.__maps = maps return self.__maps - def __parse_maps(self) -> Optional[List[Section]]: + def __parse_maps(self) -> Optional[list[Section]]: """Return the mapped memory sections. If the current arch has its maps method defined, then defer to that to generated maps, otherwise, try to figure it out from procfs, then info sections, then monitor info @@ -11077,7 +11071,7 @@ def selected_arena(self, value: GlibcArena) -> None: return @property - def arenas(self) -> Union[List, Iterator[GlibcArena]]: + def arenas(self) -> list | Iterator[GlibcArena]: if not self.main_arena: return [] return iter(self.main_arena) @@ -11098,7 +11092,7 @@ def base_address(self) -> Optional[int]: return self.__heap_base @property - def chunks(self) -> Union[List, Iterator]: + def chunks(self) -> list | Iterator: if not self.base_address: return [] return iter(GlibcChunk(self.base_address, from_base=True)) @@ -11127,20 +11121,20 @@ def tidx2size(self, idx: int) -> int: def malloc_align_address(self, address: int) -> int: """Align addresses according to glibc's MALLOC_ALIGNMENT. See also Issue #689 on Github""" - def ceil(n: int) -> int: + def ceil(n: float) -> int: return int(-1 * n // 1 * -1) malloc_alignment = self.malloc_alignment - return malloc_alignment * ceil((address // malloc_alignment)) + return malloc_alignment * ceil((address / malloc_alignment)) class GefSetting: """Basic class for storing gef settings as objects""" - def __init__(self, value: Any, cls: Optional[type] = None, description: Optional[str] = None, hooks: Optional[Dict[str, List[Callable]]] = None) -> None: + def __init__(self, value: Any, cls: Optional[type] = None, description: Optional[str] = None, hooks: Optional[dict[str, list[Callable]]] = None) -> None: self.value = value self.type = cls or type(value) self.description = description or "" - self.hooks: Dict[str, List[Callable]] = collections.defaultdict(list) + self.hooks: dict[str, list[Callable]] = collections.defaultdict(list) if not hooks: hooks = {"on_read": [], "on_write": [], "on_changed": []} @@ -11153,7 +11147,7 @@ def __str__(self) -> str: f"read_hooks={len(self.hooks['on_read'])}, write_hooks={len(self.hooks['on_write'])}, "\ f"changed_hooks={len(self.hooks['on_changed'])})" - def add_hook(self, access: str, funcs: List[Callable]): + def add_hook(self, access: str, funcs: list[Callable]): if access not in ("on_read", "on_write", "on_changed"): raise ValueError("invalid access type") for func in funcs: @@ -11241,13 +11235,13 @@ def __init__(self) -> None: self.remote_initializing: bool = False self.qemu_mode: bool = False self.convenience_vars_index: int = 0 - self.heap_allocated_chunks: List[Tuple[int, int]] = [] - self.heap_freed_chunks: List[Tuple[int, int]] = [] - self.heap_uaf_watchpoints: List[UafWatchpoint] = [] - self.pie_breakpoints: Dict[int, PieVirtualBreakpoint] = {} + self.heap_allocated_chunks: list[Tuple[int, int]] = [] + self.heap_freed_chunks: list[Tuple[int, int]] = [] + self.heap_uaf_watchpoints: list[UafWatchpoint] = [] + self.pie_breakpoints: dict[int, PieVirtualBreakpoint] = {} self.pie_counter: int = 1 - self.aliases: List[GefAlias] = [] - self.modules: List[FileFormat] = [] + self.aliases: list[GefAlias] = [] + self.modules: list[FileFormat] = [] self.constants = {} # a dict for runtime constants (like 3rd party file paths) for constant in ("python3", "readelf", "nm", "file", "ps"): self.constants[constant] = which(constant) @@ -11272,7 +11266,7 @@ def __repr__(self) -> str: return str(self) @property - def auxiliary_vector(self) -> Optional[Dict[str, int]]: + def auxiliary_vector(self) -> Optional[dict[str, int]]: if not is_alive(): return None if is_qemu_system(): @@ -11621,9 +11615,9 @@ def __init__(self) -> None: self.redirect_fd : Optional[TextIOWrapper] = None self.context_hidden = False self.stream_buffer : Optional[StringIO] = None - self.highlight_table: Dict[str, str] = {} - self.watches: Dict[int, Tuple[int, str]] = {} - self.context_messages: List[Tuple[str, str]] = [] + self.highlight_table: dict[str, str] = {} + self.watches: dict[int, Tuple[int, str]] = {} + self.context_messages: list[Tuple[str, str]] = [] return diff --git a/tests/base.py b/tests/base.py index e284dd0da..fc209be0c 100644 --- a/tests/base.py +++ b/tests/base.py @@ -110,6 +110,8 @@ def tearDown(self) -> None: @property def gdb_version(self) -> Tuple[int, int]: - res = [int(d) for d in re.search(r"(\d+)\D(\d+)", self._gdb.VERSION).groups()] - assert len(res) >= 2 - return tuple(res) + res = re.search(r"(\d+)\D(\d+)", self._gdb.VERSION) + assert res + groups = [int(d) for d in res.groups()] + assert len(groups) == 2 + return (groups[0], groups[1]) diff --git a/tests/commands/gef_remote.py b/tests/commands/gef_remote.py index 578547f8b..34fd0ab60 100644 --- a/tests/commands/gef_remote.py +++ b/tests/commands/gef_remote.py @@ -71,5 +71,5 @@ def test_cmd_target_remote(self): with gdbserver_session(port=port) as _: gdb.execute(f"target remote {GDBSERVER_DEFAULT_HOST}:{port}") res: str = root.eval("str(gef.session.remote)") - assert res.startswith(f"RemoteSession(target=':0', local='/tmp/") + assert res.startswith("RemoteSession(target=':0', local='/tmp/") assert res.endswith(f"pid={gef.session.pid}, mode={gdbserver_mode})") diff --git a/tests/commands/theme.py b/tests/commands/theme.py index a09cfaa6f..44744475e 100644 --- a/tests/commands/theme.py +++ b/tests/commands/theme.py @@ -41,5 +41,5 @@ def test_cmd_theme(self): gdb.execute(f"theme {t} {v}") - res = gdb.execute(f"theme ___I_DONT_EXIST___", to_string=True) + res = gdb.execute("theme ___I_DONT_EXIST___", to_string=True) self.assertIn("Invalid key", res) From b83586e0c0c25877ffd4d2619ebf854951cdb0f6 Mon Sep 17 00:00:00 2001 From: hugsy Date: Fri, 1 Nov 2024 12:11:46 -0700 Subject: [PATCH 03/53] removed `Set` and `Tuple` types (replaced with `tuple` and `set`) --- gef.py | 132 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/gef.py b/gef.py index 17d602c23..bb6054ace 100644 --- a/gef.py +++ b/gef.py @@ -115,10 +115,10 @@ def update_gef(argv: list[str]) -> int: sys.exit(1) -GDB_MIN_VERSION: Tuple[int, int] = (8, 0) -PYTHON_MIN_VERSION: Tuple[int, int] = (3, 6) -PYTHON_VERSION: Tuple[int, int] = sys.version_info[0:2] -GDB_VERSION: Tuple[int, int] = tuple(map(int, re.search(r"(\d+)[^\d]+(\d+)", gdb.VERSION).groups())) # type:ignore +GDB_MIN_VERSION: tuple[int, int] = (8, 0) +PYTHON_MIN_VERSION: tuple[int, int] = (3, 6) +PYTHON_VERSION: tuple[int, int] = sys.version_info[0:2] +GDB_VERSION: tuple[int, int] = tuple(map(int, re.search(r"(\d+)[^\d]+(\d+)", gdb.VERSION).groups())) # type:ignore DEFAULT_PAGE_ALIGN_SHIFT = 12 DEFAULT_PAGE_SIZE = 1 << DEFAULT_PAGE_ALIGN_SHIFT @@ -144,10 +144,10 @@ def update_gef(argv: list[str]) -> int: GEF_PROMPT_ON = f"\001\033[1;32m\002{GEF_PROMPT}\001\033[0m\002" GEF_PROMPT_OFF = f"\001\033[1;31m\002{GEF_PROMPT}\001\033[0m\002" -__registered_commands__ : Set[Type["GenericCommand"]] = set() -__registered_functions__ : Set[Type["GenericFunction"]] = set() +__registered_commands__ : set[Type["GenericCommand"]] = set() +__registered_functions__ : set[Type["GenericFunction"]] = set() __registered_architectures__ : dict[Union["Elf.Abi", str], Type["Architecture"]] = {} -__registered_file_formats__ : Set[ Type["FileFormat"] ] = set() +__registered_file_formats__ : set[ Type["FileFormat"] ] = set() GefMemoryMapProvider = Callable[[], Generator["Section", None, None]] @@ -446,8 +446,8 @@ def FakeExit(*args: Any, **kwargs: Any) -> NoReturn: sys.exit = FakeExit -def parse_arguments(required_arguments: dict[Union[str, Tuple[str, str]], Any], - optional_arguments: dict[Union[str, Tuple[str, str]], Any]) -> Callable: +def parse_arguments(required_arguments: dict[Union[str, tuple[str, str]], Any], + optional_arguments: dict[Union[str, tuple[str, str]], Any]) -> Callable: """Argument parsing decorator.""" def int_wrapper(x: str) -> int: return int(x, 0) @@ -953,7 +953,7 @@ def __init__(self, path: Union[str, pathlib.Path]) -> None: def read(self, size: int) -> bytes: return self.fd.read(size) - def read_and_unpack(self, fmt: str) -> Tuple[Any, ...]: + def read_and_unpack(self, fmt: str) -> tuple[Any, ...]: size = struct.calcsize(fmt) data = self.fd.read(size) return struct.unpack(fmt, data) @@ -1523,7 +1523,7 @@ def fastbin(self, i: int) -> Optional["GlibcFastChunk"]: return None return GlibcFastChunk(addr + 2 * gef.arch.ptrsize) - def bin(self, i: int) -> Tuple[int, int]: + def bin(self, i: int) -> tuple[int, int]: idx = i * 2 fd = int(self.bins[idx]) bk = int(self.bins[idx + 1]) @@ -1824,7 +1824,7 @@ class GlibcTcacheChunk(GlibcFastChunk): pass @deprecated("Use GefLibcManager.find_libc_version()") -def get_libc_version() -> Tuple[int, ...]: +def get_libc_version() -> tuple[int, ...]: return GefLibcManager.find_libc_version() def titlify(text: str, color: Optional[str] = None, msg_color: Optional[str] = None) -> str: @@ -2091,7 +2091,7 @@ def gef_makedirs(path: str, mode: int = 0o755) -> pathlib.Path: @lru_cache() -def gdb_lookup_symbol(sym: str) -> Optional[Tuple[gdb.Symtab_and_line, ...]]: +def gdb_lookup_symbol(sym: str) -> Optional[tuple[gdb.Symtab_and_line, ...]]: """Fetch the proper symbol or None if not defined.""" try: res = gdb.decode_line(sym)[1] # pylint: disable=E1136 @@ -2100,7 +2100,7 @@ def gdb_lookup_symbol(sym: str) -> Optional[Tuple[gdb.Symtab_and_line, ...]]: return None @lru_cache(maxsize=512) -def gdb_get_location_from_symbol(address: int) -> Optional[Tuple[str, int]]: +def gdb_get_location_from_symbol(address: int) -> Optional[tuple[str, int]]: """Retrieve the location of the `address` argument from the symbol table. Return a tuple with the name and offset if found, None otherwise.""" # this is horrible, ugly hack and shitty perf... @@ -2340,7 +2340,7 @@ def register_architecture(cls: Type["Architecture"]) -> Type["Architecture"]: class ArchitectureBase: """Class decorator for declaring an architecture to GEF.""" - aliases: Union[Tuple[()], Tuple[Union[str, Elf.Abi], ...]] = () + aliases: Union[tuple[()], tuple[Union[str, Elf.Abi], ...]] = () def __init_subclass__(cls: Type["ArchitectureBase"], **kwargs): global __registered_architectures__ @@ -2360,20 +2360,20 @@ class Architecture(ArchitectureBase): # Mandatory defined attributes by inheriting classes arch: str mode: str - all_registers: Union[Tuple[()], Tuple[str, ...]] + all_registers: Union[tuple[()], tuple[str, ...]] nop_insn: bytes return_register: str flag_register: Optional[str] instruction_length: Optional[int] flags_table: dict[int, str] syscall_register: Optional[str] - syscall_instructions: Union[Tuple[()], Tuple[str, ...]] - function_parameters: Union[Tuple[()], Tuple[str, ...]] + syscall_instructions: Union[tuple[()], tuple[str, ...]] + function_parameters: Union[tuple[()], tuple[str, ...]] # Optionally defined attributes _ptrsize: Optional[int] = None _endianness: Optional[Endianness] = None - special_registers: Union[Tuple[()], Tuple[str, ...]] = () + special_registers: Union[tuple[()], tuple[str, ...]] = () maps: Optional[GefMemoryMapProvider] = None def __init_subclass__(cls, **kwargs): @@ -2409,7 +2409,7 @@ def is_ret(self, insn: Instruction) -> bool: def is_conditional_branch(self, insn: Instruction) -> bool: raise NotImplementedError - def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: raise NotImplementedError def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: @@ -2488,7 +2488,7 @@ def endianness(self) -> Endianness: raise OSError(f"No valid endianess found in '{output}'") return self._endianness - def get_ith_parameter(self, i: int, in_func: bool = True) -> Tuple[str, Optional[int]]: + def get_ith_parameter(self, i: int, in_func: bool = True) -> tuple[str, Optional[int]]: """Retrieves the correct parameter used for the current function call.""" reg = self.function_parameters[i] val = self.register(reg) @@ -2563,7 +2563,7 @@ def ptrsize(self) -> int: def is_conditional_branch(self, insn: Instruction) -> bool: return insn.mnemonic.startswith("b") - def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: def long_to_twos_complement(v: int) -> int: """Convert a python long value to its two's complement.""" if is_32bit(): @@ -2716,7 +2716,7 @@ def is_conditional_branch(self, insn: Instruction) -> bool: conditions = {"eq", "ne", "lt", "le", "gt", "ge", "vs", "vc", "mi", "pl", "hi", "ls", "cc", "cs"} return insn.mnemonic[-2:] in conditions - def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: mnemo = insn.mnemonic # ref: https://www.davespace.co.uk/arm/introduction-to-arm/conditional.html flags = dict((self.flags_table[k], k) for k in self.flags_table) @@ -2882,7 +2882,7 @@ def is_conditional_branch(self, insn: Instruction) -> bool: branch_mnemos = {"cbnz", "cbz", "tbnz", "tbz"} return mnemo.startswith("b.") or mnemo in branch_mnemos - def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: mnemo, operands = insn.mnemonic, insn.operands taken, reason = False, "" @@ -2914,7 +2914,7 @@ def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: class X86(Architecture): - aliases: Tuple[Union[str, Elf.Abi], ...] = ("X86", Elf.Abi.X86_32) + aliases: tuple[Union[str, Elf.Abi], ...] = ("X86", Elf.Abi.X86_32) arch = "X86" mode = "32" @@ -2969,7 +2969,7 @@ def is_conditional_branch(self, insn: Instruction) -> bool: } return mnemo in branch_mnemos - def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: mnemo = insn.mnemonic # all kudos to fG! (https://github.com/gdbinit/Gdbinit/blob/master/gdbinit#L1654) flags = dict((self.flags_table[k], k) for k in self.flags_table) @@ -3042,7 +3042,7 @@ def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: ] return "; ".join(insns) - def get_ith_parameter(self, i: int, in_func: bool = True) -> Tuple[str, Optional[int]]: + def get_ith_parameter(self, i: int, in_func: bool = True) -> tuple[str, Optional[int]]: if in_func: i += 1 # Account for RA being at the top of the stack sp = gef.arch.sp @@ -3149,7 +3149,7 @@ def is_conditional_branch(self, insn: Instruction) -> bool: branch_mnemos = {"beq", "bne", "ble", "blt", "bgt", "bge"} return mnemo in branch_mnemos - def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: mnemo = insn.mnemonic flags = dict((self.flags_table[k], k) for k in self.flags_table) val = gef.arch.register(self.flag_register) @@ -3257,7 +3257,7 @@ def is_conditional_branch(self, insn: Instruction) -> bool: } return mnemo in branch_mnemos - def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: mnemo = insn.mnemonic flags = dict((self.flags_table[k], k) for k in self.flags_table) val = gef.arch.register(self.flag_register) @@ -3358,7 +3358,7 @@ def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: class MIPS(Architecture): - aliases: Tuple[Union[str, Elf.Abi], ...] = ("MIPS", Elf.Abi.MIPS) + aliases: tuple[Union[str, Elf.Abi], ...] = ("MIPS", Elf.Abi.MIPS) arch = "MIPS" mode = "MIPS32" @@ -3393,7 +3393,7 @@ def is_conditional_branch(self, insn: Instruction) -> bool: branch_mnemos = {"beq", "bne", "beqz", "bnez", "bgtz", "bgez", "bltz", "blez"} return mnemo in branch_mnemos - def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: mnemo, ops = insn.mnemonic, insn.operands taken, reason = False, "" @@ -3769,7 +3769,7 @@ def regchanged_handler(_: "gdb.RegisterChangedEvent") -> None: return -def get_terminal_size() -> Tuple[int, int]: +def get_terminal_size() -> tuple[int, int]: """Return the current terminal size.""" if is_debug(): return 600, 100 @@ -4089,7 +4089,7 @@ def gef_getpagesize() -> int: @deprecated("Use `gef.session.canary`") -def gef_read_canary() -> Optional[Tuple[int, int]]: +def gef_read_canary() -> Optional[tuple[int, int]]: return gef.session.canary @@ -4561,7 +4561,7 @@ def stop(self) -> bool: return False # software watchpoints stop after the next statement (see - # https://sourceware.org/gdb/onlinedocs/gdb/Set-Watchpoints.html) + # https://sourceware.org/gdb/onlinedocs/gdb/set-Watchpoints.html) pc = gdb_get_nth_previous_instruction_address(gef.arch.pc, 2) assert pc insn = gef_current_instruction(pc) @@ -4778,10 +4778,10 @@ def __contains__(self, name: str) -> bool: return self.__get_setting_name(name) in gef.config @deprecated("Use `self[setting_name] = value` instead") - def add_setting(self, name: str, value: Tuple[Any, type, str], description: str = "") -> None: + def add_setting(self, name: str, value: tuple[Any, type, str], description: str = "") -> None: return self.__setitem__(name, (value, description)) - def __setitem__(self, name: str, value: Union["GefSetting", Tuple[Any, str]]) -> None: + def __setitem__(self, name: str, value: Union["GefSetting", tuple[Any, str]]) -> None: # make sure settings are always associated to the root command (which derives from GenericCommand) if "GenericCommand" not in [x.__name__ for x in self.__class__.__bases__]: return @@ -4855,7 +4855,7 @@ def do_invoke(self, args: list[str]) -> None: @register class ArchSetCommand(GenericCommand): - """Set the current loaded architecture.""" + """set the current loaded architecture.""" _cmdline_ = "arch set" _syntax_ = f"{_cmdline_} " @@ -4943,7 +4943,7 @@ def __init__(self) -> None: return @property - def format_matrix(self) -> dict[int, Tuple[str, str, str]]: + def format_matrix(self) -> dict[int, tuple[str, str, str]]: # `gef.arch.endianness` is a runtime property, should not be defined as a class property return { 8: (f"{gef.arch.endianness}B", "char", "db"), @@ -5030,7 +5030,7 @@ def do_invoke(self, argv: list[str]) -> None: @register class PieBreakpointCommand(GenericCommand): - """Set a PIE breakpoint at an offset from the target binaries base address.""" + """set a PIE breakpoint at an offset from the target binaries base address.""" _cmdline_ = "pie breakpoint" _syntax_ = f"{_cmdline_} OFFSET" @@ -5410,7 +5410,7 @@ def list_sockets(self, pid: int) -> list[int]: sockets.append(int(p)) return sockets - def parse_ip_port(self, addr: str) -> Tuple[str, int]: + def parse_ip_port(self, addr: str) -> tuple[str, int]: ip, port = addr.split(":") return socket.inet_ntoa(struct.pack(" str: for name, values in struct._values_: if name != item: continue if callable(values): - return values(value) + return str(values(value)) try: for val, desc in values: if value == val: return desc @@ -5709,14 +5709,14 @@ def path(self) -> pathlib.Path: return self._path @property - def structures(self) -> Generator[Tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"], None, None]: + def structures(self) -> Generator[tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"], None, None]: for module in self.modules.values(): for structure in module.values(): yield module, structure return @lru_cache() - def find(self, structure_name: str) -> Optional[Tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"]]: + def find(self, structure_name: str) -> Optional[tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"]]: """Return the module and structure for the given structure name; `None` if the structure name was not found.""" for module in self.modules.values(): if structure_name in module: @@ -5776,7 +5776,7 @@ def do_invoke(self, *_: Any, **kwargs: dict[str, Any]) -> None: structure.apply_at(address, self["max_depth"]) return - def explode_type(self, arg: str) -> Tuple[str, str]: + def explode_type(self, arg: str) -> tuple[str, str]: modname, structname = arg.split(":", 1) if ":" in arg else (arg, arg) structname = structname.split(".", 1)[0] if "." in structname else structname return modname, structname @@ -6061,11 +6061,11 @@ def print_section(self, section: Section) -> None: ok(title) return - def print_loc(self, loc: Tuple[int, int, str]) -> None: + def print_loc(self, loc: tuple[int, int, str]) -> None: gef_print(f""" {loc[0]:#x} - {loc[1]:#x} {RIGHT_ARROW} "{Color.pinkify(loc[2])}" """) return - def search_pattern_by_address(self, pattern: str, start_address: int, end_address: int) -> list[Tuple[int, int, str]]: + def search_pattern_by_address(self, pattern: str, start_address: int, end_address: int) -> list[tuple[int, int, str]]: """Search a pattern within a range defined by arguments.""" _pattern = gef_pybytes(pattern) step = self["nr_pages_chunk"] * gef.session.pagesize @@ -6097,7 +6097,7 @@ def search_pattern_by_address(self, pattern: str, start_address: int, end_addres return locations - def search_binpattern_by_address(self, binpattern: bytes, start_address: int, end_address: int) -> list[Tuple[int, int, str]]: + def search_binpattern_by_address(self, binpattern: bytes, start_address: int, end_address: int) -> list[tuple[int, int, str]]: """Search a binary pattern within a range defined by arguments.""" step = self["nr_pages_chunk"] * gef.session.pagesize @@ -6294,7 +6294,7 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: raise e # Try to establish the remote session, throw on error - # Set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which + # set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which # calls `is_remote_debug` which checks if `remote_initializing` is True or `.remote` is None # This prevents some spurious errors being thrown during startup gef.session.remote_initializing = True @@ -6504,7 +6504,7 @@ def do_invoke(self, _: list[str]) -> None: @register class GlibcHeapSetArenaCommand(GenericCommand): - """Set the address of the main_arena or the currently selected arena.""" + """set the address of the main_arena or the currently selected arena.""" _cmdline_ = "heap set-arena" _syntax_ = f"{_cmdline_} [address|&symbol]" @@ -6939,7 +6939,7 @@ def check_thread_ids(self, tids: list[int]) -> list[int]: existing_tids = set(t.num for t in gdb.selected_inferior().threads()) return list(set(tids) & existing_tids) - def tcachebin(self, tcache_base: int, i: int) -> Tuple[Optional[GlibcTcacheChunk], int]: + def tcachebin(self, tcache_base: int, i: int) -> tuple[Optional[GlibcTcacheChunk], int]: """Return the head chunk in tcache[i] and the number of chunks in the bin.""" if i >= self.TCACHE_MAX_BINS: err("Incorrect index value, index value must be between 0 and " @@ -7624,7 +7624,7 @@ def __init__(self) -> None: self["libc_args"] = (False, "[DEPRECATED - Unused] Show libc function call args description") self["libc_args_path"] = ("", "[DEPRECATED - Unused] Path to libc function call args json files, provided via gef-extras") - self.layout_mapping: dict[str, Tuple[Callable, Optional[Callable], Optional[Callable]]] = { + self.layout_mapping: dict[str, tuple[Callable, Optional[Callable], Optional[Callable]]] = { "legend": (self.show_legend, None, None), "regs": (self.context_regs, None, None), "stack": (self.context_stack, None, None), @@ -8668,7 +8668,7 @@ def do_invoke(self, argv: list[str]) -> None: try: msg_as_bytes = codecs.escape_decode(msg, "utf-8")[0] - gef.memory.write(addr, msg_as_bytes, len(msg_as_bytes)) + gef.memory.write(addr, msg_as_bytes, len(msg_as_bytes)) # type: ignore except (binascii.Error, gdb.error): err(f"Could not decode '\\xXX' encoded string \"{msg}\"") return @@ -9912,7 +9912,7 @@ def __init__(self) -> None: gef.config["gef.buffer"] = GefSetting(True, bool, "Internally buffer command output until completion") gef.config["gef.bruteforce_main_arena"] = GefSetting(False, bool, "Allow bruteforcing main_arena symbol if everything else fails") gef.config["gef.libc_version"] = GefSetting("", str, "Specify libc version when auto-detection fails") - gef.config["gef.main_arena_offset"] = GefSetting("", str, "Offset from libc base address to main_arena symbol (int or hex). Set to empty string to disable.") + gef.config["gef.main_arena_offset"] = GefSetting("", str, "Offset from libc base address to main_arena symbol (int or hex). set to empty string to disable.") gef.config["gef.propagate_debug_exception"] = GefSetting(False, bool, "If true, when debug mode is enabled, Python exceptions will be propagated all the way.") gef.config["gef.extra_python_package_paths"] = GefSetting("", str, "Semi-colon separate list of extra paths to include for python packages") @@ -9923,7 +9923,7 @@ def __init__(self) -> None: @property @deprecated() - def loaded_commands(self) -> list[Tuple[str, Type[GenericCommand], Any]]: + def loaded_commands(self) -> list[tuple[str, Type[GenericCommand], Any]]: raise ObsoleteException("Obsolete loaded_commands") @property @@ -10115,7 +10115,7 @@ def __rebuild(self) -> None: self.should_refresh = False return - def __add__(self, command: Tuple[str, GenericCommand]): + def __add__(self, command: tuple[str, GenericCommand]): """Add command to GEF documentation.""" cmd, class_obj = command if " " in cmd: @@ -10127,7 +10127,7 @@ def __add__(self, command: Tuple[str, GenericCommand]): self.docs.append(msg) return self - def __radd__(self, command: Tuple[str, GenericCommand]): + def __radd__(self, command: tuple[str, GenericCommand]): return self.__add__(command) def __str__(self) -> str: @@ -10460,7 +10460,7 @@ def invoke(self, args: Any, from_tty: bool) -> None: gdb.execute(f"{self.command} {args}", from_tty=from_tty) return - def lookup_command(self, cmd: str) -> Optional[Tuple[str, GenericCommand]]: + def lookup_command(self, cmd: str) -> Optional[tuple[str, GenericCommand]]: global gef for _name, _instance in gef.gdb.commands.items(): if cmd == _name: @@ -11235,8 +11235,8 @@ def __init__(self) -> None: self.remote_initializing: bool = False self.qemu_mode: bool = False self.convenience_vars_index: int = 0 - self.heap_allocated_chunks: list[Tuple[int, int]] = [] - self.heap_freed_chunks: list[Tuple[int, int]] = [] + self.heap_allocated_chunks: list[tuple[int, int]] = [] + self.heap_freed_chunks: list[tuple[int, int]] = [] self.heap_uaf_watchpoints: list[UafWatchpoint] = [] self.pie_breakpoints: dict[int, PieVirtualBreakpoint] = {} self.pie_counter: int = 1 @@ -11333,7 +11333,7 @@ def pagesize(self) -> int: return self._pagesize @property - def canary(self) -> Optional[Tuple[int, int]]: + def canary(self) -> Optional[tuple[int, int]]: """Return a tuple of the canary address and value, read from the canonical location if supported by the architecture. Otherwise, read from the auxiliary vector.""" @@ -11347,7 +11347,7 @@ def canary(self) -> Optional[Tuple[int, int]]: return canary, canary_location @property - def original_canary(self) -> Optional[Tuple[int, int]]: + def original_canary(self) -> Optional[tuple[int, int]]: """Return a tuple of the initial canary address and value, read from the auxiliary vector.""" auxval = self.auxiliary_vector @@ -11616,8 +11616,8 @@ def __init__(self) -> None: self.context_hidden = False self.stream_buffer : Optional[StringIO] = None self.highlight_table: dict[str, str] = {} - self.watches: dict[int, Tuple[int, str]] = {} - self.context_messages: list[Tuple[str, str]] = [] + self.watches: dict[int, tuple[int, str]] = {} + self.context_messages: list[tuple[str, str]] = [] return @@ -11627,7 +11627,7 @@ class GefLibcManager(GefManager): PATTERN_LIBC_VERSION_FILENAME = re.compile(r"libc6?[-_](\d+)\.(\d+)\.so") def __init__(self) -> None: - self._version : Optional[Tuple[int, int]] = None + self._version : Optional[tuple[int, int]] = None self._patch: Optional[int] = None self._release: Optional[str] = None return @@ -11636,7 +11636,7 @@ def __str__(self) -> str: return f"Libc(version='{self.version}')" @property - def version(self) -> Optional[Tuple[int, int]]: + def version(self) -> Optional[tuple[int, int]]: if not is_alive(): return None @@ -11654,7 +11654,7 @@ def version(self) -> Optional[Tuple[int, int]]: @staticmethod @lru_cache() - def find_libc_version() -> Tuple[int, int]: + def find_libc_version() -> tuple[int, int]: """Attempt to determine the libc version. This operation can be long.""" libc_sections = (m for m in gef.memory.maps if "libc" in m.path and m.permission & Permission.READ) for section in libc_sections: From 32311d7a910bd26f96347791617bc12e296d6c3c Mon Sep 17 00:00:00 2001 From: hugsy Date: Fri, 1 Nov 2024 13:24:16 -0700 Subject: [PATCH 04/53] `Union` type -> `|` --- gef.py | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/gef.py b/gef.py index bb6054ace..97b6d02c2 100644 --- a/gef.py +++ b/gef.py @@ -83,8 +83,8 @@ from io import StringIO, TextIOWrapper from types import ModuleType from typing import (Any, ByteString, Callable, Generator, Iterable, - Iterator, NoReturn, Optional, Sequence, Set, Tuple, Type, TypeVar, - Union, cast) + Iterator, NoReturn, Optional, Sequence, Type, TypeVar, + cast) from urllib.request import urlopen @@ -146,7 +146,7 @@ def update_gef(argv: list[str]) -> int: __registered_commands__ : set[Type["GenericCommand"]] = set() __registered_functions__ : set[Type["GenericFunction"]] = set() -__registered_architectures__ : dict[Union["Elf.Abi", str], Type["Architecture"]] = {} +__registered_architectures__ : dict["Elf.Abi" | str, Type["Architecture"]] = {} __registered_file_formats__ : set[ Type["FileFormat"] ] = set() GefMemoryMapProvider = Callable[[], Generator["Section", None, None]] @@ -446,8 +446,8 @@ def FakeExit(*args: Any, **kwargs: Any) -> NoReturn: sys.exit = FakeExit -def parse_arguments(required_arguments: dict[Union[str, tuple[str, str]], Any], - optional_arguments: dict[Union[str, tuple[str, str]], Any]) -> Callable: +def parse_arguments(required_arguments: dict[str | tuple[str, str], Any], + optional_arguments: dict[str | tuple[str, str], Any]) -> Callable: """Argument parsing decorator.""" def int_wrapper(x: str) -> int: return int(x, 0) @@ -799,7 +799,7 @@ class FileFormat: checksec: dict[str, bool] sections: list[FileFormatSection] - def __init__(self, path: Union[str, pathlib.Path]) -> None: + def __init__(self, path: str | pathlib.Path) -> None: raise NotImplementedError def __init_subclass__(cls: Type["FileFormat"], **kwargs): @@ -892,7 +892,7 @@ class OsAbi(enum.Enum): __checksec : dict[str, bool] - def __init__(self, path: Union[str, pathlib.Path]) -> None: + def __init__(self, path: str | pathlib.Path) -> None: """Instantiate an ELF object. A valid ELF must be provided, or an exception will be thrown.""" if isinstance(path, str): @@ -1314,7 +1314,7 @@ class heap_info_cls(ctypes.Structure): heap_info_cls._fields_ = fields return heap_info_cls - def __init__(self, addr: Union[str, int]) -> None: + def __init__(self, addr: str | int) -> None: self.__address : int = parse_address(f"&{addr}") if isinstance(addr, str) else addr self.reset() return @@ -2236,7 +2236,7 @@ def gef_disassemble(addr: int, nb_insn: int, nb_prev: int = 0) -> Generator[Inst yield insn -def gef_execute_external(command: Sequence[str], as_list: bool = False, **kwargs: Any) -> Union[str, list[str]]: +def gef_execute_external(command: Sequence[str], as_list: bool = False, **kwargs: Any) -> str | list[str]: """Execute an external command and return the result.""" res = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=kwargs.get("shell", False)) return [gef_pystring(_) for _ in res.splitlines()] if as_list else gef_pystring(res) @@ -2340,7 +2340,7 @@ def register_architecture(cls: Type["Architecture"]) -> Type["Architecture"]: class ArchitectureBase: """Class decorator for declaring an architecture to GEF.""" - aliases: Union[tuple[()], tuple[Union[str, Elf.Abi], ...]] = () + aliases: tuple[()] | tuple[str | Elf.Abi | ...] = () def __init_subclass__(cls: Type["ArchitectureBase"], **kwargs): global __registered_architectures__ @@ -2360,20 +2360,20 @@ class Architecture(ArchitectureBase): # Mandatory defined attributes by inheriting classes arch: str mode: str - all_registers: Union[tuple[()], tuple[str, ...]] + all_registers: tuple[()] | tuple[str, ...] nop_insn: bytes return_register: str flag_register: Optional[str] instruction_length: Optional[int] flags_table: dict[int, str] syscall_register: Optional[str] - syscall_instructions: Union[tuple[()], tuple[str, ...]] - function_parameters: Union[tuple[()], tuple[str, ...]] + syscall_instructions: tuple[()] | tuple[str, ...] + function_parameters: tuple[()] | tuple[str, ...] # Optionally defined attributes _ptrsize: Optional[int] = None _endianness: Optional[Endianness] = None - special_registers: Union[tuple[()], tuple[str, ...]] = () + special_registers: tuple[()] | tuple[str, ...] = () maps: Optional[GefMemoryMapProvider] = None def __init_subclass__(cls, **kwargs): @@ -2914,7 +2914,7 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: class X86(Architecture): - aliases: tuple[Union[str, Elf.Abi], ...] = ("X86", Elf.Abi.X86_32) + aliases: tuple[str | Elf.Abi, ...] = ("X86", Elf.Abi.X86_32) arch = "X86" mode = "32" @@ -3358,7 +3358,7 @@ def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: class MIPS(Architecture): - aliases: tuple[Union[str, Elf.Abi], ...] = ("MIPS", Elf.Abi.MIPS) + aliases: tuple[str | Elf.Abi, ...] = ("MIPS", Elf.Abi.MIPS) arch = "MIPS" mode = "MIPS32" @@ -4026,7 +4026,7 @@ def dereference(addr: int) -> Optional["gdb.Value"]: return None -def gef_convenience(value: Union[str, bytes]) -> str: +def gef_convenience(value: str | bytes) -> str: """Defines a new convenience value.""" global gef var_name = f"$_gef{gef.session.convenience_vars_index:d}" @@ -4048,7 +4048,7 @@ def parse_string_range(s: str) -> Iterator[int]: @lru_cache() -def is_syscall(instruction: Union[Instruction,int]) -> bool: +def is_syscall(instruction: Instruction | int) -> bool: """Checks whether an instruction or address points to a system call.""" if isinstance(instruction, int): instruction = gef_current_instruction(instruction) @@ -4214,7 +4214,7 @@ def __init__(self, set_func: Callable[[int], str], vbp_num: int, addr: int) -> N self.bp_addr = 0 # this address might be a symbol, just to know where to break if isinstance(addr, int): - self.addr: Union[int, str] = hex(addr) + self.addr: int | str = hex(addr) else: self.addr = addr return @@ -4664,7 +4664,7 @@ def register_priority_command(cls: Type["GenericCommand"]) -> Type["GenericComma ValidCommandType = TypeVar("ValidCommandType", bound="GenericCommand") ValidFunctionType = TypeVar("ValidFunctionType", bound="GenericFunction") -def register(cls: Union[Type["ValidCommandType"], Type["ValidFunctionType"]]) -> Union[Type["ValidCommandType"], Type["ValidFunctionType"]]: +def register(cls: Type["ValidCommandType"] | Type["ValidFunctionType"]) -> Type["ValidCommandType"] | Type["ValidFunctionType"]: global __registered_commands__, __registered_functions__ if issubclass(cls, GenericCommand): assert hasattr(cls, "_cmdline_") @@ -4690,7 +4690,7 @@ class GenericCommand(gdb.Command): _cmdline_: str _syntax_: str - _example_: Union[str, list[str]] = "" + _example_: str | list[str] = "" _aliases_: list[str] = [] def __init_subclass__(cls, **kwargs): @@ -4781,7 +4781,7 @@ def __contains__(self, name: str) -> bool: def add_setting(self, name: str, value: tuple[Any, type, str], description: str = "") -> None: return self.__setitem__(name, (value, description)) - def __setitem__(self, name: str, value: Union["GefSetting", tuple[Any, str]]) -> None: + def __setitem__(self, name: str, value: "GefSetting" | tuple[Any, str]) -> None: # make sure settings are always associated to the root command (which derives from GenericCommand) if "GenericCommand" not in [x.__name__ for x in self.__class__.__bases__]: return @@ -6283,6 +6283,7 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: return # qemu-user support + qemu_binary = None if args.qemu_user: dbg("Setting up qemu-user session") try: From 741ce7cb846d3c8de74dd211f8cf699817d0fd08 Mon Sep 17 00:00:00 2001 From: hugsy Date: Fri, 1 Nov 2024 13:39:20 -0700 Subject: [PATCH 05/53] removed Tuple in tests --- tests/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/base.py b/tests/base.py index fc209be0c..868923d3a 100644 --- a/tests/base.py +++ b/tests/base.py @@ -5,7 +5,7 @@ import subprocess import tempfile import time -from typing import Tuple + import unittest import rpyc @@ -109,9 +109,9 @@ def tearDown(self) -> None: return super().tearDown() @property - def gdb_version(self) -> Tuple[int, int]: + def gdb_version(self) -> tuple[int, int]: res = re.search(r"(\d+)\D(\d+)", self._gdb.VERSION) assert res groups = [int(d) for d in res.groups()] assert len(groups) == 2 - return (groups[0], groups[1]) + return groups[0], groups[1] From f47f51e1c5126e03d7482e6bfdceeeac8e04e36f Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 2 Nov 2024 12:39:13 -0700 Subject: [PATCH 06/53] fixed type typo --- gef.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gef.py b/gef.py index 0dfcbc0ac..854076a15 100644 --- a/gef.py +++ b/gef.py @@ -146,8 +146,8 @@ def update_gef(argv: list[str]) -> int: __registered_commands__ : set[Type["GenericCommand"]] = set() __registered_functions__ : set[Type["GenericFunction"]] = set() -__registered_architectures__ : dict["Elf.Abi" | str, Type["Architecture"]] = {} -__registered_file_formats__ : set[ Type["FileFormat"] ] = set() +__registered_architectures__ : dict["Elf.Abi | str", Type["Architecture"]] = {} +__registered_file_formats__ : set[ Type["FileFormat"] ] = set() GefMemoryMapProvider = Callable[[], Generator["Section", None, None]] @@ -2337,7 +2337,7 @@ def register_architecture(cls: Type["Architecture"]) -> Type["Architecture"]: class ArchitectureBase: """Class decorator for declaring an architecture to GEF.""" - aliases: tuple[()] | tuple[str | Elf.Abi | ...] = () + aliases: tuple[str | Elf.Abi, ...] def __init_subclass__(cls: Type["ArchitectureBase"], **kwargs): global __registered_architectures__ @@ -8895,15 +8895,15 @@ class VMMapCommand(GenericCommand): @only_if_gdb_running @parse_arguments({"unknown_types": [""]}, {("--addr", "-a"): [""], ("--name", "-n"): [""]}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] vmmap = gef.memory.maps if not vmmap: err("No address mapping information found") return - addrs: Dict[str, int] = {x: parse_address(x) for x in args.addr} - names: List[str] = [x for x in args.name] + addrs: dict[str, int] = {x: parse_address(x) for x in args.addr} + names: list[str] = [x for x in args.name] for arg in args.unknown_types: if not arg: From d5d544db2fdc01fb38bd5fd4c07fd73afaebfd75 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 2 Nov 2024 13:28:54 -0700 Subject: [PATCH 07/53] `Optional` is optional --- gef.py | 319 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 154 insertions(+), 165 deletions(-) diff --git a/gef.py b/gef.py index 854076a15..af1151296 100644 --- a/gef.py +++ b/gef.py @@ -82,16 +82,15 @@ from functools import lru_cache from io import StringIO, TextIOWrapper from types import ModuleType -from typing import (Any, ByteString, Callable, Generator, Iterable, - Iterator, NoReturn, Optional, Sequence, Type, TypeVar, - cast) +from typing import (Any, ByteString, Callable, Generator, Iterable, Iterator, + NoReturn, Sequence, Type, TypeVar, cast) from urllib.request import urlopen GEF_DEFAULT_BRANCH = "main" GEF_EXTRAS_DEFAULT_BRANCH = "main" -def http_get(url: str) -> Optional[bytes]: +def http_get(url: str) -> bytes | None: """Basic HTTP wrapper for GET request. Return the body of the page if HTTP code is OK, otherwise return None.""" try: @@ -284,49 +283,49 @@ class ObsoleteException(Exception): pass class AlreadyRegisteredException(Exception): pass -def p8(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes: +def p8(x: int, s: bool = False, e: "Endianness | None" = None) -> bytes: """Pack one byte respecting the current architecture endianness.""" endian = e or gef.arch.endianness return struct.pack(f"{endian}B", x) if not s else struct.pack(f"{endian:s}b", x) -def p16(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes: +def p16(x: int, s: bool = False, e: "Endianness | None" = None) -> bytes: """Pack one word respecting the current architecture endianness.""" endian = e or gef.arch.endianness return struct.pack(f"{endian}H", x) if not s else struct.pack(f"{endian:s}h", x) -def p32(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes: +def p32(x: int, s: bool = False, e: "Endianness | None" = None) -> bytes: """Pack one dword respecting the current architecture endianness.""" endian = e or gef.arch.endianness return struct.pack(f"{endian}I", x) if not s else struct.pack(f"{endian:s}i", x) -def p64(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes: +def p64(x: int, s: bool = False, e: "Endianness | None" = None) -> bytes: """Pack one qword respecting the current architecture endianness.""" endian = e or gef.arch.endianness return struct.pack(f"{endian}Q", x) if not s else struct.pack(f"{endian:s}q", x) -def u8(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int: +def u8(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int: """Unpack one byte respecting the current architecture endianness.""" endian = e or gef.arch.endianness return struct.unpack(f"{endian}B", x)[0] if not s else struct.unpack(f"{endian:s}b", x)[0] -def u16(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int: +def u16(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int: """Unpack one word respecting the current architecture endianness.""" endian = e or gef.arch.endianness return struct.unpack(f"{endian}H", x)[0] if not s else struct.unpack(f"{endian:s}h", x)[0] -def u32(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int: +def u32(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int: """Unpack one dword respecting the current architecture endianness.""" endian = e or gef.arch.endianness return struct.unpack(f"{endian}I", x)[0] if not s else struct.unpack(f"{endian:s}i", x)[0] -def u64(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int: +def u64(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int: """Unpack one qword respecting the current architecture endianness.""" endian = e or gef.arch.endianness return struct.unpack(f"{endian}Q", x)[0] if not s else struct.unpack(f"{endian:s}q", x)[0] @@ -348,7 +347,7 @@ def is_alive() -> bool: return False -def calling_function() -> Optional[str]: +def calling_function() -> str | None: """Return the name of the calling function""" try: stack_info = traceback.extract_stack()[-3] @@ -452,7 +451,7 @@ def parse_arguments(required_arguments: dict[str | tuple[str, str], Any], def int_wrapper(x: str) -> int: return int(x, 0) - def decorator(f: Callable) -> Optional[Callable]: + def decorator(f: Callable) -> Callable | None: def wrapper(*args: Any, **kwargs: Any) -> Callable: parser = argparse.ArgumentParser(prog=args[0]._cmdline_, add_help=True) for argname in required_arguments: @@ -606,7 +605,7 @@ def is_in_stack_segment(self) -> bool: def is_in_heap_segment(self) -> bool: return hasattr(self.section, "path") and "[heap]" == self.section.path - def dereference(self) -> Optional[int]: + def dereference(self) -> int | None: addr = align_address(int(self.value)) derefed = dereference(addr) return None if derefed is None else int(derefed) @@ -693,7 +692,7 @@ def size(self) -> int: raise AttributeError return self.page_end - self.page_start - def _search_for_realpath_without_versions(self, path: pathlib.Path) -> Optional[str]: + def _search_for_realpath_without_versions(self, path: pathlib.Path) -> str | None: """Given a path, search for a file that exists without numeric suffixes.""" # Match the path string against a regex that will remove a suffix @@ -708,7 +707,7 @@ def _search_for_realpath_without_versions(self, path: pathlib.Path) -> Optional[ candidate = re.match(r"^(.*)\.(\d*)$", candidate) return None - def _search_for_realpath(self) -> Optional[str]: + def _search_for_realpath(self) -> str | None: """This function is a workaround for gdb bug #23764 path might be wrong for remote sessions, so try a simple search for files @@ -1199,7 +1198,7 @@ def _missing_(cls, _:int): sh_entsize: int name: str - def __init__(self, elf: Optional[Elf], off: int) -> None: + def __init__(self, elf: Elf | None, off: int) -> None: if elf is None: return elf.seek(off) @@ -1516,7 +1515,7 @@ def system_mem(self) -> int: def max_system_mem(self) -> int: return self.__arena.max_system_mem - def fastbin(self, i: int) -> Optional["GlibcFastChunk"]: + def fastbin(self, i: int) -> "GlibcFastChunk | None": """Return head chunk in fastbinsY[i].""" addr = int(self.fastbinsY[i]) if addr == 0: @@ -1537,7 +1536,7 @@ def bin_at(self, i) -> int: def is_main_arena(self) -> bool: return gef.heap.main_arena is not None and int(self) == int(gef.heap.main_arena) - def heap_addr(self, allow_unaligned: bool = False) -> Optional[int]: + def heap_addr(self, allow_unaligned: bool = False) -> int | None: if self.is_main_arena(): heap_section = gef.heap.base_address if not heap_section: @@ -1548,7 +1547,7 @@ def heap_addr(self, allow_unaligned: bool = False) -> Optional[int]: return _addr return gef.heap.malloc_align_address(_addr) - def get_heap_info_list(self) -> Optional[list[GlibcHeapInfo]]: + def get_heap_info_list(self) -> list[GlibcHeapInfo] | None: if self.is_main_arena(): return None heap_addr = self.get_heap_for_ptr(self.top) @@ -1827,7 +1826,7 @@ class GlibcTcacheChunk(GlibcFastChunk): def get_libc_version() -> tuple[int, ...]: return GefLibcManager.find_libc_version() -def titlify(text: str, color: Optional[str] = None, msg_color: Optional[str] = None) -> str: +def titlify(text: str, color: str | None = None, msg_color: str | None = None) -> str: """Print a centered title.""" _, cols = get_terminal_size() nb = (cols - len(text) - 2) // 2 @@ -2088,7 +2087,7 @@ def gef_makedirs(path: str, mode: int = 0o755) -> pathlib.Path: @lru_cache() -def gdb_lookup_symbol(sym: str) -> Optional[tuple[gdb.Symtab_and_line, ...]]: +def gdb_lookup_symbol(sym: str) -> tuple[gdb.Symtab_and_line, ...] | None: """Fetch the proper symbol or None if not defined.""" try: res = gdb.decode_line(sym)[1] # pylint: disable=E1136 @@ -2097,7 +2096,7 @@ def gdb_lookup_symbol(sym: str) -> Optional[tuple[gdb.Symtab_and_line, ...]]: return None @lru_cache(maxsize=512) -def gdb_get_location_from_symbol(address: int) -> Optional[tuple[str, int]]: +def gdb_get_location_from_symbol(address: int) -> tuple[str, int] | None: """Retrieve the location of the `address` argument from the symbol table. Return a tuple with the name and offset if found, None otherwise.""" # this is horrible, ugly hack and shitty perf... @@ -2148,7 +2147,7 @@ def gdb_disassemble(start_pc: int, **kwargs: int) -> Generator[Instruction, None yield Instruction(address, location, mnemo, operands, opcodes) -def gdb_get_nth_previous_instruction_address(addr: int, n: int) -> Optional[int]: +def gdb_get_nth_previous_instruction_address(addr: int, n: int) -> int | None: """Return the address (Integer) of the `n`-th instruction before `addr`.""" # fixed-length ABI if gef.arch.instruction_length: @@ -2288,7 +2287,7 @@ def get_arch() -> str: @deprecated("Use `gef.binary.entry_point` instead") -def get_entry_point() -> Optional[int]: +def get_entry_point() -> int | None: """Return the binary entry point.""" return gef.binary.entry_point if gef.binary else None @@ -2316,13 +2315,13 @@ def flags_to_human(reg_value: int, value_table: dict[int, str]) -> str: @lru_cache() -def get_section_base_address(name: str) -> Optional[int]: +def get_section_base_address(name: str) -> int | None: section = process_lookup_path(name) return section.page_start if section else None @lru_cache() -def get_zone_base_address(name: str) -> Optional[int]: +def get_zone_base_address(name: str) -> int | None: zone = file_lookup_name_path(name, get_filepath()) return zone.zone_start if zone else None @@ -2337,7 +2336,7 @@ def register_architecture(cls: Type["Architecture"]) -> Type["Architecture"]: class ArchitectureBase: """Class decorator for declaring an architecture to GEF.""" - aliases: tuple[str | Elf.Abi, ...] + aliases: tuple[str | Elf.Abi, ...] = () def __init_subclass__(cls: Type["ArchitectureBase"], **kwargs): global __registered_architectures__ @@ -2357,21 +2356,21 @@ class Architecture(ArchitectureBase): # Mandatory defined attributes by inheriting classes arch: str mode: str - all_registers: tuple[()] | tuple[str, ...] + all_registers: tuple[str, ...] nop_insn: bytes return_register: str - flag_register: Optional[str] - instruction_length: Optional[int] + flag_register: str | None + instruction_length: int | None flags_table: dict[int, str] - syscall_register: Optional[str] - syscall_instructions: tuple[()] | tuple[str, ...] - function_parameters: tuple[()] | tuple[str, ...] + syscall_register: str | None + syscall_instructions: tuple[str, ...] + function_parameters: tuple[str, ...] # Optionally defined attributes - _ptrsize: Optional[int] = None - _endianness: Optional[Endianness] = None + _ptrsize: int | None = None + _endianness: Endianness | None = None special_registers: tuple[()] | tuple[str, ...] = () - maps: Optional[GefMemoryMapProvider] = None + maps: GefMemoryMapProvider | None = None def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) @@ -2388,13 +2387,13 @@ def __repr__(self) -> str: return self.__str__() @staticmethod - def supports_gdb_arch(gdb_arch: str) -> Optional[bool]: + def supports_gdb_arch(gdb_arch: str) -> bool | None: """If implemented by a child `Architecture`, this function dictates if the current class supports the loaded ELF file (which can be accessed via `gef.binary`). This callback function will override any assumption made by GEF to determine the architecture.""" return None - def flag_register_to_human(self, val: Optional[int] = None) -> str: + def flag_register_to_human(self, val: int | None = None) -> str: raise NotImplementedError def is_call(self, insn: Instruction) -> bool: @@ -2409,7 +2408,7 @@ def is_conditional_branch(self, insn: Instruction) -> bool: def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: raise NotImplementedError - def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: raise NotImplementedError def canary_address(self) -> int: @@ -2485,7 +2484,7 @@ def endianness(self) -> Endianness: raise OSError(f"No valid endianess found in '{output}'") return self._endianness - def get_ith_parameter(self, i: int, in_func: bool = True) -> tuple[str, Optional[int]]: + def get_ith_parameter(self, i: int, in_func: bool = True) -> tuple[str, int | None]: """Retrieves the correct parameter used for the current function call.""" reg = self.function_parameters[i] val = self.register(reg) @@ -2616,7 +2615,7 @@ def long_to_twos_complement(v: int) -> int: return taken, reason - def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: ra = None if self.is_ret(insn): ra = gef.arch.register("$ra") @@ -2626,7 +2625,7 @@ def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: ra = to_unsigned_long(older.pc()) return ra - def flag_register_to_human(self, val: Optional[int] = None) -> str: + def flag_register_to_human(self, val: int | None = None) -> str: # RISC-V has no flags registers, return an empty string to # preserve the Architecture API return "" @@ -2660,7 +2659,7 @@ def is_thumb(self) -> bool: return is_alive() and (self.cpsr & (1 << 5) == 1) @property - def pc(self) -> Optional[int]: + def pc(self) -> int | None: pc = gef.arch.register("$pc") if self.is_thumb(): pc += 1 @@ -2677,7 +2676,7 @@ def mode(self) -> str: return "THUMB" if self.is_thumb() else "ARM" @property - def instruction_length(self) -> Optional[int]: + def instruction_length(self) -> int | None: # Thumb instructions have variable-length (2 or 4-byte) return None if self.is_thumb() else 4 @@ -2702,7 +2701,7 @@ def is_ret(self, insn: Instruction) -> bool: return insn.operands[0] == "pc" return False - def flag_register_to_human(self, val: Optional[int] = None) -> str: + def flag_register_to_human(self, val: int | None = None) -> str: # https://www.botskool.com/user-pages/tutorials/electronics/arm-7-tutorial-part-1 if val is None: reg = self.flag_register @@ -2746,7 +2745,7 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: elif mnemo.endswith("cc"): taken, reason = not val&(1< Optional[int]: + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: if not self.is_ret(insn): older = frame.older() if not older: @@ -2822,7 +2821,7 @@ def is_call(self, insn: Instruction) -> bool: call_mnemos = {"bl", "blr"} return mnemo in call_mnemos - def flag_register_to_human(self, val: Optional[int] = None) -> str: + def flag_register_to_human(self, val: int | None = None) -> str: # https://events.linuxfoundation.org/sites/events/files/slides/KoreaLinuxForum-2014.pdf reg = self.flag_register if not val: @@ -2942,7 +2941,7 @@ class X86(Architecture): _ptrsize = 4 _endianness = Endianness.LITTLE_ENDIAN - def flag_register_to_human(self, val: Optional[int] = None) -> str: + def flag_register_to_human(self, val: int | None = None) -> str: reg = self.flag_register if val is None: val = gef.arch.register(reg) @@ -3011,7 +3010,7 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: taken, reason = not val&(1< Optional[int]: + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: ra = None if self.is_ret(insn): ra = dereference(gef.arch.sp) @@ -3039,7 +3038,7 @@ def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: ] return "; ".join(insns) - def get_ith_parameter(self, i: int, in_func: bool = True) -> tuple[str, Optional[int]]: + def get_ith_parameter(self, i: int, in_func: bool = True) -> tuple[str, int | None]: if in_func: i += 1 # Account for RA being at the top of the stack sp = gef.arch.sp @@ -3128,7 +3127,7 @@ class PowerPC(Architecture): _ptrsize = 4 - def flag_register_to_human(self, val: Optional[int] = None) -> str: + def flag_register_to_human(self, val: int | None = None) -> str: # https://www.cebix.net/downloads/bebox/pem32b.pdf (% 2.1.3) if val is None: reg = self.flag_register @@ -3159,7 +3158,7 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: elif mnemo == "bgt": taken, reason = bool(val&(1< Optional[int]: + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: ra = None if self.is_ret(insn): ra = gef.arch.register("$lr") @@ -3232,7 +3231,7 @@ class SPARC(Architecture): syscall_register = "%g1" syscall_instructions = ("t 0x10",) - def flag_register_to_human(self, val: Optional[int] = None) -> str: + def flag_register_to_human(self, val: int | None = None) -> str: # https://www.gaisler.com/doc/sparcv8.pdf reg = self.flag_register if val is None: @@ -3278,7 +3277,7 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: elif mnemo == "bcc": taken, reason = val&(1< Optional[int]: + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: ra = None if self.is_ret(insn): ra = gef.arch.register("$o7") @@ -3376,7 +3375,7 @@ class MIPS(Architecture): syscall_register = "$v0" syscall_instructions = ("syscall",) - def flag_register_to_human(self, val: Optional[int] = None) -> str: + def flag_register_to_human(self, val: int | None = None) -> str: return Color.colorify("No flag register", "yellow underline") def is_call(self, insn: Instruction) -> bool: @@ -3412,7 +3411,7 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: taken, reason = gef.arch.register(ops[0]) <= 0, f"{ops[0]} <= 0" return taken, reason - def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: ra = None if self.is_ret(insn): ra = gef.arch.register("$ra") @@ -3446,7 +3445,7 @@ class MIPS64(MIPS): _ptrsize = 8 @staticmethod - def supports_gdb_arch(gdb_arch: str) -> Optional[bool]: + def supports_gdb_arch(gdb_arch: str) -> bool | None: if not gef.binary or not isinstance(gef.binary, Elf): return False return gdb_arch.startswith("mips") and gef.binary.e_class == Elf.Class.ELF_64_BITS @@ -3501,7 +3500,7 @@ def to_unsigned_long(v: gdb.Value) -> int: return int(v.cast(gdb.Value(mask).type)) & mask -def get_path_from_info_proc() -> Optional[str]: +def get_path_from_info_proc() -> str | None: for x in (gdb.execute("info proc", to_string=True) or "").splitlines(): if x.startswith("exe = "): return x.split(" = ")[1].replace("'", "") @@ -3537,7 +3536,7 @@ def is_qemu_system() -> bool: return "received: \"\"" in response -def get_filepath() -> Optional[str]: +def get_filepath() -> str | None: """Return the local absolute path of the file currently debugged.""" if gef.session.remote: return str(gef.session.remote.lfile.absolute()) @@ -3581,7 +3580,7 @@ def get_info_files() -> list[Zone]: return infos -def process_lookup_address(address: int) -> Optional[Section]: +def process_lookup_address(address: int) -> Section | None: """Look up for an address in memory. Return an Address object if found, None otherwise.""" if not is_alive(): @@ -3600,7 +3599,7 @@ def process_lookup_address(address: int) -> Optional[Section]: @lru_cache() -def process_lookup_path(name: str, perm: Permission = Permission.ALL) -> Optional[Section]: +def process_lookup_path(name: str, perm: Permission = Permission.ALL) -> Section | None: """Look up for a path in the process memory mapping. Return a Section object if found, None otherwise.""" if not is_alive(): @@ -3632,7 +3631,7 @@ def process_lookup_path(name: str, perm: Permission = Permission.ALL) -> Optiona @lru_cache() -def file_lookup_name_path(name: str, path: str) -> Optional[Zone]: +def file_lookup_name_path(name: str, path: str) -> Zone | None: """Look up a file by name and path. Return a Zone object if found, None otherwise.""" for xfile in get_info_files(): @@ -3642,7 +3641,7 @@ def file_lookup_name_path(name: str, path: str) -> Optional[Zone]: @lru_cache() -def file_lookup_address(address: int) -> Optional[Zone]: +def file_lookup_address(address: int) -> Zone | None: """Look up for a file by its address. Return a Zone object if found, None otherwise.""" for info in get_info_files(): @@ -3688,7 +3687,7 @@ def hook_stop_handler(_: "gdb.StopEvent") -> None: return -def new_objfile_handler(evt: Optional["gdb.NewObjFileEvent"]) -> None: +def new_objfile_handler(evt: "gdb.NewObjFileEvent | None") -> None: """GDB event handler for new object file cases.""" reset_all_caches() progspace = gdb.current_progspace() @@ -3828,7 +3827,7 @@ def is_arch(arch: Elf.Abi) -> bool: return arch in gef.arch.aliases -def reset_architecture(arch: Optional[str] = None) -> None: +def reset_architecture(arch: str | None = None) -> None: """Sets the current architecture. If an architecture is explicitly specified by parameter, try to use that one. If this fails, an `OSError` exception will occur. @@ -3869,7 +3868,7 @@ def reset_architecture(arch: Optional[str] = None) -> None: @lru_cache() -def cached_lookup_type(_type: str) -> Optional[gdb.Type]: +def cached_lookup_type(_type: str) -> gdb.Type | None: try: return gdb.lookup_type(_type).strip_typedefs() except RuntimeError: @@ -3993,7 +3992,7 @@ def generate_cyclic_pattern(length: int, cycle: int = 4) -> bytearray: return bytearray(itertools.islice(de_bruijn(charset, cycle), length)) -def safe_parse_and_eval(value: str) -> Optional["gdb.Value"]: +def safe_parse_and_eval(value: str) -> "gdb.Value | None": """GEF wrapper for gdb.parse_and_eval(): this function returns None instead of raising gdb.error if the eval failed.""" try: @@ -4004,7 +4003,7 @@ def safe_parse_and_eval(value: str) -> Optional["gdb.Value"]: @lru_cache() -def dereference(addr: int) -> Optional["gdb.Value"]: +def dereference(addr: int) -> "gdb.Value | None": """GEF wrapper for gdb dereference function.""" try: ulong_t = cached_lookup_type(use_stdtype()) or \ @@ -4086,7 +4085,7 @@ def gef_getpagesize() -> int: @deprecated("Use `gef.session.canary`") -def gef_read_canary() -> Optional[tuple[int, int]]: +def gef_read_canary() -> tuple[int, int] | None: return gef.session.canary @@ -4102,12 +4101,12 @@ def get_filename() -> str: @deprecated("Use `gef.heap.main_arena`") -def get_glibc_arena() -> Optional[GlibcArena]: +def get_glibc_arena() -> GlibcArena | None: return gef.heap.main_arena @deprecated("Use `gef.arch.register(regname)`") -def get_register(regname) -> Optional[int]: +def get_register(regname) -> int | None: return gef.arch.register(regname) @@ -4117,7 +4116,7 @@ def get_process_maps() -> list[Section]: @deprecated("Use `reset_architecture`") -def set_arch(arch: Optional[str] = None, _: Optional[str] = None) -> None: +def set_arch(arch: str | None = None, _: str | None = None) -> None: return reset_architecture(arch) # @@ -4282,7 +4281,7 @@ def stop(self) -> bool: class StubBreakpoint(gdb.Breakpoint): """Create a breakpoint to permanently disable a call (fork/alarm/signal/etc.).""" - def __init__(self, func: str, retval: Optional[int]) -> None: + def __init__(self, func: str, retval: int | None) -> None: super().__init__(func, gdb.BP_BREAKPOINT, internal=False) self.func = func self.retval = retval @@ -4612,7 +4611,7 @@ def __init__(self, loc: str) -> None: # Context Panes # -def register_external_context_pane(pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], Optional[str]], condition : Optional[Callable[[], bool]] = None) -> None: +def register_external_context_pane(pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], str | None], condition : Callable[[], bool] | None = None) -> None: """ Registering function for new GEF Context View. pane_name: a string that has no spaces (used in settings) @@ -4633,7 +4632,7 @@ def pane_title(): gef.gdb.add_context_pane(pane_name, display_pane_function, pane_title_function, condition) return -def register_external_context_layout_mapping(current_pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], Optional[str]], condition : Optional[Callable[[], bool]] = None) -> None: +def register_external_context_layout_mapping(current_pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], str | None], condition : Callable[[], bool] | None = None) -> None: gef.gdb.add_context_layout_mapping(current_pane_name, display_pane_function, pane_title_function, condition) return @@ -4778,7 +4777,7 @@ def __contains__(self, name: str) -> bool: def add_setting(self, name: str, value: tuple[Any, type, str], description: str = "") -> None: return self.__setitem__(name, (value, description)) - def __setitem__(self, name: str, value: "GefSetting" | tuple[Any, str]) -> None: + def __setitem__(self, name: str, value: "GefSetting | tuple[Any, str]") -> None: # make sure settings are always associated to the root command (which derives from GenericCommand) if "GenericCommand" not in [x.__name__ for x in self.__class__.__bases__]: return @@ -5713,7 +5712,7 @@ def structures(self) -> Generator[tuple["ExternalStructureManager.Module", "Exte return @lru_cache() - def find(self, structure_name: str) -> Optional[tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"]]: + def find(self, structure_name: str) -> tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"] | None: """Return the module and structure for the given structure name; `None` if the structure name was not found.""" for module in self.modules.values(): if structure_name in module: @@ -6937,7 +6936,7 @@ def check_thread_ids(self, tids: list[int]) -> list[int]: existing_tids = set(t.num for t in gdb.selected_inferior().threads()) return list(set(tids) & existing_tids) - def tcachebin(self, tcache_base: int, i: int) -> tuple[Optional[GlibcTcacheChunk], int]: + def tcachebin(self, tcache_base: int, i: int) -> tuple[GlibcTcacheChunk | None, int]: """Return the head chunk in tcache[i] and the number of chunks in the bin.""" if i >= self.TCACHE_MAX_BINS: err("Incorrect index value, index value must be between 0 and " @@ -7594,7 +7593,7 @@ class ContextCommand(GenericCommand): _syntax_ = f"{_cmdline_} [legend|regs|stack|code|args|memory|source|trace|threads|extra]" _aliases_ = ["ctx",] - old_registers: dict[str, Optional[int]] = {} + old_registers: dict[str, int | None] = {} def __init__(self) -> None: super().__init__() @@ -7622,7 +7621,7 @@ def __init__(self) -> None: self["libc_args"] = (False, "[DEPRECATED - Unused] Show libc function call args description") self["libc_args_path"] = ("", "[DEPRECATED - Unused] Path to libc function call args json files, provided via gef-extras") - self.layout_mapping: dict[str, tuple[Callable, Optional[Callable], Optional[Callable]]] = { + self.layout_mapping: dict[str, tuple[Callable, Callable | None, Callable | None]] = { "legend": (self.show_legend, None, None), "regs": (self.context_regs, None, None), "stack": (self.context_stack, None, None), @@ -7705,7 +7704,7 @@ def do_invoke(self, argv: list[str]) -> None: disable_redirect_output() return - def context_title(self, m: Optional[str]) -> None: + def context_title(self, m: str | None) -> None: # allow for not displaying a title line if m is None: return @@ -7959,7 +7958,7 @@ def print_arguments_from_symbol(self, function_name: str, symbol: "gdb.Symbol") def print_guessed_arguments(self, function_name: str) -> None: """When no symbol, read the current basic block and look for "interesting" instructions.""" - def __get_current_block_start_address() -> Optional[int]: + def __get_current_block_start_address() -> int | None: pc = gef.arch.pc try: block = gdb.block_for_pc(pc) @@ -8411,7 +8410,7 @@ class HexdumpCommand(GenericCommand): def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION, prefix=True) self["always_show_ascii"] = (False, "If true, hexdump will always display the ASCII dump") - self.format: Optional[str] = None + self.format: str | None = None self.__last_target = "$sp" return @@ -8550,7 +8549,7 @@ class PatchCommand(GenericCommand): def __init__(self) -> None: super().__init__(prefix=True, complete=gdb.COMPLETE_LOCATION) - self.format: Optional[str] = None + self.format: str | None = None return @only_if_gdb_running @@ -9945,8 +9944,8 @@ def __init__(self) -> None: gef.config["gef.propagate_debug_exception"] = GefSetting(False, bool, "If true, when debug mode is enabled, Python exceptions will be propagated all the way.") gef.config["gef.extra_python_package_paths"] = GefSetting("", str, "Semi-colon separate list of extra paths to include for python packages") - self.commands : dict[str, GenericCommand] = collections.OrderedDict() - self.functions : dict[str, GenericFunction] = collections.OrderedDict() + self.commands : dict[str, GenericCommand] = {} + self.functions : dict[str, GenericFunction] = {} self.missing: dict[str, Exception] = {} return @@ -9981,7 +9980,7 @@ def setup(self) -> None: GefRestoreCommand() return - def load_extra_plugins(self, extra_plugins_dir: Optional[pathlib.Path] = None) -> int: + def load_extra_plugins(self, extra_plugins_dir: pathlib.Path | None = None) -> int: """Load the plugins from the gef-extras setting. Returns the number of new plugins added.""" def load_plugin(fpath: pathlib.Path) -> bool: try: @@ -10035,7 +10034,7 @@ def invoke(self, args: Any, from_tty: bool) -> None: gdb.execute("gef help") return - def add_context_layout_mapping(self, current_pane_name: str, display_pane_function: Callable, pane_title_function: Callable, condition: Optional[Callable]) -> None: + def add_context_layout_mapping(self, current_pane_name: str, display_pane_function: Callable, pane_title_function: Callable, condition: Callable | None) -> None: """Add a new context layout mapping.""" context = self.commands["context"] assert isinstance(context, ContextCommand) @@ -10043,7 +10042,7 @@ def add_context_layout_mapping(self, current_pane_name: str, display_pane_functi # overload the printing of pane title context.layout_mapping[current_pane_name] = (display_pane_function, pane_title_function, condition) - def add_context_pane(self, pane_name: str, display_pane_function: Callable, pane_title_function: Callable, condition: Optional[Callable]) -> None: + def add_context_pane(self, pane_name: str, display_pane_function: Callable, pane_title_function: Callable, condition: Callable | None) -> None: """Add a new context pane to ContextCommand.""" context = self.commands["context"] assert isinstance(context, ContextCommand) @@ -10489,7 +10488,7 @@ def invoke(self, args: Any, from_tty: bool) -> None: gdb.execute(f"{self.command} {args}", from_tty=from_tty) return - def lookup_command(self, cmd: str) -> Optional[tuple[str, GenericCommand]]: + def lookup_command(self, cmd: str) -> tuple[str, GenericCommand] | None: global gef for _name, _instance in gef.gdb.commands.items(): if cmd == _name: @@ -10740,10 +10739,10 @@ def __init__(self) -> None: def reset_caches(self) -> None: super().reset_caches() - self.__maps: Optional[list[Section]] = None + self.__maps: list[Section] | None = None return - def write(self, address: int, buffer: ByteString, length: Optional[int] = None) -> None: + def write(self, address: int, buffer: ByteString, length: int | None = None) -> None: """Write `buffer` at address `address`.""" length = length or len(buffer) gdb.selected_inferior().write_memory(address, buffer, length) @@ -10762,7 +10761,7 @@ def read_integer(self, addr: int) -> int: def read_cstring(self, address: int, max_length: int = GEF_MAX_STRING_LENGTH, - encoding: Optional[str] = None) -> str: + encoding: str | None = None) -> str: """Return a C-string read from memory.""" encoding = encoding or "unicode-escape" length = min(address | (DEFAULT_PAGE_SIZE-1), max_length+1) @@ -10803,7 +10802,7 @@ def read_cstring(self, return f"{ustr[:max_length]}[...]" return ustr - def read_ascii_string(self, address: int) -> Optional[str]: + def read_ascii_string(self, address: int) -> str | None: """Read an ASCII string from memory""" cstr = self.read_cstring(address) if isinstance(cstr, str) and cstr and all(x in string.printable for x in cstr): @@ -10819,7 +10818,7 @@ def maps(self) -> list[Section]: self.__maps = maps return self.__maps - def __parse_maps(self) -> Optional[list[Section]]: + def __parse_maps(self) -> list[Section] | None: """Return the mapped memory sections. If the current arch has its maps method defined, then defer to that to generated maps, otherwise, try to figure it out from procfs, then info sections, then monitor info @@ -11002,13 +11001,13 @@ def __init__(self) -> None: return def reset_caches(self) -> None: - self.__libc_main_arena: Optional[GlibcArena] = None - self.__libc_selected_arena: Optional[GlibcArena] = None + self.__libc_main_arena: GlibcArena | None = None + self.__libc_selected_arena: GlibcArena | None = None self.__heap_base = None return @property - def main_arena(self) -> Optional[GlibcArena]: + def main_arena(self) -> GlibcArena | None: if not self.__libc_main_arena: try: __main_arena_addr = GefHeapManager.find_main_arena_addr() @@ -11104,7 +11103,7 @@ def search_filter(zone: Zone) -> bool: raise OSError(err_msg) @property - def selected_arena(self) -> Optional[GlibcArena]: + def selected_arena(self) -> GlibcArena | None: if not self.__libc_selected_arena: # `selected_arena` must default to `main_arena` self.__libc_selected_arena = self.main_arena @@ -11122,7 +11121,7 @@ def arenas(self) -> list | Iterator[GlibcArena]: return iter(self.main_arena) @property - def base_address(self) -> Optional[int]: + def base_address(self) -> int | None: if not self.__heap_base: base = 0 try: @@ -11175,7 +11174,7 @@ def ceil(n: float) -> int: class GefSetting: """Basic class for storing gef settings as objects""" - def __init__(self, value: Any, cls: Optional[type] = None, description: Optional[str] = None, hooks: Optional[dict[str, list[Callable]]] = None) -> None: + def __init__(self, value: Any, cls: type | None = None, description: str | None = None, hooks: dict[str, list[Callable]] | None = None) -> None: self.value = value self.type = cls or type(value) self.description = description or "" @@ -11276,7 +11275,7 @@ class GefSessionManager(GefManager): """Class managing the runtime properties of GEF. """ def __init__(self) -> None: self.reset_caches() - self.remote: Optional["GefRemoteSessionManager"] = None + self.remote: "GefRemoteSessionManager | None" = None self.remote_initializing: bool = False self.qemu_mode: bool = False self.convenience_vars_index: int = 0 @@ -11299,8 +11298,8 @@ def reset_caches(self) -> None: self._os = None self._pid = None self._file = None - self._maps: Optional[pathlib.Path] = None - self._root: Optional[pathlib.Path] = None + self._maps: pathlib.Path | None = None + self._root: pathlib.Path | None = None return def __str__(self) -> str: @@ -11311,7 +11310,7 @@ def __repr__(self) -> str: return str(self) @property - def auxiliary_vector(self) -> Optional[dict[str, int]]: + def auxiliary_vector(self) -> dict[str, int] | None: if not is_alive(): return None if is_qemu_system(): @@ -11351,7 +11350,7 @@ def pid(self) -> int: return self._pid @property - def file(self) -> Optional[pathlib.Path]: + def file(self) -> pathlib.Path | None: """Return a Path object of the target process.""" if self.remote is not None: return self.remote.file @@ -11363,7 +11362,7 @@ def file(self) -> Optional[pathlib.Path]: return self._file @property - def cwd(self) -> Optional[pathlib.Path]: + def cwd(self) -> pathlib.Path | None: if self.remote is not None: return self.remote.root return self.file.parent if self.file else None @@ -11378,7 +11377,7 @@ def pagesize(self) -> int: return self._pagesize @property - def canary(self) -> Optional[tuple[int, int]]: + def canary(self) -> tuple[int, int] | None: """Return a tuple of the canary address and value, read from the canonical location if supported by the architecture. Otherwise, read from the auxiliary vector.""" @@ -11392,7 +11391,7 @@ def canary(self) -> Optional[tuple[int, int]]: return canary, canary_location @property - def original_canary(self) -> Optional[tuple[int, int]]: + def original_canary(self) -> tuple[int, int] | None: """Return a tuple of the initial canary address and value, read from the auxiliary vector.""" auxval = self.auxiliary_vector @@ -11404,7 +11403,7 @@ def original_canary(self) -> Optional[tuple[int, int]]: return canary, canary_location @property - def maps(self) -> Optional[pathlib.Path]: + def maps(self) -> pathlib.Path | None: """Returns the Path to the procfs entry for the memory mapping.""" if not is_alive(): return None @@ -11416,7 +11415,7 @@ def maps(self) -> Optional[pathlib.Path]: return self._maps @property - def root(self) -> Optional[pathlib.Path]: + def root(self) -> pathlib.Path | None: """Returns the path to the process's root directory.""" if not is_alive(): return None @@ -11450,7 +11449,7 @@ def prompt_string(self) -> str: return Color.boldify("(remote) ") raise AttributeError("Unknown value") - def __init__(self, host: str, port: int, pid: int =-1, qemu: Optional[pathlib.Path] = None) -> None: + def __init__(self, host: str, port: int, pid: int =-1, qemu: pathlib.Path | None = None) -> None: super().__init__() self.__host = host self.__port = port @@ -11519,7 +11518,7 @@ def maps(self) -> pathlib.Path: def mode(self) -> RemoteMode: return self._mode - def sync(self, src: str, dst: Optional[str] = None) -> bool: + def sync(self, src: str, dst: str | None = None) -> bool: """Copy the `src` into the temporary chroot. If `dst` is provided, that path will be used instead of `src`.""" if not dst: @@ -11657,9 +11656,9 @@ def remote_objfile_event_handler(self, evt: "gdb.NewObjFileEvent") -> None: class GefUiManager(GefManager): """Class managing UI settings.""" def __init__(self) -> None: - self.redirect_fd : Optional[TextIOWrapper] = None + self.redirect_fd : TextIOWrapper | None = None self.context_hidden = False - self.stream_buffer : Optional[StringIO] = None + self.stream_buffer : StringIO | None = None self.highlight_table: dict[str, str] = {} self.watches: dict[int, tuple[int, str]] = {} self.context_messages: list[tuple[str, str]] = [] @@ -11672,16 +11671,16 @@ class GefLibcManager(GefManager): PATTERN_LIBC_VERSION_FILENAME = re.compile(r"libc6?[-_](\d+)\.(\d+)\.so") def __init__(self) -> None: - self._version : Optional[tuple[int, int]] = None - self._patch: Optional[int] = None - self._release: Optional[str] = None + self._version : tuple[int, int] | None = None + self._patch: int | None = None + self._release: str | None = None return def __str__(self) -> str: return f"Libc(version='{self.version}')" @property - def version(self) -> Optional[tuple[int, int]]: + def version(self) -> tuple[int, int] | None: if not is_alive(): return None @@ -11723,7 +11722,7 @@ def find_libc_version() -> tuple[int, int]: class Gef: """The GEF root class, which serves as a entrypoint for all the debugging session attributes (architecture, memory, settings, etc.).""" - binary: Optional[FileFormat] + binary: FileFormat | None arch: Architecture config : GefSettingsManager ui: GefUiManager @@ -11734,7 +11733,7 @@ class Gef: gdb: GefCommand def __init__(self) -> None: - self.binary: Optional[FileFormat] = None + self.binary: FileFormat | None = None self.arch: Architecture = GenericArchitecture() # see PR #516, will be reset by `new_objfile_handler` self.arch_reason: str = "This is the default architecture" self.config = GefSettingsManager() @@ -11769,32 +11768,14 @@ def reset_caches(self) -> None: def target_remote_posthook(): - conn = gdb.selected_inferior().connection - # if here, the connection should be established - if not isinstance(conn, gdb.RemoteTargetConnection): - raise EnvironmentError("Failed to create a remote session") - - # if gef.session.remote_initializing: - # dbg(f"remote session already initializing: {gef.session}") - # return - - print(f"{conn.description=}") - print(f"{conn.details=}") - print(f"{conn.type=}") - - assert conn.details - host, port = conn.details.split(":") - pid = ( - gdb.selected_inferior().pid - if False - else gdb.selected_thread().ptid[1] - ) - print(host, port, pid) - gef.session.remote = GefRemoteSessionManager(host, int(port), pid) - gef.session.remote._mode = GefRemoteSessionManager.RemoteMode.GDBSERVER + if gef.session.remote_initializing: + return + gef.session.remote = GefRemoteSessionManager("", 0) if not gef.session.remote.setup(): - raise EnvironmentError(f"Failed to create a proper environment for {gef.session.remote}") + raise EnvironmentError( + f"Failed to create a proper environment for {gef.session.remote}" + ) if __name__ == "__main__": if sys.version_info[0] == 2: @@ -11839,14 +11820,6 @@ def target_remote_posthook(): # reload settings gdb.execute("gef restore") - # extend `sys.path` for venv compat - if gef.config["gef.extra_python_package_paths"]: - for path in map(pathlib.Path, gef.config["gef.extra_python_package_paths"].split(";")): - if not path.exists(): - warn(f"Skipping invalid directory {path}") - continue - sys.path.extend(str(path.absolute())) - # setup gdb prompt gdb.prompt_hook = __gef_prompt__ @@ -11865,16 +11838,32 @@ def target_remote_posthook(): GefTmuxSetup() - # Register a post-hook for `target remote` that initialize the remote session - hook = """ - define target hookpost-{} - pi target_remote_posthook() - context - pi res = calling_function(); if res != "connect": err("Failed to initialize remote session: " + str(res)) - end - """ - gdb.execute(hook.format("remote")) - gdb.execute(hook.format("extended-remote")) + if GDB_VERSION > (9, 0): + disable_tr_overwrite_setting = "gef.disable_target_remote_overwrite" + + if not gef.config[disable_tr_overwrite_setting]: + warnmsg = ("Using `target remote` with GEF should work in most cases, " + "but use `gef-remote` if you can. You can disable the " + "overwrite of the `target remote` command by toggling " + f"`{disable_tr_overwrite_setting}` in the config.") + hook = f""" + define target hookpost-{{}} + pi target_remote_posthook() + context + pi if calling_function() != "connect": warn("{warnmsg}") + end + """ + + # Register a post-hook for `target remote` that initialize the remote session + gdb.execute(hook.format("remote")) + gdb.execute(hook.format("extended-remote")) + else: + errmsg = ("Using `target remote` does not work, use `gef-remote` " + f"instead. You can toggle `{disable_tr_overwrite_setting}` " + "if this is not desired.") + hook = f"""pi if calling_function() != "connect": err("{errmsg}")""" + gdb.execute(f"define target hook-remote\n{hook}\nend") + gdb.execute(f"define target hook-extended-remote\n{hook}\nend") # restore saved breakpoints (if any) bkp_fpath = pathlib.Path(gef.config["gef.autosave_breakpoints_file"]).expanduser().absolute() From 60be6a15cbcf9a4aa083d7251193c5ba30955d62 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 2 Nov 2024 13:31:04 -0700 Subject: [PATCH 08/53] fixed ruff toml --- ruff.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruff.toml b/ruff.toml index 6a3fc3001..11ea9dbb8 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,2 +1,2 @@ [lint.per-file-ignores] -"gef.py" = ["E701"] \ No newline at end of file +"gef.py" = ["E701"] From dc289385b9776540a6f7c09473852ca90ab87394 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 2 Nov 2024 13:41:09 -0700 Subject: [PATCH 09/53] revert changes, only focus on py3.10 improvements --- gef.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/gef.py b/gef.py index af1151296..47506d3cd 100644 --- a/gef.py +++ b/gef.py @@ -6279,16 +6279,15 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: return # qemu-user support - qemu_binary = None + qemu_binary: pathlib.Path | None = None if args.qemu_user: - dbg("Setting up qemu-user session") try: qemu_binary = pathlib.Path(args.qemu_user).expanduser().absolute() if args.qemu_user else gef.session.file if not qemu_binary or not qemu_binary.exists(): raise FileNotFoundError(f"qemu-user session was specified, but binary '{qemu_binary}' does not exist") except Exception as e: err(f"Failed to initialize qemu-user mode, reason: {str(e)}") - raise e + return # Try to establish the remote session, throw on error # set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which @@ -11773,9 +11772,7 @@ def target_remote_posthook(): gef.session.remote = GefRemoteSessionManager("", 0) if not gef.session.remote.setup(): - raise EnvironmentError( - f"Failed to create a proper environment for {gef.session.remote}" - ) + raise EnvironmentError(f"Failed to create a proper environment for {gef.session.remote}") if __name__ == "__main__": if sys.version_info[0] == 2: From 417ac632afbbeadcce54e157ad4c4de8526ec852 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 2 Nov 2024 14:11:23 -0700 Subject: [PATCH 10/53] final fixes --- gef.py | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/gef.py b/gef.py index 47506d3cd..08bed01fd 100644 --- a/gef.py +++ b/gef.py @@ -114,8 +114,8 @@ def update_gef(argv: list[str]) -> int: sys.exit(1) -GDB_MIN_VERSION: tuple[int, int] = (8, 0) -PYTHON_MIN_VERSION: tuple[int, int] = (3, 6) +GDB_MIN_VERSION: tuple[int, int] = (10, 0) +PYTHON_MIN_VERSION: tuple[int, int] = (3, 10) PYTHON_VERSION: tuple[int, int] = sys.version_info[0:2] GDB_VERSION: tuple[int, int] = tuple(map(int, re.search(r"(\d+)[^\d]+(\d+)", gdb.VERSION).groups())) # type:ignore @@ -143,10 +143,10 @@ def update_gef(argv: list[str]) -> int: GEF_PROMPT_ON = f"\001\033[1;32m\002{GEF_PROMPT}\001\033[0m\002" GEF_PROMPT_OFF = f"\001\033[1;31m\002{GEF_PROMPT}\001\033[0m\002" -__registered_commands__ : set[Type["GenericCommand"]] = set() -__registered_functions__ : set[Type["GenericFunction"]] = set() -__registered_architectures__ : dict["Elf.Abi | str", Type["Architecture"]] = {} -__registered_file_formats__ : set[ Type["FileFormat"] ] = set() +__registered_commands__ : set[Type["GenericCommand"]] = set() +__registered_functions__ : set[Type["GenericFunction"]] = set() +__registered_architectures__ : dict["Elf.Abi | str", Type["Architecture"]] = {} +__registered_file_formats__ : set[ Type["FileFormat"] ] = set() GefMemoryMapProvider = Callable[[], Generator["Section", None, None]] @@ -2910,7 +2910,7 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: class X86(Architecture): - aliases: tuple[str | Elf.Abi, ...] = ("X86", Elf.Abi.X86_32) + aliases = ("X86", Elf.Abi.X86_32) arch = "X86" mode = "32" @@ -3354,7 +3354,7 @@ def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: class MIPS(Architecture): - aliases: tuple[str | Elf.Abi, ...] = ("MIPS", Elf.Abi.MIPS) + aliases = ("MIPS", Elf.Abi.MIPS) arch = "MIPS" mode = "MIPS32" @@ -3517,7 +3517,7 @@ def is_qemu() -> bool: if not is_remote_debug(): return False response = gdb.execute("maintenance packet Qqemu.sstepbits", to_string=True, from_tty=False) or "" - return "ENABLE=1" in response + return "ENABLE=" in response @lru_cache() @@ -4557,7 +4557,7 @@ def stop(self) -> bool: return False # software watchpoints stop after the next statement (see - # https://sourceware.org/gdb/onlinedocs/gdb/set-Watchpoints.html) + # https://sourceware.org/gdb/onlinedocs/gdb/Set-Watchpoints.html) pc = gdb_get_nth_previous_instruction_address(gef.arch.pc, 2) assert pc insn = gef_current_instruction(pc) @@ -4686,7 +4686,7 @@ class GenericCommand(gdb.Command): _cmdline_: str _syntax_: str - _example_: str | list[str] = "" + _example_: str | list[str] = "" _aliases_: list[str] = [] def __init_subclass__(cls, **kwargs): @@ -4851,7 +4851,7 @@ def do_invoke(self, args: list[str]) -> None: @register class ArchSetCommand(GenericCommand): - """set the current loaded architecture.""" + """Set the current loaded architecture.""" _cmdline_ = "arch set" _syntax_ = f"{_cmdline_} " @@ -5026,7 +5026,7 @@ def do_invoke(self, argv: list[str]) -> None: @register class PieBreakpointCommand(GenericCommand): - """set a PIE breakpoint at an offset from the target binaries base address.""" + """Set a PIE breakpoint at an offset from the target binaries base address.""" _cmdline_ = "pie breakpoint" _syntax_ = f"{_cmdline_} OFFSET" @@ -5789,7 +5789,7 @@ def __init__(self) -> None: super().__init__() return - def do_invoke(self, _: list) -> None: + def do_invoke(self, _: list[str]) -> None: """Dump the list of all the structures and their respective.""" manager = ExternalStructureManager() info(f"Listing custom structures from '{manager.path}'") @@ -6266,7 +6266,7 @@ def __init__(self) -> None: super().__init__(prefix=False) return - @parse_arguments({"host": "", "port": 0}, {"--pid": 0, "--qemu-user": ""}) + @parse_arguments({"host": "", "port": 0}, {"--pid": -1, "--qemu-user": False, "--qemu-binary": ""}) def do_invoke(self, _: list[str], **kwargs: Any) -> None: if gef.session.remote is not None: err("You already are in remote session. Close it first before opening a new one...") @@ -6282,15 +6282,15 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: qemu_binary: pathlib.Path | None = None if args.qemu_user: try: - qemu_binary = pathlib.Path(args.qemu_user).expanduser().absolute() if args.qemu_user else gef.session.file + qemu_binary = pathlib.Path(args.qemu_binary).expanduser().absolute() if args.qemu_binary else gef.session.file if not qemu_binary or not qemu_binary.exists(): - raise FileNotFoundError(f"qemu-user session was specified, but binary '{qemu_binary}' does not exist") + raise FileNotFoundError(f"{qemu_binary} does not exist") except Exception as e: err(f"Failed to initialize qemu-user mode, reason: {str(e)}") return # Try to establish the remote session, throw on error - # set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which + # Set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which # calls `is_remote_debug` which checks if `remote_initializing` is True or `.remote` is None # This prevents some spurious errors being thrown during startup gef.session.remote_initializing = True @@ -6500,7 +6500,7 @@ def do_invoke(self, _: list[str]) -> None: @register class GlibcHeapSetArenaCommand(GenericCommand): - """set the address of the main_arena or the currently selected arena.""" + """Set the address of the main_arena or the currently selected arena.""" _cmdline_ = "heap set-arena" _syntax_ = f"{_cmdline_} [address|&symbol]" @@ -8572,9 +8572,9 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: var_name = values[0] try: values = str(gdb.parse_and_eval(var_name)).lstrip("{").rstrip("}").replace(",","").split(" ") - except Exception as e: + except Exception: gef_print(f"Bad variable specified, check value with command: p {var_name}") - raise e + return d = str(gef.arch.endianness) for value in values: @@ -9939,9 +9939,8 @@ def __init__(self) -> None: gef.config["gef.buffer"] = GefSetting(True, bool, "Internally buffer command output until completion") gef.config["gef.bruteforce_main_arena"] = GefSetting(False, bool, "Allow bruteforcing main_arena symbol if everything else fails") gef.config["gef.libc_version"] = GefSetting("", str, "Specify libc version when auto-detection fails") - gef.config["gef.main_arena_offset"] = GefSetting("", str, "Offset from libc base address to main_arena symbol (int or hex). set to empty string to disable.") + gef.config["gef.main_arena_offset"] = GefSetting("", str, "Offset from libc base address to main_arena symbol (int or hex). Set to empty string to disable.") gef.config["gef.propagate_debug_exception"] = GefSetting(False, bool, "If true, when debug mode is enabled, Python exceptions will be propagated all the way.") - gef.config["gef.extra_python_package_paths"] = GefSetting("", str, "Semi-colon separate list of extra paths to include for python packages") self.commands : dict[str, GenericCommand] = {} self.functions : dict[str, GenericFunction] = {} @@ -10725,7 +10724,8 @@ def reset_caches(self) -> None: if not hasattr(obj, "cache_clear"): continue obj.cache_clear() - except Exception: # we're reseting the cache here, we don't care if (or which) exception triggers + except Exception: + # we're reseting the cache here, we don't care if (or which) exception triggers continue return @@ -11472,7 +11472,7 @@ def close(self) -> None: gef_on_new_hook(new_objfile_handler) except Exception as e: warn(f"Exception while restoring local context: {str(e)}") - raise e + raise def __str__(self) -> str: return f"RemoteSession(target='{self.target}', local='{self.root}', pid={self.pid}, mode={self.mode})" From 9fb1d716540736b939e22872ebfc8d66863b675e Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 3 Nov 2024 09:00:01 -0800 Subject: [PATCH 11/53] added `untracked` dir to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6ec4d8589..09f604883 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ debug.log htmlcov .benchmarks site/ +untracked/ From a27766176cee69f3c3a8d5418eda725ca3b763dc Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 3 Nov 2024 11:45:36 -0800 Subject: [PATCH 12/53] checkpoint: added new remote modes, gdbserver & gdbserver-multi work --- gef.py | 194 ++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 122 insertions(+), 72 deletions(-) diff --git a/gef.py b/gef.py index 08bed01fd..41cb61da6 100644 --- a/gef.py +++ b/gef.py @@ -273,11 +273,13 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: return wrapper -class ValidationError(Exception): pass # # Helpers # +class ValidationError(Exception): pass + +class InitializationError(Exception): pass class ObsoleteException(Exception): pass @@ -347,10 +349,10 @@ def is_alive() -> bool: return False -def calling_function() -> str | None: +def calling_function(frame: int = 3) -> str | None: """Return the name of the calling function""" try: - stack_info = traceback.extract_stack()[-3] + stack_info = traceback.extract_stack()[-frame] return stack_info.name except Exception as e: dbg(f"traceback failed with {str(e)}") @@ -3512,30 +3514,57 @@ def get_os() -> str: return gef.session.os -@lru_cache() -def is_qemu() -> bool: - if not is_remote_debug(): +def is_target_remote(conn: gdb.TargetConnection | None = None) -> bool: + "Returns True for `extended-remote` only." + _conn = conn or gdb.selected_inferior().connection + return isinstance(_conn, gdb.RemoteTargetConnection) and _conn.type == "remote" + + +def is_target_extended_remote(conn: gdb.TargetConnection | None = None) -> bool: + "Returns True for `extended-remote` only." + _conn = conn or gdb.selected_inferior().connection + return isinstance(_conn, gdb.RemoteTargetConnection) and _conn.type == "extended-remote" + + +def is_target_remote_or_extended(conn: gdb.TargetConnection | None = None) -> bool: + return is_target_remote(conn) or is_target_extended_remote(conn) + + +def is_running_under_qemu() -> bool: + "See https://www.qemu.org/docs/master/system/gdb.html " + if not is_target_remote(): return False response = gdb.execute("maintenance packet Qqemu.sstepbits", to_string=True, from_tty=False) or "" return "ENABLE=" in response -@lru_cache() -def is_qemu_usermode() -> bool: - if not is_qemu(): +def is_running_under_qemu_user() -> bool: + if not is_running_under_qemu(): return False - response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" + response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" # Use `qAttached`? return "Text=" in response -@lru_cache() -def is_qemu_system() -> bool: - if not is_qemu(): +def is_running_under_qemu_system() -> bool: + if not is_running_under_qemu(): return False + # Use "maintenance packet qqemu.PhyMemMode"? response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" return "received: \"\"" in response +def is_running_in_gdbserver() -> bool: + if is_running_under_qemu(): + return False + return not is_running_under_qemu() + + +def is_running_in_rr() -> bool: + if not is_running_in_gdbserver(): + return False + return os.environ.get("GDB_UNDER_RR", None) == "1" + + def get_filepath() -> str | None: """Return the local absolute path of the file currently debugged.""" if gef.session.remote: @@ -3960,6 +3989,7 @@ def is_in_x86_kernel(address: int) -> bool: return (address >> memalign) == 0xF +@deprecated("Use `is_target_remote()`") def is_remote_debug() -> bool: """"Return True is the current debugging session is running through GDB remote session.""" return gef.session.remote_initializing or gef.session.remote is not None @@ -6294,7 +6324,10 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: # calls `is_remote_debug` which checks if `remote_initializing` is True or `.remote` is None # This prevents some spurious errors being thrown during startup gef.session.remote_initializing = True - session = GefRemoteSessionManager(args.host, args.port, args.pid, qemu_binary) + # session = GefRemoteSessionManager(args.host, args.port, args.pid, qemu_binary) + conn = gdb.selected_inferior().connection + assert isinstance(conn, gdb.RemoteTargetConnection) + session = GefRemoteSessionManager(conn) dbg(f"[remote] initializing remote session with {session.target} under {session.root}") if not session.connect(args.pid) or not session.setup(): @@ -7414,7 +7447,7 @@ def __init__(self) -> None: def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] - if is_qemu_system(): + if is_running_under_qemu_system(): err("Unsupported") return @@ -11312,7 +11345,7 @@ def __repr__(self) -> str: def auxiliary_vector(self) -> dict[str, int] | None: if not is_alive(): return None - if is_qemu_system(): + if is_running_under_qemu_system(): return None if not self._auxiliary_vector: auxiliary_vector = {} @@ -11431,6 +11464,9 @@ class RemoteMode(enum.IntEnum): GDBSERVER = 0 QEMU = 1 RR = 2 + GDBSERVER_MULTI = 3 + QEMU_USER = 4 + QEMU_SYSTEM = 5 def __str__(self): return self.name @@ -11440,30 +11476,48 @@ def __repr__(self): def prompt_string(self) -> str: match self: - case GefRemoteSessionManager.RemoteMode.QEMU: + case ( + GefRemoteSessionManager.RemoteMode.QEMU | + GefRemoteSessionManager.RemoteMode.QEMU_USER | + GefRemoteSessionManager.RemoteMode.QEMU_SYSTEM + ): return Color.boldify("(qemu) ") case GefRemoteSessionManager.RemoteMode.RR: return Color.boldify("(rr) ") - case GefRemoteSessionManager.RemoteMode.GDBSERVER: + case ( + GefRemoteSessionManager.RemoteMode.GDBSERVER | + GefRemoteSessionManager.RemoteMode.GDBSERVER_MULTI + ): return Color.boldify("(remote) ") raise AttributeError("Unknown value") - def __init__(self, host: str, port: int, pid: int =-1, qemu: pathlib.Path | None = None) -> None: + @staticmethod + def init() -> "GefRemoteSessionManager.RemoteMode": + if is_running_under_qemu_system(): + return GefRemoteSessionManager.RemoteMode.QEMU_SYSTEM + if is_running_under_qemu_user(): + return GefRemoteSessionManager.RemoteMode.QEMU_USER + if is_running_in_rr(): + return GefRemoteSessionManager.RemoteMode.RR + if is_running_in_gdbserver(): + if is_target_extended_remote(): + return GefRemoteSessionManager.RemoteMode.GDBSERVER_MULTI + return GefRemoteSessionManager.RemoteMode.GDBSERVER + raise AttributeError + + def __init__(self, conn: gdb.RemoteTargetConnection) -> None: super().__init__() - self.__host = host - self.__port = port + assert is_target_remote_or_extended() + remote_host = conn.details + assert remote_host + host, port = remote_host.split(":", 1) + self.__host = host or "localhost" + self.__port = int(port) self.__local_root_fd = tempfile.TemporaryDirectory() self.__local_root_path = pathlib.Path(self.__local_root_fd.name) - self.__qemu = qemu - if pid > 0: - self._pid = pid + self._mode = GefRemoteSessionManager.RemoteMode.init() - if self.__qemu is not None: - self._mode = GefRemoteSessionManager.RemoteMode.QEMU - elif os.environ.get("GDB_UNDER_RR", None) == "1": - self._mode = GefRemoteSessionManager.RemoteMode.RR - else: - self._mode = GefRemoteSessionManager.RemoteMode.GDBSERVER + self.setup() def close(self) -> None: self.__local_root_fd.cleanup() @@ -11475,7 +11529,11 @@ def close(self) -> None: raise def __str__(self) -> str: - return f"RemoteSession(target='{self.target}', local='{self.root}', pid={self.pid}, mode={self.mode})" + msg = f"RemoteSession(target='{self.target}', local='{self.root}', mode={self.mode}" + if self.mode == GefRemoteSessionManager.RemoteMode.GDBSERVER: + msg += f", pid={self.pid}" + msg += ")" + return msg def __repr__(self) -> str: return str(self) @@ -11561,16 +11619,18 @@ def connect(self, pid: int) -> bool: def setup(self) -> bool: # setup remote adequately depending on remote or qemu mode + dbg(f"Setting up the {self._mode} session") match self.mode: - case GefRemoteSessionManager.RemoteMode.QEMU: - dbg(f"Setting up as qemu session, target={self.__qemu}") - self.__setup_qemu() + case GefRemoteSessionManager.RemoteMode.QEMU_USER: + self.__setup_qemu_user() + case GefRemoteSessionManager.RemoteMode.QEMU_SYSTEM: + raise Exception("TODO") case GefRemoteSessionManager.RemoteMode.RR: - dbg("Setting up as rr session") self.__setup_rr() case GefRemoteSessionManager.RemoteMode.GDBSERVER: - dbg("Setting up as remote session") self.__setup_remote() + case GefRemoteSessionManager.RemoteMode.GDBSERVER_MULTI: + pass case _: raise ValueError @@ -11580,13 +11640,13 @@ def setup(self) -> bool: reset_architecture() return True - def __setup_qemu(self) -> bool: + def __setup_qemu_user(self) -> bool: # setup emulated file in the chroot - assert self.__qemu - target = self.root / str(self.__qemu.parent).lstrip("/") + __qemu_target = pathlib.Path("foo") # TODO + target = self.root / str(__qemu_target.parent).lstrip("/") target.mkdir(parents=True, exist_ok=False) - shutil.copy2(self.__qemu, target) - self._file = self.__qemu + shutil.copy2(__qemu_target, target) + self._file = __qemu_target assert self.lfile.exists() # create a procfs @@ -11767,12 +11827,20 @@ def reset_caches(self) -> None: def target_remote_posthook(): - if gef.session.remote_initializing: - return + print(f"{is_target_remote()=}") + print(f"{is_target_remote_or_extended()=}") + print(f"{is_target_extended_remote()=}") + print(f"{is_running_under_qemu()=}") + print(f"{is_running_under_qemu_system()=}") + print(f"{is_running_under_qemu_user()=}") + print(f"{is_running_in_gdbserver()=}") + print(f"{is_running_in_rr()=}") + conn = gdb.selected_inferior().connection + if not isinstance(conn, gdb.RemoteTargetConnection): + raise TypeError("Expected type gdb.RemoteTargetConnection") + assert is_target_remote_or_extended(conn), "Target is not remote" + gef.session.remote = GefRemoteSessionManager(conn) - gef.session.remote = GefRemoteSessionManager("", 0) - if not gef.session.remote.setup(): - raise EnvironmentError(f"Failed to create a proper environment for {gef.session.remote}") if __name__ == "__main__": if sys.version_info[0] == 2: @@ -11835,32 +11903,14 @@ def target_remote_posthook(): GefTmuxSetup() - if GDB_VERSION > (9, 0): - disable_tr_overwrite_setting = "gef.disable_target_remote_overwrite" - - if not gef.config[disable_tr_overwrite_setting]: - warnmsg = ("Using `target remote` with GEF should work in most cases, " - "but use `gef-remote` if you can. You can disable the " - "overwrite of the `target remote` command by toggling " - f"`{disable_tr_overwrite_setting}` in the config.") - hook = f""" - define target hookpost-{{}} - pi target_remote_posthook() - context - pi if calling_function() != "connect": warn("{warnmsg}") - end - """ - - # Register a post-hook for `target remote` that initialize the remote session - gdb.execute(hook.format("remote")) - gdb.execute(hook.format("extended-remote")) - else: - errmsg = ("Using `target remote` does not work, use `gef-remote` " - f"instead. You can toggle `{disable_tr_overwrite_setting}` " - "if this is not desired.") - hook = f"""pi if calling_function() != "connect": err("{errmsg}")""" - gdb.execute(f"define target hook-remote\n{hook}\nend") - gdb.execute(f"define target hook-extended-remote\n{hook}\nend") + # Initialize `target *remote` post hooks + hook = """ + define target hookpost-{0} + pi target_remote_posthook() + end + """ + gdb.execute(hook.format("remote")) + gdb.execute(hook.format("extended-remote")) # restore saved breakpoints (if any) bkp_fpath = pathlib.Path(gef.config["gef.autosave_breakpoints_file"]).expanduser().absolute() From 6d166f0a088f0098c8703e042ee0ec7af64b6311 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 3 Nov 2024 18:24:41 -0800 Subject: [PATCH 13/53] checkpoint: added very basic pytests --- gef.py | 57 +++++++++++++++------------ tests/api/gef_remote.py | 87 +++++++++++++++++++++++++++++++++++++++++ tests/base.py | 4 +- tests/utils.py | 30 ++++++++++++++ 4 files changed, 150 insertions(+), 28 deletions(-) create mode 100644 tests/api/gef_remote.py diff --git a/gef.py b/gef.py index 41cb61da6..4799c5e69 100644 --- a/gef.py +++ b/gef.py @@ -3530,7 +3530,7 @@ def is_target_remote_or_extended(conn: gdb.TargetConnection | None = None) -> bo return is_target_remote(conn) or is_target_extended_remote(conn) -def is_running_under_qemu() -> bool: +def is_running_in_qemu() -> bool: "See https://www.qemu.org/docs/master/system/gdb.html " if not is_target_remote(): return False @@ -3538,15 +3538,15 @@ def is_running_under_qemu() -> bool: return "ENABLE=" in response -def is_running_under_qemu_user() -> bool: - if not is_running_under_qemu(): +def is_running_in_qemu_user() -> bool: + if not is_running_in_qemu(): return False response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" # Use `qAttached`? return "Text=" in response -def is_running_under_qemu_system() -> bool: - if not is_running_under_qemu(): +def is_running_in_qemu_system() -> bool: + if not is_running_in_qemu(): return False # Use "maintenance packet qqemu.PhyMemMode"? response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" @@ -3554,9 +3554,7 @@ def is_running_under_qemu_system() -> bool: def is_running_in_gdbserver() -> bool: - if is_running_under_qemu(): - return False - return not is_running_under_qemu() + return not is_running_in_qemu() def is_running_in_rr() -> bool: @@ -7447,7 +7445,7 @@ def __init__(self) -> None: def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] - if is_running_under_qemu_system(): + if is_running_in_qemu_system(): err("Unsupported") return @@ -11345,7 +11343,7 @@ def __repr__(self) -> str: def auxiliary_vector(self) -> dict[str, int] | None: if not is_alive(): return None - if is_running_under_qemu_system(): + if is_running_in_qemu_system(): return None if not self._auxiliary_vector: auxiliary_vector = {} @@ -11493,9 +11491,9 @@ def prompt_string(self) -> str: @staticmethod def init() -> "GefRemoteSessionManager.RemoteMode": - if is_running_under_qemu_system(): + if is_running_in_qemu_system(): return GefRemoteSessionManager.RemoteMode.QEMU_SYSTEM - if is_running_under_qemu_user(): + if is_running_in_qemu_user(): return GefRemoteSessionManager.RemoteMode.QEMU_USER if is_running_in_rr(): return GefRemoteSessionManager.RemoteMode.RR @@ -11619,7 +11617,7 @@ def connect(self, pid: int) -> bool: def setup(self) -> bool: # setup remote adequately depending on remote or qemu mode - dbg(f"Setting up the {self._mode} session") + info(f"Setting up remote session as '{self._mode}'") match self.mode: case GefRemoteSessionManager.RemoteMode.QEMU_USER: self.__setup_qemu_user() @@ -11826,21 +11824,24 @@ def reset_caches(self) -> None: return +def target_remote_hook(): + # disable the context until the session has been fully established + gef.config["context.enable"] = False + + def target_remote_posthook(): - print(f"{is_target_remote()=}") - print(f"{is_target_remote_or_extended()=}") - print(f"{is_target_extended_remote()=}") - print(f"{is_running_under_qemu()=}") - print(f"{is_running_under_qemu_system()=}") - print(f"{is_running_under_qemu_user()=}") - print(f"{is_running_in_gdbserver()=}") - print(f"{is_running_in_rr()=}") conn = gdb.selected_inferior().connection if not isinstance(conn, gdb.RemoteTargetConnection): raise TypeError("Expected type gdb.RemoteTargetConnection") assert is_target_remote_or_extended(conn), "Target is not remote" gef.session.remote = GefRemoteSessionManager(conn) + # re-enable context + gef.config["context.enable"] = True + + # if here, no exception was thrown, print context + gdb.execute("context") + if __name__ == "__main__": if sys.version_info[0] == 2: @@ -11903,14 +11904,18 @@ def target_remote_posthook(): GefTmuxSetup() - # Initialize `target *remote` post hooks + # Initialize `target *remote` pre/post hooks hook = """ - define target hookpost-{0} - pi target_remote_posthook() + define target hook{1}-{0} + pi target_remote_{1}hook() end """ - gdb.execute(hook.format("remote")) - gdb.execute(hook.format("extended-remote")) + # pre-hooks + gdb.execute(hook.format("remote", "")) + gdb.execute(hook.format("extended-remote", "")) + # post-hooks + gdb.execute(hook.format("remote", "post")) + gdb.execute(hook.format("extended-remote", "post")) # restore saved breakpoints (if any) bkp_fpath = pathlib.Path(gef.config["gef.autosave_breakpoints_file"]).expanduser().absolute() diff --git a/tests/api/gef_remote.py b/tests/api/gef_remote.py new file mode 100644 index 000000000..fceb96546 --- /dev/null +++ b/tests/api/gef_remote.py @@ -0,0 +1,87 @@ +""" +`target remote/extended-remote` test module. +""" + + +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import debug_target, gdbserver_session, gdbserver_multi_session, get_random_port, qemuuser_session + + +class GefRemoteApi(RemoteGefUnitTestGeneric): + + def setUp(self) -> None: + self._target = debug_target("default") + return super().setUp() + + def test_gef_remote_test_gdbserver(self): + """Test `gdbserver file`""" + _gdb = self._gdb + _root = self._conn.root + port = get_random_port() + + with gdbserver_session(port=port): + assert not _root.eval("is_target_remote()") + assert not _root.eval("is_target_remote_or_extended()") + assert not _root.eval("is_running_in_gdbserver()") + + _gdb.execute(f"target remote :{port}") + + assert _root.eval("is_target_remote()") + assert _root.eval("is_target_remote_or_extended()") + assert _root.eval("is_running_in_gdbserver()") + + assert not _root.eval("is_target_extended_remote()") + assert not _root.eval("is_running_under_qemu()") + assert not _root.eval("is_running_under_qemu_system()") + assert not _root.eval("is_running_under_qemu_user()") + assert not _root.eval("is_running_in_rr()") + + def test_gef_remote_test_gdbserver_multi(self): + """Test `gdbserver --multi file`""" + _gdb = self._gdb + _root = self._conn.root + port = get_random_port() + + with gdbserver_multi_session(port=port): + assert not _root.eval("is_target_remote()") + assert not _root.eval("is_target_remote_or_extended()") + assert not _root.eval("is_running_in_gdbserver()") + + _gdb.execute(f"target extended-remote :{port}") + + assert _root.eval("is_target_remote()") + assert _root.eval("is_target_remote_or_extended()") + assert _root.eval("is_target_extended_remote()") + assert _root.eval("is_running_in_gdbserver()") + + assert not _root.eval("is_running_under_qemu()") + assert not _root.eval("is_running_under_qemu_system()") + assert not _root.eval("is_running_under_qemu_user()") + assert not _root.eval("is_running_in_rr()") + + def test_gef_remote_test_qemuuser(self): + """Test `qemu-user -g`""" + _gdb = self._gdb + _root = self._conn.root + port = get_random_port() + + with qemuuser_session(port=port): + assert not _root.eval("is_target_remote()") + assert not _root.eval("is_target_remote_or_extended()") + assert not _root.eval("is_running_in_gdbserver()") + + _gdb.execute(f"target remote :{port}") + + assert _root.eval("is_target_remote()") + assert _root.eval("is_target_remote_or_extended()") + assert _root.eval("is_running_under_qemu()") + assert _root.eval("is_running_under_qemu_user()") + + assert not _root.eval("is_target_extended_remote()") + assert not _root.eval("is_running_under_qemu_system()") + assert not _root.eval("is_running_in_gdbserver()") + assert not _root.eval("is_running_in_rr()") + + # TODO add tests for + # - [ ] qemu-system + # - [ ] rr diff --git a/tests/base.py b/tests/base.py index 868923d3a..e1584eaf7 100644 --- a/tests/base.py +++ b/tests/base.py @@ -10,7 +10,7 @@ import rpyc -from .utils import debug_target +from .utils import debug_target, get_random_port COVERAGE_DIR = os.getenv("COVERAGE_DIR", "") GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "gef.py")).absolute() @@ -58,7 +58,7 @@ def __setup(self): # # Select a random tcp port for rpyc # - self._port = random.randint(1025, 65535) + self._port = get_random_port() self._commands = "" if COVERAGE_DIR: diff --git a/tests/utils.py b/tests/utils.py index f88d3b304..8b9247758 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -12,6 +12,7 @@ import subprocess import tempfile import time +import random from typing import Iterable, List, Optional, Union from urllib.request import urlopen @@ -112,6 +113,13 @@ def start_gdbserver( logging.debug(f"Starting {cmd}") return subprocess.Popen(cmd) +def start_gdbserver_multi( + host: str = GDBSERVER_DEFAULT_HOST, + port: int = GDBSERVER_DEFAULT_PORT, +) -> subprocess.Popen: + cmd = [GDBSERVER_BINARY, "--multi", f"{host}:{port}"] + logging.debug(f"Starting {cmd}") + return subprocess.Popen(cmd) def stop_gdbserver(gdbserver: subprocess.Popen) -> None: """Stop the gdbserver and wait until it is terminated if it was @@ -138,6 +146,17 @@ def gdbserver_session( finally: stop_gdbserver(sess) +@contextlib.contextmanager +def gdbserver_multi_session( + port: int = GDBSERVER_DEFAULT_PORT, + host: str = GDBSERVER_DEFAULT_HOST, +): + sess = start_gdbserver_multi(host, port) + try: + time.sleep(1) # forced delay to allow gdbserver to start listening + yield sess + finally: + stop_gdbserver(sess) def start_qemuuser( exe: Union[str, pathlib.Path] = debug_target("default"), @@ -301,3 +320,14 @@ def p32(x: int) -> bytes: def p64(x: int) -> bytes: return struct.pack(" int: + global __available_ports + if len(__available_ports) < 2: + __available_ports = list( range(1024, 65535) ) + idx = random.choice(range(len(__available_ports))) + port = __available_ports[idx] + __available_ports.pop(idx) + return port From 771a598f649e8162477902759dbc968cc079c2e2 Mon Sep 17 00:00:00 2001 From: hugsy Date: Mon, 4 Nov 2024 17:19:29 -0800 Subject: [PATCH 14/53] removed all obsolete code --- gef.py | 128 +++++------------------------------ tests/api/gef_remote.py | 41 ++++++++--- tests/commands/gef_remote.py | 45 +++++------- tests/utils.py | 7 +- 4 files changed, 70 insertions(+), 151 deletions(-) diff --git a/gef.py b/gef.py index 4799c5e69..092257cff 100644 --- a/gef.py +++ b/gef.py @@ -3554,13 +3554,12 @@ def is_running_in_qemu_system() -> bool: def is_running_in_gdbserver() -> bool: - return not is_running_in_qemu() + return is_target_remote_or_extended() and not is_running_in_qemu() def is_running_in_rr() -> bool: - if not is_running_in_gdbserver(): - return False - return os.environ.get("GDB_UNDER_RR", None) == "1" + return is_running_in_gdbserver() and \ + os.environ.get("GDB_UNDER_RR", None) == "1" def get_filepath() -> str | None: @@ -3774,7 +3773,7 @@ def exit_handler(_: "gdb.ExitedEvent") -> None: with bkp_fpath.open("w") as fd: for bp in list(gdb.breakpoints()): - if not bp.enabled or not bp.is_valid: + if not bp.enabled or not bp.is_valid(): continue fd.write(f"{'t' if bp.temporary else ''}break {bp.location}\n") return @@ -11519,12 +11518,6 @@ def __init__(self, conn: gdb.RemoteTargetConnection) -> None: def close(self) -> None: self.__local_root_fd.cleanup() - try: - gef_on_new_unhook(self.remote_objfile_event_handler) - gef_on_new_hook(new_objfile_handler) - except Exception as e: - warn(f"Exception while restoring local context: {str(e)}") - raise def __str__(self) -> str: msg = f"RemoteSession(target='{self.target}', local='{self.root}', mode={self.mode}" @@ -11574,46 +11567,10 @@ def mode(self) -> RemoteMode: return self._mode def sync(self, src: str, dst: str | None = None) -> bool: - """Copy the `src` into the temporary chroot. If `dst` is provided, that path will be - used instead of `src`.""" - if not dst: - dst = src - tgt = self.root / dst.lstrip("/") - if tgt.exists(): - return True - tgt.parent.mkdir(parents=True, exist_ok=True) - dbg(f"[remote] downloading '{src}' -> '{tgt}'") - gdb.execute(f"remote get '{src}' '{tgt.absolute()}'") - return tgt.exists() + raise DeprecationWarning def connect(self, pid: int) -> bool: - """Connect to remote target. If in extended mode, also attach to the given PID.""" - # before anything, register our new hook to download files from the remote target - dbg("[remote] Installing new objfile handlers") - try: - gef_on_new_unhook(new_objfile_handler) - except SystemError: - # the default objfile handler might already have been removed, ignore failure - pass - - gef_on_new_hook(self.remote_objfile_event_handler) - - # then attempt to connect - is_extended_mode = (pid > -1) - dbg(f"[remote] Enabling extended remote: {bool(is_extended_mode)}") - try: - with DisableContextOutputContext(): - cmd = f"target {'extended-' if is_extended_mode else ''}remote {self.target}" - dbg(f"[remote] Executing '{cmd}'") - gdb.execute(cmd) - if is_extended_mode: - gdb.execute(f"attach {pid:d}") - return True - except Exception as e: - err(f"Failed to connect to {self.target}: {e}") - - # a failure will trigger the cleanup, deleting our hook anyway - return False + raise DeprecationWarning def setup(self) -> bool: # setup remote adequately depending on remote or qemu mode @@ -11622,13 +11579,13 @@ def setup(self) -> bool: case GefRemoteSessionManager.RemoteMode.QEMU_USER: self.__setup_qemu_user() case GefRemoteSessionManager.RemoteMode.QEMU_SYSTEM: - raise Exception("TODO") + self.__setup_qemu_system() case GefRemoteSessionManager.RemoteMode.RR: self.__setup_rr() case GefRemoteSessionManager.RemoteMode.GDBSERVER: self.__setup_remote() case GefRemoteSessionManager.RemoteMode.GDBSERVER_MULTI: - pass + self.__setup_remote_multi() case _: raise ValueError @@ -11638,77 +11595,26 @@ def setup(self) -> bool: reset_architecture() return True + def __setup_qemu_system(self) -> bool: + raise Exception("TODO") + return True + def __setup_qemu_user(self) -> bool: - # setup emulated file in the chroot - __qemu_target = pathlib.Path("foo") # TODO - target = self.root / str(__qemu_target.parent).lstrip("/") - target.mkdir(parents=True, exist_ok=False) - shutil.copy2(__qemu_target, target) - self._file = __qemu_target - assert self.lfile.exists() - - # create a procfs - procfs = self.root / f"proc/{self.pid}/" - procfs.mkdir(parents=True, exist_ok=True) - - ## /proc/pid/cmdline - cmdline = procfs / "cmdline" - if not cmdline.exists(): - with cmdline.open("w") as fd: - fd.write("") - - ## /proc/pid/environ - environ = procfs / "environ" - if not environ.exists(): - with environ.open("wb") as fd: - fd.write(b"PATH=/bin\x00HOME=/tmp\x00") - - ## /proc/pid/maps - maps = procfs / "maps" - if not maps.exists(): - with maps.open("w") as fd: - fname = self.file.absolute() - mem_range = "00000000-ffffffff" if is_32bit() else "0000000000000000-ffffffffffffffff" - fd.write(f"{mem_range} rwxp 00000000 00:00 0 {fname}\n") + self.__local_root_path = pathlib.Path("/") return True def __setup_remote(self) -> bool: - # get the file - fpath = f"/proc/{self.pid}/exe" - if not self.sync(fpath, str(self.file)): - err(f"'{fpath}' could not be fetched on the remote system.") - return False - - # pseudo procfs - for _file in ("maps", "environ", "cmdline"): - fpath = f"/proc/{self.pid}/{_file}" - if not self.sync(fpath): - err(f"'{fpath}' could not be fetched on the remote system.") - return False + self.__local_root_path = pathlib.Path("/") + return True + def __setup_remote_multi(self) -> bool: + self.__local_root_path = pathlib.Path("/") return True def __setup_rr(self) -> bool: - # - # Simply override the local root path, the binary must exist - # on the host. - # self.__local_root_path = pathlib.Path("/") return True - def remote_objfile_event_handler(self, evt: "gdb.NewObjFileEvent") -> None: - dbg(f"[remote] in remote_objfile_handler({evt.new_objfile.filename if evt else 'None'}))") - if not evt or not evt.new_objfile.filename: - return - if not evt.new_objfile.filename.startswith("target:") and not evt.new_objfile.filename.startswith("/"): - warn(f"[remote] skipping '{evt.new_objfile.filename}'") - return - if evt.new_objfile.filename.startswith("target:"): - src: str = evt.new_objfile.filename[len("target:"):] - if not self.sync(src): - raise FileNotFoundError(f"Failed to sync '{src}'") - return - class GefUiManager(GefManager): """Class managing UI settings.""" diff --git a/tests/api/gef_remote.py b/tests/api/gef_remote.py index fceb96546..35e673f6a 100644 --- a/tests/api/gef_remote.py +++ b/tests/api/gef_remote.py @@ -16,6 +16,7 @@ def setUp(self) -> None: def test_gef_remote_test_gdbserver(self): """Test `gdbserver file`""" _gdb = self._gdb + _gef = self._gef _root = self._conn.root port = get_random_port() @@ -23,22 +24,30 @@ def test_gef_remote_test_gdbserver(self): assert not _root.eval("is_target_remote()") assert not _root.eval("is_target_remote_or_extended()") assert not _root.eval("is_running_in_gdbserver()") + assert not _root.eval("is_running_in_rr()") + assert not _root.eval("is_running_in_qemu()") _gdb.execute(f"target remote :{port}") assert _root.eval("is_target_remote()") assert _root.eval("is_target_remote_or_extended()") assert _root.eval("is_running_in_gdbserver()") + assert _root.eval("is_running_in_gdbserver()") assert not _root.eval("is_target_extended_remote()") - assert not _root.eval("is_running_under_qemu()") - assert not _root.eval("is_running_under_qemu_system()") - assert not _root.eval("is_running_under_qemu_user()") + assert not _root.eval("is_running_in_qemu()") + assert not _root.eval("is_running_in_qemu_system()") + assert not _root.eval("is_running_in_qemu_user()") assert not _root.eval("is_running_in_rr()") + assert hasattr(_gef.session, "remote") + assert "GDBSERVER" in str(_gef.session.remote) + assert "GDBSERVER_MULTI" not in str(_gef.session.remote) + def test_gef_remote_test_gdbserver_multi(self): """Test `gdbserver --multi file`""" _gdb = self._gdb + _gef = self._gef _root = self._conn.root port = get_random_port() @@ -46,22 +55,31 @@ def test_gef_remote_test_gdbserver_multi(self): assert not _root.eval("is_target_remote()") assert not _root.eval("is_target_remote_or_extended()") assert not _root.eval("is_running_in_gdbserver()") + assert not _root.eval("is_running_in_rr()") + assert not _root.eval("is_running_in_qemu()") _gdb.execute(f"target extended-remote :{port}") + _gdb.execute(f"set remote exec-file {self._target}") + _gdb.execute(f"file {self._target}") + _gdb.execute(f"start {self._target}") - assert _root.eval("is_target_remote()") assert _root.eval("is_target_remote_or_extended()") assert _root.eval("is_target_extended_remote()") assert _root.eval("is_running_in_gdbserver()") + assert not _root.eval("is_target_remote()") - assert not _root.eval("is_running_under_qemu()") - assert not _root.eval("is_running_under_qemu_system()") - assert not _root.eval("is_running_under_qemu_user()") + assert not _root.eval("is_running_in_qemu()") + assert not _root.eval("is_running_in_qemu_system()") + assert not _root.eval("is_running_in_qemu_user()") assert not _root.eval("is_running_in_rr()") + assert hasattr(_gef.session, "remote") + assert "GDBSERVER_MULTI" in str(_gef.session.remote) + def test_gef_remote_test_qemuuser(self): """Test `qemu-user -g`""" _gdb = self._gdb + _gef = self._gef _root = self._conn.root port = get_random_port() @@ -74,14 +92,17 @@ def test_gef_remote_test_qemuuser(self): assert _root.eval("is_target_remote()") assert _root.eval("is_target_remote_or_extended()") - assert _root.eval("is_running_under_qemu()") - assert _root.eval("is_running_under_qemu_user()") + assert _root.eval("is_running_in_qemu()") + assert _root.eval("is_running_in_qemu_user()") assert not _root.eval("is_target_extended_remote()") - assert not _root.eval("is_running_under_qemu_system()") + assert not _root.eval("is_running_in_qemu_system()") assert not _root.eval("is_running_in_gdbserver()") assert not _root.eval("is_running_in_rr()") + assert hasattr(_gef.session, "remote") + assert "QEMU_USER" in str(_gef.session.remote) + # TODO add tests for # - [ ] qemu-system # - [ ] rr diff --git a/tests/commands/gef_remote.py b/tests/commands/gef_remote.py index 34fd0ab60..e5e1e54b2 100644 --- a/tests/commands/gef_remote.py +++ b/tests/commands/gef_remote.py @@ -11,6 +11,7 @@ ARCH, debug_target, gdbserver_session, + get_random_port, qemuuser_session, GDBSERVER_DEFAULT_HOST, ) @@ -26,50 +27,40 @@ def setUp(self) -> None: def test_cmd_gef_remote_gdbserver(self): gdb = self._gdb gef = self._gef - root = self._conn.root + port = get_random_port() gdbserver_mode = "GDBSERVER" - while True: - port = random.randint(1025, 65535) - if port != self._port: - break with gdbserver_session(port=port): - gdb.execute(f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}") - res: str = root.eval("str(gef.session.remote)") - assert res.startswith(f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/tmp/") - assert res.endswith(f"pid={gef.session.pid}, mode={gdbserver_mode})") + gdb.execute(f"target remote {GDBSERVER_DEFAULT_HOST}:{port}") + res: str = str(gef.session.remote) + assert res.startswith(f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/") + assert res.endswith(f"mode={gdbserver_mode}, pid={gef.session.pid})") @pytest.mark.slow @pytest.mark.skipif(ARCH not in ("x86_64",), reason=f"Skipped for {ARCH}") def test_cmd_gef_remote_qemu_user(self): gdb = self._gdb gef = self._gef - root = self._conn.root - qemu_mode = "QEMU" - while True: - port = random.randint(1025, 65535) - if port != self._port: - break + qemu_mode = "QEMU_USER" + port = get_random_port() with qemuuser_session(port=port): - cmd = f"gef-remote --qemu-user --qemu-binary {self._target} {GDBSERVER_DEFAULT_HOST} {port}" + cmd = f"target remote {GDBSERVER_DEFAULT_HOST}:{port}" gdb.execute(cmd) - res = root.eval("str(gef.session.remote)") - assert res.startswith(f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/tmp/") - assert res.endswith(f"pid={gef.session.pid}, mode={qemu_mode})") + res = str(gef.session.remote) + assert res.startswith(f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/") + assert res.endswith(f"mode={qemu_mode})") def test_cmd_target_remote(self): gdb = self._gdb gef = self._gef - root = self._conn.root gdbserver_mode = "GDBSERVER" - while True: - port = random.randint(1025, 65535) - if port != self._port: - break + port = get_random_port() with gdbserver_session(port=port) as _: gdb.execute(f"target remote {GDBSERVER_DEFAULT_HOST}:{port}") - res: str = root.eval("str(gef.session.remote)") - assert res.startswith("RemoteSession(target=':0', local='/tmp/") - assert res.endswith(f"pid={gef.session.pid}, mode={gdbserver_mode})") + res: str = str(gef.session.remote) + assert res.startswith( + f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/" + ) + assert res.endswith(f"mode={gdbserver_mode}, pid={gef.session.pid})") diff --git a/tests/utils.py b/tests/utils.py index 8b9247758..ee15db5b8 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -42,7 +42,8 @@ def which(program: str) -> pathlib.Path: STRIP_ANSI_DEFAULT = True GDBSERVER_DEFAULT_HOST = "localhost" GDBSERVER_DEFAULT_PORT = 1234 -GDBSERVER_BINARY = which("gdbserver") +GDBSERVER_BINARY: pathlib.Path = which("gdbserver") +GDBSERVER_STARTUP_DELAY_SEC : float = 0.5 assert GDBSERVER_BINARY.exists() QEMU_USER_X64_BINARY = which("qemu-x86_64") @@ -141,7 +142,7 @@ def gdbserver_session( ): sess = start_gdbserver(exe, host, port) try: - time.sleep(1) # forced delay to allow gdbserver to start listening + time.sleep(GDBSERVER_STARTUP_DELAY_SEC) yield sess finally: stop_gdbserver(sess) @@ -153,7 +154,7 @@ def gdbserver_multi_session( ): sess = start_gdbserver_multi(host, port) try: - time.sleep(1) # forced delay to allow gdbserver to start listening + time.sleep(GDBSERVER_STARTUP_DELAY_SEC) yield sess finally: stop_gdbserver(sess) From 79447be3f1879a63eabb659010ec0f9e6f29bffe Mon Sep 17 00:00:00 2001 From: hugsy Date: Mon, 4 Nov 2024 17:40:26 -0800 Subject: [PATCH 15/53] fixed `gef-remote` from tests, using only `target remote` --- tests/api/gef_memory.py | 30 +++++++---------------- tests/api/gef_session.py | 15 +++++------- tests/regressions/gdbserver_connection.py | 4 +-- 3 files changed, 17 insertions(+), 32 deletions(-) diff --git a/tests/api/gef_memory.py b/tests/api/gef_memory.py index 6b0b6760d..d77ec0f1a 100644 --- a/tests/api/gef_memory.py +++ b/tests/api/gef_memory.py @@ -12,6 +12,7 @@ ARCH, debug_target, gdbserver_session, + get_random_port, qemuuser_session, GDBSERVER_DEFAULT_HOST, ) @@ -121,48 +122,35 @@ def test_func_parse_maps_local_procfs(self): @pytest.mark.slow def test_func_parse_maps_remote_gdbserver(self): gef, gdb = self._gef, self._gdb - # When in a gef-remote session `parse_gdb_info_proc_maps` should work to + # When in a remote session `parse_gdb_info_proc_maps` should work to # query the memory maps - while True: - port = random.randint(1025, 65535) - if port != self._port: - break + port = get_random_port() with pytest.raises(Exception): - gdb.execute(f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}") + gdb.execute(f"target remote :{port}") with gdbserver_session(port=port) as _: - gdb.execute(f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}") + gdb.execute(f"target remote :{port}") sections = gef.memory.maps assert len(sections) > 0 @pytest.mark.skipif(ARCH not in ("x86_64",), reason=f"Skipped for {ARCH}") def test_func_parse_maps_remote_qemu(self): gdb, gef = self._gdb, self._gef - # When in a gef-remote qemu-user session `parse_gdb_info_proc_maps` - # should work to query the memory maps - while True: - port = random.randint(1025, 65535) - if port != self._port: - break + port = get_random_port() with qemuuser_session(port=port) as _: - cmd = f"gef-remote --qemu-user --qemu-binary {self._target} {GDBSERVER_DEFAULT_HOST} {port}" + cmd = f"target remote :{port}" gdb.execute(cmd) sections = gef.memory.maps assert len(sections) > 0 def test_func_parse_maps_realpath(self): gef, gdb = self._gef, self._gdb - # When in a gef-remote session `parse_gdb_info_proc_maps` should work to - # query the memory maps - while True: - port = random.randint(1025, 65535) - if port != self._port: - break + port = get_random_port() with gdbserver_session(port=port) as _: - gdb.execute(f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}") + gdb.execute(f"target remote :{port}") gdb.execute("b main") gdb.execute("continue") sections = gef.memory.maps diff --git a/tests/api/gef_session.py b/tests/api/gef_session.py index a24c04370..5ebc8a0c1 100644 --- a/tests/api/gef_session.py +++ b/tests/api/gef_session.py @@ -14,6 +14,7 @@ ARCH, debug_target, gdbserver_session, + get_random_port, qemuuser_session, GDBSERVER_DEFAULT_HOST, ) @@ -63,12 +64,11 @@ def test_root_dir_local(self): def test_root_dir_remote(self): gdb = self._gdb gdb.execute("start") - expected = os.stat("/") - host = GDBSERVER_DEFAULT_HOST - port = random.randint(1025, 65535) + port = get_random_port() + with gdbserver_session(port=port): - gdb.execute(f"gef-remote {host} {port}") + gdb.execute(f"target remote :{port}") result = self._conn.root.eval("os.stat(gef.session.root)") assert (expected.st_dev == result.st_dev) and ( expected.st_ino == result.st_ino @@ -77,11 +77,8 @@ def test_root_dir_remote(self): @pytest.mark.skipif(ARCH not in ("x86_64",), reason=f"Skipped for {ARCH}") def test_root_dir_qemu(self): gdb, gef = self._gdb, self._gef + port = get_random_port() - host = GDBSERVER_DEFAULT_HOST - port = random.randint(1025, 65535) with qemuuser_session(port=port): - gdb.execute( - f"gef-remote --qemu-user --qemu-binary {self._target} {host} {port}" - ) + gdb.execute(f"target remote :{port}") assert re.search(r"\/proc\/[0-9]+/root", str(gef.session.root)) diff --git a/tests/regressions/gdbserver_connection.py b/tests/regressions/gdbserver_connection.py index 6c46d9659..244b4e55b 100644 --- a/tests/regressions/gdbserver_connection.py +++ b/tests/regressions/gdbserver_connection.py @@ -10,8 +10,8 @@ def test_can_establish_connection_to_gdbserver_again_after_disconnect(self): gdb = self._gdb with gdbserver_session(port=5001) as _, gdbserver_session(port=5002) as _: - gdb.execute("gef-remote 127.0.0.1 5001") + gdb.execute("target remote :5001") gdb.execute("detach") - gdb.execute("gef-remote 127.0.0.1 5002") + gdb.execute("target remote :5002") gdb.execute("continue") From ea873bbdfedacb15716ca2645c9e56c0cccaec0b Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Wed, 6 Nov 2024 20:06:25 -0800 Subject: [PATCH 16/53] use `NotImplementedError` --- gef.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gef.py b/gef.py index 092257cff..de041e3cc 100644 --- a/gef.py +++ b/gef.py @@ -11596,8 +11596,7 @@ def setup(self) -> bool: return True def __setup_qemu_system(self) -> bool: - raise Exception("TODO") - return True + raise NotImplementedError("TODO") def __setup_qemu_user(self) -> bool: self.__local_root_path = pathlib.Path("/") From 4ae683f8bd73231258892779cf27eab3550cc959 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Wed, 6 Nov 2024 20:22:56 -0800 Subject: [PATCH 17/53] plop --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 416626e93..c1a3a32b3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,7 +21,7 @@ jobs: matrix: runner: [ubuntu-24.04, ubuntu-22.04] - name: "Run Unit tests on ${{ matrix.runner }}" + name: "Tests/${{ matrix.runner }}" runs-on: ${{ matrix.runner }} defaults: run: From 8130895c3b9edf6efb59c25cc726671c1a7073af Mon Sep 17 00:00:00 2001 From: hugsy Date: Wed, 6 Nov 2024 21:21:18 -0800 Subject: [PATCH 18/53] minor lint --- gef.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gef.py b/gef.py index de041e3cc..23f696776 100644 --- a/gef.py +++ b/gef.py @@ -3723,6 +3723,8 @@ def new_objfile_handler(evt: "gdb.NewObjFileEvent | None") -> None: path = progspace.filename else: raise RuntimeError("Cannot determine file path") + + assert path try: if gef.session.root and path.startswith("target:"): # If the process is in a container, replace the "target:" prefix @@ -11385,7 +11387,7 @@ def file(self) -> pathlib.Path | None: return self.remote.file progspace = gdb.current_progspace() assert progspace - fpath: str = progspace.filename + fpath: str = progspace.filename or "" if fpath and not self._file: self._file = pathlib.Path(fpath).expanduser() return self._file @@ -11548,7 +11550,7 @@ def file(self) -> pathlib.Path: if not filename: raise RuntimeError("No session started") start_idx = len("target:") if filename.startswith("target:") else 0 - self._file = pathlib.Path(progspace.filename[start_idx:]) + self._file = pathlib.Path(filename[start_idx:]) return self._file @property From 7973a3f279198965f3919b27b0d702dde7ff9cd1 Mon Sep 17 00:00:00 2001 From: hugsy Date: Wed, 6 Nov 2024 21:58:12 -0800 Subject: [PATCH 19/53] allow mock mem layout for old qemu versions --- gef.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/gef.py b/gef.py index 23f696776..1f017b6fe 100644 --- a/gef.py +++ b/gef.py @@ -10859,16 +10859,32 @@ def __parse_maps(self) -> list[Section] | None: try: return list(self.parse_gdb_info_proc_maps()) - except Exception: - pass + except Exception as e: + dbg(f"parse_gdb_info_proc_maps() failed, reason: {str(e)}") try: return list(self.parse_procfs_maps()) - except Exception: - pass + except Exception as e: + dbg(f"parse_procfs_maps() failed, reason: {str(e)}") try: return list(self.parse_monitor_info_mem()) + except Exception as e: + dbg(f"parse_monitor_info_mem() failed, reason: {str(e)}") + + try: + # as a very last resort, use a mock rwx memory layout only if a session is running + assert gef.binary and gef.session.pid + warn("Could not determine memory layout accurately, using mock layout") + fname = gef.binary.path + if is_32bit(): + page_start, page_end = 0x00000000, 0xffffffff + else: + page_start, page_end = 0x0000000000000000, 0xffffffffffffffff + return [Section(page_start=page_start, + page_end=page_end, + permission = Permission.ALL, + path = str(fname)),] except Exception: pass From 8cc223183ee53a722e478e75b28b3a997535799f Mon Sep 17 00:00:00 2001 From: hugsy Date: Thu, 7 Nov 2024 17:56:40 -0800 Subject: [PATCH 20/53] added repr for Gef class --- gef.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gef.py b/gef.py index 1f017b6fe..dddc2f945 100644 --- a/gef.py +++ b/gef.py @@ -11724,6 +11724,12 @@ def __init__(self) -> None: def __str__(self) -> str: return f"Gef(binary='{self.binary or 'None'}', arch={self.arch})" + def __repr__(self) -> str: + binary = self.binary + arch = self.arch + session = self.session + return f"Gef({binary=:}, {arch=:}, {session=:})" + def reinitialize_managers(self) -> None: """Reinitialize the managers. Avoid calling this function directly, using `pi reset()` is preferred""" self.memory = GefMemoryManager() From 2ba9ac715a693f11a4c31162b2f363f87ad8f7f3 Mon Sep 17 00:00:00 2001 From: hugsy Date: Thu, 7 Nov 2024 17:56:57 -0800 Subject: [PATCH 21/53] [tests] use `gdb-multiarch` by default --- tests/base.py | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/tests/base.py b/tests/base.py index e1584eaf7..07248174c 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,16 +1,14 @@ import os import pathlib -import random import re import subprocess -import tempfile import time import unittest import rpyc -from .utils import debug_target, get_random_port +from .utils import debug_target, get_random_port, which COVERAGE_DIR = os.getenv("COVERAGE_DIR", "") GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "gef.py")).absolute() @@ -19,6 +17,7 @@ RPYC_PORT = 18812 RPYC_SPAWN_TIME = 1.0 RPYC_MAX_REMOTE_CONNECTION_ATTEMPTS = 5 +GDB_BINARY_PATH = which("gdb-multiarch") class RemoteGefUnitTestGeneric(unittest.TestCase): @@ -28,6 +27,7 @@ class RemoteGefUnitTestGeneric(unittest.TestCase): """ def setUp(self) -> None: + self._gdb_path = GDB_BINARY_PATH attempt = RPYC_MAX_REMOTE_CONNECTION_ATTEMPTS while True: try: @@ -71,25 +71,20 @@ def __setup(self): pi cov.start() """ - self._commands += f""" -source {GEF_PATH} -gef config gef.debug True -gef config gef.propagate_debug_exception True -gef config gef.disable_color True -source {RPYC_GEF_PATH} -pi start_rpyc_service({self._port}) -""" - - self._initfile = tempfile.NamedTemporaryFile(mode="w", delete=False) - self._initfile.write(self._commands) - self._initfile.flush() + # self._initfile = tempfile.NamedTemporaryFile(mode="w", delete=False) + # self._initfile.write(self._commands) + # self._initfile.flush() self._command = [ - "gdb", - "-q", - "-nx", - "-ex", - f"source {self._initfile.name}", + # fmt: off + self._gdb_path, "-q", "-nx", + "-ex", f"source {GEF_PATH}", + "-ex", "gef config gef.debug True", + "-ex", "gef config gef.propagate_debug_exception True", + "-ex", "gef config gef.disable_color True", + "-ex", f"source {RPYC_GEF_PATH}", + "-ex", f"pi start_rpyc_service({self._port})", "--", + # fmt: off str(self._target.absolute()), # type: ignore pylint: disable=E1101 ] self._process = subprocess.Popen(self._command) From 437cbe213a61a1f0f098bfb342512d475896f959 Mon Sep 17 00:00:00 2001 From: hugsy Date: Thu, 7 Nov 2024 17:57:18 -0800 Subject: [PATCH 22/53] added regression for issue #1131 --- tests/commands/gef_remote.py | 2 - .../1131_target_remote_registers.py | 39 +++++++++++++++++++ tests/utils.py | 34 +++++++++++----- 3 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 tests/regressions/1131_target_remote_registers.py diff --git a/tests/commands/gef_remote.py b/tests/commands/gef_remote.py index e5e1e54b2..2f8d5d299 100644 --- a/tests/commands/gef_remote.py +++ b/tests/commands/gef_remote.py @@ -2,8 +2,6 @@ `gef_remote` command test module """ -import random - import pytest from tests.base import RemoteGefUnitTestGeneric diff --git a/tests/regressions/1131_target_remote_registers.py b/tests/regressions/1131_target_remote_registers.py new file mode 100644 index 000000000..8d6512b79 --- /dev/null +++ b/tests/regressions/1131_target_remote_registers.py @@ -0,0 +1,39 @@ +import pathlib +import pytest +import os +import tempfile + +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import get_random_port, qemuuser_session + +URL = "https://github.com/user-attachments/files/16913262/repr.zip" + +@pytest.mark.slow +class MissingTargetRemoteRegisters(RemoteGefUnitTestGeneric): + """@ref https://github.com/hugsy/gef/pull/1131""" + + def setUp(self) -> None: + repro_script = f""" + wget -O {{0}}/repr.zip {URL} + unzip {{0}}/repr.zip -d {{0}} + """ + + self._tempdir = tempfile.TemporaryDirectory(prefix="gef-tests-") + self._tempdir_path = pathlib.Path(self._tempdir.name) + os.system(repro_script.format(self._tempdir_path)) + self._current_dir = self._tempdir_path / "repr" + os.chdir(self._current_dir) + self._target = self._current_dir / "chal" + return super().setUp() + + def test_target_remote_validate_post_hook_registers_display(self): + _gdb = self._gdb + _gef = self._gef + port = get_random_port() + + # cmd: ./qemu-mipsel-static -g 1234 -L ./target ./chal + with qemuuser_session(exe=self._target, port=port, qemu_exe=self._current_dir / "qemu-mipsel-static", args=["-L", str(self._current_dir / "target")]): + _gdb.execute(f"target remote :{port}") + + res = str(_gef.session.remote) + assert f"RemoteSession(target='localhost:{port}', local='/', mode=QEMU_USER)" in res diff --git a/tests/utils.py b/tests/utils.py index ee15db5b8..ab3f488a1 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -8,6 +8,7 @@ import os import pathlib import platform +import shutil import struct import subprocess import tempfile @@ -19,12 +20,10 @@ def which(program: str) -> pathlib.Path: - for path in os.environ["PATH"].split(os.pathsep): - dirname = pathlib.Path(path) - fpath = dirname / program - if os.access(fpath, os.X_OK): - return fpath - raise FileNotFoundError(f"Missing file `{program}`") + fpath = shutil.which(program) + if not fpath: + raise FileNotFoundError(f"Missing file `{program}`") + return pathlib.Path(fpath) TMPDIR = pathlib.Path(tempfile.gettempdir()) @@ -162,9 +161,16 @@ def gdbserver_multi_session( def start_qemuuser( exe: Union[str, pathlib.Path] = debug_target("default"), port: int = GDBSERVER_DEFAULT_PORT, + qemu_exe: pathlib.Path = QEMU_USER_X64_BINARY, + args: list[str] | None = None ) -> subprocess.Popen: + cmd = [qemu_exe, "-g", str(port)] + if args: + cmd.extend(args) + cmd.append(exe) + logging.info(f"Starting '{cmd}' in {qemu_exe} on :{port}") return subprocess.Popen( - [QEMU_USER_X64_BINARY, "-g", str(port), exe], + cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) @@ -179,9 +185,19 @@ def stop_qemuuser(process: subprocess.Popen) -> None: @contextlib.contextmanager def qemuuser_session(*args, **kwargs): exe = kwargs.get("exe", "") or debug_target("default") - port = kwargs.get("port", 0) or GDBSERVER_DEFAULT_PORT - sess = start_qemuuser(exe, port) + port = kwargs.get("port", GDBSERVER_DEFAULT_PORT) + qemu_exe = kwargs.get("qemu_exe", None) or QEMU_USER_X64_BINARY + args = kwargs.get("args", None) + if args: + # if specified, expect a list of strings + assert isinstance(args, list) + assert len(args) + for arg in args: + assert isinstance(arg, str) + + sess = start_qemuuser(exe, port=port, qemu_exe=qemu_exe, args=args) try: + time.sleep(GDBSERVER_STARTUP_DELAY_SEC) yield sess finally: stop_qemuuser(sess) From e5a02b1bbded56cc8afe0df479b7321b0a513350 Mon Sep 17 00:00:00 2001 From: hugsy Date: Thu, 7 Nov 2024 18:04:31 -0800 Subject: [PATCH 23/53] constantify all the things --- tests/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/base.py b/tests/base.py index 07248174c..2752ea361 100644 --- a/tests/base.py +++ b/tests/base.py @@ -18,6 +18,7 @@ RPYC_SPAWN_TIME = 1.0 RPYC_MAX_REMOTE_CONNECTION_ATTEMPTS = 5 GDB_BINARY_PATH = which("gdb-multiarch") +RPYC_CONNECT_FAILURE_DELAY = 0.2 class RemoteGefUnitTestGeneric(unittest.TestCase): @@ -41,7 +42,7 @@ def setUp(self) -> None: attempt -= 1 if attempt == 0: raise - time.sleep(0.2) + time.sleep(RPYC_CONNECT_FAILURE_DELAY) continue self._gdb = self._conn.root.gdb From 68fcc1d007c5c00888f633a243e26c704174f346 Mon Sep 17 00:00:00 2001 From: hugsy Date: Thu, 7 Nov 2024 18:16:39 -0800 Subject: [PATCH 24/53] officially deprecating `gef-remote` --- docs/commands/gef-remote.md | 13 +++------- gef.py | 52 +++++++++---------------------------- 2 files changed, 15 insertions(+), 50 deletions(-) diff --git a/docs/commands/gef-remote.md b/docs/commands/gef-remote.md index 917601dd9..77cc79308 100644 --- a/docs/commands/gef-remote.md +++ b/docs/commands/gef-remote.md @@ -1,15 +1,8 @@ ## Command `gef-remote` -[`target remote`](https://sourceware.org/gdb/onlinedocs/gdb/Remote-Debugging.html#Remote-Debugging) -is the traditional GDB way of debugging process or system remotely. However this command by itself -does a limited job (80's bandwith FTW) to collect more information about the target, making the -process of debugging more cumbersome. GEF greatly improves that state with the `gef-remote` command. - -📝 **Note**: If using GEF, `gef-remote` **must** be your way or debugging remote processes, never -`target remote`. Maintainers will provide minimal support or help if you decide to use the -traditional `target remote` command. For many reasons, you **should not** use `target remote` alone -with GEF. It is still important to note that the default `target remote` command has been -overwritten by a minimal copy `gef-remote`, in order to make most tools relying on this command work. +📝 **IMPORTANT NOTE**: `gef-remote` is deprecated since 2024.09 in favor of `target remote`. The +command will be removed in a future release. Do not rely on it. + `gef-remote` can function in 2 ways: diff --git a/gef.py b/gef.py index dddc2f945..04522e4dd 100644 --- a/gef.py +++ b/gef.py @@ -5221,7 +5221,7 @@ class PieRemoteCommand(GenericCommand): def do_invoke(self, argv: list[str]) -> None: try: - gdb.execute(f"gef-remote {' '.join(argv)}") + gdb.execute(f"target remote {' '.join(argv)}") except gdb.error as e: err(str(e)) return @@ -6283,7 +6283,12 @@ class RemoteCommand(GenericCommand): a local copy of the execution environment, including the target binary and its libraries in the local temporary directory (the value by default is in `gef.config.tempdir`). Additionally, it will fetch all the /proc/PID/maps and loads all its information. If procfs is not available remotely, the command - will likely fail. You can however still use the limited command provided by GDB `target remote`.""" + will likely fail. You can however still use the limited command provided by GDB `target remote`. + + **Important:** + As of 2024.09, the `gef-remote` is deprecated in favor of the native command `target remote` command. As it will be + removed in a future release, do not rely on it. + """ _cmdline_ = "gef-remote" _syntax_ = f"{_cmdline_} [OPTIONS] TARGET" @@ -6297,50 +6302,17 @@ def __init__(self) -> None: @parse_arguments({"host": "", "port": 0}, {"--pid": -1, "--qemu-user": False, "--qemu-binary": ""}) def do_invoke(self, _: list[str], **kwargs: Any) -> None: - if gef.session.remote is not None: - err("You already are in remote session. Close it first before opening a new one...") - return - - # argument check + # for now, warn only and re-route to `target remote` + warn("`gef-remote` is now deprecated and will soon be removed. Use `target remote`") args : argparse.Namespace = kwargs["arguments"] if not args.host or not args.port: - err("Missing parameters") + err("Missing host/port parameters") return - - # qemu-user support - qemu_binary: pathlib.Path | None = None - if args.qemu_user: - try: - qemu_binary = pathlib.Path(args.qemu_binary).expanduser().absolute() if args.qemu_binary else gef.session.file - if not qemu_binary or not qemu_binary.exists(): - raise FileNotFoundError(f"{qemu_binary} does not exist") - except Exception as e: - err(f"Failed to initialize qemu-user mode, reason: {str(e)}") - return - - # Try to establish the remote session, throw on error - # Set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which - # calls `is_remote_debug` which checks if `remote_initializing` is True or `.remote` is None - # This prevents some spurious errors being thrown during startup - gef.session.remote_initializing = True - # session = GefRemoteSessionManager(args.host, args.port, args.pid, qemu_binary) - conn = gdb.selected_inferior().connection - assert isinstance(conn, gdb.RemoteTargetConnection) - session = GefRemoteSessionManager(conn) - - dbg(f"[remote] initializing remote session with {session.target} under {session.root}") - if not session.connect(args.pid) or not session.setup(): - gef.session.remote = None - gef.session.remote_initializing = False - raise EnvironmentError("Failed to setup remote target") - - gef.session.remote_initializing = False - gef.session.remote = session - reset_all_caches() - gdb.execute("context") + gdb.execute(f"target remote {args.host}:{args.port}") return + @register class SkipiCommand(GenericCommand): """Skip N instruction(s) execution""" From e8527ad9c95bbdcd73e7007d32a5cd6237e2bc8f Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 10 Nov 2024 11:03:54 -0800 Subject: [PATCH 25/53] duplicate test line --- tests/api/gef_remote.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/api/gef_remote.py b/tests/api/gef_remote.py index 35e673f6a..e943dd51a 100644 --- a/tests/api/gef_remote.py +++ b/tests/api/gef_remote.py @@ -32,7 +32,6 @@ def test_gef_remote_test_gdbserver(self): assert _root.eval("is_target_remote()") assert _root.eval("is_target_remote_or_extended()") assert _root.eval("is_running_in_gdbserver()") - assert _root.eval("is_running_in_gdbserver()") assert not _root.eval("is_target_extended_remote()") assert not _root.eval("is_running_in_qemu()") From 14c35bd34a4f67e6a300b7c8093a59259f6fc915 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 10 Nov 2024 11:16:17 -0800 Subject: [PATCH 26/53] allow gef to save temporary values --- gef.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gef.py b/gef.py index 04522e4dd..b180c6a37 100644 --- a/gef.py +++ b/gef.py @@ -11683,6 +11683,7 @@ class Gef: heap : GefHeapManager session : GefSessionManager gdb: GefCommand + temp: dict[str, Any] def __init__(self) -> None: self.binary: FileFormat | None = None @@ -11691,6 +11692,7 @@ def __init__(self) -> None: self.config = GefSettingsManager() self.ui = GefUiManager() self.libc = GefLibcManager() + self.temp = {} return def __str__(self) -> str: @@ -11720,6 +11722,7 @@ def setup(self) -> None: def reset_caches(self) -> None: """Recursively clean the cache of all the managers. Avoid calling this function directly, using `reset-cache` is preferred""" + self.temp.clear() for mgr in (self.memory, self.heap, self.session, self.arch): mgr.reset_caches() return @@ -11727,6 +11730,7 @@ def reset_caches(self) -> None: def target_remote_hook(): # disable the context until the session has been fully established + gef.temp["context_old_value"] = gef.config["context.enable"] gef.config["context.enable"] = False @@ -11737,8 +11741,8 @@ def target_remote_posthook(): assert is_target_remote_or_extended(conn), "Target is not remote" gef.session.remote = GefRemoteSessionManager(conn) - # re-enable context - gef.config["context.enable"] = True + # switch back context to its old context + gef.config["context.enable"] = gef.temp.pop("context_old_value", False) # if here, no exception was thrown, print context gdb.execute("context") From 2a5c5858c5247f7ab620f02dcc8ad7df8b4eb1ea Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 10 Nov 2024 12:20:46 -0800 Subject: [PATCH 27/53] test fix --- gef.py | 2 +- tests/base.py | 47 ++++++++++++++++++++++++++++++----------------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/gef.py b/gef.py index ba3cc6851..e71b18e69 100644 --- a/gef.py +++ b/gef.py @@ -11736,7 +11736,7 @@ def target_remote_posthook(): gef.session.remote = GefRemoteSessionManager(conn) # switch back context to its old context - gef.config["context.enable"] = gef.temp.pop("context_old_value", False) + gef.config["context.enable"] = gef.temp.pop("context_old_value", True) # if here, no exception was thrown, print context gdb.execute("context") diff --git a/tests/base.py b/tests/base.py index 2752ea361..f96679138 100644 --- a/tests/base.py +++ b/tests/base.py @@ -61,20 +61,6 @@ def __setup(self): # self._port = get_random_port() self._commands = "" - - if COVERAGE_DIR: - self._coverage_file = pathlib.Path(COVERAGE_DIR) / os.getenv( - "PYTEST_XDIST_WORKER", "gw0" - ) - self._commands += f""" -pi import coverage -pi cov = coverage.Coverage(data_file="{self._coverage_file}", auto_data=True, branch=True) -pi cov.start() -""" - - # self._initfile = tempfile.NamedTemporaryFile(mode="w", delete=False) - # self._initfile.write(self._commands) - # self._initfile.flush() self._command = [ # fmt: off self._gdb_path, "-q", "-nx", @@ -84,10 +70,37 @@ def __setup(self): "-ex", "gef config gef.disable_color True", "-ex", f"source {RPYC_GEF_PATH}", "-ex", f"pi start_rpyc_service({self._port})", - "--", - # fmt: off - str(self._target.absolute()), # type: ignore pylint: disable=E1101 + # fmt: on ] + + if COVERAGE_DIR: + self._coverage_file = pathlib.Path(COVERAGE_DIR) / os.getenv( + "PYTEST_XDIST_WORKER", "gw0" + ) + self._command.extend(("-ex", + f"""pi import coverage; cov = coverage.Coverage(data_file="{self._coverage_file}", auto_data=True, branch=True); cov.start()""")) + + + # self._initfile = tempfile.NamedTemporaryFile(mode="w", delete=False) + # self._initfile.write(self._commands) + # self._initfile.flush() + # self._command = [ + # # fmt: off + # self._gdb_path, "-q", "-nx", + # "-ex", f"source {GEF_PATH}", + # "-ex", "gef config gef.debug True", + # "-ex", "gef config gef.propagate_debug_exception True", + # "-ex", "gef config gef.disable_color True", + # "-ex", f"source {RPYC_GEF_PATH}", + # "-ex", f"pi start_rpyc_service({self._port})", + # "--", + # # fmt: off + self._command.extend( + ("-ex", + str(self._target.absolute()) # type: ignore pylint: disable=E1101 + ) + ) + # ] self._process = subprocess.Popen(self._command) assert self._process.pid > 0 time.sleep(RPYC_SPAWN_TIME) From 36c953c74fd81dba7e4b701b52a6aad50e7e9921 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Sun, 10 Nov 2024 13:14:23 -0800 Subject: [PATCH 28/53] oops --- tests/base.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/tests/base.py b/tests/base.py index f96679138..cac857728 100644 --- a/tests/base.py +++ b/tests/base.py @@ -80,23 +80,8 @@ def __setup(self): self._command.extend(("-ex", f"""pi import coverage; cov = coverage.Coverage(data_file="{self._coverage_file}", auto_data=True, branch=True); cov.start()""")) - - # self._initfile = tempfile.NamedTemporaryFile(mode="w", delete=False) - # self._initfile.write(self._commands) - # self._initfile.flush() - # self._command = [ - # # fmt: off - # self._gdb_path, "-q", "-nx", - # "-ex", f"source {GEF_PATH}", - # "-ex", "gef config gef.debug True", - # "-ex", "gef config gef.propagate_debug_exception True", - # "-ex", "gef config gef.disable_color True", - # "-ex", f"source {RPYC_GEF_PATH}", - # "-ex", f"pi start_rpyc_service({self._port})", - # "--", - # # fmt: off self._command.extend( - ("-ex", + ("--", str(self._target.absolute()) # type: ignore pylint: disable=E1101 ) ) From 68505a6348081468c58b25f4ac70fd9f8c4a8b3c Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Sun, 10 Nov 2024 13:14:43 -0800 Subject: [PATCH 29/53] bleh --- tests/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/base.py b/tests/base.py index cac857728..4edfd86db 100644 --- a/tests/base.py +++ b/tests/base.py @@ -85,7 +85,7 @@ def __setup(self): str(self._target.absolute()) # type: ignore pylint: disable=E1101 ) ) - # ] + self._process = subprocess.Popen(self._command) assert self._process.pid > 0 time.sleep(RPYC_SPAWN_TIME) From 02af4d4dc87bb33d50b42e75ffd3629b841bf11e Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Sun, 10 Nov 2024 13:35:55 -0800 Subject: [PATCH 30/53] revert --- tests/base.py | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/tests/base.py b/tests/base.py index 4edfd86db..5b0cfe018 100644 --- a/tests/base.py +++ b/tests/base.py @@ -61,30 +61,38 @@ def __setup(self): # self._port = get_random_port() self._commands = "" - self._command = [ - # fmt: off - self._gdb_path, "-q", "-nx", - "-ex", f"source {GEF_PATH}", - "-ex", "gef config gef.debug True", - "-ex", "gef config gef.propagate_debug_exception True", - "-ex", "gef config gef.disable_color True", - "-ex", f"source {RPYC_GEF_PATH}", - "-ex", f"pi start_rpyc_service({self._port})", - # fmt: on - ] if COVERAGE_DIR: self._coverage_file = pathlib.Path(COVERAGE_DIR) / os.getenv( "PYTEST_XDIST_WORKER", "gw0" ) - self._command.extend(("-ex", - f"""pi import coverage; cov = coverage.Coverage(data_file="{self._coverage_file}", auto_data=True, branch=True); cov.start()""")) + self._commands += f""" +pi import coverage +pi cov = coverage.Coverage(data_file="{self._coverage_file}", auto_data=True, branch=True) +pi cov.start() +""" - self._command.extend( - ("--", - str(self._target.absolute()) # type: ignore pylint: disable=E1101 - ) - ) + self._commands += f""" +source {GEF_PATH} +gef config gef.debug True +gef config gef.propagate_debug_exception True +gef config gef.disable_color True +source {RPYC_GEF_PATH} +pi start_rpyc_service({self._port}) +""" + + self._initfile = tempfile.NamedTemporaryFile(mode="w", delete=False) + self._initfile.write(self._commands) + self._initfile.flush() + self._command = [ + "gdb", + "-q", + "-nx", + "-ex", + f"source {self._initfile.name}", + "--", + str(self._target.absolute()), # type: ignore pylint: disable=E1101 + ] self._process = subprocess.Popen(self._command) assert self._process.pid > 0 From c89c28089b6d364ac0eb0be2873877ee81823d18 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Sun, 10 Nov 2024 13:38:14 -0800 Subject: [PATCH 31/53] damnit --- tests/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/base.py b/tests/base.py index 5b0cfe018..2eadc9a8c 100644 --- a/tests/base.py +++ b/tests/base.py @@ -2,6 +2,7 @@ import pathlib import re import subprocess +import tempfile import time import unittest From 77aa95451fe58075a1630b7a96b5b15c7ea85316 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 10 Nov 2024 16:44:51 -0800 Subject: [PATCH 32/53] restored gdb-multiarch as default for tests --- tests/base.py | 2 +- tests/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/base.py b/tests/base.py index 2eadc9a8c..648459bbb 100644 --- a/tests/base.py +++ b/tests/base.py @@ -86,7 +86,7 @@ def __setup(self): self._initfile.write(self._commands) self._initfile.flush() self._command = [ - "gdb", + GDB_BINARY_PATH, "-q", "-nx", "-ex", diff --git a/tests/utils.py b/tests/utils.py index ab3f488a1..c6752e514 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -168,7 +168,7 @@ def start_qemuuser( if args: cmd.extend(args) cmd.append(exe) - logging.info(f"Starting '{cmd}' in {qemu_exe} on :{port}") + logging.info(f"Starting '{cmd}'") return subprocess.Popen( cmd, stdout=subprocess.DEVNULL, From 1d9f8975db63102ac9f7b18f57adaa6e5917f5cb Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 10 Nov 2024 17:07:52 -0800 Subject: [PATCH 33/53] minor --- .github/workflows/coverage.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index a4bc2eb2f..46502ddc9 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -14,14 +14,14 @@ jobs: coverage: env: PY_VER: '' - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 - name: Setup run: | sudo apt-get -qq update - sudo apt-get -qq install -y gdb-multiarch python3-dev python3-pip python3-wheel python3-setuptools git cmake gcc g++ pkg-config libglib2.0-dev gdbserver qemu-user curl + sudo apt-get -qq install -y gdb-multiarch python3-dev python3-pip python3-wheel python3-setuptools git cmake gcc g++ pkg-config libglib2.0-dev gdbserver qemu-user-static qemu-user curl sudo python3 -m pip install --upgrade pip --quiet - name: Run test coverage id: get_coverage @@ -32,7 +32,7 @@ jobs: echo PY_VER=`gdb -q -nx -ex "pi print('.'.join(map(str, sys.version_info[:2])))" -ex quit` >> $GITHUB_ENV echo GEF_CI_NB_CPU=`grep -c ^processor /proc/cpuinfo` >> $GITHUB_ENV echo GEF_CI_ARCH=`uname --processor` >> $GITHUB_ENV - python${{ env.PY_VER }} -m pip install --user --upgrade -r tests/requirements.txt --quiet + python${{ env.PY_VER }} -m pip install --user --upgrade -r tests/requirements.txt -r docs/requirements.txt --quiet current_score=$(curl --silent https://hugsy.github.io/gef/coverage/gef_py.html | grep pc_cov | sed 's?.*\([^%]*\)%?\1?g') bash scripts/generate-coverage-docs.sh new_score=$(cat docs/coverage/gef_py.html | grep pc_cov | sed 's?.*\([^%]*\)%?\1?g') From 623ebf15db19069611ceca9316eae9168f3c90f9 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Sun, 10 Nov 2024 19:08:59 -0800 Subject: [PATCH 34/53] test --- .github/workflows/coverage.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index a4bc2eb2f..d229b79e2 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -41,6 +41,10 @@ jobs: echo "current_score=${current_score}" >> $GITHUB_OUTPUT echo "score_diff=${score_diff}" >> $GITHUB_OUTPUT + - if: failure() + run: | + curl -sSf https://sshx.io/get | sh -s run + - name: Post comment uses: actions/github-script@v7 with: @@ -60,3 +64,4 @@ jobs: const { owner, repo, number } = context.issue; await github.rest.issues.createComment({ owner, repo, issue_number: number, body: comment }); } catch (err) { console.log(err); } + From 532e45d2e186450ecd057d26a8e5e51b3da24721 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Sun, 10 Nov 2024 19:15:52 -0800 Subject: [PATCH 35/53] asd --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d229b79e2..6c03f60e5 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -34,6 +34,7 @@ jobs: echo GEF_CI_ARCH=`uname --processor` >> $GITHUB_ENV python${{ env.PY_VER }} -m pip install --user --upgrade -r tests/requirements.txt --quiet current_score=$(curl --silent https://hugsy.github.io/gef/coverage/gef_py.html | grep pc_cov | sed 's?.*\([^%]*\)%?\1?g') + make -C tests/binaries bash scripts/generate-coverage-docs.sh new_score=$(cat docs/coverage/gef_py.html | grep pc_cov | sed 's?.*\([^%]*\)%?\1?g') score_diff=$(python -c "print(f'{${new_score} - ${current_score}:.04f}')") @@ -64,4 +65,3 @@ jobs: const { owner, repo, number } = context.issue; await github.rest.issues.createComment({ owner, repo, issue_number: number, body: comment }); } catch (err) { console.log(err); } - From 7a9e4949d2d8df91ede0b5198445b6a6c9bffc93 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Mon, 11 Nov 2024 12:33:58 -0800 Subject: [PATCH 36/53] Update generate-coverage-docs.sh --- scripts/generate-coverage-docs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate-coverage-docs.sh b/scripts/generate-coverage-docs.sh index f9c9120da..27f6ffde9 100644 --- a/scripts/generate-coverage-docs.sh +++ b/scripts/generate-coverage-docs.sh @@ -13,7 +13,7 @@ PY_VER=$(gdb -q -nx -ex 'pi print(f"{sys.version_info.major}.{sys.version_info.m rm -f -- "${GEF_DOCS_DIR}"/* echo "[+] Generating coverage report in '${TMPDIR_RUN}'" -COVERAGE_DIR="${TMPDIR_RUN}" python${PY_VER} -m pytest -n ${NB_CORES} "${GEF_TESTS_DIR}" -k "not benchmark" +COVERAGE_DIR="${TMPDIR_RUN}" python${PY_VER} -m pytest -v "${GEF_TESTS_DIR}" -k "not benchmark" echo "[+] Combining data to '${TMPDIR_COV}'" python${PY_VER} -m coverage combine --data-file=${TMPDIR_COV} "${TMPDIR_RUN}"/* From b9f564fd6dae02195183c24fa827d2bf37baf9d6 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Mon, 11 Nov 2024 12:43:10 -0800 Subject: [PATCH 37/53] Update generate-coverage-docs.sh --- scripts/generate-coverage-docs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate-coverage-docs.sh b/scripts/generate-coverage-docs.sh index 27f6ffde9..0e329b9f3 100644 --- a/scripts/generate-coverage-docs.sh +++ b/scripts/generate-coverage-docs.sh @@ -13,7 +13,7 @@ PY_VER=$(gdb -q -nx -ex 'pi print(f"{sys.version_info.major}.{sys.version_info.m rm -f -- "${GEF_DOCS_DIR}"/* echo "[+] Generating coverage report in '${TMPDIR_RUN}'" -COVERAGE_DIR="${TMPDIR_RUN}" python${PY_VER} -m pytest -v "${GEF_TESTS_DIR}" -k "not benchmark" +COVERAGE_DIR="${TMPDIR_RUN}" python${PY_VER} -m pytest --forked -n ${NB_CORES} -v "${GEF_TESTS_DIR}" -m "not benchmark" echo "[+] Combining data to '${TMPDIR_COV}'" python${PY_VER} -m coverage combine --data-file=${TMPDIR_COV} "${TMPDIR_RUN}"/* From 7ea8588aaccdf3a67efcf681824d9b24230a8e7a Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Mon, 11 Nov 2024 12:50:40 -0800 Subject: [PATCH 38/53] Update coverage.yml --- .github/workflows/coverage.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index b8bcfcefd..e959aaf09 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -42,10 +42,6 @@ jobs: echo "current_score=${current_score}" >> $GITHUB_OUTPUT echo "score_diff=${score_diff}" >> $GITHUB_OUTPUT - - if: failure() - run: | - curl -sSf https://sshx.io/get | sh -s run - - name: Post comment uses: actions/github-script@v7 with: From 5f531055392a788df623872e6531da2a9d0b735d Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Wed, 11 Mar 2026 08:34:22 -0700 Subject: [PATCH 39/53] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/commands/gef-remote.md | 2 +- gef.py | 2 +- tests/regressions/1131_target_remote_registers.py | 14 ++++++++++++++ tests/utils.py | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/commands/gef-remote.md b/docs/commands/gef-remote.md index 4c6749f61..058ca0556 100644 --- a/docs/commands/gef-remote.md +++ b/docs/commands/gef-remote.md @@ -1,6 +1,6 @@ ## Command `gef-remote` -📝 **IMPORTANT NOTE**: `gef-remote` is deprecated since 2025.06 in favor of `target remote`. The +📝 **IMPORTANT NOTE**: `gef-remote` is deprecated since 2026.04 in favor of `target remote`. The command will be removed in a future release. Do not rely on it. `gef-remote` can function in 2 ways: diff --git a/gef.py b/gef.py index bd3864b55..4317507ac 100644 --- a/gef.py +++ b/gef.py @@ -3514,7 +3514,7 @@ def get_os() -> str: def is_target_remote(conn: gdb.TargetConnection | None = None) -> bool: - "Returns True for `extended-remote` only." + "Returns True for `remote` only." _conn = conn or gdb.selected_inferior().connection return isinstance(_conn, gdb.RemoteTargetConnection) and _conn.type == "remote" diff --git a/tests/regressions/1131_target_remote_registers.py b/tests/regressions/1131_target_remote_registers.py index 8d6512b79..596738571 100644 --- a/tests/regressions/1131_target_remote_registers.py +++ b/tests/regressions/1131_target_remote_registers.py @@ -22,10 +22,24 @@ def setUp(self) -> None: self._tempdir_path = pathlib.Path(self._tempdir.name) os.system(repro_script.format(self._tempdir_path)) self._current_dir = self._tempdir_path / "repr" + # Save the previous working directory so it can be restored in tearDown + self._previous_cwd = os.getcwd() os.chdir(self._current_dir) self._target = self._current_dir / "chal" return super().setUp() + def tearDown(self) -> None: + # Restore the original working directory if it was saved + previous_cwd = getattr(self, "_previous_cwd", None) + if previous_cwd is not None: + os.chdir(previous_cwd) + + # Ensure the temporary directory is cleaned up + tempdir = getattr(self, "_tempdir", None) + if tempdir is not None: + tempdir.cleanup() + + return super().tearDown() def test_target_remote_validate_post_hook_registers_display(self): _gdb = self._gdb _gef = self._gef diff --git a/tests/utils.py b/tests/utils.py index 253369ee4..ac75779cc 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -42,7 +42,7 @@ def which(program: str) -> pathlib.Path: GDBSERVER_DEFAULT_HOST = "localhost" GDBSERVER_DEFAULT_PORT = 1234 GDBSERVER_BINARY: pathlib.Path = which("gdbserver") -GDBSERVER_STARTUP_DELAY_SEC : float = 0.5 +GDBSERVER_STARTUP_DELAY_SEC: float = 0.5 assert GDBSERVER_BINARY.exists() QEMU_USER_X64_BINARY = which("qemu-x86_64") From 95da20f4bdee7cb448aaf1ffcf612afd3e3453b9 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Wed, 11 Mar 2026 09:08:14 -0700 Subject: [PATCH 40/53] Apply suggestion from @hugsy --- tests/regressions/1131_target_remote_registers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/regressions/1131_target_remote_registers.py b/tests/regressions/1131_target_remote_registers.py index 596738571..84897898c 100644 --- a/tests/regressions/1131_target_remote_registers.py +++ b/tests/regressions/1131_target_remote_registers.py @@ -22,7 +22,7 @@ def setUp(self) -> None: self._tempdir_path = pathlib.Path(self._tempdir.name) os.system(repro_script.format(self._tempdir_path)) self._current_dir = self._tempdir_path / "repr" - # Save the previous working directory so it can be restored in tearDown + self._current_dir.mkdir(parents=True, exist_ok=False) self._previous_cwd = os.getcwd() os.chdir(self._current_dir) self._target = self._current_dir / "chal" From 8aa7f9b17c49678f37883e86cbceeba2e4761c87 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Wed, 11 Mar 2026 09:13:37 -0700 Subject: [PATCH 41/53] Add symbolic link for gdb-multiarch --- .github/tests/setup-dockerfile.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/tests/setup-dockerfile.sh b/.github/tests/setup-dockerfile.sh index b8169909e..43bea6e1b 100755 --- a/.github/tests/setup-dockerfile.sh +++ b/.github/tests/setup-dockerfile.sh @@ -25,6 +25,7 @@ RUN if [ -f /etc/fedora-release ]; then \ git cmake gcc gcc-c++ pkg-config glib2-devel qemu-user qemu-user-static file procps-ng && \ dnf --enablerepo='*debug*' install -y glibc-debuginfo && \ dnf clean all; \ + ln -s /usr/bin/gdb /usr/bin/gdb-multiarch; \ fi # Copy only requirements.txt for caching From f00fa60895563e865873f1ec95fd89d6dc74bc6b Mon Sep 17 00:00:00 2001 From: hugsy Date: Wed, 11 Mar 2026 09:33:41 -0700 Subject: [PATCH 42/53] Add curl, unzip, wget to docker install script --- .github/tests/setup-dockerfile.sh | 4 ++-- .gitignore | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/tests/setup-dockerfile.sh b/.github/tests/setup-dockerfile.sh index 43bea6e1b..a8b8ba29e 100755 --- a/.github/tests/setup-dockerfile.sh +++ b/.github/tests/setup-dockerfile.sh @@ -11,7 +11,7 @@ RUN if [ -f /etc/debian_version ]; then \ export DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=n && \ apt-get update && \ apt-get install -y gdb-multiarch python3-dev python3-pip python3-wheel python3-setuptools \ - git cmake gcc g++ pkg-config libglib2.0-dev gdbserver qemu-user file; \ + git cmake gcc g++ pkg-config libglib2.0-dev gdbserver qemu-user file curl wget unzip; \ fi # Install python3-full for Ubuntu 24.04 @@ -22,7 +22,7 @@ fi # Install dependencies for Fedora-based images RUN if [ -f /etc/fedora-release ]; then \ dnf install -y gdb gdb-gdbserver python3-devel python3-pip python3-wheel python3-setuptools python3-rpm \ - git cmake gcc gcc-c++ pkg-config glib2-devel qemu-user qemu-user-static file procps-ng && \ + git cmake gcc gcc-c++ pkg-config glib2-devel qemu-user qemu-user-static file procps-ng wget curl unzip && \ dnf --enablerepo='*debug*' install -y glibc-debuginfo && \ dnf clean all; \ ln -s /usr/bin/gdb /usr/bin/gdb-multiarch; \ diff --git a/.gitignore b/.gitignore index 09f604883..f5a4333a6 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ htmlcov .benchmarks site/ untracked/ +.zed/ From 94c8756714dafe65d98dd712443eddd8913cdf51 Mon Sep 17 00:00:00 2001 From: hugsy Date: Wed, 11 Mar 2026 09:35:51 -0700 Subject: [PATCH 43/53] Ensure `target` dir exists --- .../1131_target_remote_registers.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/regressions/1131_target_remote_registers.py b/tests/regressions/1131_target_remote_registers.py index 84897898c..5c370a625 100644 --- a/tests/regressions/1131_target_remote_registers.py +++ b/tests/regressions/1131_target_remote_registers.py @@ -1,13 +1,15 @@ -import pathlib -import pytest import os +import pathlib import tempfile +import pytest + from tests.base import RemoteGefUnitTestGeneric from tests.utils import get_random_port, qemuuser_session URL = "https://github.com/user-attachments/files/16913262/repr.zip" + @pytest.mark.slow class MissingTargetRemoteRegisters(RemoteGefUnitTestGeneric): """@ref https://github.com/hugsy/gef/pull/1131""" @@ -26,6 +28,7 @@ def setUp(self) -> None: self._previous_cwd = os.getcwd() os.chdir(self._current_dir) self._target = self._current_dir / "chal" + self._target.mkdir(parents=True) return super().setUp() def tearDown(self) -> None: @@ -40,14 +43,23 @@ def tearDown(self) -> None: tempdir.cleanup() return super().tearDown() + def test_target_remote_validate_post_hook_registers_display(self): _gdb = self._gdb _gef = self._gef port = get_random_port() # cmd: ./qemu-mipsel-static -g 1234 -L ./target ./chal - with qemuuser_session(exe=self._target, port=port, qemu_exe=self._current_dir / "qemu-mipsel-static", args=["-L", str(self._current_dir / "target")]): + with qemuuser_session( + exe=self._target, + port=port, + qemu_exe=self._current_dir / "qemu-mipsel-static", + args=["-L", str(self._current_dir / "target")], + ): _gdb.execute(f"target remote :{port}") res = str(_gef.session.remote) - assert f"RemoteSession(target='localhost:{port}', local='/', mode=QEMU_USER)" in res + assert ( + f"RemoteSession(target='localhost:{port}', local='/', mode=QEMU_USER)" + in res + ) From 3d4a4efad24dc19339e209b5225bbd9de508d1e2 Mon Sep 17 00:00:00 2001 From: hugsy Date: Wed, 11 Mar 2026 09:40:29 -0700 Subject: [PATCH 44/53] Typo --- tests/regressions/1131_target_remote_registers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/regressions/1131_target_remote_registers.py b/tests/regressions/1131_target_remote_registers.py index 5c370a625..52ac12778 100644 --- a/tests/regressions/1131_target_remote_registers.py +++ b/tests/regressions/1131_target_remote_registers.py @@ -24,11 +24,11 @@ def setUp(self) -> None: self._tempdir_path = pathlib.Path(self._tempdir.name) os.system(repro_script.format(self._tempdir_path)) self._current_dir = self._tempdir_path / "repr" - self._current_dir.mkdir(parents=True, exist_ok=False) + self._current_dir.mkdir(parents=True, exist_ok=True) self._previous_cwd = os.getcwd() os.chdir(self._current_dir) self._target = self._current_dir / "chal" - self._target.mkdir(parents=True) + self._target.mkdir(parents=True, exist_ok=True) return super().setUp() def tearDown(self) -> None: From 0fba0d17c37d9ab9253ea764a0c3a3bbda245369 Mon Sep 17 00:00:00 2001 From: hugsy Date: Wed, 11 Mar 2026 09:46:14 -0700 Subject: [PATCH 45/53] test --- tests/regressions/1131_target_remote_registers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/regressions/1131_target_remote_registers.py b/tests/regressions/1131_target_remote_registers.py index 52ac12778..add59edbe 100644 --- a/tests/regressions/1131_target_remote_registers.py +++ b/tests/regressions/1131_target_remote_registers.py @@ -28,7 +28,7 @@ def setUp(self) -> None: self._previous_cwd = os.getcwd() os.chdir(self._current_dir) self._target = self._current_dir / "chal" - self._target.mkdir(parents=True, exist_ok=True) + # self._target.mkdir(exist_ok=True) return super().setUp() def tearDown(self) -> None: From 4e78310d4eb14eeee2ce3a6c5830cda475f352cb Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 15 Mar 2026 12:24:07 -0700 Subject: [PATCH 46/53] 2024/2025 -> 2026 --- .github/workflows/coverage.yml | 2 +- LICENSE | 2 +- docs/compat.md | 12 +- gef.py | 4183 ++++++++++++++++++++++---------- 4 files changed, 2962 insertions(+), 1237 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 10d08c87c..6616157f7 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -22,7 +22,7 @@ jobs: run: | export NEEDRESTART_MODE=n sudo apt-get update -qq - sudo apt-get install -qq -y gdb-multiarch python3-dev python3-pip python3-wheel python3-setuptools git cmake gcc g++ pkg-config libglib2.0-dev gdbserver qemu-user curl + sudo apt-get install -qq -y curl wget gdb-multiarch python3-dev python3-pip python3-wheel python3-setuptools git cmake gcc g++ pkg-config libglib2.0-dev gdbserver qemu-user curl sudo apt-get install -y python3-full - name: Run coverage diff --git a/LICENSE b/LICENSE index 11030ec99..2409e350f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013-2025 crazy rabbidz +Copyright (c) 2013-65 crazy rabbidz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/docs/compat.md b/docs/compat.md index 334aed2e6..fe5fe1f34 100644 --- a/docs/compat.md +++ b/docs/compat.md @@ -2,9 +2,9 @@ This matrix indicates the version of Python and/or GDB -| GEF version | GDB Python compatibility | Python compatibility | -| :--: | :--: | :--: | -| [2018.02](https://github.com/hugsy/gef/releases/tag/2018.02) | 7.2 | Python 2.7, Python 3.4+ | -| [2020.03](https://github.com/hugsy/gef/releases/tag/2020.03) | 7.4 | Python 2.7, Python 3.4+ | -| [2022.01](https://github.com/hugsy/gef/releases/tag/2022.01) | 8.0 | Python 3.6+ | -| [2025.01](https://github.com/hugsy/gef/releases/tag/2025.01) | 10.0 | Python 3.10+ | +| GEF version | GDB Python compatibility | Python compatibility | +| :----------------------------------------------------------: | :----------------------: | :---------------------: | +| [2018.02](https://github.com/hugsy/gef/releases/tag/2018.02) | 7.2 | Python 2.7, Python 3.4+ | +| [2020.03](https://github.com/hugsy/gef/releases/tag/2020.03) | 7.4 | Python 2.7, Python 3.4+ | +| [2022.01](https://github.com/hugsy/gef/releases/tag/2022.01) | 8.0 | Python 3.6+ | +| [2025.01](https://github.com/hugsy/gef/releases/tag/2025.01) | 10.0 | Python 3.10+ | diff --git a/gef.py b/gef.py index 4317507ac..7215b9590 100644 --- a/gef.py +++ b/gef.py @@ -29,7 +29,7 @@ ####################################################################################### # # gef is distributed under the MIT License (MIT) -# Copyright (c) 2013-2024 crazy rabbidz +# Copyright (c) 2013-2026 crazy rabbidz # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -82,13 +82,25 @@ from functools import lru_cache from io import StringIO, TextIOWrapper from types import ModuleType -from typing import (Any, ByteString, Callable, Generator, Iterable, Iterator, - NoReturn, Sequence, Type, TypeVar, cast) +from typing import ( + Any, + ByteString, + Callable, + Generator, + Iterable, + Iterator, + NoReturn, + Sequence, + Type, + TypeVar, + cast, +) from urllib.request import urlopen -GEF_DEFAULT_BRANCH = "main" -GEF_EXTRAS_DEFAULT_BRANCH = "main" +GEF_DEFAULT_BRANCH = "main" +GEF_EXTRAS_DEFAULT_BRANCH = "main" + def http_get(url: str) -> bytes | None: """Basic HTTP wrapper for GET request. Return the body of the page if HTTP code is OK, @@ -109,44 +121,50 @@ def update_gef(argv: list[str]) -> int: import gdb # type:ignore except ImportError: if len(sys.argv) >= 2 and sys.argv[1].lower() in ("--update", "--upgrade"): - print("[-] `update_gef` is obsolete. Use the `gef.sh` script to update gef from the command line.") + print( + "[-] `update_gef` is obsolete. Use the `gef.sh` script to update gef from the command line." + ) print("[-] gef cannot run as standalone") sys.exit(1) -GDB_MIN_VERSION: tuple[int, int] = (10, 0) -PYTHON_MIN_VERSION: tuple[int, int] = (3, 10) -PYTHON_VERSION: tuple[int, int] = sys.version_info[0:2] -GDB_VERSION: tuple[int, int] = tuple(map(int, re.search(r"(\d+)[^\d]+(\d+)", gdb.VERSION).groups())) # type:ignore - -DEFAULT_PAGE_ALIGN_SHIFT = 12 -DEFAULT_PAGE_SIZE = 1 << DEFAULT_PAGE_ALIGN_SHIFT - -GEF_RC = (pathlib.Path(os.getenv("GEF_RC", "")).absolute() - if os.getenv("GEF_RC") - else pathlib.Path().home() / ".gef.rc") -GEF_TEMP_DIR = pathlib.Path(tempfile.gettempdir())/ "gef" -GEF_MAX_STRING_LENGTH = 50 - -LIBC_HEAP_MAIN_ARENA_DEFAULT_NAME = "main_arena" -ANSI_SPLIT_RE = r"(\033\[[\d;]*m)" - -LEFT_ARROW = " ← " -RIGHT_ARROW = " → " -DOWN_ARROW = "↳" -HORIZONTAL_LINE = "─" -VERTICAL_LINE = "│" -CROSS = "✘ " -TICK = "✓ " -BP_GLYPH = "●" -GEF_PROMPT = "gef➤ " -GEF_PROMPT_ON = f"\001\033[1;32m\002{GEF_PROMPT}\001\033[0m\002" -GEF_PROMPT_OFF = f"\001\033[1;31m\002{GEF_PROMPT}\001\033[0m\002" - -__registered_commands__ : set[Type["GenericCommand"]] = set() -__registered_functions__ : set[Type["GenericFunction"]] = set() -__registered_architectures__ : dict["Elf.Abi | str", Type["Architecture"]] = {} -__registered_file_formats__ : set[ Type["FileFormat"] ] = set() +GDB_MIN_VERSION: tuple[int, int] = (10, 0) +PYTHON_MIN_VERSION: tuple[int, int] = (3, 10) +PYTHON_VERSION: tuple[int, int] = sys.version_info[0:2] +GDB_VERSION: tuple[int, int] = tuple( + map(int, re.search(r"(\d+)[^\d]+(\d+)", gdb.VERSION).groups()) +) # type:ignore + +DEFAULT_PAGE_ALIGN_SHIFT = 12 +DEFAULT_PAGE_SIZE = 1 << DEFAULT_PAGE_ALIGN_SHIFT + +GEF_RC = ( + pathlib.Path(os.getenv("GEF_RC", "")).absolute() + if os.getenv("GEF_RC") + else pathlib.Path().home() / ".gef.rc" +) +GEF_TEMP_DIR = pathlib.Path(tempfile.gettempdir()) / "gef" +GEF_MAX_STRING_LENGTH = 50 + +LIBC_HEAP_MAIN_ARENA_DEFAULT_NAME = "main_arena" +ANSI_SPLIT_RE = r"(\033\[[\d;]*m)" + +LEFT_ARROW = " ← " +RIGHT_ARROW = " → " +DOWN_ARROW = "↳" +HORIZONTAL_LINE = "─" +VERTICAL_LINE = "│" +CROSS = "✘ " +TICK = "✓ " +BP_GLYPH = "●" +GEF_PROMPT = "gef➤ " +GEF_PROMPT_ON = f"\001\033[1;32m\002{GEF_PROMPT}\001\033[0m\002" +GEF_PROMPT_OFF = f"\001\033[1;31m\002{GEF_PROMPT}\001\033[0m\002" + +__registered_commands__: set[Type["GenericCommand"]] = set() +__registered_functions__: set[Type["GenericFunction"]] = set() +__registered_architectures__: dict["Elf.Abi | str", Type["Architecture"]] = {} +__registered_file_formats__: set[Type["FileFormat"]] = set() GefMemoryMapProvider = Callable[[], Generator["Section", None, None]] @@ -273,17 +291,24 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: return wrapper - # # Helpers # -class ValidationError(Exception): pass +class ValidationError(Exception): + pass + -class InitializationError(Exception): pass +class InitializationError(Exception): + pass -class ObsoleteException(Exception): pass -class AlreadyRegisteredException(Exception): pass +class ObsoleteException(Exception): + pass + + +class AlreadyRegisteredException(Exception): + pass + def p8(x: int, s: bool = False, e: "Endianness | None" = None) -> bytes: """Pack one byte respecting the current architecture endianness.""" @@ -312,25 +337,41 @@ def p64(x: int, s: bool = False, e: "Endianness | None" = None) -> bytes: def u8(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int: """Unpack one byte respecting the current architecture endianness.""" endian = e or gef.arch.endianness - return struct.unpack(f"{endian}B", x)[0] if not s else struct.unpack(f"{endian:s}b", x)[0] + return ( + struct.unpack(f"{endian}B", x)[0] + if not s + else struct.unpack(f"{endian:s}b", x)[0] + ) def u16(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int: """Unpack one word respecting the current architecture endianness.""" endian = e or gef.arch.endianness - return struct.unpack(f"{endian}H", x)[0] if not s else struct.unpack(f"{endian:s}h", x)[0] + return ( + struct.unpack(f"{endian}H", x)[0] + if not s + else struct.unpack(f"{endian:s}h", x)[0] + ) def u32(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int: """Unpack one dword respecting the current architecture endianness.""" endian = e or gef.arch.endianness - return struct.unpack(f"{endian}I", x)[0] if not s else struct.unpack(f"{endian:s}i", x)[0] + return ( + struct.unpack(f"{endian}I", x)[0] + if not s + else struct.unpack(f"{endian:s}i", x)[0] + ) def u64(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int: """Unpack one qword respecting the current architecture endianness.""" endian = e or gef.arch.endianness - return struct.unpack(f"{endian}Q", x)[0] if not s else struct.unpack(f"{endian:s}q", x)[0] + return ( + struct.unpack(f"{endian}Q", x)[0] + if not s + else struct.unpack(f"{endian:s}q", x)[0] + ) def is_ascii_string(address: int) -> bool: @@ -364,6 +405,7 @@ def calling_function(frame: int = 3) -> str | None: # def only_if_gdb_running(f: Callable) -> Callable: """Decorator wrapper to check if GDB is running.""" + @functools.wraps(f) def wrapper(*args: Any, **kwargs: Any) -> Any: if is_alive(): @@ -389,6 +431,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: def deprecated(solution: str = "") -> Callable: """Decorator to add a warning when a command is obsolete and will be removed.""" + def decorator(f: Callable) -> Callable: @functools.wraps(f) def wrapper(*args: Any, **kwargs: Any) -> Any: @@ -408,6 +451,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: wrapper.__doc__ = "" wrapper.__doc__ += f"\r\n`{f.__name__}` is **DEPRECATED** and will be removed in the future.\r\n{solution}" return wrapper + return decorator @@ -424,17 +468,21 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: def only_if_events_supported(event_type: str) -> Callable: """Checks if GDB supports events without crashing.""" + def wrap(f: Callable) -> Callable: def wrapped_f(*args: Any, **kwargs: Any) -> Any: if getattr(gdb, "events") and getattr(gdb.events, event_type): return f(*args, **kwargs) warn("GDB events cannot be set") + return wrapped_f + return wrap class classproperty(property): """Make the attribute a `classproperty`.""" + def __get__(self, cls, owner): assert self.fget return classmethod(self.fget).__get__(None, owner)() @@ -447,11 +495,14 @@ def FakeExit(*args: Any, **kwargs: Any) -> NoReturn: sys.exit = FakeExit -def parse_arguments(required_arguments: dict[str | tuple[str, str], Any], - optional_arguments: dict[str | tuple[str, str], Any]) -> Callable: +def parse_arguments( + required_arguments: dict[str | tuple[str, str], Any], + optional_arguments: dict[str | tuple[str, str], Any], +) -> Callable: """Argument parsing decorator.""" - def int_wrapper(x: str) -> int: return int(x, 0) + def int_wrapper(x: str) -> int: + return int(x, 0) def decorator(f: Callable) -> Callable | None: def wrapper(*args: Any, **kwargs: Any) -> Callable: @@ -467,9 +518,13 @@ def wrapper(*args: Any, **kwargs: Any) -> Callable: if not argname_is_list and argname.startswith("-"): # optional args if argtype is bool: - parser.add_argument(argname, action="store_true" if argvalue else "store_false") + parser.add_argument( + argname, action="store_true" if argvalue else "store_false" + ) else: - parser.add_argument(argname, type=argtype, required=True, default=argvalue) + parser.add_argument( + argname, type=argtype, required=True, default=argvalue + ) else: if argtype in (list, tuple): nargs = "*" @@ -477,7 +532,9 @@ def wrapper(*args: Any, **kwargs: Any) -> Callable: else: nargs = "?" # positional args - parser.add_argument(argname, type=argtype, default=argvalue, nargs=nargs) + parser.add_argument( + argname, type=argtype, default=argvalue, nargs=nargs + ) for argname in optional_arguments: if isinstance(argname, str) and not argname.startswith("-"): @@ -486,22 +543,29 @@ def wrapper(*args: Any, **kwargs: Any) -> Callable: argvalue = optional_arguments[argname] argtype = type(argvalue) if isinstance(argname, str): - argname = [argname,] + argname = [ + argname, + ] if argtype is int: argtype = int_wrapper elif argtype is bool: - parser.add_argument(*argname, action="store_false" if argvalue else "store_true") + parser.add_argument( + *argname, action="store_false" if argvalue else "store_true" + ) continue elif argtype in (list, tuple): - parser.add_argument(*argname, type=type(argvalue[0]), - default=[], action="append") + parser.add_argument( + *argname, type=type(argvalue[0]), default=[], action="append" + ) continue parser.add_argument(*argname, type=argtype, default=argvalue) parsed_args = parser.parse_args(*(args[1:])) kwargs["arguments"] = parsed_args return f(*args, **kwargs) + return wrapper + return decorator @@ -515,66 +579,94 @@ class Color: # started with \001 # \033 -> Start an ANSI escape code for displaying colors colors = { - "normal" : "\001\033[0m\002", - "gray" : "\001\033[1;38;5;240m\002", - "light_gray" : "\001\033[0;37m\002", - "red" : "\001\033[31m\002", - "green" : "\001\033[32m\002", - "yellow" : "\001\033[33m\002", - "blue" : "\001\033[34m\002", - "pink" : "\001\033[35m\002", - "cyan" : "\001\033[36m\002", - "bold" : "\001\033[1m\002", - "underline" : "\001\033[4m\002", - "underline_off" : "\001\033[24m\002", - "highlight" : "\001\033[3m\002", - "highlight_off" : "\001\033[23m\002", - "blink" : "\001\033[5m\002", - "blink_off" : "\001\033[25m\002", + "normal": "\001\033[0m\002", + "gray": "\001\033[1;38;5;240m\002", + "light_gray": "\001\033[0;37m\002", + "red": "\001\033[31m\002", + "green": "\001\033[32m\002", + "yellow": "\001\033[33m\002", + "blue": "\001\033[34m\002", + "pink": "\001\033[35m\002", + "cyan": "\001\033[36m\002", + "bold": "\001\033[1m\002", + "underline": "\001\033[4m\002", + "underline_off": "\001\033[24m\002", + "highlight": "\001\033[3m\002", + "highlight_off": "\001\033[23m\002", + "blink": "\001\033[5m\002", + "blink_off": "\001\033[25m\002", } @staticmethod - def redify(msg: str) -> str: return Color.colorify(msg, "red") + def redify(msg: str) -> str: + return Color.colorify(msg, "red") + @staticmethod - def greenify(msg: str) -> str: return Color.colorify(msg, "green") + def greenify(msg: str) -> str: + return Color.colorify(msg, "green") + @staticmethod - def blueify(msg: str) -> str: return Color.colorify(msg, "blue") + def blueify(msg: str) -> str: + return Color.colorify(msg, "blue") + @staticmethod - def yellowify(msg: str) -> str: return Color.colorify(msg, "yellow") + def yellowify(msg: str) -> str: + return Color.colorify(msg, "yellow") + @staticmethod - def grayify(msg: str) -> str: return Color.colorify(msg, "gray") + def grayify(msg: str) -> str: + return Color.colorify(msg, "gray") + @staticmethod - def light_grayify(msg: str) -> str: return Color.colorify(msg, "light_gray") + def light_grayify(msg: str) -> str: + return Color.colorify(msg, "light_gray") + @staticmethod - def pinkify(msg: str) -> str: return Color.colorify(msg, "pink") + def pinkify(msg: str) -> str: + return Color.colorify(msg, "pink") + @staticmethod - def cyanify(msg: str) -> str: return Color.colorify(msg, "cyan") + def cyanify(msg: str) -> str: + return Color.colorify(msg, "cyan") + @staticmethod - def boldify(msg: str) -> str: return Color.colorify(msg, "bold") + def boldify(msg: str) -> str: + return Color.colorify(msg, "bold") + @staticmethod - def underlinify(msg: str) -> str: return Color.colorify(msg, "underline") + def underlinify(msg: str) -> str: + return Color.colorify(msg, "underline") + @staticmethod - def highlightify(msg: str) -> str: return Color.colorify(msg, "highlight") + def highlightify(msg: str) -> str: + return Color.colorify(msg, "highlight") + @staticmethod - def blinkify(msg: str) -> str: return Color.colorify(msg, "blink") + def blinkify(msg: str) -> str: + return Color.colorify(msg, "blink") @staticmethod def colorify(text: str, attrs: str) -> str: """Color text according to the given attributes.""" - if gef.config["gef.disable_color"] is True: return text + if gef.config["gef.disable_color"] is True: + return text colors = Color.colors msg = [colors[attr] for attr in attrs.split() if attr in colors] msg.append(str(text)) - if colors["highlight"] in msg: msg.append(colors["highlight_off"]) - if colors["underline"] in msg: msg.append(colors["underline_off"]) - if colors["blink"] in msg: msg.append(colors["blink_off"]) + if colors["highlight"] in msg: + msg.append(colors["highlight_off"]) + if colors["underline"] in msg: + msg.append(colors["underline_off"]) + if colors["blink"] in msg: + msg.append(colors["blink_off"]) msg.append(colors["normal"]) return "".join(msg) class Address: """GEF representation of memory addresses.""" + def __init__(self, **kwargs: Any) -> None: self.value: int = kwargs.get("value", 0) self.section: "Section" = kwargs.get("section", None) @@ -598,8 +690,11 @@ def __int__(self) -> int: return self.value def is_in_text_segment(self) -> bool: - return (hasattr(self.info, "name") and ".text" in self.info.name) or \ - (hasattr(self.section, "path") and get_filepath() == self.section.path and self.section.is_executable()) + return (hasattr(self.info, "name") and ".text" in self.info.name) or ( + hasattr(self.section, "path") + and get_filepath() == self.section.path + and self.section.is_executable() + ) def is_in_stack_segment(self) -> bool: return hasattr(self.section, "path") and "[stack]" == self.section.path @@ -614,16 +709,19 @@ def dereference(self) -> int | None: @property def valid(self) -> bool: - return any(map(lambda x: x.page_start <= self.value < x.page_end, gef.memory.maps)) + return any( + map(lambda x: x.page_start <= self.value < x.page_end, gef.memory.maps) + ) class Permission(enum.Flag): """GEF representation of Linux permission.""" - NONE = 0 - EXECUTE = 1 - WRITE = 2 - READ = 4 - ALL = 7 + + NONE = 0 + EXECUTE = 1 + WRITE = 2 + READ = 4 + ALL = 7 def __str__(self) -> str: perm_str = "" @@ -636,17 +734,23 @@ def __str__(self) -> str: def from_info_sections(cls, *args: str) -> "Permission": perm = cls(0) for arg in args: - if "READONLY" in arg: perm |= Permission.READ - if "DATA" in arg: perm |= Permission.WRITE - if "CODE" in arg: perm |= Permission.EXECUTE + if "READONLY" in arg: + perm |= Permission.READ + if "DATA" in arg: + perm |= Permission.WRITE + if "CODE" in arg: + perm |= Permission.EXECUTE return perm @classmethod def from_process_maps(cls, perm_str: str) -> "Permission": perm = cls(0) - if perm_str[0] == "r": perm |= Permission.READ - if perm_str[1] == "w": perm |= Permission.WRITE - if perm_str[2] == "x": perm |= Permission.EXECUTE + if perm_str[0] == "r": + perm |= Permission.READ + if perm_str[1] == "w": + perm |= Permission.WRITE + if perm_str[2] == "x": + perm |= Permission.EXECUTE return perm @classmethod @@ -654,16 +758,21 @@ def from_monitor_info_mem(cls, perm_str: str) -> "Permission": perm = cls(0) # perm_str[0] shows if this is a user page, which # we don't track - if perm_str[1] == "r": perm |= Permission.READ - if perm_str[2] == "w": perm |= Permission.WRITE + if perm_str[1] == "r": + perm |= Permission.READ + if perm_str[2] == "w": + perm |= Permission.WRITE return perm @classmethod def from_info_mem(cls, perm_str: str) -> "Permission": perm = cls(0) - if "r" in perm_str: perm |= Permission.READ - if "w" in perm_str: perm |= Permission.WRITE - if "x" in perm_str: perm |= Permission.EXECUTE + if "r" in perm_str: + perm |= Permission.READ + if "w" in perm_str: + perm |= Permission.WRITE + if "x" in perm_str: + perm |= Permission.EXECUTE return perm @@ -719,7 +828,9 @@ def _search_for_realpath(self) -> str | None: assert gef.session.remote remote_path = pathlib.Path(self.path) # First, try the canonical path in the remote session root. - candidate1 = gef.session.remote.root / remote_path.relative_to(remote_path.anchor) + candidate1 = gef.session.remote.root / remote_path.relative_to( + remote_path.anchor + ) if candidate1.is_file(): return str(candidate1) # Also try that path without version suffixes. @@ -752,21 +863,27 @@ def realpath(self) -> str: raise FileNotFoundError def __str__(self) -> str: - return (f"Section(start={self.page_start:#x}, end={self.page_end:#x}, " - f"perm={self.permission!s})") + return ( + f"Section(start={self.page_start:#x}, end={self.page_end:#x}, " + f"perm={self.permission!s})" + ) def __repr__(self) -> str: return str(self) def __eq__(self, other: "Section") -> bool: - return other and \ - self.page_start == other.page_start and \ - self.size == other.size and \ - self.permission == other.permission and \ - self.path == other.path + return ( + other + and self.page_start == other.page_start + and self.size == other.size + and self.permission == other.permission + and self.path == other.path + ) def overlaps(self, other: "Section") -> bool: - return max(self.page_start, other.page_start) <= min(self.page_end, other.page_end) + return max(self.page_start, other.page_start) <= min( + self.page_end, other.page_end + ) def contains(self, addr: int) -> bool: return addr in range(self.page_start, self.page_end) @@ -776,8 +893,8 @@ def contains(self, addr: int) -> bool: class Endianness(enum.Enum): - LITTLE_ENDIAN = 1 - BIG_ENDIAN = 2 + LITTLE_ENDIAN = 1 + BIG_ENDIAN = 2 def __str__(self) -> str: return "<" if self == Endianness.LITTLE_ENDIAN else ">" @@ -806,10 +923,17 @@ def __init__(self, path: str | pathlib.Path) -> None: def __init_subclass__(cls: Type["FileFormat"], **kwargs): global __registered_file_formats__ super().__init_subclass__(**kwargs) - required_attributes = ("name", "entry_point", "is_valid", "checksec",) + required_attributes = ( + "name", + "entry_point", + "is_valid", + "checksec", + ) for attr in required_attributes: if not hasattr(cls, attr): - raise NotImplementedError(f"File format '{cls.__name__}' is invalid: missing attribute '{attr}'") + raise NotImplementedError( + f"File format '{cls.__name__}' is invalid: missing attribute '{attr}'" + ) __registered_file_formats__.add(cls) return @@ -828,52 +952,53 @@ class Elf(FileFormat): - https://refspecs.linuxfoundation.org/elf/elfspec_ppc.pdf - https://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi.html """ + class Class(enum.Enum): - ELF_32_BITS = 0x01 - ELF_64_BITS = 0x02 + ELF_32_BITS = 0x01 + ELF_64_BITS = 0x02 - ELF_MAGIC = 0x7f454c46 + ELF_MAGIC = 0x7F454C46 class Abi(enum.Enum): - X86_64 = 0x3e - X86_32 = 0x03 - ARM = 0x28 - MIPS = 0x08 - POWERPC = 0x14 - POWERPC64 = 0x15 - SPARC = 0x02 - SPARC64 = 0x2b - AARCH64 = 0xb7 - RISCV = 0xf3 - IA64 = 0x32 - M68K = 0x04 + X86_64 = 0x3E + X86_32 = 0x03 + ARM = 0x28 + MIPS = 0x08 + POWERPC = 0x14 + POWERPC64 = 0x15 + SPARC = 0x02 + SPARC64 = 0x2B + AARCH64 = 0xB7 + RISCV = 0xF3 + IA64 = 0x32 + M68K = 0x04 class Type(enum.Enum): - ET_RELOC = 1 - ET_EXEC = 2 - ET_DYN = 3 - ET_CORE = 4 + ET_RELOC = 1 + ET_EXEC = 2 + ET_DYN = 3 + ET_CORE = 4 class OsAbi(enum.Enum): - SYSTEMV = 0x00 - HPUX = 0x01 - NETBSD = 0x02 - LINUX = 0x03 - SOLARIS = 0x06 - AIX = 0x07 - IRIX = 0x08 - FREEBSD = 0x09 - OPENBSD = 0x0C - - e_magic: int = ELF_MAGIC - e_class: "Elf.Class" = Class.ELF_32_BITS - e_endianness: Endianness = Endianness.LITTLE_ENDIAN + SYSTEMV = 0x00 + HPUX = 0x01 + NETBSD = 0x02 + LINUX = 0x03 + SOLARIS = 0x06 + AIX = 0x07 + IRIX = 0x08 + FREEBSD = 0x09 + OPENBSD = 0x0C + + e_magic: int = ELF_MAGIC + e_class: "Elf.Class" = Class.ELF_32_BITS + e_endianness: Endianness = Endianness.LITTLE_ENDIAN e_eiversion: int e_osabi: "Elf.OsAbi" e_abiversion: int e_pad: bytes - e_type: "Elf.Type" = Type.ET_EXEC - e_machine: Abi = Abi.X86_32 + e_type: "Elf.Type" = Type.ET_EXEC + e_machine: Abi = Abi.X86_32 e_version: int e_entry: int e_phoff: int @@ -887,11 +1012,11 @@ class OsAbi(enum.Enum): e_shstrndx: int path: pathlib.Path - phdrs : list["Phdr"] - shdrs : list["Shdr"] + phdrs: list["Phdr"] + shdrs: list["Shdr"] name: str = "ELF" - __checksec : dict[str, bool] + __checksec: dict[str, bool] def __init__(self, path: str | pathlib.Path) -> None: """Instantiate an ELF object. A valid ELF must be provided, or an exception will be thrown.""" @@ -904,18 +1029,25 @@ def __init__(self, path: str | pathlib.Path) -> None: raise TypeError if not self.path.exists(): - raise FileNotFoundError(f"'{self.path}' not found/readable, most gef features will not work") + raise FileNotFoundError( + f"'{self.path}' not found/readable, most gef features will not work" + ) self.__checksec = {} with self.path.open("rb") as self.fd: # off 0x0 - self.e_magic, e_class, e_endianness, self.e_eiversion = self.read_and_unpack(">IBBB") + self.e_magic, e_class, e_endianness, self.e_eiversion = ( + self.read_and_unpack(">IBBB") + ) if self.e_magic != Elf.ELF_MAGIC: # The ELF is corrupted, GDB won't handle it, no point going further raise RuntimeError("Not a valid ELF file (magic)") - self.e_class, self.e_endianness = Elf.Class(e_class), Endianness(e_endianness) + self.e_class, self.e_endianness = ( + Elf.Class(e_class), + Endianness(e_endianness), + ) if self.e_endianness != gef.arch.endianness: warn("Unexpected endianness for architecture") @@ -935,12 +1067,20 @@ def __init__(self, path: str | pathlib.Path) -> None: # off 0x18 if self.e_class == Elf.Class.ELF_64_BITS: - self.e_entry, self.e_phoff, self.e_shoff = self.read_and_unpack(f"{endian}QQQ") + self.e_entry, self.e_phoff, self.e_shoff = self.read_and_unpack( + f"{endian}QQQ" + ) else: - self.e_entry, self.e_phoff, self.e_shoff = self.read_and_unpack(f"{endian}III") + self.e_entry, self.e_phoff, self.e_shoff = self.read_and_unpack( + f"{endian}III" + ) - self.e_flags, self.e_ehsize, self.e_phentsize, self.e_phnum = self.read_and_unpack(f"{endian}IHHH") - self.e_shentsize, self.e_shnum, self.e_shstrndx = self.read_and_unpack(f"{endian}HHH") + self.e_flags, self.e_ehsize, self.e_phentsize, self.e_phnum = ( + self.read_and_unpack(f"{endian}IHHH") + ) + self.e_shentsize, self.e_shnum, self.e_shstrndx = self.read_and_unpack( + f"{endian}HHH" + ) self.phdrs = [] for i in range(self.e_phnum): @@ -963,10 +1103,14 @@ def seek(self, off: int) -> None: self.fd.seek(off, 0) def __str__(self) -> str: - return f"ELF('{self.path.absolute()}', {self.e_class.name}, {self.e_machine.name})" + return ( + f"ELF('{self.path.absolute()}', {self.e_class.name}, {self.e_machine.name})" + ) def __repr__(self) -> str: - return f"ELF('{self.path.absolute()}', {self.e_class.name}, {self.e_machine.name})" + return ( + f"ELF('{self.path.absolute()}', {self.e_class.name}, {self.e_machine.name})" + ) @property def entry_point(self) -> int: @@ -974,7 +1118,7 @@ def entry_point(self) -> int: @classmethod def is_valid(cls, path: pathlib.Path) -> bool: - return u32(path.open("rb").read(4), e = Endianness.BIG_ENDIAN) == Elf.ELF_MAGIC + return u32(path.open("rb").read(4), e=Endianness.BIG_ENDIAN) == Elf.ELF_MAGIC @property def checksec(self) -> dict[str, bool]: @@ -987,10 +1131,17 @@ def checksec(self) -> dict[str, bool]: Return a dict() with the different keys mentioned above, and the boolean associated whether the protection was found.""" if not self.__checksec: - def __check_security_property(opt: str, filename: str, pattern: str) -> bool: - cmd = [readelf,] - cmd += opt.split() - cmd += [filename,] + + def __check_security_property( + opt: str, filename: str, pattern: str + ) -> bool: + cmd = [ + readelf, + ] + cmd += opt.split() + cmd += [ + filename, + ] lines = gef_execute_external(cmd, as_list=True) for line in lines: if re.search(pattern, line): @@ -999,92 +1150,118 @@ def __check_security_property(opt: str, filename: str, pattern: str) -> bool: abspath = str(self.path.absolute()) readelf = gef.session.constants["readelf"] - self.__checksec["Canary"] = __check_security_property("-rs", abspath, r"__stack_chk_fail") is True - has_gnu_stack = __check_security_property("-W -l", abspath, r"GNU_STACK") is True + self.__checksec["Canary"] = ( + __check_security_property("-rs", abspath, r"__stack_chk_fail") is True + ) + has_gnu_stack = ( + __check_security_property("-W -l", abspath, r"GNU_STACK") is True + ) if has_gnu_stack: - self.__checksec["NX"] = __check_security_property("-W -l", abspath, r"GNU_STACK.*RWE") is False + self.__checksec["NX"] = ( + __check_security_property("-W -l", abspath, r"GNU_STACK.*RWE") + is False + ) else: self.__checksec["NX"] = False - self.__checksec["PIE"] = __check_security_property("-h", abspath, r":.*EXEC") is False - self.__checksec["Fortify"] = __check_security_property("-s", abspath, r"_chk@GLIBC") is True - self.__checksec["Partial RelRO"] = __check_security_property("-l", abspath, r"GNU_RELRO") is True - self.__checksec["Full RelRO"] = self.__checksec["Partial RelRO"] and __check_security_property("-d", abspath, r"BIND_NOW") is True + self.__checksec["PIE"] = ( + __check_security_property("-h", abspath, r":.*EXEC") is False + ) + self.__checksec["Fortify"] = ( + __check_security_property("-s", abspath, r"_chk@GLIBC") is True + ) + self.__checksec["Partial RelRO"] = ( + __check_security_property("-l", abspath, r"GNU_RELRO") is True + ) + self.__checksec["Full RelRO"] = ( + self.__checksec["Partial RelRO"] + and __check_security_property("-d", abspath, r"BIND_NOW") is True + ) return self.__checksec @classproperty @deprecated("use `Elf.Abi.X86_64`") - def X86_64(cls) -> int: return Elf.Abi.X86_64.value # pylint: disable=no-self-argument + def X86_64(cls) -> int: + return Elf.Abi.X86_64.value # pylint: disable=no-self-argument @classproperty @deprecated("use `Elf.Abi.X86_32`") - def X86_32(cls) -> int : return Elf.Abi.X86_32.value # pylint: disable=no-self-argument + def X86_32(cls) -> int: + return Elf.Abi.X86_32.value # pylint: disable=no-self-argument @classproperty @deprecated("use `Elf.Abi.ARM`") - def ARM(cls) -> int : return Elf.Abi.ARM.value # pylint: disable=no-self-argument + def ARM(cls) -> int: + return Elf.Abi.ARM.value # pylint: disable=no-self-argument @classproperty @deprecated("use `Elf.Abi.MIPS`") - def MIPS(cls) -> int : return Elf.Abi.MIPS.value # pylint: disable=no-self-argument + def MIPS(cls) -> int: + return Elf.Abi.MIPS.value # pylint: disable=no-self-argument @classproperty @deprecated("use `Elf.Abi.POWERPC`") - def POWERPC(cls) -> int : return Elf.Abi.POWERPC.value # pylint: disable=no-self-argument + def POWERPC(cls) -> int: + return Elf.Abi.POWERPC.value # pylint: disable=no-self-argument @classproperty @deprecated("use `Elf.Abi.POWERPC64`") - def POWERPC64(cls) -> int : return Elf.Abi.POWERPC64.value # pylint: disable=no-self-argument + def POWERPC64(cls) -> int: + return Elf.Abi.POWERPC64.value # pylint: disable=no-self-argument @classproperty @deprecated("use `Elf.Abi.SPARC`") - def SPARC(cls) -> int : return Elf.Abi.SPARC.value # pylint: disable=no-self-argument + def SPARC(cls) -> int: + return Elf.Abi.SPARC.value # pylint: disable=no-self-argument @classproperty @deprecated("use `Elf.Abi.SPARC64`") - def SPARC64(cls) -> int : return Elf.Abi.SPARC64.value # pylint: disable=no-self-argument + def SPARC64(cls) -> int: + return Elf.Abi.SPARC64.value # pylint: disable=no-self-argument @classproperty @deprecated("use `Elf.Abi.AARCH64`") - def AARCH64(cls) -> int : return Elf.Abi.AARCH64.value # pylint: disable=no-self-argument + def AARCH64(cls) -> int: + return Elf.Abi.AARCH64.value # pylint: disable=no-self-argument @classproperty @deprecated("use `Elf.Abi.RISCV`") - def RISCV(cls) -> int : return Elf.Abi.RISCV.value # pylint: disable=no-self-argument + def RISCV(cls) -> int: + return Elf.Abi.RISCV.value # pylint: disable=no-self-argument class Phdr: class Type(enum.IntEnum): - PT_NULL = 0 - PT_LOAD = 1 - PT_DYNAMIC = 2 - PT_INTERP = 3 - PT_NOTE = 4 - PT_SHLIB = 5 - PT_PHDR = 6 - PT_TLS = 7 - PT_LOOS = 0x60000000 - PT_GNU_EH_FRAME = 0x6474e550 - PT_GNU_STACK = 0x6474e551 - PT_GNU_RELRO = 0x6474e552 - PT_GNU_PROPERTY = 0x6474e553 - PT_LOSUNW = 0x6ffffffa - PT_SUNWBSS = 0x6ffffffa - PT_SUNWSTACK = 0x6ffffffb - PT_HISUNW = PT_HIOS = 0x6fffffff - PT_LOPROC = 0x70000000 - PT_ARM_EIDX = 0x70000001 - PT_MIPS_ABIFLAGS= 0x70000003 - PT_HIPROC = 0x7fffffff - UNKNOWN_PHDR = 0xffffffff + PT_NULL = 0 + PT_LOAD = 1 + PT_DYNAMIC = 2 + PT_INTERP = 3 + PT_NOTE = 4 + PT_SHLIB = 5 + PT_PHDR = 6 + PT_TLS = 7 + PT_LOOS = 0x60000000 + PT_GNU_EH_FRAME = 0x6474E550 + PT_GNU_STACK = 0x6474E551 + PT_GNU_RELRO = 0x6474E552 + PT_GNU_PROPERTY = 0x6474E553 + PT_LOSUNW = 0x6FFFFFFA + PT_SUNWBSS = 0x6FFFFFFA + PT_SUNWSTACK = 0x6FFFFFFB + PT_HISUNW = PT_HIOS = 0x6FFFFFFF + PT_LOPROC = 0x70000000 + PT_ARM_EIDX = 0x70000001 + PT_MIPS_ABIFLAGS = 0x70000003 + PT_HIPROC = 0x7FFFFFFF + UNKNOWN_PHDR = 0xFFFFFFFF @classmethod - def _missing_(cls, _:int) -> "Phdr.Type": + def _missing_(cls, _: int) -> "Phdr.Type": return cls.UNKNOWN_PHDR class Flags(enum.IntFlag): - PF_X = 1 - PF_W = 2 - PF_R = 4 + PF_X = 1 + PF_W = 2 + PF_R = 4 p_type: "Phdr.Type" p_flags: "Phdr.Flags" @@ -1104,88 +1281,94 @@ def __init__(self, elf: Elf, off: int) -> None: if elf.e_class == Elf.Class.ELF_64_BITS: p_type, p_flags, self.p_offset = elf.read_and_unpack(f"{endian}IIQ") self.p_vaddr, self.p_paddr = elf.read_and_unpack(f"{endian}QQ") - self.p_filesz, self.p_memsz, self.p_align = elf.read_and_unpack(f"{endian}QQQ") + self.p_filesz, self.p_memsz, self.p_align = elf.read_and_unpack( + f"{endian}QQQ" + ) else: p_type, self.p_offset = elf.read_and_unpack(f"{endian}II") self.p_vaddr, self.p_paddr = elf.read_and_unpack(f"{endian}II") - self.p_filesz, self.p_memsz, p_flags, self.p_align = elf.read_and_unpack(f"{endian}IIII") + self.p_filesz, self.p_memsz, p_flags, self.p_align = elf.read_and_unpack( + f"{endian}IIII" + ) self.p_type, self.p_flags = Phdr.Type(p_type), Phdr.Flags(p_flags) return def __str__(self) -> str: - return (f"Phdr(offset={self.offset}, type={self.p_type.name}, flags={self.p_flags.name}, " - f"vaddr={self.p_vaddr}, paddr={self.p_paddr}, filesz={self.p_filesz}, " - f"memsz={self.p_memsz}, align={self.p_align})") + return ( + f"Phdr(offset={self.offset}, type={self.p_type.name}, flags={self.p_flags.name}, " + f"vaddr={self.p_vaddr}, paddr={self.p_paddr}, filesz={self.p_filesz}, " + f"memsz={self.p_memsz}, align={self.p_align})" + ) class Shdr: class Type(enum.IntEnum): - SHT_NULL = 0 - SHT_PROGBITS = 1 - SHT_SYMTAB = 2 - SHT_STRTAB = 3 - SHT_RELA = 4 - SHT_HASH = 5 - SHT_DYNAMIC = 6 - SHT_NOTE = 7 - SHT_NOBITS = 8 - SHT_REL = 9 - SHT_SHLIB = 10 - SHT_DYNSYM = 11 - SHT_NUM = 12 - SHT_INIT_ARRAY = 14 - SHT_FINI_ARRAY = 15 - SHT_PREINIT_ARRAY = 16 - SHT_GROUP = 17 - SHT_SYMTAB_SHNDX = 18 - SHT_LOOS = 0x60000000 - SHT_GNU_ATTRIBUTES = 0x6ffffff5 - SHT_GNU_HASH = 0x6ffffff6 - SHT_GNU_LIBLIST = 0x6ffffff7 - SHT_CHECKSUM = 0x6ffffff8 - SHT_LOSUNW = 0x6ffffffa - SHT_SUNW_move = 0x6ffffffa - SHT_SUNW_COMDAT = 0x6ffffffb - SHT_SUNW_syminfo = 0x6ffffffc - SHT_GNU_verdef = 0x6ffffffd - SHT_GNU_verneed = 0x6ffffffe - SHT_GNU_versym = 0x6fffffff - SHT_LOPROC = 0x70000000 - SHT_ARM_EXIDX = 0x70000001 - SHT_X86_64_UNWIND = 0x70000001 - SHT_ARM_ATTRIBUTES = 0x70000003 - SHT_MIPS_OPTIONS = 0x7000000d - DT_MIPS_INTERFACE = 0x7000002a - SHT_HIPROC = 0x7fffffff - SHT_LOUSER = 0x80000000 - SHT_HIUSER = 0x8fffffff - UNKNOWN_SHDR = 0xffffffff + SHT_NULL = 0 + SHT_PROGBITS = 1 + SHT_SYMTAB = 2 + SHT_STRTAB = 3 + SHT_RELA = 4 + SHT_HASH = 5 + SHT_DYNAMIC = 6 + SHT_NOTE = 7 + SHT_NOBITS = 8 + SHT_REL = 9 + SHT_SHLIB = 10 + SHT_DYNSYM = 11 + SHT_NUM = 12 + SHT_INIT_ARRAY = 14 + SHT_FINI_ARRAY = 15 + SHT_PREINIT_ARRAY = 16 + SHT_GROUP = 17 + SHT_SYMTAB_SHNDX = 18 + SHT_LOOS = 0x60000000 + SHT_GNU_ATTRIBUTES = 0x6FFFFFF5 + SHT_GNU_HASH = 0x6FFFFFF6 + SHT_GNU_LIBLIST = 0x6FFFFFF7 + SHT_CHECKSUM = 0x6FFFFFF8 + SHT_LOSUNW = 0x6FFFFFFA + SHT_SUNW_move = 0x6FFFFFFA + SHT_SUNW_COMDAT = 0x6FFFFFFB + SHT_SUNW_syminfo = 0x6FFFFFFC + SHT_GNU_verdef = 0x6FFFFFFD + SHT_GNU_verneed = 0x6FFFFFFE + SHT_GNU_versym = 0x6FFFFFFF + SHT_LOPROC = 0x70000000 + SHT_ARM_EXIDX = 0x70000001 + SHT_X86_64_UNWIND = 0x70000001 + SHT_ARM_ATTRIBUTES = 0x70000003 + SHT_MIPS_OPTIONS = 0x7000000D + DT_MIPS_INTERFACE = 0x7000002A + SHT_HIPROC = 0x7FFFFFFF + SHT_LOUSER = 0x80000000 + SHT_HIUSER = 0x8FFFFFFF + UNKNOWN_SHDR = 0xFFFFFFFF @classmethod - def _missing_(cls, _:int) -> "Shdr.Type": + def _missing_(cls, _: int) -> "Shdr.Type": return cls.UNKNOWN_SHDR class Flags(enum.IntFlag): - WRITE = 1 - ALLOC = 2 - EXECINSTR = 4 - MERGE = 0x10 - STRINGS = 0x20 - INFO_LINK = 0x40 - LINK_ORDER = 0x80 + WRITE = 1 + ALLOC = 2 + EXECINSTR = 4 + MERGE = 0x10 + STRINGS = 0x20 + INFO_LINK = 0x40 + LINK_ORDER = 0x80 OS_NONCONFORMING = 0x100 - GROUP = 0x200 - TLS = 0x400 - COMPRESSED = 0x800 - RELA_LIVEPATCH = 0x00100000 - RO_AFTER_INIT = 0x00200000 - ORDERED = 0x40000000 - EXCLUDE = 0x80000000 - UNKNOWN_FLAG = 0xffffffff + GROUP = 0x200 + TLS = 0x400 + COMPRESSED = 0x800 + RELA_LIVEPATCH = 0x00100000 + RO_AFTER_INIT = 0x00200000 + ORDERED = 0x40000000 + EXCLUDE = 0x80000000 + UNKNOWN_FLAG = 0xFFFFFFFF @classmethod - def _missing_(cls, _:int): + def _missing_(cls, _: int): return cls.UNKNOWN_FLAG sh_name: int @@ -1208,12 +1391,16 @@ def __init__(self, elf: Elf | None, off: int) -> None: if elf.e_class == Elf.Class.ELF_64_BITS: self.sh_name, sh_type, sh_flags = elf.read_and_unpack(f"{endian}IIQ") self.sh_addr, self.sh_offset = elf.read_and_unpack(f"{endian}QQ") - self.sh_size, self.sh_link, self.sh_info = elf.read_and_unpack(f"{endian}QII") + self.sh_size, self.sh_link, self.sh_info = elf.read_and_unpack( + f"{endian}QII" + ) self.sh_addralign, self.sh_entsize = elf.read_and_unpack(f"{endian}QQ") else: self.sh_name, sh_type, sh_flags = elf.read_and_unpack(f"{endian}III") self.sh_addr, self.sh_offset = elf.read_and_unpack(f"{endian}II") - self.sh_size, self.sh_link, self.sh_info = elf.read_and_unpack(f"{endian}III") + self.sh_size, self.sh_link, self.sh_info = elf.read_and_unpack( + f"{endian}III" + ) self.sh_addralign, self.sh_entsize = elf.read_and_unpack(f"{endian}II") self.sh_type = Shdr.Type(sh_type) @@ -1236,17 +1423,31 @@ def __init__(self, elf: Elf | None, off: int) -> None: return def __str__(self) -> str: - return (f"Shdr(name={self.name}, type={self.sh_type.name}, flags={self.sh_flags.name}, " - f"addr={self.sh_addr:#x}, offset={self.sh_offset}, size={self.sh_size}, link={self.sh_link}, " - f"info={self.sh_info}, addralign={self.sh_addralign}, entsize={self.sh_entsize})") + return ( + f"Shdr(name={self.name}, type={self.sh_type.name}, flags={self.sh_flags.name}, " + f"addr={self.sh_addr:#x}, offset={self.sh_offset}, size={self.sh_size}, link={self.sh_link}, " + f"info={self.sh_info}, addralign={self.sh_addralign}, entsize={self.sh_entsize})" + ) class Instruction: """GEF representation of a CPU instruction.""" - def __init__(self, address: int, location: str, mnemo: str, operands: list[str], opcodes: bytes) -> None: - self.address, self.location, self.mnemonic, self.operands, self.opcodes = \ - address, location, mnemo, operands, opcodes + def __init__( + self, + address: int, + location: str, + mnemo: str, + operands: list[str], + opcodes: bytes, + ) -> None: + self.address, self.location, self.mnemonic, self.operands, self.opcodes = ( + address, + location, + mnemo, + operands, + opcodes, + ) return # Allow formatting an instruction with {:o} to show opcodes. @@ -1263,8 +1464,10 @@ def __format__(self, format_spec: str) -> str: opcodes_text = "".join(f"{b:02x}" for b in self.opcodes[:opcodes_len]) if opcodes_len < len(self.opcodes): opcodes_text += "..." - return (f"{self.address:#10x} {opcodes_text:{opcodes_len * 2 + 3:d}s} {self.location:16} " - f"{self.mnemonic:6} {', '.join(self.operands)}") + return ( + f"{self.address:#10x} {opcodes_text:{opcodes_len * 2 + 3:d}s} {self.location:16} " + f"{self.mnemonic:6} {', '.join(self.operands)}" + ) def __str__(self) -> str: return f"{self.address:#10x} {self.location:16} {self.mnemonic:6} {', '.join(self.operands)}" @@ -1284,45 +1487,46 @@ def next(self) -> "Instruction": def search_for_main_arena() -> int: return GefHeapManager.find_main_arena_addr() + class GlibcHeapInfo: """Glibc heap_info struct""" @staticmethod def heap_info_t() -> Type[ctypes.Structure]: assert gef.libc.version + class heap_info_cls(ctypes.Structure): pass + pointer = ctypes.c_uint64 if gef.arch.ptrsize == 8 else ctypes.c_uint32 pad_size = -5 * gef.arch.ptrsize & (gef.heap.malloc_alignment - 1) fields = [ ("ar_ptr", ctypes.POINTER(GlibcArena.malloc_state_t())), ("prev", ctypes.POINTER(heap_info_cls)), - ("size", pointer) + ("size", pointer), ] if gef.libc.version >= (2, 5): - fields += [ - ("mprotect_size", pointer) - ] + fields += [("mprotect_size", pointer)] pad_size = -6 * gef.arch.ptrsize & (gef.heap.malloc_alignment - 1) if gef.libc.version >= (2, 34): - fields += [ - ("pagesize", pointer) - ] + fields += [("pagesize", pointer)] pad_size = -3 * gef.arch.ptrsize & (gef.heap.malloc_alignment - 1) - fields += [ - ("pad", ctypes.c_uint8*pad_size) - ] + fields += [("pad", ctypes.c_uint8 * pad_size)] heap_info_cls._fields_ = fields return heap_info_cls - def __init__(self, addr: str | int) -> None: - self.__address : int = parse_address(f"&{addr}") if isinstance(addr, str) else addr + def __init__(self, addr: str | int) -> None: + self.__address: int = ( + parse_address(f"&{addr}") if isinstance(addr, str) else addr + ) self.reset() return def reset(self): self._sizeof = ctypes.sizeof(GlibcHeapInfo.heap_info_t()) - self._data = gef.memory.read(self.__address, ctypes.sizeof(GlibcHeapInfo.heap_info_t())) + self._data = gef.memory.read( + self.__address, ctypes.sizeof(GlibcHeapInfo.heap_info_t()) + ) self._heap_info = GlibcHeapInfo.heap_info_t().from_buffer_copy(self._data) return @@ -1392,7 +1596,7 @@ def malloc_state_t() -> Type[ctypes.Structure]: # https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L1684 fields += [ ("have_fastchunks", ctypes.c_uint32), - ("UNUSED_c", ctypes.c_uint32), # padding to align to 0x10 + ("UNUSED_c", ctypes.c_uint32), # padding to align to 0x10 ] fields += [ ("fastbinsY", GlibcArena.NFASTBINS * pointer), @@ -1401,26 +1605,26 @@ def malloc_state_t() -> Type[ctypes.Structure]: ("bins", (GlibcArena.NBINS * 2 - 2) * pointer), ("binmap", GlibcArena.BINMAPSIZE * ctypes.c_uint32), ("next", pointer), - ("next_free", pointer) + ("next_free", pointer), ] if gef and gef.libc.version and gef.libc.version >= (2, 23): # https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L1719 - fields += [ - ("attached_threads", pointer) - ] + fields += [("attached_threads", pointer)] fields += [ ("system_mem", pointer), ("max_system_mem", pointer), ] + class malloc_state_cls(ctypes.Structure): _fields_ = fields + return malloc_state_cls def __init__(self, addr: str) -> None: try: - self.__address : int = parse_address(f"&{addr}") + self.__address: int = parse_address(f"&{addr}") except gdb.error: - self.__address : int = GefHeapManager.find_main_arena_addr() + self.__address: int = GefHeapManager.find_main_arena_addr() # if `find_main_arena_addr` throws `gdb.error` on symbol lookup: # it means the session is not started, so just propagate the exception self.reset() @@ -1428,7 +1632,9 @@ def __init__(self, addr: str) -> None: def reset(self): self._sizeof = ctypes.sizeof(GlibcArena.malloc_state_t()) - self._data = gef.memory.read(self.__address, ctypes.sizeof(GlibcArena.malloc_state_t())) + self._data = gef.memory.read( + self.__address, ctypes.sizeof(GlibcArena.malloc_state_t()) + ) self.__arena = GlibcArena.malloc_state_t().from_buffer_copy(self._data) return @@ -1457,10 +1663,12 @@ def __eq__(self, other: "GlibcArena") -> bool: return self.__address == int(other) def __str__(self) -> str: - properties = f"base={self.__address:#x}, top={self.top:#x}, " \ - f"last_remainder={self.last_remainder:#x}, next={self.next:#x}, " \ - f"mem={self.system_mem}, mempeak={self.max_system_mem}" - return (f"{Color.colorify('Arena', 'blue bold underline')}({properties})") + properties = ( + f"base={self.__address:#x}, top={self.top:#x}, " + f"last_remainder={self.last_remainder:#x}, next={self.next:#x}, " + f"mem={self.system_mem}, mempeak={self.max_system_mem}" + ) + return f"{Color.colorify('Arena', 'blue bold underline')}({properties})" def __repr__(self) -> str: return f"GlibcArena(address={self.__address:#x}, size={self._sizeof})" @@ -1533,7 +1741,7 @@ def bin(self, i: int) -> tuple[int, int]: def bin_at(self, i) -> int: header_sz = 2 * gef.arch.ptrsize offset = ctypes.addressof(self.__arena.bins) - ctypes.addressof(self.__arena) - return self.__address + offset + (i-1) * 2 * gef.arch.ptrsize + header_sz + return self.__address + offset + (i - 1) * 2 * gef.arch.ptrsize + header_sz def is_main_arena(self) -> bool: return gef.heap.main_arena is not None and int(self) == int(gef.heap.main_arena) @@ -1600,15 +1808,24 @@ class ChunkFlags(enum.IntFlag): NON_MAIN_ARENA = 4 def __str__(self) -> str: - return " | ".join([ - Color.greenify("PREV_INUSE") if self.value & self.PREV_INUSE else Color.redify("PREV_INUSE"), - Color.greenify("IS_MMAPPED") if self.value & self.IS_MMAPPED else Color.redify("IS_MMAPPED"), - Color.greenify("NON_MAIN_ARENA") if self.value & self.NON_MAIN_ARENA else Color.redify("NON_MAIN_ARENA") - ]) + return " | ".join( + [ + Color.greenify("PREV_INUSE") + if self.value & self.PREV_INUSE + else Color.redify("PREV_INUSE"), + Color.greenify("IS_MMAPPED") + if self.value & self.IS_MMAPPED + else Color.redify("IS_MMAPPED"), + Color.greenify("NON_MAIN_ARENA") + if self.value & self.NON_MAIN_ARENA + else Color.redify("NON_MAIN_ARENA"), + ] + ) @staticmethod def malloc_chunk_t() -> Type[ctypes.Structure]: pointer = ctypes.c_uint64 if gef and gef.arch.ptrsize == 8 else ctypes.c_uint32 + class malloc_chunk_cls(ctypes.Structure): pass @@ -1622,7 +1839,9 @@ class malloc_chunk_cls(ctypes.Structure): ] return malloc_chunk_cls - def __init__(self, addr: int, from_base: bool = False, allow_unaligned: bool = True) -> None: + def __init__( + self, addr: int, from_base: bool = False, allow_unaligned: bool = True + ) -> None: ptrsize = gef.arch.ptrsize self.data_address = addr + 2 * ptrsize if from_base else addr self.base_address = addr if from_base else addr - 2 * ptrsize @@ -1636,7 +1855,8 @@ def __init__(self, addr: int, from_base: bool = False, allow_unaligned: bool = T def reset(self): self._sizeof = ctypes.sizeof(GlibcChunk.malloc_chunk_t()) self._data = gef.memory.read( - self.base_address, ctypes.sizeof(GlibcChunk.malloc_chunk_t())) + self.base_address, ctypes.sizeof(GlibcChunk.malloc_chunk_t()) + ) self._chunk = GlibcChunk.malloc_chunk_t().from_buffer_copy(self._data) return @@ -1672,8 +1892,10 @@ def get_usable_size(self) -> int: # https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L4537 ptrsz = gef.arch.ptrsize cursz = self.size - if cursz == 0: return cursz - if self.has_m_bit(): return cursz - 2 * ptrsz + if cursz == 0: + return cursz + if self.has_m_bit(): + return cursz - 2 * ptrsz return cursz - ptrsz @property @@ -1751,7 +1973,9 @@ def __str_sizes(self) -> str: msg.append(f"Previous chunk size: {prev_chunk_sz:d} ({prev_chunk_sz:#x})") failed = True except gdb.MemoryError: - msg.append(f"Previous chunk size: Cannot read at {self.base_address:#x} (corrupted?)") + msg.append( + f"Previous chunk size: Cannot read at {self.base_address:#x} (corrupted?)" + ) if failed: msg.append(str(self.flags)) @@ -1776,8 +2000,10 @@ def _str_pointers(self) -> str: return "\n".join(msg) def __str__(self) -> str: - return (f"{Color.colorify('Chunk', 'yellow bold underline')}(addr={self.data_address:#x}, " - f"size={self.size:#x}, flags={self.flags!s})") + return ( + f"{Color.colorify('Chunk', 'yellow bold underline')}(addr={self.data_address:#x}, " + f"size={self.size:#x}, flags={self.flags!s})" + ) def psprint(self) -> str: msg = [ @@ -1799,35 +2025,37 @@ def resolve_type(self) -> str: class GlibcFastChunk(GlibcChunk): - @property def fd(self) -> int: - assert(gef and gef.libc.version) + assert gef and gef.libc.version if gef.libc.version < (2, 32): return self._chunk.fd return self.reveal_ptr(self.data_address) def protect_ptr(self, pos: int, pointer: int) -> int: """https://elixir.bootlin.com/glibc/glibc-2.32/source/malloc/malloc.c#L339""" - assert(gef and gef.libc.version) + assert gef and gef.libc.version if gef.libc.version < (2, 32): return pointer return (pos >> 12) ^ pointer def reveal_ptr(self, pointer: int) -> int: """https://elixir.bootlin.com/glibc/glibc-2.32/source/malloc/malloc.c#L341""" - assert(gef and gef.libc.version) + assert gef and gef.libc.version if gef.libc.version < (2, 32): return pointer return gef.memory.read_integer(pointer) ^ (pointer >> 12) + class GlibcTcacheChunk(GlibcFastChunk): pass + @deprecated("Use GefLibcManager.find_libc_version()") def get_libc_version() -> tuple[int, ...]: return GefLibcManager.find_libc_version() + def titlify(text: str, color: str | None = None, msg_color: str | None = None) -> str: """Print a centered title.""" _, cols = get_terminal_size() @@ -1835,9 +2063,11 @@ def titlify(text: str, color: str | None = None, msg_color: str | None = None) - line_color = color or gef.config["theme.default_title_line"] text_color = msg_color or gef.config["theme.default_title_message"] - msg = [Color.colorify(f"{HORIZONTAL_LINE * nb} ", line_color), - Color.colorify(text, text_color), - Color.colorify(f" {HORIZONTAL_LINE * nb}", line_color)] + msg = [ + Color.colorify(f"{HORIZONTAL_LINE * nb} ", line_color), + Color.colorify(text, text_color), + Color.colorify(f" {HORIZONTAL_LINE * nb}", line_color), + ] return "".join(msg) @@ -1897,7 +2127,9 @@ def _show_code_line(fname: str, idx: int) -> str: if not code or not code.strip(): code = _show_code_line(filename, lineno) - gef_print(f"""{DOWN_ARROW} File "{Color.yellowify(filename)}", line {lineno:d}, in {Color.greenify(method)}()""") + gef_print( + f"""{DOWN_ARROW} File "{Color.yellowify(filename)}", line {lineno:d}, in {Color.greenify(method)}()""" + ) gef_print(f" {RIGHT_ARROW} {code}") gef_print(" Version ".center(80, HORIZONTAL_LINE)) @@ -1906,8 +2138,12 @@ def _show_code_line(fname: str, idx: int) -> str: gdb.execute("show commands") gef_print(" Runtime environment ".center(80, HORIZONTAL_LINE)) gef_print(f"* GDB: {gdb.VERSION}") - gef_print(f"* Python: {sys.version_info.major:d}.{sys.version_info.minor:d}.{sys.version_info.micro:d} - {sys.version_info.releaselevel}") - gef_print(f"* OS: {platform.system()} - {platform.release()} ({platform.machine()})") + gef_print( + f"* Python: {sys.version_info.major:d}.{sys.version_info.minor:d}.{sys.version_info.micro:d} - {sys.version_info.releaselevel}" + ) + gef_print( + f"* OS: {platform.system()} - {platform.release()} ({platform.machine()})" + ) try: lsb_release = which("lsb_release") @@ -1915,7 +2151,7 @@ def _show_code_line(fname: str, idx: int) -> str: except FileNotFoundError: pass - gef_print(HORIZONTAL_LINE*80) + gef_print(HORIZONTAL_LINE * 80) gef_print("") return @@ -1923,8 +2159,15 @@ def _show_code_line(fname: str, idx: int) -> str: def gef_pystring(x: bytes) -> str: """Returns a sanitized version as string of the bytes list given in input.""" res = str(x, encoding="utf-8") - substs = [("\n", "\\n"), ("\r", "\\r"), ("\t", "\\t"), ("\v", "\\v"), ("\b", "\\b"), ] - for _x, _y in substs: res = res.replace(_x, _y) + substs = [ + ("\n", "\\n"), + ("\r", "\\r"), + ("\t", "\\t"), + ("\v", "\\v"), + ("\b", "\\b"), + ] + for _x, _y in substs: + res = res.replace(_x, _y) return res @@ -1965,7 +2208,14 @@ def style_byte(b: int, color: bool = True) -> str: return sbyte -def hexdump(source: ByteString, length: int = 0x10, separator: str = ".", show_raw: bool = False, show_symbol: bool = True, base: int = 0x00) -> str: +def hexdump( + source: ByteString, + length: int = 0x10, + separator: str = ".", + show_raw: bool = False, + show_symbol: bool = True, + base: int = 0x00, +) -> str: """Return the hexdump of `src` argument. @param source *MUST* be of type bytes or bytearray @param length is the length of items per line @@ -2029,7 +2279,8 @@ def __exit__(self, *exc: Any) -> None: class RedirectOutputContext: def __init__(self, to_file: str = "/dev/null") -> None: - if " " in to_file: raise ValueError("Target filepath cannot contain spaces") + if " " in to_file: + raise ValueError("Target filepath cannot contain spaces") self.redirection_target_file = to_file return @@ -2057,7 +2308,8 @@ def __exit__(self, *exc: Any) -> None: def enable_redirect_output(to_file: str = "/dev/null") -> None: """Redirect all GDB output to `to_file` parameter. By default, `to_file` redirects to `/dev/null`.""" - if " " in to_file: raise ValueError("Target filepath cannot contain spaces") + if " " in to_file: + raise ValueError("Target filepath cannot contain spaces") gdb.execute("set logging overwrite") gdb.execute(f"set logging file {to_file}") gdb.execute("set logging redirect on") @@ -2078,6 +2330,7 @@ def disable_redirect_output() -> None: gdb.execute("set logging redirect off") return + @deprecated("use `pathlib.Path(...).mkdir()`") def gef_makedirs(path: str, mode: int = 0o755) -> pathlib.Path: """Recursive mkdir() creation. If successful, return the absolute path of the directory created.""" @@ -2091,11 +2344,12 @@ def gef_makedirs(path: str, mode: int = 0o755) -> pathlib.Path: def gdb_lookup_symbol(sym: str) -> tuple[gdb.Symtab_and_line, ...] | None: """Fetch the proper symbol or None if not defined.""" try: - res = gdb.decode_line(sym)[1] # pylint: disable=E1136 + res = gdb.decode_line(sym)[1] # pylint: disable=E1136 return res except gdb.error: return None + @lru_cache(maxsize=512) def gdb_get_location_from_symbol(address: int) -> tuple[str, int] | None: """Retrieve the location of the `address` argument from the symbol table. @@ -2187,7 +2441,7 @@ def gdb_get_nth_previous_instruction_address(addr: int, n: int) -> int | None: @deprecated(solution="Use `gef_instruction_n().address`") def gdb_get_nth_next_instruction_address(addr: int, n: int) -> int: - """Return the address of the `n`-th instruction after `addr`. """ + """Return the address of the `n`-th instruction after `addr`.""" return gef_instruction_n(addr, n).address @@ -2213,7 +2467,9 @@ def gef_next_instruction(addr: int) -> Instruction: return gef_instruction_n(addr, 1) -def gef_disassemble(addr: int, nb_insn: int, nb_prev: int = 0) -> Generator[Instruction, None, None]: +def gef_disassemble( + addr: int, nb_insn: int, nb_prev: int = 0 +) -> Generator[Instruction, None, None]: """Disassemble `nb_insn` instructions after `addr` and `nb_prev` before `addr`. Return an iterator of Instruction objects.""" nb_insn = max(1, nb_insn) @@ -2222,9 +2478,10 @@ def gef_disassemble(addr: int, nb_insn: int, nb_prev: int = 0) -> Generator[Inst try: start_addr = gdb_get_nth_previous_instruction_address(addr, nb_prev) if start_addr: - for insn in gdb_disassemble(start_addr, count=nb_prev): - if insn.address == addr: break - yield insn + for insn in gdb_disassemble(start_addr, count=nb_prev): + if insn.address == addr: + break + yield insn except gdb.MemoryError: # If the address pointing to the previous instruction(s) is not mapped, simply skip them pass @@ -2233,9 +2490,13 @@ def gef_disassemble(addr: int, nb_insn: int, nb_prev: int = 0) -> Generator[Inst yield insn -def gef_execute_external(command: Sequence[str], as_list: bool = False, **kwargs: Any) -> str | list[str]: +def gef_execute_external( + command: Sequence[str], as_list: bool = False, **kwargs: Any +) -> str | list[str]: """Execute an external command and return the result.""" - res = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=kwargs.get("shell", False)) + res = subprocess.check_output( + command, stderr=subprocess.STDOUT, shell=kwargs.get("shell", False) + ) return [gef_pystring(_) for _ in res.splitlines()] if as_list else gef_pystring(res) @@ -2269,12 +2530,12 @@ def get_arch() -> str: arch_str = (gdb.execute("show architecture", to_string=True) or "").strip() pat = "The target architecture is set automatically (currently " if arch_str.startswith(pat): - arch_str = arch_str[len(pat):].rstrip(")") + arch_str = arch_str[len(pat) :].rstrip(")") return arch_str pat = "The target architecture is assumed to be " if arch_str.startswith(pat): - return arch_str[len(pat):] + return arch_str[len(pat) :] pat = "The target architecture is set to " if arch_str.startswith(pat): @@ -2311,7 +2572,11 @@ def flags_to_human(reg_value: int, value_table: dict[int, str]) -> str: """Return a human readable string showing the flag states.""" flags = [] for bit_index, name in value_table.items(): - flags.append(Color.boldify(name.upper()) if reg_value & (1< int | None: # Architecture classes # + @deprecated("Using the decorator `register_architecture` is unnecessary") def register_architecture(cls: Type["Architecture"]) -> Type["Architecture"]: return cls + class ArchitectureBase: """Class decorator for declaring an architecture to GEF.""" + aliases: tuple[str | Elf.Abi, ...] = () def __init_subclass__(cls: Type["ArchitectureBase"], **kwargs): @@ -2370,19 +2638,30 @@ class Architecture(ArchitectureBase): # Optionally defined attributes _ptrsize: int | None = None _endianness: Endianness | None = None - special_registers: tuple[()] | tuple[str, ...] = () + special_registers: tuple[()] | tuple[str, ...] = () maps: GefMemoryMapProvider | None = None def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) - attributes = ("arch", "mode", "aliases", "all_registers", "nop_insn", - "return_register", "flag_register", "instruction_length", "flags_table", - "function_parameters",) + attributes = ( + "arch", + "mode", + "aliases", + "all_registers", + "nop_insn", + "return_register", + "flag_register", + "instruction_length", + "flags_table", + "function_parameters", + ) if not all(map(lambda x: hasattr(cls, x), attributes)): raise NotImplementedError def __str__(self) -> str: - return f"Architecture({self.arch}, {self.mode or 'None'}, {repr(self.endianness)})" + return ( + f"Architecture({self.arch}, {self.mode or 'None'}, {repr(self.endianness)})" + ) def __repr__(self) -> str: return self.__str__() @@ -2426,7 +2705,9 @@ def reset_caches(self) -> None: def __get_register(self, regname: str) -> int: """Return a register's value.""" curframe = gdb.selected_frame() - key = curframe.pc() ^ int(curframe.read_register('sp')) # todo: check when/if gdb.Frame implements `level()` + key = curframe.pc() ^ int( + curframe.read_register("sp") + ) # todo: check when/if gdb.Frame implements `level()` return self.__get_register_for_selected_frame(regname, int(key)) @lru_cache() @@ -2512,11 +2793,40 @@ class RISCV(Architecture): arch = "RISCV" mode = "RISCV" aliases = ("RISCV", Elf.Abi.RISCV) - all_registers = ("$zero", "$ra", "$sp", "$gp", "$tp", "$t0", "$t1", - "$t2", "$fp", "$s1", "$a0", "$a1", "$a2", "$a3", - "$a4", "$a5", "$a6", "$a7", "$s2", "$s3", "$s4", - "$s5", "$s6", "$s7", "$s8", "$s9", "$s10", "$s11", - "$t3", "$t4", "$t5", "$t6",) + all_registers = ( + "$zero", + "$ra", + "$sp", + "$gp", + "$tp", + "$t0", + "$t1", + "$t2", + "$fp", + "$s1", + "$a0", + "$a1", + "$a2", + "$a3", + "$a4", + "$a5", + "$a6", + "$a7", + "$s2", + "$s3", + "$s4", + "$s5", + "$s6", + "$s7", + "$s8", + "$s9", + "$s10", + "$s11", + "$t3", + "$t4", + "$t5", + "$t6", + ) return_register = "$a0" function_parameters = ("$a0", "$a1", "$a2", "$a3", "$a4", "$a5", "$a6", "$a7") syscall_register = "$a7" @@ -2537,10 +2847,14 @@ def is_ret(self, insn: Instruction) -> bool: mnemo = insn.mnemonic if mnemo == "ret": return True - elif (mnemo == "jalr" and insn.operands[0] == "zero" and - insn.operands[1] == "ra" and insn.operands[2] == 0): + elif ( + mnemo == "jalr" + and insn.operands[0] == "zero" + and insn.operands[1] == "ra" + and insn.operands[2] == 0 + ): return True - elif (mnemo == "c.jalr" and insn.operands[0] == "ra"): + elif mnemo == "c.jalr" and insn.operands[0] == "ra": return True return False @@ -2570,7 +2884,9 @@ def long_to_twos_complement(v: int) -> int: if v & 0x8000000000000000: return v - 0x10000000000000000 else: - raise OSError("RISC-V: ELF file is not ELF32 or ELF64. This is not currently supported") + raise OSError( + "RISC-V: ELF file is not ELF32 or ELF64. This is not currently supported" + ) return v mnemo = insn.mnemonic @@ -2586,7 +2902,9 @@ def long_to_twos_complement(v: int) -> int: rs1 = gef.arch.register(insn.operands[0]) rs2 = gef.arch.register(insn.operands[1]) else: - raise OSError(f"RISC-V: Failed to get rs1 and rs2 for instruction: `{insn}`") + raise OSError( + f"RISC-V: Failed to get rs1 and rs2 for instruction: `{insn}`" + ) # If the conditional operation is not unsigned, convert the python long into # its two's complement @@ -2597,20 +2915,30 @@ def long_to_twos_complement(v: int) -> int: condition = condition[:-1] if condition == "eq": - if rs1 == rs2: taken, reason = True, f"{rs1}={rs2}" - else: taken, reason = False, f"{rs1}!={rs2}" + if rs1 == rs2: + taken, reason = True, f"{rs1}={rs2}" + else: + taken, reason = False, f"{rs1}!={rs2}" elif condition == "ne": - if rs1 != rs2: taken, reason = True, f"{rs1}!={rs2}" - else: taken, reason = False, f"{rs1}={rs2}" + if rs1 != rs2: + taken, reason = True, f"{rs1}!={rs2}" + else: + taken, reason = False, f"{rs1}={rs2}" elif condition == "lt": - if rs1 < rs2: taken, reason = True, f"{rs1}<{rs2}" - else: taken, reason = False, f"{rs1}>={rs2}" + if rs1 < rs2: + taken, reason = True, f"{rs1}<{rs2}" + else: + taken, reason = False, f"{rs1}>={rs2}" elif condition == "le": - if rs1 <= rs2: taken, reason = True, f"{rs1}<={rs2}" - else: taken, reason = False, f"{rs1}>{rs2}" + if rs1 <= rs2: + taken, reason = True, f"{rs1}<={rs2}" + else: + taken, reason = False, f"{rs1}>{rs2}" elif condition == "ge": - if rs1 >= rs2: taken, reason = True, f"{rs1}>={rs2}" - else: taken, reason = False, f"{rs1}<{rs2}" + if rs1 >= rs2: + taken, reason = True, f"{rs1}>={rs2}" + else: + taken, reason = False, f"{rs1}<{rs2}" else: raise OSError(f"RISC-V: Conditional instruction `{insn}` not supported yet") @@ -2631,14 +2959,31 @@ def flag_register_to_human(self, val: int | None = None) -> str: # preserve the Architecture API return "" + class ARM(Architecture): aliases = ("ARM", Elf.Abi.ARM) arch = "ARM" - all_registers = ("$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6", - "$r7", "$r8", "$r9", "$r10", "$r11", "$r12", "$sp", - "$lr", "$pc", "$cpsr",) + all_registers = ( + "$r0", + "$r1", + "$r2", + "$r3", + "$r4", + "$r5", + "$r6", + "$r7", + "$r8", + "$r9", + "$r10", + "$r11", + "$r12", + "$sp", + "$lr", + "$pc", + "$cpsr", + ) - nop_insn = b"\x00\xf0\x20\xe3" # hint #0 + nop_insn = b"\x00\xf0\x20\xe3" # hint #0 return_register = "$r0" flag_register: str = "$cpsr" flags_table = { @@ -2710,7 +3055,22 @@ def flag_register_to_human(self, val: int | None = None) -> str: return flags_to_human(val, self.flags_table) def is_conditional_branch(self, insn: Instruction) -> bool: - conditions = {"eq", "ne", "lt", "le", "gt", "ge", "vs", "vc", "mi", "pl", "hi", "ls", "cc", "cs"} + conditions = { + "eq", + "ne", + "lt", + "le", + "gt", + "ge", + "vs", + "vc", + "mi", + "pl", + "hi", + "ls", + "cc", + "cs", + } return insn.mnemonic[-2:] in conditions def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: @@ -2720,30 +3080,59 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: val = gef.arch.register(self.flag_register) taken, reason = False, "" - if mnemo.endswith("eq"): taken, reason = bool(val&(1< int | None: @@ -2755,7 +3144,7 @@ def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: # If it's a pop, we have to peek into the stack, otherwise use lr if insn.mnemonic == "pop": - ra_addr = gef.arch.sp + (len(insn.operands)-1) * self.ptrsize + ra_addr = gef.arch.sp + (len(insn.operands) - 1) * self.ptrsize if not ra_addr: return None ra = dereference(ra_addr) @@ -2775,12 +3164,12 @@ def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: _NR_mprotect = 125 insns = [ "push {r0-r2, r7}", - f"mov r1, {addr & 0xffff:d}", - f"mov r0, {(addr & 0xffff0000) >> 16:d}", + f"mov r1, {addr & 0xFFFF:d}", + f"mov r0, {(addr & 0xFFFF0000) >> 16:d}", "lsl r0, r0, 16", "add r0, r0, r1", - f"mov r1, {size & 0xffff:d}", - f"mov r2, {perm.value & 0xff:d}", + f"mov r1, {size & 0xFFFF:d}", + f"mov r2, {perm.value & 0xFF:d}", f"mov r7, {_NR_mprotect:d}", "svc 0", "pop {r0-r2, r7}", @@ -2794,11 +3183,43 @@ class AARCH64(ARM): mode: str = "" all_registers = ( - "$x0", "$x1", "$x2", "$x3", "$x4", "$x5", "$x6", "$x7", - "$x8", "$x9", "$x10", "$x11", "$x12", "$x13", "$x14","$x15", - "$x16", "$x17", "$x18", "$x19", "$x20", "$x21", "$x22", "$x23", - "$x24", "$x25", "$x26", "$x27", "$x28", "$x29", "$x30", "$sp", - "$pc", "$cpsr", "$fpsr", "$fpcr",) + "$x0", + "$x1", + "$x2", + "$x3", + "$x4", + "$x5", + "$x6", + "$x7", + "$x8", + "$x9", + "$x10", + "$x11", + "$x12", + "$x13", + "$x14", + "$x15", + "$x16", + "$x17", + "$x18", + "$x19", + "$x20", + "$x21", + "$x22", + "$x23", + "$x24", + "$x25", + "$x26", + "$x27", + "$x28", + "$x29", + "$x30", + "$sp", + "$pc", + "$cpsr", + "$fpsr", + "$fpcr", + ) return_register = "$x0" flag_register = "$cpsr" flags_table = { @@ -2812,8 +3233,17 @@ class AARCH64(ARM): 5: "t32", 4: "m[4]", } - nop_insn = b"\x1f\x20\x03\xd5" # hint #0 - function_parameters = ("$x0", "$x1", "$x2", "$x3", "$x4", "$x5", "$x6", "$x7",) + nop_insn = b"\x1f\x20\x03\xd5" # hint #0 + function_parameters = ( + "$x0", + "$x1", + "$x2", + "$x3", + "$x4", + "$x5", + "$x6", + "$x7", + ) syscall_register = "$x8" syscall_instructions = ("svc $x0",) @@ -2887,23 +3317,31 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: reg = f"${operands[0]}" op = gef.arch.register(reg) if mnemo == "cbnz": - if op!=0: taken, reason = True, f"{reg}!=0" - else: taken, reason = False, f"{reg}==0" + if op != 0: + taken, reason = True, f"{reg}!=0" + else: + taken, reason = False, f"{reg}==0" elif mnemo == "cbz": - if op == 0: taken, reason = True, f"{reg}==0" - else: taken, reason = False, f"{reg}!=0" + if op == 0: + taken, reason = True, f"{reg}==0" + else: + taken, reason = False, f"{reg}!=0" elif mnemo == "tbnz": # operands[1] has one or more white spaces in front, then a #, then the number # so we need to eliminate them i = int(operands[1].strip().lstrip("#")) - if (op & 1< bool: def is_conditional_branch(self, insn: Instruction) -> bool: mnemo = insn.mnemonic branch_mnemos = { - "ja", "jnbe", "jae", "jnb", "jnc", "jb", "jc", "jnae", "jbe", "jna", - "jcxz", "jecxz", "jrcxz", "je", "jz", "jg", "jnle", "jge", "jnl", - "jl", "jnge", "jle", "jng", "jne", "jnz", "jno", "jnp", "jpo", "jns", - "jo", "jp", "jpe", "js" + "ja", + "jnbe", + "jae", + "jnb", + "jnc", + "jb", + "jc", + "jnae", + "jbe", + "jna", + "jcxz", + "jecxz", + "jrcxz", + "je", + "jz", + "jg", + "jnle", + "jge", + "jnl", + "jl", + "jnge", + "jle", + "jng", + "jne", + "jnz", + "jno", + "jnp", + "jpo", + "jns", + "jo", + "jp", + "jpe", + "js", } return mnemo in branch_mnemos @@ -2975,40 +3459,64 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: taken, reason = False, "" if mnemo in ("ja", "jnbe"): - taken, reason = not val&(1< int | None: @@ -3056,9 +3564,25 @@ class X86_64(X86): mode = "64" gpr_registers = ( - "$rax", "$rbx", "$rcx", "$rdx", "$rsp", "$rbp", "$rsi", "$rdi", "$rip", - "$r8", "$r9", "$r10", "$r11", "$r12", "$r13", "$r14", "$r15", ) - all_registers = gpr_registers + ( X86.flag_register, ) + X86.special_registers + "$rax", + "$rbx", + "$rcx", + "$rdx", + "$rsp", + "$rbp", + "$rsi", + "$rdi", + "$rip", + "$r8", + "$r9", + "$r10", + "$r11", + "$r12", + "$r13", + "$r14", + "$r15", + ) + all_registers = gpr_registers + (X86.flag_register,) + X86.special_registers return_register = "$rax" function_parameters = ["$rdi", "$rsi", "$rdx", "$rcx", "$r8", "$r9"] syscall_register = "$rax" @@ -3096,19 +3620,55 @@ def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: def canary_address(self) -> int: return self.register("fs_base") + 0x28 + class PowerPC(Architecture): aliases = ("PowerPC", Elf.Abi.POWERPC, "PPC") arch = "PPC" mode = "PPC32" all_registers = ( - "$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6", "$r7", - "$r8", "$r9", "$r10", "$r11", "$r12", "$r13", "$r14", "$r15", - "$r16", "$r17", "$r18", "$r19", "$r20", "$r21", "$r22", "$r23", - "$r24", "$r25", "$r26", "$r27", "$r28", "$r29", "$r30", "$r31", - "$pc", "$msr", "$cr", "$lr", "$ctr", "$xer", "$trap",) + "$r0", + "$r1", + "$r2", + "$r3", + "$r4", + "$r5", + "$r6", + "$r7", + "$r8", + "$r9", + "$r10", + "$r11", + "$r12", + "$r13", + "$r14", + "$r15", + "$r16", + "$r17", + "$r18", + "$r19", + "$r20", + "$r21", + "$r22", + "$r23", + "$r24", + "$r25", + "$r26", + "$r27", + "$r28", + "$r29", + "$r30", + "$r31", + "$pc", + "$msr", + "$cr", + "$lr", + "$ctr", + "$xer", + "$trap", + ) instruction_length = 4 - nop_insn = b"\x60\x00\x00\x00" # https://developer.ibm.com/articles/l-ppc/ + nop_insn = b"\x60\x00\x00\x00" # https://developer.ibm.com/articles/l-ppc/ return_register = "$r0" flag_register: str = "$cr" flags_table = { @@ -3127,7 +3687,6 @@ class PowerPC(Architecture): syscall_instructions = ("sc",) _ptrsize = 4 - def flag_register_to_human(self, val: int | None = None) -> str: # https://www.cebix.net/downloads/bebox/pem32b.pdf (% 2.1.3) if val is None: @@ -3151,12 +3710,26 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: flags = dict((self.flags_table[k], k) for k in self.flags_table) val = gef.arch.register(self.flag_register) taken, reason = False, "" - if mnemo == "beq": taken, reason = bool(val&(1< int | None: @@ -3203,19 +3776,51 @@ class PowerPC64(PowerPC): class SPARC(Architecture): - """ Refs: + """Refs: - https://www.cse.scu.edu/~atkinson/teaching/sp05/259/sparc.pdf """ + aliases = ("SPARC", Elf.Abi.SPARC) arch = "SPARC" mode = "" all_registers = ( - "$g0", "$g1", "$g2", "$g3", "$g4", "$g5", "$g6", "$g7", - "$o0", "$o1", "$o2", "$o3", "$o4", "$o5", "$o7", - "$l0", "$l1", "$l2", "$l3", "$l4", "$l5", "$l6", "$l7", - "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i7", - "$pc", "$npc", "$sp ", "$fp ", "$psr",) + "$g0", + "$g1", + "$g2", + "$g3", + "$g4", + "$g5", + "$g6", + "$g7", + "$o0", + "$o1", + "$o2", + "$o3", + "$o4", + "$o5", + "$o7", + "$l0", + "$l1", + "$l2", + "$l3", + "$l4", + "$l5", + "$l6", + "$l7", + "$i0", + "$i1", + "$i2", + "$i3", + "$i4", + "$i5", + "$i7", + "$pc", + "$npc", + "$sp ", + "$fp ", + "$psr", + ) instruction_length = 4 nop_insn = b"\x00\x00\x00\x00" # sethi 0, %g0 return_register = "$i0" @@ -3228,7 +3833,15 @@ class SPARC(Architecture): 7: "supervisor", 5: "trap", } - function_parameters = ("$o0 ", "$o1 ", "$o2 ", "$o3 ", "$o4 ", "$o5 ", "$o7 ",) + function_parameters = ( + "$o0 ", + "$o1 ", + "$o2 ", + "$o3 ", + "$o4 ", + "$o5 ", + "$o7 ", + ) syscall_register = "%g1" syscall_instructions = ("t 0x10",) @@ -3249,8 +3862,22 @@ def is_conditional_branch(self, insn: Instruction) -> bool: mnemo = insn.mnemonic # http://moss.csc.ncsu.edu/~mueller/codeopt/codeopt00/notes/condbranch.html branch_mnemos = { - "be", "bne", "bg", "bge", "bgeu", "bgu", "bl", "ble", "blu", "bleu", - "bneg", "bpos", "bvs", "bvc", "bcs", "bcc" + "be", + "bne", + "bg", + "bge", + "bgeu", + "bgu", + "bl", + "ble", + "blu", + "bleu", + "bneg", + "bpos", + "bvs", + "bvc", + "bcs", + "bcc", } return mnemo in branch_mnemos @@ -3260,22 +3887,65 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: val = gef.arch.register(self.flag_register) taken, reason = False, "" - if mnemo == "be": taken, reason = bool(val&(1< int | None: @@ -3290,21 +3960,27 @@ def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: @classmethod def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: - hi = (addr & 0xffff0000) >> 16 - lo = (addr & 0x0000ffff) + hi = (addr & 0xFFFF0000) >> 16 + lo = addr & 0x0000FFFF _NR_mprotect = 125 - insns = ["add %sp, -16, %sp", - "st %g1, [ %sp ]", "st %o0, [ %sp + 4 ]", - "st %o1, [ %sp + 8 ]", "st %o2, [ %sp + 12 ]", - f"sethi %hi({hi}), %o0", - f"or %o0, {lo}, %o0", - "clr %o1", - "clr %o2", - f"mov {_NR_mprotect}, %g1", - "t 0x10", - "ld [ %sp ], %g1", "ld [ %sp + 4 ], %o0", - "ld [ %sp + 8 ], %o1", "ld [ %sp + 12 ], %o2", - "add %sp, 16, %sp",] + insns = [ + "add %sp, -16, %sp", + "st %g1, [ %sp ]", + "st %o0, [ %sp + 4 ]", + "st %o1, [ %sp + 8 ]", + "st %o2, [ %sp + 12 ]", + f"sethi %hi({hi}), %o0", + f"or %o0, {lo}, %o0", + "clr %o1", + "clr %o2", + f"mov {_NR_mprotect}, %g1", + "t 0x10", + "ld [ %sp ], %g1", + "ld [ %sp + 4 ], %o0", + "ld [ %sp + 8 ], %o1", + "ld [ %sp + 12 ], %o2", + "add %sp, 16, %sp", + ] return "; ".join(insns) @@ -3313,16 +3989,48 @@ class SPARC64(SPARC): - http://math-atlas.sourceforge.net/devel/assembly/abi_sysV_sparc.pdf - https://cr.yp.to/2005-590/sparcv9.pdf """ + aliases = ("SPARC64", Elf.Abi.SPARC64) arch = "SPARC" mode = "V9" all_registers = [ - "$g0", "$g1", "$g2", "$g3", "$g4", "$g5", "$g6", "$g7", - "$o0", "$o1", "$o2", "$o3", "$o4", "$o5", "$o7", - "$l0", "$l1", "$l2", "$l3", "$l4", "$l5", "$l6", "$l7", - "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i7", - "$pc", "$npc", "$sp", "$fp", "$state", ] + "$g0", + "$g1", + "$g2", + "$g3", + "$g4", + "$g5", + "$g6", + "$g7", + "$o0", + "$o1", + "$o2", + "$o3", + "$o4", + "$o5", + "$o7", + "$l0", + "$l1", + "$l2", + "$l3", + "$l4", + "$l5", + "$l6", + "$l7", + "$i0", + "$i1", + "$i2", + "$i3", + "$i4", + "$i5", + "$i7", + "$pc", + "$npc", + "$sp", + "$fp", + "$state", + ] flag_register = "$state" # sparcv9.pdf, 5.1.5.1 (ccr) flags_table = { @@ -3336,21 +4044,27 @@ class SPARC64(SPARC): @classmethod def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: - hi = (addr & 0xffff0000) >> 16 - lo = (addr & 0x0000ffff) + hi = (addr & 0xFFFF0000) >> 16 + lo = addr & 0x0000FFFF _NR_mprotect = 125 - insns = ["add %sp, -16, %sp", - "st %g1, [ %sp ]", "st %o0, [ %sp + 4 ]", - "st %o1, [ %sp + 8 ]", "st %o2, [ %sp + 12 ]", - f"sethi %hi({hi}), %o0", - f"or %o0, {lo}, %o0", - "clr %o1", - "clr %o2", - f"mov {_NR_mprotect}, %g1", - "t 0x6d", - "ld [ %sp ], %g1", "ld [ %sp + 4 ], %o0", - "ld [ %sp + 8 ], %o1", "ld [ %sp + 12 ], %o2", - "add %sp, 16, %sp",] + insns = [ + "add %sp, -16, %sp", + "st %g1, [ %sp ]", + "st %o0, [ %sp + 4 ]", + "st %o1, [ %sp + 8 ]", + "st %o2, [ %sp + 12 ]", + f"sethi %hi({hi}), %o0", + f"or %o0, {lo}, %o0", + "clr %o1", + "clr %o2", + f"mov {_NR_mprotect}, %g1", + "t 0x6d", + "ld [ %sp ], %g1", + "ld [ %sp + 4 ], %o0", + "ld [ %sp + 8 ], %o1", + "ld [ %sp + 12 ], %o2", + "add %sp, 16, %sp", + ] return "; ".join(insns) @@ -3361,11 +4075,43 @@ class MIPS(Architecture): # https://vhouten.home.xs4all.nl/mipsel/r3000-isa.html all_registers = ( - "$zero", "$at", "$v0", "$v1", "$a0", "$a1", "$a2", "$a3", - "$t0", "$t1", "$t2", "$t3", "$t4", "$t5", "$t6", "$t7", - "$s0", "$s1", "$s2", "$s3", "$s4", "$s5", "$s6", "$s7", - "$t8", "$t9", "$k0", "$k1", "$s8", "$pc", "$sp", "$hi", - "$lo", "$fir", "$ra", "$gp", ) + "$zero", + "$at", + "$v0", + "$v1", + "$a0", + "$a1", + "$a2", + "$a3", + "$t0", + "$t1", + "$t2", + "$t3", + "$t4", + "$t5", + "$t6", + "$t7", + "$s0", + "$s1", + "$s2", + "$s3", + "$s4", + "$s5", + "$s6", + "$s7", + "$t8", + "$t9", + "$k0", + "$k1", + "$s8", + "$pc", + "$sp", + "$hi", + "$lo", + "$fir", + "$ra", + "$gp", + ) instruction_length = 4 _ptrsize = 4 nop_insn = b"\x00\x00\x00\x00" # sll $0,$0,0 @@ -3395,9 +4141,15 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: taken, reason = False, "" if mnemo == "beq": - taken, reason = gef.arch.register(ops[0]) == gef.arch.register(ops[1]), f"{ops[0]} == {ops[1]}" + taken, reason = ( + gef.arch.register(ops[0]) == gef.arch.register(ops[1]), + f"{ops[0]} == {ops[1]}", + ) elif mnemo == "bne": - taken, reason = gef.arch.register(ops[0]) != gef.arch.register(ops[1]), f"{ops[0]} != {ops[1]}" + taken, reason = ( + gef.arch.register(ops[0]) != gef.arch.register(ops[1]), + f"{ops[0]} != {ops[1]}", + ) elif mnemo == "beqz": taken, reason = gef.arch.register(ops[0]) == 0, f"{ops[0]} == 0" elif mnemo == "bnez": @@ -3425,17 +4177,23 @@ def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: @classmethod def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: _NR_mprotect = 4125 - insns = ["addi $sp, $sp, -16", - "sw $v0, 0($sp)", "sw $a0, 4($sp)", - "sw $a3, 8($sp)", "sw $a3, 12($sp)", - f"li $v0, {_NR_mprotect:d}", - f"li $a0, {addr:d}", - f"li $a1, {size:d}", - f"li $a2, {perm.value:d}", - "syscall", - "lw $v0, 0($sp)", "lw $a1, 4($sp)", - "lw $a3, 8($sp)", "lw $a3, 12($sp)", - "addi $sp, $sp, 16",] + insns = [ + "addi $sp, $sp, -16", + "sw $v0, 0($sp)", + "sw $a0, 4($sp)", + "sw $a3, 8($sp)", + "sw $a3, 12($sp)", + f"li $v0, {_NR_mprotect:d}", + f"li $a0, {addr:d}", + f"li $a1, {size:d}", + f"li $a2, {perm.value:d}", + "syscall", + "lw $v0, 0($sp)", + "lw $a1, 4($sp)", + "lw $a3, 8($sp)", + "lw $a3, 12($sp)", + "addi $sp, $sp, 16", + ] return "; ".join(insns) @@ -3449,7 +4207,9 @@ class MIPS64(MIPS): def supports_gdb_arch(gdb_arch: str) -> bool | None: if not gef.binary or not isinstance(gef.binary, Elf): return False - return gdb_arch.startswith("mips") and gef.binary.e_class == Elf.Class.ELF_64_BITS + return ( + gdb_arch.startswith("mips") and gef.binary.e_class == Elf.Class.ELF_64_BITS + ) def copy_to_clipboard(data: bytes) -> None: @@ -3472,32 +4232,40 @@ def copy_to_clipboard(data: bytes) -> None: def use_stdtype() -> str: - if is_32bit(): return "uint32_t" - elif is_64bit(): return "uint64_t" + if is_32bit(): + return "uint32_t" + elif is_64bit(): + return "uint64_t" return "uint16_t" def use_default_type() -> str: - if is_32bit(): return "unsigned int" - elif is_64bit(): return "unsigned long" + if is_32bit(): + return "unsigned int" + elif is_64bit(): + return "unsigned long" return "unsigned short" def use_golang_type() -> str: - if is_32bit(): return "uint32" - elif is_64bit(): return "uint64" + if is_32bit(): + return "uint32" + elif is_64bit(): + return "uint64" return "uint16" def use_rust_type() -> str: - if is_32bit(): return "u32" - elif is_64bit(): return "u64" + if is_32bit(): + return "u32" + elif is_64bit(): + return "u64" return "u16" def to_unsigned_long(v: gdb.Value) -> int: """Cast a gdb.Value to unsigned long.""" - mask = (1 << (gef.arch.ptrsize*8)) - 1 + mask = (1 << (gef.arch.ptrsize * 8)) - 1 return int(v.cast(gdb.Value(mask).type)) & mask @@ -3522,7 +4290,10 @@ def is_target_remote(conn: gdb.TargetConnection | None = None) -> bool: def is_target_extended_remote(conn: gdb.TargetConnection | None = None) -> bool: "Returns True for `extended-remote` only." _conn = conn or gdb.selected_inferior().connection - return isinstance(_conn, gdb.RemoteTargetConnection) and _conn.type == "extended-remote" + return ( + isinstance(_conn, gdb.RemoteTargetConnection) + and _conn.type == "extended-remote" + ) def is_target_remote_or_extended(conn: gdb.TargetConnection | None = None) -> bool: @@ -3530,17 +4301,24 @@ def is_target_remote_or_extended(conn: gdb.TargetConnection | None = None) -> bo def is_running_in_qemu() -> bool: - "See https://www.qemu.org/docs/master/system/gdb.html " + "See https://www.qemu.org/docs/master/system/gdb.html" if not is_target_remote(): return False - response = gdb.execute("maintenance packet Qqemu.sstepbits", to_string=True, from_tty=False) or "" + response = ( + gdb.execute( + "maintenance packet Qqemu.sstepbits", to_string=True, from_tty=False + ) + or "" + ) return "ENABLE=" in response def is_running_in_qemu_user() -> bool: if not is_running_in_qemu(): return False - response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" # Use `qAttached`? + response = ( + gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" + ) # Use `qAttached`? return "Text=" in response @@ -3548,8 +4326,10 @@ def is_running_in_qemu_system() -> bool: if not is_running_in_qemu(): return False # Use "maintenance packet qqemu.PhyMemMode"? - response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" - return "received: \"\"" in response + response = ( + gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" + ) + return 'received: ""' in response def is_running_in_gdbserver() -> bool: @@ -3557,8 +4337,7 @@ def is_running_in_gdbserver() -> bool: def is_running_in_rr() -> bool: - return is_running_in_gdbserver() and \ - os.environ.get("GDB_UNDER_RR", None) == "1" + return is_running_in_gdbserver() and os.environ.get("GDB_UNDER_RR", None) == "1" def is_target_coredump() -> bool: @@ -3740,8 +4519,12 @@ def new_objfile_handler(evt: "gdb.NewObjFileEvent | None") -> None: # with the actual root directory of the process. path = path.replace("target:", str(gef.session.root), 1) target = pathlib.Path(path) - FileFormatClasses = list(filter(lambda fmtcls: fmtcls.is_valid(target), __registered_file_formats__)) - GuessedFileFormatClass : Type[FileFormat] = FileFormatClasses.pop() if len(FileFormatClasses) else Elf + FileFormatClasses = list( + filter(lambda fmtcls: fmtcls.is_valid(target), __registered_file_formats__) + ) + GuessedFileFormatClass: Type[FileFormat] = ( + FileFormatClasses.pop() if len(FileFormatClasses) else Elf + ) binary = GuessedFileFormatClass(target) if not gef.binary: gef.binary = binary @@ -3808,13 +4591,16 @@ def get_terminal_size() -> tuple[int, int]: return 600, 100 if platform.system() == "Windows": - from ctypes import create_string_buffer, windll # type: ignore + from ctypes import create_string_buffer, windll # type: ignore + hStdErr = -12 herr = windll.kernel32.GetStdHandle(hStdErr) csbi = create_string_buffer(22) res = windll.kernel32.GetConsoleScreenBufferInfo(herr, csbi) if res: - _, _, _, _, _, left, top, right, bottom, _, _ = struct.unpack("hhhhHhhhhhh", csbi.raw) + _, _, _, _, _, left, top, right, bottom, _, _ = struct.unpack( + "hhhhHhhhhhh", csbi.raw + ) tty_columns = right - left + 1 tty_rows = bottom - top + 1 return tty_rows, tty_columns @@ -3823,8 +4609,11 @@ def get_terminal_size() -> tuple[int, int]: else: import fcntl import termios + try: - tty_rows, tty_columns, _, _ = struct.unpack("hhhh", fcntl.ioctl(1, termios.TIOCGWINSZ, "12345678")) # type: ignore + tty_rows, tty_columns, _, _ = struct.unpack( + "hhhh", fcntl.ioctl(1, termios.TIOCGWINSZ, "12345678") + ) # type: ignore return tty_rows, tty_columns except OSError: return 600, 100 @@ -3886,7 +4675,9 @@ def reset_architecture(arch: str | None = None) -> None: # check for bin running if is_alive(): gdb_arch = gdb.selected_frame().architecture().name() - preciser_arch = next((a for a in arches.values() if a.supports_gdb_arch(gdb_arch)), None) + preciser_arch = next( + (a for a in arches.values() if a.supports_gdb_arch(gdb_arch)), None + ) if preciser_arch: gef.arch = preciser_arch() gef.arch_reason = "The architecture has been detected by GDB" @@ -3898,7 +4689,9 @@ def reset_architecture(arch: str | None = None) -> None: gef.arch = arches[gef.binary.e_machine]() gef.arch_reason = "The architecture has been detected via the ELF headers" except KeyError: - raise OSError(f"CPU type is currently not supported: {gef.binary.e_machine}") + raise OSError( + f"CPU type is currently not supported: {gef.binary.e_machine}" + ) return warn("Did not find any way to guess the correct architecture :(") @@ -3963,7 +4756,9 @@ def format_address_spaces(addr: int, left: bool = True) -> str: def align_address(address: int) -> int: """Align the provided address to the process's native length.""" - return address & 0xFFFFFFFFFFFFFFFF if gef.arch.ptrsize == 8 else address & 0xFFFFFFFF + return ( + address & 0xFFFFFFFFFFFFFFFF if gef.arch.ptrsize == 8 else address & 0xFFFFFFFF + ) def align_address_to_size(address: int, align: int) -> int: @@ -3986,13 +4781,13 @@ def parse_address(address: str) -> int: def is_in_x86_kernel(address: int) -> bool: address = align_address(address) - memalign = gef.arch.ptrsize*8 - 1 + memalign = gef.arch.ptrsize * 8 - 1 return (address >> memalign) == 0xF @deprecated("Use `is_target_remote()`") def is_remote_debug() -> bool: - """"Return True is the current debugging session is running through GDB remote session.""" + """ "Return True is the current debugging session is running through GDB remote session.""" return gef.session.remote_initializing or gef.session.remote is not None @@ -4037,10 +4832,12 @@ def safe_parse_and_eval(value: str) -> "gdb.Value | None": def dereference(addr: int) -> "gdb.Value | None": """GEF wrapper for gdb dereference function.""" try: - ulong_t = cached_lookup_type(use_stdtype()) or \ - cached_lookup_type(use_default_type()) or \ - cached_lookup_type(use_golang_type()) or \ - cached_lookup_type(use_rust_type()) + ulong_t = ( + cached_lookup_type(use_stdtype()) + or cached_lookup_type(use_default_type()) + or cached_lookup_type(use_golang_type()) + or cached_lookup_type(use_rust_type()) + ) if not ulong_t: raise gdb.MemoryError("Failed to determine unsigned long type") unsigned_long_type = ulong_t.pointer() @@ -4053,7 +4850,7 @@ def dereference(addr: int) -> "gdb.Value | None": return None -def gef_convenience(value: str | bytes) -> str: +def gef_convenience(value: str | bytes) -> str: """Defines a new convenience value.""" global gef var_name = f"$_gef{gef.session.convenience_vars_index:d}" @@ -4089,6 +4886,7 @@ def is_syscall(instruction: Instruction | int) -> bool: # Deprecated API # + @deprecated("Use `gef.session.pie_breakpoints[num]`") def gef_get_pie_breakpoint(num: int) -> "PieVirtualBreakpoint": return gef.session.pie_breakpoints[num] @@ -4150,10 +4948,12 @@ def get_process_maps() -> list[Section]: def set_arch(arch: str | None = None, _: str | None = None) -> None: return reset_architecture(arch) + # # GDB event hooking # + @only_if_events_supported("cont") def gef_on_continue_hook(func: Callable[["gdb.ContinueEvent"], None]) -> None: gdb.events.cont.connect(func) @@ -4195,12 +4995,16 @@ def gef_on_new_unhook(func: Callable[["gdb.NewObjFileEvent"], None]) -> None: @only_if_events_supported("clear_objfiles") -def gef_on_unload_objfile_hook(func: Callable[["gdb.ClearObjFilesEvent"], None]) -> None: +def gef_on_unload_objfile_hook( + func: Callable[["gdb.ClearObjFilesEvent"], None], +) -> None: gdb.events.clear_objfiles.connect(func) @only_if_events_supported("clear_objfiles") -def gef_on_unload_objfile_unhook(func: Callable[["gdb.ClearObjFilesEvent"], None]) -> None: +def gef_on_unload_objfile_unhook( + func: Callable[["gdb.ClearObjFilesEvent"], None], +) -> None: gdb.events.clear_objfiles.disconnect(func) @@ -4220,7 +5024,9 @@ def gef_on_regchanged_hook(func: Callable[["gdb.RegisterChangedEvent"], None]) - @only_if_events_supported("register_changed") -def gef_on_regchanged_unhook(func: Callable[["gdb.RegisterChangedEvent"], None]) -> None: +def gef_on_regchanged_unhook( + func: Callable[["gdb.RegisterChangedEvent"], None], +) -> None: gdb.events.register_changed.disconnect(func) @@ -4228,6 +5034,7 @@ def gef_on_regchanged_unhook(func: Callable[["gdb.RegisterChangedEvent"], None]) # Virtual breakpoints # + class PieVirtualBreakpoint: """PIE virtual breakpoint (not real breakpoint).""" @@ -4241,7 +5048,7 @@ def __init__(self, set_func: Callable[[int], str], vbp_num: int, addr: int) -> N self.bp_addr = 0 # this address might be a symbol, just to know where to break if isinstance(addr, int): - self.addr: int | str = hex(addr) + self.addr: int | str = hex(addr) else: self.addr = addr return @@ -4252,7 +5059,8 @@ def instantiate(self, base: int) -> None: try: res = gdb.execute(self.set_func(base), to_string=True) or "" - if not res: return + if not res: + return except gdb.error as e: err(str(e)) return @@ -4279,8 +5087,10 @@ def destroy(self) -> None: # Breakpoints # + class FormatStringBreakpoint(gdb.Breakpoint): """Inspect stack for format string.""" + def __init__(self, spec: str, num_args: int) -> None: super().__init__(spec, type=gdb.BP_BREAKPOINT, internal=False) self.num_args = num_args @@ -4300,9 +5110,13 @@ def stop(self) -> bool: content = gef.memory.read_cstring(addr.value) name = addr.info.name if addr.info else addr.section.path msg.append(Color.colorify("Format string helper", "yellow bold")) - msg.append(f"Possible insecure format string: {self.location}('{ptr}' {RIGHT_ARROW} {addr.value:#x}: '{content}')") - msg.append(f"Reason: Call to '{self.location}()' with format string argument in position " - f"#{self.num_args:d} is in page {addr.section.page_start:#x} ({name}) that has write permission") + msg.append( + f"Possible insecure format string: {self.location}('{ptr}' {RIGHT_ARROW} {addr.value:#x}: '{content}')" + ) + msg.append( + f"Reason: Call to '{self.location}()' with format string argument in position " + f"#{self.num_args:d} is in page {addr.section.page_start:#x} ({name}) that has write permission" + ) push_context_message("warn", "\n".join(msg)) return True @@ -4326,8 +5140,7 @@ def __init__(self, func: str, retval: int | None) -> None: def stop(self) -> bool: size = "long" if gef.arch.ptrsize == 8 else "int" gdb.execute(f"return (unsigned {size}){self.retval:#x}") - ok(f"Ignoring call to '{self.func}' " - f"(setting return value to {self.retval:#x})") + ok(f"Ignoring call to '{self.func}' (setting return value to {self.retval:#x})") return False @@ -4383,7 +5196,9 @@ def stop(self) -> bool: loc = parse_address(gef.arch.return_register) size = self.size - ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - {self.name}({size})={loc:#x}") + ok( + f"{Color.colorify('Heap-Analysis', 'yellow bold')} - {self.name}({size})={loc:#x}" + ) check_heap_overlap = gef.config["heap-analysis-helper.check_heap_overlap"] # pop from free-ed list if it was in it @@ -4418,15 +5233,24 @@ def stop(self) -> bool: current_chunk_size = current_chunk.size if chunk_addr <= loc < chunk_addr + current_chunk_size: - offset = loc - chunk_addr - 2*align - if offset < 0: continue # false positive, discard + offset = loc - chunk_addr - 2 * align + if offset < 0: + continue # false positive, discard msg.append(Color.colorify("Heap-Analysis", "yellow bold")) msg.append("Possible heap overlap detected") - msg.append(f"Reason {RIGHT_ARROW} new allocated chunk {loc:#x} (of size {size:d}) overlaps in-used chunk {chunk_addr:#x} (of size {current_chunk_size:#x})") - msg.append(f"Writing {offset:d} bytes from {chunk_addr:#x} will reach chunk {loc:#x}") - msg.append(f"Payload example for chunk {chunk_addr:#x} (to overwrite {loc:#x} headers):") - msg.append(f" data = 'A'*{offset:d} + 'B'*{align:d} + 'C'*{align:d}") + msg.append( + f"Reason {RIGHT_ARROW} new allocated chunk {loc:#x} (of size {size:d}) overlaps in-used chunk {chunk_addr:#x} (of size {current_chunk_size:#x})" + ) + msg.append( + f"Writing {offset:d} bytes from {chunk_addr:#x} will reach chunk {loc:#x}" + ) + msg.append( + f"Payload example for chunk {chunk_addr:#x} (to overwrite {loc:#x} headers):" + ) + msg.append( + f" data = 'A'*{offset:d} + 'B'*{align:d} + 'C'*{align:d}" + ) push_context_message("warn", "\n".join(msg)) return True @@ -4514,7 +5338,9 @@ def stop(self) -> bool: if check_free_null: msg.append(Color.colorify("Heap-Analysis", "yellow bold")) msg.append(f"Attempting to free(NULL) at {gef.arch.pc:#x}") - msg.append("Reason: if NULL page is allocatable, this can lead to code execution.") + msg.append( + "Reason: if NULL page is allocatable, this can lead to code execution." + ) push_context_message("warn", "\n".join(msg)) return True return False @@ -4522,7 +5348,9 @@ def stop(self) -> bool: if addr in [x for (x, _) in gef.session.heap_freed_chunks]: if check_double_free: msg.append(Color.colorify("Heap-Analysis", "yellow bold")) - msg.append(f"Double-free detected {RIGHT_ARROW} free({addr:#x}) is called at {gef.arch.pc:#x} but is already in the free-ed list") + msg.append( + f"Double-free detected {RIGHT_ARROW} free({addr:#x}) is called at {gef.arch.pc:#x} but is already in the free-ed list" + ) msg.append("Execution will likely crash...") push_context_message("warn", "\n".join(msg)) return True @@ -4584,7 +5412,11 @@ def stop(self) -> bool: """If this method is triggered, we likely have a UaF. Break the execution and report it.""" reset_all_caches() frame = gdb.selected_frame() - if frame.name() in ("_int_malloc", "malloc_consolidate", "__libc_calloc", ): + if frame.name() in ( + "_int_malloc", + "malloc_consolidate", + "__libc_calloc", + ): return False # software watchpoints stop after the next statement (see @@ -4594,9 +5426,13 @@ def stop(self) -> bool: insn = gef_current_instruction(pc) msg = [] msg.append(Color.colorify("Heap-Analysis", "yellow bold")) - msg.append(f"Possible Use-after-Free in '{get_filepath()}': " - f"pointer {self.address:#x} was freed, but is attempted to be used at {pc:#x}") - msg.append(f"{insn.address:#x} {insn.mnemonic} {Color.yellowify(', '.join(insn.operands))}") + msg.append( + f"Possible Use-after-Free in '{get_filepath()}': " + f"pointer {self.address:#x} was freed, but is attempted to be used at {pc:#x}" + ) + msg.append( + f"{insn.address:#x} {insn.mnemonic} {Color.yellowify(', '.join(insn.operands))}" + ) push_context_message("warn", "\n".join(msg)) return True @@ -4618,14 +5454,19 @@ class NamedBreakpoint(gdb.Breakpoint): """Breakpoint which shows a specified name, when hit.""" def __init__(self, location: str, name: str) -> None: - super().__init__(spec=location, type=gdb.BP_BREAKPOINT, internal=False, temporary=False) + super().__init__( + spec=location, type=gdb.BP_BREAKPOINT, internal=False, temporary=False + ) self.name = name self.loc = location return def stop(self) -> bool: reset_all_caches() - push_context_message("info", f"Hit breakpoint {self.loc} ({Color.colorify(self.name, 'red bold')})") + push_context_message( + "info", + f"Hit breakpoint {self.loc} ({Color.colorify(self.name, 'red bold')})", + ) return True @@ -4642,7 +5483,13 @@ def __init__(self, loc: str) -> None: # Context Panes # -def register_external_context_pane(pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], str | None], condition : Callable[[], bool] | None = None) -> None: + +def register_external_context_pane( + pane_name: str, + display_pane_function: Callable[[], None], + pane_title_function: Callable[[], str | None], + condition: Callable[[], bool] | None = None, +) -> None: """ Registering function for new GEF Context View. pane_name: a string that has no spaces (used in settings) @@ -4660,11 +5507,21 @@ def pane_title(): return "example:pane" register_external_context_pane("example_pane", display_pane, pane_title, only_syscall) """ - gef.gdb.add_context_pane(pane_name, display_pane_function, pane_title_function, condition) + gef.gdb.add_context_pane( + pane_name, display_pane_function, pane_title_function, condition + ) return -def register_external_context_layout_mapping(current_pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], str | None], condition : Callable[[], bool] | None = None) -> None: - gef.gdb.add_context_layout_mapping(current_pane_name, display_pane_function, pane_title_function, condition) + +def register_external_context_layout_mapping( + current_pane_name: str, + display_pane_function: Callable[[], None], + pane_title_function: Callable[[], str | None], + condition: Callable[[], bool] | None = None, +) -> None: + gef.gdb.add_context_layout_mapping( + current_pane_name, display_pane_function, pane_title_function, condition + ) return @@ -4676,11 +5533,13 @@ def register_external_command(cls: Type["GenericCommand"]) -> Type["GenericComma """Registering function for new GEF (sub-)command to GDB.""" return cls + @deprecated("Use `register()`, and inherit from `GenericCommand` instead") def register_command(cls: Type["GenericCommand"]) -> Type["GenericCommand"]: """Decorator for registering new GEF (sub-)command to GDB.""" return cls + @deprecated("") def register_priority_command(cls: Type["GenericCommand"]) -> Type["GenericCommand"]: """Decorator for registering new command with priority, meaning that it must @@ -4691,7 +5550,10 @@ def register_priority_command(cls: Type["GenericCommand"]) -> Type["GenericComma ValidCommandType = TypeVar("ValidCommandType", bound="GenericCommand") ValidFunctionType = TypeVar("ValidFunctionType", bound="GenericFunction") -def register(cls: Type["ValidCommandType"] | Type["ValidFunctionType"]) -> Type["ValidCommandType"] | Type["ValidFunctionType"]: + +def register( + cls: Type["ValidCommandType"] | Type["ValidFunctionType"], +) -> Type["ValidCommandType"] | Type["ValidFunctionType"]: global __registered_commands__, __registered_functions__ if issubclass(cls, GenericCommand): assert hasattr(cls, "_cmdline_") @@ -4722,7 +5584,10 @@ class GenericCommand(gdb.Command): def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) - attributes = ("_cmdline_", "_syntax_", ) + attributes = ( + "_cmdline_", + "_syntax_", + ) if not all(map(lambda x: hasattr(cls, x), attributes)): raise NotImplementedError @@ -4734,15 +5599,21 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: example += "\n\t".join(self._example_) elif isinstance(self._example_, str): example += self._example_ - self.__doc__ = (self.__doc__ or "").replace(" "*4, "") + syntax + example + self.__doc__ = (self.__doc__ or "").replace(" " * 4, "") + syntax + example self.repeat = False self.repeat_count = 0 self.__last_command = None command_type = kwargs.setdefault("command", gdb.COMMAND_USER) - complete_type = kwargs.setdefault("complete", -1) # -1=allow user-defined `complete()` + complete_type = kwargs.setdefault( + "complete", -1 + ) # -1=allow user-defined `complete()` prefix = kwargs.setdefault("prefix", False) - super().__init__(name=self._cmdline_, command_class=command_type, - completer_class=complete_type, prefix=prefix) + super().__init__( + name=self._cmdline_, + command_class=command_type, + completer_class=complete_type, + prefix=prefix, + ) self.post_load() return @@ -4759,7 +5630,9 @@ def invoke(self, args: str, from_tty: bool) -> None: if gef.config["gef.propagate_debug_exception"] is True: raise else: - err(f"Command '{self._cmdline_}' failed to execute properly, reason: {e}") + err( + f"Command '{self._cmdline_}' failed to execute properly, reason: {e}" + ) return def usage(self) -> None: @@ -4805,7 +5678,9 @@ def __contains__(self, name: str) -> bool: return self.__get_setting_name(name) in gef.config @deprecated("Use `self[setting_name] = value` instead") - def add_setting(self, name: str, value: tuple[Any, type, str], description: str = "") -> None: + def add_setting( + self, name: str, value: tuple[Any, type, str], description: str = "" + ) -> None: return self.__setitem__(name, (value, description)) def __setitem__(self, name: str, value: "GefSetting | tuple[Any, str]") -> None: @@ -4843,7 +5718,9 @@ def __set_repeat_count(self, argv: list[str], from_tty: bool) -> None: self.repeat_count = 0 return - command = (gdb.execute("show commands", to_string=True) or "").strip().split("\n")[-1] + command = ( + (gdb.execute("show commands", to_string=True) or "").strip().split("\n")[-1] + ) self.repeat = self.__last_command == command self.repeat_count = self.repeat_count + 1 if self.repeat else 0 self.__last_command = command @@ -4867,6 +5744,7 @@ def do_invoke(self, argv: list[str]) -> None: self.usage() return + @register class ArchGetCommand(GenericCommand): """Get the current loaded architecture.""" @@ -4892,8 +5770,12 @@ def do_invoke(self, args: list[str]) -> None: reset_architecture(args[0] if args else None) def complete(self, text: str, word: str) -> list[str]: - return sorted(x for x in __registered_architectures__.keys() if - isinstance(x, str) and x.lower().startswith(text.lower().strip())) + return sorted( + x + for x in __registered_architectures__.keys() + if isinstance(x, str) and x.lower().startswith(text.lower().strip()) + ) + @register class ArchListCommand(GenericCommand): @@ -4905,11 +5787,13 @@ class ArchListCommand(GenericCommand): def do_invoke(self, args: list[str]) -> None: gef_print(Color.greenify("Available architectures:")) - for arch in sorted(set(__registered_architectures__.values()), key=lambda x: x.arch): + for arch in sorted( + set(__registered_architectures__.values()), key=lambda x: x.arch + ): if arch is GenericArchitecture: continue - gef_print(' ' + Color.yellowify(str(arch()))) + gef_print(" " + Color.yellowify(str(arch()))) for alias in arch.aliases: if isinstance(alias, str): gef_print(f" {alias}") @@ -4935,11 +5819,31 @@ def do_invoke(self, argv: list[str]) -> None: if git: if (gef_dir / ".git").is_dir(): - ver = subprocess.check_output("git log --format='%H' -n 1 HEAD", cwd=gef_dir, shell=True).decode("utf8").strip() - extra = "dirty" if len(subprocess.check_output("git ls-files -m", cwd=gef_dir, shell=True).decode("utf8").strip()) else "clean" + ver = ( + subprocess.check_output( + "git log --format='%H' -n 1 HEAD", cwd=gef_dir, shell=True + ) + .decode("utf8") + .strip() + ) + extra = ( + "dirty" + if len( + subprocess.check_output( + "git ls-files -m", cwd=gef_dir, shell=True + ) + .decode("utf8") + .strip() + ) + else "clean" + ) gef_print(f"GEF: rev:{ver} (Git - {extra})") else: - gef_blob_hash = subprocess.check_output(f"git hash-object {gef_fpath}", shell=True).decode().strip() + gef_blob_hash = ( + subprocess.check_output(f"git hash-object {gef_fpath}", shell=True) + .decode() + .strip() + ) gef_print("GEF: (Standalone)") gef_print(f"Blob Hash({gef_fpath}): {gef_blob_hash}") gef_print(f"SHA256({gef_fpath}): {gef_hash}") @@ -4960,13 +5864,17 @@ class PrintFormatCommand(GenericCommand): valid_bitness = (8, 16, 32, 64) _cmdline_ = "print-format" - _aliases_ = ["pf",] - _syntax_ = (f"{_cmdline_} [--lang LANG] [--bitlen SIZE] [(--length,-l) LENGTH] [--clip] LOCATION" - f"\t--lang LANG specifies the output format for programming language (available: {valid_formats!s}, default 'py')." - f"\t--bitlen SIZE specifies size of bit (possible values: {valid_bitness!s}, default is 8)." - "\t--length LENGTH specifies length of array (default is 256)." - "\t--clip The output data will be copied to clipboard" - "\tLOCATION specifies where the address of bytes is stored.") + _aliases_ = [ + "pf", + ] + _syntax_ = ( + f"{_cmdline_} [--lang LANG] [--bitlen SIZE] [(--length,-l) LENGTH] [--clip] LOCATION" + f"\t--lang LANG specifies the output format for programming language (available: {valid_formats!s}, default 'py')." + f"\t--bitlen SIZE specifies size of bit (possible values: {valid_bitness!s}, default is 8)." + "\t--length LENGTH specifies length of array (default is 256)." + "\t--clip The output data will be copied to clipboard" + "\tLOCATION specifies where the address of bytes is stored." + ) _example_ = f"{_cmdline_} --lang py -l 16 $rsp" def __init__(self) -> None: @@ -4978,14 +5886,24 @@ def __init__(self) -> None: def format_matrix(self) -> dict[int, tuple[str, str, str]]: # `gef.arch.endianness` is a runtime property, should not be defined as a class property return { - 8: (f"{gef.arch.endianness}B", "char", "db"), + 8: (f"{gef.arch.endianness}B", "char", "db"), 16: (f"{gef.arch.endianness}H", "short", "dw"), 32: (f"{gef.arch.endianness}I", "int", "dd"), 64: (f"{gef.arch.endianness}Q", "long long", "dq"), } @only_if_gdb_running - @parse_arguments({"location": "$pc", }, {("--length", "-l"): 256, "--bitlen": 0, "--lang": "py", "--clip": False,}) + @parse_arguments( + { + "location": "$pc", + }, + { + ("--length", "-l"): 256, + "--bitlen": 0, + "--lang": "py", + "--clip": False, + }, + ) def do_invoke(self, _: list[str], **kwargs: Any) -> None: """Default value for print-format command.""" args: argparse.Namespace = kwargs["arguments"] @@ -5016,7 +5934,7 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: if args.lang == "bytearray": data = gef.memory.read(start_addr, args.length) - preview = str(data[0:self["max_size_preview"]]) + preview = str(data[0 : self["max_size_preview"]]) out = f"Saved data {preview}... in '{gef_convenience(data)}'" elif args.lang == "py": out = f"buf = [{sdata}]" @@ -5029,7 +5947,7 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: asm_type = self.format_matrix[args.bitlen][2] out = f"buf {asm_type} {sdata}" elif args.lang == "hex": - out = gef.memory.read(start_addr, end_addr-start_addr).hex() + out = gef.memory.read(start_addr, end_addr - start_addr).hex() else: raise ValueError(f"Invalid format: {args.lang}") @@ -5069,7 +5987,7 @@ class PieBreakpointCommand(GenericCommand): @parse_arguments({"offset": ""}, {}) def do_invoke(self, _: list[str], **kwargs: Any) -> None: - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] if not args.offset: self.usage() return @@ -5087,7 +6005,9 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: @staticmethod def set_pie_breakpoint(set_func: Callable[[int], str], addr: int) -> None: - gef.session.pie_breakpoints[gef.session.pie_counter] = PieVirtualBreakpoint(set_func, gef.session.pie_counter, addr) + gef.session.pie_breakpoints[gef.session.pie_counter] = PieVirtualBreakpoint( + set_func, gef.session.pie_counter, addr + ) gef.session.pie_counter += 1 return @@ -5099,20 +6019,30 @@ class PieInfoCommand(GenericCommand): _cmdline_ = "pie info" _syntax_ = f"{_cmdline_} BREAKPOINT" - @parse_arguments({"breakpoints": [-1,]}, {}) + @parse_arguments( + { + "breakpoints": [ + -1, + ] + }, + {}, + ) def do_invoke(self, _: list[str], **kwargs: Any) -> None: - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] if args.breakpoints[0] == -1: # No breakpoint info needed bps = gef.session.pie_breakpoints.values() else: - bps = [gef.session.pie_breakpoints[x] - for x in args.breakpoints - if x in gef.session.pie_breakpoints] + bps = [ + gef.session.pie_breakpoints[x] + for x in args.breakpoints + if x in gef.session.pie_breakpoints + ] lines = [f"{'VNum':6s} {'Num':6s} {'Addr':18s}"] lines += [ - f"{x.vbp_num:6d} {str(x.bp_num) if x.bp_num else 'N/A':6s} {x.addr:18s}" for x in bps + f"{x.vbp_num:6d} {str(x.bp_num) if x.bp_num else 'N/A':6s} {x.addr:18s}" + for x in bps ] gef_print("\n".join(lines)) return @@ -5125,21 +6055,31 @@ class PieDeleteCommand(GenericCommand): _cmdline_ = "pie delete" _syntax_ = f"{_cmdline_} [BREAKPOINT]" - @parse_arguments({"breakpoints": [-1,]}, {}) + @parse_arguments( + { + "breakpoints": [ + -1, + ] + }, + {}, + ) def do_invoke(self, _: list[str], **kwargs: Any) -> None: global gef - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] if args.breakpoints[0] == -1: # no arg, delete all to_delete = list(gef.session.pie_breakpoints.values()) self.delete_bp(to_delete) else: - self.delete_bp([gef.session.pie_breakpoints[x] - for x in args.breakpoints - if x in gef.session.pie_breakpoints]) + self.delete_bp( + [ + gef.session.pie_breakpoints[x] + for x in args.breakpoints + if x in gef.session.pie_breakpoints + ] + ) return - @staticmethod def delete_bp(breakpoints: list[PieVirtualBreakpoint]) -> None: global gef @@ -5248,9 +6188,10 @@ class SmartEvalCommand(GenericCommand): """SmartEval: Smart eval (vague approach to mimic WinDBG `?`).""" _cmdline_ = "$" - _syntax_ = f"{_cmdline_} EXPR\n{_cmdline_} ADDRESS1 ADDRESS2" - _example_ = (f"\n{_cmdline_} $pc+1" - f"\n{_cmdline_} 0x00007ffff7a10000 0x00007ffff7bce000") + _syntax_ = f"{_cmdline_} EXPR\n{_cmdline_} ADDRESS1 ADDRESS2" + _example_ = ( + f"\n{_cmdline_} $pc+1\n{_cmdline_} 0x00007ffff7a10000 0x00007ffff7bce000" + ) def do_invoke(self, argv: list[str]) -> None: argc = len(argv) @@ -5264,13 +6205,17 @@ def do_invoke(self, argv: list[str]) -> None: def evaluate(self, expr: list[str]) -> None: def show_as_int(i: int) -> None: - off = gef.arch.ptrsize*8 - def comp2_x(x: Any) -> str: return f"{(x + (1 << off)) % (1 << off):x}" - def comp2_b(x: Any) -> str: return f"{(x + (1 << off)) % (1 << off):b}" + off = gef.arch.ptrsize * 8 + + def comp2_x(x: Any) -> str: + return f"{(x + (1 << off)) % (1 << off):x}" + + def comp2_b(x: Any) -> str: + return f"{(x + (1 << off)) % (1 << off):b}" try: s_i = comp2_x(res) - s_i = s_i.rjust(len(s_i)+1, "0") if len(s_i)%2 else s_i + s_i = s_i.rjust(len(s_i) + 1, "0") if len(s_i) % 2 else s_i gef_print(f"{i:d}") gef_print("0x" + comp2_x(res)) gef_print("0b" + comp2_b(res)) @@ -5333,7 +6278,9 @@ def do_invoke(self, argv: list[str]) -> None: return canary, location = res - info(f"The canary of process {gef.session.pid} is at {location:#x}, value is {canary:#x}") + info( + f"The canary of process {gef.session.pid} is at {location:#x}, value is {canary:#x}" + ) return @@ -5343,8 +6290,10 @@ class ProcessStatusCommand(GenericCommand): process status (file descriptors, ancestor, descendants, etc.).""" _cmdline_ = "process-status" - _syntax_ = _cmdline_ - _aliases_ = ["status", ] + _syntax_ = _cmdline_ + _aliases_ = [ + "status", + ] def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_NONE) @@ -5377,7 +6326,14 @@ def get_process_path_of(self, pid: int) -> str: return os.readlink(f"/proc/{pid}/exe") def get_children_pids(self, pid: int) -> list[int]: - cmd = [gef.session.constants["ps"], "-o", "pid", "--ppid", f"{pid}", "--noheaders"] + cmd = [ + gef.session.constants["ps"], + "-o", + "pid", + "--ppid", + f"{pid}", + "--noheaders", + ] try: return [int(x) for x in gef_execute_external(cmd, as_list=True)] except Exception: @@ -5387,9 +6343,12 @@ def show_info_proc(self) -> None: info("Process Information") pid = gef.session.pid cmdline = self.get_cmdline_of(pid) - gef_print(f"\tPID {RIGHT_ARROW} {pid}", - f"\tExecutable {RIGHT_ARROW} {self.get_process_path_of(pid)}", - f"\tCommand line {RIGHT_ARROW} '{cmdline}'", sep="\n") + gef_print( + f"\tPID {RIGHT_ARROW} {pid}", + f"\tExecutable {RIGHT_ARROW} {self.get_process_path_of(pid)}", + f"\tCommand line {RIGHT_ARROW} '{cmdline}'", + sep="\n", + ) return def show_ancestor(self) -> None: @@ -5397,8 +6356,11 @@ def show_ancestor(self) -> None: ppid = int(self.get_state_of(gef.session.pid)["PPid"]) state = self.get_state_of(ppid) cmdline = self.get_cmdline_of(ppid) - gef_print(f"\tParent PID {RIGHT_ARROW} {state['Pid']}", - f"\tCommand line {RIGHT_ARROW} '{cmdline}'", sep="\n") + gef_print( + f"\tParent PID {RIGHT_ARROW} {state['Pid']}", + f"\tCommand line {RIGHT_ARROW} '{cmdline}'", + sep="\n", + ) return def show_descendants(self) -> None: @@ -5411,8 +6373,10 @@ def show_descendants(self) -> None: for child_pid in children: state = self.get_state_of(child_pid) pid = int(state["Pid"]) - gef_print(f"\tPID {RIGHT_ARROW} {pid} (Name: '{self.get_process_path_of(pid)}'," - f" CmdLine: '{self.get_cmdline_of(pid)}')") + gef_print( + f"\tPID {RIGHT_ARROW} {pid} (Name: '{self.get_process_path_of(pid)}'," + f" CmdLine: '{self.get_cmdline_of(pid)}')" + ) return def show_fds(self) -> None: @@ -5488,9 +6452,15 @@ def show_connections(self) -> None: local = self.parse_ip_port(local) remote = self.parse_ip_port(remote) state = int(state, 16) - state_str = tcp_states_str[state] if proto == "TCP" else udp_states_str[state] + state_str = ( + tcp_states_str[state] + if proto == "TCP" + else udp_states_str[state] + ) - gef_print(f"\t{local[0]}:{local[1]} {RIGHT_ARROW} {remote[0]}:{remote[1]} ({state_str})") + gef_print( + f"\t{local[0]}:{local[1]} {RIGHT_ARROW} {remote[0]}:{remote[1]} ({state_str})" + ) return @@ -5500,7 +6470,7 @@ class GefThemeCommand(GenericCommand): _cmdline_ = "theme" _syntax_ = f"{_cmdline_} [KEY [VALUE]]" - _example_ = (f"{_cmdline_} address_stack green") + _example_ = f"{_cmdline_} address_stack green" def __init__(self) -> None: super().__init__(self._cmdline_) @@ -5508,19 +6478,40 @@ def __init__(self) -> None: self["context_title_message"] = ("cyan", "Color of the title in context window") self["default_title_line"] = ("gray", "Default color of borders") self["default_title_message"] = ("cyan", "Default color of title") - self["table_heading"] = ("blue", "Color of the column headings to tables (e.g. vmmap)") - self["old_context"] = ("gray", "Color to use to show things such as code that is not immediately relevant") - self["disassemble_current_instruction"] = ("green", "Color to use to highlight the current $pc when disassembling") + self["table_heading"] = ( + "blue", + "Color of the column headings to tables (e.g. vmmap)", + ) + self["old_context"] = ( + "gray", + "Color to use to show things such as code that is not immediately relevant", + ) + self["disassemble_current_instruction"] = ( + "green", + "Color to use to highlight the current $pc when disassembling", + ) self["dereference_string"] = ("yellow", "Color of dereferenced string") self["dereference_code"] = ("gray", "Color of dereferenced code") self["dereference_base_address"] = ("cyan", "Color of dereferenced address") - self["dereference_register_value"] = ("bold blue", "Color of dereferenced register") - self["registers_register_name"] = ("blue", "Color of the register name in the register window") - self["registers_value_changed"] = ("bold red", "Color of the changed register in the register window") + self["dereference_register_value"] = ( + "bold blue", + "Color of dereferenced register", + ) + self["registers_register_name"] = ( + "blue", + "Color of the register name in the register window", + ) + self["registers_value_changed"] = ( + "bold red", + "Color of the changed register in the register window", + ) self["address_stack"] = ("pink", "Color to use when a stack address is found") self["address_heap"] = ("green", "Color to use when a heap address is found") self["address_code"] = ("red", "Color to use when a code address is found") - self["source_current_line"] = ("green", "Color to use for the current code line in the source window") + self["source_current_line"] = ( + "green", + "Color to use for the current code line in the source window", + ) return def do_invoke(self, args: list[str]) -> None: @@ -5545,12 +6536,17 @@ def do_invoke(self, args: list[str]) -> None: return colors = (color for color in args[1:] if color in Color.colors) - self[setting_name] = " ".join(colors) # type: ignore // this is valid since we overwrote __setitem__() + self[setting_name] = " ".join(colors) # type: ignore // this is valid since we overwrote __setitem__() class ExternalStructureManager: class Structure: - def __init__(self, manager: "ExternalStructureManager", mod_path: pathlib.Path, struct_name: str) -> None: + def __init__( + self, + manager: "ExternalStructureManager", + mod_path: pathlib.Path, + struct_name: str, + ) -> None: self.manager = manager self.module_path = mod_path self.name = struct_name @@ -5565,10 +6561,12 @@ def __str__(self) -> str: def pprint(self) -> None: res: list[str] = [] - for _name, _type in self.class_type._fields_: # type: ignore + for _name, _type in self.class_type._fields_: # type: ignore size = ctypes.sizeof(_type) name = Color.colorify(_name, gef.config["pcustom.structure_name"]) - type = Color.colorify(_type.__name__, gef.config["pcustom.structure_type"]) + type = Color.colorify( + _type.__name__, gef.config["pcustom.structure_type"] + ) size = Color.colorify(hex(size), gef.config["pcustom.structure_size"]) offset = Color.boldify(f"{getattr(self.class_type, _name).offset:04x}") res.append(f"{offset} {name:32s} {type:16s} /* size={size} */") @@ -5616,9 +6614,14 @@ def apply_at(self, address: int, max_depth: int, depth: int = 0) -> None: _value = getattr(_structure, _name) _offset = getattr(self.class_type, _name).offset - if ((ptrsize == 4 and _type is ctypes.c_uint32) + if ( + (ptrsize == 4 and _type is ctypes.c_uint32) or (ptrsize == 8 and _type is ctypes.c_uint64) - or (ptrsize == ctypes.sizeof(ctypes.c_void_p) and _type is ctypes.c_void_p)): + or ( + ptrsize == ctypes.sizeof(ctypes.c_void_p) + and _type is ctypes.c_void_p + ) + ): # try to dereference pointers _value = RIGHT_ARROW.join(dereference_from(_value)) @@ -5643,29 +6646,37 @@ def apply_at(self, address: int, max_depth: int, depth: int = 0) -> None: return def __get_ctypes_value(self, struct, item, value) -> str: - if not hasattr(struct, "_values_"): return "" + if not hasattr(struct, "_values_"): + return "" default = "" for name, values in struct._values_: - if name != item: continue + if name != item: + continue if callable(values): return str(values(value)) try: for val, desc in values: - if value == val: return desc - if val is None: default = desc + if value == val: + return desc + if val is None: + default = desc except Exception as e: err(f"Error parsing '{name}': {e}") return default class Module(dict): - def __init__(self, manager: "ExternalStructureManager", path: pathlib.Path) -> None: + def __init__( + self, manager: "ExternalStructureManager", path: pathlib.Path + ) -> None: self.manager = manager self.path = path self.name = path.stem self.raw = self.__load() for entry in self: - structure = ExternalStructureManager.Structure(manager, self.path, entry) + structure = ExternalStructureManager.Structure( + manager, self.path, entry + ) self[structure.name] = structure return @@ -5685,7 +6696,8 @@ def __str__(self) -> str: def __iter__(self) -> Generator[str, None, None]: _invalid = {"BigEndianStructure", "LittleEndianStructure", "Structure"} for x in dir(self.raw): - if x in _invalid: continue + if x in _invalid: + continue _attr = getattr(self.raw, x) # if it's a ctypes.Structure class, add it @@ -5694,7 +6706,11 @@ def __iter__(self) -> Generator[str, None, None]: continue # also accept class factory functions - if callable(_attr) and _attr.__module__ == self.name and x.endswith("_t"): + if ( + callable(_attr) + and _attr.__module__ == self.name + and x.endswith("_t") + ): yield x continue return @@ -5705,9 +6721,12 @@ def __init__(self, manager: "ExternalStructureManager") -> None: self.root: pathlib.Path = manager.path for entry in self.root.iterdir(): - if not entry.is_file(): continue - if entry.suffix != ".py": continue - if entry.name == "__init__.py": continue + if not entry.is_file(): + continue + if entry.suffix != ".py": + continue + if entry.name == "__init__.py": + continue module = ExternalStructureManager.Module(manager, entry) self[module.name] = module return @@ -5741,14 +6760,25 @@ def path(self) -> pathlib.Path: return self._path @property - def structures(self) -> Generator[tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"], None, None]: + def structures( + self, + ) -> Generator[ + tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"], + None, + None, + ]: for module in self.modules.values(): for structure in module.values(): yield module, structure return @lru_cache() - def find(self, structure_name: str) -> tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"] | None: + def find( + self, structure_name: str + ) -> ( + tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"] + | None + ): """Return the module and structure for the given structure name; `None` if the structure name was not found.""" for module in self.modules.values(): if structure_name in module: @@ -5766,7 +6796,7 @@ class PCustomCommand(GenericCommand): configuration setting.""" _cmdline_ = "pcustom" - _syntax_ = f"{_cmdline_} [list|edit |show ]| 0xADDRESS]" + _syntax_ = f"{_cmdline_} [list|edit |show ]| 0xADDRESS]" def __init__(self) -> None: global gef @@ -5775,9 +6805,16 @@ def __init__(self) -> None: self["structure_name"] = ("bold blue", "Color of the structure name") self["structure_type"] = ("bold red", "Color of the attribute type") self["structure_size"] = ("green", "Color of the attribute size") - gef.config[f"{self._cmdline_}.struct_path"] = GefSetting( gef.config["gef.tempdir"] / "structs", pathlib.Path, - "Path to store/load the structure ctypes files", - hooks={"on_write": [GefSetting.create_folder_tree,]}) + gef.config[f"{self._cmdline_}.struct_path"] = GefSetting( + gef.config["gef.tempdir"] / "structs", + pathlib.Path, + "Path to store/load the structure ctypes files", + hooks={ + "on_write": [ + GefSetting.create_folder_tree, + ] + }, + ) return @parse_arguments({"type": "", "address": ""}, {}) @@ -5832,7 +6869,12 @@ def do_invoke(self, _: list[str]) -> None: struct_color = gef.config["pcustom.structure_type"] filename_color = gef.config["pcustom.structure_name"] for module in manager.modules.values(): - __modules = ", ".join([Color.colorify(str(structure), struct_color) for structure in module.values()]) + __modules = ", ".join( + [ + Color.colorify(str(structure), struct_color) + for structure in module.values() + ] + ) __filename = Color.colorify(str(module.path), filename_color) gef_print(f"{RIGHT_ARROW} {__filename} ({__modules})") return @@ -5940,13 +6982,20 @@ def do_invoke(self, argv: list[str]) -> None: AF_INET = 2 SOCK_STREAM = 1 - res = gdb.execute(f"call (int)socket({AF_INET}, {SOCK_STREAM}, 0)", to_string=True) or "" + res = ( + gdb.execute( + f"call (int)socket({AF_INET}, {SOCK_STREAM}, 0)", to_string=True + ) + or "" + ) new_fd = self.get_fd_from_result(res) # fill in memory with sockaddr_in struct contents # we will do this in the stack, since connect() wants a pointer to a struct vmmap = gef.memory.maps - stack_addr = [entry.page_start for entry in vmmap if entry.path == "[stack]"][0] + stack_addr = [ + entry.page_start for entry in vmmap if entry.path == "[stack]" + ][0] original_contents = gef.memory.read(stack_addr, 8) gef.memory.write(stack_addr, b"\x02\x00", 2) @@ -5954,7 +7003,9 @@ def do_invoke(self, argv: list[str]) -> None: gef.memory.write(stack_addr + 0x4, socket.inet_aton(address), 4) info(f"Trying to connect to {new_output}") - res = gdb.execute(f"""call (int)connect({new_fd}, {stack_addr}, {16})""", to_string=True) + res = gdb.execute( + f"""call (int)connect({new_fd}, {stack_addr}, {16})""", to_string=True + ) if res is None: err("Call to `connect` failed") return @@ -5969,7 +7020,9 @@ def do_invoke(self, argv: list[str]) -> None: info(f"Connected to {new_output}") else: - res = gdb.execute(f"""call (int)open("{new_output}", 66, 0666)""", to_string=True) + res = gdb.execute( + f"""call (int)open("{new_output}", 66, 0666)""", to_string=True + ) if res is None: err("Call to `open` failed") return @@ -5995,8 +7048,10 @@ class ScanSectionCommand(GenericCommand): to another (needle).""" _cmdline_ = "scan" - _syntax_ = f"{_cmdline_} HAYSTACK NEEDLE" - _aliases_ = ["lookup",] + _syntax_ = f"{_cmdline_} HAYSTACK NEEDLE" + _aliases_ = [ + "lookup", + ] _example_ = f"\n{_cmdline_} stack libc" @only_if_gdb_running @@ -6008,8 +7063,10 @@ def do_invoke(self, argv: list[str]) -> None: haystack = argv[0] needle = argv[1] - info(f"Searching for addresses in '{Color.yellowify(haystack)}' " - f"that point to '{Color.yellowify(needle)}'") + info( + f"Searching for addresses in '{Color.yellowify(haystack)}' " + f"that point to '{Color.yellowify(needle)}'" + ) fpath = get_filepath() or "" @@ -6034,7 +7091,9 @@ def do_invoke(self, argv: list[str]) -> None: if sect.path is None: continue if haystack in sect.path: - haystack_sections.append((sect.page_start, sect.page_end, os.path.basename(sect.path))) + haystack_sections.append( + (sect.page_start, sect.page_end, os.path.basename(sect.path)) + ) if needle in sect.path: needle_sections.append((sect.page_start, sect.page_end)) @@ -6051,10 +7110,12 @@ def do_invoke(self, argv: list[str]) -> None: continue for i in range(0, len(mem), step): - target = unpack(mem[i:i+step]) + target = unpack(mem[i : i + step]) for nstart, nend in needle_sections: if target >= nstart and target < nend: - deref = dereference_cmd.pprint_dereferenced(hstart, int(i / step)) + deref = dereference_cmd.pprint_dereferenced( + hstart, int(i / step) + ) if hname != "": name = Color.colorify(hname, "yellow") gef_print(f"{name}: {deref}") @@ -6072,15 +7133,20 @@ class SearchPatternCommand(GenericCommand): _cmdline_ = "search-pattern" _syntax_ = f"{_cmdline_} PATTERN [little|big] [section]" _aliases_ = ["grep", "xref"] - _example_ = [f"{_cmdline_} AAAAAAAA", - f"{_cmdline_} 0x555555554000 little stack", - f"{_cmdline_} AAAA 0x600000-0x601000", - f"{_cmdline_} --regex 0x401000 0x401500 ([\\\\x20-\\\\x7E]{{2,}})(?=\\\\x00) <-- It matches null-end-printable(from x20-x7e) C strings (min size 2 bytes)"] + _example_ = [ + f"{_cmdline_} AAAAAAAA", + f"{_cmdline_} 0x555555554000 little stack", + f"{_cmdline_} AAAA 0x600000-0x601000", + f"{_cmdline_} --regex 0x401000 0x401500 ([\\\\x20-\\\\x7E]{{2,}})(?=\\\\x00) <-- It matches null-end-printable(from x20-x7e) C strings (min size 2 bytes)", + ] def __init__(self) -> None: super().__init__() self["max_size_preview"] = (10, "max size preview of bytes") - self["nr_pages_chunk"] = (0x400, "number of pages readed for each memory read chunk") + self["nr_pages_chunk"] = ( + 0x400, + "number of pages readed for each memory read chunk", + ) return def print_section(self, section: Section) -> None: @@ -6094,10 +7160,14 @@ def print_section(self, section: Section) -> None: return def print_loc(self, loc: tuple[int, int, str]) -> None: - gef_print(f""" {loc[0]:#x} - {loc[1]:#x} {RIGHT_ARROW} "{Color.pinkify(loc[2])}" """) + gef_print( + f""" {loc[0]:#x} - {loc[1]:#x} {RIGHT_ARROW} "{Color.pinkify(loc[2])}" """ + ) return - def search_pattern_by_address(self, pattern: str, start_address: int, end_address: int) -> list[tuple[int, int, str]]: + def search_pattern_by_address( + self, pattern: str, start_address: int, end_address: int + ) -> list[tuple[int, int, str]]: """Search a pattern within a range defined by arguments.""" _pattern = gef_pybytes(pattern) step = self["nr_pages_chunk"] * gef.session.pagesize @@ -6129,7 +7199,9 @@ def search_pattern_by_address(self, pattern: str, start_address: int, end_addres return locations - def search_binpattern_by_address(self, binpattern: bytes, start_address: int, end_address: int) -> list[tuple[int, int, str]]: + def search_binpattern_by_address( + self, binpattern: bytes, start_address: int, end_address: int + ) -> list[tuple[int, int, str]]: """Search a binary pattern within a range defined by arguments.""" step = self["nr_pages_chunk"] * gef.session.pagesize @@ -6163,9 +7235,12 @@ def search_binpattern_by_address(self, binpattern: bytes, start_address: int, en def search_pattern(self, pattern: str, section_name: str) -> None: """Search a pattern within the whole userland memory.""" for section in gef.memory.maps: - if not section.permission & Permission.READ: continue - if section.path == "[vvar]": continue - if section_name not in section.path: continue + if not section.permission & Permission.READ: + continue + if section.path == "[vvar]": + continue + if section_name not in section.path: + continue start = section.page_start end = section.page_end - 1 @@ -6189,7 +7264,7 @@ def do_invoke(self, argv: list[str]) -> None: return if argc > 3 and argv[0].startswith("--regex"): - pattern = ' '.join(argv[3:]) + pattern = " ".join(argv[3:]) pattern = ast.literal_eval("b'" + pattern + "'") addr_start = parse_address(argv[1]) @@ -6204,14 +7279,20 @@ def do_invoke(self, argv: list[str]) -> None: endian = gef.arch.endianness if argc >= 2: - if argv[1].lower() == "big": endian = Endianness.BIG_ENDIAN - elif argv[1].lower() == "little": endian = Endianness.LITTLE_ENDIAN + if argv[1].lower() == "big": + endian = Endianness.BIG_ENDIAN + elif argv[1].lower() == "little": + endian = Endianness.LITTLE_ENDIAN if is_hex(pattern): if endian == Endianness.BIG_ENDIAN: - pattern = "".join(["\\x" + pattern[i:i + 2] for i in range(2, len(pattern), 2)]) + pattern = "".join( + ["\\x" + pattern[i : i + 2] for i in range(2, len(pattern), 2)] + ) else: - pattern = "".join(["\\x" + pattern[i:i + 2] for i in range(len(pattern) - 2, 0, -2)]) + pattern = "".join( + ["\\x" + pattern[i : i + 2] for i in range(len(pattern) - 2, 0, -2)] + ) if argc == 3: info(f"Searching '{Color.yellowify(pattern)}' in {argv[2]}") @@ -6242,14 +7323,17 @@ class FlagsCommand(GenericCommand): """Edit flags in a human friendly way.""" _cmdline_ = "edit-flags" - _syntax_ = f"{_cmdline_} [(+|-|~)FLAGNAME ...]" - _aliases_ = ["flags",] - _example_ = (f"\n{_cmdline_}" - f"\n{_cmdline_} +zero # sets ZERO flag") + _syntax_ = f"{_cmdline_} [(+|-|~)FLAGNAME ...]" + _aliases_ = [ + "flags", + ] + _example_ = f"\n{_cmdline_}\n{_cmdline_} +zero # sets ZERO flag" def do_invoke(self, argv: list[str]) -> None: if not gef.arch.flag_register: - warn(f"The architecture {gef.arch.arch}:{gef.arch.mode} doesn't have flag register.") + warn( + f"The architecture {gef.arch.arch}:{gef.arch.mode} doesn't have flag register." + ) return for flag in argv: @@ -6293,25 +7377,32 @@ class RemoteCommand(GenericCommand): will likely fail. You can however still use the limited command provided by GDB `target remote`. **Important:** - As of 2024.09, the `gef-remote` is deprecated in favor of the native command `target remote` command. As it will be + As of 2026.04, the `gef-remote` is deprecated in favor of the native command `target remote` command. As it will be removed in a future release, do not rely on it. """ _cmdline_ = "gef-remote" - _syntax_ = f"{_cmdline_} [OPTIONS] TARGET" - _example_ = [f"{_cmdline_} localhost 1234", - f"{_cmdline_} --pid 6789 localhost 1234", - f"{_cmdline_} --qemu-user --qemu-binary /bin/debugme localhost 4444 "] + _syntax_ = f"{_cmdline_} [OPTIONS] TARGET" + _example_ = [ + f"{_cmdline_} localhost 1234", + f"{_cmdline_} --pid 6789 localhost 1234", + f"{_cmdline_} --qemu-user --qemu-binary /bin/debugme localhost 4444 ", + ] def __init__(self) -> None: super().__init__(prefix=False) return - @parse_arguments({"host": "", "port": 0}, {"--pid": -1, "--qemu-user": False, "--qemu-binary": ""}) + @parse_arguments( + {"host": "", "port": 0}, + {"--pid": -1, "--qemu-user": False, "--qemu-binary": ""}, + ) def do_invoke(self, _: list[str], **kwargs: Any) -> None: # for now, warn only and re-route to `target remote` - warn("`gef-remote` is now deprecated and will soon be removed. Use `target remote`") - args : argparse.Namespace = kwargs["arguments"] + warn( + "`gef-remote` is now deprecated and will soon be removed. Use `target remote`" + ) + args: argparse.Namespace = kwargs["arguments"] if not args.host or not args.port: err("Missing host/port parameters") return @@ -6320,20 +7411,23 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: return - @register class SkipiCommand(GenericCommand): """Skip N instruction(s) execution""" _cmdline_ = "skipi" - _syntax_ = (f"{_cmdline_} [LOCATION] [--n NUM_INSTRUCTIONS]" - "\n\tLOCATION\taddress/symbol from where to skip" - "\t--n NUM_INSTRUCTIONS\tSkip the specified number of instructions instead of the default 1.") + _syntax_ = ( + f"{_cmdline_} [LOCATION] [--n NUM_INSTRUCTIONS]" + "\n\tLOCATION\taddress/symbol from where to skip" + "\t--n NUM_INSTRUCTIONS\tSkip the specified number of instructions instead of the default 1." + ) - _example_ = [f"{_cmdline_}", - f"{_cmdline_} --n 3", - f"{_cmdline_} 0x69696969", - f"{_cmdline_} 0x69696969 --n 6",] + _example_ = [ + f"{_cmdline_}", + f"{_cmdline_} --n 3", + f"{_cmdline_} 0x69696969", + f"{_cmdline_} 0x69696969 --n 6", + ] def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) @@ -6342,15 +7436,17 @@ def __init__(self) -> None: @only_if_gdb_running @parse_arguments({"address": "$pc"}, {"--n": 1}) def do_invoke(self, _: list[str], **kwargs: Any) -> None: - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] address = parse_address(args.address) num_instructions = args.n - last_insn = gef_instruction_n(address, num_instructions-1) + last_insn = gef_instruction_n(address, num_instructions - 1) total_bytes = (last_insn.address - address) + last_insn.size() - target_addr = address + total_bytes + target_addr = address + total_bytes - info(f"skipping {num_instructions} instructions ({total_bytes} bytes) from {address:#x} to {target_addr:#x}") + info( + f"skipping {num_instructions} instructions ({total_bytes} bytes) from {address:#x} to {target_addr:#x}" + ) gdb.execute(f"set $pc = {target_addr:#x}") return @@ -6360,10 +7456,16 @@ class StepoverCommand(GenericCommand): """Breaks on the instruction immediately following this one. Ex: Step over call instruction""" _cmdline_ = "stepover" - _syntax_ = (f"{_cmdline_}" - "\n\tBreaks on the instruction immediately following this one. Ex: Step over call instruction.") - _aliases_ = ["so",] - _example_ = [f"{_cmdline_}",] + _syntax_ = ( + f"{_cmdline_}" + "\n\tBreaks on the instruction immediately following this one. Ex: Step over call instruction." + ) + _aliases_ = [ + "so", + ] + _example_ = [ + f"{_cmdline_}", + ] def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) @@ -6372,7 +7474,7 @@ def __init__(self) -> None: @only_if_gdb_running def do_invoke(self, _: list[str]) -> None: target_addr = gef_next_instruction(parse_address("$pc")).address - JustSilentStopBreakpoint("".join(["*", str(target_addr)])) + JustSilentStopBreakpoint("".join(["*", str(target_addr)])) gdb.execute("continue") return @@ -6383,28 +7485,33 @@ class NopCommand(GenericCommand): aware.""" _cmdline_ = "nop" - _syntax_ = (f"{_cmdline_} [LOCATION] [--i ITEMS] [--f] [--n] [--b]" - "\n\tLOCATION\taddress/symbol to patch (by default this command replaces whole instructions)" - "\t--i ITEMS\tnumber of items to insert (default 1)" - "\t--f\tForce patch even when the selected settings could overwrite partial instructions" - "\t--n\tInstead of replacing whole instructions, insert ITEMS nop instructions, no matter how many instructions it overwrites" - "\t--b\tInstead of replacing whole instructions, fill ITEMS bytes with nops") - _example_ = [f"{_cmdline_}", - f"{_cmdline_} $pc+3", - f"{_cmdline_} --i 2 $pc+3", - f"{_cmdline_} --b", - f"{_cmdline_} --b $pc+3", - f"{_cmdline_} --f --b --i 2 $pc+3" - f"{_cmdline_} --n --i 2 $pc+3",] + _syntax_ = ( + f"{_cmdline_} [LOCATION] [--i ITEMS] [--f] [--n] [--b]" + "\n\tLOCATION\taddress/symbol to patch (by default this command replaces whole instructions)" + "\t--i ITEMS\tnumber of items to insert (default 1)" + "\t--f\tForce patch even when the selected settings could overwrite partial instructions" + "\t--n\tInstead of replacing whole instructions, insert ITEMS nop instructions, no matter how many instructions it overwrites" + "\t--b\tInstead of replacing whole instructions, fill ITEMS bytes with nops" + ) + _example_ = [ + f"{_cmdline_}", + f"{_cmdline_} $pc+3", + f"{_cmdline_} --i 2 $pc+3", + f"{_cmdline_} --b", + f"{_cmdline_} --b $pc+3", + f"{_cmdline_} --f --b --i 2 $pc+3{_cmdline_} --n --i 2 $pc+3", + ] def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) return @only_if_gdb_running - @parse_arguments({"address": "$pc"}, {"--i": 1, "--b": False, "--f": False, "--n": False}) + @parse_arguments( + {"address": "$pc"}, {"--i": 1, "--b": False, "--f": False, "--n": False} + ) def do_invoke(self, _: list[str], **kwargs: Any) -> None: - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] address = parse_address(args.address) nop = gef.arch.nop_insn num_items = int(args.i) or 1 @@ -6423,17 +7530,23 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: total_bytes = num_items * len(nop) else: try: - last_insn = gef_instruction_n(address, num_items-1) + last_insn = gef_instruction_n(address, num_items - 1) last_addr = last_insn.address except Exception as e: - err(f"Cannot patch instruction at {address:#x} reaching unmapped area, reason: {e}") + err( + f"Cannot patch instruction at {address:#x} reaching unmapped area, reason: {e}" + ) return - total_bytes = (last_addr - address) + gef_get_instruction_at(last_addr).size() + total_bytes = (last_addr - address) + gef_get_instruction_at( + last_addr + ).size() if len(nop) > total_bytes or total_bytes % len(nop): - warn(f"Patching {total_bytes} bytes at {address:#x} will result in LAST-NOP " - f"(byte nr {total_bytes % len(nop):#x}) broken and may cause a crash or " - "break disassembly.") + warn( + f"Patching {total_bytes} bytes at {address:#x} will result in LAST-NOP " + f"(byte nr {total_bytes % len(nop):#x}) broken and may cause a crash or " + "break disassembly." + ) if not force_flag: warn("Use --f (force) to ignore this warning.") return @@ -6449,9 +7562,11 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: final_ins_end_addr = curr_ins.address + curr_ins.size() if final_ins_end_addr != target_end_address: - warn(f"Patching {total_bytes} bytes at {address:#x} will result in LAST-INSTRUCTION " - f"({curr_ins.address:#x}) being partial overwritten and may cause a crash or " - "break disassembly.") + warn( + f"Patching {total_bytes} bytes at {address:#x} will result in LAST-INSTRUCTION " + f"({curr_ins.address:#x}) being partial overwritten and may cause a crash or " + "break disassembly." + ) if not force_flag: warn("Use --f (force) to ignore this warning.") return @@ -6459,8 +7574,10 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: nops = bytearray(nop * total_bytes) end_address = Address(value=address + total_bytes - 1) if not end_address.valid: - err(f"Cannot patch instruction at {address:#x}: reaching unmapped " - f"area: {end_address:#x}") + err( + f"Cannot patch instruction at {address:#x}: reaching unmapped " + f"area: {end_address:#x}" + ) return ok(f"Patching {total_bytes} bytes from {address:#x}") @@ -6475,9 +7592,11 @@ class StubCommand(GenericCommand): function to be called and disrupt your runtime flow (ex. fork).""" _cmdline_ = "stub" - _syntax_ = (f"{_cmdline_} [--retval RETVAL] [address]" - "\taddress\taddress/symbol to stub out" - "\t--retval RETVAL\tSet the return value") + _syntax_ = ( + f"{_cmdline_} [--retval RETVAL] [address]" + "\taddress\taddress/symbol to stub out" + "\t--retval RETVAL\tSet the return value" + ) _example_ = f"{_cmdline_} --retval 0 fork" def __init__(self) -> None: @@ -6487,7 +7606,7 @@ def __init__(self) -> None: @only_if_gdb_running @parse_arguments({"address": ""}, {("-r", "--retval"): 0}) def do_invoke(self, _: list[str], **kwargs: Any) -> None: - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] loc = args.address if args.address else f"*{gef.arch.pc:#x}" StubBreakpoint(loc, args.retval) return @@ -6498,7 +7617,7 @@ class GlibcHeapCommand(GenericCommand): """Base command to get information about the Glibc heap structure.""" _cmdline_ = "heap" - _syntax_ = f"{_cmdline_} (chunk|chunks|bins|arenas|set-arena)" + _syntax_ = f"{_cmdline_} (chunk|chunks|bins|arenas|set-arena)" def __init__(self) -> None: super().__init__(prefix=True) @@ -6515,7 +7634,7 @@ class GlibcHeapSetArenaCommand(GenericCommand): """Set the address of the main_arena or the currently selected arena.""" _cmdline_ = "heap set-arena" - _syntax_ = f"{_cmdline_} [address|&symbol]" + _syntax_ = f"{_cmdline_} [address|&symbol]" _example_ = f"{_cmdline_} 0x001337001337" def __init__(self) -> None: @@ -6543,7 +7662,7 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: err("Invalid symbol for arena") return - new_arena = GlibcArena( f"*{new_arena_address:#x}") + new_arena = GlibcArena(f"*{new_arena_address:#x}") if new_arena in gef.heap.arenas: # if entered arena is in arena list then just select it gef.heap.selected_arena = new_arena @@ -6558,7 +7677,7 @@ class GlibcHeapArenaCommand(GenericCommand): """Display information on a heap chunk.""" _cmdline_ = "heap arenas" - _syntax_ = _cmdline_ + _syntax_ = _cmdline_ @only_if_gdb_running def do_invoke(self, _: list[str]) -> None: @@ -6573,7 +7692,7 @@ class GlibcHeapChunkCommand(GenericCommand): See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123.""" _cmdline_ = "heap chunk" - _syntax_ = f"{_cmdline_} [-h] [--allow-unaligned] [--number] address" + _syntax_ = f"{_cmdline_} [-h] [--allow-unaligned] [--number] address" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) @@ -6582,7 +7701,7 @@ def __init__(self) -> None: @parse_arguments({"address": ""}, {"--allow-unaligned": False, "--number": 1}) @only_if_gdb_running def do_invoke(self, _: list[str], **kwargs: Any) -> None: - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] if not args.address: err("Missing chunk address") self.usage() @@ -6612,7 +7731,7 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: class GlibcHeapChunkSummary: - def __init__(self, desc = ""): + def __init__(self, desc=""): self.desc = desc self.count = 0 self.total_bytes = 0 @@ -6623,13 +7742,13 @@ def process_chunk(self, chunk: GlibcChunk) -> None: class GlibcHeapArenaSummary: - def __init__(self, resolve_type = False) -> None: + def __init__(self, resolve_type=False) -> None: self.resolve_symbol = resolve_type self.size_distribution = {} self.flag_distribution = { "PREV_INUSE": GlibcHeapChunkSummary(), "IS_MMAPPED": GlibcHeapChunkSummary(), - "NON_MAIN_ARENA": GlibcHeapChunkSummary() + "NON_MAIN_ARENA": GlibcHeapChunkSummary(), } def process_chunk(self, chunk: GlibcChunk) -> None: @@ -6650,17 +7769,35 @@ def process_chunk(self, chunk: GlibcChunk) -> None: def print(self) -> None: gef_print("== Chunk distribution by size ==") - gef_print(f"{'ChunkBytes':<10s}\t{'Count':<10s}\t{'TotalBytes':15s}\t{'Description':s}") - for chunk_info, chunk_summary in sorted(self.size_distribution.items(), key=lambda x: x[1].total_bytes, reverse=True): - gef_print(f"{chunk_info[0]:<10d}\t{chunk_summary.count:<10d}\t{chunk_summary.total_bytes:<15d}\t{chunk_summary.desc:s}") + gef_print( + f"{'ChunkBytes':<10s}\t{'Count':<10s}\t{'TotalBytes':15s}\t{'Description':s}" + ) + for chunk_info, chunk_summary in sorted( + self.size_distribution.items(), key=lambda x: x[1].total_bytes, reverse=True + ): + gef_print( + f"{chunk_info[0]:<10d}\t{chunk_summary.count:<10d}\t{chunk_summary.total_bytes:<15d}\t{chunk_summary.desc:s}" + ) gef_print("\n== Chunk distribution by flag ==") gef_print(f"{'Flag':<15s}\t{'TotalCount':<10s}\t{'TotalBytes':s}") for chunk_flag, chunk_summary in self.flag_distribution.items(): - gef_print(f"{chunk_flag:<15s}\t{chunk_summary.count:<10d}\t{chunk_summary.total_bytes: None: + def __init__( + self, + print_arena: bool = False, + allow_unaligned: bool = False, + min_size: int = 0, + max_size: int = 0, + count: int = -1, + resolve_type: bool = False, + summary: bool = False, + ) -> None: self.print_arena = print_arena self.allow_unaligned = allow_unaligned self.min_size = min_size @@ -6669,26 +7806,48 @@ def __init__(self, print_arena: bool = False, allow_unaligned: bool = False, min self.summary = summary self.resolve_type = resolve_type + @register class GlibcHeapChunksCommand(GenericCommand): """Display all heap chunks for the current arena. As an optional argument the base address of a different arena can be passed""" _cmdline_ = "heap chunks" - _syntax_ = f"{_cmdline_} [-h] [--all] [--allow-unaligned] [--summary] [--min-size MIN_SIZE] [--max-size MAX_SIZE] [--count COUNT] [--resolve] [arena_address]" - _example_ = (f"\n{_cmdline_}" - f"\n{_cmdline_} 0x555555775000") + _syntax_ = f"{_cmdline_} [-h] [--all] [--allow-unaligned] [--summary] [--min-size MIN_SIZE] [--max-size MAX_SIZE] [--count COUNT] [--resolve] [arena_address]" + _example_ = f"\n{_cmdline_}\n{_cmdline_} 0x555555775000" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) - self["peek_nb_byte"] = (16, "Hexdump N first byte(s) inside the chunk data (0 to disable)") + self["peek_nb_byte"] = ( + 16, + "Hexdump N first byte(s) inside the chunk data (0 to disable)", + ) return - @parse_arguments({"arena_address": ""}, {("--all", "-a"): False, "--allow-unaligned": False, "--min-size": 0, "--max-size": 0, ("--count", "-n"): -1, ("--summary", "-s"): False, "--resolve": False}) + @parse_arguments( + {"arena_address": ""}, + { + ("--all", "-a"): False, + "--allow-unaligned": False, + "--min-size": 0, + "--max-size": 0, + ("--count", "-n"): -1, + ("--summary", "-s"): False, + "--resolve": False, + }, + ) @only_if_gdb_running def do_invoke(self, _: list[str], **kwargs: Any) -> None: args = kwargs["arguments"] - ctx = GlibcHeapWalkContext(print_arena=args.all, allow_unaligned=args.allow_unaligned, min_size=args.min_size, max_size=args.max_size, count=args.count, resolve_type=args.resolve, summary=args.summary) + ctx = GlibcHeapWalkContext( + print_arena=args.all, + allow_unaligned=args.allow_unaligned, + min_size=args.min_size, + max_size=args.max_size, + count=args.count, + resolve_type=args.resolve, + summary=args.summary, + ) if args.all or not args.arena_address: for arena in gef.heap.arenas: self.dump_chunks_arena(arena, ctx) @@ -6717,13 +7876,19 @@ def dump_chunks_arena(self, arena: GlibcArena, ctx: GlibcHeapWalkContext) -> Non else: heap_info_structs = arena.get_heap_info_list() or [] for heap_info in heap_info_structs: - if not self.dump_chunks_heap(heap_info.heap_start, heap_info.heap_end, arena, ctx): + if not self.dump_chunks_heap( + heap_info.heap_start, heap_info.heap_end, arena, ctx + ): break return - def dump_chunks_heap(self, start: int, end: int, arena: GlibcArena, ctx: GlibcHeapWalkContext) -> bool: + def dump_chunks_heap( + self, start: int, end: int, arena: GlibcArena, ctx: GlibcHeapWalkContext + ) -> bool: nb = self["peek_nb_byte"] - chunk_iterator = GlibcChunk(start, from_base=True, allow_unaligned=ctx.allow_unaligned) + chunk_iterator = GlibcChunk( + start, from_base=True, allow_unaligned=ctx.allow_unaligned + ) heap_summary = GlibcHeapArenaSummary(resolve_type=ctx.resolve_type) top_printed = False for chunk in chunk_iterator: @@ -6732,8 +7897,7 @@ def dump_chunks_heap(self, start: int, end: int, arena: GlibcArena, ctx: GlibcHe if not ctx.summary and chunk.base_address == arena.top: if should_process: - gef_print( - f"{chunk!s} {LEFT_ARROW} {Color.greenify('top chunk')}") + gef_print(f"{chunk!s} {LEFT_ARROW} {Color.greenify('top chunk')}") top_printed = True break @@ -6758,7 +7922,9 @@ def dump_chunks_heap(self, start: int, end: int, arena: GlibcArena, ctx: GlibcHe ctx.remaining_chunk_count -= 1 if not top_printed and ctx.print_arena: - top_chunk = GlibcChunk(arena.top, from_base=True, allow_unaligned=ctx.allow_unaligned) + top_chunk = GlibcChunk( + arena.top, from_base=True, allow_unaligned=ctx.allow_unaligned + ) gef_print(f"{top_chunk!s} {LEFT_ARROW} {Color.greenify('top chunk')}") if ctx.summary: @@ -6766,7 +7932,9 @@ def dump_chunks_heap(self, start: int, end: int, arena: GlibcArena, ctx: GlibcHe return True - def should_process_chunk(self, chunk: GlibcChunk, ctx: GlibcHeapWalkContext) -> bool: + def should_process_chunk( + self, chunk: GlibcChunk, ctx: GlibcHeapWalkContext + ) -> bool: if chunk.size < ctx.min_size: return False @@ -6844,7 +8012,7 @@ class GlibcHeapTcachebinsCommand(GenericCommand): See https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc.""" _cmdline_ = "heap bins tcache" - _syntax_ = f"{_cmdline_} [all] [thread_ids...]" + _syntax_ = f"{_cmdline_} [all] [thread_ids...]" TCACHE_MAX_BINS = 0x40 @@ -6915,7 +8083,9 @@ def do_invoke(self, argv: list[str]) -> None: chunk = GlibcTcacheChunk(next_chunk) except gdb.MemoryError: - msg.append(f"{LEFT_ARROW} [Corrupted chunk at {chunk.data_address:#x}]") + msg.append( + f"{LEFT_ARROW} [Corrupted chunk at {chunk.data_address:#x}]" + ) break if msg: @@ -6923,7 +8093,10 @@ def do_invoke(self, argv: list[str]) -> None: tidx = gef.heap.csize2tidx(chunk_size) size = gef.heap.tidx2size(tidx) count = len(chunks) - gef_print(f"Tcachebins[idx={tidx:d}, size={size:#x}, count={count}]", end="") + gef_print( + f"Tcachebins[idx={tidx:d}, size={size:#x}, count={count}]", + end="", + ) gef_print("".join(msg)) if tcache_empty: @@ -6955,10 +8128,13 @@ def check_thread_ids(self, tids: list[int]) -> list[int]: existing_tids = set(t.num for t in gdb.selected_inferior().threads()) return list(set(tids) & existing_tids) - def tcachebin(self, tcache_base: int, i: int) -> tuple[GlibcTcacheChunk | None, int]: + def tcachebin( + self, tcache_base: int, i: int + ) -> tuple[GlibcTcacheChunk | None, int]: """Return the head chunk in tcache[i] and the number of chunks in the bin.""" if i >= self.TCACHE_MAX_BINS: - err("Incorrect index value, index value must be between 0 and " + err( + "Incorrect index value, index value must be between 0 and " f"{self.TCACHE_MAX_BINS}-1, given {i}" ) return None, 0 @@ -6975,17 +8151,21 @@ def tcachebin(self, tcache_base: int, i: int) -> tuple[GlibcTcacheChunk | None, # For old tcache: # TCACHE_MAX_BINS * _1_ + TCACHE_MAX_BINS * ptrsize new_tcache_min_size = ( - self.TCACHE_MAX_BINS * 2 + - self.TCACHE_MAX_BINS * gef.arch.ptrsize) + self.TCACHE_MAX_BINS * 2 + self.TCACHE_MAX_BINS * gef.arch.ptrsize + ) if tcache_chunk.usable_size < new_tcache_min_size: tcache_count_size = 1 - count = ord(gef.memory.read(tcache_base + tcache_count_size*i, 1)) + count = ord(gef.memory.read(tcache_base + tcache_count_size * i, 1)) else: tcache_count_size = 2 - count = u16(gef.memory.read(tcache_base + tcache_count_size*i, 2)) + count = u16(gef.memory.read(tcache_base + tcache_count_size * i, 2)) - chunk = dereference(tcache_base + tcache_count_size*self.TCACHE_MAX_BINS + i*gef.arch.ptrsize) + chunk = dereference( + tcache_base + + tcache_count_size * self.TCACHE_MAX_BINS + + i * gef.arch.ptrsize + ) chunk = GlibcTcacheChunk(int(chunk)) if chunk else None return chunk, count @@ -6996,7 +8176,7 @@ class GlibcHeapFastbinsYCommand(GenericCommand): See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123.""" _cmdline_ = "heap bins fast" - _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" + _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) @@ -7008,7 +8188,7 @@ def do_invoke(self, *_: Any, **kwargs: Any) -> None: def fastbin_index(sz: int) -> int: return (sz >> 4) - 2 if SIZE_SZ == 8 else (sz >> 3) - 2 - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] if not gef.heap.main_arena: err("Heap not initialized") return @@ -7017,14 +8197,18 @@ def fastbin_index(sz: int) -> int: MAX_FAST_SIZE = 80 * SIZE_SZ // 4 NFASTBINS = fastbin_index(MAX_FAST_SIZE) - 1 - arena = GlibcArena(f"*{args.arena_address}") if args.arena_address else gef.heap.selected_arena + arena = ( + GlibcArena(f"*{args.arena_address}") + if args.arena_address + else gef.heap.selected_arena + ) if arena is None: err("Invalid Glibc arena") return gef_print(titlify(f"Fastbins for arena at {arena.addr:#x}")) for i in range(NFASTBINS): - gef_print(f"Fastbins[idx={i:d}, size={(i+2)*SIZE_SZ*2:#x}] ", end="") + gef_print(f"Fastbins[idx={i:d}, size={(i + 2) * SIZE_SZ * 2:#x}] ", end="") chunk = arena.fastbin(i) chunks = set() @@ -7050,7 +8234,10 @@ def fastbin_index(sz: int) -> int: chunk = GlibcFastChunk(next_chunk, from_base=True) except gdb.MemoryError: - gef_print(f"{LEFT_ARROW} [Corrupted chunk at {chunk.data_address:#x}]", end="") + gef_print( + f"{LEFT_ARROW} [Corrupted chunk at {chunk.data_address:#x}]", + end="", + ) break gef_print() return @@ -7062,7 +8249,7 @@ class GlibcHeapUnsortedBinsCommand(GenericCommand): See: https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1689.""" _cmdline_ = "heap bins unsorted" - _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" + _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) @@ -7071,12 +8258,16 @@ def __init__(self) -> None: @parse_arguments({"arena_address": ""}, {}) @only_if_gdb_running def do_invoke(self, *_: Any, **kwargs: Any) -> None: - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] if not gef.heap.main_arena or not gef.heap.selected_arena: err("Heap not initialized") return - arena_addr = args.arena_address if args.arena_address else f"{gef.heap.selected_arena.addr:#x}" + arena_addr = ( + args.arena_address + if args.arena_address + else f"{gef.heap.selected_arena.addr:#x}" + ) gef_print(titlify(f"Unsorted Bin for arena at {arena_addr}")) heap_bins_cmd = gef.gdb.commands["heap bins"] assert isinstance(heap_bins_cmd, GlibcHeapBinsCommand) @@ -7091,7 +8282,7 @@ class GlibcHeapSmallBinsCommand(GenericCommand): """Convenience command for viewing small bins.""" _cmdline_ = "heap bins small" - _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" + _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) @@ -7100,7 +8291,7 @@ def __init__(self) -> None: @parse_arguments({"arena_address": ""}, {}) @only_if_gdb_running def do_invoke(self, *_: Any, **kwargs: Any) -> None: - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] if not gef.heap.main_arena or not gef.heap.selected_arena: err("Heap not initialized") return @@ -7109,14 +8300,16 @@ def do_invoke(self, *_: Any, **kwargs: Any) -> None: gef_print(titlify(f"Small Bins for arena at {arena_address}")) bins: dict[int, int] = {} heap_bins_cmd = gef.gdb.commands["heap bins"] - assert isinstance (heap_bins_cmd, GlibcHeapBinsCommand) + assert isinstance(heap_bins_cmd, GlibcHeapBinsCommand) for i in range(1, 63): nb_chunk = heap_bins_cmd.pprint_bin(f"*{arena_address}", i, "small_") if nb_chunk < 0: break if nb_chunk > 0: bins[i] = nb_chunk - info(f"Found {sum(list(bins.values())):d} chunks in {len(bins):d} small non-empty bins.") + info( + f"Found {sum(list(bins.values())):d} chunks in {len(bins):d} small non-empty bins." + ) return @@ -7125,7 +8318,7 @@ class GlibcHeapLargeBinsCommand(GenericCommand): """Convenience command for viewing large bins.""" _cmdline_ = "heap bins large" - _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" + _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) @@ -7134,12 +8327,16 @@ def __init__(self) -> None: @parse_arguments({"arena_address": ""}, {}) @only_if_gdb_running def do_invoke(self, *_: Any, **kwargs: Any) -> None: - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] if not gef.heap.main_arena or not gef.heap.selected_arena: err("Heap not initialized") return - arena_addr = args.arena_address if args.arena_address else f"{gef.heap.selected_arena.addr:#x}" + arena_addr = ( + args.arena_address + if args.arena_address + else f"{gef.heap.selected_arena.addr:#x}" + ) gef_print(titlify(f"Large Bins for arena at {arena_addr}")) bins = {} heap_bins_cmd = gef.gdb.commands["heap bins"] @@ -7150,7 +8347,9 @@ def do_invoke(self, *_: Any, **kwargs: Any) -> None: break if nb_chunk > 0: bins[i] = nb_chunk - info(f"Found {sum(bins.values()):d} chunks in {len(bins):d} large non-empty bins.") + info( + f"Found {sum(bins.values()):d} chunks in {len(bins):d} large non-empty bins." + ) return @@ -7159,9 +8358,8 @@ class DetailRegistersCommand(GenericCommand): """Display full details on one, many or all registers value from current architecture.""" _cmdline_ = "registers" - _syntax_ = f"{_cmdline_} [[Register1][Register2] ... [RegisterN]]" - _example_ = (f"\n{_cmdline_}" - f"\n{_cmdline_} $eax $eip $esp") + _syntax_ = f"{_cmdline_} [[Register1][Register2] ... [RegisterN]]" + _example_ = f"\n{_cmdline_}\n{_cmdline_} $eax $eip $esp" @only_if_gdb_running @parse_arguments({"registers": [""]}, {}) @@ -7171,7 +8369,7 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: string_color = gef.config["theme.dereference_string"] regs = gef.arch.all_registers - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] if args.registers and args.registers[0]: all_regs = set(gef.arch.all_registers) regs = [reg for reg in args.registers if reg in all_regs] @@ -7193,8 +8391,10 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: padreg = regname.ljust(widest, " ") if str(reg) == "": - gef_print(f"{Color.colorify(padreg, unchanged_color)}: " - f"{Color.colorify('no value', 'yellow underline')}") + gef_print( + f"{Color.colorify(padreg, unchanged_color)}: " + f"{Color.colorify('no value', 'yellow underline')}" + ) continue value = align_address(int(reg)) @@ -7237,7 +8437,7 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: last_addr = int(addrs[-1], 16) val = gef_pystring(struct.pack(fmt, last_addr)) if all([_ in charset for _ in val]): - line += f" (\"{Color.colorify(val, string_color)}\"?)" + line += f' ("{Color.colorify(val, string_color)}"?)' except ValueError: pass @@ -7254,7 +8454,7 @@ class ShellcodeCommand(GenericCommand): download shellcodes.""" _cmdline_ = "shellcode" - _syntax_ = f"{_cmdline_} (search|get)" + _syntax_ = f"{_cmdline_} (search|get)" def __init__(self) -> None: super().__init__(prefix=True) @@ -7271,8 +8471,10 @@ class ShellcodeSearchCommand(GenericCommand): """Search pattern in shell-storm's shellcode database.""" _cmdline_ = "shellcode search" - _syntax_ = f"{_cmdline_} PATTERN1 PATTERN2" - _aliases_ = ["sc-search",] + _syntax_ = f"{_cmdline_} PATTERN1 PATTERN2" + _aliases_ = [ + "sc-search", + ] api_base = "http://shell-storm.org" search_url = f"{api_base}/api/?s=" @@ -7316,8 +8518,10 @@ class ShellcodeGetCommand(GenericCommand): """Download shellcode from shell-storm's shellcode database.""" _cmdline_ = "shellcode get" - _syntax_ = f"{_cmdline_} SHELLCODE_ID" - _aliases_ = ["sc-get",] + _syntax_ = f"{_cmdline_} SHELLCODE_ID" + _aliases_ = [ + "sc-get", + ] api_base = "http://shell-storm.org" get_url = f"{api_base}/shellcode/files/shellcode-{{:d}}.html" @@ -7344,7 +8548,13 @@ def get_shellcode(self, sid: int) -> None: return ok("Downloaded, written to disk...") - with tempfile.NamedTemporaryFile(prefix="sc-", suffix=".txt", mode='w+b', delete=False, dir=gef.config["gef.tempdir"]) as fd: + with tempfile.NamedTemporaryFile( + prefix="sc-", + suffix=".txt", + mode="w+b", + delete=False, + dir=gef.config["gef.tempdir"], + ) as fd: shellcode = res.split(b"
")[1].split(b"
")[0] shellcode = shellcode.replace(b""", b'"') fd.write(shellcode) @@ -7358,18 +8568,21 @@ class ProcessListingCommand(GenericCommand): by this pattern.""" _cmdline_ = "process-search" - _syntax_ = f"{_cmdline_} [-h] [--attach] [--smart-scan] [REGEX_PATTERN]" + _syntax_ = f"{_cmdline_} [-h] [--attach] [--smart-scan] [REGEX_PATTERN]" _aliases_ = ["ps"] _example_ = f"{_cmdline_} gdb.*" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) - self["ps_command"] = (f"{gef.session.constants['ps']} auxww", "`ps` command to get process information") + self["ps_command"] = ( + f"{gef.session.constants['ps']} auxww", + "`ps` command to get process information", + ) return @parse_arguments({"pattern": ""}, {"--attach": False, "--smart-scan": False}) def do_invoke(self, _: list, **kwargs: Any) -> None: - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] do_attach = args.attach smart_scan = args.smart_scan pattern = args.pattern @@ -7383,10 +8596,14 @@ def do_invoke(self, _: list, **kwargs: Any) -> None: continue if smart_scan: - if command.startswith("[") and command.endswith("]"): continue - if command.startswith("socat "): continue - if command.startswith("grep "): continue - if command.startswith("gdb "): continue + if command.startswith("[") and command.endswith("]"): + continue + if command.startswith("socat "): + continue + if command.startswith("grep "): + continue + if command.startswith("gdb "): + continue if args and do_attach: ok(f"Attaching to process='{process['command']}' pid={pid:d}") @@ -7423,8 +8640,8 @@ class ElfInfoCommand(GenericCommand): show information about the current ELF being debugged.""" _cmdline_ = "elf-info" - _syntax_ = f"{_cmdline_} [FILE]" - _example_ = f"{_cmdline_} /bin/ls" + _syntax_ = f"{_cmdline_} [FILE]" + _example_ = f"{_cmdline_} /bin/ls" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) @@ -7432,7 +8649,7 @@ def __init__(self) -> None: @parse_arguments({}, {"--filename": ""}) def do_invoke(self, _: list[str], **kwargs: Any) -> None: - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] if is_running_in_qemu_system(): err("Unsupported") @@ -7451,9 +8668,15 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: data = [ ("Magic", f"{hexdump(struct.pack('>I', elf.e_magic), show_raw=True)}"), ("Class", f"{elf.e_class.value:#x} - {elf.e_class.name}"), - ("Endianness", f"{elf.e_endianness.value:#x} - {Endianness(elf.e_endianness).name}"), + ( + "Endianness", + f"{elf.e_endianness.value:#x} - {Endianness(elf.e_endianness).name}", + ), ("Version", f"{elf.e_eiversion:#x}"), - ("OS ABI", f"{elf.e_osabi.value:#x} - {elf.e_osabi.name if elf.e_osabi else ''}"), + ( + "OS ABI", + f"{elf.e_osabi.value:#x} - {elf.e_osabi.name if elf.e_osabi else ''}", + ), ("ABI Version", f"{elf.e_abiversion:#x}"), ("Type", f"{elf.e_type.value:#x} - {elf.e_type.name}"), ("Machine", f"{elf.e_machine.value:#x} - {elf.e_machine.name}"), @@ -7471,27 +8694,35 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: gef_print("") gef_print(titlify("Program Header")) - gef_print(f" [{'#':>2s}] {'Type':12s} {'Offset':>8s} {'Virtaddr':>10s} {'Physaddr':>10s}" - f" {'FileSiz':>8s} {'MemSiz':>8s} {'Flags':5s} {'Align':>8s}") + gef_print( + f" [{'#':>2s}] {'Type':12s} {'Offset':>8s} {'Virtaddr':>10s} {'Physaddr':>10s}" + f" {'FileSiz':>8s} {'MemSiz':>8s} {'Flags':5s} {'Align':>8s}" + ) for i, p in enumerate(elf.phdrs): p_type = p.p_type.name if p.p_type else "" p_flags = str(p.p_flags.name).lstrip("Flag.") if p.p_flags else "???" - gef_print(f" [{i:2d}] {p_type:12s} {p.p_offset:#8x} {p.p_vaddr:#10x} {p.p_paddr:#10x}" - f" {p.p_filesz:#8x} {p.p_memsz:#8x} {p_flags:5s} {p.p_align:#8x}") + gef_print( + f" [{i:2d}] {p_type:12s} {p.p_offset:#8x} {p.p_vaddr:#10x} {p.p_paddr:#10x}" + f" {p.p_filesz:#8x} {p.p_memsz:#8x} {p_flags:5s} {p.p_align:#8x}" + ) gef_print("") gef_print(titlify("Section Header")) - gef_print(f" [{'#':>2s}] {'Name':20s} {'Type':>15s} {'Address':>10s} {'Offset':>8s}" - f" {'Size':>8s} {'EntSiz':>8s} {'Flags':5s} {'Link':4s} {'Info':4s} {'Align':>8s}") + gef_print( + f" [{'#':>2s}] {'Name':20s} {'Type':>15s} {'Address':>10s} {'Offset':>8s}" + f" {'Size':>8s} {'EntSiz':>8s} {'Flags':5s} {'Link':4s} {'Info':4s} {'Align':>8s}" + ) for i, s in enumerate(elf.shdrs): sh_type = s.sh_type.name if s.sh_type else "UNKN" sh_flags = str(s.sh_flags).lstrip("Flags.") if s.sh_flags else "UNKN" - gef_print(f" [{i:2d}] {s.name:20s} {sh_type:>15s} {s.sh_addr:#10x} {s.sh_offset:#8x} " - f"{s.sh_size:#8x} {s.sh_entsize:#8x} {sh_flags:5s} {s.sh_link:#4x} {s.sh_info:#4x} {s.sh_addralign:#8x}") + gef_print( + f" [{i:2d}] {s.name:20s} {sh_type:>15s} {s.sh_addr:#10x} {s.sh_offset:#8x} " + f"{s.sh_size:#8x} {s.sh_entsize:#8x} {sh_flags:5s} {s.sh_link:#4x} {s.sh_info:#4x} {s.sh_addralign:#8x}" + ) return @@ -7502,12 +8733,17 @@ class EntryPointBreakCommand(GenericCommand): the setting `entrypoint_symbols`.""" _cmdline_ = "entry-break" - _syntax_ = _cmdline_ - _aliases_ = ["start",] + _syntax_ = _cmdline_ + _aliases_ = [ + "start", + ] def __init__(self) -> None: super().__init__() - self["entrypoint_symbols"] = ("main _main __libc_start_main __uClibc_main start _start", "Possible symbols for entry points") + self["entrypoint_symbols"] = ( + "main _main __libc_start_main __uClibc_main start _start", + "Possible symbols for entry points", + ) return def do_invoke(self, argv: list[str]) -> None: @@ -7581,9 +8817,11 @@ class NamedBreakpointCommand(GenericCommand): """Sets a breakpoint and assigns a name to it, which will be shown, when it's hit.""" _cmdline_ = "name-break" - _syntax_ = f"{_cmdline_} name [address]" - _aliases_ = ["nb",] - _example = f"{_cmdline_} main *0x4008a9" + _syntax_ = f"{_cmdline_} name [address]" + _aliases_ = [ + "nb", + ] + _example = f"{_cmdline_} main *0x4008a9" def __init__(self) -> None: super().__init__() @@ -7591,7 +8829,7 @@ def __init__(self) -> None: @parse_arguments({"name": "", "address": "*$pc"}, {}) def do_invoke(self, _: list[str], **kwargs: Any) -> None: - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] if not args.name: err("Missing name for breakpoint") self.usage() @@ -7609,38 +8847,83 @@ class ContextCommand(GenericCommand): states, the stack, and the disassembly code around $pc.""" _cmdline_ = "context" - _syntax_ = f"{_cmdline_} [legend|regs|stack|code|args|memory|source|trace|threads|extra]" - _aliases_ = ["ctx",] + _syntax_ = ( + f"{_cmdline_} [legend|regs|stack|code|args|memory|source|trace|threads|extra]" + ) + _aliases_ = [ + "ctx", + ] old_registers: dict[str, int | None] = {} def __init__(self) -> None: super().__init__() self["enable"] = (True, "Enable/disable printing the context when breaking") - self["show_source_code_variable_values"] = (True, "Show extra PC context info in the source code") - self["show_full_source_file_name_max_len"] = (30, "Show full source path name, if less than this value") - self["show_basename_source_file_name_max_len"] = (20, "Show the source basename in full, if less than this value") - self["show_prefix_source_path_name_len"] = (10, "When truncating source path, show this many path prefix characters") - self["show_stack_raw"] = (False, "Show the stack pane as raw hexdump (no dereference)") - self["show_registers_raw"] = (False, "Show the registers pane with raw values (no dereference)") - self["show_opcodes_size"] = (0, "Number of bytes of opcodes to display next to the disassembly") + self["show_source_code_variable_values"] = ( + True, + "Show extra PC context info in the source code", + ) + self["show_full_source_file_name_max_len"] = ( + 30, + "Show full source path name, if less than this value", + ) + self["show_basename_source_file_name_max_len"] = ( + 20, + "Show the source basename in full, if less than this value", + ) + self["show_prefix_source_path_name_len"] = ( + 10, + "When truncating source path, show this many path prefix characters", + ) + self["show_stack_raw"] = ( + False, + "Show the stack pane as raw hexdump (no dereference)", + ) + self["show_registers_raw"] = ( + False, + "Show the registers pane with raw values (no dereference)", + ) + self["show_opcodes_size"] = ( + 0, + "Number of bytes of opcodes to display next to the disassembly", + ) self["peek_calls"] = (True, "Peek into calls") self["peek_ret"] = (True, "Peek at return address") self["nb_lines_stack"] = (8, "Number of line in the stack pane") - self["grow_stack_down"] = (False, "Order of stack downward starts at largest down to stack pointer") + self["grow_stack_down"] = ( + False, + "Order of stack downward starts at largest down to stack pointer", + ) self["nb_lines_backtrace"] = (10, "Number of line in the backtrace pane") - self["nb_lines_backtrace_before"] = (2, "Number of line in the backtrace pane before selected frame") + self["nb_lines_backtrace_before"] = ( + 2, + "Number of line in the backtrace pane before selected frame", + ) self["nb_lines_threads"] = (-1, "Number of line in the threads pane") self["nb_lines_code"] = (6, "Number of instruction after $pc") self["nb_lines_code_prev"] = (3, "Number of instruction before $pc") - self["ignore_registers"] = ("", "Space-separated list of registers not to display (e.g. '$cs $ds $gs')") + self["ignore_registers"] = ( + "", + "Space-separated list of registers not to display (e.g. '$cs $ds $gs')", + ) self["clear_screen"] = (True, "Clear the screen before printing the context") - self["layout"] = ("legend regs stack code args source memory threads trace extra", "Change the order/presence of the context sections") + self["layout"] = ( + "legend regs stack code args source memory threads trace extra", + "Change the order/presence of the context sections", + ) self["redirect"] = ("", "Redirect the context information to another TTY") - self["libc_args"] = (False, "[DEPRECATED - Unused] Show libc function call args description") - self["libc_args_path"] = ("", "[DEPRECATED - Unused] Path to libc function call args json files, provided via gef-extras") + self["libc_args"] = ( + False, + "[DEPRECATED - Unused] Show libc function call args description", + ) + self["libc_args_path"] = ( + "", + "[DEPRECATED - Unused] Path to libc function call args json files, provided via gef-extras", + ) - self.layout_mapping: dict[str, tuple[Callable, Callable | None, Callable | None]] = { + self.layout_mapping: dict[ + str, tuple[Callable, Callable | None, Callable | None] + ] = { "legend": (self.show_legend, None, None), "regs": (self.context_regs, None, None), "stack": (self.context_stack, None, None), @@ -7664,12 +8947,16 @@ def post_load(self) -> None: def show_legend(self) -> None: if gef.config["gef.disable_color"] is True: return - changed_register_title = Color.colorify("Modified register", gef.config["theme.registers_value_changed"]) + changed_register_title = Color.colorify( + "Modified register", gef.config["theme.registers_value_changed"] + ) code_title = Color.colorify("Code", gef.config["theme.address_code"]) heap_title = Color.colorify("Heap", gef.config["theme.address_heap"]) stack_title = Color.colorify("Stack", gef.config["theme.address_stack"]) str_title = Color.colorify("String", gef.config["theme.dereference_string"]) - gef_print(f"[ Legend: {changed_register_title} | {code_title} | {heap_title} | {stack_title} | {str_title} ]") + gef_print( + f"[ Legend: {changed_register_title} | {code_title} | {heap_title} | {stack_title} | {str_title} ]" + ) return @only_if_gdb_running @@ -7703,7 +8990,9 @@ def do_invoke(self, argv: list[str]) -> None: continue try: - display_pane_function, pane_title_function, condition = self.layout_mapping[section] + display_pane_function, pane_title_function, condition = ( + self.layout_mapping[section] + ) if condition: if not condition(): continue @@ -7752,7 +9041,9 @@ def context_regs(self) -> None: # Defer to DetailRegisters by default if self["show_registers_raw"] is False: - regs = [reg for reg in gef.arch.all_registers if reg not in ignored_registers] + regs = [ + reg for reg in gef.arch.all_registers if reg not in ignored_registers + ] printable_registers = " ".join(regs) gdb.execute(f"registers {printable_registers}") return @@ -7849,10 +9140,19 @@ def context_code(self) -> None: breakpoints = gdb.breakpoints() or [] # breakpoint.locations was introduced in gdb 13.1 if len(breakpoints) and hasattr(breakpoints[-1], "locations"): - bp_locations = [hex(location.address) for b in breakpoints for location in b.locations if location is not None] # type: ignore + bp_locations = [ + hex(location.address) + for b in breakpoints + for location in b.locations + if location is not None + ] # type: ignore else: # location relies on the user setting the breakpoints with "b *{hex(address)}" - bp_locations = [b.location for b in breakpoints if b.location and b.location.startswith("*")] + bp_locations = [ + b.location + for b in breakpoints + if b.location and b.location.startswith("*") + ] frame = gdb.selected_frame() arch_name = f"{gef.arch.arch.lower()}:{gef.arch.mode}" @@ -7860,13 +9160,15 @@ def context_code(self) -> None: self.context_title(f"code:{arch_name}") try: - - for insn in self.instruction_iterator(pc, nb_insn, nb_prev=nb_insn_prev): line = [] - is_taken = False - target = None - bp_prefix = Color.redify(BP_GLYPH) if self.addr_has_breakpoint(insn.address, bp_locations) else " " + is_taken = False + target = None + bp_prefix = ( + Color.redify(BP_GLYPH) + if self.addr_has_breakpoint(insn.address, bp_locations) + else " " + ) if show_opcodes_size == 0: text = str(insn) @@ -7905,8 +9207,10 @@ def context_code(self) -> None: except ValueError: # If the operand isn't an address right now we can't parse it continue - for i, tinsn in enumerate(self.instruction_iterator(address, nb_insn)): - text= f" {DOWN_ARROW if i == 0 else ' '} {tinsn!s}" + for i, tinsn in enumerate( + self.instruction_iterator(address, nb_insn) + ): + text = f" {DOWN_ARROW if i == 0 else ' '} {tinsn!s}" gef_print(text) break @@ -7926,9 +9230,9 @@ def context_args(self) -> None: 8: "QWORD", } - if insn.operands[-1].startswith(self.size2type[gef.arch.ptrsize]+" PTR"): + if insn.operands[-1].startswith(self.size2type[gef.arch.ptrsize] + " PTR"): target = "*" + insn.operands[-1].split()[-1] - elif "$"+insn.operands[0] in gef.arch.all_registers: + elif "$" + insn.operands[0] in gef.arch.all_registers: target = f"*{gef.arch.register('$' + insn.operands[0]):#x}" else: # is there a symbol? @@ -7952,7 +9256,9 @@ def context_args(self) -> None: self.print_arguments_from_symbol(target, sym) return - def print_arguments_from_symbol(self, function_name: str, symbol: "gdb.Symbol") -> None: + def print_arguments_from_symbol( + self, function_name: str, symbol: "gdb.Symbol" + ) -> None: """If symbols were found, parse them and print the argument adequately.""" args = [] fields = symbol.type.fields() if symbol.type else [] @@ -7971,7 +9277,7 @@ def print_arguments_from_symbol(self, function_name: str, symbol: "gdb.Symbol") gef_print(f"{function_name} ()") return - gef_print(f"{function_name} (\n "+",\n ".join(args)+"\n)") + gef_print(f"{function_name} (\n " + ",\n ".join(args) + "\n)") return def print_guessed_arguments(self, function_name: str) -> None: @@ -7982,9 +9288,11 @@ def __get_current_block_start_address() -> int | None: max_distance = 10 * 16 try: block = gdb.block_for_pc(pc) - block_start = block.start \ - if block is not None and (pc - block.start) <= max_distance \ + block_start = ( + block.start + if block is not None and (pc - block.start) <= max_distance else gdb_get_nth_previous_instruction_address(pc, 5) + ) except RuntimeError: block_start = gdb_get_nth_previous_instruction_address(pc, 5) return block_start @@ -8012,11 +9320,12 @@ def __get_current_block_start_address() -> int | None: if is_x86_64(): # also consider extended registers - extended_registers = {"$rdi": ["$edi", "$di"], - "$rsi": ["$esi", "$si"], - "$rdx": ["$edx", "$dx"], - "$rcx": ["$ecx", "$cx"], - } + extended_registers = { + "$rdi": ["$edi", "$di"], + "$rsi": ["$esi", "$si"], + "$rdx": ["$edx", "$dx"], + "$rcx": ["$ecx", "$cx"], + } for exreg in extended_registers: if op in extended_registers[exreg]: parameter_set.add(exreg) @@ -8024,7 +9333,9 @@ def __get_current_block_start_address() -> int | None: if is_x86_32(): nb_argument = len(parameter_set) else: - nb_argument = max([function_parameters.index(p)+1 for p in parameter_set], default=0) + nb_argument = max( + [function_parameters.index(p) + 1 for p in parameter_set], default=0 + ) args = [] for i in range(nb_argument): @@ -8039,7 +9350,9 @@ def __get_current_block_start_address() -> int | None: gef_print(")") return - def line_has_breakpoint(self, file_name: str, line_number: int, bp_locations: list[str]) -> bool: + def line_has_breakpoint( + self, file_name: str, line_number: int, bp_locations: list[str] + ) -> bool: filename_line = f"{file_name}:{line_number}" return any(filename_line in loc for loc in bp_locations) @@ -8061,7 +9374,11 @@ def context_source(self) -> None: file_base_name = os.path.basename(symtab.filename) breakpoints = gdb.breakpoints() or [] - bp_locations = [b.location for b in breakpoints if b.location and file_base_name in b.location] + bp_locations = [ + b.location + for b in breakpoints + if b.location and file_base_name in b.location + ] past_lines_color = gef.config["theme.old_context"] show_full_path_max = self["show_full_source_file_name_max_len"] @@ -8083,10 +9400,19 @@ def context_source(self) -> None: if i < 0: continue - bp_prefix = Color.redify(BP_GLYPH) if self.line_has_breakpoint(file_base_name, i + 1, bp_locations) else " " + bp_prefix = ( + Color.redify(BP_GLYPH) + if self.line_has_breakpoint(file_base_name, i + 1, bp_locations) + else " " + ) if i < line_num: - gef_print("{}{}".format(bp_prefix, Color.colorify(f" {i + 1:4d}\t {lines[i]}", past_lines_color))) + gef_print( + "{}{}".format( + bp_prefix, + Color.colorify(f" {i + 1:4d}\t {lines[i]}", past_lines_color), + ) + ) if i == line_num: prefix = f"{bp_prefix}{RIGHT_ARROW[1:]}{i + 1:4d}\t " @@ -8107,12 +9433,13 @@ def context_source(self) -> None: def get_pc_context_info(self, pc: int, line: str) -> str: try: current_block = gdb.block_for_pc(pc) - if not current_block or not current_block.is_valid(): return "" + if not current_block or not current_block.is_valid(): + return "" m = collections.OrderedDict() while current_block and not current_block.is_static: for sym in list(current_block): symbol = sym.name - if not sym.is_function and re.search(fr"\W{symbol}\W", line): + if not sym.is_function and re.search(rf"\W{symbol}\W", line): val = gdb.parse_and_eval(symbol) if val.type.code in (gdb.TYPE_CODE_PTR, gdb.TYPE_CODE_ARRAY): addr = int(val.address) @@ -8132,7 +9459,9 @@ def get_pc_context_info(self, pc: int, line: str) -> str: current_block = current_block.superblock if m: - return "// " + ", ".join([f"{Color.yellowify(a)}={b}" for a, b in m.items()]) + return "// " + ", ".join( + [f"{Color.yellowify(a)}={b}" for a, b in m.items()] + ) except Exception: pass return "" @@ -8151,10 +9480,13 @@ def context_trace(self) -> None: orig_frame: gdb.Frame = gdb.selected_frame() current_frame: gdb.Frame = gdb.newest_frame() - frames = [current_frame,] + frames = [ + current_frame, + ] while current_frame != orig_frame and current_frame: current_frame = current_frame.older() - if not current_frame: break + if not current_frame: + break frames.append(current_frame) nb_backtrace_before = self["nb_lines_backtrace_before"] @@ -8171,8 +9503,15 @@ def context_trace(self) -> None: items = [] items.append(f"{pc:#x}") if name: - frame_args = gdb.FrameDecorator.FrameDecorator(current_frame).frame_args() or [] # type: ignore - symstr= ", ".join([f"{Color.yellowify(x.sym)}={x.sym.value(current_frame)!s}" for x in frame_args]) + frame_args = ( + gdb.FrameDecorator.FrameDecorator(current_frame).frame_args() or [] + ) # type: ignore + symstr = ", ".join( + [ + f"{Color.yellowify(x.sym)}={x.sym.value(current_frame)!s}" + for x in frame_args + ] + ) m = f"{Color.greenify(name)}({symstr})" items.append(m) else: @@ -8188,9 +9527,14 @@ def context_trace(self) -> None: sym_name, offset = sym_found symbol = f" <{sym_name}+{offset:x}> " - items.append(Color.redify(f"{symbol}{insn.mnemonic} {', '.join(insn.operands)}")) + items.append( + Color.redify(f"{symbol}{insn.mnemonic} {', '.join(insn.operands)}") + ) - title = Color.colorify(f"#{level}", "bold green" if current_frame == orig_frame else "bold pink") + title = Color.colorify( + f"#{level}", + "bold green" if current_frame == orig_frame else "bold pink", + ) gef_print(f"[{title}] {RIGHT_ARROW.join(items)}") older = current_frame.older() level += 1 @@ -8261,9 +9605,11 @@ def reason() -> str: sym_name, offset = sym_found frame_name = f"<{sym_name}+{offset:x}>" - line += (f" {Color.colorify(f'{frame.pc():#x}', 'blue')} in " - f"{Color.colorify(frame_name or '??', 'bold yellow')} (), " - f"reason: {Color.colorify(reason(), 'bold pink')}") + line += ( + f" {Color.colorify(f'{frame.pc():#x}', 'blue')} in " + f"{Color.colorify(frame_name or '??', 'bold yellow')} (), " + f"reason: {Color.colorify(reason(), 'bold pink')}" + ) elif thread.is_exited(): line += Color.colorify("exited", "bold yellow") gef_print(line) @@ -8279,10 +9625,14 @@ def context_additional_information(self) -> None: self.context_title("extra") for level, text in gef.ui.context_messages: - if level == "error": err(text) - elif level == "warn": warn(text) - elif level == "success": ok(text) - else: info(text) + if level == "error": + err(text) + elif level == "warn": + warn(text) + elif level == "success": + ok(text) + else: + info(text) return def context_memory(self) -> None: @@ -8311,8 +9661,9 @@ def empty_extra_messages(self, _) -> None: @register class MemoryCommand(GenericCommand): """Add or remove address ranges to the memory view.""" + _cmdline_ = "memory" - _syntax_ = f"{_cmdline_} (watch|unwatch|reset|list)" + _syntax_ = f"{_cmdline_} (watch|unwatch|reset|list)" def __init__(self) -> None: super().__init__(prefix=True) @@ -8327,10 +9678,10 @@ def do_invoke(self, argv: list[str]) -> None: @register class MemoryWatchCommand(GenericCommand): """Adds address ranges to the memory view.""" + _cmdline_ = "memory watch" - _syntax_ = f"{_cmdline_} ADDRESS [SIZE] [(qword|dword|word|byte|pointers)]" - _example_ = (f"\n{_cmdline_} 0x603000 0x100 byte" - f"\n{_cmdline_} $sp") + _syntax_ = f"{_cmdline_} ADDRESS [SIZE] [(qword|dword|word|byte|pointers)]" + _example_ = f"\n{_cmdline_} 0x603000 0x100 byte\n{_cmdline_} $sp" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) @@ -8343,8 +9694,8 @@ def do_invoke(self, argv: list[str]) -> None: return address = parse_address(argv[0]) - size = parse_address(argv[1]) if len(argv) > 1 else 0x10 - group = "byte" + size = parse_address(argv[1]) if len(argv) > 1 else 0x10 + group = "byte" if len(argv) == 3: group = argv[2].lower() @@ -8366,10 +9717,10 @@ def do_invoke(self, argv: list[str]) -> None: @register class MemoryUnwatchCommand(GenericCommand): """Removes address ranges to the memory view.""" + _cmdline_ = "memory unwatch" - _syntax_ = f"{_cmdline_} ADDRESS" - _example_ = (f"\n{_cmdline_} 0x603000" - f"\n{_cmdline_} $sp") + _syntax_ = f"{_cmdline_} ADDRESS" + _example_ = f"\n{_cmdline_} 0x603000\n{_cmdline_} $sp" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) @@ -8393,8 +9744,9 @@ def do_invoke(self, argv: list[str]) -> None: @register class MemoryWatchResetCommand(GenericCommand): """Removes all watchpoints.""" + _cmdline_ = "memory reset" - _syntax_ = f"{_cmdline_}" + _syntax_ = f"{_cmdline_}" @only_if_gdb_running def do_invoke(self, _: list[str]) -> None: @@ -8406,8 +9758,9 @@ def do_invoke(self, _: list[str]) -> None: @register class MemoryWatchListCommand(GenericCommand): """Lists all watchpoints to display in context layout.""" + _cmdline_ = "memory list" - _syntax_ = f"{_cmdline_}" + _syntax_ = f"{_cmdline_}" @only_if_gdb_running def do_invoke(self, _: list[str]) -> None: @@ -8426,25 +9779,35 @@ class HexdumpCommand(GenericCommand): """Display SIZE lines of hexdump from the memory location pointed by LOCATION.""" _cmdline_ = "hexdump" - _syntax_ = f"{_cmdline_} (qword|dword|word|byte) [LOCATION] [--size SIZE] [--reverse]" + _syntax_ = ( + f"{_cmdline_} (qword|dword|word|byte) [LOCATION] [--size SIZE] [--reverse]" + ) _example_ = f"{_cmdline_} byte $rsp --size 16 --reverse" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION, prefix=True) - self["always_show_ascii"] = (False, "If true, hexdump will always display the ASCII dump") + self["always_show_ascii"] = ( + False, + "If true, hexdump will always display the ASCII dump", + ) self.format: str | None = None self.__last_target = "$sp" return @only_if_gdb_running - @parse_arguments({"address": "",}, {("--reverse", "-r"): False, ("--size", "-s"): 0}) + @parse_arguments( + { + "address": "", + }, + {("--reverse", "-r"): False, ("--size", "-s"): 0}, + ) def do_invoke(self, _: list[str], **kwargs: Any) -> None: valid_formats = ["byte", "word", "dword", "qword"] if not self.format or self.format not in valid_formats: err("Invalid command") return - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] target = args.address or self.__last_target start_addr = parse_address(target) read_from = align_address(start_addr) @@ -8456,7 +9819,9 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: lines = hexdump(mem, base=read_from).splitlines() else: read_len = args.size or 0x10 - lines = self._hexdump(read_from, read_len, self.format, self.repeat_count * read_len) + lines = self._hexdump( + read_from, read_len, self.format, self.repeat_count * read_len + ) if args.reverse: lines.reverse() @@ -8465,7 +9830,9 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: gef_print("\n".join(lines)) return - def _hexdump(self, start_addr: int, length: int, arrange_as: str, offset: int = 0) -> list[str]: + def _hexdump( + self, start_addr: int, length: int, arrange_as: str, offset: int = 0 + ) -> list[str]: endianness = gef.arch.endianness base_address_color = gef.config["theme.dereference_base_address"] @@ -8478,7 +9845,7 @@ def _hexdump(self, start_addr: int, length: int, arrange_as: str, offset: int = } formatter, width = formats[arrange_as] - fmt_str = f"{{base}}{VERTICAL_LINE}+{{offset:#06x}} {{sym}}{{val:#0{width*2+2}x}} {{text}}" + fmt_str = f"{{base}}{VERTICAL_LINE}+{{offset:#06x}} {{sym}}{{val:#0{width * 2 + 2}x}} {{text}}" fmt_pack = f"{endianness!s}{formatter}" lines = [] @@ -8492,8 +9859,15 @@ def _hexdump(self, start_addr: int, length: int, arrange_as: str, offset: int = val = struct.unpack(fmt_pack, mem)[0] if show_ascii: text = "".join([chr(b) if 0x20 <= b < 0x7F else "." for b in mem]) - lines.append(fmt_str.format(base=Color.colorify(format_address(cur_addr), base_address_color), - offset=(i + offset) * width, sym=sym, val=val, text=text)) + lines.append( + fmt_str.format( + base=Color.colorify(format_address(cur_addr), base_address_color), + offset=(i + offset) * width, + sym=sym, + val=val, + text=text, + ) + ) i += 1 return lines @@ -8504,7 +9878,7 @@ class HexdumpQwordCommand(HexdumpCommand): """Display SIZE lines of hexdump as QWORD from the memory location pointed by ADDRESS.""" _cmdline_ = "hexdump qword" - _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]" + _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]" _example_ = f"{_cmdline_} qword $rsp -s 16 --reverse" def __init__(self) -> None: @@ -8518,7 +9892,7 @@ class HexdumpDwordCommand(HexdumpCommand): """Display SIZE lines of hexdump as DWORD from the memory location pointed by ADDRESS.""" _cmdline_ = "hexdump dword" - _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]" + _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]" _example_ = f"{_cmdline_} $esp -s 16 --reverse" def __init__(self) -> None: @@ -8532,7 +9906,7 @@ class HexdumpWordCommand(HexdumpCommand): """Display SIZE lines of hexdump as WORD from the memory location pointed by ADDRESS.""" _cmdline_ = "hexdump word" - _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]" + _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]" _example_ = f"{_cmdline_} $esp -s 16 --reverse" def __init__(self) -> None: @@ -8546,7 +9920,7 @@ class HexdumpByteCommand(HexdumpCommand): """Display SIZE lines of hexdump as BYTE from the memory location pointed by ADDRESS.""" _cmdline_ = "hexdump byte" - _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]" + _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]" _example_ = f"{_cmdline_} $rsp -s 16" def __init__(self) -> None: @@ -8560,8 +9934,10 @@ class PatchCommand(GenericCommand): """Write specified values to the specified address.""" _cmdline_ = "patch" - _syntax_ = (f"{_cmdline_} (qword|dword|word|byte) LOCATION VALUES\n" - f"{_cmdline_} string LOCATION \"double-escaped string\"") + _syntax_ = ( + f"{_cmdline_} (qword|dword|word|byte) LOCATION VALUES\n" + f'{_cmdline_} string LOCATION "double-escaped string"' + ) SUPPORTED_SIZES = { "qword": (8, "Q"), "dword": (4, "L"), @@ -8575,9 +9951,17 @@ def __init__(self) -> None: return @only_if_gdb_running - @parse_arguments({"location": "", "values": ["", ]}, {}) + @parse_arguments( + { + "location": "", + "values": [ + "", + ], + }, + {}, + ) def do_invoke(self, _: list[str], **kwargs: Any) -> None: - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] if not self.format or self.format not in self.SUPPORTED_SIZES: self.usage() return @@ -8594,9 +9978,17 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: if values[0].startswith("$_gef"): var_name = values[0] try: - values = str(gdb.parse_and_eval(var_name)).lstrip("{").rstrip("}").replace(",","").split(" ") + values = ( + str(gdb.parse_and_eval(var_name)) + .lstrip("{") + .rstrip("}") + .replace(",", "") + .split(" ") + ) except Exception: - gef_print(f"Bad variable specified, check value with command: p {var_name}") + gef_print( + f"Bad variable specified, check value with command: p {var_name}" + ) return d = str(gef.arch.endianness) @@ -8613,7 +10005,7 @@ class PatchQwordCommand(PatchCommand): """Write specified QWORD to the specified address.""" _cmdline_ = "patch qword" - _syntax_ = f"{_cmdline_} LOCATION QWORD1 [QWORD2 [QWORD3..]]" + _syntax_ = f"{_cmdline_} LOCATION QWORD1 [QWORD2 [QWORD3..]]" _example_ = f"{_cmdline_} $rip 0x4141414141414141" def __init__(self) -> None: @@ -8627,7 +10019,7 @@ class PatchDwordCommand(PatchCommand): """Write specified DWORD to the specified address.""" _cmdline_ = "patch dword" - _syntax_ = f"{_cmdline_} LOCATION DWORD1 [DWORD2 [DWORD3..]]" + _syntax_ = f"{_cmdline_} LOCATION DWORD1 [DWORD2 [DWORD3..]]" _example_ = f"{_cmdline_} $rip 0x41414141" def __init__(self) -> None: @@ -8641,7 +10033,7 @@ class PatchWordCommand(PatchCommand): """Write specified WORD to the specified address.""" _cmdline_ = "patch word" - _syntax_ = f"{_cmdline_} LOCATION WORD1 [WORD2 [WORD3..]]" + _syntax_ = f"{_cmdline_} LOCATION WORD1 [WORD2 [WORD3..]]" _example_ = f"{_cmdline_} $rip 0x4141" def __init__(self) -> None: @@ -8655,7 +10047,7 @@ class PatchByteCommand(PatchCommand): """Write specified BYTE to the specified address.""" _cmdline_ = "patch byte" - _syntax_ = f"{_cmdline_} LOCATION BYTE1 [BYTE2 [BYTE3..]]" + _syntax_ = f"{_cmdline_} LOCATION BYTE1 [BYTE2 [BYTE3..]]" _example_ = f"{_cmdline_} $pc 0x41 0x41 0x41 0x41 0x41" def __init__(self) -> None: @@ -8669,10 +10061,10 @@ class PatchStringCommand(GenericCommand): """Write specified string to the specified memory location pointed by ADDRESS.""" _cmdline_ = "patch string" - _syntax_ = f"{_cmdline_} ADDRESS \"double backslash-escaped string\"" + _syntax_ = f'{_cmdline_} ADDRESS "double backslash-escaped string"' _example_ = [ - f"{_cmdline_} $sp \"GEFROCKS\"", - f"{_cmdline_} $sp \"\\\\x41\\\\x41\\\\x41\\\\x41\"" + f'{_cmdline_} $sp "GEFROCKS"', + f'{_cmdline_} $sp "\\\\x41\\\\x41\\\\x41\\\\x41"', ] @only_if_gdb_running @@ -8687,7 +10079,7 @@ def do_invoke(self, argv: list[str]) -> None: try: msg_as_bytes = codecs.escape_decode(msg, "utf-8")[0] - gef.memory.write(addr, msg_as_bytes, len(msg_as_bytes)) # type: ignore + gef.memory.write(addr, msg_as_bytes, len(msg_as_bytes)) # type: ignore except (binascii.Error, gdb.error): err(f"Could not decode '\\xXX' encoded string \"{msg}\"") return @@ -8696,13 +10088,17 @@ def do_invoke(self, argv: list[str]) -> None: @lru_cache() def dereference_from(address: int) -> list[str]: if not is_alive(): - return [format_address(address),] + return [ + format_address(address), + ] code_color = gef.config["theme.dereference_code"] string_color = gef.config["theme.dereference_string"] max_recursion = gef.config["dereference.max_recursion"] or 10 addr = lookup_address(align_address(address)) - msg = [format_address(addr.value),] + msg = [ + format_address(addr.value), + ] seen_addrs = set() while addr.section and max_recursion: @@ -8729,7 +10125,11 @@ def dereference_from(address: int) -> list[str]: # -- Otherwise try to parse the value if addr.section: - if addr.section.is_executable() and addr.is_in_text_segment() and not is_ascii_string(addr.value): + if ( + addr.section.is_executable() + and addr.is_in_text_segment() + and not is_ascii_string(addr.value) + ): insn = gef_current_instruction(addr.value) insn_str = f"{insn.location} {insn.mnemonic} {', '.join(insn.operands)}" msg.append(Color.colorify(insn_str, code_color)) @@ -8741,7 +10141,9 @@ def dereference_from(address: int) -> list[str]: if len(s) < gef.arch.ptrsize: txt = f'{format_address(deref)} ("{Color.colorify(s, string_color)}"?)' elif len(s) > GEF_MAX_STRING_LENGTH: - txt = Color.colorify(f'"{s[:GEF_MAX_STRING_LENGTH]}[...]"', string_color) + txt = Color.colorify( + f'"{s[:GEF_MAX_STRING_LENGTH]}[...]"', string_color + ) else: txt = Color.colorify(f'"{s}"', string_color) @@ -8761,8 +10163,10 @@ class DereferenceCommand(GenericCommand): command.""" _cmdline_ = "dereference" - _syntax_ = f"{_cmdline_} [-h] [--length LENGTH] [--reference REFERENCE] [address]" - _aliases_ = ["telescope", ] + _syntax_ = f"{_cmdline_} [-h] [--length LENGTH] [--reference REFERENCE] [address]" + _aliases_ = [ + "telescope", + ] _example_ = f"{_cmdline_} --length 20 --reference $sp+0x10 $sp" def __init__(self) -> None: @@ -8782,10 +10186,10 @@ def pprint_dereferenced(addr: int, idx: int, base_offset: int = 0) -> str: current_address = align_address(addr + offset) addrs = dereference_from(current_address) addr_l = format_address(int(addrs[0], 16)) - ma = (memalign*2 + 2) + ma = memalign * 2 + 2 line = ( f"{Color.colorify(addr_l, base_address_color)}{VERTICAL_LINE}" - f"{base_offset+offset:+#07x}: {sep.join(addrs[1:]):{ma}s}" + f"{base_offset + offset:+#07x}: {sep.join(addrs[1:]):{ma}s}" ) register_hints = [] @@ -8803,9 +10207,11 @@ def pprint_dereferenced(addr: int, idx: int, base_offset: int = 0) -> str: return line @only_if_gdb_running - @parse_arguments({"address": "$sp"}, {("-r", "--reference"): "", ("-l", "--length"): 10}) + @parse_arguments( + {"address": "$sp"}, {("-r", "--reference"): "", ("-l", "--length"): 10} + ) def do_invoke(self, _: list[str], **kwargs: Any) -> None: - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] nb = args.length target = args.address @@ -8845,7 +10251,9 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: dereference_cmd = gef.gdb.commands["dereference"] assert isinstance(dereference_cmd, DereferenceCommand) for i in range(from_insnum, to_insnum, insnum_step): - gef_print(dereference_cmd.pprint_dereferenced(start_address, i, base_offset)) + gef_print( + dereference_cmd.pprint_dereferenced(start_address, i, base_offset) + ) return @@ -8856,7 +10264,7 @@ class ASLRCommand(GenericCommand): attached). This command allows to change that setting.""" _cmdline_ = "aslr" - _syntax_ = f"{_cmdline_} [(on|off)]" + _syntax_ = f"{_cmdline_} [(on|off)]" def do_invoke(self, argv: list[str]) -> None: argc = len(argv) @@ -8868,7 +10276,7 @@ def do_invoke(self, argv: list[str]) -> None: return msg = "ASLR is currently " - if ret[i + 25:].strip() == "on.": + if ret[i + 25 :].strip() == "on.": msg += Color.redify("disabled") else: msg += Color.greenify("enabled") @@ -8898,7 +10306,7 @@ class ResetCacheCommand(GenericCommand): handles properly the cache reset under "normal" scenario.""" _cmdline_ = "reset-cache" - _syntax_ = _cmdline_ + _syntax_ = _cmdline_ def do_invoke(self, _: list[str]) -> None: reset_all_caches() @@ -8911,13 +10319,15 @@ class VMMapCommand(GenericCommand): filter out the mapping whose pathname do not match that filter.""" _cmdline_ = "vmmap" - _syntax_ = f"{_cmdline_} [FILTER]" + _syntax_ = f"{_cmdline_} [FILTER]" _example_ = f"{_cmdline_} libc" @only_if_gdb_running - @parse_arguments({"unknown_types": [""]}, {("--addr", "-a"): [""], ("--name", "-n"): [""]}) + @parse_arguments( + {"unknown_types": [""]}, {("--addr", "-a"): [""], ("--name", "-n"): [""]} + ) def do_invoke(self, _: list[str], **kwargs: Any) -> None: - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] vmmap = gef.memory.maps if not vmmap: err("No address mapping information found") @@ -8940,8 +10350,12 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: warn(f"`{arg}` has no type specified. We guessed it was a name filter.") else: addrs[arg] = int(addr) - warn(f"`{arg}` has no type specified. We guessed it was an address filter.") - warn("You can use --name or --addr before the filter value for specifying its type manually.") + warn( + f"`{arg}` has no type specified. We guessed it was an address filter." + ) + warn( + "You can use --name or --addr before the filter value for specifying its type manually." + ) gef_print() if not gef.config["gef.disable_color"]: @@ -8950,14 +10364,24 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: color = gef.config["theme.table_heading"] headers = ["Start", "End", "Offset", "Perm", "Path"] - gef_print(Color.colorify("{:<{w}s}{:<{w}s}{:<{w}s}{:<4s} {:s}".format(*headers, w=gef.arch.ptrsize*2+3), color)) + gef_print( + Color.colorify( + "{:<{w}s}{:<{w}s}{:<{w}s}{:<4s} {:s}".format( + *headers, w=gef.arch.ptrsize * 2 + 3 + ), + color, + ) + ) last_printed_filter = None for entry in vmmap: names_filter = [f"name = '{x}'" for x in names if x in entry.path] - addrs_filter = [f"addr = {self.format_addr_filter(arg, addr)}" for arg, addr in addrs.items() - if entry.page_start <= addr < entry.page_end] + addrs_filter = [ + f"addr = {self.format_addr_filter(arg, addr)}" + for arg, addr in addrs.items() + if entry.page_start <= addr < entry.page_end + ] filter_content = f"[{' & '.join([*names_filter, *addrs_filter])}]" if not names and not addrs: @@ -8965,7 +10389,7 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: elif names_filter or addrs_filter: if filter_content != last_printed_filter: - gef_print() # skip a line between different filters + gef_print() # skip a line between different filters gef_print(Color.greenify(filter_content)) last_printed_filter = filter_content self.print_entry(entry) @@ -8982,7 +10406,9 @@ def print_entry(self, entry: Section) -> None: line_color = gef.config["theme.address_stack"] elif entry.path == "[heap]": line_color = gef.config["theme.address_heap"] - elif entry.permission & Permission.READ and entry.permission & Permission.EXECUTE: + elif ( + entry.permission & Permission.READ and entry.permission & Permission.EXECUTE + ): line_color = gef.config["theme.address_code"] line_parts = [ @@ -8991,7 +10417,9 @@ def print_entry(self, entry: Section) -> None: Color.colorify(format_address(entry.offset), line_color), ] if entry.permission == Permission.ALL: - line_parts.append(Color.colorify(str(entry.permission), "underline " + line_color)) + line_parts.append( + Color.colorify(str(entry.permission), "underline " + line_color) + ) else: line_parts.append(Color.colorify(str(entry.permission), line_color)) @@ -9024,14 +10452,21 @@ class XFilesCommand(GenericCommand): If an argument name is also given, the output will grep to the name within FILE.""" _cmdline_ = "xfiles" - _syntax_ = f"{_cmdline_} [FILE [NAME]]" + _syntax_ = f"{_cmdline_} [FILE [NAME]]" _example_ = f"\n{_cmdline_} libc\n{_cmdline_} libc IO_vtables" @only_if_gdb_running def do_invoke(self, argv: list[str]) -> None: color = gef.config["theme.table_heading"] headers = ["Start", "End", "Name", "File"] - gef_print(Color.colorify("{:<{w}s}{:<{w}s}{:<21s} {:s}".format(*headers, w=gef.arch.ptrsize*2+3), color)) + gef_print( + Color.colorify( + "{:<{w}s}{:<{w}s}{:<21s} {:s}".format( + *headers, w=gef.arch.ptrsize * 2 + 3 + ), + color, + ) + ) filter_by_file = argv[0] if argv and argv[0] else None filter_by_name = argv[1] if len(argv) > 1 and argv[1] else None @@ -9058,7 +10493,7 @@ class XAddressInfoCommand(GenericCommand): """Retrieve and display runtime information for the location(s) given as parameter.""" _cmdline_ = "xinfo" - _syntax_ = f"{_cmdline_} LOCATION" + _syntax_ = f"{_cmdline_} LOCATION" _example_ = f"{_cmdline_} $pc" def __init__(self) -> None: @@ -9092,17 +10527,21 @@ def infos(self, address: int) -> None: info = addr.info if sect: - gef_print(f"Page: {format_address(sect.page_start)} {RIGHT_ARROW} " - f"{format_address(sect.page_end)} (size={sect.page_end-sect.page_start:#x})" - f"\nPermissions: {sect.permission}" - f"\nPathname: {sect.path}" - f"\nOffset (from page): {addr.value-sect.page_start:#x}" - f"\nInode: {sect.inode}") + gef_print( + f"Page: {format_address(sect.page_start)} {RIGHT_ARROW} " + f"{format_address(sect.page_end)} (size={sect.page_end - sect.page_start:#x})" + f"\nPermissions: {sect.permission}" + f"\nPathname: {sect.path}" + f"\nOffset (from page): {addr.value - sect.page_start:#x}" + f"\nInode: {sect.inode}" + ) if info: - gef_print(f"Segment: {info.name} " - f"({format_address(info.zone_start)}-{format_address(info.zone_end)})" - f"\nOffset (from segment): {addr.value-info.zone_start:#x}") + gef_print( + f"Segment: {info.name} " + f"({format_address(info.zone_start)}-{format_address(info.zone_end)})" + f"\nOffset (from segment): {addr.value - info.zone_start:#x}" + ) sym = gdb_get_location_from_symbol(address) if sym: @@ -9121,7 +10560,7 @@ class XorMemoryCommand(GenericCommand): runtime at runtime.""" _cmdline_ = "xor-memory" - _syntax_ = f"{_cmdline_} (display|patch) ADDRESS SIZE KEY" + _syntax_ = f"{_cmdline_} (display|patch) ADDRESS SIZE KEY" def __init__(self) -> None: super().__init__(prefix=True) @@ -9138,7 +10577,7 @@ class XorMemoryDisplayCommand(GenericCommand): provided in hexadecimal format.""" _cmdline_ = "xor-memory display" - _syntax_ = f"{_cmdline_} ADDRESS SIZE KEY" + _syntax_ = f"{_cmdline_} ADDRESS SIZE KEY" _example_ = f"{_cmdline_} $sp 16 41414141" @only_if_gdb_running @@ -9167,7 +10606,7 @@ class XorMemoryPatchCommand(GenericCommand): provided in hexadecimal format.""" _cmdline_ = "xor-memory patch" - _syntax_ = f"{_cmdline_} ADDRESS SIZE KEY" + _syntax_ = f"{_cmdline_} ADDRESS SIZE KEY" _example_ = f"{_cmdline_} $sp 16 41414141" @only_if_gdb_running @@ -9193,13 +10632,16 @@ class TraceRunCommand(GenericCommand): path.""" _cmdline_ = "trace-run" - _syntax_ = f"{_cmdline_} LOCATION [MAX_CALL_DEPTH]" + _syntax_ = f"{_cmdline_} LOCATION [MAX_CALL_DEPTH]" _example_ = f"{_cmdline_} 0x555555554610" def __init__(self) -> None: super().__init__(self._cmdline_, complete=gdb.COMPLETE_LOCATION) self["max_tracing_recursion"] = (1, "Maximum depth of tracing") - self["tracefile_prefix"] = ("./gef-trace-", "Specify the tracing output file prefix") + self["tracefile_prefix"] = ( + "./gef-trace-", + "Specify the tracing output file prefix", + ) return @only_if_gdb_running @@ -9214,8 +10656,8 @@ def do_invoke(self, argv: list[str]) -> None: depth = 1 try: - loc_start = gef.arch.pc - loc_end = parse_address(argv[0]) + loc_start = gef.arch.pc + loc_end = parse_address(argv[0]) except gdb.error as e: err(f"Invalid location: {e}") return @@ -9239,20 +10681,25 @@ def trace(self, loc_start: int, loc_end: int, depth: int) -> None: self.start_tracing(loc_start, loc_end, depth) unhide_context() ok(f"Done, logfile stored as '{logfile}'") - info("Hint: import logfile with `ida_color_gdb_trace.py` script in IDA to visualize path") + info( + "Hint: import logfile with `ida_color_gdb_trace.py` script in IDA to visualize path" + ) return def start_tracing(self, loc_start: int, loc_end: int, depth: int) -> None: loc_cur = loc_start frame_count_init = self.get_frames_size() - gef_print("#", - f"# Execution tracing of {get_filepath()}", - f"# Start address: {format_address(loc_start)}", - f"# End address: {format_address(loc_end)}", - f"# Recursion level: {depth:d}", - "# automatically generated by gef.py", - "#\n", sep="\n") + gef_print( + "#", + f"# Execution tracing of {get_filepath()}", + f"# Start address: {format_address(loc_start)}", + f"# End address: {format_address(loc_end)}", + f"# Recursion level: {depth:d}", + "# automatically generated by gef.py", + "#\n", + sep="\n", + ) while loc_cur != loc_end: try: @@ -9267,10 +10714,13 @@ def start_tracing(self, loc_start: int, loc_end: int, depth: int) -> None: gdb.flush() except gdb.error as e: - gef_print("#", - f"# Execution interrupted at address {format_address(loc_cur)}", - f"# Exception: {e}", - "#\n", sep="\n") + gef_print( + "#", + f"# Execution interrupted at address {format_address(loc_cur)}", + f"# Exception: {e}", + "#\n", + sep="\n", + ) break return @@ -9283,7 +10733,7 @@ class PatternCommand(GenericCommand): currently loaded architecture.""" _cmdline_ = "pattern" - _syntax_ = f"{_cmdline_} (create|search) ARGS" + _syntax_ = f"{_cmdline_} (create|search) ARGS" def __init__(self) -> None: super().__init__(prefix=True) @@ -9302,15 +10752,17 @@ class PatternCreateCommand(GenericCommand): loaded architecture.""" _cmdline_ = "pattern create" - _syntax_ = f"{_cmdline_} [-h] [-n N] [length]" - _example_ = [ - f"{_cmdline_} 4096", - f"{_cmdline_} -n 4 128" - ] - - @parse_arguments({"length": 0}, {"-n": 0,}) + _syntax_ = f"{_cmdline_} [-h] [-n N] [length]" + _example_ = [f"{_cmdline_} 4096", f"{_cmdline_} -n 4 128"] + + @parse_arguments( + {"length": 0}, + { + "-n": 0, + }, + ) def do_invoke(self, _: list[str], **kwargs: Any) -> None: - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] length = args.length or gef.config["pattern.length"] n = args.n or gef.arch.ptrsize info(f"Generating a pattern of {length:d} bytes (n={n:d})") @@ -9328,14 +10780,14 @@ class PatternSearchCommand(GenericCommand): (such as a register name), a string or a hexadecimal value""" _cmdline_ = "pattern search" - _syntax_ = f"{_cmdline_} [-h] [-n N] [--max-length MAX_LENGTH] [pattern]" - _example_ = [f"{_cmdline_} $pc", - f"{_cmdline_} 0x61616164", - f"{_cmdline_} aaab"] + _syntax_ = f"{_cmdline_} [-h] [-n N] [--max-length MAX_LENGTH] [pattern]" + _example_ = [f"{_cmdline_} $pc", f"{_cmdline_} 0x61616164", f"{_cmdline_} aaab"] _aliases_ = ["pattern offset"] @only_if_gdb_running - @parse_arguments({"pattern": ""}, {("--period", "-n"): 0, ("--max-length", "-l"): 0}) + @parse_arguments( + {"pattern": ""}, {("--period", "-n"): 0, ("--max-length", "-l"): 0} + ) def do_invoke(self, _: list[str], **kwargs: Any) -> None: args = kwargs["arguments"] if not args.pattern: @@ -9360,27 +10812,33 @@ def search(self, pattern: str, size: int, period: int) -> None: dereferenced_value = dereference(addr) if dereferenced_value: addr = int(abs(to_unsigned_long(dereferenced_value))) - mask = (1<<(8 * period))-1 + mask = (1 << (8 * period)) - 1 addr &= mask - pattern_le = addr.to_bytes(period, 'little') - pattern_be = addr.to_bytes(period, 'big') + pattern_le = addr.to_bytes(period, "little") + pattern_be = addr.to_bytes(period, "big") else: # 2. assume it's a plain string pattern_be = gef_pybytes(pattern) pattern_le = gef_pybytes(pattern[::-1]) - info(f"Searching for '{pattern_le.hex()}'/'{pattern_be.hex()}' with period={period}") + info( + f"Searching for '{pattern_le.hex()}'/'{pattern_be.hex()}' with period={period}" + ) cyclic_pattern = generate_cyclic_pattern(size, period) off = cyclic_pattern.find(pattern_le) if off >= 0: - ok(f"Found at offset {off:d} (little-endian search) " - f"{Color.colorify('likely', 'bold red') if gef.arch.endianness == Endianness.LITTLE_ENDIAN else ''}") + ok( + f"Found at offset {off:d} (little-endian search) " + f"{Color.colorify('likely', 'bold red') if gef.arch.endianness == Endianness.LITTLE_ENDIAN else ''}" + ) return off = cyclic_pattern.find(pattern_be) if off >= 0: - ok(f"Found at offset {off:d} (big-endian search) " - f"{Color.colorify('likely', 'bold green') if gef.arch.endianness == Endianness.BIG_ENDIAN else ''}") + ok( + f"Found at offset {off:d} (big-endian search) " + f"{Color.colorify('likely', 'bold green') if gef.arch.endianness == Endianness.BIG_ENDIAN else ''}" + ) return err(f"Pattern '{pattern}' not found") @@ -9398,7 +10856,7 @@ class ChecksecCommand(GenericCommand): - Fortify Source""" _cmdline_ = "checksec" - _syntax_ = f"{_cmdline_} [FILENAME]" + _syntax_ = f"{_cmdline_} [FILENAME]" _example_ = f"{_cmdline_} /bin/ls" def __init__(self) -> None: @@ -9429,9 +10887,14 @@ def do_invoke(self, argv: list[str]) -> None: def print_security_properties(self, filename: str) -> None: sec = Elf(filename).checksec for prop in sec: - if prop in ("Partial RelRO", "Full RelRO"): continue + if prop in ("Partial RelRO", "Full RelRO"): + continue val = sec[prop] - msg = Color.greenify(Color.boldify(TICK)) if val is True else Color.redify(Color.boldify(CROSS)) + msg = ( + Color.greenify(Color.boldify(TICK)) + if val is True + else Color.redify(Color.boldify(CROSS)) + ) if val and prop == "Canary" and is_alive(): canary = gef.session.canary[0] if gef.session.canary else 0 msg += f"(value: {canary:#x})" @@ -9457,13 +10920,19 @@ class GotCommand(GenericCommand): def __init__(self): super().__init__() - self["function_resolved"] = ("green", - "Line color of the got command output for resolved function") - self["function_not_resolved"] = ("yellow", - "Line color of the got command output for unresolved function") + self["function_resolved"] = ( + "green", + "Line color of the got command output for resolved function", + ) + self["function_not_resolved"] = ( + "yellow", + "Line color of the got command output for unresolved function", + ) return - def build_line(self, name: str, _path: str, color: str, address_val: int, got_address: int) -> str: + def build_line( + self, name: str, _path: str, color: str, address_val: int, got_address: int + ) -> str: line = f"[{hex(address_val)}] " line += Color.colorify(f"{name} {RIGHT_ARROW} {hex(got_address)}", color) return line @@ -9471,12 +10940,15 @@ def build_line(self, name: str, _path: str, color: str, address_val: int, got_ad @only_if_gdb_running @parse_arguments({"symbols": [""]}, {"--all": False}) def do_invoke(self, _: list[str], **kwargs: Any) -> None: - args : argparse.Namespace = kwargs["arguments"] + args: argparse.Namespace = kwargs["arguments"] vmmap = gef.memory.maps - mapfiles = [mapfile for mapfile in vmmap if - (args.all or mapfile.path == str(gef.session.file)) and - pathlib.Path(mapfile.realpath).is_file() and - mapfile.permission & Permission.EXECUTE] + mapfiles = [ + mapfile + for mapfile in vmmap + if (args.all or mapfile.path == str(gef.session.file)) + and pathlib.Path(mapfile.realpath).is_file() + and mapfile.permission & Permission.EXECUTE + ] for mapfile in mapfiles: self.print_got_for(mapfile.path, mapfile.realpath, args.symbols) @@ -9495,7 +10967,9 @@ def print_got_for(self, file: str, realpath: str, argv: list[str]) -> None: checksec_status = Elf(elf_file).checksec relro_status = "Full RelRO" full_relro = checksec_status["Full RelRO"] - pie = checksec_status["PIE"] # if pie we will have offset instead of abs address. + pie = checksec_status[ + "PIE" + ] # if pie we will have offset instead of abs address. if not full_relro: relro_status = "Partial RelRO" @@ -9505,10 +10979,14 @@ def print_got_for(self, file: str, realpath: str, argv: list[str]) -> None: relro_status = "No RelRO" # retrieve jump slots using readelf - lines = gef_execute_external([readelf, "--wide", "--relocs", elf_file], as_list=True) + lines = gef_execute_external( + [readelf, "--wide", "--relocs", elf_file], as_list=True + ) jmpslots = [line for line in lines if "JUMP" in line] - gef_print(f"{titlify(file)}\n\nGOT protection: {relro_status} | GOT functions: {len(jmpslots)}\n ") + gef_print( + f"{titlify(file)}\n\nGOT protection: {relro_status} | GOT functions: {len(jmpslots)}\n " + ) for line in jmpslots: address, _, _, _, name = line.split()[:5] @@ -9533,7 +11011,9 @@ def print_got_for(self, file: str, realpath: str, argv: list[str]) -> None: else: color = self["function_resolved"] - line = self.build_line(name, elf_virtual_path, color, address_val, got_address) + line = self.build_line( + name, elf_virtual_path, color, address_val, got_address + ) gef_print(line) return @@ -9541,6 +11021,7 @@ def print_got_for(self, file: str, realpath: str, argv: list[str]) -> None: @register class HighlightCommand(GenericCommand): """Highlight user-defined text matches in GEF output universally.""" + _cmdline_ = "highlight" _syntax_ = f"{_cmdline_} (add|remove|list|clear)" _aliases_ = ["hl"] @@ -9556,6 +11037,7 @@ def do_invoke(self, _: list[str]) -> None: @register class HighlightListCommand(GenericCommand): """Show the current highlight table with matches to colors.""" + _cmdline_ = "highlight list" _aliases_ = ["highlight ls", "hll"] _syntax_ = _cmdline_ @@ -9567,8 +11049,10 @@ def print_highlight_table(self) -> None: left_pad = max(map(len, gef.ui.highlight_table.keys())) for match, color in sorted(gef.ui.highlight_table.items()): - print(f"{Color.colorify(match.ljust(left_pad), color)} {VERTICAL_LINE} " - f"{Color.colorify(color, color)}") + print( + f"{Color.colorify(match.ljust(left_pad), color)} {VERTICAL_LINE} " + f"{Color.colorify(color, color)}" + ) return def do_invoke(self, _: list[str]) -> None: @@ -9578,6 +11062,7 @@ def do_invoke(self, _: list[str]) -> None: @register class HighlightClearCommand(GenericCommand): """Clear the highlight table, remove all matches.""" + _cmdline_ = "highlight clear" _aliases_ = ["hlc"] _syntax_ = _cmdline_ @@ -9589,6 +11074,7 @@ def do_invoke(self, _: list[str]) -> None: @register class HighlightAddCommand(GenericCommand): """Add a match to the highlight table.""" + _cmdline_ = "highlight add" _syntax_ = f"{_cmdline_} MATCH COLOR" _aliases_ = ["highlight set", "hla"] @@ -9606,6 +11092,7 @@ def do_invoke(self, argv: list[str]) -> None: @register class HighlightRemoveCommand(GenericCommand): """Remove a match in the highlight table.""" + _cmdline_ = "highlight remove" _syntax_ = f"{_cmdline_} MATCH" _aliases_ = [ @@ -9631,9 +11118,12 @@ class FormatStringSearchCommand(GenericCommand): at well-known dangerous functions (printf, snprintf, etc.), and check if the pointer holding the format string is writable, and therefore susceptible to format string attacks if an attacker can control its content.""" + _cmdline_ = "format-string-helper" _syntax_ = _cmdline_ - _aliases_ = ["fmtstr-helper",] + _aliases_ = [ + "fmtstr-helper", + ] def do_invoke(self, _: list[str]) -> None: dangerous_functions = { @@ -9652,8 +11142,10 @@ def do_invoke(self, _: list[str]) -> None: FormatStringBreakpoint(function_name, argument_number) nb_installed_breaks += 1 - ok(f"Enabled {nb_installed_breaks} FormatString " - f"breakpoint{'s' if nb_installed_breaks > 1 else ''}") + ok( + f"Enabled {nb_installed_breaks} FormatString " + f"breakpoint{'s' if nb_installed_breaks > 1 else ''}" + ) return @@ -9666,16 +11158,32 @@ class HeapAnalysisCommand(GenericCommand): - Use-after-Free - Double Free - Heap overlap""" + _cmdline_ = "heap-analysis-helper" _syntax_ = _cmdline_ def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_NONE) - self["check_free_null"] = (False, "Break execution when a free(NULL) is encountered") - self["check_double_free"] = (True, "Break execution when a double free is encountered") - self["check_weird_free"] = (True, "Break execution when free() is called against a non-tracked pointer") - self["check_uaf"] = (True, "Break execution when a possible Use-after-Free condition is found") - self["check_heap_overlap"] = (True, "Break execution when a possible overlap in allocation is found") + self["check_free_null"] = ( + False, + "Break execution when a free(NULL) is encountered", + ) + self["check_double_free"] = ( + True, + "Break execution when a double free is encountered", + ) + self["check_weird_free"] = ( + True, + "Break execution when free() is called against a non-tracked pointer", + ) + self["check_uaf"] = ( + True, + "Break execution when a possible Use-after-Free condition is found", + ) + self["check_heap_overlap"] = ( + True, + "Break execution when a possible overlap in allocation is found", + ) self.bp_malloc = None self.bp_calloc = None @@ -9706,10 +11214,14 @@ def setup(self) -> None: ok("Disabling hardware watchpoints (this may increase the latency)") gdb.execute("set can-use-hw-watchpoints 0") - info("Dynamic breakpoints correctly setup, " - "GEF will break execution if a possible vulnerability is found.") - warn(f"{Color.colorify('Note', 'bold underline yellow')}: " - "The heap analysis slows down the execution noticeably.") + info( + "Dynamic breakpoints correctly setup, " + "GEF will break execution if a possible vulnerability is found." + ) + warn( + f"{Color.colorify('Note', 'bold underline yellow')}: " + "The heap analysis slows down the execution noticeably." + ) # when inferior quits, we need to clean everything for a next execution gef_on_exit_hook(self.clean) @@ -9755,7 +11267,9 @@ def clean(self, _: "gdb.ExitedEvent") -> None: gef.session.heap_freed_chunks = [] gef.session.heap_uaf_watchpoints = [] - ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - Re-enabling hardware watchpoints") + ok( + f"{Color.colorify('Heap-Analysis', 'yellow bold')} - Re-enabling hardware watchpoints" + ) gdb.execute("set can-use-hw-watchpoints 1") gef_on_exit_unhook(self.clean) @@ -9774,9 +11288,9 @@ def register_function(cls: Type["GenericFunction"]) -> Type["GenericFunction"]: class GenericFunction(gdb.Function): """This is an abstract class for invoking convenience functions, should not be instantiated.""" - _function_ : str + _function_: str _syntax_: str = "" - _example_ : str = "" + _example_: str = "" def __init__(self) -> None: super().__init__(self._function_) @@ -9800,8 +11314,9 @@ def do_invoke(self, args: Any) -> int: @register class StackOffsetFunction(GenericFunction): """Return the current stack base address plus an optional offset.""" + _function_ = "_stack" - _syntax_ = f"${_function_}()" + _syntax_ = f"${_function_}()" def do_invoke(self, args: list) -> int: base = get_section_base_address("[stack]") @@ -9814,8 +11329,9 @@ def do_invoke(self, args: list) -> int: @register class HeapBaseFunction(GenericFunction): """Return the current heap base address plus an optional offset.""" + _function_ = "_heap" - _syntax_ = f"${_function_}()" + _syntax_ = f"${_function_}()" def do_invoke(self, args: list[str]) -> int: base = gef.heap.base_address @@ -9830,9 +11346,10 @@ def do_invoke(self, args: list[str]) -> int: class SectionBaseFunction(GenericFunction): """Return the matching file's base address plus an optional offset. Defaults to current file. Note that quotes need to be escaped""" + _function_ = "_base" - _syntax_ = "$_base([filepath])" - _example_ = "p $_base(\\\"/usr/lib/ld-2.33.so\\\")" + _syntax_ = "$_base([filepath])" + _example_ = 'p $_base(\\"/usr/lib/ld-2.33.so\\")' def do_invoke(self, args: list) -> int: addr = 0 @@ -9858,8 +11375,9 @@ def do_invoke(self, args: list) -> int: @register class BssBaseFunction(GenericFunction): """Return the current bss base address plus the given offset.""" + _function_ = "_bss" - _syntax_ = f"${_function_}([OFFSET])" + _syntax_ = f"${_function_}([OFFSET])" _example_ = "deref $_bss(0x20)" def do_invoke(self, args: list) -> int: @@ -9872,8 +11390,9 @@ def do_invoke(self, args: list) -> int: @register class GotBaseFunction(GenericFunction): """Return the current GOT base address plus the given offset.""" + _function_ = "_got" - _syntax_ = f"${_function_}([OFFSET])" + _syntax_ = f"${_function_}([OFFSET])" _example_ = "deref $_got(0x20)" def do_invoke(self, args: list) -> int: @@ -9886,6 +11405,7 @@ def do_invoke(self, args: list) -> int: @register class GefFunctionsCommand(GenericCommand): """List the convenience functions provided by GEF.""" + _cmdline_ = "functions" _syntax_ = _cmdline_ @@ -9923,7 +11443,7 @@ def __rebuild(self) -> None: self.command_size = len(gef.gdb.commands) _, cols = get_terminal_size() - separator = HORIZONTAL_LINE*cols + separator = HORIZONTAL_LINE * cols self.__doc__ = f"\n{separator}\n".join(sorted(self.docs)) self.should_refresh = False return @@ -9931,8 +11451,10 @@ def __rebuild(self) -> None: def do_invoke(self, argv) -> None: self.dont_repeat() gef_print(titlify("GEF - Convenience Functions")) - gef_print("These functions can be used as arguments to other " - "commands to dynamically calculate values\n") + gef_print( + "These functions can be used as arguments to other " + "commands to dynamically calculate values\n" + ) gef_print(str(self)) return @@ -9944,29 +11466,75 @@ class GefCommand(gdb.Command): """GEF main command: view all new commands by typing `gef`.""" _cmdline_ = "gef" - _syntax_ = f"{_cmdline_} (missing|config|save|restore|set|run)" + _syntax_ = f"{_cmdline_} (missing|config|save|restore|set|run)" def __init__(self) -> None: super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, True) - gef.config["gef.follow_child"] = GefSetting(True, bool, "Automatically set GDB to follow child when forking") - gef.config["gef.readline_compat"] = GefSetting(False, bool, "Workaround for readline SOH/ETX issue (SEGV)") + gef.config["gef.follow_child"] = GefSetting( + True, bool, "Automatically set GDB to follow child when forking" + ) + gef.config["gef.readline_compat"] = GefSetting( + False, bool, "Workaround for readline SOH/ETX issue (SEGV)" + ) gef.config["gef.debug"] = GefSetting(False, bool, "Enable debug mode for gef") - gef.config["gef.autosave_breakpoints_file"] = GefSetting("", str, "Automatically save and restore breakpoints") - gef.config["gef.disable_target_remote_overwrite"] = GefSetting(False, bool, "Disable the overwrite of `target remote`") - plugins_dir = GefSetting("", str, "Autoload additional GEF commands from external directory", hooks={"on_write": [GefSetting.no_spaces, ]}) - plugins_dir.add_hook("on_changed", [lambda _, new_val: GefSetting.must_exist(new_val), lambda _, new_val: self.load_extra_plugins(new_val), ]) + gef.config["gef.autosave_breakpoints_file"] = GefSetting( + "", str, "Automatically save and restore breakpoints" + ) + gef.config["gef.disable_target_remote_overwrite"] = GefSetting( + False, bool, "Disable the overwrite of `target remote`" + ) + plugins_dir = GefSetting( + "", + str, + "Autoload additional GEF commands from external directory", + hooks={ + "on_write": [ + GefSetting.no_spaces, + ] + }, + ) + plugins_dir.add_hook( + "on_changed", + [ + lambda _, new_val: GefSetting.must_exist(new_val), + lambda _, new_val: self.load_extra_plugins(new_val), + ], + ) gef.config["gef.extra_plugins_dir"] = plugins_dir - gef.config["gef.disable_color"] = GefSetting(False, bool, "Disable all colors in GEF") - gef.config["gef.tempdir"] = GefSetting(GEF_TEMP_DIR, pathlib.Path, "Directory to use for temporary/cache content", hooks={"on_write": [GefSetting.no_spaces, GefSetting.create_folder_tree]}) - gef.config["gef.show_deprecation_warnings"] = GefSetting(True, bool, "Toggle the display of the `deprecated` warnings") - gef.config["gef.buffer"] = GefSetting(True, bool, "Internally buffer command output until completion") - gef.config["gef.bruteforce_main_arena"] = GefSetting(False, bool, "Allow bruteforcing main_arena symbol if everything else fails") - gef.config["gef.libc_version"] = GefSetting("", str, "Specify libc version when auto-detection fails") - gef.config["gef.main_arena_offset"] = GefSetting("", str, "Offset from libc base address to main_arena symbol (int or hex). Set to empty string to disable.") - gef.config["gef.propagate_debug_exception"] = GefSetting(False, bool, "If true, when debug mode is enabled, Python exceptions will be propagated all the way.") - - self.commands : dict[str, GenericCommand] = {} - self.functions : dict[str, GenericFunction] = {} + gef.config["gef.disable_color"] = GefSetting( + False, bool, "Disable all colors in GEF" + ) + gef.config["gef.tempdir"] = GefSetting( + GEF_TEMP_DIR, + pathlib.Path, + "Directory to use for temporary/cache content", + hooks={"on_write": [GefSetting.no_spaces, GefSetting.create_folder_tree]}, + ) + gef.config["gef.show_deprecation_warnings"] = GefSetting( + True, bool, "Toggle the display of the `deprecated` warnings" + ) + gef.config["gef.buffer"] = GefSetting( + True, bool, "Internally buffer command output until completion" + ) + gef.config["gef.bruteforce_main_arena"] = GefSetting( + False, bool, "Allow bruteforcing main_arena symbol if everything else fails" + ) + gef.config["gef.libc_version"] = GefSetting( + "", str, "Specify libc version when auto-detection fails" + ) + gef.config["gef.main_arena_offset"] = GefSetting( + "", + str, + "Offset from libc base address to main_arena symbol (int or hex). Set to empty string to disable.", + ) + gef.config["gef.propagate_debug_exception"] = GefSetting( + False, + bool, + "If true, when debug mode is enabled, Python exceptions will be propagated all the way.", + ) + + self.commands: dict[str, GenericCommand] = {} + self.functions: dict[str, GenericFunction] = {} self.missing: dict[str, Exception] = {} return @@ -10003,6 +11571,7 @@ def setup(self) -> None: def load_extra_plugins(self, extra_plugins_dir: pathlib.Path | None = None) -> int: """Load the plugins from the gef-extras setting. Returns the number of new plugins added.""" + def load_plugin(fpath: pathlib.Path) -> bool: try: dbg(f"Loading '{fpath}'") @@ -10027,11 +11596,15 @@ def load_plugins_from_directory(plugin_directory: pathlib.Path): self.load() nb_failed = len(__registered_commands__) - len(self.commands) load_time = time.perf_counter() - start_time - ok(f"{Color.colorify(str(nb_added), 'bold green')} extra commands added " \ - f"in {load_time:.2f} seconds") + ok( + f"{Color.colorify(str(nb_added), 'bold green')} extra commands added " + f"in {load_time:.2f} seconds" + ) if nb_failed != 0: - warn(f"{Color.colorify(str(nb_failed), 'bold light_gray')} extra commands/functions failed to be added. " - "Check `gef missing` to know why") + warn( + f"{Color.colorify(str(nb_failed), 'bold light_gray')} extra commands/functions failed to be added. " + "Check `gef missing` to know why" + ) except gdb.error as e: err(f"failed: {e}") return nb_added @@ -10055,33 +11628,60 @@ def invoke(self, args: Any, from_tty: bool) -> None: gdb.execute("gef help") return - def add_context_layout_mapping(self, current_pane_name: str, display_pane_function: Callable, pane_title_function: Callable, condition: Callable | None) -> None: + def add_context_layout_mapping( + self, + current_pane_name: str, + display_pane_function: Callable, + pane_title_function: Callable, + condition: Callable | None, + ) -> None: """Add a new context layout mapping.""" context = self.commands["context"] assert isinstance(context, ContextCommand) # overload the printing of pane title - context.layout_mapping[current_pane_name] = (display_pane_function, pane_title_function, condition) + context.layout_mapping[current_pane_name] = ( + display_pane_function, + pane_title_function, + condition, + ) - def add_context_pane(self, pane_name: str, display_pane_function: Callable, pane_title_function: Callable, condition: Callable | None) -> None: + def add_context_pane( + self, + pane_name: str, + display_pane_function: Callable, + pane_title_function: Callable, + condition: Callable | None, + ) -> None: """Add a new context pane to ContextCommand.""" # assure users can toggle the new context corrected_settings_name: str = pane_name.replace(" ", "_") if corrected_settings_name in gef.config["context.layout"]: - warn(f"Duplicate name for `{pane_name}` (`{corrected_settings_name}`), skipping") + warn( + f"Duplicate name for `{pane_name}` (`{corrected_settings_name}`), skipping" + ) return gef.config["context.layout"] += f" {corrected_settings_name}" - self.add_context_layout_mapping(corrected_settings_name, display_pane_function, pane_title_function, condition) + self.add_context_layout_mapping( + corrected_settings_name, + display_pane_function, + pane_title_function, + condition, + ) def load(self) -> None: """Load all the commands and functions defined by GEF into GDB.""" current_commands = set(self.commands.keys()) - new_commands = set(x._cmdline_ for x in __registered_commands__) - current_commands + new_commands = ( + set(x._cmdline_ for x in __registered_commands__) - current_commands + ) current_functions = set(self.functions.keys()) - new_functions = set(x._function_ for x in __registered_functions__) - current_functions + new_functions = ( + set(x._function_ for x in __registered_functions__) - current_functions + ) self.missing.clear() - self.__load_time_ms = time.time()* 1000 + self.__load_time_ms = time.time() * 1000 # load all new functions for name in sorted(new_functions): @@ -10111,33 +11711,39 @@ def load(self) -> None: except Exception as reason: self.missing[name] = reason - self.__load_time_ms = (time.time()* 1000) - self.__load_time_ms + self.__load_time_ms = (time.time() * 1000) - self.__load_time_ms return - def show_banner(self) -> None: - gef_print(f"{Color.greenify('GEF')} for {gef.session.os} ready, " - f"type `{Color.colorify('gef', 'underline yellow')}' to start, " - f"`{Color.colorify('gef config', 'underline pink')}' to configure") + gef_print( + f"{Color.greenify('GEF')} for {gef.session.os} ready, " + f"type `{Color.colorify('gef', 'underline yellow')}' to start, " + f"`{Color.colorify('gef config', 'underline pink')}' to configure" + ) ver = f"{sys.version_info.major:d}.{sys.version_info.minor:d}" - gef_print(f"{Color.colorify(str(len(self.commands)), 'bold green')} commands loaded " - f"and {Color.colorify(str(len(self.functions)), 'bold blue')} functions added for " - f"GDB {Color.colorify(gdb.VERSION, 'bold yellow')} in {self.__load_time_ms:.2f}ms " - f"using Python engine {Color.colorify(ver, 'bold red')}") + gef_print( + f"{Color.colorify(str(len(self.commands)), 'bold green')} commands loaded " + f"and {Color.colorify(str(len(self.functions)), 'bold blue')} functions added for " + f"GDB {Color.colorify(gdb.VERSION, 'bold yellow')} in {self.__load_time_ms:.2f}ms " + f"using Python engine {Color.colorify(ver, 'bold red')}" + ) nb_missing = len(self.missing) if nb_missing: - warn(f"{Color.colorify(str(nb_missing), 'bold red')} " - f"command{'s' if nb_missing > 1 else ''} could not be loaded, " - f"run `{Color.colorify('gef missing', 'underline pink')}` to know why.") + warn( + f"{Color.colorify(str(nb_missing), 'bold red')} " + f"command{'s' if nb_missing > 1 else ''} could not be loaded, " + f"run `{Color.colorify('gef missing', 'underline pink')}` to know why." + ) return class GefHelpCommand(gdb.Command): """GEF help sub-command.""" + _cmdline_ = "gef help" - _syntax_ = _cmdline_ + _syntax_ = _cmdline_ def __init__(self) -> None: super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) @@ -10159,7 +11765,7 @@ def __rebuild(self) -> None: self.command_size = len(gef.gdb.commands) _, cols = get_terminal_size() - separator = HORIZONTAL_LINE*cols + separator = HORIZONTAL_LINE * cols self.__doc__ = f"\n{separator}\n".join(sorted(self.docs)) self.should_refresh = False return @@ -10171,7 +11777,11 @@ def __add__(self, command: tuple[str, GenericCommand]): # do not print subcommands in gef help return self doc = getattr(class_obj, "__doc__", "").lstrip() - aliases = f"Aliases: {', '.join(class_obj._aliases_)}" if hasattr(class_obj, "_aliases_") else "" + aliases = ( + f"Aliases: {', '.join(class_obj._aliases_)}" + if hasattr(class_obj, "_aliases_") + else "" + ) msg = f"{Color.colorify(cmd, 'bold red')}\n{doc}\n{aliases}" self.docs.append(msg) return self @@ -10194,8 +11804,9 @@ class GefConfigCommand(gdb.Command): to this command help), and/or restore previously saved settings by running `gef restore` (refer help). """ + _cmdline_ = "gef config" - _syntax_ = f"{_cmdline_} [setting_name] [setting_value]" + _syntax_ = f"{_cmdline_} [setting_name] [setting_value]" def __init__(self) -> None: super().__init__(self._cmdline_, gdb.COMMAND_NONE, prefix=False) @@ -10223,8 +11834,11 @@ def invoke(self, args: str, from_tty: bool) -> None: gef_print(titlify(f"GEF configuration setting: {names[0]}")) self.print_setting(names[0], verbose=True) else: - gef_print(titlify(f"GEF configuration settings matching '{argv[0]}'")) - for name in names: self.print_setting(name) + gef_print( + titlify(f"GEF configuration settings matching '{argv[0]}'") + ) + for name in names: + self.print_setting(name) return if not is_debug(): @@ -10272,7 +11886,7 @@ def set_setting(self, argv: list[str]) -> bool: err("Invalid command format") return False - loaded_commands = list( gef.gdb.commands.keys()) + ["gef"] + loaded_commands = list(gef.gdb.commands.keys()) + ["gef"] plugin_name = key.split(".", 1)[0] if plugin_name not in loaded_commands: err(f"Unknown plugin '{plugin_name}'") @@ -10318,8 +11932,9 @@ def complete(self, text: str, word: str) -> list[str]: class GefSaveCommand(gdb.Command): """GEF save sub-command. Saves the current configuration of GEF to disk (by default in file '~/.gef.rc').""" + _cmdline_ = "gef save" - _syntax_ = _cmdline_ + _syntax_ = _cmdline_ def __init__(self) -> None: super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) @@ -10356,8 +11971,9 @@ def invoke(self, args: Any, from_tty: bool) -> None: class GefRestoreCommand(gdb.Command): """GEF restore sub-command. Loads settings from file '~/.gef.rc' and apply them to the configuration of GEF.""" + _cmdline_ = "gef restore" - _syntax_ = _cmdline_ + _syntax_ = _cmdline_ def __init__(self) -> None: super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) @@ -10367,7 +11983,7 @@ def __init__(self) -> None: def invoke(self, args: str, from_tty: bool) -> None: self.dont_repeat() if GEF_RC.is_file(): - quiet = (args.lower() == "quiet") + quiet = args.lower() == "quiet" self.reload(quiet) return @@ -10394,11 +12010,15 @@ def reload(self, quiet: bool): continue new_value = cfg.get(section, optname) if setting.type is bool: - new_value = True if new_value.upper() in ("TRUE", "T", "1") else False + new_value = ( + True if new_value.upper() in ("TRUE", "T", "1") else False + ) setting.value = setting.type(new_value) if not quiet: - ok(f"Configuration from '{Color.colorify(str(GEF_RC), 'bold blue')}' restored") + ok( + f"Configuration from '{Color.colorify(str(GEF_RC), 'bold blue')}' restored" + ) return @@ -10407,8 +12027,9 @@ class GefMissingCommand(gdb.Command): Display the GEF commands that could not be loaded, along with the reason of why they could not be loaded. """ + _cmdline_ = "gef missing" - _syntax_ = _cmdline_ + _syntax_ = _cmdline_ def __init__(self) -> None: super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) @@ -10428,17 +12049,23 @@ def invoke(self, args: Any, from_tty: bool) -> None: class GefSetCommand(gdb.Command): """Override GDB set commands with the context from GEF.""" + _cmdline_ = "gef set" - _syntax_ = f"{_cmdline_} [GDB_SET_ARGUMENTS]" + _syntax_ = f"{_cmdline_} [GDB_SET_ARGUMENTS]" def __init__(self) -> None: - super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_SYMBOL, False) + super().__init__( + self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_SYMBOL, False + ) return def invoke(self, args: Any, from_tty: bool) -> None: self.dont_repeat() args = args.split() - cmd = ["set", args[0],] + cmd = [ + "set", + args[0], + ] for p in args[1:]: if p.startswith("$_gef"): c = gdb.parse_and_eval(p) @@ -10453,11 +12080,14 @@ def invoke(self, args: Any, from_tty: bool) -> None: class GefRunCommand(gdb.Command): """Override GDB run commands with the context from GEF. Simple wrapper for GDB run command to use arguments set from `gef set args`.""" + _cmdline_ = "gef run" - _syntax_ = f"{_cmdline_} [GDB_RUN_ARGUMENTS]" + _syntax_ = f"{_cmdline_} [GDB_RUN_ARGUMENTS]" def __init__(self) -> None: - super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_FILENAME, False) + super().__init__( + self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_FILENAME, False + ) return def invoke(self, args: Any, from_tty: bool) -> None: @@ -10475,7 +12105,13 @@ def invoke(self, args: Any, from_tty: bool) -> None: class GefAlias(gdb.Command): """Simple aliasing wrapper because GDB doesn't do what it should.""" - def __init__(self, alias: str, command: str, completer_class: int = gdb.COMPLETE_NONE, command_class: int = gdb.COMMAND_NONE) -> None: + def __init__( + self, + alias: str, + command: str, + completer_class: int = gdb.COMPLETE_NONE, + command_class: int = gdb.COMMAND_NONE, + ) -> None: p = command.split() if not p: return @@ -10492,7 +12128,7 @@ def __init__(self, alias: str, command: str, completer_class: int = gdb.COMPLETE _instance = r[1] self.__doc__ += f": {_instance.__doc__}" - if hasattr(_instance, "complete"): + if hasattr(_instance, "complete"): self.complete = _instance.complete super().__init__(alias, command_class, completer_class=completer_class) @@ -10523,7 +12159,7 @@ class AliasesCommand(GenericCommand): """Base command to add, remove, or list aliases.""" _cmdline_ = "aliases" - _syntax_ = f"{_cmdline_} (add|rm|ls)" + _syntax_ = f"{_cmdline_} (add|rm|ls)" def __init__(self) -> None: super().__init__(prefix=True) @@ -10539,7 +12175,7 @@ class AliasesAddCommand(AliasesCommand): """Command to add aliases.""" _cmdline_ = "aliases add" - _syntax_ = f"{_cmdline_} [ALIAS] [COMMAND]" + _syntax_ = f"{_cmdline_} [ALIAS] [COMMAND]" _example_ = f"{_cmdline_} scope telescope" def __init__(self) -> None: @@ -10571,7 +12207,9 @@ def do_invoke(self, argv: list[str]) -> None: self.usage() return try: - alias_to_remove = next(filter(lambda x: x.alias == argv[0], gef.session.aliases)) + alias_to_remove = next( + filter(lambda x: x.alias == argv[0], gef.session.aliases) + ) gef.session.aliases.remove(alias_to_remove) except (ValueError, StopIteration): err(f"{argv[0]} not found in aliases.") @@ -10628,8 +12266,21 @@ def tmux_setup(self) -> None: tmux = which("tmux") ok("tmux session found, splitting window...") - pane, pty = subprocess.check_output([tmux, "splitw", "-h", '-F#{session_name}:#{window_index}.#{pane_index}-#{pane_tty}', "-P"]).decode().strip().split("-") - atexit.register(lambda : subprocess.run([tmux, "kill-pane", "-t", pane])) + pane, pty = ( + subprocess.check_output( + [ + tmux, + "splitw", + "-h", + "-F#{session_name}:#{window_index}.#{pane_index}-#{pane_tty}", + "-P", + ] + ) + .decode() + .strip() + .split("-") + ) + atexit.register(lambda: subprocess.run([tmux, "kill-pane", "-t", pane])) # clear the screen and let it wait for input forever gdb.execute(f"!'{tmux}' send-keys -t {pane} 'clear ; cat' C-m") gdb.execute(f"!'{tmux}' select-pane -L") @@ -10671,12 +12322,15 @@ def screen_setup(self) -> None: class GefInstallExtraScriptCommand(gdb.Command): """`gef install` command: installs one or more scripts from the `gef-extras` script repo. Note that the command doesn't check for external dependencies the script(s) might require.""" + _cmdline_ = "gef install" - _syntax_ = f"{_cmdline_} SCRIPTNAME [SCRIPTNAME [SCRIPTNAME...]]" + _syntax_ = f"{_cmdline_} SCRIPTNAME [SCRIPTNAME [SCRIPTNAME...]]" def __init__(self) -> None: super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) - self.branch = gef.config.get("gef.extras_default_branch", GEF_EXTRAS_DEFAULT_BRANCH) + self.branch = gef.config.get( + "gef.extras_default_branch", GEF_EXTRAS_DEFAULT_BRANCH + ) return def invoke(self, argv: str, from_tty: bool) -> None: @@ -10688,7 +12342,9 @@ def invoke(self, argv: str, from_tty: bool) -> None: args = argv.split() if "--list" in args or "-l" in args: - subprocess.run(["xdg-open", f"https://github.com/hugsy/gef-extras/{self.branch}/"]) + subprocess.run( + ["xdg-open", f"https://github.com/hugsy/gef-extras/{self.branch}/"] + ) return self.dirpath = gef.config["gef.tempdir"].expanduser().absolute() @@ -10702,7 +12358,6 @@ def invoke(self, argv: str, from_tty: bool) -> None: warn(f"Failed to install '{script}', skipping...") return - def __install_extras_script(self, script: str) -> bool: fpath = self.dirpath / f"{script}.py" if not fpath.exists(): @@ -10721,7 +12376,9 @@ def __install_extras_script(self, script: str) -> bool: gdb.execute(f"source {fpath}") new_command_set = set(gef.gdb.commands) new_commands = [f"`{c[0]}`" for c in (new_command_set - old_command_set)] - ok(f"Installed file '{fpath}', new command(s) available: {', '.join(new_commands)}") + ok( + f"Installed file '{fpath}', new command(s) available: {', '.join(new_commands)}" + ) return True @@ -10729,10 +12386,13 @@ def __install_extras_script(self, script: str) -> bool: # GEF internal classes # + def __gef_prompt__(current_prompt: Callable[[Callable], str]) -> str: """GEF custom prompt function.""" - if gef.config["gef.readline_compat"] is True: return GEF_PROMPT - if gef.config["gef.disable_color"] is True: return GEF_PROMPT + if gef.config["gef.readline_compat"] is True: + return GEF_PROMPT + if gef.config["gef.disable_color"] is True: + return GEF_PROMPT prompt = gef.session.remote.mode.prompt_string() if gef.session.remote else "" prompt += "(core) " if is_target_coredump() else "" prompt += GEF_PROMPT_ON if is_alive() else GEF_PROMPT_OFF @@ -10756,6 +12416,7 @@ def reset_caches(self) -> None: class GefMemoryManager(GefManager): """Class that manages memory access for gef.""" + def __init__(self) -> None: self.reset_caches() return @@ -10765,7 +12426,9 @@ def reset_caches(self) -> None: self.__maps: list[Section] | None = None return - def write(self, address: int, buffer: ByteString, length: int | None = None) -> None: + def write( + self, address: int, buffer: ByteString, length: int | None = None + ) -> None: """Write `buffer` at address `address`.""" length = length or len(buffer) gdb.selected_inferior().write_memory(address, buffer, length) @@ -10781,13 +12444,15 @@ def read_integer(self, addr: int) -> int: unpack = u32 if sz == 4 else u64 return unpack(mem) - def read_cstring(self, - address: int, - max_length: int = GEF_MAX_STRING_LENGTH, - encoding: str | None = None) -> str: + def read_cstring( + self, + address: int, + max_length: int = GEF_MAX_STRING_LENGTH, + encoding: str | None = None, + ) -> str: """Return a C-string read from memory.""" encoding = encoding or "unicode-escape" - length = min(address | (DEFAULT_PAGE_SIZE-1), max_length+1) + length = min(address | (DEFAULT_PAGE_SIZE - 1), max_length + 1) try: res_bytes = self.read(address, length) @@ -10876,13 +12541,17 @@ def __parse_maps(self) -> list[Section] | None: warn("Could not determine memory layout accurately, using mock layout") fname = gef.binary.path if is_32bit(): - page_start, page_end = 0x00000000, 0xffffffff + page_start, page_end = 0x00000000, 0xFFFFFFFF else: - page_start, page_end = 0x0000000000000000, 0xffffffffffffffff - return [Section(page_start=page_start, + page_start, page_end = 0x0000000000000000, 0xFFFFFFFFFFFFFFFF + return [ + Section( + page_start=page_start, page_end=page_end, - permission = Permission.ALL, - path = str(fname)),] + permission=Permission.ALL, + path=str(fname), + ), + ] except Exception: pass @@ -10894,7 +12563,9 @@ def parse_procfs_maps() -> Generator[Section, None, None]: procfs_mapfile = gef.session.maps if not procfs_mapfile: is_remote = gef.session.remote is not None - raise FileNotFoundError(f"Missing {'remote ' if is_remote else ''}procfs map file") + raise FileNotFoundError( + f"Missing {'remote ' if is_remote else ''}procfs map file" + ) with procfs_mapfile.open("r") as fd: for line in fd: @@ -10912,12 +12583,14 @@ def parse_procfs_maps() -> Generator[Section, None, None]: off = int(off, 16) perm = Permission.from_process_maps(perm) inode = int(inode) - yield Section(page_start=addr_start, - page_end=addr_end, - offset=off, - permission=perm, - inode=inode, - path=pathname) + yield Section( + page_start=addr_start, + page_end=addr_end, + offset=off, + permission=perm, + inode=inode, + path=pathname, + ) return @staticmethod @@ -10926,7 +12599,7 @@ def parse_gdb_info_proc_maps() -> Generator[Section, None, None]: if GDB_VERSION < (11, 0): raise AttributeError("Disregarding old format") - output = (gdb.execute("info proc mappings", to_string=True) or "") + output = gdb.execute("info proc mappings", to_string=True) or "" if not output: raise AttributeError @@ -10996,10 +12669,7 @@ def parse_monitor_info_mem() -> Generator[Section, None, None]: continue perm = Permission.from_monitor_info_mem(perms) - yield Section(page_start=start, - page_end=end, - offset=off, - permission=perm) + yield Section(page_start=start, page_end=end, offset=off, permission=perm) @staticmethod def parse_gdb_maintenance_info_sections() -> Generator[Section, None, None]: @@ -11051,9 +12721,9 @@ def parse_info_mem(): perm = Permission.from_info_mem("r") else: perm = Permission.from_info_mem("rw") - yield Section(page_start=int(start, 0), - page_end=int(end, 0), - permission=perm) + yield Section( + page_start=int(start, 0), page_end=int(end, 0), permission=perm + ) def append(self, section: Section): if not self.maps: @@ -11074,6 +12744,7 @@ def __iadd__(self, section: Section): class GefHeapManager(GefManager): """Class managing session heap.""" + def __init__(self) -> None: self.reset_caches() return @@ -11138,9 +12809,11 @@ def find_main_arena_addr() -> int: struct_size = ctypes.sizeof(GlibcArena.malloc_state_t()) if is_x86(): - addr = align_address_to_size(malloc_hook_addr + gef.arch.ptrsize, 0x20) + addr = align_address_to_size( + malloc_hook_addr + gef.arch.ptrsize, 0x20 + ) elif is_arch(Elf.Abi.AARCH64): - addr = malloc_hook_addr - gef.arch.ptrsize*2 - struct_size + addr = malloc_hook_addr - gef.arch.ptrsize * 2 - struct_size elif is_arch(Elf.Abi.ARM): addr = malloc_hook_addr - gef.arch.ptrsize - struct_size else: @@ -11158,12 +12831,18 @@ def find_main_arena_addr() -> int: alignment = 0x8 try: dbg("Trying to bruteforce main_arena address") + # setup search_range for `main_arena` to `.data` of glibc def search_filter(zone: Zone) -> bool: - return "libc" in pathlib.Path(zone.filename).name and zone.name == ".data" + return ( + "libc" in pathlib.Path(zone.filename).name + and zone.name == ".data" + ) for dotdata in list(filter(search_filter, get_info_files())): - search_range = range(dotdata.zone_start, dotdata.zone_end, alignment) + search_range = range( + dotdata.zone_start, dotdata.zone_end, alignment + ) # find first possible candidate for addr in search_range: if GlibcArena.verify(addr): @@ -11236,15 +12915,20 @@ def malloc_alignment(self) -> int: return 2 * gef.arch.ptrsize def csize2tidx(self, size: int) -> int: - return abs((size - self.min_chunk_size + self.malloc_alignment - 1)) // self.malloc_alignment + return ( + abs((size - self.min_chunk_size + self.malloc_alignment - 1)) + // self.malloc_alignment + ) def tidx2size(self, idx: int) -> int: return idx * self.malloc_alignment + self.min_chunk_size def malloc_align_address(self, address: int) -> int: """Align addresses according to glibc's MALLOC_ALIGNMENT. See also Issue #689 on Github""" + def ceil(n: float) -> int: return int(-1 * n // 1 * -1) + malloc_alignment = self.malloc_alignment return malloc_alignment * ceil((address / malloc_alignment)) @@ -11252,7 +12936,13 @@ def ceil(n: float) -> int: class GefSetting: """Basic class for storing gef settings as objects""" - def __init__(self, value: Any, cls: type | None = None, description: str | None = None, hooks: dict[str, list[Callable]] | None = None) -> None: + def __init__( + self, + value: Any, + cls: type | None = None, + description: str | None = None, + hooks: dict[str, list[Callable]] | None = None, + ) -> None: self.value = value self.type = cls or type(value) self.description = description or "" @@ -11265,9 +12955,11 @@ def __init__(self, value: Any, cls: type | None = None, description: str | None return def __str__(self) -> str: - return f"Setting(type={self.type.__name__}, value='{self.value}', desc='{self.description[:10]}...', " \ - f"read_hooks={len(self.hooks['on_read'])}, write_hooks={len(self.hooks['on_write'])}, "\ - f"changed_hooks={len(self.hooks['on_changed'])})" + return ( + f"Setting(type={self.type.__name__}, value='{self.value}', desc='{self.description[:10]}...', " + f"read_hooks={len(self.hooks['on_read'])}, write_hooks={len(self.hooks['on_write'])}, " + f"changed_hooks={len(self.hooks['on_changed'])})" + ) def add_hook(self, access: str, funcs: list[Callable]): if access not in ("on_read", "on_write", "on_changed"): @@ -11298,8 +12990,9 @@ class GefSettingsManager(dict): GefSettings acts as a dict where the global settings are stored and can be read, written or deleted as any other dict. For instance, to read a specific command setting: `gef.config[mycommand.mysetting]` """ + def __getitem__(self, name: str) -> Any: - setting : GefSetting = super().__getitem__(name) + setting: GefSetting = super().__getitem__(name) self.__invoke_read_hooks(setting) return setting.value @@ -11308,18 +13001,24 @@ def __setitem__(self, name: str, value: Any) -> None: if super().__contains__(name): # if so, update its value directly setting = super().__getitem__(name) - if not isinstance(setting, GefSetting): raise TypeError + if not isinstance(setting, GefSetting): + raise TypeError new_value = setting.type(value) - dbg(f"in __invoke_changed_hooks(\"{name}\"), setting.value={setting.value} -> new_value={new_value}, changing={bool(setting.value != new_value)}") + dbg( + f'in __invoke_changed_hooks("{name}"), setting.value={setting.value} -> new_value={new_value}, changing={bool(setting.value != new_value)}' + ) self.__invoke_changed_hooks(setting, new_value) self.__invoke_write_hooks(setting, new_value) setting.value = new_value return # if not, assert `value` is a GefSetting, then insert it - if not isinstance(value, GefSetting): raise TypeError("Invalid argument") - if not value.type: raise TypeError("Invalid type") - if not value.description: raise AttributeError("Invalid description") + if not isinstance(value, GefSetting): + raise TypeError("Invalid argument") + if not value.type: + raise TypeError("Invalid type") + if not value.description: + raise AttributeError("Invalid description") setting = value value = setting.value self.__invoke_write_hooks(setting, value) @@ -11350,7 +13049,8 @@ def __invoke_write_hooks(self, setting: GefSetting, new_value: Any) -> None: class GefSessionManager(GefManager): - """Class managing the runtime properties of GEF. """ + """Class managing the runtime properties of GEF.""" + def __init__(self) -> None: self.reset_caches() self.remote: "GefRemoteSessionManager | None" = None @@ -11365,7 +13065,7 @@ def __init__(self) -> None: self.pie_counter: int = 1 self.aliases: list[GefAlias] = [] self.modules: list[FileFormat] = [] - self.constants = {} # a dict for runtime constants (like 3rd party file paths) + self.constants = {} # a dict for runtime constants (like 3rd party file paths) for constant in ("python3", "readelf", "nm", "file", "ps"): self.constants[constant] = which(constant) return @@ -11422,7 +13122,11 @@ def os(self) -> str: def pid(self) -> int: """Return the PID of the target process.""" if not self._pid: - pid = gdb.selected_inferior().pid if not self.qemu_mode else gdb.selected_thread().ptid[1] + pid = ( + gdb.selected_inferior().pid + if not self.qemu_mode + else gdb.selected_thread().ptid[1] + ) if not pid: raise RuntimeError("cannot retrieve PID for target process") self._pid = pid @@ -11524,16 +13228,16 @@ def __repr__(self): def prompt_string(self) -> str: match self: case ( - GefRemoteSessionManager.RemoteMode.QEMU | - GefRemoteSessionManager.RemoteMode.QEMU_USER | - GefRemoteSessionManager.RemoteMode.QEMU_SYSTEM + GefRemoteSessionManager.RemoteMode.QEMU + | GefRemoteSessionManager.RemoteMode.QEMU_USER + | GefRemoteSessionManager.RemoteMode.QEMU_SYSTEM ): return Color.boldify("(qemu) ") case GefRemoteSessionManager.RemoteMode.RR: return Color.boldify("(rr) ") case ( - GefRemoteSessionManager.RemoteMode.GDBSERVER | - GefRemoteSessionManager.RemoteMode.GDBSERVER_MULTI + GefRemoteSessionManager.RemoteMode.GDBSERVER + | GefRemoteSessionManager.RemoteMode.GDBSERVER_MULTI ): return Color.boldify("(remote) ") raise AttributeError("Unknown value") @@ -11667,10 +13371,11 @@ def __setup_rr(self) -> bool: class GefUiManager(GefManager): """Class managing UI settings.""" + def __init__(self) -> None: - self.redirect_fd : TextIOWrapper | None = None + self.redirect_fd: TextIOWrapper | None = None self.context_hidden = False - self.stream_buffer : StringIO | None = None + self.stream_buffer: StringIO | None = None self.highlight_table: dict[str, str] = {} self.watches: dict[int, tuple[int, str]] = {} self.context_messages: list[tuple[str, str]] = [] @@ -11679,11 +13384,12 @@ def __init__(self) -> None: class GefLibcManager(GefManager): """Class managing everything libc-related (except heap).""" + PATTERN_LIBC_VERSION_MEMORY = re.compile(rb"glibc (\d+)\.(\d+)") PATTERN_LIBC_VERSION_FILENAME = re.compile(r"libc6?[-_](\d+)\.(\d+)\.so") def __init__(self) -> None: - self._version : tuple[int, int] | None = None + self._version: tuple[int, int] | None = None self._patch: int | None = None self._release: str | None = None return @@ -11712,10 +13418,16 @@ def version(self) -> tuple[int, int] | None: @lru_cache() def find_libc_version() -> tuple[int, int]: """Attempt to determine the libc version. This operation can be long.""" - libc_sections = (m for m in gef.memory.maps if "libc" in m.path and m.permission & Permission.READ) + libc_sections = ( + m + for m in gef.memory.maps + if "libc" in m.path and m.permission & Permission.READ + ) for section in libc_sections: # Try to determine from the filepath - match = re.search(GefLibcManager.PATTERN_LIBC_VERSION_FILENAME, section.path) + match = re.search( + GefLibcManager.PATTERN_LIBC_VERSION_FILENAME, section.path + ) if match: return int(match.group(1)), int(match.group(2)) @@ -11734,20 +13446,23 @@ def find_libc_version() -> tuple[int, int]: class Gef: """The GEF root class, which serves as a entrypoint for all the debugging session attributes (architecture, memory, settings, etc.).""" + binary: FileFormat | None arch: Architecture - config : GefSettingsManager + config: GefSettingsManager ui: GefUiManager libc: GefLibcManager - memory : GefMemoryManager - heap : GefHeapManager - session : GefSessionManager + memory: GefMemoryManager + heap: GefHeapManager + session: GefSessionManager gdb: GefCommand temp: dict[str, Any] def __init__(self) -> None: self.binary: FileFormat | None = None - self.arch: Architecture = GenericArchitecture() # see PR #516, will be reset by `new_objfile_handler` + self.arch: Architecture = ( + GenericArchitecture() + ) # see PR #516, will be reset by `new_objfile_handler` self.arch_reason: str = "This is the default architecture" self.config = GefSettingsManager() self.ui = GefUiManager() @@ -11810,14 +13525,20 @@ def target_remote_posthook(): if __name__ == "__main__": if sys.version_info[0] == 2: - err("GEF has dropped Python2 support for GDB when it reached EOL on 2020/01/01.") - err("If you require GEF for GDB+Python2, use https://github.com/hugsy/gef-legacy.") + err( + "GEF has dropped Python2 support for GDB when it reached EOL on 2020/01/01." + ) + err( + "If you require GEF for GDB+Python2, use https://github.com/hugsy/gef-legacy." + ) exit(1) if GDB_VERSION < GDB_MIN_VERSION or PYTHON_VERSION < PYTHON_MIN_VERSION: - err("You're using an old version of GDB. GEF will not work correctly. " + err( + "You're using an old version of GDB. GEF will not work correctly. " f"Consider updating to GDB {'.'.join(map(str, GDB_MIN_VERSION))} or higher " - f"(with Python {'.'.join(map(str, PYTHON_MIN_VERSION))} or higher).") + f"(with Python {'.'.join(map(str, PYTHON_MIN_VERSION))} or higher)." + ) exit(1) # setup config @@ -11897,7 +13618,11 @@ def target_remote_posthook(): gdb.execute(hook.format("extended-remote", "post")) # restore saved breakpoints (if any) - bkp_fpath = pathlib.Path(gef.config["gef.autosave_breakpoints_file"]).expanduser().absolute() + bkp_fpath = ( + pathlib.Path(gef.config["gef.autosave_breakpoints_file"]) + .expanduser() + .absolute() + ) if bkp_fpath.is_file(): gdb.execute(f"source {bkp_fpath}") From 02a5a69467dad2c0e09a06cfe5bf96e99b304d1a Mon Sep 17 00:00:00 2001 From: hugsy Date: Tue, 17 Mar 2026 18:16:20 -0700 Subject: [PATCH 47/53] Drop test from known failure on gdb/fedora --- .gitignore | 1 + .../1131_target_remote_registers.py | 12 +++- tests/utils.py | 61 +++++++++++++------ 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index f5a4333a6..2cc5a59d5 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ htmlcov site/ untracked/ .zed/ +Dockerfile diff --git a/tests/regressions/1131_target_remote_registers.py b/tests/regressions/1131_target_remote_registers.py index add59edbe..fecf78888 100644 --- a/tests/regressions/1131_target_remote_registers.py +++ b/tests/regressions/1131_target_remote_registers.py @@ -5,12 +5,22 @@ import pytest from tests.base import RemoteGefUnitTestGeneric -from tests.utils import get_random_port, qemuuser_session +from tests.utils import OS, ARCH, get_random_port, qemuuser_session URL = "https://github.com/user-attachments/files/16913262/repr.zip" +# +# gdb-multiarch acts weird on arm64, and simply doesn't exist on fedora (ubuntu only), so we skip this test on arm64 on CI. +# It cant either be run locally on arm64 if gdb-multiarch is available. We might want to revisit later, for now just skip. +# + + @pytest.mark.slow +@pytest.mark.skipif( + OS != "ubuntu" or ARCH != "x86_64", + reason=f"Skipped for {OS} on CI", +) class MissingTargetRemoteRegisters(RemoteGefUnitTestGeneric): """@ref https://github.com/hugsy/gef/pull/1131""" diff --git a/tests/utils.py b/tests/utils.py index ac75779cc..fb1cb19b2 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -26,8 +26,22 @@ def which(program: str) -> pathlib.Path: return pathlib.Path(fpath) +def os_release() -> str: + return ( + [ + x + for x in pathlib.Path("/etc/os-release").read_text().splitlines() + if x.startswith("ID=") + ][0] + .strip() + .replace("ID=", "") + .replace('"', "") + ) + + TMPDIR = pathlib.Path(tempfile.gettempdir()) ARCH = (os.getenv("GEF_CI_ARCH") or platform.machine()).lower() +OS = (os.getenv("GEF_CI_OS") or os_release()).lower() BIN_SH = pathlib.Path("/bin/sh") CI_VALID_ARCHITECTURES_32B = ("i686", "armv7l") CI_VALID_ARCHITECTURES_64B = ("x86_64", "aarch64", "mips64el", "ppc64le", "riscv64") @@ -58,31 +72,32 @@ def which(program: str) -> pathlib.Path: IN_GITHUB_ACTIONS = os.getenv("GITHUB_ACTIONS") == "true" + class Color(enum.Enum): """Used to colorify terminal output.""" - NORMAL = "\001\033[0m\002" - GRAY = "\001\033[1;38;5;240m\002" - LIGHT_GRAY = "\001\033[0;37m\002" - RED = "\001\033[31m\002" - GREEN = "\001\033[32m\002" - YELLOW = "\001\033[33m\002" - BLUE = "\001\033[34m\002" - PINK = "\001\033[35m\002" - CYAN = "\001\033[36m\002" - BOLD = "\001\033[1m\002" - UNDERLINE = "\001\033[4m\002" - UNDERLINE_OFF = "\001\033[24m\002" - HIGHLIGHT = "\001\033[3m\002" - HIGHLIGHT_OFF = "\001\033[23m\002" - BLINK = "\001\033[5m\002" - BLINK_OFF = "\001\033[25m\002" + NORMAL = "\001\033[0m\002" + GRAY = "\001\033[1;38;5;240m\002" + LIGHT_GRAY = "\001\033[0;37m\002" + RED = "\001\033[31m\002" + GREEN = "\001\033[32m\002" + YELLOW = "\001\033[33m\002" + BLUE = "\001\033[34m\002" + PINK = "\001\033[35m\002" + CYAN = "\001\033[36m\002" + BOLD = "\001\033[1m\002" + UNDERLINE = "\001\033[4m\002" + UNDERLINE_OFF = "\001\033[24m\002" + HIGHLIGHT = "\001\033[3m\002" + HIGHLIGHT_OFF = "\001\033[23m\002" + BLINK = "\001\033[5m\002" + BLINK_OFF = "\001\033[25m\002" def is_glibc_ge(major, minor): ver = platform.libc_ver() - if ver[0] == 'glibc': - (glibc_major, glibc_minor, *glibc_patch) = list(map(int, ver[1].split('.'))) + if ver[0] == "glibc": + (glibc_major, glibc_minor, *glibc_patch) = list(map(int, ver[1].split("."))) return (glibc_major, glibc_minor) >= (major, minor) return False @@ -122,6 +137,7 @@ def start_gdbserver( logging.debug(f"Starting {cmd}") return subprocess.Popen(cmd) + def start_gdbserver_multi( host: str = GDBSERVER_DEFAULT_HOST, port: int = GDBSERVER_DEFAULT_PORT, @@ -130,6 +146,7 @@ def start_gdbserver_multi( logging.debug(f"Starting {cmd}") return subprocess.Popen(cmd) + def stop_gdbserver(gdbserver: subprocess.Popen) -> None: """Stop the gdbserver and wait until it is terminated if it was still running. Needed to make the used port available again. @@ -155,6 +172,7 @@ def gdbserver_session( finally: stop_gdbserver(sess) + @contextlib.contextmanager def gdbserver_multi_session( port: int = GDBSERVER_DEFAULT_PORT, @@ -167,11 +185,12 @@ def gdbserver_multi_session( finally: stop_gdbserver(sess) + def start_qemuuser( exe: Union[str, pathlib.Path] = debug_target("default"), port: int = GDBSERVER_DEFAULT_PORT, qemu_exe: pathlib.Path = QEMU_USER_X64_BINARY, - args: list[str] | None = None + args: list[str] | None = None, ) -> subprocess.Popen: cmd = [qemu_exe, "-g", str(port)] if args: @@ -349,10 +368,12 @@ def p64(x: int) -> bytes: __available_ports = list() + + def get_random_port() -> int: global __available_ports if len(__available_ports) < 2: - __available_ports = list( range(1024, 65535) ) + __available_ports = list(range(1024, 65535)) idx = random.choice(range(len(__available_ports))) port = __available_ports[idx] __available_ports.pop(idx) From 487262cc5b369b944b4e9be9b2f802218281fdfd Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Thu, 26 Mar 2026 09:36:07 -0700 Subject: [PATCH 48/53] Update docs/commands/gef-remote.md Co-authored-by: Frank Dana --- docs/commands/gef-remote.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/commands/gef-remote.md b/docs/commands/gef-remote.md index 058ca0556..1e1fd5d0e 100644 --- a/docs/commands/gef-remote.md +++ b/docs/commands/gef-remote.md @@ -1,7 +1,8 @@ ## Command `gef-remote` -📝 **IMPORTANT NOTE**: `gef-remote` is deprecated since 2026.04 in favor of `target remote`. The -command will be removed in a future release. Do not rely on it. +> [!IMPORTANT] +> `gef-remote` is deprecated since 2026.04 in favor of `target remote`. +> The command will be removed in a future release. Do not rely on it. `gef-remote` can function in 2 ways: From d590a91f94ce80094c7785b552d35b893814b4ba Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 3 May 2026 13:40:42 -0700 Subject: [PATCH 49/53] Fixed merge fix --- gef.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/gef.py b/gef.py index 0d8ef8acc..33cb0ef69 100644 --- a/gef.py +++ b/gef.py @@ -301,7 +301,6 @@ class InitializationError(Exception): pass - class ObsoleteException(Exception): pass @@ -4103,6 +4102,14 @@ def is_running_in_qemu_system() -> bool: return 'received: ""' in response +def is_running_in_gdbserver() -> bool: + return is_target_remote_or_extended() and not is_running_in_qemu() + + +def is_running_in_rr() -> bool: + return is_running_in_gdbserver() and os.environ.get("GDB_UNDER_RR", None) == "1" + + def is_target_coredump() -> bool: global gef if gef.session.coredump_mode is not None: From 88fa26242c442ebfc5d34bb756fc2e0dbf1235b1 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 3 May 2026 14:14:38 -0700 Subject: [PATCH 50/53] =?UTF-8?q?qemu-user=20test=20still=20don=C2=B4t=20w?= =?UTF-8?q?ork=20on=20ARM64...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/api/gef_remote.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/api/gef_remote.py b/tests/api/gef_remote.py index e943dd51a..f71a906c3 100644 --- a/tests/api/gef_remote.py +++ b/tests/api/gef_remote.py @@ -2,13 +2,25 @@ `target remote/extended-remote` test module. """ +import pytest from tests.base import RemoteGefUnitTestGeneric -from tests.utils import debug_target, gdbserver_session, gdbserver_multi_session, get_random_port, qemuuser_session - - +from tests.utils import ( + ARCH, + OS, + debug_target, + gdbserver_multi_session, + gdbserver_session, + get_random_port, + qemuuser_session, +) + + +@pytest.mark.skipif( + OS != "ubuntu" or ARCH != "x86_64", + reason=f"Skipped for {OS} on CI", +) class GefRemoteApi(RemoteGefUnitTestGeneric): - def setUp(self) -> None: self._target = debug_target("default") return super().setUp() From 17caf8666d09b913e9dbd2b63279b0ec403129bf Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 3 May 2026 14:18:30 -0700 Subject: [PATCH 51/53] Formatting --- tests/commands/gef_remote.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/commands/gef_remote.py b/tests/commands/gef_remote.py index 2f8d5d299..561e638a6 100644 --- a/tests/commands/gef_remote.py +++ b/tests/commands/gef_remote.py @@ -7,11 +7,11 @@ from tests.base import RemoteGefUnitTestGeneric from tests.utils import ( ARCH, + GDBSERVER_DEFAULT_HOST, debug_target, gdbserver_session, get_random_port, qemuuser_session, - GDBSERVER_DEFAULT_HOST, ) @@ -31,7 +31,9 @@ def test_cmd_gef_remote_gdbserver(self): with gdbserver_session(port=port): gdb.execute(f"target remote {GDBSERVER_DEFAULT_HOST}:{port}") res: str = str(gef.session.remote) - assert res.startswith(f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/") + assert res.startswith( + f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/" + ) assert res.endswith(f"mode={gdbserver_mode}, pid={gef.session.pid})") @pytest.mark.slow @@ -46,7 +48,9 @@ def test_cmd_gef_remote_qemu_user(self): cmd = f"target remote {GDBSERVER_DEFAULT_HOST}:{port}" gdb.execute(cmd) res = str(gef.session.remote) - assert res.startswith(f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/") + assert res.startswith( + f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/" + ) assert res.endswith(f"mode={qemu_mode})") def test_cmd_target_remote(self): From 28dc775fb488c14fce0c78ea2ec8e5d4cb0a8967 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 3 May 2026 14:21:18 -0700 Subject: [PATCH 52/53] More formatting --- tests/api/gef_memory.py | 2 -- tests/api/gef_session.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/tests/api/gef_memory.py b/tests/api/gef_memory.py index ea292395d..e15317dfa 100644 --- a/tests/api/gef_memory.py +++ b/tests/api/gef_memory.py @@ -3,7 +3,6 @@ """ import pathlib -import random import pytest from tests.base import RemoteGefUnitTestGeneric @@ -15,7 +14,6 @@ gdbserver_session, get_random_port, qemuuser_session, - GDBSERVER_DEFAULT_HOST, ) diff --git a/tests/api/gef_session.py b/tests/api/gef_session.py index 5ebc8a0c1..87d090c7c 100644 --- a/tests/api/gef_session.py +++ b/tests/api/gef_session.py @@ -4,7 +4,6 @@ import os import pathlib -import random import re import pytest @@ -16,7 +15,6 @@ gdbserver_session, get_random_port, qemuuser_session, - GDBSERVER_DEFAULT_HOST, ) From 525816f9ffe770d97b62f8dd7595174c16985b24 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Mon, 4 May 2026 08:36:28 -0700 Subject: [PATCH 53/53] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/coverage.yml | 2 +- LICENSE | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 6616157f7..45643851f 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -22,7 +22,7 @@ jobs: run: | export NEEDRESTART_MODE=n sudo apt-get update -qq - sudo apt-get install -qq -y curl wget gdb-multiarch python3-dev python3-pip python3-wheel python3-setuptools git cmake gcc g++ pkg-config libglib2.0-dev gdbserver qemu-user curl + sudo apt-get install -qq -y curl wget gdb-multiarch python3-dev python3-pip python3-wheel python3-setuptools git cmake gcc g++ pkg-config libglib2.0-dev gdbserver qemu-user sudo apt-get install -y python3-full - name: Run coverage diff --git a/LICENSE b/LICENSE index 2409e350f..fdee1e53e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013-65 crazy rabbidz +Copyright (c) 2013-2026 crazy rabbidz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal