####################################################################################### # GEF - Multi-Architecture GDB Enhanced Features for Exploiters & Reverse-Engineers # # by @_hugsy_ ####################################################################################### # # GEF is a kick-ass set of commands for X86, ARM, MIPS, PowerPC and SPARC to # make GDB cool again for exploit dev. It is aimed to be used mostly by exploit # devs and reversers, to provides additional features to GDB using the Python # API to assist during the process of dynamic analysis. # # GEF fully relies on GDB API and other Linux-specific sources of information # (such as /proc/). As a consequence, some of the features might not work # on custom or hardened systems such as GrSec. # # Since January 2020, GEF solely support GDB compiled with Python3 and was tested on # * x86-32 & x86-64 # * arm v5,v6,v7 # * aarch64 (armv8) # * mips & mips64 # * powerpc & powerpc64 # * sparc & sparc64(v9) # # For GEF with Python2 (only) support was moved to the GEF-Legacy # (https://github.com/hugsy/gef-legacy) # # To start: in gdb, type `source /path/to/gef.py` # ####################################################################################### # # gef is distributed under the MIT License (MIT) # Copyright (c) 2013-2022 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 # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import abc import argparse import binascii import codecs import collections import configparser import ctypes import enum import functools import hashlib import importlib import inspect import itertools import json import os import pathlib import platform import re import shutil import site import socket import string import struct import subprocess import sys import tempfile import time import traceback import warnings import xmlrpc.client as xmlrpclib from functools import lru_cache from io import StringIO, TextIOWrapper from types import ModuleType from typing import (Any, ByteString, Callable, Dict, Generator, IO, Iterator, List, NoReturn, Optional, Sequence, Tuple, Type, Union) from urllib.request import urlopen def http_get(url: str) -> Optional[bytes]: """Basic HTTP wrapper for GET request. Return the body of the page if HTTP code is OK, otherwise return None.""" try: http = urlopen(url) if http.getcode() != 200: return None return http.read() except Exception: return None def update_gef(argv: List[str]) -> int: """Try to update `gef` to the latest version pushed on GitHub master branch. Return 0 on success, 1 on failure. """ ver = "dev" if "--dev" in argv[2:] else "master" latest_gef_data = http_get(f"https://raw.githubusercontent.com/hugsy/gef/{ver}/scripts/gef.sh") if latest_gef_data is None: print("[-] Failed to get remote gef") return 1 fd, fname = tempfile.mkstemp(suffix=".sh") os.write(fd, latest_gef_data) os.close(fd) retcode = subprocess.run(["bash", fname, ver], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode os.unlink(fname) return retcode try: import gdb # pylint: disable= except ImportError: # if out of gdb, the only action allowed is to update gef.py if len(sys.argv) == 2 and sys.argv[1].lower() in ("--update", "--upgrade"): sys.exit(update_gef(sys.argv)) print("[-] gef cannot run as standalone") sys.exit(0) GDB_MIN_VERSION = (8, 0) GDB_VERSION = tuple(map(int, re.search(r"(\d+)[^\d]+(\d+)", gdb.VERSION).groups())) PYTHON_MIN_VERSION = (3, 6) PYTHON_VERSION = sys.version_info[0:2] 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 = os.path.join(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 = " \u2190 " RIGHT_ARROW = " \u2192 " DOWN_ARROW = "\u21b3" HORIZONTAL_LINE = "\u2500" VERTICAL_LINE = "\u2502" CROSS = "\u2718 " TICK = "\u2713 " BP_GLYPH = "\u25cf" GEF_PROMPT = "gef\u27a4 " 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" PATTERN_LIBC_VERSION = re.compile(rb"glibc (\d+)\.(\d+)") gef : "Gef" = None __registered_commands__ : List[Type["GenericCommand"]] = [] __registered_functions__ : List[Type["GenericFunction"]] = [] __registered_architectures__ : Dict[Union["Elf.Abi", str], Type["Architecture"]] = {} def reset_all_caches() -> None: """Free all caches. If an object is cached, it will have a callable attribute `cache_clear` which will be invoked to purge the function cache.""" for mod in dir(sys.modules["__main__"]): obj = getattr(sys.modules["__main__"], mod) if hasattr(obj, "cache_clear"): obj.cache_clear() gef.reset_caches() return def reset() -> None: global gef arch = None if gef: reset_all_caches() arch = gef.arch del gef gef = Gef() gef.setup() if arch: gef.arch = arch return def highlight_text(text: str) -> str: """ Highlight text using `gef.ui.highlight_table` { match -> color } settings. If RegEx is enabled it will create a match group around all items in the `gef.ui.highlight_table` and wrap the specified color in the `gef.ui.highlight_table` around those matches. If RegEx is disabled, split by ANSI codes and 'colorify' each match found within the specified string. """ global gef if not gef.ui.highlight_table: return text if gef.config["highlight.regex"]: for match, color in gef.ui.highlight_table.items(): text = re.sub("(" + match + ")", Color.colorify("\\1", color), text) return text ansiSplit = re.split(ANSI_SPLIT_RE, text) for match, color in gef.ui.highlight_table.items(): for index, val in enumerate(ansiSplit): found = val.find(match) if found > -1: ansiSplit[index] = val.replace(match, Color.colorify(match, color)) break text = "".join(ansiSplit) ansiSplit = re.split(ANSI_SPLIT_RE, text) return "".join(ansiSplit) def gef_print(*args: str, end="\n", sep=" ", **kwargs: Any) -> None: """Wrapper around print(), using string buffering feature.""" parts = [highlight_text(a) for a in args] if gef.ui.stream_buffer and not is_debug(): gef.ui.stream_buffer.write(sep.join(parts) + end) return print(*parts, sep=sep, end=end, **kwargs) return def bufferize(f: Callable) -> Callable: """Store the content to be printed for a function in memory, and flush it on function exit.""" @functools.wraps(f) def wrapper(*args: Any, **kwargs: Any) -> Any: global gef if gef.ui.stream_buffer: return f(*args, **kwargs) gef.ui.stream_buffer = StringIO() try: rv = f(*args, **kwargs) finally: redirect = gef.config["context.redirect"] if redirect.startswith("/dev/pts/"): if not gef.ui.redirect_fd: # if the FD has never been open, open it fd = open(redirect, "wt") gef.ui.redirect_fd = fd elif redirect != gef.ui.redirect_fd.name: # if the user has changed the redirect setting during runtime, update the state gef.ui.redirect_fd.close() fd = open(redirect, "wt") gef.ui.redirect_fd = fd else: # otherwise, keep using it fd = gef.ui.redirect_fd else: fd = sys.stdout gef.ui.redirect_fd = None if gef.ui.redirect_fd and fd.closed: # if the tty was closed, revert back to stdout fd = sys.stdout gef.ui.redirect_fd = None gef.config["context.redirect"] = "" fd.write(gef.ui.stream_buffer.getvalue()) fd.flush() gef.ui.stream_buffer = None return rv return wrapper # # Helpers # def p8(x: int, s: bool = False) -> bytes: """Pack one byte respecting the current architecture endianness.""" return struct.pack(f"{gef.arch.endianness}B", x) if not s else struct.pack(f"{gef.arch.endianness}b", x) def p16(x: int, s: bool = False) -> bytes: """Pack one word respecting the current architecture endianness.""" return struct.pack(f"{gef.arch.endianness}H", x) if not s else struct.pack(f"{gef.arch.endianness}h", x) def p32(x: int, s: bool = False) -> bytes: """Pack one dword respecting the current architecture endianness.""" return struct.pack(f"{gef.arch.endianness}I", x) if not s else struct.pack(f"{gef.arch.endianness}i", x) def p64(x: int, s: bool = False) -> bytes: """Pack one qword respecting the current architecture endianness.""" return struct.pack(f"{gef.arch.endianness}Q", x) if not s else struct.pack(f"{gef.arch.endianness}q", x) def u8(x: bytes, s: bool = False) -> int: """Unpack one byte respecting the current architecture endianness.""" return struct.unpack(f"{gef.arch.endianness}B", x)[0] if not s else struct.unpack(f"{gef.arch.endianness}b", x)[0] def u16(x: bytes, s: bool = False) -> int: """Unpack one word respecting the current architecture endianness.""" return struct.unpack(f"{gef.arch.endianness}H", x)[0] if not s else struct.unpack(f"{gef.arch.endianness}h", x)[0] def u32(x: bytes, s: bool = False) -> int: """Unpack one dword respecting the current architecture endianness.""" return struct.unpack(f"{gef.arch.endianness}I", x)[0] if not s else struct.unpack(f"{gef.arch.endianness}i", x)[0] def u64(x: bytes, s: bool = False) -> int: """Unpack one qword respecting the current architecture endianness.""" return struct.unpack(f"{gef.arch.endianness}Q", x)[0] if not s else struct.unpack(f"{gef.arch.endianness}q", x)[0] def is_ascii_string(address: int) -> bool: """Helper function to determine if the buffer pointed by `address` is an ASCII string (in GDB)""" try: return gef.memory.read_ascii_string(address) is not None except Exception: return False def is_alive() -> bool: """Check if GDB is running.""" try: return gdb.selected_inferior().pid > 0 except Exception: return False # # Decorators # 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(): return f(*args, **kwargs) else: warn("No debugging session active") return wrapper def only_if_gdb_target_local(f: Callable) -> Callable: """Decorator wrapper to check if GDB is running locally (target not remote).""" @functools.wraps(f) def wrapper(*args: Any, **kwargs: Any) -> Any: if not is_remote_debug(): return f(*args, **kwargs) else: warn("This command cannot work for remote sessions.") return wrapper 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: if gef.config["gef.show_deprecation_warnings"] is True: msg = f"'{f.__name__}' is deprecated and will be removed in a feature release. " if solution: msg += solution warn(msg) return f(*args, **kwargs) if not wrapper.__doc__: 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 def experimental_feature(f: Callable) -> Callable: """Decorator to add a warning when a feature is experimental.""" @functools.wraps(f) def wrapper(*args: Any, **kwargs: Any) -> Any: warn("This feature is under development, expect bugs and unstability...") return f(*args, **kwargs) return wrapper def only_if_gdb_version_higher_than(required_gdb_version: Tuple[int, ...]) -> Callable: """Decorator to check whether current GDB version requirements.""" def wrapper(f: Callable) -> Callable: def inner_f(*args: Any, **kwargs: Any) -> None: if GDB_VERSION >= required_gdb_version: f(*args, **kwargs) else: reason = f"GDB >= {required_gdb_version} for this command" raise OSError(reason) return inner_f return wrapper def only_if_current_arch_in(valid_architectures: List["Architecture"]) -> Callable: """Decorator to allow commands for only a subset of the architectured supported by GEF. This decorator is to use lightly, as it goes against the purpose of GEF to support all architectures GDB does. However in some cases, it is necessary.""" def wrapper(f: Callable) -> Callable: def inner_f(*args: Any, **kwargs: Any) -> None: if gef.arch in valid_architectures: f(*args, **kwargs) else: reason = f"This command cannot work for the '{gef.arch.arch}' architecture" raise OSError(reason) return inner_f return wrapper 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): return classmethod(self.fget).__get__(None, owner)() def FakeExit(*args: Any, **kwargs: Any) -> NoReturn: raise RuntimeWarning sys.exit = FakeExit def parse_arguments(required_arguments: Dict[Union[str, Tuple[str, str]], Any], optional_arguments: Dict[Union[str, Tuple[str, str]], Any]) -> Optional[Callable]: """Argument parsing decorator.""" def int_wrapper(x: str) -> int: return int(x, 0) def decorator(f: Callable) -> Optional[Callable]: def wrapper(*args: Any, **kwargs: Any) -> Optional[Callable]: parser = argparse.ArgumentParser(prog=args[0]._cmdline_, add_help=True) for argname in required_arguments: argvalue = required_arguments[argname] argtype = type(argvalue) if argtype is int: argtype = int_wrapper argname_is_list = isinstance(argname, list) or isinstance(argname, tuple) 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") else: parser.add_argument(argname, type=argtype, required=True, default=argvalue) else: if argtype in (list, tuple): nargs = "*" argtype = type(argvalue[0]) else: nargs = "?" # positional args parser.add_argument(argname, type=argtype, default=argvalue, nargs=nargs) for argname in optional_arguments: argname_is_list = isinstance(argname, list) or isinstance(argname, tuple) if not argname_is_list and not argname.startswith("-"): # refuse positional arguments continue argvalue = optional_arguments[argname] argtype = type(argvalue) if not argname_is_list: argname = [argname,] if argtype is int: argtype = int_wrapper if argtype is bool: parser.add_argument(*argname, action="store_true" if argvalue else "store_false") else: parser.add_argument(*argname, type=argtype, default=argvalue) try: parsed_args = parser.parse_args(*(args[1:])) except RuntimeWarning: return kwargs["arguments"] = parsed_args return f(*args, **kwargs) return wrapper return decorator class Color: """Used to colorify terminal output.""" colors = { "normal" : "\033[0m", "gray" : "\033[1;38;5;240m", "light_gray" : "\033[0;37m", "red" : "\033[31m", "green" : "\033[32m", "yellow" : "\033[33m", "blue" : "\033[34m", "pink" : "\033[35m", "cyan" : "\033[36m", "bold" : "\033[1m", "underline" : "\033[4m", "underline_off" : "\033[24m", "highlight" : "\033[3m", "highlight_off" : "\033[23m", "blink" : "\033[5m", "blink_off" : "\033[25m", } @staticmethod def redify(msg: str) -> str: return Color.colorify(msg, "red") @staticmethod def greenify(msg: str) -> str: return Color.colorify(msg, "green") @staticmethod def blueify(msg: str) -> str: return Color.colorify(msg, "blue") @staticmethod def yellowify(msg: str) -> str: return Color.colorify(msg, "yellow") @staticmethod def grayify(msg: str) -> str: return Color.colorify(msg, "gray") @staticmethod def light_grayify(msg: str) -> str: return Color.colorify(msg, "light_gray") @staticmethod def pinkify(msg: str) -> str: return Color.colorify(msg, "pink") @staticmethod def cyanify(msg: str) -> str: return Color.colorify(msg, "cyan") @staticmethod def boldify(msg: str) -> str: return Color.colorify(msg, "bold") @staticmethod def underlinify(msg: str) -> str: return Color.colorify(msg, "underline") @staticmethod def highlightify(msg: str) -> str: return Color.colorify(msg, "highlight") @staticmethod 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 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"]) 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) self.info: "Zone" = kwargs.get("info", None) self.valid: bool = kwargs.get("valid", True) return def __str__(self) -> str: value = format_address(self.value) code_color = gef.config["theme.address_code"] stack_color = gef.config["theme.address_stack"] heap_color = gef.config["theme.address_heap"] if self.is_in_text_segment(): return Color.colorify(value, code_color) if self.is_in_heap_segment(): return Color.colorify(value, heap_color) if self.is_in_stack_segment(): return Color.colorify(value, stack_color) return value 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()) def is_in_stack_segment(self) -> bool: return hasattr(self.section, "path") and "[stack]" == self.section.path def is_in_heap_segment(self) -> bool: return hasattr(self.section, "path") and "[heap]" == self.section.path def dereference(self) -> Optional[int]: addr = align_address(int(self.value)) derefed = dereference(addr) return None if derefed is None else int(derefed) class Permission(enum.Flag): """GEF representation of Linux permission.""" NONE = 0 EXECUTE = 1 WRITE = 2 READ = 4 ALL = 7 def __str__(self) -> str: perm_str = "" perm_str += "r" if self & Permission.READ else "-" perm_str += "w" if self & Permission.WRITE else "-" perm_str += "x" if self & Permission.EXECUTE else "-" return perm_str @staticmethod def from_info_sections(*args: str) -> "Permission": perm = Permission(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 return perm @staticmethod def from_process_maps(perm_str: str) -> "Permission": perm = Permission(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 return perm class Section: """GEF representation of process memory sections.""" def __init__(self, **kwargs: Any) -> None: self.page_start: int = kwargs.get("page_start", 0) self.page_end: int = kwargs.get("page_end", 0) self.offset: int = kwargs.get("offset", 0) self.permission: Permission = kwargs.get("permission", Permission(0)) self.inode: int = kwargs.get("inode", 0) self.path: str = kwargs.get("path", "") return def is_readable(self) -> bool: return (self.permission & Permission.READ) != 0 def is_writable(self) -> bool: return (self.permission & Permission.WRITE) != 0 def is_executable(self) -> bool: return (self.permission & Permission.EXECUTE) != 0 @property def size(self) -> int: if self.page_end is None or self.page_start is None: return -1 return self.page_end - self.page_start @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 f"/tmp/gef/{gef.session.remote:d}/{self.path}" def __str__(self) -> str: return (f"Section(page_start={self.page_start:#x}, page_end={self.page_end:#x}, " f"permissions={self.permission!s})") Zone = collections.namedtuple("Zone", ["name", "zone_start", "zone_end", "filename"]) class Endianness(enum.Enum): LITTLE_ENDIAN = 1 BIG_ENDIAN = 2 def __str__(self) -> str: if self == Endianness.LITTLE_ENDIAN: return "<" return ">" def __repr__(self) -> str: return self.name def __int__(self) -> int: return self.value class Elf: """Basic ELF parsing. Ref: - http://www.skyfree.org/linux/references/ELF_Format.pdf - 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_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 class Type(enum.Enum): 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: Class = Class.ELF_32_BITS e_endianness: Endianness = Endianness.LITTLE_ENDIAN e_eiversion: int e_osabi: OsAbi e_abiversion: int e_pad: bytes e_type: Type = Type.ET_EXEC e_machine: Abi = Abi.X86_32 e_version: int e_entry: int e_phoff: int e_shoff: int e_flags: int e_ehsize: int e_phentsize: int e_phnum: int e_shentsize: int e_shnum: int e_shstrndx: int path: Optional[pathlib.Path] = None def __init__(self, path: str = "", minimalist: bool = False) -> None: """Instantiate an ELF object. The default behavior is to create the object by parsing the ELF file. But in some cases (QEMU-stub), we may just want a simple minimal object with default values.""" if minimalist: return self.fpath = pathlib.Path(path).expanduser() if not os.access(self.fpath, os.R_OK): raise FileNotFoundError(f"'{self.fpath}' not found/readable, most gef features will not work") with self.fpath.open("rb") as self.fd: # off 0x0 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) if self.e_endianness != gef.arch.endianness: warn("Unexpected endianness for architecture") endian = self.e_endianness # off 0x7 e_osabi, self.e_abiversion = self.read_and_unpack(f"{endian}BB") self.e_osabi = Elf.OsAbi(e_osabi) # off 0x9 self.e_pad = self.read(7) # off 0x10 e_type, e_machine, self.e_version = self.read_and_unpack(f"{endian}HHI") self.e_type, self.e_machine = Elf.Type(e_type), Elf.Abi(e_machine) # 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") else: 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.phdrs : List["Phdr"] = [] for i in range(self.e_phnum): self.phdrs.append(Phdr(self, self.e_phoff + self.e_phentsize * i)) self.shdrs : List["Shdr"] = [] for i in range(self.e_shnum): self.shdrs.append(Shdr(self, self.e_shoff + self.e_shentsize * i)) return def read(self, size: int) -> bytes: return self.fd.read(size) def read_and_unpack(self, fmt: str) -> Tuple[Any, ...]: size = struct.calcsize(fmt) data = self.fd.read(size) return struct.unpack(fmt, data) def seek(self, off: int) -> None: self.fd.seek(off, 0) def __str__(self) -> str: return f"ELF('{self.fpath.absolute()}', {self.e_class.name}, {self.e_machine.name})" @property def entry_point(self) -> int: return self.e_entry @classproperty @deprecated("use `Elf.Abi.X86_64`") 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 @classproperty @deprecated("use `Elf.Abi.ARM`") 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 @classproperty @deprecated("use `Elf.Abi.POWERPC`") 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 @classproperty @deprecated("use `Elf.Abi.SPARC`") 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 @classproperty @deprecated("use `Elf.Abi.AARCH64`") 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 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 @classmethod def _missing_(cls, _:int) -> Type: return cls.UNKNOWN_PHDR class Flags(enum.IntFlag): PF_X = 1 PF_W = 2 PF_R = 4 p_type: Type p_flags: Flags p_offset: int p_vaddr: int p_paddr: int p_filesz: int p_memsz: int p_align: int def __init__(self, elf: Elf, off: int) -> None: if not elf: return elf.seek(off) self.offset = off endian = gef.arch.endianness 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") 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_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})") 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 @classmethod def _missing_(cls, _:int) -> 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 OS_NONCONFORMING = 0x100 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): return cls.UNKNOWN_FLAG sh_name: int sh_type: Type sh_flags: Flags sh_addr: int sh_offset: int sh_size: int sh_link: int sh_info: int sh_addralign: int sh_entsize: int name: str def __init__(self, elf: Optional[Elf], off: int) -> None: if elf is None: return elf.seek(off) endian = gef.arch.endianness 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_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_addralign, self.sh_entsize = elf.read_and_unpack(f"{endian}II") self.sh_type = Shdr.Type(sh_type) self.sh_flags = Shdr.Flags(sh_flags) stroff = elf.e_shoff + elf.e_shentsize * elf.e_shstrndx if elf.e_class == Elf.Class.ELF_64_BITS: elf.seek(stroff + 16 + 8) offset = u64(elf.read(8)) else: elf.seek(stroff + 12 + 4) offset = u32(elf.read(4)) elf.seek(offset + self.sh_name) self.name = "" while True: c = u8(elf.read(1)) if c == 0: break self.name += chr(c) 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})") class Instruction: """GEF representation of a CPU instruction.""" def __init__(self, address: int, location: str, mnemo: str, operands: List[str], opcodes: bytearray) -> 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. # The number of bytes to display can be configured, e.g. {:4o} to only show 4 bytes of the opcodes def __format__(self, format_spec: str) -> str: if len(format_spec) == 0 or format_spec[-1] != "o": return str(self) if format_spec == "o": opcodes_len = len(self.opcodes) else: opcodes_len = int(format_spec[:-1]) 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)}") def __str__(self) -> str: return f"{self.address:#10x} {self.location:16} {self.mnemonic:6} {', '.join(self.operands)}" def is_valid(self) -> bool: return "(bad)" not in self.mnemonic @lru_cache() def search_for_main_arena() -> int: """A helper function to find the libc `main_arena` address, either from symbol or from its offset from `__malloc_hook`.""" try: addr = parse_address(f"&{LIBC_HEAP_MAIN_ARENA_DEFAULT_NAME}") except gdb.error: malloc_hook_addr = parse_address("(void *)&__malloc_hook") if is_x86(): 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 - MallocStateStruct("*0").struct_size elif is_arch(Elf.Abi.ARM): addr = malloc_hook_addr - gef.arch.ptrsize - MallocStateStruct("*0").struct_size else: raise OSError(f"Cannot find main_arena for {gef.arch.arch}") return addr class MallocStateStruct: """GEF representation of malloc_state from https://github.com/bminor/glibc/blob/glibc-2.28/malloc/malloc.c#L1658""" def __init__(self, addr: str) -> None: try: self.__addr = parse_address(f"&{addr}") except gdb.error: self.__addr = search_for_main_arena() # if `search_for_main_arena` throws `gdb.error` on symbol lookup: # it means the session is not started, so just propagate the exception self.num_fastbins = 10 self.num_bins = 254 self.int_size = cached_lookup_type("int").sizeof self.size_t = cached_lookup_type("size_t") if not self.size_t: ptr_type = "unsigned long" if gef.arch.ptrsize == 8 else "unsigned int" self.size_t = cached_lookup_type(ptr_type) # Account for separation of have_fastchunks flag into its own field # within the malloc_state struct in GLIBC >= 2.27 # https://sourceware.org/git/?p=glibc.git;a=commit;h=e956075a5a2044d05ce48b905b10270ed4a63e87 # Be aware you could see this change backported into GLIBC release # branches. if get_libc_version() >= (2, 27): self.fastbin_offset = align_address_to_size(self.int_size * 3, 8) else: self.fastbin_offset = self.int_size * 2 return # struct offsets @property def addr(self) -> int: return self.__addr @property def fastbins_addr(self) -> int: return self.__addr + self.fastbin_offset @property def top_addr(self) -> int: return self.fastbins_addr + self.num_fastbins * gef.arch.ptrsize @property def last_remainder_addr(self) -> int: return self.top_addr + gef.arch.ptrsize @property def bins_addr(self) -> int: return self.last_remainder_addr + gef.arch.ptrsize @property def next_addr(self) -> int: return self.bins_addr + self.num_bins * gef.arch.ptrsize + self.int_size * 4 @property def next_free_addr(self) -> int: return self.next_addr + gef.arch.ptrsize @property def system_mem_addr(self) -> int: return self.next_free_addr + gef.arch.ptrsize * 2 @property def struct_size(self) -> int: return self.system_mem_addr + gef.arch.ptrsize * 2 - self.__addr # struct members @property def fastbinsY(self) -> "gdb.Value": return self.get_size_t_array(self.fastbins_addr, self.num_fastbins) @property def top(self) -> "gdb.Value": return self.get_size_t_pointer(self.top_addr) @property def last_remainder(self) -> "gdb.Value": return self.get_size_t_pointer(self.last_remainder_addr) @property def bins(self) -> "gdb.Value": return self.get_size_t_array(self.bins_addr, self.num_bins) @property def next(self) -> "gdb.Value": return self.get_size_t_pointer(self.next_addr) @property def next_free(self) -> "gdb.Value": return self.get_size_t_pointer(self.next_free_addr) @property def system_mem(self) -> "gdb.Value": return self.get_size_t(self.system_mem_addr) # helper methods def get_size_t(self, addr: int) -> "gdb.Value": return dereference(addr).cast(self.size_t) def get_size_t_pointer(self, addr: int) -> "gdb.Value": size_t_pointer = self.size_t.pointer() return dereference(addr).cast(size_t_pointer) def get_size_t_array(self, addr: int, length: int) -> "gdb.Value": size_t_array = self.size_t.array(length) return dereference(addr).cast(size_t_array) def __getitem__(self, item: str) -> Any: return getattr(self, item) class GlibcHeapInfo: """Glibc heap_info struct See https://github.com/bminor/glibc/blob/glibc-2.34/malloc/arena.c#L64""" def __init__(self, addr: Union[int, str]) -> None: self.__addr = addr if type(addr) is int else parse_address(addr) self.size_t = cached_lookup_type("size_t") if not self.size_t: ptr_type = "unsigned long" if gef.arch.ptrsize == 8 else "unsigned int" self.size_t = cached_lookup_type(ptr_type) @property def addr(self) -> int: return self.__addr @property def ar_ptr_addr(self) -> int: return self.addr @property def prev_addr(self) -> int: return self.ar_ptr_addr + gef.arch.ptrsize @property def size_addr(self) -> int: return self.prev_addr + gef.arch.ptrsize @property def mprotect_size_addr(self) -> int: return self.size_addr + self.size_t.sizeof @property def ar_ptr(self) -> "gdb.Value": return self._get_size_t_pointer(self.ar_ptr_addr) @property def prev(self) -> "gdb.Value": return self._get_size_t_pointer(self.prev_addr) @property def size(self) -> "gdb.Value": return self._get_size_t(self.size_addr) @property def mprotect_size(self) -> "gdb.Value": return self._get_size_t(self.mprotect_size_addr) # helper methods def _get_size_t_pointer(self, addr: int) -> "gdb.Value": size_t_pointer = self.size_t.pointer() return dereference(addr).cast(size_t_pointer) def _get_size_t(self, addr: int) -> "gdb.Value": return dereference(addr).cast(self.size_t) class GlibcArena: """Glibc arena class Ref: https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1671""" def __init__(self, addr: str) -> None: self.__arena: Union["gdb.Value", MallocStateStruct] try: arena = gdb.parse_and_eval(addr) malloc_state_t = cached_lookup_type("struct malloc_state") self.__arena = arena.cast(malloc_state_t) # here __arena becomes a "gdb.Value" self.__addr = int(arena.address) self.struct_size: int = malloc_state_t.sizeof except: self.__arena = MallocStateStruct(addr) # here __arena becomes MallocStateStruct self.__addr = self.__arena.addr try: self.top = int(self.top) self.last_remainder = int(self.last_remainder) self.n = int(self.next) self.nfree = int(self.next_free) self.sysmem = int(self.system_mem) except gdb.error as e: err("Glibc arena: {}".format(e)) return def __getitem__(self, item: Any) -> Any: return self.__arena[item] def __getattr__(self, item: Any) -> Any: return self.__arena[item] def __int__(self) -> int: return self.__addr def __iter__(self) -> Generator["GlibcArena", None, None]: yield self current_arena = self while True: next_arena_address = int(current_arena.next) if next_arena_address == int(gef.heap.main_arena): break current_arena = GlibcArena(f"*{next_arena_address:#x} ") yield current_arena return def __eq__(self, other: "GlibcArena") -> bool: # You cannot have 2 arenas at the same address, so this check should be enough return self.__addr == int(other) def fastbin(self, i: int) -> Optional["GlibcChunk"]: """Return head chunk in fastbinsY[i].""" addr = int(self.fastbinsY[i]) if addr == 0: return None return GlibcChunk(addr + 2 * gef.arch.ptrsize) def bin(self, i: int) -> Tuple[int, int]: idx = i * 2 fd = int(self.bins[idx]) bw = int(self.bins[idx + 1]) return fd, bw def is_main_arena(self) -> bool: return int(self) == int(gef.heap.main_arena) def heap_addr(self, allow_unaligned: bool = False) -> Optional[int]: if self.is_main_arena(): heap_section = gef.heap.base_address if not heap_section: return None return heap_section _addr = int(self) + self.struct_size if allow_unaligned: return _addr return malloc_align_address(_addr) 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) heap_infos = [GlibcHeapInfo(heap_addr)] while heap_infos[-1].prev != 0: prev = int(heap_infos[-1].prev) heap_info = GlibcHeapInfo(prev) heap_infos.append(heap_info) return heap_infos[::-1] @staticmethod def get_heap_for_ptr(ptr: int) -> int: """Find the corresponding heap for a given pointer (int). See https://github.com/bminor/glibc/blob/glibc-2.34/malloc/arena.c#L129""" if is_32bit(): default_mmap_threshold_max = 512 * 1024 else: # 64bit default_mmap_threshold_max = 4 * 1024 * 1024 * cached_lookup_type("long").sizeof heap_max_size = 2 * default_mmap_threshold_max return ptr & ~(heap_max_size - 1) def __str__(self) -> str: return (f"{Color.colorify('Arena', 'blue bold underline')}(base={self.__addr:#x}, top={self.top:#x}, " f"last_remainder={self.last_remainder:#x}, next={self.n:#x}, next_free={self.nfree:#x}, " f"system_mem={self.sysmem:#x})") @property def addr(self) -> int: return int(self) class GlibcChunk: """Glibc chunk class. The default behavior (from_base=False) is to interpret the data starting at the memory address pointed to as the chunk data. Setting from_base to True instead treats that data as the chunk header. Ref: https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/.""" def __init__(self, addr: int, from_base: bool = False, allow_unaligned: bool = True) -> None: self.ptrsize = gef.arch.ptrsize if from_base: self.data_address = addr + 2 * self.ptrsize else: self.data_address = addr if not allow_unaligned: self.data_address = malloc_align_address(self.data_address) self.base_address = addr - 2 * self.ptrsize self.size_addr = int(self.data_address - self.ptrsize) self.prev_size_addr = self.base_address return def get_chunk_size(self) -> int: return gef.memory.read_integer(self.size_addr) & (~0x07) @property def size(self) -> int: return self.get_chunk_size() def get_usable_size(self) -> int: # https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L4537 cursz = self.get_chunk_size() if cursz == 0: return cursz if self.has_m_bit(): return cursz - 2 * self.ptrsize return cursz - self.ptrsize @property def usable_size(self) -> int: return self.get_usable_size() def get_prev_chunk_size(self) -> int: return gef.memory.read_integer(self.prev_size_addr) def __iter__(self) -> Generator["GlibcChunk", None, None]: current_chunk = self top = gef.heap.main_arena.top while True: yield current_chunk if current_chunk.base_address == top: break if current_chunk.size == 0: break next_chunk_addr = current_chunk.get_next_chunk_addr() if not Address(value=next_chunk_addr).valid: break next_chunk = current_chunk.get_next_chunk() if next_chunk is None: break current_chunk = next_chunk return def get_next_chunk(self, allow_unaligned: bool = False) -> "GlibcChunk": addr = self.get_next_chunk_addr() return GlibcChunk(addr, allow_unaligned=allow_unaligned) def get_next_chunk_addr(self) -> int: return self.data_address + self.get_chunk_size() # if free-ed functions def get_fwd_ptr(self, sll: bool) -> int: # Not a single-linked-list (sll) or no Safe-Linking support yet if not sll or get_libc_version() < (2, 32): return gef.memory.read_integer(self.data_address) # Unmask ("reveal") the Safe-Linking pointer else: return gef.memory.read_integer(self.data_address) ^ (self.data_address >> 12) @property def fwd(self) -> int: return self.get_fwd_ptr(False) fd = fwd # for compat def get_bkw_ptr(self) -> int: return gef.memory.read_integer(self.data_address + self.ptrsize) @property def bck(self) -> int: return self.get_bkw_ptr() bk = bck # for compat # endif free-ed functions def has_p_bit(self) -> bool: return bool(gef.memory.read_integer(self.size_addr) & 0x01) def has_m_bit(self) -> bool: return bool(gef.memory.read_integer(self.size_addr) & 0x02) def has_n_bit(self) -> bool: return bool(gef.memory.read_integer(self.size_addr) & 0x04) def is_used(self) -> bool: """Check if the current block is used by: - checking the M bit is true - or checking that next chunk PREV_INUSE flag is true""" if self.has_m_bit(): return True next_chunk = self.get_next_chunk() return True if next_chunk.has_p_bit() else False def str_chunk_size_flag(self) -> str: msg = [] msg.append(f"PREV_INUSE flag: {Color.greenify('On') if self.has_p_bit() else Color.redify('Off')}") msg.append(f"IS_MMAPPED flag: {Color.greenify('On') if self.has_m_bit() else Color.redify('Off')}") msg.append(f"NON_MAIN_ARENA flag: {Color.greenify('On') if self.has_n_bit() else Color.redify('Off')}") return "\n".join(msg) def _str_sizes(self) -> str: msg = [] failed = False try: msg.append("Chunk size: {0:d} ({0:#x})".format(self.get_chunk_size())) msg.append("Usable size: {0:d} ({0:#x})".format(self.get_usable_size())) failed = True except gdb.MemoryError: msg.append(f"Chunk size: Cannot read at {self.size_addr:#x} (corrupted?)") try: msg.append("Previous chunk size: {0:d} ({0:#x})".format(self.get_prev_chunk_size())) failed = True except gdb.MemoryError: msg.append(f"Previous chunk size: Cannot read at {self.base_address:#x} (corrupted?)") if failed: msg.append(self.str_chunk_size_flag()) return "\n".join(msg) def _str_pointers(self) -> str: fwd = self.data_address bkw = self.data_address + self.ptrsize msg = [] try: msg.append(f"Forward pointer: {self.get_fwd_ptr(False):#x}") except gdb.MemoryError: msg.append(f"Forward pointer: {fwd:#x} (corrupted?)") try: msg.append(f"Backward pointer: {self.get_bkw_ptr():#x}") except gdb.MemoryError: msg.append(f"Backward pointer: {bkw:#x} (corrupted?)") return "\n".join(msg) def str_as_alloced(self) -> str: return self._str_sizes() def str_as_freed(self) -> str: return f"{self._str_sizes()}\n\n{self._str_pointers()}" def flags_as_string(self) -> str: flags = [] if self.has_p_bit(): flags.append(Color.colorify("PREV_INUSE", "red bold")) else: flags.append(Color.colorify("! PREV_INUSE", "green bold")) if self.has_m_bit(): flags.append(Color.colorify("IS_MMAPPED", "red bold")) if self.has_n_bit(): flags.append(Color.colorify("NON_MAIN_ARENA", "red bold")) return "|".join(flags) def __str__(self) -> str: return (f"{Color.colorify('Chunk', 'yellow bold underline')}(addr={self.data_address:#x}, " f"size={self.get_chunk_size():#x}, flags={self.flags_as_string()})") def psprint(self) -> str: msg = [] msg.append(str(self)) if self.is_used(): msg.append(self.str_as_alloced()) else: msg.append(self.str_as_freed()) return "\n".join(msg) + "\n" @lru_cache() def get_libc_version() -> Tuple[int, ...]: sections = gef.memory.maps for section in sections: match = re.search(r"libc6?[-_](\d+)\.(\d+)\.so", section.path) if match: return tuple(int(_) for _ in match.groups()) if "libc" in section.path: try: with open(section.path, "rb") as f: data = f.read() except OSError: continue match = re.search(PATTERN_LIBC_VERSION, data) if match: return tuple(int(_) for _ in match.groups()) return 0, 0 def titlify(text: str, color: Optional[str] = None, msg_color: Optional[str] = None) -> str: """Print a centered title.""" cols = get_terminal_size()[1] nb = (cols - len(text) - 2) // 2 if color is None: color = gef.config["theme.default_title_line"] if msg_color is None: msg_color = gef.config["theme.default_title_message"] msg = [Color.colorify(f"{HORIZONTAL_LINE * nb} ", color), Color.colorify(text, msg_color), Color.colorify(f" {HORIZONTAL_LINE * nb}", color)] return "".join(msg) def err(msg: str) -> None: gef_print(f"{Color.colorify('[!]', 'bold red')} {msg}") return def warn(msg: str) -> None: gef_print(f"{Color.colorify('[*]', 'bold yellow')} {msg}") return def ok(msg: str) -> None: gef_print(f"{Color.colorify('[+]', 'bold green')} {msg}") return def info(msg: str) -> None: gef_print(f"{Color.colorify('[+]', 'bold blue')} {msg}") return def push_context_message(level: str, message: str) -> None: """Push the message to be displayed the next time the context is invoked.""" if level not in ("error", "warn", "ok", "info"): err(f"Invalid level '{level}', discarding message") return gef.ui.context_messages.append((level, message)) return def show_last_exception() -> None: """Display the last Python exception.""" def _show_code_line(fname: str, idx: int) -> str: fname = os.path.expanduser(os.path.expandvars(fname)) with open(fname, "r") as f: __data = f.readlines() return __data[idx - 1] if 0 < idx < len(__data) else "" gef_print("") exc_type, exc_value, exc_traceback = sys.exc_info() gef_print(" Exception raised ".center(80, HORIZONTAL_LINE)) gef_print(f"{Color.colorify(exc_type.__name__, 'bold underline red')}: {exc_value}") gef_print(" Detailed stacktrace ".center(80, HORIZONTAL_LINE)) for fs in traceback.extract_tb(exc_traceback)[::-1]: filename, lineno, method, code = fs 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" {RIGHT_ARROW} {code}") gef_print(" Version ".center(80, HORIZONTAL_LINE)) gdb.execute("version full") gef_print(" Last 10 GDB commands ".center(80, HORIZONTAL_LINE)) 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()})") try: lsb_release = which("lsb_release") gdb.execute(f"!{lsb_release} -a") except FileNotFoundError: gef_print("lsb_release is missing, cannot collect additional debug information") gef_print(HORIZONTAL_LINE*80) gef_print("") return 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) return res def gef_pybytes(x: str) -> bytes: """Returns an immutable bytes list from the string given as input.""" return bytes(str(x), encoding="utf-8") @lru_cache() def which(program: str) -> Optional[pathlib.Path]: """Locate a command on the filesystem.""" 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}`") def style_byte(b: int, color: bool = True) -> str: style = { "nonprintable": "yellow", "printable": "white", "00": "gray", "0a": "blue", "ff": "green", } sbyte = f"{b:02x}" if not color or gef.config["highlight.regex"]: return sbyte if sbyte in style: st = style[sbyte] elif chr(b) in (string.ascii_letters + string.digits + string.punctuation + " "): st = style.get("printable") else: st = style.get("nonprintable") if st: sbyte = Color.colorify(sbyte, st) return sbyte 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 @param separator is the default character to use if one byte is not printable @param show_raw if True, do not add the line nor the text translation @param base is the start address of the block being hexdump @return a string with the hexdump""" result = [] align = gef.arch.ptrsize * 2 + 2 if is_alive() else 18 for i in range(0, len(source), length): chunk = bytearray(source[i : i + length]) hexa = " ".join([style_byte(b, color=not show_raw) for b in chunk]) if show_raw: result.append(hexa) continue text = "".join([chr(b) if 0x20 <= b < 0x7F else separator for b in chunk]) if show_symbol: sym = gdb_get_location_from_symbol(base + i) sym = "<{:s}+{:04x}>".format(*sym) if sym else "" else: sym = "" result.append(f"{base + i:#0{align}x} {sym} {hexa:<{3 * length}} {text}") return "\n".join(result) def is_debug() -> bool: """Check if debug mode is enabled.""" return gef.config["gef.debug"] is True def hide_context() -> bool: """Helper function to hide the context pane.""" gef.ui.context_hidden = True return True def unhide_context() -> bool: """Helper function to unhide the context pane.""" gef.ui.context_hidden = False return True class RedirectOutputContext(): def __init__(self, to: str = "/dev/null") -> None: self.redirection_target_file = to return def __enter__(self) -> None: """Redirect all GDB output to `to_file` parameter. By default, `to_file` redirects to `/dev/null`.""" gdb.execute("set logging overwrite") gdb.execute(f"set logging file {self.redirection_target_file}") gdb.execute("set logging redirect on") gdb.execute("set logging on") return def __exit__(self, *exc: Any) -> None: """Disable the output redirection, if any.""" gdb.execute("set logging off") gdb.execute("set logging redirect off") return 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`.""" gdb.execute("set logging overwrite") gdb.execute(f"set logging file {to_file}") gdb.execute("set logging redirect on") gdb.execute("set logging on") return def disable_redirect_output() -> None: """Disable the output redirection, if any.""" gdb.execute("set logging off") gdb.execute("set logging redirect off") return def gef_makedirs(path: str, mode: int = 0o755) -> pathlib.Path: """Recursive mkdir() creation. If successful, return the absolute path of the directory created.""" fpath = pathlib.Path(path) if not fpath.is_dir(): fpath.mkdir(mode=mode, exist_ok=True, parents=True) return fpath.absolute() @lru_cache() def gdb_lookup_symbol(sym: str) -> Optional[Tuple[Optional[str], Optional[Tuple[gdb.Symtab_and_line, ...]]]]: """Fetch the proper symbol or None if not defined.""" try: return gdb.decode_line(sym)[1] except gdb.error: return None @lru_cache(maxsize=512) 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... # find a *clean* way to get gdb.Location from an address sym = gdb.execute(f"info symbol {address:#x}", to_string=True) if sym.startswith("No symbol matches"): return None i = sym.find(" in section ") sym = sym[:i].split() name, offset = sym[0], 0 if len(sym) == 3 and sym[2].isdigit(): offset = int(sym[2]) return name, offset def gdb_disassemble(start_pc: int, **kwargs: int) -> Generator[Instruction, None, None]: """Disassemble instructions from `start_pc` (Integer). Accepts the following named parameters: - `end_pc` (Integer) only instructions whose start address fall in the interval from start_pc to end_pc are returned. - `count` (Integer) list at most this many disassembled instructions If `end_pc` and `count` are not provided, the function will behave as if `count=1`. Return an iterator of Instruction objects """ frame = gdb.selected_frame() arch = frame.architecture() for insn in arch.disassemble(start_pc, **kwargs): address = insn["addr"] asm = insn["asm"].rstrip().split(None, 1) if len(asm) > 1: mnemo, operands = asm operands = operands.split(",") else: mnemo, operands = asm[0], [] loc = gdb_get_location_from_symbol(address) location = "<{}+{}>".format(*loc) if loc else "" opcodes = gef.memory.read(insn["addr"], insn["length"]) yield Instruction(address, location, mnemo, operands, opcodes) def gdb_get_nth_previous_instruction_address(addr: int, n: int) -> Optional[int]: """Return the address (Integer) of the `n`-th instruction before `addr`.""" # fixed-length ABI if gef.arch.instruction_length: return max(0, addr - n * gef.arch.instruction_length) # variable-length ABI cur_insn_addr = gef_current_instruction(addr).address # we try to find a good set of previous instructions by "guessing" disassembling backwards # the 15 comes from the longest instruction valid size for i in range(15 * n, 0, -1): try: insns = list(gdb_disassemble(addr - i, end_pc=cur_insn_addr)) except gdb.MemoryError: # this is because we can hit an unmapped page trying to read backward break # 1. check that the disassembled instructions list size can satisfy if len(insns) < n + 1: # we expect the current instruction plus the n before it continue # If the list of instructions is longer than what we need, then we # could get lucky and already have more than what we need, so slice down insns = insns[-n - 1 :] # 2. check that the sequence ends with the current address if insns[-1].address != cur_insn_addr: continue # 3. check all instructions are valid if all(insn.is_valid() for insn in insns): return insns[0].address return None def gdb_get_nth_next_instruction_address(addr: int, n: int) -> int: """Return the address (Integer) of the `n`-th instruction after `addr`.""" # fixed-length ABI if gef.arch.instruction_length: return addr + n * gef.arch.instruction_length # variable-length ABI insn = list(gdb_disassemble(addr, count=n))[-1] return insn.address def gef_instruction_n(addr: int, n: int) -> Instruction: """Return the `n`-th instruction after `addr` as an Instruction object.""" return list(gdb_disassemble(addr, count=n + 1))[n] def gef_get_instruction_at(addr: int) -> Instruction: """Return the full Instruction found at the specified address.""" insn = next(gef_disassemble(addr, 1)) return insn def gef_current_instruction(addr: int) -> Instruction: """Return the current instruction as an Instruction object.""" return gef_instruction_n(addr, 0) def gef_next_instruction(addr: int) -> Instruction: """Return the next instruction as an Instruction object.""" return gef_instruction_n(addr, 1) 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) if nb_prev: 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(addr, count=nb_insn): yield insn def capstone_disassemble(location: int, nb_insn: int, **kwargs: Any) -> Generator[Instruction, None, None]: """Disassemble `nb_insn` instructions after `addr` and `nb_prev` before `addr` using the Capstone-Engine disassembler, if available. Return an iterator of Instruction objects.""" def cs_insn_to_gef_insn(cs_insn: "capstone.CsInsn") -> Instruction: sym_info = gdb_get_location_from_symbol(cs_insn.address) loc = "<{}+{}>".format(*sym_info) if sym_info else "" ops = [] + cs_insn.op_str.split(", ") return Instruction(cs_insn.address, loc, cs_insn.mnemonic, ops, cs_insn.bytes) capstone = sys.modules["capstone"] arch, mode = get_capstone_arch(arch=kwargs.get("arch"), mode=kwargs.get("mode"), endian=kwargs.get("endian")) cs = capstone.Cs(arch, mode) cs.detail = True page_start = align_address_to_page(location) offset = location - page_start pc = gef.arch.pc skip = int(kwargs.get("skip", 0)) nb_prev = int(kwargs.get("nb_prev", 0)) if nb_prev > 0: location = gdb_get_nth_previous_instruction_address(pc, nb_prev) nb_insn += nb_prev code = kwargs.get("code", gef.memory.read(location, gef.session.pagesize - offset - 1)) for insn in cs.disasm(code, location): if skip: skip -= 1 continue nb_insn -= 1 yield cs_insn_to_gef_insn(insn) if nb_insn == 0: break return 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) def gef_execute_gdb_script(commands: str) -> None: """Execute the parameter `source` as GDB command. This is done by writing `commands` to a temporary file, which is then executed via GDB `source` command. The tempfile is then deleted.""" fd, fname = tempfile.mkstemp(suffix=".gdb", prefix="gef_") with os.fdopen(fd, "w") as f: f.write(commands) f.flush() fname = pathlib.Path(fname) if fname.is_file() and os.access(fname, os.R_OK): gdb.execute(f"source {fname}") fname.unlink() return @lru_cache(32) def checksec(filename: str) -> Dict[str, bool]: """Check the security property of the ELF binary. The following properties are: - Canary - NX - PIE - Fortify - Partial/Full RelRO. Return a dict() with the different keys mentioned above, and the boolean associated whether the protection was found.""" readelf = gef.session.constants["readelf"] 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): return True return False results = collections.OrderedDict() results["Canary"] = __check_security_property("-s", filename, r"__stack_chk_fail") is True has_gnu_stack = __check_security_property("-W -l", filename, r"GNU_STACK") is True if has_gnu_stack: results["NX"] = __check_security_property("-W -l", filename, r"GNU_STACK.*RWE") is False else: results["NX"] = False results["PIE"] = __check_security_property("-h", filename, r":.*EXEC") is False results["Fortify"] = __check_security_property("-s", filename, r"_chk@GLIBC") is True results["Partial RelRO"] = __check_security_property("-l", filename, r"GNU_RELRO") is True results["Full RelRO"] = results["Partial RelRO"] and __check_security_property("-d", filename, r"BIND_NOW") is True return results @lru_cache() def get_arch() -> str: """Return the binary's architecture.""" if is_alive(): arch = gdb.selected_frame().architecture() return arch.name() arch_str = gdb.execute("show architecture", to_string=True).strip() if "The target architecture is set automatically (currently " in arch_str: arch_str = arch_str.split("(currently ", 1)[1] arch_str = arch_str.split(")", 1)[0] elif "The target architecture is assumed to be " in arch_str: arch_str = arch_str.replace("The target architecture is assumed to be ", "") elif "The target architecture is set to " in arch_str: # GDB version >= 10.1 arch_str = re.findall(r"\"(.+)\"", arch_str)[0] else: # Unknown, we throw an exception to be safe raise RuntimeError(f"Unknown architecture: {arch_str}") return arch_str @deprecated("Use `gef.binary.entry_point` instead") def get_entry_point() -> Optional[int]: """Return the binary entry point.""" return gef.binary.entry_point if gef.binary else None def is_pie(fpath: str) -> bool: return checksec(fpath)["PIE"] @deprecated("Prefer `gef.arch.endianness == Endianness.BIG_ENDIAN`") def is_big_endian() -> bool: return gef.arch.endianness == Endianness.BIG_ENDIAN @deprecated("gef.arch.endianness == Endianness.LITTLE_ENDIAN") 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: """Return a human readable string showing the flag states.""" flags = [] for i in value_table: flag_str = Color.boldify(value_table[i].upper()) if reg_value & (1< Optional[int]: section = process_lookup_path(name) return section.page_start if section else None @lru_cache() def get_zone_base_address(name: str) -> Optional[int]: zone = file_lookup_name_path(name, get_filepath()) return zone.zone_start if zone else None # # Architecture classes # def register_architecture(cls: Type["Architecture"]) -> Type["Architecture"]: """Class decorator for declaring an architecture to GEF.""" global __registered_architectures__ for key in cls.aliases: __registered_architectures__[key] = cls return cls class Architecture(metaclass=abc.ABCMeta): """Generic metaclass for the architecture supported by GEF.""" @abc.abstractproperty def all_registers(self) -> List[str]: pass @abc.abstractproperty def instruction_length(self) -> Optional[int]: pass @abc.abstractproperty def nop_insn(self) -> bytes: pass @abc.abstractproperty def return_register(self) -> str: pass @abc.abstractproperty def flag_register(self) -> Optional[str]: pass @abc.abstractproperty def flags_table(self) -> Optional[Dict[int, str]]: pass @abc.abstractproperty def function_parameters(self) -> List[str]: pass @abc.abstractmethod def flag_register_to_human(self, val: Optional[int] = None) -> str: pass @abc.abstractmethod def is_call(self, insn: Instruction) -> bool: pass @abc.abstractmethod def is_ret(self, insn: Instruction) -> bool: pass @abc.abstractmethod def is_conditional_branch(self, insn: Instruction) -> bool: pass @abc.abstractmethod def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: pass @abc.abstractmethod def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: pass @classmethod @abc.abstractmethod def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: pass arch = "" mode = "" aliases: Tuple[Union[str, int], ...] = [] special_registers: List[str] = [] def reset_caches(self) -> None: self.__get_register_for_selected_frame.cache_clear() return def __get_register(self, regname: str) -> Optional[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()` return self.__get_register_for_selected_frame(regname, key) @lru_cache() def __get_register_for_selected_frame(self, regname: str, hash_key: int) -> Optional[int]: # 1st chance try: return parse_address(regname) except gdb.error: pass # 2nd chance try: regname = regname.lstrip("$") value = gdb.selected_frame().read_register(regname) return int(value) except (ValueError, gdb.error): pass return None def register(self, name: str) -> Optional[int]: return self.__get_register(name) @property def registers(self) -> Generator[str, None, None]: yield from self.all_registers @property def pc(self) -> Optional[int]: return self.register("$pc") @property def sp(self) -> Optional[int]: return self.register("$sp") @property def fp(self) -> Optional[int]: return self.register("$fp") _ptrsize = None @property def ptrsize(self) -> int: if not self._ptrsize: res = cached_lookup_type("size_t") if res is not None: self._ptrsize = res.sizeof else: self._ptrsize = gdb.parse_and_eval("$pc").type.sizeof return self._ptrsize _endianness = None @property def endianness(self) -> Endianness: if not self._endianness: output = gdb.execute("show endian", to_string=True).strip().lower() if "little endian" in output: self._endianness = Endianness.LITTLE_ENDIAN elif "big endian" in output: self._endianness = Endianness.BIG_ENDIAN else: 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]]: """Retrieves the correct parameter used for the current function call.""" reg = self.function_parameters[i] val = self.register(reg) key = reg return key, val class GenericArchitecture(Architecture): arch = "Generic" mode = "" all_registers = () instruction_length = 0 ptrsize = 0 return_register = "" function_parameters = () syscall_register = "" syscall_instructions = () nop_insn = b"" flag_register = None flags_table = None def flag_register_to_human(self, val: Optional[int] = None) -> str: return "" def is_call(self, insn: Instruction) -> bool: return False def is_ret(self, insn: Instruction) -> bool: return False def is_conditional_branch(self, insn: Instruction) -> bool: return False def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: return False, "" def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: return 0 @classmethod def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: raise OSError(f"Architecture {cls.arch} not supported") @register_architecture class RISCV(Architecture): arch = "RISCV" mode = "RISCV" aliases = ("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",] return_register = "$a0" function_parameters = ["$a0", "$a1", "$a2", "$a3", "$a4", "$a5", "$a6", "$a7"] syscall_register = "$a7" syscall_instructions = ["ecall"] nop_insn = b"\x00\x00\x00\x13" # RISC-V has no flags registers flag_register = None flag_register_to_human = None flags_table = None @property def instruction_length(self) -> int: return 4 def is_call(self, insn: Instruction) -> bool: return insn.mnemonic == "call" 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): return True elif (mnemo == "c.jalr" and insn.operands[0] == "ra"): return True return False @classmethod def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: raise OSError(f"Architecture {cls.arch} not supported yet") def is_conditional_branch(self, insn: Instruction) -> bool: return insn.mnemonic.startswith("b") 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(): if v & 0x80000000: return v - 0x100000000 elif is_64bit(): if v & 0x8000000000000000: return v - 0x10000000000000000 else: raise OSError("RISC-V: ELF file is not ELF32 or ELF64. This is not currently supported") return v mnemo = insn.mnemonic condition = mnemo[1:] if condition.endswith("z"): # r2 is the zero register if we are comparing to 0 rs1 = gef.arch.register(insn.operands[0]) rs2 = gef.arch.register("$zero") condition = condition[:-1] elif len(insn.operands) > 2: # r2 is populated with the second operand 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}`") # If the conditional operation is not unsigned, convert the python long into # its two's complement if not condition.endswith("u"): rs2 = long_to_twos_complement(rs2) rs1 = long_to_twos_complement(rs1) else: condition = condition[:-1] if condition == "eq": 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}" elif condition == "lt": 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}" else: raise OSError(f"RISC-V: Conditional instruction `{insn}` not supported yet") return taken, reason def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int: ra = None if self.is_ret(insn): ra = gef.arch.register("$ra") elif frame.older(): ra = frame.older().pc() return ra @register_architecture 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",] # https://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0041c/Caccegih.html nop_insn = b"\x01\x10\xa0\xe1" # mov r1, r1 return_register = "$r0" flag_register = "$cpsr" flags_table = { 31: "negative", 30: "zero", 29: "carry", 28: "overflow", 7: "interrupt", 6: "fast", 5: "thumb", } function_parameters = ["$r0", "$r1", "$r2", "$r3"] syscall_register = "$r7" syscall_instructions = ["swi 0x0", "swi NR"] endianness = Endianness.LITTLE_ENDIAN def is_thumb(self) -> bool: """Determine if the machine is currently in THUMB mode.""" return is_alive() and gef.arch.register(self.flag_register) & (1 << 5) @property def pc(self) -> Optional[int]: pc = gef.arch.register("$pc") if self.is_thumb(): pc += 1 return pc @property def mode(self) -> str: return "THUMB" if self.is_thumb() else "ARM" @property def instruction_length(self) -> Optional[int]: # Thumb instructions have variable-length (2 or 4-byte) return None if self.is_thumb() else 4 @property def ptrsize(self) -> int: return 2 if self.is_thumb() else 4 def is_call(self, insn: Instruction) -> bool: mnemo = insn.mnemonic call_mnemos = {"bl", "blx"} return mnemo in call_mnemos def is_ret(self, insn: Instruction) -> bool: pop_mnemos = {"pop"} branch_mnemos = {"bl", "bx"} write_mnemos = {"ldr", "add"} if insn.mnemonic in pop_mnemos: return insn.operands[-1] == " pc}" if insn.mnemonic in branch_mnemos: return insn.operands[-1] == "lr" if insn.mnemonic in write_mnemos: return insn.operands[0] == "pc" return False def flag_register_to_human(self, val: Optional[int] = None) -> str: # https://www.botskool.com/user-pages/tutorials/electronics/arm-7-tutorial-part-1 if val is None: reg = self.flag_register val = gef.arch.register(reg) 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"} return insn.mnemonic[-2:] in conditions 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) val = gef.arch.register(self.flag_register) taken, reason = False, "" if mnemo.endswith("eq"): taken, reason = bool(val&(1< int: ra = None if self.is_ret(insn): # 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 = to_unsigned_long(dereference(ra_addr)) elif insn.mnemonic == "ldr": return to_unsigned_long(dereference(gef.arch.sp)) else: # 'bx lr' or 'add pc, lr, #0' return gef.arch.register("$lr") elif frame.older(): ra = frame.older().pc() return ra @classmethod 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}", "lsl r0, r0, 16", "add r0, r0, r1", 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}", ] return "; ".join(insns) @register_architecture class AARCH64(ARM): aliases = ("ARM64", "AARCH64", Elf.Abi.AARCH64) arch = "ARM64" mode = "" 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",] return_register = "$x0" flag_register = "$cpsr" flags_table = { 31: "negative", 30: "zero", 29: "carry", 28: "overflow", 7: "interrupt", 6: "fast", } function_parameters = ["$x0", "$x1", "$x2", "$x3", "$x4", "$x5", "$x6", "$x7"] syscall_register = "$x8" syscall_instructions = ["svc $x0"] ptrsize = 8 def is_call(self, insn: Instruction) -> bool: mnemo = insn.mnemonic call_mnemos = {"bl", "blr"} return mnemo in call_mnemos def flag_register_to_human(self, val: Optional[int] = None) -> str: # https://events.linuxfoundation.org/sites/events/files/slides/KoreaLinuxForum-2014.pdf reg = self.flag_register if not val: val = gef.arch.register(reg) return flags_to_human(val, self.flags_table) @classmethod def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: _NR_mprotect = 226 insns = [ "str x8, [sp, -16]!", "str x0, [sp, -16]!", "str x1, [sp, -16]!", "str x2, [sp, -16]!", f"mov x8, {_NR_mprotect:d}", f"movz x0, {addr & 0xFFFF:#x}", f"movk x0, {(addr >> 16) & 0xFFFF:#x}, lsl 16", f"movk x0, {(addr >> 32) & 0xFFFF:#x}, lsl 32", f"movk x0, {(addr >> 48) & 0xFFFF:#x}, lsl 48", f"movz x1, {size & 0xFFFF:#x}", f"movk x1, {(size >> 16) & 0xFFFF:#x}, lsl 16", f"mov x2, {perm.value:d}", "svc 0", "ldr x2, [sp], 16", "ldr x1, [sp], 16", "ldr x0, [sp], 16", "ldr x8, [sp], 16", ] return "; ".join(insns) def is_conditional_branch(self, insn: Instruction) -> bool: # https://www.element14.com/community/servlet/JiveServlet/previewBody/41836-102-1-229511/ARM.Reference_Manual.pdf # sect. 5.1.1 mnemo = insn.mnemonic 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]: mnemo, operands = insn.mnemonic, insn.operands taken, reason = False, "" if mnemo in {"cbnz", "cbz", "tbnz", "tbz"}: 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" elif mnemo == "cbz": 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< str: reg = self.flag_register if not val: val = gef.arch.register(reg) return flags_to_human(val, self.flags_table) def is_call(self, insn: Instruction) -> bool: mnemo = insn.mnemonic call_mnemos = {"call", "callq"} return mnemo in call_mnemos def is_ret(self, insn: Instruction) -> bool: return insn.mnemonic == "ret" 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" } return mnemo in branch_mnemos 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) val = gef.arch.register(self.flag_register) taken, reason = False, "" if mnemo in ("ja", "jnbe"): taken, reason = not val&(1< Optional[int]: ra = None if self.is_ret(insn): ra = to_unsigned_long(dereference(gef.arch.sp)) if frame.older(): ra = frame.older().pc() return ra @classmethod def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: _NR_mprotect = 125 insns = [ "pushad", f"mov eax, {_NR_mprotect:d}", f"mov ebx, {addr:d}", f"mov ecx, {size:d}", f"mov edx, {perm.value:d}", "int 0x80", "popad", ] return "; ".join(insns) 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 sz = gef.arch.ptrsize loc = sp + (i * sz) val = gef.memory.read_integer(loc) key = f"[sp + {i * sz:#x}]" return key, val @register_architecture class X86_64(X86): aliases = ("X86_64", Elf.Abi.X86_64, "i386:x86-64") arch = "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 return_register = "$rax" function_parameters = ["$rdi", "$rsi", "$rdx", "$rcx", "$r8", "$r9"] syscall_register = "$rax" syscall_instructions = ["syscall"] # We don't want to inherit x86's stack based param getter get_ith_parameter = Architecture.get_ith_parameter ptrsize = 8 @classmethod def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: _NR_mprotect = 10 insns = [ "push rax", "push rdi", "push rsi", "push rdx", "push rcx", "push r11", f"mov rax, {_NR_mprotect:d}", f"mov rdi, {addr:d}", f"mov rsi, {size:d}", f"mov rdx, {perm.value:d}", "syscall", "pop r11", "pop rcx", "pop rdx", "pop rsi", "pop rdi", "pop rax", ] return "; ".join(insns) @register_architecture 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",] instruction_length = 4 nop_insn = b"\x60\x00\x00\x00" # https://developer.ibm.com/articles/l-ppc/ return_register = "$r0" flag_register = "$cr" flags_table = { 3: "negative[0]", 2: "positive[0]", 1: "equal[0]", 0: "overflow[0]", # cr7 31: "less[7]", 30: "greater[7]", 29: "equal[7]", 28: "overflow[7]", } function_parameters = ["$i0", "$i1", "$i2", "$i3", "$i4", "$i5"] syscall_register = "$r0" syscall_instructions = ["sc"] def flag_register_to_human(self, val: Optional[int] = None) -> str: # https://www.cebix.net/downloads/bebox/pem32b.pdf (% 2.1.3) if not val: reg = self.flag_register val = gef.arch.register(reg) return flags_to_human(val, self.flags_table) def is_call(self, insn: Instruction) -> bool: return False def is_ret(self, insn: Instruction) -> bool: return insn.mnemonic == "blr" def is_conditional_branch(self, insn: Instruction) -> bool: mnemo = insn.mnemonic branch_mnemos = {"beq", "bne", "ble", "blt", "bgt", "bge"} return mnemo in branch_mnemos 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) taken, reason = False, "" if mnemo == "beq": taken, reason = val&(1< Optional[int]: ra = None if self.is_ret(insn): ra = gef.arch.register("$lr") elif frame.older(): ra = frame.older().pc() return ra @classmethod def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: # Ref: https://developer.ibm.com/articles/l-ppc/ _NR_mprotect = 125 insns = [ "addi 1, 1, -16", # 1 = r1 = sp "stw 0, 0(1)", "stw 3, 4(1)", # r0 = syscall_code | r3, r4, r5 = args "stw 4, 8(1)", "stw 5, 12(1)", f"li 0, {_NR_mprotect:d}", f"lis 3, {addr:#x}@h", f"ori 3, 3, {addr:#x}@l", f"lis 4, {size:#x}@h", f"ori 4, 4, {size:#x}@l", f"li 5, {perm.value:d}", "sc", "lwz 0, 0(1)", "lwz 3, 4(1)", "lwz 4, 8(1)", "lwz 5, 12(1)", "addi 1, 1, 16", ] return ";".join(insns) @register_architecture class PowerPC64(PowerPC): aliases = ("PowerPC64", Elf.Abi.POWERPC64, "PPC64") arch = "PPC" mode = "PPC64" @register_architecture class SPARC(Architecture): """ 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",] instruction_length = 4 nop_insn = b"\x00\x00\x00\x00" # sethi 0, %g0 return_register = "$i0" flag_register = "$psr" flags_table = { 23: "negative", 22: "zero", 21: "overflow", 20: "carry", 7: "supervisor", 5: "trap", } function_parameters = ["$o0 ", "$o1 ", "$o2 ", "$o3 ", "$o4 ", "$o5 ", "$o7 ",] syscall_register = "%g1" syscall_instructions = ["t 0x10"] def flag_register_to_human(self, val: Optional[int] = None) -> str: # https://www.gaisler.com/doc/sparcv8.pdf reg = self.flag_register if not val: val = gef.arch.register(reg) return flags_to_human(val, self.flags_table) def is_call(self, insn: Instruction) -> bool: return False def is_ret(self, insn: Instruction) -> bool: return insn.mnemonic == "ret" 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" } return mnemo in branch_mnemos 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) taken, reason = False, "" if mnemo == "be": taken, reason = val&(1< Optional[int]: ra = None if self.is_ret(insn): ra = gef.arch.register("$o7") elif frame.older(): ra = frame.older().pc() return ra @classmethod def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: 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",] return "; ".join(insns) @register_architecture class SPARC64(SPARC): """Refs: - 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", ] flag_register = "$state" # sparcv9.pdf, 5.1.5.1 (ccr) flags_table = { 35: "negative", 34: "zero", 33: "overflow", 32: "carry", } syscall_instructions = ["t 0x6d"] @classmethod def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: 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",] return "; ".join(insns) @register_architecture class MIPS(Architecture): aliases: Tuple[Union[str, Elf.Abi], ...] = ("MIPS", Elf.Abi.MIPS) arch = "MIPS" mode = "MIPS32" # 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", ] instruction_length = 4 ptrsize = 4 nop_insn = b"\x00\x00\x00\x00" # sll $0,$0,0 return_register = "$v0" flag_register = "$fcsr" flags_table = {} function_parameters = ["$a0", "$a1", "$a2", "$a3"] syscall_register = "$v0" syscall_instructions = ["syscall"] def flag_register_to_human(self, val: Optional[int] = None) -> str: return Color.colorify("No flag register", "yellow underline") def is_call(self, insn: Instruction) -> bool: return False def is_ret(self, insn: Instruction) -> bool: return insn.mnemonic == "jr" and insn.operands[0] == "ra" def is_conditional_branch(self, insn: Instruction) -> bool: mnemo = insn.mnemonic 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]: mnemo, ops = insn.mnemonic, insn.operands taken, reason = False, "" if mnemo == "beq": taken, reason = gef.arch.register(ops[0]) == gef.arch.register(ops[1]), "{0[0]} == {0[1]}".format(ops) elif mnemo == "bne": taken, reason = gef.arch.register(ops[0]) != gef.arch.register(ops[1]), "{0[0]} != {0[1]}".format(ops) elif mnemo == "beqz": taken, reason = gef.arch.register(ops[0]) == 0, "{0[0]} == 0".format(ops) elif mnemo == "bnez": taken, reason = gef.arch.register(ops[0]) != 0, "{0[0]} != 0".format(ops) elif mnemo == "bgtz": taken, reason = gef.arch.register(ops[0]) > 0, "{0[0]} > 0".format(ops) elif mnemo == "bgez": taken, reason = gef.arch.register(ops[0]) >= 0, "{0[0]} >= 0".format(ops) elif mnemo == "bltz": taken, reason = gef.arch.register(ops[0]) < 0, "{0[0]} < 0".format(ops) elif mnemo == "blez": taken, reason = gef.arch.register(ops[0]) <= 0, "{0[0]} <= 0".format(ops) return taken, reason def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: ra = None if self.is_ret(insn): ra = gef.arch.register("$ra") elif frame.older(): ra = frame.older().pc() return ra @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",] return "; ".join(insns) @register_architecture class MIPS64(MIPS): aliases = ("MIPS64",) arch = "MIPS" mode = "MIPS64" ptrsize = 8 def copy_to_clipboard(data: str) -> None: """Helper function to submit data to the clipboard""" if sys.platform == "linux": xclip = which("xclip") prog = [xclip, "-selection", "clipboard", "-i"] elif sys.platform == "darwin": pbcopy = which("pbcopy") prog = [pbcopy] else: raise NotImplementedError("copy: Unsupported OS") with subprocess.Popen(prog, stdin=subprocess.PIPE) as p: p.stdin.write(data) p.stdin.close() p.wait() return def use_stdtype() -> str: 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" return "unsigned short" def use_golang_type() -> str: 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" return "u16" def to_unsigned_long(v: gdb.Value) -> int: """Cast a gdb.Value to unsigned long.""" mask = (1 << 64) - 1 return int(v.cast(gdb.Value(mask).type)) & mask def get_path_from_info_proc() -> Optional[str]: for x in gdb.execute("info proc", to_string=True).splitlines(): if x.startswith("exe = "): return x.split(" = ")[1].replace("'", "") return None @deprecated("Use `gef.session.os`") def get_os() -> str: return gef.session.os @lru_cache() def is_qemu() -> bool: if not is_remote_debug(): return False response = gdb.execute('maintenance packet Qqemu.sstepbits', to_string=True, from_tty=False) return 'ENABLE=' in response @lru_cache() def is_qemu_usermode() -> bool: if not is_qemu(): return False response = gdb.execute('maintenance packet QOffsets', to_string=True, from_tty=False) return "Text=" in response @lru_cache() def is_qemu_system() -> bool: if not is_qemu(): return False response = gdb.execute('maintenance packet QOffsets', to_string=True, from_tty=False) return 'received: ""' in response @lru_cache() def get_filepath() -> Optional[str]: """Return the local absolute path of the file currently debugged.""" filename = gdb.current_progspace().filename if is_remote_debug(): # if no filename specified, try downloading target from /proc if filename is None: pid = gef.session.pid if pid > 0: return download_file(f"/proc/{pid:d}/exe", use_cache=True) return None # if target is remote file, download elif filename.startswith("target:"): fname = filename[len("target:") :] return download_file(fname, use_cache=True, local_name=fname) elif filename.startswith(".gnu_debugdata for target:"): fname = filename[len(".gnu_debugdata for target:") :] return download_file(fname, use_cache=True, local_name=fname) elif gef.session.remote is not None: return f"/tmp/gef/{gef.session.remote:d}/{get_path_from_info_proc()}" return filename else: if filename is not None: return filename # inferior probably did not have name, extract cmdline from info proc return get_path_from_info_proc() def download_file(remote_path: str, use_cache: bool = False, local_name: Optional[str] = None) -> Optional[str]: """Download filename `remote_path` inside the mirror tree inside the `gef.config["gef.tempdir"]`. The tree architecture must be `gef.config["gef.tempdir"]/gef//`. This allow a "chroot-like" tree format.""" local_root = pathlib.Path(gef.config["gef.tempdir"]) / str(gef.session.pid) if local_name is None: local_path = local_root / remote_path.strip(os.sep) else: local_path = local_root / local_name.strip(os.sep) if use_cache and local_path.exists(): return str(local_path.absolute()) try: local_path.parent.mkdir(parents=True, exist_ok=True) gdb.execute(f"remote get {remote_path} {local_path.absolute()}") local_path = str(local_path.absolute()) except gdb.error: # fallback memory view with open(local_path, "w") as f: if is_32bit(): f.write(f"00000000-ffffffff rwxp 00000000 00:00 0 {get_filepath()}\n") else: f.write(f"0000000000000000-ffffffffffffffff rwxp 00000000 00:00 0 {get_filepath()}\n") except Exception as e: err(f"download_file() failed: {e}") local_path = None return local_path def get_function_length(sym: str) -> int: """Attempt to get the length of the raw bytes of a function.""" dis = gdb.execute(f"disassemble {sym}", to_string=True).splitlines() start_addr = int(dis[1].split()[0], 16) end_addr = int(dis[-2].split()[0], 16) return end_addr - start_addr @lru_cache() def get_info_files() -> List[Zone]: """Retrieve all the files loaded by debuggee.""" lines = gdb.execute("info files", to_string=True).splitlines() infos = [] for line in lines: line = line.strip() if not line: break if not line.startswith("0x"): continue blobs = [x.strip() for x in line.split(" ")] addr_start = int(blobs[0], 16) addr_end = int(blobs[2], 16) section_name = blobs[4] if len(blobs) == 7: filename = blobs[6] else: filename = get_filepath() infos.append(Zone(section_name, addr_start, addr_end, filename)) return infos def process_lookup_address(address: int) -> Optional[Section]: """Look up for an address in memory. Return an Address object if found, None otherwise.""" if not is_alive(): err("Process is not running") return None if is_x86(): if is_in_x86_kernel(address): return None for sect in gef.memory.maps: if sect.page_start <= address < sect.page_end: return sect return None @lru_cache() def process_lookup_path(name: str, perm: Permission = Permission.ALL) -> Optional[Section]: """Look up for a path in the process memory mapping. Return a Section object if found, None otherwise.""" if not is_alive(): err("Process is not running") return None for sect in gef.memory.maps: if name in sect.path and sect.permission & perm: return sect return None @lru_cache() def file_lookup_name_path(name: str, path: str) -> Optional[Zone]: """Look up a file by name and path. Return a Zone object if found, None otherwise.""" for xfile in get_info_files(): if path == xfile.filename and name == xfile.name: return xfile return None @lru_cache() def file_lookup_address(address: int) -> Optional[Zone]: """Look up for a file by its address. Return a Zone object if found, None otherwise.""" for info in get_info_files(): if info.zone_start <= address < info.zone_end: return info return None @lru_cache() def lookup_address(address: int) -> Address: """Try to find the address in the process address space. Return an Address object, with validity flag set based on success.""" sect = process_lookup_address(address) info = file_lookup_address(address) if sect is None and info is None: # i.e. there is no info on this address return Address(value=address, valid=False) return Address(value=address, section=sect, info=info) def xor(data: ByteString, key: str) -> bytearray: """Return `data` xor-ed with `key`.""" key_raw = binascii.unhexlify(key.lstrip("0x")) return bytearray(x ^ y for x, y in zip(data, itertools.cycle(key_raw))) def is_hex(pattern: str) -> bool: """Return whether provided string is a hexadecimal value.""" if not pattern.lower().startswith("0x"): return False return len(pattern) % 2 == 0 and all(c in string.hexdigits for c in pattern[2:]) def ida_synchronize_handler(_: "gdb.Event") -> None: gdb.execute("ida-interact sync", from_tty=True) return def continue_handler(_: "gdb.Event") -> None: """GDB event handler for new object continue cases.""" return def hook_stop_handler(_: "gdb.Event") -> None: """GDB event handler for stop cases.""" reset_all_caches() gdb.execute("context") return def new_objfile_handler(_: "gdb.Event") -> None: """GDB event handler for new object file cases.""" reset_all_caches() reset_architecture() load_libc_args() return def exit_handler(_: "gdb.Event") -> None: """GDB event handler for exit cases.""" reset_all_caches() gef.session.qemu_mode = False if gef.session.remote and gef.config["gef-remote.clean_on_exit"] is True: shutil.rmtree(f"/tmp/gef/{gef.session.remote:d}") gef.session.remote = None return def memchanged_handler(_: "gdb.Event") -> None: """GDB event handler for mem changes cases.""" reset_all_caches() return def regchanged_handler(_: "gdb.Event") -> None: """GDB event handler for reg changes cases.""" reset_all_caches() return def load_libc_args() -> bool: """Load the LIBC function arguments. Returns `True` on success, `False` or an Exception otherwise.""" global gef # load libc function arguments' definitions if not gef.config["context.libc_args"]: return False path = gef.config["context.libc_args_path"] if not path: return False path = pathlib.Path(path).expanduser().absolute() if not path.exists(): raise RuntimeError("Config `context.libc_args_path` set but it's not a directory") _arch_mode = f"{gef.arch.arch.lower()}_{gef.arch.mode}" _libc_args_file = path / f"{_arch_mode}.json" # current arch and mode already loaded if _arch_mode in gef.ui.highlight_table: return True gef.ui.highlight_table[_arch_mode] = {} try: with _libc_args_file.open() as _libc_args: gef.ui.highlight_table[_arch_mode] = json.load(_libc_args) return True except FileNotFoundError: del gef.ui.highlight_table[_arch_mode] warn(f"Config context.libc_args is set but definition cannot be loaded: file {_libc_args_file} not found") except json.decoder.JSONDecodeError as e: del gef.ui.highlight_table[_arch_mode] warn(f"Config context.libc_args is set but definition cannot be loaded from file {_libc_args_file}: {e}") return False def get_terminal_size() -> Tuple[int, int]: """Return the current terminal size.""" if is_debug(): return 600, 100 if platform.system() == "Windows": from ctypes import windll, create_string_buffer 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) tty_columns = right - left + 1 tty_rows = bottom - top + 1 return tty_rows, tty_columns else: return 600, 100 else: import fcntl import termios try: tty_rows, tty_columns = struct.unpack("hh", fcntl.ioctl(1, termios.TIOCGWINSZ, "1234")) return tty_rows, tty_columns except OSError: return 600, 100 def get_generic_arch(module: ModuleType, prefix: str, arch: str, mode: Optional[str], big_endian: Optional[bool], to_string: bool = False) -> Tuple[str, Union[int, str]]: """ Retrieves architecture and mode from the arguments for use for the holy {cap,key}stone/unicorn trinity. """ if to_string: arch = f"{module.__name__}.{prefix}_ARCH_{arch}" if mode: mode = f"{module.__name__}.{prefix}_MODE_{mode}" else: mode = "" if gef.arch.endianness == Endianness.BIG_ENDIAN: mode += f" + {module.__name__}.{prefix}_MODE_BIG_ENDIAN" else: mode += f" + {module.__name__}.{prefix}_MODE_LITTLE_ENDIAN" else: arch = getattr(module, f"{prefix}_ARCH_{arch}") if mode: mode = getattr(module, f"{prefix}_MODE_{mode}") else: mode = 0 if big_endian: mode |= getattr(module, f"{prefix}_MODE_BIG_ENDIAN") else: mode |= getattr(module, f"{prefix}_MODE_LITTLE_ENDIAN") return arch, mode def get_generic_running_arch(module: ModuleType, prefix: str, to_string: bool = False) -> Union[Tuple[None, None], Tuple[str, Union[int, str]]]: """ Retrieves architecture and mode from the current context. """ if not is_alive(): return None, None if gef.arch is not None: arch, mode = gef.arch.arch, gef.arch.mode else: raise OSError("Emulation not supported for your OS") return get_generic_arch(module, prefix, arch, mode, gef.arch.endianness == Endianness.BIG_ENDIAN, to_string) def get_unicorn_arch(arch: Optional[str] = None, mode: Optional[str] = None, endian: Optional[bool] = None, to_string: bool = False) -> Union[Tuple[None, None], Tuple[str, Union[int, str]]]: unicorn = sys.modules["unicorn"] if (arch, mode, endian) == (None, None, None): return get_generic_running_arch(unicorn, "UC", to_string) return get_generic_arch(unicorn, "UC", arch, mode, endian, to_string) def get_capstone_arch(arch: Optional[str] = None, mode: Optional[str] = None, endian: Optional[bool] = None, to_string: bool = False) -> Union[Tuple[None, None], Tuple[str, Union[int, str]]]: capstone = sys.modules["capstone"] # hacky patch to unify capstone/ppc syntax with keystone & unicorn: # CS_MODE_PPC32 does not exist (but UC_MODE_32 & KS_MODE_32 do) if is_arch(Elf.Abi.POWERPC64): raise OSError("Capstone not supported for PPC64 yet.") if is_alive() and is_arch(Elf.Abi.POWERPC): arch = "PPC" mode = "32" endian = (gef.arch.endianness == Endianness.BIG_ENDIAN) return get_generic_arch(capstone, "CS", arch or gef.arch.arch, mode or gef.arch.mode, endian, to_string) if (arch, mode, endian) == (None, None, None): return get_generic_running_arch(capstone, "CS", to_string) return get_generic_arch(capstone, "CS", arch or gef.arch.arch, mode or gef.arch.mode, endian or gef.arch.endianness == Endianness.BIG_ENDIAN, to_string) def get_keystone_arch(arch: Optional[str] = None, mode: Optional[str] = None, endian: Optional[bool] = None, to_string: bool = False) -> Union[Tuple[None, None], Tuple[str, Union[int, str]]]: keystone = sys.modules["keystone"] if (arch, mode, endian) == (None, None, None): return get_generic_running_arch(keystone, "KS", to_string) if arch in ["ARM64", "SYSTEMZ"]: modes = [None] elif arch == "ARM" and mode == "ARMV8": modes = ["ARM", "V8"] elif arch == "ARM" and mode == "THUMBV8": modes = ["THUMB", "V8"] else: modes = [mode] a = arch if not to_string: mode = 0 for m in modes: arch, _mode = get_generic_arch(keystone, "KS", a, m, endian, to_string) mode |= _mode else: mode = "" for m in modes: arch, _mode = get_generic_arch(keystone, "KS", a, m, endian, to_string) mode += f"|{_mode}" mode = mode[1:] return arch, mode def get_unicorn_registers(to_string: bool = False) -> Union[Dict[str, int], Dict[str, str]]: "Return a dict matching the Unicorn identifier for a specific register." unicorn = sys.modules["unicorn"] regs = {} if gef.arch is not None: arch = gef.arch.arch.lower() else: raise OSError("Oops") const = getattr(unicorn, f"{arch}_const") for reg in gef.arch.all_registers: regname = f"UC_{arch.upper()}_REG_{reg[1:].upper()}" if to_string: regs[reg] = f"{const.__name__}.{regname}" else: regs[reg] = getattr(const, regname) return regs def keystone_assemble(code: str, arch: int, mode: int, **kwargs: Any) -> Optional[Union[str, bytearray]]: """Assembly encoding function based on keystone.""" keystone = sys.modules["keystone"] code = gef_pybytes(code) addr = kwargs.get("addr", 0x1000) try: ks = keystone.Ks(arch, mode) enc, cnt = ks.asm(code, addr) except keystone.KsError as e: err(f"Keystone assembler error: {e}") return None if cnt == 0: return "" enc = bytearray(enc) if "raw" not in kwargs: s = binascii.hexlify(enc) enc = b"\\x" + b"\\x".join([s[i : i + 2] for i in range(0, len(s), 2)]) enc = enc.decode("utf-8") return enc @lru_cache() def get_elf_headers(filename: Optional[str] = None) -> Optional[Elf]: """Return an Elf object with info from `filename`. If not provided, will return the currently debugged file.""" if not filename: filename = get_filepath() if not filename: raise Exception("No file provided") if filename.startswith("target:"): warn("Your file is remote, you should try using `gef-remote` instead") return return Elf(filename) @lru_cache() def is_64bit() -> bool: """Checks if current target is 64bit.""" return gef.arch.ptrsize == 8 @lru_cache() def is_32bit() -> bool: """Checks if current target is 32bit.""" return gef.arch.ptrsize == 4 @lru_cache() def is_x86_64() -> bool: """Checks if current target is x86-64""" return Elf.Abi.X86_64 in gef.arch.aliases @lru_cache() def is_x86_32(): """Checks if current target is an x86-32""" return Elf.Abi.X86_32 in gef.arch.aliases @lru_cache() def is_x86() -> bool: return is_x86_32() or is_x86_64() @lru_cache() def is_arch(arch: Elf.Abi) -> bool: return arch in gef.arch.aliases def reset_architecture(arch: Optional[str] = None, default: Optional[str] = None) -> None: """Sets the current architecture. If an arch is explicitly specified, use that one, otherwise try to parse it out of the current target. If that fails, and default is specified, select and set that arch. Raise an exception if the architecture cannot be set. Does not return a value. """ global gef arches = __registered_architectures__ if arch: try: gef.arch = arches[arch.upper()]() return except KeyError: raise OSError(f"Specified arch {arch.upper()} is not supported") if not gef.binary: gef.binary = get_elf_headers() arch_name = gef.binary.e_machine if gef.binary else get_arch() if ((arch_name == "MIPS" or arch_name == Elf.Abi.MIPS) and (gef.binary is not None and gef.binary.e_class == Elf.Class.ELF_64_BITS)): # MIPS64 = arch(MIPS) + 64b flag arch_name = "MIPS64" try: gef.arch = arches[arch_name]() except KeyError: if default: try: gef.arch = arches[default.upper()]() except KeyError: raise OSError(f"CPU not supported, neither is default {default.upper()}") else: raise OSError(f"CPU type is currently not supported: {get_arch()}") return @lru_cache() def cached_lookup_type(_type: str) -> Optional[gdb.Type]: try: return gdb.lookup_type(_type).strip_typedefs() except RuntimeError: return None @deprecated("Use `gef.arch.ptrsize` instead") def get_memory_alignment(in_bits: bool = False) -> int: """Try to determine the size of a pointer on this system. First, try to parse it out of the ELF header. Next, use the size of `size_t`. Finally, try the size of $pc. If `in_bits` is set to True, the result is returned in bits, otherwise in bytes.""" res = cached_lookup_type("size_t") if res is not None: return res.sizeof if not in_bits else res.sizeof * 8 try: return gdb.parse_and_eval("$pc").type.sizeof except: pass raise OSError("GEF is running under an unsupported mode") def clear_screen(tty: str = "") -> None: """Clear the screen.""" global gef if not tty: gdb.execute("shell clear -x") return # Since the tty can be closed at any time, a PermissionError exception can # occur when `clear_screen` is called. We handle this scenario properly try: with open(tty, "wt") as f: f.write("\x1b[H\x1b[J") except PermissionError: gef.ui.redirect_fd = None gef.config["context.redirect"] = "" return def format_address(addr: int) -> str: """Format the address according to its size.""" memalign_size = gef.arch.ptrsize addr = align_address(addr) if memalign_size == 4: return f"{addr:#08x}" return f"{addr:#016x}" def format_address_spaces(addr: int, left: bool = True) -> str: """Format the address according to its size, but with spaces instead of zeroes.""" width = gef.arch.ptrsize * 2 + 2 addr = align_address(addr) if not left: return f"{addr:#x}".rjust(width) return f"{addr:#x}".ljust(width) def align_address(address: int) -> int: """Align the provided address to the process's native length.""" if gef.arch.ptrsize == 4: return address & 0xFFFFFFFF return address & 0xFFFFFFFFFFFFFFFF def align_address_to_size(address: int, align: int) -> int: """Align the address to the given size.""" return address + ((align - (address % align)) % align) def align_address_to_page(address: int) -> int: """Align the address to a page.""" a = align_address(address) >> DEFAULT_PAGE_ALIGN_SHIFT return a << DEFAULT_PAGE_ALIGN_SHIFT def malloc_align_address(address: int) -> int: """Align addresses according to glibc's MALLOC_ALIGNMENT. See also Issue #689 on Github""" __default_malloc_alignment = 0x10 if is_x86_32() and get_libc_version() >= (2, 26): # Special case introduced in Glibc 2.26: # https://elixir.bootlin.com/glibc/glibc-2.26/source/sysdeps/i386/malloc-alignment.h#L22 malloc_alignment = __default_malloc_alignment else: # Generic case: # https://elixir.bootlin.com/glibc/glibc-2.26/source/sysdeps/generic/malloc-alignment.h#L22 __alignof__long_double = int(safe_parse_and_eval("_Alignof(long double)") or __default_malloc_alignment) # fallback to default if the expression fails to evaluate malloc_alignment = max(__alignof__long_double, 2 * gef.arch.ptrsize) ceil = lambda n: int(-1 * n // 1 * -1) # align address to nearest next multiple of malloc_alignment return malloc_alignment * ceil((address / malloc_alignment)) def parse_address(address: str) -> int: """Parse an address and return it as an Integer.""" if is_hex(address): return int(address, 16) return to_unsigned_long(gdb.parse_and_eval(address)) def is_in_x86_kernel(address: int) -> bool: address = align_address(address) memalign = gef.arch.ptrsize*8 - 1 return (address >> memalign) == 0xF @lru_cache() def is_remote_debug() -> bool: """"Return True is the current debugging session is running through GDB remote session.""" return gef.session.remote is not None or "remote" in gdb.execute("maintenance print target-stack", to_string=True) def de_bruijn(alphabet: bytes, n: int) -> Generator[str, None, None]: """De Bruijn sequence for alphabet and subsequences of length n (for compat. w/ pwnlib).""" k = len(alphabet) a = [0] * k * n def db(t: int, p: int) -> Generator[str, None, None]: if t > n: if n % p == 0: for j in range(1, p + 1): yield alphabet[a[j]] else: a[t] = a[t - p] yield from db(t + 1, p) for j in range(a[t - p] + 1, k): a[t] = j yield from db(t + 1, t) return db(1, 1) def generate_cyclic_pattern(length: int, cycle: int = 4) -> bytearray: """Create a `length` byte bytearray of a de Bruijn cyclic pattern.""" charset = bytearray(b"abcdefghijklmnopqrstuvwxyz") return bytearray(itertools.islice(de_bruijn(charset, cycle), length)) def safe_parse_and_eval(value: str) -> Optional["gdb.Value"]: """GEF wrapper for gdb.parse_and_eval(): this function returns None instead of raising gdb.error if the eval failed.""" try: return gdb.parse_and_eval(value) except gdb.error: pass return None @lru_cache() def dereference(addr: int) -> Optional["gdb.Value"]: """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()) unsigned_long_type = ulong_t.pointer() res = gdb.Value(addr).cast(unsigned_long_type).dereference() # GDB does lazy fetch by default so we need to force access to the value res.fetch_lazy() return res except gdb.MemoryError: pass return None def gef_convenience(value: str) -> str: """Defines a new convenience value.""" global gef var_name = f"$_gef{gef.session.convenience_vars_index:d}" gef.session.convenience_vars_index += 1 gdb.execute(f"""set {var_name} = "{value}" """) return var_name def parse_string_range(s: str) -> Iterator[int]: """Parses an address range (e.g. 0x400000-0x401000)""" addrs = s.split("-") return map(lambda x: int(x, 16), addrs) @deprecated("Use `gef.session.pie_breakpoints[num]`") def gef_get_pie_breakpoint(num: int) -> "PieVirtualBreakpoint": return gef.session.pie_breakpoints[num] # # Deprecated API # @deprecated("Use `str(gef.arch.endianness)` instead") def endian_str() -> str: return str(gef.arch.endianness) @deprecated("Use `gef.config[key]`") def get_gef_setting(name: str) -> Any: return gef.config[name] @deprecated("Use `gef.config[key] = value`") def set_gef_setting(name: str, value: Any) -> None: gef.config[name] = value return @deprecated("Use `gef.session.pagesize`") def gef_getpagesize() -> int: return gef.session.pagesize @deprecated("Use `gef.session.canary`") def gef_read_canary() -> Optional[Tuple[int, int]]: return gef.session.canary @deprecated("Use `gef.session.pid`") def get_pid() -> int: return gef.session.pid @deprecated("Use `gef.session.file.name`") def get_filename() -> str: return gef.session.file.name @deprecated("Use `gef.heap.main_arena`") def get_glibc_arena() -> Optional[GlibcArena]: return gef.heap.main_arena @deprecated("Use `gef.arch.register(regname)`") def get_register(regname) -> Optional[int]: return gef.arch.register(regname) @deprecated("Use `gef.memory.maps`") def get_process_maps() -> List[Section]: return gef.memory.maps @deprecated("Use `reset_architecture`") def set_arch(arch: Optional[str] = None, default: Optional[str] = None) -> None: return reset_architecture(arch, default) # # GDB event hooking # @only_if_events_supported("cont") def gef_on_continue_hook(func: Callable[["gdb.Event"], None]) -> None: gdb.events.cont.connect(func) @only_if_events_supported("cont") def gef_on_continue_unhook(func: Callable[["gdb.Event"], None]) -> None: gdb.events.cont.disconnect(func) @only_if_events_supported("stop") def gef_on_stop_hook(func: Callable[["gdb.Event"], None]) -> None: gdb.events.stop.connect(func) @only_if_events_supported("stop") def gef_on_stop_unhook(func: Callable[["gdb.Event"], None]) -> None: gdb.events.stop.disconnect(func) @only_if_events_supported("exited") def gef_on_exit_hook(func: Callable[["gdb.Event"], None]) -> None: gdb.events.exited.connect(func) @only_if_events_supported("exited") def gef_on_exit_unhook(func: Callable[["gdb.Event"], None]) -> None: gdb.events.exited.disconnect(func) @only_if_events_supported("new_objfile") def gef_on_new_hook(func: Callable[["gdb.Event"], None]) -> None: gdb.events.new_objfile.connect(func) @only_if_events_supported("new_objfile") def gef_on_new_unhook(func: Callable[["gdb.Event"], None]) -> None: gdb.events.new_objfile.disconnect(func) @only_if_events_supported("memory_changed") def gef_on_memchanged_hook(func: Callable[["gdb.Event"], None]) -> None: gdb.events.memory_changed.connect(func) @only_if_events_supported("memory_changed") def gef_on_memchanged_unhook(func: Callable[["gdb.Event"], None]) -> None: gdb.events.memory_changed.disconnect(func) @only_if_events_supported("register_changed") def gef_on_regchanged_hook(func: Callable[["gdb.Event"], None]) -> None: gdb.events.register_changed.connect(func) @only_if_events_supported("register_changed") def gef_on_regchanged_unhook(func: Callable[["gdb.Event"], None]) -> None: gdb.events.register_changed.disconnect(func) # # Virtual breakpoints # class PieVirtualBreakpoint: """PIE virtual breakpoint (not real breakpoint).""" def __init__(self, set_func: Callable[[int], str], vbp_num: int, addr: int) -> None: # set_func(base): given a base address return a # "set breakpoint" gdb command string self.set_func = set_func self.vbp_num = vbp_num # breakpoint num, 0 represents not instantiated yet self.bp_num = 0 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) else: self.addr = addr return def instantiate(self, base: int) -> None: if self.bp_num: self.destroy() try: res = gdb.execute(self.set_func(base), to_string=True) except gdb.error as e: err(e) return if "Breakpoint" not in res: err(res) return res_list = res.split() self.bp_num = res_list[1] self.bp_addr = res_list[3] return def destroy(self) -> None: if not self.bp_num: err("Destroy PIE breakpoint not even set") return gdb.execute(f"delete {self.bp_num}") self.bp_num = 0 return # # 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 self.enabled = True return def stop(self) -> bool: reset_all_caches() msg = [] ptr, addr = gef.arch.get_ith_parameter(self.num_args) addr = lookup_address(addr) if not addr.valid: return False if addr.section.is_writable(): 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") push_context_message("warn", "\n".join(msg)) return True return False 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: super().__init__(func, gdb.BP_BREAKPOINT, internal=False) self.func = func self.retval = retval m = f"All calls to '{self.func}' will be skipped" if self.retval is not None: m += f" (with return value set to {self.retval:#x})" info(m) return def stop(self) -> bool: gdb.execute(f"return (unsigned int){self.retval:#x}") ok(f"Ignoring call to '{self.func}' " f"(setting return value to {self.retval:#x})") return False class ChangePermissionBreakpoint(gdb.Breakpoint): """When hit, this temporary breakpoint will restore the original code, and position $pc correctly.""" def __init__(self, loc: str, code: ByteString, pc: int) -> None: super().__init__(loc, gdb.BP_BREAKPOINT, internal=False) self.original_code = code self.original_pc = pc return def stop(self) -> bool: info("Restoring original context") gef.memory.write(self.original_pc, self.original_code, len(self.original_code)) info("Restoring $pc") gdb.execute(f"set $pc = {self.original_pc:#x}") return True class TraceMallocBreakpoint(gdb.Breakpoint): """Track allocations done with malloc() or calloc().""" def __init__(self, name: str) -> None: super().__init__(name, gdb.BP_BREAKPOINT, internal=True) self.silent = True self.name = name return def stop(self) -> bool: reset_all_caches() _, size = gef.arch.get_ith_parameter(0) self.retbp = TraceMallocRetBreakpoint(size, self.name) return False class TraceMallocRetBreakpoint(gdb.FinishBreakpoint): """Internal temporary breakpoint to retrieve the return value of malloc().""" def __init__(self, size: int, name: str) -> None: super().__init__(gdb.newest_frame(), internal=True) self.size = size self.name = name self.silent = True return def stop(self) -> bool: if self.return_value: loc = int(self.return_value) else: loc = parse_address(gef.arch.return_register) size = self.size 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 if gef.session.heap_freed_chunks: idx = 0 for item in gef.session.heap_freed_chunks: addr = item[0] if addr == loc: gef.session.heap_freed_chunks.remove(item) continue idx += 1 # pop from uaf watchlist if gef.session.heap_uaf_watchpoints: idx = 0 for wp in gef.session.heap_uaf_watchpoints: wp_addr = wp.address if loc <= wp_addr < loc + size: gef.session.heap_uaf_watchpoints.remove(wp) wp.enabled = False continue idx += 1 item = (loc, size) if check_heap_overlap: # seek all the currently allocated chunks, read their effective size and check for overlap msg = [] align = gef.arch.ptrsize for chunk_addr, _ in gef.session.heap_allocated_chunks: current_chunk = GlibcChunk(chunk_addr) current_chunk_size = current_chunk.get_chunk_size() if chunk_addr <= loc < chunk_addr + current_chunk_size: 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(" data = 'A'*{0:d} + 'B'*{1:d} + 'C'*{1:d}".format(offset, align)) push_context_message("warn", "\n".join(msg)) return True # add it to alloc-ed list gef.session.heap_allocated_chunks.append(item) return False class TraceReallocBreakpoint(gdb.Breakpoint): """Track re-allocations done with realloc().""" def __init__(self) -> None: super().__init__("__libc_realloc", gdb.BP_BREAKPOINT, internal=True) self.silent = True return def stop(self) -> bool: _, ptr = gef.arch.get_ith_parameter(0) _, size = gef.arch.get_ith_parameter(1) self.retbp = TraceReallocRetBreakpoint(ptr, size) return False class TraceReallocRetBreakpoint(gdb.FinishBreakpoint): """Internal temporary breakpoint to retrieve the return value of realloc().""" def __init__(self, ptr: int, size: int) -> None: super().__init__(gdb.newest_frame(), internal=True) self.ptr = ptr self.size = size self.silent = True return def stop(self) -> bool: if self.return_value: newloc = int(self.return_value) else: newloc = parse_address(gef.arch.return_register) if newloc != self: ok("{} - realloc({:#x}, {})={}".format(Color.colorify("Heap-Analysis", "yellow bold"), self.ptr, self.size, Color.colorify(f"{newloc:#x}", "green"),)) else: ok("{} - realloc({:#x}, {})={}".format(Color.colorify("Heap-Analysis", "yellow bold"), self.ptr, self.size, Color.colorify(f"{newloc:#x}", "red"),)) item = (newloc, self.size) try: # check if item was in alloc-ed list idx = [x for x, y in gef.session.heap_allocated_chunks].index(self.ptr) # if so pop it out item = gef.session.heap_allocated_chunks.pop(idx) except ValueError: if is_debug(): warn(f"Chunk {self.ptr:#x} was not in tracking list") finally: # add new item to alloc-ed list gef.session.heap_allocated_chunks.append(item) return False class TraceFreeBreakpoint(gdb.Breakpoint): """Track calls to free() and attempts to detect inconsistencies.""" def __init__(self) -> None: super().__init__("__libc_free", gdb.BP_BREAKPOINT, internal=True) self.silent = True return def stop(self) -> bool: reset_all_caches() _, addr = gef.arch.get_ith_parameter(0) msg = [] check_free_null = gef.config["heap-analysis-helper.check_free_null"] check_double_free = gef.config["heap-analysis-helper.check_double_free"] check_weird_free = gef.config["heap-analysis-helper.check_weird_free"] check_uaf = gef.config["heap-analysis-helper.check_uaf"] ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - free({addr:#x})") if addr == 0: 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.") push_context_message("warn", "\n".join(msg)) return True return False if addr in [x for (x, y) 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("Execution will likely crash...") push_context_message("warn", "\n".join(msg)) return True return False # if here, no error # 1. move alloc-ed item to free list try: # pop from alloc-ed list idx = [x for x, y in gef.session.heap_allocated_chunks].index(addr) item = gef.session.heap_allocated_chunks.pop(idx) except ValueError: if check_weird_free: msg.append(Color.colorify("Heap-Analysis", "yellow bold")) msg.append("Heap inconsistency detected:") msg.append(f"Attempting to free an unknown value: {addr:#x}") push_context_message("warn", "\n".join(msg)) return True return False # 2. add it to free-ed list gef.session.heap_freed_chunks.append(item) self.retbp = None if check_uaf: # 3. (opt.) add a watchpoint on pointer self.retbp = TraceFreeRetBreakpoint(addr) return False class TraceFreeRetBreakpoint(gdb.FinishBreakpoint): """Internal temporary breakpoint to track free()d values.""" def __init__(self, addr: int) -> None: super().__init__(gdb.newest_frame(), internal=True) self.silent = True self.addr = addr return def stop(self) -> bool: reset_all_caches() wp = UafWatchpoint(self.addr) gef.session.heap_uaf_watchpoints.append(wp) return False class UafWatchpoint(gdb.Breakpoint): """Custom watchpoints set TraceFreeBreakpoint() to monitor free()d pointers being used.""" def __init__(self, addr: int) -> None: super().__init__(f"*{addr:#x}", gdb.BP_WATCHPOINT, internal=True) self.address = addr self.silent = True self.enabled = True return 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", ): return False # software watchpoints stop after the next statement (see # https://sourceware.org/gdb/onlinedocs/gdb/Set-Watchpoints.html) pc = gdb_get_nth_previous_instruction_address(gef.arch.pc, 2) 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))}") push_context_message("warn", "\n".join(msg)) return True class EntryBreakBreakpoint(gdb.Breakpoint): """Breakpoint used internally to stop execution at the most convenient entry point.""" def __init__(self, location: str) -> None: super().__init__(location, gdb.BP_BREAKPOINT, internal=True, temporary=True) self.silent = True return def stop(self) -> bool: reset_all_caches() return True 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) 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')})") return True # # Context Panes # def register_external_context_pane(pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], Optional[str]]) -> None: """ Registering function for new GEF Context View. pane_name: a string that has no spaces (used in settings) display_pane_function: a function that uses gef_print() to print strings pane_title_function: a function that returns a string or None, which will be displayed as the title. If None, no title line is displayed. Example Usage: def display_pane(): gef_print("Wow, I am a context pane!") def pane_title(): return "example:pane" register_external_context_pane("example_pane", display_pane, pane_title) """ gef.gdb.add_context_pane(pane_name, display_pane_function, pane_title_function) return # # Commands # def register_external_command(obj: "GenericCommand") -> Type["GenericCommand"]: """Registering function for new GEF (sub-)command to GDB.""" cls = obj.__class__ __registered_commands__.append(cls) gef.gdb.load(initial=False) gef.gdb.doc.add_command_to_doc((cls._cmdline_, cls, None)) gef.gdb.doc.refresh() return cls def register_command(cls: Type["GenericCommand"]) -> Type["GenericCommand"]: """Decorator for registering new GEF (sub-)command to GDB.""" __registered_commands__.append(cls) return cls def register_priority_command(cls: Type["GenericCommand"]) -> Type["GenericCommand"]: """Decorator for registering new command with priority, meaning that it must loaded before the other generic commands.""" __registered_commands__.insert(0, cls) return cls def register_function(cls: Type["GenericFunction"]) -> Type["GenericFunction"]: """Decorator for registering a new convenience function to GDB.""" __registered_functions__.append(cls) return cls class GenericCommand(gdb.Command, metaclass=abc.ABCMeta): """This is an abstract class for invoking commands, should not be instantiated.""" def __init__(self, *args: Any, **kwargs: Any) -> None: self.pre_load() syntax = Color.yellowify("\nSyntax: ") + self._syntax_ example = Color.yellowify("\nExample: ") + self._example_ if self._example_ else "" self.__doc__ = self.__doc__.replace(" "*4, "") + syntax + example self.repeat = False self.repeat_count = 0 self.__last_command = None command_type = kwargs.setdefault("command", gdb.COMMAND_OBSCURE) complete_type = kwargs.setdefault("complete", gdb.COMPLETE_NONE) prefix = kwargs.setdefault("prefix", False) super().__init__(self._cmdline_, command_type, complete_type, prefix) self.post_load() return def invoke(self, args: str, from_tty: bool) -> None: try: argv = gdb.string_to_argv(args) self.__set_repeat_count(argv, from_tty) bufferize(self.do_invoke)(argv) except Exception as e: # Note: since we are intercepting cleaning exceptions here, commands preferably should avoid # catching generic Exception, but rather specific ones. This is allows a much cleaner use. if is_debug(): show_last_exception() else: err(f"Command '{self._cmdline_}' failed to execute properly, reason: {e}") return def usage(self) -> None: err(f"Syntax\n{self._syntax_}") return @abc.abstractproperty def _cmdline_(self) -> Optional[str]: pass @abc.abstractproperty def _syntax_(self) -> Optional[str]: pass @abc.abstractproperty def _example_(self) -> str: return "" @abc.abstractmethod def do_invoke(self, argv: List[str]) -> None: pass def pre_load(self) -> None: pass def post_load(self) -> None: pass def __get_setting_name(self, name: str) -> str: clsname = self.__class__._cmdline_.replace(" ", "-") return f"{clsname}.{name}" def __iter__(self) -> Generator[str, None, None]: for key in gef.config.keys(): if key.startswith(self._cmdline_): yield key.replace(f"{self._cmdline_}.", "", 1) @property def settings(self) -> List[str]: """Return the list of settings for this command.""" return list(iter(self)) @deprecated("") def get_setting(self, name: str) -> Any: return self.__getitem__(name) def __getitem__(self, name: str) -> Any: key = self.__get_setting_name(name) return gef.config[key] @deprecated("") 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("") def add_setting(self, name: str, value: Tuple[Any, type, str], description: str = "") -> None: return self.__setitem__(name, (value, type(value), description)) def __setitem__(self, name: str, value: Union[Any, 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 key = self.__get_setting_name(name) if key in gef.config: setting = gef.config.raw_entry(key) setting.value = value else: if len(value) == 1: gef.config[key] = GefSetting(value[0]) elif len(value) == 2: gef.config[key] = GefSetting(value[0], description=value[1]) return @deprecated("") def del_setting(self, name: str) -> None: return self.__delitem__(name) 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: if not from_tty: self.repeat = False self.repeat_count = 0 return command = gdb.execute("show commands", to_string=True).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 return @register_command class VersionCommand(GenericCommand): """Display GEF version info.""" _cmdline_ = "version" _syntax_ = f"{_cmdline_}" _example_ = f"{_cmdline_}" 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: gef_hash = hashlib.sha256(f.read()).hexdigest() if os.access(f"{gef_dir}/.git", os.X_OK): 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_print("GEF: (Standalone)") gef_print(f"Blob Hash({gef_fpath}): {gef_blob_hash}") gef_print(f"SHA256({gef_fpath}): {gef_hash}") gef_print(f"GDB: {gdb.VERSION}") py_ver = f"{sys.version_info.major:d}.{sys.version_info.minor:d}" gef_print(f"GDB-Python: {py_ver}") if "full" in argv: gef_print(f"Loaded commands: {', '.join(gef.gdb.loaded_command_names)}") return @register_command class PrintFormatCommand(GenericCommand): """Print bytes format in commonly used formats, such as literals in high level languages.""" valid_formats = ("py", "c", "js", "asm", "hex") 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.") _example_ = f"{_cmdline_} --lang py -l 16 $rsp" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) return @property 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"), 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": True,}) def do_invoke(self, _: List[str], **kwargs: Any) -> None: """Default value for print-format command.""" args = kwargs["arguments"] args.bitlen = args.bitlen or gef.arch.ptrsize * 2 valid_bitlens = self.format_matrix.keys() if args.bitlen not in valid_bitlens: err(f"Size of bit must be in: {valid_bitlens!s}") return if args.lang not in self.valid_formats: err(f"Language must be in: {self.valid_formats!s}") return start_addr = parse_address(args.location) size = int(args.bitlen / 8) end_addr = start_addr + args.length * size fmt = self.format_matrix[args.bitlen][0] data = [] for addr in range(start_addr, end_addr, size): value = struct.unpack(fmt, gef.memory.read(addr, size))[0] data += [value] sdata = ", ".join(map(hex, data)) if args.lang == "py": out = f"buf = [{sdata}]" elif args.lang == "c": c_type = self.format_matrix[args.bitlen][1] out = f"unsigned {c_type} buf[{args.length}] = {{{sdata}}};" elif args.lang == "js": out = f"var buf = [{sdata}]" elif args.lang == "asm": asm_type = self.format_matrix[args.bitlen][2] out = "buf {0} {1}".format(asm_type, sdata) elif args.lang == "hex": out = binascii.hexlify(gef.memory.read(start_addr, end_addr-start_addr)).decode() if args.clip: if copy_to_clipboard(gef_pybytes(out)): info("Copied to clipboard") else: warn("There's a problem while copying") gef_print(out) return @register_command class PieCommand(GenericCommand): """PIE breakpoint support.""" _cmdline_ = "pie" _syntax_ = f"{_cmdline_} (breakpoint|info|delete|run|attach|remote)" def __init__(self) -> None: super().__init__(prefix=True) return def do_invoke(self, argv: List[str]) -> None: if not argv: self.usage() return @register_command class PieBreakpointCommand(GenericCommand): """Set a PIE breakpoint at an offset from the target binaries base address.""" _cmdline_ = "pie breakpoint" _syntax_ = f"{_cmdline_} OFFSET" @parse_arguments({"offset": ""}, {}) def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = kwargs["arguments"] if not args.offset: self.usage() return addr = parse_address(args.offset) self.set_pie_breakpoint(lambda base: f"b *{base + addr}", addr) # When the process is already on, set real breakpoints immediately if is_alive(): vmmap = gef.memory.maps base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0] for bp_ins in gef.session.pie_breakpoints.values(): bp_ins.instantiate(base_address) return @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_counter += 1 return @register_command class PieInfoCommand(GenericCommand): """Display breakpoint info.""" _cmdline_ = "pie info" _syntax_ = f"{_cmdline_} BREAKPOINT" @parse_arguments({"breakpoints": [-1,]}, {}) def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = kwargs["arguments"] if args.breakpoints[0] == -1: # No breakpoint info needed bps = [gef.session.pie_breakpoints[x] for x in gef.session.pie_breakpoints] else: bps = [gef.session.pie_breakpoints[x] for x in args.breakpoints] lines = [] lines.append("VNum\tNum\tAddr") lines += [ f"{x.vbp_num}\t{x.bp_num if x.bp_num else 'N/A'}\t{x.addr}" for x in bps ] gef_print("\n".join(lines)) return @register_command class PieDeleteCommand(GenericCommand): """Delete a PIE breakpoint.""" _cmdline_ = "pie delete" _syntax_ = f"{_cmdline_} [BREAKPOINT]" @parse_arguments({"breakpoints": [-1,]}, {}) def do_invoke(self, _: List[str], **kwargs: Any) -> None: global gef args = kwargs["arguments"] if args.breakpoints[0] == -1: # no arg, delete all to_delete = [gef.session.pie_breakpoints[x] for x in gef.session.pie_breakpoints] self.delete_bp(to_delete) else: self.delete_bp([gef.session.pie_breakpoints[x] for x in args.breakpoints]) return @staticmethod def delete_bp(breakpoints: List) -> None: global gef for bp in breakpoints: # delete current real breakpoints if exists if bp.bp_num: gdb.execute(f"delete {bp.bp_num}") # delete virtual breakpoints del gef.session.pie_breakpoints[bp.vbp_num] return @register_command class PieRunCommand(GenericCommand): """Run process with PIE breakpoint support.""" _cmdline_ = "pie run" _syntax_ = _cmdline_ def do_invoke(self, argv: List[str]) -> None: global gef fpath = get_filepath() if fpath is None: warn("No executable to debug, use `file` to load a binary") return if not os.access(fpath, os.X_OK): warn(f"The file '{fpath}' is not executable.") return if is_alive(): warn("gdb is already running. Restart process.") # get base address gdb.execute("set stop-on-solib-events 1") hide_context() gdb.execute(f"run {' '.join(argv)}") unhide_context() gdb.execute("set stop-on-solib-events 0") vmmap = gef.memory.maps base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0] info(f"base address {hex(base_address)}") # modify all breakpoints for bp_ins in gef.session.pie_breakpoints.values(): bp_ins.instantiate(base_address) try: gdb.execute("continue") except gdb.error as e: err(e) gdb.execute("kill") return @register_command class PieAttachCommand(GenericCommand): """Do attach with PIE breakpoint support.""" _cmdline_ = "pie attach" _syntax_ = f"{_cmdline_} PID" def do_invoke(self, argv: List[str]) -> None: try: gdb.execute(f"attach {' '.join(argv)}", to_string=True) except gdb.error as e: err(e) return # after attach, we are stopped so that we can # get base address to modify our breakpoint vmmap = gef.memory.maps base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0] for bp_ins in gef.session.pie_breakpoints.values(): bp_ins.instantiate(base_address) gdb.execute("context") return @register_command class PieRemoteCommand(GenericCommand): """Attach to a remote connection with PIE breakpoint support.""" _cmdline_ = "pie remote" _syntax_ = f"{_cmdline_} REMOTE" def do_invoke(self, argv: List[str]) -> None: try: gdb.execute(f"gef-remote {' '.join(argv)}") except gdb.error as e: err(e) return # after remote attach, we are stopped so that we can # get base address to modify our breakpoint vmmap = gef.memory.maps base_address = [x.page_start for x in vmmap if x.realpath == get_filepath()][0] for bp_ins in gef.session.pie_breakpoints.values(): bp_ins.instantiate(base_address) gdb.execute("context") return @register_command 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") def do_invoke(self, argv: List[str]) -> None: argc = len(argv) if argc == 1: self.evaluate(argv) return if argc == 2: self.distance(argv) return 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}" try: s_i = comp2_x(res) 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)) gef_print(f"{binascii.unhexlify(s_i)}") gef_print(f"{binascii.unhexlify(s_i)[::-1]}") except: pass return parsed_expr = [] for xp in expr: try: xp = gdb.parse_and_eval(xp) xp = int(xp) parsed_expr.append(f"{xp:d}") except gdb.error: parsed_expr.append(str(xp)) try: res = eval(" ".join(parsed_expr)) if isinstance(res, int): show_as_int(res) else: gef_print(f"{res}") except SyntaxError: gef_print(" ".join(parsed_expr)) return def distance(self, args: Tuple[str, 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]) gef_print(f"{abs(x - y)}") except ValueError: warn(f"Distance requires 2 numbers: {self._cmdline_} 0 0xffff") return @register_command class CanaryCommand(GenericCommand): """Shows the canary value of the current process.""" _cmdline_ = "canary" _syntax_ = _cmdline_ @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: self.dont_repeat() has_canary = checksec(get_filepath())["Canary"] if not has_canary: warn("This binary was not compiled with SSP.") return res = gef.session.canary if not res: err("Failed to get the canary") return canary, location = res info(f"The canary of process {gef.session.pid} is at {location:#x}, value is {canary:#x}") return @register_command class ProcessStatusCommand(GenericCommand): """Extends the info given by GDB `info proc`, by giving an exhaustive description of the process status (file descriptors, ancestor, descendants, etc.).""" _cmdline_ = "process-status" _syntax_ = _cmdline_ _aliases_ = ["status", ] def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_NONE) return @only_if_gdb_running @only_if_gdb_target_local def do_invoke(self, argv: List[str]) -> None: self.show_info_proc() self.show_ancestor() self.show_descendants() self.show_fds() self.show_connections() return def get_state_of(self, pid: int) -> Dict[str, str]: res = {} with open(f"/proc/{pid}/status", "r") as f: file = f.readlines() for line in file: key, value = line.split(":", 1) res[key.strip()] = value.strip() return res def get_cmdline_of(self, pid: int) -> str: with open(f"/proc/{pid}/cmdline", "r") as f: return f.read().replace("\x00", "\x20").strip() 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"] try: return [int(x) for x in gef_execute_external(cmd, as_list=True)] except Exception: return [] 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") return def show_ancestor(self) -> None: info("Parent Process Information") 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") return def show_descendants(self) -> None: info("Children Process Information") children = self.get_children_pids(gef.session.pid) if not children: gef_print("\tNo child process") return for child_pid in children: state = self.get_state_of(child_pid) pid = state["Pid"] gef_print(f"\tPID {RIGHT_ARROW} {pid} (Name: '{self.get_process_path_of(pid)}', CmdLine: '{self.get_cmdline_of(pid)}')") return def show_fds(self) -> None: pid = gef.session.pid path = f"/proc/{pid:d}/fd" info("File Descriptors:") items = os.listdir(path) if not items: gef_print("\tNo FD opened") return for fname in items: fullpath = os.path.join(path, fname) if os.path.islink(fullpath): gef_print(f"\t{fullpath} {RIGHT_ARROW} {os.readlink(fullpath)}") return def list_sockets(self, pid: int) -> List[int]: sockets = [] path = f"/proc/{pid:d}/fd" items = os.listdir(path) for fname in items: fullpath = os.path.join(path, fname) if os.path.islink(fullpath) and os.readlink(fullpath).startswith("socket:"): p = os.readlink(fullpath).replace("socket:", "")[1:-1] sockets.append(int(p)) return sockets def parse_ip_port(self, addr: str) -> Tuple[str, int]: ip, port = addr.split(":") return socket.inet_ntoa(struct.pack(" None: # https://github.com/torvalds/linux/blob/v4.7/include/net/tcp_states.h#L16 tcp_states_str = { 0x01: "TCP_ESTABLISHED", 0x02: "TCP_SYN_SENT", 0x03: "TCP_SYN_RECV", 0x04: "TCP_FIN_WAIT1", 0x05: "TCP_FIN_WAIT2", 0x06: "TCP_TIME_WAIT", 0x07: "TCP_CLOSE", 0x08: "TCP_CLOSE_WAIT", 0x09: "TCP_LAST_ACK", 0x0A: "TCP_LISTEN", 0x0B: "TCP_CLOSING", 0x0C: "TCP_NEW_SYN_RECV", } udp_states_str = { 0x07: "UDP_LISTEN", } info("Network Connections") pid = gef.session.pid sockets = self.list_sockets(pid) if not sockets: gef_print("\tNo open connections") return entries = dict() with open(f"/proc/{pid:d}/net/tcp", "r") as tcp: entries["TCP"] = [x.split() for x in tcp.readlines()[1:]] with open(f"/proc/{pid:d}/net/udp", "r") as udp: entries["UDP"] = [x.split() for x in udp.readlines()[1:]] for proto in entries: for entry in entries[proto]: local, remote, state = entry[1:4] inode = int(entry[9]) if inode in sockets: 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] gef_print(f"\t{local[0]}:{local[1]} {RIGHT_ARROW} {remote[0]}:{remote[1]} ({state_str})") return @register_priority_command class GefThemeCommand(GenericCommand): """Customize GEF appearance.""" _cmdline_ = "theme" _syntax_ = f"{_cmdline_} [KEY [VALUE]]" def __init__(self) -> None: super().__init__(self._cmdline_) self["context_title_line"] = ("gray", "Color of the borders in context window") 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["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["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") return def do_invoke(self, args: List[str]) -> None: self.dont_repeat() argc = len(args) if argc == 0: for key in self.settings: setting = self[key] value = Color.colorify(setting, setting) gef_print(f"{key:40s}: {value}") return setting_name = args[0] if not setting_name in self: err("Invalid key") return if argc == 1: value = self[setting_name] 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) return class ExternalStructureManager: class Structure: 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 self.class_type = self.__get_structure_class() return def __str__(self) -> str: return self.name def pprint(self) -> None: res = [] for _name, _type in self.class_type._fields_: size = ctypes.sizeof(_type) name = Color.colorify(_name, gef.config["pcustom.structure_name"]) 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} */") gef_print("\n".join(res)) return def __get_structure_class(self) -> Type: """Returns a tuple of (class, instance) if modname!classname exists""" fpath = self.module_path spec = importlib.util.spec_from_file_location(fpath.stem, fpath) module = importlib.util.module_from_spec(spec) sys.modules[fpath.stem] = module spec.loader.exec_module(module) _class = getattr(module, self.name) return _class def apply_at(self, address: int, max_depth: int, depth: int = 0) -> None: """Apply (recursively if possible) the structure format to the given address.""" if depth >= max_depth: warn("maximum recursion level reached") return # read the data at the specified address _structure = self.class_type() _sizeof_structure = ctypes.sizeof(_structure) try: data = gef.memory.read(address, _sizeof_structure) except gdb.MemoryError: err(f"{' ' * depth}Cannot read memory {address:#x}") return # deserialize the data length = min(len(data), _sizeof_structure) ctypes.memmove(ctypes.addressof(_structure), data, length) # pretty print all the fields (and call recursively if possible) ptrsize = gef.arch.ptrsize unpack = u32 if ptrsize == 4 else u64 for field in _structure._fields_: _name, _type = field _value = getattr(_structure, _name) _offset = getattr(self.class_type, _name).offset 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)): # try to dereference pointers _value = RIGHT_ARROW.join(dereference_from(_value)) line = f"{' ' * depth}" line += f"{address:#x}+{_offset:#04x} {_name} : ".ljust(40) line += f"{_value} ({_type.__name__})" parsed_value = self.__get_ctypes_value(_structure, _name, _value) if parsed_value: line += f"{RIGHT_ARROW} {parsed_value}" gef_print(line) if issubclass(_type, ctypes.Structure): self.apply_at(address + _offset, max_depth, depth + 1) elif _type.__name__.startswith("LP_"): # Pointer to a structure of a different type __sub_type_name = _type.__name__.lstrip("LP_") result = self.manager.find(__sub_type_name) if result: _, __structure = result __address = unpack(gef.memory.read(address + _offset, ptrsize)) __structure.apply_at(__address, max_depth, depth + 1) return def __get_ctypes_value(self, struct, item, value) -> str: if not hasattr(struct, "_values_"): return "" default = "" for name, values in struct._values_: if name != item: continue if callable(values): return values(value) try: for val, desc in values: 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: 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) self[structure.name] = structure return def __load(self) -> ModuleType: """Load a custom module, and return it.""" fpath = self.path spec = importlib.util.spec_from_file_location(fpath.stem, fpath) module = importlib.util.module_from_spec(spec) sys.modules[fpath.stem] = module spec.loader.exec_module(module) return module def __str__(self) -> str: return self.name def __iter__(self) -> Generator[str, None, None]: _invalid = {"BigEndianStructure", "LittleEndianStructure", "Structure"} _structs = {x for x in dir(self.raw) \ if inspect.isclass(getattr(self.raw, x)) \ and issubclass(getattr(self.raw, x), ctypes.Structure)} for entry in (_structs - _invalid): yield entry return class Modules(dict): def __init__(self, manager: "ExternalStructureManager") -> None: self.manager: "ExternalStructureManager" = manager 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 module = ExternalStructureManager.Module(manager, entry) self[module.name] = module return def __contains__(self, structure_name: str) -> bool: """Return True if the structure name is found in any of the modules""" for module in self.values(): if structure_name in module: return True return False def __init__(self) -> None: self.clear_caches() return def clear_caches(self) -> None: self._path = None self._modules = None return @property def modules(self) -> "ExternalStructureManager.Modules": if not self._modules: self._modules = ExternalStructureManager.Modules(self) return self._modules @property def path(self) -> pathlib.Path: if not self._path: self._path = pathlib.Path(gef.config["pcustom.struct_path"]).expanduser().absolute() return self._path @property 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"]]: """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: return module, module[structure_name] return None @register_command class PCustomCommand(GenericCommand): """Dump user defined structure. This command attempts to reproduce WinDBG awesome `dt` command for GDB and allows to apply structures (from symbols or custom) directly to an address. Custom structures can be defined in pure Python using ctypes, and should be stored in a specific directory, whose path must be stored in the `pcustom.struct_path` configuration setting.""" _cmdline_ = "pcustom" _syntax_ = f"{_cmdline_} [list|edit |show ]| 0xADDRESS]" def __init__(self) -> None: super().__init__(prefix=True) self["struct_path"] = (str(pathlib.Path(gef.config["gef.tempdir"]) / "structs"), "Path to store/load the structure ctypes files") self["max_depth"] = (4, "Maximum level of recursion supported") 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") return @parse_arguments({"type": "", "address": ""}, {}) def do_invoke(self, *_: Any, **kwargs: Dict[str, Any]) -> None: args = kwargs["arguments"] if not args.type: gdb.execute("pcustom list") return _, structname = self.explode_type(args.type) if not args.address: gdb.execute(f"pcustom show {structname}") return if not is_alive(): err("Session is not active") return manager = ExternalStructureManager() address = parse_address(args.address) result = manager.find(structname) if not result: err(f"No structure named '{structname}' found") return _, structure = result structure.apply_at(address, self["max_depth"]) return 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 @register_command class PCustomListCommand(PCustomCommand): """PCustom: list available structures""" _cmdline_ = "pcustom list" _syntax_ = f"{_cmdline_}" def __init__(self) -> None: super().__init__() return 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}'") struct_color = gef.config["pcustom.structure_type"] filename_color = gef.config["pcustom.structure_name"] for module in manager.modules.values(): __modules = ", ".join([Color.colorify(structure_name, struct_color) for structure_name in module.values()]) __filename = Color.colorify(str(module.path), filename_color) gef_print(f"{RIGHT_ARROW} {__filename} ({__modules})") return @register_command class PCustomShowCommand(PCustomCommand): """PCustom: show the content of a given structure""" _cmdline_ = "pcustom show" _syntax_ = f"{_cmdline_} StructureName" __aliases__ = ["pcustom create", "pcustom update"] def __init__(self) -> None: super().__init__() return def do_invoke(self, argv: List[str]) -> None: if len(argv) == 0: self.usage() return _, structname = self.explode_type(argv[0]) manager = ExternalStructureManager() result = manager.find(structname) if result: _, structure = result structure.pprint() else: err(f"No structure named '{structname}' found") return @register_command class PCustomEditCommand(PCustomCommand): """PCustom: edit the content of a given structure""" _cmdline_ = "pcustom edit" _syntax_ = f"{_cmdline_} StructureName" __aliases__ = ["pcustom create", "pcustom new", "pcustom update"] def __init__(self) -> None: super().__init__() return def do_invoke(self, argv: List[str]) -> None: if len(argv) == 0: self.usage() return modname, structname = self.explode_type(argv[0]) self.__create_or_edit_structure(modname, structname) return def __create_or_edit_structure(self, mod_name: str, struct_name: str) -> int: path = pathlib.Path(gef.config["pcustom.struct_path"]).expanduser() / f"{mod_name}.py" if path.is_file(): info(f"Editing '{path}'") else: ok(f"Creating '{path}' from template") self.__create_template(struct_name, path) cmd = (os.getenv("EDITOR") or "nano").split() cmd.append(str(path.absolute())) return subprocess.call(cmd) def __create_template(self, structname: str, fpath: pathlib.Path) -> None: template = f"""from ctypes import * class {structname}(Structure): _fields_ = [] _values_ = [] """ with fpath.open("w") as f: f.write(template) return @register_command class ChangeFdCommand(GenericCommand): """ChangeFdCommand: redirect file descriptor during runtime.""" _cmdline_ = "hijack-fd" _syntax_ = f"{_cmdline_} FD_NUM NEW_OUTPUT" _example_ = f"{_cmdline_} 2 /tmp/stderr_output.txt" @only_if_gdb_running @only_if_gdb_target_local def do_invoke(self, argv: List[str]) -> None: if len(argv) != 2: self.usage() return if not os.access(f"/proc/{gef.session.pid:d}/fd/{argv[0]}", os.R_OK): self.usage() return old_fd = int(argv[0]) new_output = argv[1] if ":" in new_output: address = socket.gethostbyname(new_output.split(":")[0]) port = int(new_output.split(":")[1]) AF_INET = 2 SOCK_STREAM = 1 res = gdb.execute(f"""call (int)socket({AF_INET}, {SOCK_STREAM}, 0)""", to_string=True) 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] original_contents = gef.memory.read(stack_addr, 8) gef.memory.write(stack_addr, b"\x02\x00", 2) gef.memory.write(stack_addr + 0x2, struct.pack(" int: # Output example: $1 = 3 res = int(res.split()[2], 0) res = gdb.execute(f"""p/d {res}""", to_string=True) res = int(res.split()[2], 0) return res @register_command class IdaInteractCommand(GenericCommand): """IDA Interact: set of commands to interact with IDA via a XML RPC service deployed via the IDA script `ida_gef.py`. It should be noted that this command can also be used to interact with Binary Ninja (using the script `binja_gef.py`) using the same interface.""" _cmdline_ = "ida-interact" _syntax_ = f"{_cmdline_} METHOD [ARGS]" _aliases_ = ["binaryninja-interact", "bn", "binja"] _example_ = f"\n{_cmdline_} Jump $pc\n{_cmdline_} SetColor $pc ff00ff" def __init__(self) -> None: super().__init__(prefix=False) self["host"] = ("127.0.0.1", "IP address to use connect to IDA/Binary Ninja script") self["port"] = (1337, "Port to use connect to IDA/Binary Ninja script") self["sync_cursor"] = (False, "Enable real-time $pc synchronization") self.sock = None self.version = ("", "") self.old_bps = set() return def is_target_alive(self, host: str, port: int) -> bool: try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(1) s.connect((host, port)) s.close() except OSError: return False return True def connect(self, host: Optional[str] = None, port: Optional[int] = None) -> None: """Connect to the XML-RPC service.""" host = host or self["host"] port = port or self["port"] try: sock = xmlrpclib.ServerProxy(f"http://{host}:{port:d}") gef_on_stop_hook(ida_synchronize_handler) gef_on_continue_hook(ida_synchronize_handler) self.version = sock.version() except ConnectionRefusedError: err(f"Failed to connect to '{host}:{port:d}'") sock = None self.sock = sock return def disconnect(self) -> None: gef_on_stop_unhook(ida_synchronize_handler) gef_on_continue_unhook(ida_synchronize_handler) self.sock = None return @deprecated("") def do_invoke(self, argv: List[str]) -> None: def parsed_arglist(arglist: List[str]) -> List[str]: args = [] for arg in arglist: try: # try to solve the argument using gdb argval = gdb.parse_and_eval(arg) argval.fetch_lazy() # check if value is addressable argval = int(argval) if argval.address is None else int(argval.address) # if the bin is PIE, we need to subtract the base address if is_pie(get_filepath()) and main_base_address <= argval < main_end_address: argval -= main_base_address args.append(f"{argval:#x}") except Exception: # if gdb can't parse the value, let ida deal with it args.append(arg) return args if self.sock is None: # trying to reconnect self.connect() if self.sock is None: self.disconnect() return if len(argv) == 0 or argv[0] in ("-h", "--help"): method_name = argv[1] if len(argv) > 1 else None self.usage(method_name) return method_name = argv[0].lower() if method_name == "version": self.version = self.sock.version() info(f"Enhancing {Color.greenify('gef')} with {Color.redify(self.version[0])} " f"(SDK {Color.yellowify(self.version[1])})") return if not is_alive(): main_base_address = main_end_address = 0 else: vmmap = gef.memory.maps main_base_address = min([x.page_start for x in vmmap if x.realpath == get_filepath()]) main_end_address = max([x.page_end for x in vmmap if x.realpath == get_filepath()]) try: if method_name == "sync": self.synchronize() else: method = getattr(self.sock, method_name) if len(argv) > 1: args = parsed_arglist(argv[1:]) res = method(*args) else: res = method() if method_name == "importstruct": self.import_structures(res) else: gef_print(str(res)) if self["sync_cursor"] is True: jump = getattr(self.sock, "jump") jump(hex(gef.arch.pc-main_base_address),) except OSError: self.disconnect() return def synchronize(self) -> None: """Submit all active breakpoint addresses to IDA/BN.""" pc = gef.arch.pc vmmap = gef.memory.maps base_address = min([x.page_start for x in vmmap if x.path == get_filepath()]) end_address = max([x.page_end for x in vmmap if x.path == get_filepath()]) if not (base_address <= pc < end_address): # do not sync in library return breakpoints = gdb.breakpoints() or [] gdb_bps = set() for bp in breakpoints: if bp.enabled and not bp.temporary: if bp.location[0] == "*": # if it's an address i.e. location starts with "*" addr = parse_address(bp.location[1:]) else: # it is a symbol addr = int(gdb.parse_and_eval(bp.location).address) if not (base_address <= addr < end_address): continue gdb_bps.add(addr - base_address) added = gdb_bps - self.old_bps removed = self.old_bps - gdb_bps self.old_bps = gdb_bps try: # it is possible that the server was stopped between now and the last sync rc = self.sock.sync(f"{pc-base_address:#x}", list(added), list(removed)) except ConnectionRefusedError: self.disconnect() return ida_added, ida_removed = rc # add new bp from IDA for new_bp in ida_added: location = base_address + new_bp gdb.Breakpoint(f"*{location:#x}", type=gdb.BP_BREAKPOINT) self.old_bps.add(location) # and remove the old ones breakpoints = gdb.breakpoints() or [] for bp in breakpoints: if bp.enabled and not bp.temporary: if bp.location[0] == "*": # if it's an address i.e. location starts with "*" addr = parse_address(bp.location[1:]) else: # it is a symbol addr = int(gdb.parse_and_eval(bp.location).address) if not (base_address <= addr < end_address): continue if (addr - base_address) in ida_removed: if (addr - base_address) in self.old_bps: self.old_bps.remove((addr - base_address)) bp.delete() return def usage(self, meth: Optional[str] = None) -> None: if self.sock is None: return if meth is not None: gef_print(titlify(meth)) gef_print(self.sock.system.methodHelp(meth)) return info("Listing available methods and syntax examples: ") for m in self.sock.system.listMethods(): if m.startswith("system."): continue gef_print(titlify(m)) gef_print(self.sock.system.methodHelp(m)) return def import_structures(self, structs: Dict[str, List[Tuple[int, str, int]]]) -> None: if self.version[0] != "IDA Pro": return path = gef.config["pcustom.struct_path"] if path is None: return if not os.path.isdir(path): gef_makedirs(path) for struct_name in structs: fullpath = pathlib.Path(path) / f"{struct_name}.py" with fullpath.open("w") as f: f.write("from ctypes import *\n\n") f.write("class ") f.write(struct_name) f.write("(Structure):\n") f.write(" _fields_ = [\n") for _, name, size in structs[struct_name]: name = bytes(name, encoding="utf-8") if size == 1: csize = "c_uint8" elif size == 2: csize = "c_uint16" elif size == 4: csize = "c_uint32" elif size == 8: csize = "c_uint64" else: csize = f"c_byte * {size}" m = f' (\"{name}\", {csize}),\n' f.write(m) f.write("]\n") ok(f"Success, {len(structs):d} structure{'s' if len(structs) > 1 else ''} imported") return @register_command class ScanSectionCommand(GenericCommand): """Search for addresses that are located in a memory mapping (haystack) that belonging to another (needle).""" _cmdline_ = "scan" _syntax_ = f"{_cmdline_} HAYSTACK NEEDLE" _aliases_ = ["lookup",] _example_ = f"\n{_cmdline_} stack libc" @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: if len(argv) != 2: self.usage() return haystack = argv[0] needle = argv[1] info(f"Searching for addresses in '{Color.yellowify(haystack)}' " f"that point to '{Color.yellowify(needle)}'") if haystack == "binary": haystack = get_filepath() if needle == "binary": needle = get_filepath() needle_sections = [] haystack_sections = [] if "0x" in haystack: start, end = parse_string_range(haystack) haystack_sections.append((start, end, "")) if "0x" in needle: start, end = parse_string_range(needle) needle_sections.append((start, end)) for sect in gef.memory.maps: if haystack in 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)) step = gef.arch.ptrsize unpack = u32 if step == 4 else u64 for hstart, hend, hname in haystack_sections: try: mem = gef.memory.read(hstart, hend - hstart) except gdb.MemoryError: continue for i in range(0, len(mem), step): target = unpack(mem[i:i+step]) for nstart, nend in needle_sections: if target >= nstart and target < nend: deref = DereferenceCommand.pprint_dereferenced(hstart, int(i / step)) if hname != "": name = Color.colorify(hname, "yellow") gef_print(f"{name}: {deref}") else: gef_print(f" {deref}") return @register_command class SearchPatternCommand(GenericCommand): """SearchPatternCommand: search a pattern in memory. If given an hex value (starting with 0x) the command will also try to look for upwards cross-references to this address.""" _cmdline_ = "search-pattern" _syntax_ = f"{_cmdline_} PATTERN [little|big] [section]" _aliases_ = ["grep", "xref"] _example_ = (f"\n{_cmdline_} AAAAAAAA" f"\n{_cmdline_} 0x555555554000 little stack" f"\n{_cmdline_} AAAA 0x600000-0x601000") def print_section(self, section: Section) -> None: title = "In " if section.path: title += f"'{Color.blueify(section.path)}'" title += f"({section.page_start:#x}-{section.page_end:#x})" title += f", permission={section.permission}" ok(title) 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])}" """) return def search_pattern_by_address(self, pattern: str, start_address: int, end_address: int) -> List[Tuple[int, int, Optional[str]]]: """Search a pattern within a range defined by arguments.""" _pattern = gef_pybytes(pattern) step = 0x400 * 0x1000 locations = [] for chunk_addr in range(start_address, end_address, step): if chunk_addr + step > end_address: chunk_size = end_address - chunk_addr else: chunk_size = step try: mem = gef.memory.read(chunk_addr, chunk_size) except gdb.error as e: estr = str(e) if estr.startswith("Cannot access memory "): # # This is a special case where /proc/$pid/maps # shows virtual memory address with a read bit, # but it cannot be read directly from userspace. # # See: https://github.com/hugsy/gef/issues/674 # err(estr) return [] else: raise e for match in re.finditer(_pattern, mem): start = chunk_addr + match.start() if is_ascii_string(start): ustr = gef.memory.read_ascii_string(start) end = start + len(ustr) else: ustr = gef_pystring(_pattern) + "[...]" end = start + len(_pattern) locations.append((start, end, ustr)) del mem return locations 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 not section_name in section.path: continue start = section.page_start end = section.page_end - 1 old_section = None for loc in self.search_pattern_by_address(pattern, start, end): addr_loc_start = lookup_address(loc[0]) if addr_loc_start and addr_loc_start.section: if old_section != addr_loc_start.section: self.print_section(addr_loc_start.section) old_section = addr_loc_start.section self.print_loc(loc) return @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: argc = len(argv) if argc < 1: self.usage() return pattern = argv[0] 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 is_hex(pattern): if endian == Endianness.BIG_ENDIAN: 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)]) if argc == 3: info(f"Searching '{Color.yellowify(pattern)}' in {argv[2]}") if "0x" in argv[2]: start, end = parse_string_range(argv[2]) loc = lookup_address(start) if loc.valid: self.print_section(loc.section) for loc in self.search_pattern_by_address(pattern, start, end): self.print_loc(loc) else: section_name = argv[2] if section_name == "binary": section_name = get_filepath() self.search_pattern(pattern, section_name) else: info(f"Searching '{Color.yellowify(pattern)}' in memory") self.search_pattern(pattern, "") return @register_command 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") def do_invoke(self, argv: List[str]) -> None: for flag in argv: if len(flag) < 2: continue action = flag[0] name = flag[1:].lower() if action not in ("+", "-", "~"): err(f"Invalid action for flag '{flag}'") continue if name not in gef.arch.flags_table.values(): err(f"Invalid flag name '{flag[1:]}'") continue for off in gef.arch.flags_table: if gef.arch.flags_table[off] == name: old_flag = gef.arch.register(gef.arch.flag_register) if action == "+": new_flags = old_flag | (1 << off) elif action == "-": new_flags = old_flag & ~(1 << off) else: new_flags = old_flag ^ (1 << off) gdb.execute(f"set ({gef.arch.flag_register}) = {new_flags:#x}") gef_print(gef.arch.flag_register_to_human()) return @register_command class ChangePermissionCommand(GenericCommand): """Change a page permission. By default, it will change it to 7 (RWX).""" _cmdline_ = "set-permission" _syntax_ = (f"{_cmdline_} address [permission]\n" "\taddress\t\tan address within the memory page for which the permissions should be changed\n" "\tpermission\ta 3-bit bitmask with read=1, write=2 and execute=4 as integer") _aliases_ = ["mprotect"] _example_ = f"{_cmdline_} $sp 7" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) return def pre_load(self) -> None: try: __import__("keystone") except ImportError: msg = "Missing `keystone-engine` package, install with: `pip install keystone-engine`." raise ImportWarning(msg) return @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: if len(argv) not in (1, 2): err("Incorrect syntax") self.usage() return if len(argv) == 2: perm = Permission(int(argv[1])) else: perm = Permission.ALL loc = safe_parse_and_eval(argv[0]) if loc is None: err("Invalid address") return loc = int(loc) sect = process_lookup_address(loc) if sect is None: err("Unmapped address") return size = sect.page_end - sect.page_start original_pc = gef.arch.pc info(f"Generating sys_mprotect({sect.page_start:#x}, {size:#x}, " f"'{perm!s}') stub for arch {get_arch()}") stub = self.get_stub_by_arch(sect.page_start, size, perm) if stub is None: err("Failed to generate mprotect opcodes") return info("Saving original code") original_code = gef.memory.read(original_pc, len(stub)) bp_loc = f"*{original_pc + len(stub):#x}" info(f"Setting a restore breakpoint at {bp_loc}") ChangePermissionBreakpoint(bp_loc, original_code, original_pc) info(f"Overwriting current memory at {loc:#x} ({len(stub)} bytes)") gef.memory.write(original_pc, stub, len(stub)) info("Resuming execution") gdb.execute("continue") return def get_stub_by_arch(self, addr: int, size: int, perm: Permission) -> Union[str, bytearray, None]: code = gef.arch.mprotect_asm(addr, size, perm) arch, mode = get_keystone_arch() raw_insns = keystone_assemble(code, arch, mode, raw=True) return raw_insns @register_command class UnicornEmulateCommand(GenericCommand): """Use Unicorn-Engine to emulate the behavior of the binary, without affecting the GDB runtime. By default the command will emulate only the next instruction, but location and number of instruction can be changed via arguments to the command line. By default, it will emulate the next instruction from current PC.""" _cmdline_ = "unicorn-emulate" _syntax_ = (f"{_cmdline_} [--start LOCATION] [--until LOCATION] [--skip-emulation] [--output-file PATH] [NB_INSTRUCTION]" "\n\t--start LOCATION specifies the start address of the emulated run (default $pc)." "\t--until LOCATION specifies the end address of the emulated run." "\t--skip-emulation\t do not execute the script once generated." "\t--output-file /PATH/TO/SCRIPT.py writes the persistent Unicorn script into this file." "\tNB_INSTRUCTION indicates the number of instructions to execute" "\nAdditional options can be setup via `gef config unicorn-emulate`") _aliases_ = ["emulate",] _example_ = f"{_cmdline_} --start $pc 10 --output-file /tmp/my-gef-emulation.py" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) self["verbose"] = (False, "Set unicorn-engine in verbose mode") self["show_disassembly"] = (False, "Show every instruction executed") return def pre_load(self) -> None: try: __import__("unicorn") except ImportError: msg = "Missing `unicorn` package for Python. Install with `pip install unicorn`." raise ImportWarning(msg) try: __import__("capstone") except ImportError: msg = "Missing `capstone` package for Python. Install with `pip install capstone`." raise ImportWarning(msg) return @only_if_gdb_running @parse_arguments({"nb": 1}, {"--start": "", "--until": "", "--skip-emulation": True, "--output-file": ""}) def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = kwargs["arguments"] start_address = parse_address(str(args.start or gef.arch.pc)) end_address = parse_address(str(args.until or self.get_unicorn_end_addr(start_address, args.nb))) self.run_unicorn(start_address, end_address, skip_emulation=args.skip_emulation, to_file=args.output_file) return def get_unicorn_end_addr(self, start_addr: int, nb: int) -> int: dis = list(gef_disassemble(start_addr, nb + 1)) last_insn = dis[-1] return last_insn.address def run_unicorn(self, start_insn_addr: int, end_insn_addr: int, **kwargs: Any) -> None: verbose = self["verbose"] or False skip_emulation = kwargs.get("skip_emulation", False) arch, mode = get_unicorn_arch(to_string=True) unicorn_registers = get_unicorn_registers(to_string=True) cs_arch, cs_mode = get_capstone_arch(to_string=True) fname = gef.session.file.name to_file = kwargs.get("to_file", None) emulate_segmentation_block = "" context_segmentation_block = "" if to_file: tmp_filename = to_file to_file = open(to_file, "w") tmp_fd = to_file.fileno() else: tmp_fd, tmp_filename = tempfile.mkstemp(suffix=".py", prefix="gef-uc-") if is_x86(): # need to handle segmentation (and pagination) via MSR emulate_segmentation_block = """ # from https://github.com/unicorn-engine/unicorn/blob/master/tests/regress/x86_64_msr.py SCRATCH_ADDR = 0xf000 SEGMENT_FS_ADDR = 0x5000 SEGMENT_GS_ADDR = 0x6000 FSMSR = 0xC0000100 GSMSR = 0xC0000101 def set_msr(uc, msr, value, scratch=SCRATCH_ADDR): buf = b"\\x0f\\x30" # x86: wrmsr uc.mem_map(scratch, 0x1000) uc.mem_write(scratch, buf) uc.reg_write(unicorn.x86_const.UC_X86_REG_RAX, value & 0xFFFFFFFF) uc.reg_write(unicorn.x86_const.UC_X86_REG_RDX, (value >> 32) & 0xFFFFFFFF) uc.reg_write(unicorn.x86_const.UC_X86_REG_RCX, msr & 0xFFFFFFFF) uc.emu_start(scratch, scratch+len(buf), count=1) uc.mem_unmap(scratch, 0x1000) return def set_gs(uc, addr): return set_msr(uc, GSMSR, addr) def set_fs(uc, addr): return set_msr(uc, FSMSR, addr) """ context_segmentation_block = """ emu.mem_map(SEGMENT_FS_ADDR-0x1000, 0x3000) set_fs(emu, SEGMENT_FS_ADDR) set_gs(emu, SEGMENT_GS_ADDR) """ content = """#!{pythonbin} -i # # Emulation script for "{fname}" from {start:#x} to {end:#x} # # Powered by gef, unicorn-engine, and capstone-engine # # @_hugsy_ # import collections import capstone, unicorn registers = collections.OrderedDict(sorted({{{regs}}}.items(), key=lambda t: t[0])) uc = None verbose = {verbose} syscall_register = "{syscall_reg}" def disassemble(code, addr): cs = capstone.Cs({cs_arch}, {cs_mode}) for i in cs.disasm(code, addr): return i def hook_code(emu, address, size, user_data): code = emu.mem_read(address, size) insn = disassemble(code, address) print(">>> {{:#x}}: {{:s}} {{:s}}".format(insn.address, insn.mnemonic, insn.op_str)) return def code_hook(emu, address, size, user_data): code = emu.mem_read(address, size) insn = disassemble(code, address) print(">>> {{:#x}}: {{:s}} {{:s}}".format(insn.address, insn.mnemonic, insn.op_str)) return def intr_hook(emu, intno, data): print(" \\-> interrupt={{:d}}".format(intno)) return def syscall_hook(emu, user_data): sysno = emu.reg_read(registers[syscall_register]) print(" \\-> syscall={{:d}}".format(sysno)) return def print_regs(emu, regs): for i, r in enumerate(regs): print("{{:7s}} = {{:#0{ptrsize}x}} ".format(r, emu.reg_read(regs[r])), end="") if (i % 4 == 3) or (i == len(regs)-1): print("") return {emu_block} def reset(): emu = unicorn.Uc({arch}, {mode}) {context_block} """.format(pythonbin=PYTHONBIN, fname=fname, start=start_insn_addr, end=end_insn_addr, regs=",".join([f"'{k.strip()}': {unicorn_registers[k]}" for k in unicorn_registers]), verbose="True" if verbose else "False", syscall_reg=gef.arch.syscall_register, cs_arch=cs_arch, cs_mode=cs_mode, ptrsize=gef.arch.ptrsize * 2 + 2, # two hex chars per byte plus "0x" prefix emu_block=emulate_segmentation_block if is_x86() else "", arch=arch, mode=mode, context_block=context_segmentation_block if is_x86() else "") if verbose: info("Duplicating registers") for r in gef.arch.all_registers: gregval = gef.arch.register(r) content += f" emu.reg_write({unicorn_registers[r]}, {gregval:#x})\n" vmmap = gef.memory.maps if not vmmap: warn("An error occurred when reading memory map.") return if verbose: info("Duplicating memory map") for sect in vmmap: if sect.path == "[vvar]": # this section is for GDB only, skip it continue page_start = sect.page_start page_end = sect.page_end size = sect.size perm = sect.permission content += f" # Mapping {sect.path}: {page_start:#x}-{page_end:#x}\n" content += f" emu.mem_map({page_start:#x}, {size:#x}, {perm.value:#o})\n" if perm & Permission.READ: code = gef.memory.read(page_start, size) loc = f"/tmp/gef-{fname}-{page_start:#x}.raw" with open(loc, "wb") as f: f.write(bytes(code)) content += f" emu.mem_write({page_start:#x}, open('{loc}', 'rb').read())\n" content += "\n" content += " emu.hook_add(unicorn.UC_HOOK_CODE, code_hook)\n" content += " emu.hook_add(unicorn.UC_HOOK_INTR, intr_hook)\n" if is_x86_64(): content += " emu.hook_add(unicorn.UC_HOOK_INSN, syscall_hook, None, 1, 0, unicorn.x86_const.UC_X86_INS_SYSCALL)\n" content += " return emu\n" content += """ def emulate(emu, start_addr, end_addr): print("========================= Initial registers =========================") print_regs(emu, registers) try: print("========================= Starting emulation =========================") emu.emu_start(start_addr, end_addr) except Exception as e: emu.emu_stop() print("========================= Emulation failed =========================") print("[!] Error: {{}}".format(e)) print("========================= Final registers =========================") print_regs(emu, registers) return uc = reset() emulate(uc, {start:#x}, {end:#x}) # unicorn-engine script generated by gef """.format(start=start_insn_addr, end=end_insn_addr) os.write(tmp_fd, gef_pybytes(content)) os.close(tmp_fd) if kwargs.get("to_file", None): info(f"Unicorn script generated as '{tmp_filename}'") os.chmod(tmp_filename, 0o700) if skip_emulation: return ok(f"Starting emulation: {start_insn_addr:#x} {RIGHT_ARROW} {end_insn_addr:#x}") res = gef_execute_external([PYTHONBIN, tmp_filename], as_list=True) gef_print("\n".join(res)) if not kwargs.get("to_file", None): os.unlink(tmp_filename) return @register_command class RemoteCommand(GenericCommand): """gef wrapper for the `target remote` command. This command will automatically download the target binary in the local temporary directory (defaut /tmp) and then source it. Additionally, it will fetch all the /proc/PID/maps and loads all its information.""" _cmdline_ = "gef-remote" _syntax_ = f"{_cmdline_} [OPTIONS] TARGET" _example_ = (f"\n{_cmdline_} --pid 6789 localhost:1234" f"\n{_cmdline_} --qemu-mode localhost:4444 # when using qemu-user") def __init__(self) -> None: super().__init__(prefix=False) self.handler_connected = False self["clean_on_exit"] = (False, "Clean the temporary data downloaded when the session exits.") return @parse_arguments( {"target": ""}, {"--update-solib": True, "--download-everything": True, "--download-lib": "", "--is-extended-remote": True, "--pid": 0, "--qemu-mode": True}) 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 args = kwargs["arguments"] if not args.target or ":" not in args.target: err("A target (HOST:PORT) must always be provided.") return if args.is_extended_remote and not args.pid: err("A PID (--pid) is required for extended remote debugging") return target = args.target self.download_all_libs = args.download_everything if args.qemu_mode: # compat layer for qemu-user self.prepare_qemu_stub(target) return # lazily install handler on first use if not self.handler_connected: gef_on_new_hook(self.new_objfile_handler) self.handler_connected = True if not self.connect_target(target, args.is_extended_remote): return pid = args.pid if args.is_extended_remote and args.pid else gef.session.pid if args.is_extended_remote: ok(f"Attaching to {pid:d}") hide_context() gdb.execute(f"attach {pid:d}") unhide_context() self.setup_remote_environment(pid, args.update_solib) if not is_remote_debug(): err("Failed to establish remote target environment.") return if self.download_all_libs: vmmap = gef.memory.maps success = 0 for sect in vmmap: if sect.path.startswith("/"): _file = download_file(sect.path) if _file is None: err(f"Failed to download {sect.path}") else: success += 1 ok(f"Downloaded {success:d} files") elif args.download_lib: _file = download_file(args.download_lib) if _file is None: err("Failed to download remote file") return ok(f"Download success: {args.download_lib} {RIGHT_ARROW} {_file}") if args.update_solib: self.refresh_shared_library_path() # refresh the architecture setting reset_architecture() gef.session.remote = pid return def new_objfile_handler(self, event: "gdb.Event") -> None: """Hook that handles new_objfile events, will update remote environment accordingly.""" if not is_remote_debug(): return if self.download_all_libs and event.new_objfile.filename.startswith("target:"): remote_lib = event.new_objfile.filename[len("target:"):] local_lib = download_file(remote_lib, use_cache=True) if local_lib: ok(f"Download success: {remote_lib} {RIGHT_ARROW} {local_lib}") return def setup_remote_environment(self, pid: int, update_solib: bool = False) -> None: """Clone the remote environment locally in the temporary directory. The command will duplicate the entries in the /proc/ locally and then source those information into the current gdb context to allow gef to use all the extra commands as it was local debugging.""" gdb.execute("reset-cache") infos = {} for i in ("maps", "environ", "cmdline",): infos[i] = self.load_from_remote_proc(pid, i) if infos[i] is None: err(f"Failed to load memory map of '{i}'") return exepath = get_path_from_info_proc() infos["exe"] = download_file(f"/proc/{pid:d}/exe", use_cache=False, local_name=exepath) if not os.access(infos["exe"], os.R_OK): err("Source binary is not readable") return directory = os.path.sep.join([gef.config["gef.tempdir"], str(gef.session.pid)]) # gdb.execute(f"file {infos['exe']}") self["root"] = (directory, "Path to store the remote data") ok(f"Remote information loaded to temporary path '{directory}'") return def connect_target(self, target: str, is_extended_remote: bool) -> bool: """Connect to remote target and get symbols. To prevent `gef` from requesting information not fetched just yet, we disable the context disable when connection was successful.""" hide_context() try: cmd = f"target {'extended-remote' if is_extended_remote else 'remote'} {target}" gdb.execute(cmd) ok(f"Connected to '{target}'") ret = True except Exception as e: err(f"Failed to connect to {target}: {e}") ret = False unhide_context() return ret def load_from_remote_proc(self, pid: int, info: str) -> Optional[str]: """Download one item from /proc/pid.""" remote_name = f"/proc/{pid:d}/{info}" return download_file(remote_name, use_cache=False) def refresh_shared_library_path(self) -> None: dirs = [r for r, d, f in os.walk(self["root"])] path = ":".join(dirs) gdb.execute(f"set solib-search-path {path}") return def usage(self) -> None: h = self._syntax_ h += "\n\t TARGET (mandatory) specifies the host:port, serial port or tty to connect to.\n" h += "\t-U will update gdb `solib-search-path` attribute to include the files downloaded from server (default: False).\n" h += "\t-A will download *ALL* the remote shared libraries and store them in the new environment. " \ "This command can take a few minutes to complete (default: False).\n" h += "\t-D LIB will download the remote library called LIB.\n" h += "\t-E Use 'extended-remote' to connect to the target.\n" h += "\t-p PID (mandatory if -E is used) specifies PID of the debugged process on gdbserver's end.\n" h += "\t-q Uses this option when connecting to a Qemu GDBserver.\n" info(h) return def prepare_qemu_stub(self, target: str) -> None: global gef reset_all_caches() arch = get_arch() gef.binary = Elf(minimalist=True) if arch.startswith("arm"): gef.binary.e_machine = Elf.Abi.ARM gef.arch = ARM() elif arch.startswith("aarch64"): gef.binary.e_machine = Elf.Abi.AARCH64 gef.arch = AARCH64() elif arch.startswith("i386:intel"): gef.binary.e_machine = Elf.Abi.X86_32 gef.arch = X86() elif arch.startswith("i386:x86-64"): gef.binary.e_machine = Elf.Abi.X86_64 gef.binary.e_class = Elf.Class.ELF_64_BITS gef.arch = X86_64() elif arch.startswith("mips"): gef.binary.e_machine = Elf.Abi.MIPS gef.arch = MIPS() elif arch.startswith("powerpc"): gef.binary.e_machine = Elf.Abi.POWERPC gef.arch = PowerPC() elif arch.startswith("sparc"): gef.binary.e_machine = Elf.Abi.SPARC gef.arch = SPARC() else: raise RuntimeError(f"unsupported architecture: {arch}") ok(f"Setting Qemu-user stub for '{gef.arch.arch}' (memory mapping may be wrong)") hide_context() gdb.execute(f"target remote {target}") unhide_context() if gef.session.pid == 1 and "ENABLE=1" in gdb.execute("maintenance packet Qqemu.sstepbits", to_string=True, from_tty=False): gef.session.qemu_mode = True reset_all_caches() info("Note: By using Qemu mode, GEF will display the memory mapping of the Qemu process where the emulated binary resides") gef.memory.maps gdb.execute("context") return @register_command class NopCommand(GenericCommand): """Patch the instruction(s) pointed by parameters with NOP. Note: this command is architecture aware.""" _cmdline_ = "nop" _syntax_ = ("{_cmdline_} [LOCATION] [--nb NUM_BYTES]" "\n\tLOCATION\taddress/symbol to patch" "\t--nb NUM_BYTES\tInstead of writing one instruction, patch the specified number of bytes") _example_ = f"{_cmdline_} $pc" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) return def get_insn_size(self, addr: int) -> int: cur_insn = gef_current_instruction(addr) next_insn = gef_instruction_n(addr, 2) return next_insn.address - cur_insn.address @parse_arguments({"address": "$pc"}, {"--nb": 0, }) def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = kwargs["arguments"] address = parse_address(args.address) if args.address else gef.arch.pc number_of_bytes = args.nb or 1 self.nop_bytes(address, number_of_bytes) return @only_if_gdb_running def nop_bytes(self, loc: int, num_bytes: int) -> None: size = self.get_insn_size(loc) if num_bytes == 0 else num_bytes nops = gef.arch.nop_insn if len(nops) > size: err(f"Cannot patch instruction at {loc:#x} " f"(nop_size is:{len(nops)}, insn_size is:{size})") return while len(nops) < size: nops += gef.arch.nop_insn if len(nops) != size: err(f"Cannot patch instruction at {loc:#x} " "(nop instruction does not evenly fit in requested size)") return ok(f"Patching {size:d} bytes from {format_address(loc)}") gef.memory.write(loc, nops, size) return @register_command class StubCommand(GenericCommand): """Stub out the specified function. This function is useful when needing to skip one 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") _example_ = f"{_cmdline_} --retval 0 fork" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) return @only_if_gdb_running @parse_arguments({"address": ""}, {("-r", "--retval"): 0}) def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = kwargs["arguments"] loc = args.address if args.address else f"*{gef.arch.pc:#x}" StubBreakpoint(loc, args.retval) return @register_command class CapstoneDisassembleCommand(GenericCommand): """Use capstone disassembly framework to disassemble code.""" _cmdline_ = "capstone-disassemble" _syntax_ = f"{_cmdline_} [-h] [--show-opcodes] [--length LENGTH] [LOCATION]" _aliases_ = ["cs-dis"] _example_ = f"{_cmdline_} --length 50 $pc" def pre_load(self) -> None: try: __import__("capstone") except ImportError: msg = "Missing `capstone` package for Python. Install with `pip install capstone`." raise ImportWarning(msg) return def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) return @only_if_gdb_running @parse_arguments({("location"): "$pc"}, {("--show-opcodes", "-s"): True, "--length": 0}) def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = kwargs["arguments"] show_opcodes = args.show_opcodes length = args.length or gef.config["context.nb_lines_code"] location = parse_address(args.location) if not location: info(f"Can't find address for {args.location}") return insns = [] opcodes_len = 0 for insn in capstone_disassemble(location, length, skip=length * self.repeat_count, **kwargs): insns.append(insn) opcodes_len = max(opcodes_len, len(insn.opcodes)) for insn in insns: insn_fmt = f"{{:{opcodes_len}o}}" if show_opcodes else "{}" text_insn = insn_fmt.format(insn) msg = "" if insn.address == gef.arch.pc: msg = Color.colorify(f"{RIGHT_ARROW} {text_insn}", "bold red") reason = self.capstone_analyze_pc(insn, length)[0] if reason: gef_print(msg) gef_print(reason) break else: msg = f" {text_insn}" gef_print(msg) return def capstone_analyze_pc(self, insn: Instruction, nb_insn: int) -> Tuple[bool, str]: if gef.arch.is_conditional_branch(insn): is_taken, reason = gef.arch.is_branch_taken(insn) if is_taken: reason = f"[Reason: {reason}]" if reason else "" msg = Color.colorify(f"\tTAKEN {reason}", "bold green") else: reason = f"[Reason: !({reason})]" if reason else "" msg = Color.colorify(f"\tNOT taken {reason}", "bold red") return (is_taken, msg) if gef.arch.is_call(insn): target_address = int(insn.operands[-1].split()[0], 16) msg = [] for i, new_insn in enumerate(capstone_disassemble(target_address, nb_insn)): msg.append(f" {DOWN_ARROW if i == 0 else ' '} {new_insn!s}") return (True, "\n".join(msg)) return (False, "") @register_command class GlibcHeapCommand(GenericCommand): """Base command to get information about the Glibc heap structure.""" _cmdline_ = "heap" _syntax_ = f"{_cmdline_} (chunk|chunks|bins|arenas|set-arena)" def __init__(self) -> None: super().__init__(prefix=True) return @only_if_gdb_running def do_invoke(self, _: List[str]) -> None: self.usage() return @register_command class GlibcHeapSetArenaCommand(GenericCommand): """Display information on a heap chunk.""" _cmdline_ = "heap set-arena" _syntax_ = f"{_cmdline_} [address|&symbol]" _example_ = f"{_cmdline_} 0x001337001337" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) return @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: global gef if not argv: ok(f"Current arena set to: '{gef.heap.selected_arena}'") return if is_hex(argv[0]): new_arena_address = int(argv[0], 16) else: new_arena_symbol = safe_parse_and_eval(argv[0]) if not new_arena_symbol: err("Invalid symbol for arena") return new_arena_address = to_unsigned_long(new_arena_symbol) new_arena = GlibcArena( f"*{new_arena_address:#x}") if new_arena not in gef.heap.arenas: err("Invalid arena") return gef.heap.selected_arena = new_arena return @register_command class GlibcHeapArenaCommand(GenericCommand): """Display information on a heap chunk.""" _cmdline_ = "heap arenas" _syntax_ = _cmdline_ @only_if_gdb_running def do_invoke(self, _: List[str]) -> None: for arena in gef.heap.arenas: gef_print(str(arena)) return @register_command class GlibcHeapChunkCommand(GenericCommand): """Display information on a heap chunk. See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123.""" _cmdline_ = "heap chunk" _syntax_ = f"{_cmdline_} [-h] [--allow-unaligned] [--number] address" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) return @parse_arguments({"address": ""}, {"--allow-unaligned": True, "--number": 1}) @only_if_gdb_running def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = kwargs["arguments"] if not args.address: err("Missing chunk address") self.usage() return addr = parse_address(args.address) current_chunk = GlibcChunk(addr, allow_unaligned=args.allow_unaligned) if args.number > 1: for _ in range(args.number): if current_chunk.size == 0: break gef_print(str(current_chunk)) next_chunk_addr = current_chunk.get_next_chunk_addr() if not Address(value=next_chunk_addr).valid: break next_chunk = current_chunk.get_next_chunk() if next_chunk is None: break current_chunk = next_chunk else: gef_print(current_chunk.psprint()) return @register_command 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] [arena_address]" _example_ = (f"\n{_cmdline_}" f"\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)") return @parse_arguments({"arena_address": ""}, {("--all", "-a"): True, "--allow-unaligned": True}) @only_if_gdb_running def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = kwargs["arguments"] arenas = gef.heap.arenas for arena in arenas: self.dump_chunks_arena(arena, print_arena=args.all, allow_unaligned=args.allow_unaligned) if not args.all: break def dump_chunks_arena(self, arena: GlibcArena, print_arena: bool = False, allow_unaligned: bool = False) -> None: top_chunk_addr = arena.top heap_addr = arena.heap_addr(allow_unaligned=allow_unaligned) if heap_addr is None: err("Could not find heap for arena") return if print_arena: gef_print(str(arena)) if arena.is_main_arena(): self.dump_chunks_heap(heap_addr, top=top_chunk_addr, allow_unaligned=allow_unaligned) else: heap_info_structs = arena.get_heap_info_list() first_heap_info = heap_info_structs.pop(0) heap_info_t_size = int(arena) - first_heap_info.addr until = first_heap_info.addr + first_heap_info.size self.dump_chunks_heap(heap_addr, until=until, top=top_chunk_addr, allow_unaligned=allow_unaligned) for heap_info in heap_info_structs: start = heap_info.addr + heap_info_t_size until = heap_info.addr + heap_info.size self.dump_chunks_heap(start, until=until, top=top_chunk_addr, allow_unaligned=allow_unaligned) return def dump_chunks_heap(self, start: int, until: Optional[int] = None, top: Optional[int] = None, allow_unaligned: bool = False) -> None: nb = self["peek_nb_byte"] chunk_iterator = GlibcChunk(start, from_base=True, allow_unaligned=allow_unaligned) for chunk in chunk_iterator: line = str(chunk) if nb: line += f"\n [{hexdump(gef.memory.read(chunk.data_address, nb), nb, base=chunk.data_address)}]" gef_print(line) next_chunk_addr = chunk.get_next_chunk_addr() if until and next_chunk_addr >= until: break if chunk.base_address == top: gef_print(f"{chunk!s} {LEFT_ARROW} {Color.greenify('top chunk')}") break return @register_command class GlibcHeapBinsCommand(GenericCommand): """Display information on the bins on an arena (default: main_arena). See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123.""" _bin_types_ = ["tcache", "fast", "unsorted", "small", "large"] _cmdline_ = "heap bins" _syntax_ = f"{_cmdline_} [{'|'.join(_bin_types_)}]" def __init__(self) -> None: super().__init__(prefix=True, complete=gdb.COMPLETE_LOCATION) return @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: if not argv: for bin_t in GlibcHeapBinsCommand._bin_types_: gdb.execute(f"heap bins {bin_t}") return bin_t = argv[0] if bin_t not in GlibcHeapBinsCommand._bin_types_: self.usage() return gdb.execute(f"heap bins {bin_t}") return @staticmethod def pprint_bin(arena_addr: str, index: int, _type: str = "") -> int: arena = GlibcArena(arena_addr) fw, bk = arena.bin(index) if bk == 0x00 and fw == 0x00: warn("Invalid backward and forward bin pointers(fw==bk==NULL)") return -1 nb_chunk = 0 head = GlibcChunk(bk, from_base=True).fwd if fw == head: return nb_chunk ok(f"{_type}bins[{index:d}]: fw={fw:#x}, bk={bk:#x}") m = [] while fw != head: chunk = GlibcChunk(fw, from_base=True) m.append(f"{RIGHT_ARROW} {chunk!s}") fw = chunk.fwd nb_chunk += 1 if m: gef_print(" ".join(m)) return nb_chunk @register_command class GlibcHeapTcachebinsCommand(GenericCommand): """Display information on the Tcachebins on an arena (default: main_arena). See https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc.""" _cmdline_ = "heap bins tcache" _syntax_ = f"{_cmdline_} [all] [thread_ids...]" TCACHE_MAX_BINS = 0x40 def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) return @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: # Determine if we are using libc with tcache built in (2.26+) if get_libc_version() < (2, 26): info("No Tcache in this version of libc") return current_thread = gdb.selected_thread() if current_thread is None: err("Couldn't find current thread") return # As a nicety, we want to display threads in ascending order by gdb number threads = sorted(gdb.selected_inferior().threads(), key=lambda t: t.num) if argv: if "all" in argv: tids = [t.num for t in threads] else: tids = self.check_thread_ids(argv) else: tids = [current_thread.num] for thread in threads: if thread.num not in tids: continue thread.switch() tcache_addr = self.find_tcache() if tcache_addr == 0: info(f"Uninitialized tcache for thread {thread.num:d}") continue gef_print(titlify(f"Tcachebins for thread {thread.num:d}")) tcache_empty = True for i in range(self.TCACHE_MAX_BINS): chunk, count = self.tcachebin(tcache_addr, i) chunks = set() msg = [] # Only print the entry if there are valid chunks. Don't trust count while True: if chunk is None: break try: msg.append(f"{LEFT_ARROW} {chunk!s} ") if chunk.data_address in chunks: msg.append(f"{RIGHT_ARROW} [loop detected]") break chunks.add(chunk.data_address) next_chunk = chunk.get_fwd_ptr(True) if next_chunk == 0: break chunk = GlibcChunk(next_chunk) except gdb.MemoryError: msg.append(f"{LEFT_ARROW} [Corrupted chunk at {chunk.data_address:#x}]") break if msg: tcache_empty = False gef_print(f"Tcachebins[idx={i:d}, size={(i+2)*(gef.arch.ptrsize)*2:#x}] count={count:d} ", end="") gef_print("".join(msg)) if tcache_empty: gef_print("All tcachebins are empty") current_thread.switch() return @staticmethod def find_tcache() -> int: """Return the location of the current thread's tcache.""" try: # For multithreaded binaries, the tcache symbol (in thread local # storage) will give us the correct address. tcache_addr = parse_address("(void *) tcache") except gdb.error: # In binaries not linked with pthread (and therefore there is only # one thread), we can't use the tcache symbol, but we can guess the # correct address because the tcache is consistently the first # allocation in the main arena. heap_base = gef.heap.base_address if heap_base is None: err("No heap section") return 0x0 tcache_addr = heap_base + 0x10 return tcache_addr @staticmethod def check_thread_ids(tids: List[int]) -> List[int]: """Check the validity, dedup, and return all valid tids.""" existing_tids = [t.num for t in gdb.selected_inferior().threads()] valid_tids = set() for tid in tids: try: tid = int(tid) except ValueError: err(f"Invalid thread id {tid:d}") continue if tid in existing_tids: valid_tids.add(tid) else: err(f"Unknown thread {tid}") return list(valid_tids) @staticmethod def tcachebin(tcache_base: int, i: int) -> Tuple[Optional[GlibcChunk], int]: """Return the head chunk in tcache[i] and the number of chunks in the bin.""" if i >= GlibcHeapTcachebinsCommand.TCACHE_MAX_BINS: err("Incorrect index value, index value must be between 0 and {}-1, given {}".format(GlibcHeapTcachebinsCommand.TCACHE_MAX_BINS, i)) return None, 0 tcache_chunk = GlibcChunk(tcache_base) # Glibc changed the size of the tcache in version 2.30; this fix has # been backported inconsistently between distributions. We detect the # difference by checking the size of the allocated chunk for the # tcache. # Minimum usable size of allocated tcache chunk = ? # For new tcache: # TCACHE_MAX_BINS * _2_ + TCACHE_MAX_BINS * ptrsize # For old tcache: # TCACHE_MAX_BINS * _1_ + TCACHE_MAX_BINS * ptrsize new_tcache_min_size = ( GlibcHeapTcachebinsCommand.TCACHE_MAX_BINS * 2 + GlibcHeapTcachebinsCommand.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)) else: tcache_count_size = 2 count = u16(gef.memory.read(tcache_base + tcache_count_size*i, 2)) chunk = dereference(tcache_base + tcache_count_size*GlibcHeapTcachebinsCommand.TCACHE_MAX_BINS + i*gef.arch.ptrsize) chunk = GlibcChunk(int(chunk)) if chunk else None return chunk, count @register_command class GlibcHeapFastbinsYCommand(GenericCommand): """Display information on the fastbinsY on an arena (default: main_arena). See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123.""" _cmdline_ = "heap bins fast" _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) return @parse_arguments({"arena_address": ""}, {}) @only_if_gdb_running 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 = kwargs["arguments"] if not gef.heap.main_arena: err("Heap not initialized") return SIZE_SZ = gef.arch.ptrsize 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 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="") chunk = arena.fastbin(i) chunks = set() while True: if chunk is None: gef_print("0x00", end="") break try: gef_print(f"{LEFT_ARROW} {chunk!s} ", end="") if chunk.data_address in chunks: gef_print(f"{RIGHT_ARROW} [loop detected]", end="") break if fastbin_index(chunk.get_chunk_size()) != i: gef_print("[incorrect fastbin_index] ", end="") chunks.add(chunk.data_address) next_chunk = chunk.get_fwd_ptr(True) if next_chunk == 0: break chunk = GlibcChunk(next_chunk, from_base=True) except gdb.MemoryError: gef_print(f"{LEFT_ARROW} [Corrupted chunk at {chunk.data_address:#x}]", end="") break gef_print() return @register_command class GlibcHeapUnsortedBinsCommand(GenericCommand): """Display information on the Unsorted Bins of an arena (default: main_arena). See: https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1689.""" _cmdline_ = "heap bins unsorted" _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) return @parse_arguments({"arena_address": ""}, {}) @only_if_gdb_running def do_invoke(self, *_: Any, **kwargs: Any) -> None: args = kwargs["arguments"] if gef.heap.main_arena is None: err("Heap not initialized") return 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}")) nb_chunk = GlibcHeapBinsCommand.pprint_bin(f"*{arena_addr}", 0, "unsorted_") if nb_chunk >= 0: info(f"Found {nb_chunk:d} chunks in unsorted bin.") return @register_command class GlibcHeapSmallBinsCommand(GenericCommand): """Convenience command for viewing small bins.""" _cmdline_ = "heap bins small" _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) return @parse_arguments({"arena_address": ""}, {}) @only_if_gdb_running def do_invoke(self, *_: Any, **kwargs: Any) -> None: args = kwargs["arguments"] if not gef.heap.main_arena: err("Heap not initialized") return arena_addr = args.arena_address if args.arena_address else f"{gef.heap.selected_arena.addr:#x}" gef_print(titlify(f"Small Bins for arena at {arena_addr}")) bins = {} for i in range(1, 63): nb_chunk = GlibcHeapBinsCommand.pprint_bin(f"*{arena_addr}", i, "small_") if nb_chunk < 0: break if nb_chunk > 0: bins[i] = nb_chunk info(f"Found {sum(bins.values()):d} chunks in {len(bins):d} small non-empty bins.") return @register_command class GlibcHeapLargeBinsCommand(GenericCommand): """Convenience command for viewing large bins.""" _cmdline_ = "heap bins large" _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) return @parse_arguments({"arena_address": ""}, {}) @only_if_gdb_running def do_invoke(self, *_: Any, **kwargs: Any) -> None: args = kwargs["arguments"] if gef.heap.main_arena is None: err("Heap not initialized") return 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 = {} for i in range(63, 126): nb_chunk = GlibcHeapBinsCommand.pprint_bin(f"*{arena_addr}", i, "large_") if nb_chunk < 0: 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.") return @register_command class SolveKernelSymbolCommand(GenericCommand): """Solve kernel symbols from kallsyms table.""" _cmdline_ = "ksymaddr" _syntax_ = f"{_cmdline_} SymbolToSearch" _example_ = f"{_cmdline_} prepare_creds" @parse_arguments({"symbol": ""}, {}) def do_invoke(self, _: List[str], **kwargs: Any) -> None: def hex_to_int(num): try: return int(num, 16) except ValueError: return 0 args = kwargs["arguments"] if not args.symbol: self.usage() return sym = args.symbol with open("/proc/kallsyms", "r") as f: syms = [line.strip().split(" ", 2) for line in f] matches = [(hex_to_int(addr), sym_t, " ".join(name.split())) for addr, sym_t, name in syms if sym in name] for addr, sym_t, name in matches: if sym == name.split()[0]: ok(f"Found matching symbol for '{name}' at {addr:#x} (type={sym_t})") else: warn(f"Found partial match for '{sym}' at {addr:#x} (type={sym_t}): {name}") if not matches: err(f"No match for '{sym}'") elif matches[0][0] == 0: err("Check that you have the correct permissions to view kernel symbol addresses") return @register_command 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") @only_if_gdb_running @parse_arguments({"registers": [""]}, {}) 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"] regs = gef.arch.all_registers args = kwargs["arguments"] if args.registers and args.registers[0]: required_regs = set(args.registers) valid_regs = [reg for reg in gef.arch.all_registers if reg in required_regs] if valid_regs: regs = valid_regs invalid_regs = [reg for reg in required_regs if reg not in valid_regs] if invalid_regs: err(f"invalid registers for architecture: {', '.join(invalid_regs)}") memsize = gef.arch.ptrsize endian = str(gef.arch.endianness) charset = string.printable widest = max(map(len, gef.arch.all_registers)) special_line = "" for regname in regs: reg = gdb.parse_and_eval(regname) if reg.type.code == gdb.TYPE_CODE_VOID: continue padreg = regname.ljust(widest, " ") if str(reg) == "": gef_print(f"{Color.colorify(padreg, unchanged_color)}: " f"{Color.colorify('no value', 'yellow underline')}") continue value = align_address(int(reg)) old_value = ContextCommand.old_registers.get(regname, 0) if value == old_value: color = unchanged_color else: color = changed_color # Special (e.g. segment) registers go on their own line if regname in gef.arch.special_registers: special_line += f"{Color.colorify(regname, color)}: " special_line += f"{gef.arch.register(regname):#04x} " continue line = f"{Color.colorify(padreg, color)}: " if regname == gef.arch.flag_register: line += gef.arch.flag_register_to_human() gef_print(line) continue addr = lookup_address(align_address(int(value))) if addr.valid: line += str(addr) else: line += format_address_spaces(value) addrs = dereference_from(value) if len(addrs) > 1: sep = f" {RIGHT_ARROW} " line += sep line += sep.join(addrs[1:]) # check to see if reg value is ascii try: fmt = f"{endian}{'I' if memsize == 4 else 'Q'}" 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)}\"?)" except ValueError: pass gef_print(line) if special_line: gef_print(special_line) return @register_command class ShellcodeCommand(GenericCommand): """ShellcodeCommand uses @JonathanSalwan simple-yet-awesome shellcode API to download shellcodes.""" _cmdline_ = "shellcode" _syntax_ = f"{_cmdline_} (search|get)" def __init__(self) -> None: super().__init__(prefix=True) return def do_invoke(self, _: List[str]) -> None: err("Missing sub-command (search|get)") self.usage() return @register_command class ShellcodeSearchCommand(GenericCommand): """Search pattern in shell-storm's shellcode database.""" _cmdline_ = "shellcode search" _syntax_ = f"{_cmdline_} PATTERN1 PATTERN2" _aliases_ = ["sc-search",] api_base = "http://shell-storm.org" search_url = f"{api_base}/api/?s=" 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) res = http_get(self.search_url + args) if res is None: err("Could not query search page") return ret = gef_pystring(res) # format: [author, OS/arch, cmd, id, link] lines = ret.split("\\n") refs = [line.split("::::") for line in lines] if refs: info("Showing matching shellcodes") info("\t".join(["Id", "Platform", "Description"])) for ref in refs: try: _, arch, cmd, sid, _ = ref gef_print("\t".join([sid, arch, cmd])) except ValueError: continue info("Use `shellcode get ` to fetch shellcode") return @register_command class ShellcodeGetCommand(GenericCommand): """Download shellcode from shell-storm's shellcode database.""" _cmdline_ = "shellcode get" _syntax_ = f"{_cmdline_} SHELLCODE_ID" _aliases_ = ["sc-get",] api_base = "http://shell-storm.org" get_url = f"{api_base}/shellcode/files/shellcode-{{:d}}.php" def do_invoke(self, argv: List[str]) -> None: if len(argv) != 1: err("Missing ID to download") self.usage() return if not argv[0].isdigit(): err("ID is not a number") self.usage() return self.get_shellcode(int(argv[0])) return def get_shellcode(self, sid: int) -> None: info(f"Downloading shellcode id={sid}") res = http_get(self.get_url.format(sid)) if res is None: err(f"Failed to fetch shellcode #{sid}") return ok("Downloaded, written to disk...") tempdir = gef.config["gef.tempdir"] fd, fname = tempfile.mkstemp(suffix=".txt", prefix="sc-", text=True, dir=tempdir) shellcode = res.splitlines()[7:-11] shellcode = b"\n".join(shellcode).replace(b""", b'"') os.write(fd, shellcode) os.close(fd) ok(f"Shellcode written to '{fname}'") return @register_command class RopperCommand(GenericCommand): """Ropper (https://scoding.de/ropper/) plugin.""" _cmdline_ = "ropper" _syntax_ = f"{_cmdline_} [ROPPER_OPTIONS]" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_NONE) return def pre_load(self) -> None: try: __import__("ropper") except ImportError: msg = "Missing `ropper` package for Python, install with: `pip install ropper`." raise ImportWarning(msg) return @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: ropper = sys.modules["ropper"] if "--file" not in argv: path = get_filepath() sect = next(filter(lambda x: x.path == path, gef.memory.maps)) argv.append("--file") argv.append(path) argv.append("-I") argv.append(f"{sect.page_start:#x}") import readline # ropper set up own autocompleter after which gdb/gef autocomplete don't work old_completer_delims = readline.get_completer_delims() old_completer = readline.get_completer() try: ropper.start(argv) except RuntimeWarning: return readline.set_completer(old_completer) readline.set_completer_delims(old_completer_delims) return @register_command class AssembleCommand(GenericCommand): """Inline code assemble. Architecture can be set in GEF runtime config. """ _cmdline_ = "assemble" _syntax_ = f"{_cmdline_} [-h] [--list-archs] [--mode MODE] [--arch ARCH] [--overwrite-location LOCATION] [--endian ENDIAN] [--as-shellcode] instruction;[instruction;...instruction;])" _aliases_ = ["asm",] _example_ = (f"\n{_cmdline_} -a x86 -m 32 nop ; nop ; inc eax ; int3" f"\n{_cmdline_} -a arm -m arm add r0, r0, 1") valid_arch_modes = { # Format: ARCH = [MODES] with MODE = (NAME, HAS_LITTLE_ENDIAN, HAS_BIG_ENDIAN) "ARM": [("ARM", True, True), ("THUMB", True, True), ("ARMV8", True, True), ("THUMBV8", True, True)], "ARM64": [("0", True, False)], "MIPS": [("MIPS32", True, True), ("MIPS64", True, True)], "PPC": [("PPC32", False, True), ("PPC64", True, True)], "SPARC": [("SPARC32", True, True), ("SPARC64", False, True)], "SYSTEMZ": [("SYSTEMZ", True, True)], "X86": [("16", True, False), ("32", True, False), ("64", True, False)] } valid_archs = valid_arch_modes.keys() valid_modes = [_ for sublist in valid_arch_modes.values() for _ in sublist] def __init__(self) -> None: super().__init__() self["default_architecture"] = ("X86", "Specify the default architecture to use when assembling") self["default_mode"] = ("64", "Specify the default architecture to use when assembling") return def pre_load(self) -> None: try: __import__("keystone") except ImportError: msg = "Missing `keystone-engine` package for Python, install with: `pip install keystone-engine`." raise ImportWarning(msg) return def usage(self) -> None: super().usage() gef_print("") self.list_archs() return def list_archs(self) -> None: gef_print("Available architectures/modes (with endianness):") # for updates, see https://github.com/keystone-engine/keystone/blob/master/include/keystone/keystone.h for arch in self.valid_arch_modes: gef_print(f"- {arch}") for mode, le, be in self.valid_arch_modes[arch]: if le and be: endianness = "little, big" elif le: endianness = "little" elif be: endianness = "big" gef_print(f" * {mode:<7} ({endianness})") return @parse_arguments({"instructions": [""]}, {"--mode": "", "--arch": "", "--overwrite-location": 0, "--endian": "little", "--list-archs": True, "--as-shellcode": True}) def do_invoke(self, _: List[str], **kwargs: Any) -> None: arch_s, mode_s, endian_s = self["default_architecture"], self["default_mode"], "" args = kwargs["arguments"] if args.list_archs: self.list_archs() return if not args.instructions: err("No instruction given.") return if is_alive(): arch_s, mode_s = gef.arch.arch, gef.arch.mode endian_s = "big" if gef.arch.endianness == Endianness.BIG_ENDIAN else "" if args.arch: arch_s = args.arch arch_s = arch_s.upper() if args.mode: mode_s = args.mode mode_s = mode_s.upper() if args.endian == "big": endian_s = "big" endian_s = endian_s.upper() if arch_s not in self.valid_arch_modes: raise AttributeError(f"invalid arch '{arch_s}'") valid_modes = self.valid_arch_modes[arch_s] try: mode_idx = [m[0] for m in valid_modes].index(mode_s) except ValueError: raise AttributeError(f"invalid mode '{mode_s}' for arch '{arch_s}'") if endian_s == "little" and not valid_modes[mode_idx][1] or endian_s == "big" and not valid_modes[mode_idx][2]: raise AttributeError(f"invalid endianness '{endian_s}' for arch/mode '{arch_s}:{mode_s}'") arch, mode = get_keystone_arch(arch=arch_s, mode=mode_s, endian=endian_s) insns = [x.strip() for x in " ".join(args.instructions).split(";") if x] info(f"Assembling {len(insns)} instruction(s) for {arch_s}:{mode_s}") if args.as_shellcode: gef_print("""sc="" """) raw = b"" for insn in insns: res = keystone_assemble(insn, arch, mode, raw=True) if res is None: gef_print("(Invalid)") continue if args.overwrite_location: raw += res continue s = binascii.hexlify(res) res = b"\\x" + b"\\x".join([s[i:i + 2] for i in range(0, len(s), 2)]) res = res.decode("utf-8") if args.as_shellcode: res = f"""sc+="{res}" """ gef_print(f"{res!s:60s} # {insn}") if args.overwrite_location: l = len(raw) info(f"Overwriting {l:d} bytes at {format_address(args.overwrite_location)}") gef.memory.write(args.overwrite_location, raw, l) return @register_command class ProcessListingCommand(GenericCommand): """List and filter process. If a PATTERN is given as argument, results shown will be grepped by this pattern.""" _cmdline_ = "process-search" _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") return @parse_arguments({"pattern": ""}, {"--attach": True, "--smart-scan": True}) def do_invoke(self, _: List, **kwargs: Any) -> None: args = kwargs["arguments"] do_attach = args.attach smart_scan = args.smart_scan pattern = args.pattern pattern = re.compile("^.*$") if not args else re.compile(pattern) for process in self.get_processes(): pid = int(process["pid"]) command = process["command"] if not re.search(pattern, command): 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 args and do_attach: ok(f"Attaching to process='{process['command']}' pid={pid:d}") gdb.execute(f"attach {pid:d}") return None line = [process[i] for i in ("pid", "user", "cpu", "mem", "tty", "command")] gef_print("\t\t".join(line)) return 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()] for line in output[1:]: fields = line.split() t = {} for i, name in enumerate(names): if i == len(names) - 1: t[name] = " ".join(fields[i:]) else: t[name] = fields[i] yield t return @register_command class ElfInfoCommand(GenericCommand): """Display a limited subset of ELF header information. If no argument is provided, the command will show information about the current ELF being debugged.""" _cmdline_ = "elf-info" _syntax_ = f"{_cmdline_} [FILE]" _example_ = f"{_cmdline_} /bin/ls" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) return @parse_arguments({}, {"--filename": ""}) def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = kwargs["arguments"] if is_qemu_system(): err("Unsupported") return filename = args.filename or get_filepath() if filename is None: return elf = get_elf_headers(filename) if elf is None: return 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}"), ("Version", f"{elf.e_eiversion:#x}"), ("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}"), ("Program Header Table", f"{format_address(elf.e_phoff)}"), ("Section Header Table", f"{format_address(elf.e_shoff)}"), ("Header Table", f"{format_address(elf.e_phoff)}"), ("ELF Version", f"{elf.e_version:#x}"), ("Header size", "{0} ({0:#x})".format(elf.e_ehsize)), ("Entry point", f"{format_address(elf.e_entry)}"), ] for title, content in data: gef_print(f"{Color.boldify(f'{title:<22}')}: {content}") gef_print("") gef_print(titlify("Program Header")) gef_print(" [{:>2s}] {:12s} {:>8s} {:>10s} {:>10s} {:>8s} {:>8s} {:5s} {:>8s}".format( "#", "Type", "Offset", "Virtaddr", "Physaddr", "FileSiz", "MemSiz", "Flags", "Align")) 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(" [{:2d}] {:12s} {:#8x} {:#10x} {:#10x} {:#8x} {:#8x} {:5s} {:#8x}".format( i, p_type, p.p_offset, p.p_vaddr, p.p_paddr, p.p_filesz, p.p_memsz, p_flags, p.p_align)) gef_print("") gef_print(titlify("Section Header")) gef_print(" [{:>2s}] {:20s} {:>15s} {:>10s} {:>8s} {:>8s} {:>8s} {:5s} {:4s} {:4s} {:>8s}".format( "#", "Name", "Type", "Address", "Offset", "Size", "EntSiz", "Flags", "Link", "Info", "Align")) 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}") return @register_command class EntryPointBreakCommand(GenericCommand): """Tries to find best entry point and sets a temporary breakpoint on it. The command will test for well-known symbols for entry points, such as `main`, `_main`, `__libc_start_main`, etc. defined by the setting `entrypoint_symbols`.""" _cmdline_ = "entry-break" _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") return 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") return if not os.access(fpath, os.X_OK): warn(f"The file '{fpath}' is not executable.") return if is_alive() and not gef.session.qemu_mode: warn("gdb is already running") return bp = None entrypoints = self["entrypoint_symbols"].split() for sym in entrypoints: try: value = parse_address(sym) info(f"Breaking at '{value:#x}'") bp = EntryBreakBreakpoint(sym) gdb.execute(f"run {' '.join(argv)}") return except gdb.error as gdb_error: if 'The "remote" target does not support "run".' in str(gdb_error): # this case can happen when doing remote debugging gdb.execute("continue") return continue # if here, clear the breakpoint if any set if bp: bp.delete() # break at entry point entry = gef.binary.entry_point if is_pie(fpath): self.set_init_tbreak_pie(entry, argv) gdb.execute("continue") return self.set_init_tbreak(entry) gdb.execute(f"run {' '.join(argv)}") return def set_init_tbreak(self, addr: int) -> EntryBreakBreakpoint: info(f"Breaking at entry-point: {addr:#x}") bp = EntryBreakBreakpoint(f"*{addr:#x}") return bp 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() gdb.execute(f"run {' '.join(argv)}") unhide_context() gdb.execute("set stop-on-solib-events 0") vmmap = gef.memory.maps base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0] return self.set_init_tbreak(base_address + addr) @register_command 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" def __init__(self) -> None: super().__init__() return @parse_arguments({"name": "", "address": "*$pc"}, {}) def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = kwargs["arguments"] if not args.name: err("Missing name for breakpoint") self.usage() return NamedBreakpoint(args.address, args.name) return @register_command class ContextCommand(GenericCommand): """Displays a comprehensive and modular summary of runtime context. Unless setting `enable` is set to False, this command will be spawned automatically every time GDB hits a breakpoint, a watchpoint, or any kind of interrupt. By default, it will show panes that contain the register 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",] old_registers: Dict[str, Optional[int]] = {} 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_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["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_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["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["redirect"] = ("", "Redirect the context information to another TTY") self["libc_args"] = (False, "Show libc function call args description") self["libc_args_path"] = ("", "Path to libc function call args json files, provided via gef-extras") if "capstone" in list(sys.modules.keys()): self["use_capstone"] = (False, "Use capstone as disassembler in the code pane (instead of GDB)") self.layout_mapping = { "legend": (self.show_legend, None), "regs": (self.context_regs, None), "stack": (self.context_stack, None), "code": (self.context_code, None), "args": (self.context_args, None), "memory": (self.context_memory, None), "source": (self.context_source, None), "trace": (self.context_trace, None), "threads": (self.context_threads, None), "extra": (self.context_additional_information, None), } return def post_load(self) -> None: gef_on_continue_hook(self.update_registers) gef_on_continue_hook(self.empty_extra_messages) return def show_legend(self) -> None: if gef.config["gef.disable_color"] is True: return str_color = gef.config["theme.dereference_string"] code_addr_color = gef.config["theme.address_code"] stack_addr_color = gef.config["theme.address_stack"] heap_addr_color = gef.config["theme.address_heap"] changed_register_color = gef.config["theme.registers_value_changed"] gef_print("[ Legend: {} | {} | {} | {} | {} ]".format(Color.colorify("Modified register", changed_register_color), Color.colorify("Code", code_addr_color), Color.colorify("Heap", heap_addr_color), Color.colorify("Stack", stack_addr_color), Color.colorify("String", str_color))) return @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: if not self["enable"] or gef.ui.context_hidden: return if not all(_ in self.layout_mapping for _ in argv): self.usage() return if len(argv) > 0: current_layout = argv else: current_layout = self["layout"].strip().split() if not current_layout: return self.tty_rows, self.tty_columns = get_terminal_size() redirect = self["redirect"] if redirect and os.access(redirect, os.W_OK): enable_redirect_output(to_file=redirect) for section in current_layout: if section[0] == "-": continue try: display_pane_function, pane_title_function = self.layout_mapping[section] if pane_title_function: self.context_title(pane_title_function()) display_pane_function() except gdb.MemoryError as e: # a MemoryError will happen when $pc is corrupted (invalid address) err(str(e)) self.context_title("") if self["clear_screen"] and len(argv) == 0: clear_screen(redirect) if redirect and os.access(redirect, os.W_OK): disable_redirect_output() return def context_title(self, m: Optional[str]) -> None: # allow for not displaying a title line if m is None: return line_color = gef.config["theme.context_title_line"] msg_color = gef.config["theme.context_title_message"] # print an empty line in case of "" if not m: gef_print(Color.colorify(HORIZONTAL_LINE * self.tty_columns, line_color)) return trail_len = len(m) + 6 title = "" title += Color.colorify("{:{padd}<{width}} ".format("", width=max(self.tty_columns - trail_len, 0), padd=HORIZONTAL_LINE), line_color) title += Color.colorify(m, msg_color) title += Color.colorify(" {:{padd}<4}".format("", padd=HORIZONTAL_LINE), line_color) gef_print(title) return def context_regs(self) -> None: self.context_title("registers") ignored_registers = set(self["ignore_registers"].split()) if self["show_registers_raw"] is False: regs = set(gef.arch.all_registers) printable_registers = " ".join(list(regs - ignored_registers)) 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 i = 1 line = "" changed_color = gef.config["theme.registers_value_changed"] regname_color = gef.config["theme.registers_register_name"] for reg in gef.arch.all_registers: if reg in ignored_registers: continue try: r = gdb.parse_and_eval(reg) if r.type.code == gdb.TYPE_CODE_VOID: continue new_value_type_flag = r.type.code == gdb.TYPE_CODE_FLAGS new_value = int(r) except (gdb.MemoryError, gdb.error): # If this exception is triggered, it means that the current register # is corrupted. Just use the register "raw" value (not eval-ed) new_value = gef.arch.register(reg) new_value_type_flag = False except Exception: new_value = 0 new_value_type_flag = False old_value = self.old_registers.get(reg, 0) padreg = reg.ljust(widest, " ") value = align_address(new_value) old_value = align_address(old_value) if value == old_value: line += f"{Color.colorify(padreg, regname_color)}: " else: line += f"{Color.colorify(padreg, changed_color)}: " if new_value_type_flag: line += f"{format_address_spaces(value)} " else: addr = lookup_address(align_address(int(value))) if addr.valid: line += f"{addr!s} " else: line += f"{format_address_spaces(value)} " if i % nb == 0: gef_print(line) line = "" i += 1 if line: gef_print(line) gef_print(f"Flags: {gef.arch.flag_register_to_human()}") return def context_stack(self) -> None: self.context_title("stack") show_raw = self["show_stack_raw"] nb_lines = self["nb_lines_stack"] try: sp = gef.arch.sp if show_raw is True: mem = gef.memory.read(sp, 0x10 * nb_lines) gef_print(hexdump(mem, base=sp)) else: gdb.execute(f"dereference -l {nb_lines:d} {sp:#x}") except gdb.MemoryError: err("Cannot read memory from $SP (corrupted stack pointer?)") return 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: nb_insn = self["nb_lines_code"] nb_insn_prev = self["nb_lines_code_prev"] use_capstone = "use_capstone" in self and self["use_capstone"] show_opcodes_size = "show_opcodes_size" in self and self["show_opcodes_size"] past_insns_color = gef.config["theme.old_context"] cur_insn_color = gef.config["theme.disassemble_current_instruction"] pc = gef.arch.pc breakpoints = gdb.breakpoints() or [] 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}" self.context_title(f"code:{arch_name}") try: instruction_iterator = capstone_disassemble if use_capstone else gef_disassemble for insn in 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 " " if show_opcodes_size == 0: text = str(insn) else: insn_fmt = f"{{:{show_opcodes_size}o}}" text = insn_fmt.format(insn) if insn.address < pc: line += f"{bp_prefix} {Color.colorify(text, past_insns_color)}" elif insn.address == pc: line += f"{bp_prefix}{Color.colorify(f'{RIGHT_ARROW[1:]}{text}', cur_insn_color)}" if gef.arch.is_conditional_branch(insn): is_taken, reason = gef.arch.is_branch_taken(insn) if is_taken: target = insn.operands[-1].split()[0] reason = f"[Reason: {reason}]" if reason else "" line += Color.colorify(f"\tTAKEN {reason}", "bold green") else: reason = f"[Reason: !({reason})]" if reason else "" line += Color.colorify(f"\tNOT taken {reason}", "bold red") elif gef.arch.is_call(insn) and self["peek_calls"] is True: target = insn.operands[-1].split()[0] elif gef.arch.is_ret(insn) and self["peek_ret"] is True: target = gef.arch.get_ra(insn, frame) else: line += f"{bp_prefix} {text}" gef_print("".join(line)) if target: try: target = int(target, 0) except TypeError: # Already an int pass except ValueError: # If the operand isn't an address right now we can't parse it continue for i, tinsn in enumerate(instruction_iterator(target, nb_insn)): text= f" {DOWN_ARROW if i == 0 else ' '} {tinsn!s}" gef_print(text) break except gdb.MemoryError: err("Cannot disassemble from $PC") return def context_args(self) -> None: insn = gef_current_instruction(gef.arch.pc) if not gef.arch.is_call(insn): return self.size2type = { 1: "BYTE", 2: "WORD", 4: "DWORD", 8: "QWORD", } 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: target = f"*{gef.arch.register('$' + insn.operands[0]):#x}" else: # is there a symbol? ops = " ".join(insn.operands) if "<" in ops and ">" in ops: # extract it target = re.sub(r".*<([^\(> ]*).*", r"\1", ops) else: # it's an address, just use as is target = re.sub(r".*(0x[a-fA-F0-9]*).*", r"\1", ops) sym = gdb.lookup_global_symbol(target) if sym is None: self.print_guessed_arguments(target) return if sym.type.code != gdb.TYPE_CODE_FUNC: err(f"Symbol '{target}' is not a function: type={sym.type.code}") return self.print_arguments_from_symbol(target, sym) return 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 = [] for i, f in enumerate(symbol.type.fields()): _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}" _type = f.type.name or self.size2type[f.type.sizeof] args.append(f"{_type} {_name} = {_value}") self.context_title("arguments") if not args: gef_print(f"{function_name} ()") return gef_print(f"{function_name} (\n "+",\n ".join(args)+"\n)") return 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]: pc = gef.arch.pc try: block = gdb.block_for_pc(pc) block_start = block.start if block else gdb_get_nth_previous_instruction_address(pc, 5) except RuntimeError: block_start = gdb_get_nth_previous_instruction_address(pc, 5) return block_start parameter_set = set() pc = gef.arch.pc block_start = __get_current_block_start_address() if not block_start: return use_capstone = "use_capstone" in self and self["use_capstone"] instruction_iterator = capstone_disassemble if use_capstone else gef_disassemble function_parameters = gef.arch.function_parameters arg_key_color = gef.config["theme.registers_register_name"] for insn in instruction_iterator(block_start, pc - block_start): if not insn.operands: continue if is_x86_32(): if insn.mnemonic == "push": parameter_set.add(insn.operands[0]) else: op = "$" + insn.operands[0] if op in function_parameters: parameter_set.add(op) if is_x86_64(): # also consider extended registers 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) nb_argument = None _arch_mode = f"{gef.arch.arch.lower()}_{gef.arch.mode}" _function_name = None if function_name.endswith("@plt"): _function_name = function_name.split("@")[0] try: nb_argument = len(gef.ui.highlight_table[_arch_mode][_function_name]) except KeyError: pass if not nb_argument: 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) args = [] for i in range(nb_argument): _key, _values = gef.arch.get_ith_parameter(i, in_func=False) _values = RIGHT_ARROW.join(dereference_from(_values)) try: args.append("{} = {} (def: {})".format(Color.colorify(_key, arg_key_color), _values, gef.ui.highlight_table[_arch_mode][_function_name][_key])) except KeyError: args.append(f"{Color.colorify(_key, arg_key_color)} = {_values}") self.context_title("arguments (guessed)") gef_print(f"{function_name} (") if args: gef_print(" " + ",\n ".join(args)) gef_print(")") return 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) def context_source(self) -> None: try: pc = gef.arch.pc symtabline = gdb.find_pc_line(pc) symtab = symtabline.symtab # we subtract one because the line number returned by gdb start at 1 line_num = symtabline.line - 1 if not symtab.is_valid(): return fpath = symtab.fullname() with open(fpath, "r") as f: lines = [l.rstrip() for l in f.readlines()] except Exception: return 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] past_lines_color = gef.config["theme.old_context"] nb_line = self["nb_lines_code"] fn = symtab.filename if len(fn) > 20: fn = f"{fn[:15]}[...]{os.path.splitext(fn)[1]}" title = f"source:{fn}+{line_num + 1}" cur_line_color = gef.config["theme.source_current_line"] self.context_title(title) show_extra_info = self["show_source_code_variable_values"] for i in range(line_num - nb_line + 1, line_num + nb_line): if i < 0: continue 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))) if i == line_num: prefix = f"{bp_prefix}{RIGHT_ARROW[1:]}{i + 1:4d}\t " leading = len(lines[i]) - len(lines[i].lstrip()) if show_extra_info: extra_info = self.get_pc_context_info(pc, lines[i]) if extra_info: gef_print(f"{' ' * (len(prefix) + leading)}{extra_info}") gef_print(Color.colorify(f"{prefix}{lines[i]}", cur_line_color)) if i > line_num: try: gef_print(f"{bp_prefix} {i + 1:4d}\t {lines[i]}") except IndexError: break return 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 "" m = collections.OrderedDict() while current_block and not current_block.is_static: for sym in current_block: symbol = sym.name if not sym.is_function and re.search(fr"\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) addrs = dereference_from(addr) if len(addrs) > 2: addrs = [addrs[0], "[...]", addrs[-1]] f = f" {RIGHT_ARROW} " val = f.join(addrs) elif val.type.code == gdb.TYPE_CODE_INT: val = hex(int(val)) else: continue if symbol not in m: m[symbol] = val current_block = current_block.superblock if m: return "// " + ", ".join([f"{Color.yellowify(a)}={b}" for a, b in m.items()]) except Exception: pass return "" def context_trace(self) -> None: self.context_title("trace") nb_backtrace = self["nb_lines_backtrace"] if nb_backtrace <= 0: return # backward compat for gdb (gdb < 7.10) if not hasattr(gdb, "FrameDecorator"): gdb.execute(f"backtrace {nb_backtrace:d}") return orig_frame = gdb.selected_frame() current_frame = gdb.newest_frame() frames = [current_frame] while current_frame != orig_frame: current_frame = current_frame.older() frames.append(current_frame) nb_backtrace_before = self["nb_lines_backtrace_before"] level = max(len(frames) - nb_backtrace_before - 1, 0) current_frame = frames[level] while current_frame: current_frame.select() if not current_frame.is_valid(): continue pc = current_frame.pc() name = current_frame.name() items = [] items.append(f"{pc:#x}") if name: frame_args = gdb.FrameDecorator.FrameDecorator(current_frame).frame_args() or [] m = "{}({})".format(Color.greenify(name), ", ".join(["{}={!s}".format(Color.yellowify(x.sym), x.sym.value(current_frame)) for x in frame_args])) items.append(m) else: try: insn = next(gef_disassemble(pc, 1)) except gdb.MemoryError: break # check if the gdb symbol table may know the address sym_found = gdb_get_location_from_symbol(pc) symbol = "" if sym_found: sym_name, offset = sym_found symbol = f" <{sym_name}+{offset:x}> " items.append(Color.redify(f"{symbol}{insn.mnemonic} {', '.join(insn.operands)}")) gef_print("[{}] {}".format(Color.colorify(f"#{level}", "bold green" if current_frame == orig_frame else "bold pink"), RIGHT_ARROW.join(items))) current_frame = current_frame.older() level += 1 nb_backtrace -= 1 if nb_backtrace == 0: break orig_frame.select() return def context_threads(self) -> None: def reason() -> str: res = gdb.execute("info program", to_string=True).splitlines() if not res: return "NOT RUNNING" for line in res: line = line.strip() if line.startswith("It stopped with signal "): return line.replace("It stopped with signal ", "").split(",", 1)[0] if line == "The program being debugged is not being run.": return "NOT RUNNING" if line == "It stopped at a breakpoint that has since been deleted.": return "TEMPORARY BREAKPOINT" if line.startswith("It stopped at breakpoint "): return "BREAKPOINT" if line == "It stopped after being stepped.": return "SINGLE STEP" return "STOPPED" self.context_title("threads") threads = gdb.selected_inferior().threads()[::-1] idx = self["nb_lines_threads"] if idx > 0: threads = threads[0:idx] if idx == 0: return if not threads: err("No thread selected") return selected_thread = gdb.selected_thread() selected_frame = gdb.selected_frame() for i, thread in enumerate(threads): line = f"[{Color.colorify(f'#{i:d}', 'bold green' if thread == selected_thread else 'bold pink')}] Id {thread.num:d}, " if thread.name: line += f"""Name: "{thread.name}", """ if thread.is_running(): line += Color.colorify("running", "bold green") elif thread.is_stopped(): line += Color.colorify("stopped", "bold red") thread.switch() frame = gdb.selected_frame() frame_name = frame.name() # check if the gdb symbol table may know the address if not frame_name: sym_found = gdb_get_location_from_symbol(frame.pc()) if sym_found: 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')}") elif thread.is_exited(): line += Color.colorify("exited", "bold yellow") gef_print(line) i += 1 selected_thread.switch() selected_frame.select() return def context_additional_information(self) -> None: if not gef.ui.context_messages: return 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) return def context_memory(self) -> None: for address, opt in sorted(gef.ui.watches.items()): sz, fmt = opt[0:2] self.context_title(f"memory:{address:#x}") if fmt == "pointers": gdb.execute(f"dereference -l {sz:d} {address:#x}") else: gdb.execute(f"hexdump {fmt} -s {sz:d} {address:#x}") @classmethod def update_registers(cls, _) -> None: for reg in gef.arch.all_registers: try: cls.old_registers[reg] = gef.arch.register(reg) except Exception: cls.old_registers[reg] = 0 return def empty_extra_messages(self, _) -> None: gef.ui.context_messages.clear() return @register_command class MemoryCommand(GenericCommand): """Add or remove address ranges to the memory view.""" _cmdline_ = "memory" _syntax_ = f"{_cmdline_} (watch|unwatch|reset|list)" def __init__(self) -> None: super().__init__(prefix=True) return @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: self.usage() return @register_command 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") def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) return @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: if len(argv) not in (1, 2, 3): self.usage() return address = parse_address(argv[0]) size = parse_address(argv[1]) if len(argv) > 1 else 0x10 group = "byte" if len(argv) == 3: group = argv[2].lower() if group not in ("qword", "dword", "word", "byte", "pointers"): warn(f"Unexpected grouping '{group}'") self.usage() return else: if gef.arch.ptrsize == 4: group = "dword" elif gef.arch.ptrsize == 8: group = "qword" gef.ui.watches[address] = (size, group) ok(f"Adding memwatch to {address:#x}") return @register_command 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") def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) return @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: if not argv: self.usage() return address = parse_address(argv[0]) res = gef.ui.watches.pop(address, None) if not res: warn(f"You weren't watching {address:#x}") else: ok(f"Removed memwatch of {address:#x}") return @register_command class MemoryWatchResetCommand(GenericCommand): """Removes all watchpoints.""" _cmdline_ = "memory reset" _syntax_ = f"{_cmdline_}" @only_if_gdb_running def do_invoke(self, _: List[str]) -> None: gef.ui.watches.clear() ok("Memory watches cleared") return @register_command class MemoryWatchListCommand(GenericCommand): """Lists all watchpoints to display in context layout.""" _cmdline_ = "memory list" _syntax_ = f"{_cmdline_}" @only_if_gdb_running def do_invoke(self, _: List[str]) -> None: if not gef.ui.watches: info("No memory watches") return info("Memory watches:") for address, opt in sorted(gef.ui.watches.items()): gef_print(f"- {address:#x} ({opt[0]}, {opt[1]})") return @register_command 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]" _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.format: Optional[str] = None self.__last_target = "$sp" return @only_if_gdb_running @parse_arguments({"address": "",}, {("--reverse", "-r"): True, ("--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 = kwargs["arguments"] target = args.address or self.__last_target start_addr = parse_address(target) read_from = align_address(start_addr) if self.format == "byte": read_len = args.size or 0x40 read_from += self.repeat_count * read_len mem = gef.memory.read(read_from, read_len) 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) if args.reverse: lines.reverse() self.__last_target = target gef_print("\n".join(lines)) return 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"] show_ascii = gef.config["hexdump.always_show_ascii"] formats = { "qword": ("Q", 8), "dword": ("I", 4), "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}" lines = [] i = 0 text = "" while i < length: cur_addr = start_addr + (i + offset) * l sym = gdb_get_location_from_symbol(cur_addr) sym = "<{:s}+{:04x}> ".format(*sym) if sym else "" mem = gef.memory.read(cur_addr, l) 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)) i += 1 return lines @register_command class HexdumpQwordCommand(HexdumpCommand): """Display SIZE lines of hexdump as QWORD from the memory location pointed by ADDRESS.""" _cmdline_ = "hexdump qword" _syntax_ = f"{_cmdline_} [ADDRESS] [[L][SIZE]] [REVERSE]" _example_ = f"{_cmdline_} qword $rsp L16 REVERSE" def __init__(self) -> None: super().__init__() self.format = "qword" return @register_command class HexdumpDwordCommand(HexdumpCommand): """Display SIZE lines of hexdump as DWORD from the memory location pointed by ADDRESS.""" _cmdline_ = "hexdump dword" _syntax_ = f"{_cmdline_} [ADDRESS] [[L][SIZE]] [REVERSE]" _example_ = f"{_cmdline_} $esp L16 REVERSE" def __init__(self) -> None: super().__init__() self.format = "dword" return @register_command class HexdumpWordCommand(HexdumpCommand): """Display SIZE lines of hexdump as WORD from the memory location pointed by ADDRESS.""" _cmdline_ = "hexdump word" _syntax_ = f"{_cmdline_} [ADDRESS] [[L][SIZE]] [REVERSE]" _example_ = f"{_cmdline_} $esp L16 REVERSE" def __init__(self) -> None: super().__init__() self.format = "word" return @register_command class HexdumpByteCommand(HexdumpCommand): """Display SIZE lines of hexdump as BYTE from the memory location pointed by ADDRESS.""" _cmdline_ = "hexdump byte" _syntax_ = f"{_cmdline_} [ADDRESS] [[L][SIZE]] [REVERSE]" _example_ = f"{_cmdline_} $rsp L16" def __init__(self) -> None: super().__init__() self.format = "byte" return @register_command 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\"") SUPPORTED_SIZES = { "qword": (8, "Q"), "dword": (4, "L"), "word": (2, "H"), "byte": (1, "B"), } def __init__(self) -> None: super().__init__(prefix=True, complete=gdb.COMPLETE_LOCATION) self.format: Optional[str] = None return @only_if_gdb_running @parse_arguments({"location": "", "values": ["", ]}, {}) def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = kwargs["arguments"] if not self.format or self.format not in self.SUPPORTED_SIZES: self.usage() return if not args.location or not args.values: self.usage() return addr = align_address(parse_address(args.location)) size, fcode = self.SUPPORTED_SIZES[self.format] d = str(gef.arch.endianness) for value in args.values: value = parse_address(value) & ((1 << size * 8) - 1) vstr = struct.pack(d + fcode, value) gef.memory.write(addr, vstr, length=size) addr += size return @register_command class PatchQwordCommand(PatchCommand): """Write specified QWORD to the specified address.""" _cmdline_ = "patch qword" _syntax_ = f"{_cmdline_} LOCATION QWORD1 [QWORD2 [QWORD3..]]" _example_ = f"{_cmdline_} $rip 0x4141414141414141" def __init__(self) -> None: super().__init__() self.format = "qword" return @register_command class PatchDwordCommand(PatchCommand): """Write specified DWORD to the specified address.""" _cmdline_ = "patch dword" _syntax_ = f"{_cmdline_} LOCATION DWORD1 [DWORD2 [DWORD3..]]" _example_ = f"{_cmdline_} $rip 0x41414141" def __init__(self) -> None: super().__init__() self.format = "dword" return @register_command class PatchWordCommand(PatchCommand): """Write specified WORD to the specified address.""" _cmdline_ = "patch word" _syntax_ = f"{_cmdline_} LOCATION WORD1 [WORD2 [WORD3..]]" _example_ = f"{_cmdline_} $rip 0x4141" def __init__(self) -> None: super().__init__() self.format = "word" return @register_command class PatchByteCommand(PatchCommand): """Write specified WORD to the specified address.""" _cmdline_ = "patch byte" _syntax_ = f"{_cmdline_} LOCATION BYTE1 [BYTE2 [BYTE3..]]" _example_ = f"{_cmdline_} $pc 0x41 0x41 0x41 0x41 0x41" def __init__(self) -> None: super().__init__() self.format = "byte" return @register_command 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\"" _example_ = f"{_cmdline_} $sp \"GEFROCKS\"" @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: argc = len(argv) if argc != 2: self.usage() return location, s = argv[0:2] addr = align_address(parse_address(location)) try: s = codecs.escape_decode(s)[0] except binascii.Error: gef_print(f"Could not decode '\\xXX' encoded string \"{s}\"") return gef.memory.write(addr, s, len(s)) return @lru_cache() def dereference_from(addr: int) -> List[str]: if not is_alive(): return [format_address(addr),] 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(int(addr))) msg = [format_address(addr.value),] seen_addrs = set() while addr.section and max_recursion: if addr.value in seen_addrs: msg.append("[loop detected]") break seen_addrs.add(addr.value) max_recursion -= 1 # Is this value a pointer or a value? # -- If it's a pointer, dereference deref = addr.dereference() if deref is None: # if here, dereferencing addr has triggered a MemoryError, no need to go further msg.append(str(addr)) break new_addr = lookup_address(deref) if new_addr.valid: addr = new_addr msg.append(str(addr)) continue # -- 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): insn = gef_current_instruction(addr.value) insn_str = f"{insn.location} {insn.mnemonic} {', '.join(insn.operands)}" msg.append(Color.colorify(insn_str, code_color)) break elif addr.section.permission & Permission.READ: if is_ascii_string(addr.value): s = gef.memory.read_cstring(addr.value) if len(s) < gef.arch.ptrsize: txt = f'{format_address(deref)} ("{Color.colorify(s, string_color)}"?)' elif len(s) > 50: txt = Color.colorify(f'"{s[:50]}[...]"', string_color) else: txt = Color.colorify(f'"{s}"', string_color) msg.append(txt) break # if not able to parse cleanly, simply display and break val = "{:#0{ma}x}".format(int(deref & 0xFFFFFFFFFFFFFFFF), ma=(gef.arch.ptrsize * 2 + 2)) msg.append(val) break return msg @register_command class DereferenceCommand(GenericCommand): """Dereference recursively from an address and display information. This acts like WinDBG `dps` command.""" _cmdline_ = "dereference" _syntax_ = f"{_cmdline_} [-h] [--length LENGTH] [--reference REFERENCE] [address]" _aliases_ = ["telescope", ] _example_ = f"{_cmdline_} --length 20 --reference $sp+0x10 $sp" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) self["max_recursion"] = (7, "Maximum level of pointer recursion") return @staticmethod def pprint_dereferenced(addr: int, idx: int, base_offset: int = 0) -> str: base_address_color = gef.config["theme.dereference_base_address"] registers_color = gef.config["theme.dereference_register_value"] sep = f" {RIGHT_ARROW} " memalign = gef.arch.ptrsize offset = idx * memalign current_address = align_address(addr + offset) addrs = dereference_from(current_address) l = "" addr_l = format_address(int(addrs[0], 16)) l += "{}{}{:+#07x}: {:{ma}s}".format(Color.colorify(addr_l, base_address_color), VERTICAL_LINE, base_offset+offset, sep.join(addrs[1:]), ma=(memalign*2 + 2)) register_hints = [] for regname in gef.arch.all_registers: regvalue = gef.arch.register(regname) if current_address == regvalue: register_hints.append(regname) if register_hints: m = f"\t{LEFT_ARROW}{', '.join(list(register_hints))}" l += Color.colorify(m, registers_color) offset += memalign return l @only_if_gdb_running @parse_arguments({"address": "$sp"}, {("-r", "--reference"): "", ("-l", "--length"): 10}) def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = kwargs["arguments"] nb = args.length target = args.address target_addr = parse_address(target) reference = args.reference or target ref_addr = parse_address(reference) if process_lookup_address(target_addr) is None: err(f"Unmapped address: '{target}'") return if process_lookup_address(ref_addr) is None: err(f"Unmapped address: '{reference}'") return if gef.config["context.grow_stack_down"] is True: from_insnum = nb * (self.repeat_count + 1) - 1 to_insnum = self.repeat_count * nb - 1 insnum_step = -1 else: from_insnum = 0 + self.repeat_count * nb to_insnum = nb * (self.repeat_count + 1) insnum_step = 1 start_address = align_address(target_addr) base_offset = start_address - align_address(ref_addr) for i in range(from_insnum, to_insnum, insnum_step): gef_print(DereferenceCommand.pprint_dereferenced(start_address, i, base_offset)) return @register_command class ASLRCommand(GenericCommand): """View/modify the ASLR setting of GDB. By default, GDB will disable ASLR when it starts the process. (i.e. not attached). This command allows to change that setting.""" _cmdline_ = "aslr" _syntax_ = f"{_cmdline_} [(on|off)]" def do_invoke(self, argv: List[str]) -> None: argc = len(argv) if argc == 0: ret = gdb.execute("show disable-randomization", to_string=True) i = ret.find("virtual address space is ") if i < 0: return msg = "ASLR is currently " if ret[i + 25:].strip() == "on.": msg += Color.redify("disabled") else: msg += Color.greenify("enabled") gef_print(msg) return elif argc == 1: if argv[0] == "on": info("Enabling ASLR") gdb.execute("set disable-randomization off") return elif argv[0] == "off": info("Disabling ASLR") gdb.execute("set disable-randomization on") return warn("Invalid command") self.usage() return @register_command class ResetCacheCommand(GenericCommand): """Reset cache of all stored data. This command is here for debugging and test purposes, GEF handles properly the cache reset under "normal" scenario.""" _cmdline_ = "reset-cache" _syntax_ = _cmdline_ def do_invoke(self, _: List[str]) -> None: reset_all_caches() return @register_command class VMMapCommand(GenericCommand): """Display a comprehensive layout of the virtual memory mapping. If a filter argument, GEF will filter out the mapping whose pathname do not match that filter.""" _cmdline_ = "vmmap" _syntax_ = f"{_cmdline_} [FILTER]" _example_ = f"{_cmdline_} libc" @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: vmmap = gef.memory.maps if not vmmap: err("No address mapping information found") return if not gef.config["gef.disable_color"]: self.show_legend() 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)) for entry in vmmap: if not argv: self.print_entry(entry) continue if argv[0] in entry.path: self.print_entry(entry) elif self.is_integer(argv[0]): addr = int(argv[0], 0) if addr >= entry.page_start and addr < entry.page_end: self.print_entry(entry) return def print_entry(self, entry: Section) -> None: line_color = "" if entry.path == "[stack]": 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: line_color = gef.config["theme.address_code"] l = [ 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)) else: l.append(Color.colorify(str(entry.permission), line_color)) l.append(Color.colorify(entry.path, line_color)) line = " ".join(l) gef_print(line) return def show_legend(self) -> None: code_addr_color = gef.config["theme.address_code"] stack_addr_color = gef.config["theme.address_stack"] heap_addr_color = gef.config["theme.address_heap"] gef_print("[ Legend: {} | {} | {} ]".format(Color.colorify("Code", code_addr_color), Color.colorify("Heap", heap_addr_color), Color.colorify("Stack", stack_addr_color) )) return def is_integer(self, n: str) -> bool: try: int(n, 0) except ValueError: return False return True @register_command class XFilesCommand(GenericCommand): """Shows all libraries (and sections) loaded by binary. This command extends the GDB command `info files`, by retrieving more information from extra sources, and providing a better display. If an argument FILE is given, the output will grep information related to only that file. If an argument name is also given, the output will grep to the name within FILE.""" _cmdline_ = "xfiles" _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)) 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 for xfile in get_info_files(): if filter_by_file: if filter_by_file not in xfile.filename: continue if filter_by_name and filter_by_name not in xfile.name: continue l = [ format_address(xfile.zone_start), format_address(xfile.zone_end), f"{xfile.name:<21s}", xfile.filename, ] gef_print(" ".join(l)) return @register_command class XAddressInfoCommand(GenericCommand): """Retrieve and display runtime information for the location(s) given as parameter.""" _cmdline_ = "xinfo" _syntax_ = f"{_cmdline_} LOCATION" _example_ = f"{_cmdline_} $pc" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) return @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: if not argv: err("At least one valid address must be specified") self.usage() return for sym in argv: try: addr = align_address(parse_address(sym)) gef_print(titlify(f"xinfo: {addr:#x}")) self.infos(addr) except gdb.error as gdb_err: err(f"{gdb_err}") return def infos(self, address: int) -> None: addr = lookup_address(address) if not addr.valid: warn(f"Cannot reach {address:#x} in memory space") return sect = addr.section 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}") 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}") sym = gdb_get_location_from_symbol(address) if sym: name, offset = sym msg = f"Symbol: {name}" if offset: msg += f"+{offset:d}" gef_print(msg) return @register_command class XorMemoryCommand(GenericCommand): """XOR a block of memory. The command allows to simply display the result, or patch it runtime at runtime.""" _cmdline_ = "xor-memory" _syntax_ = f"{_cmdline_} (display|patch) ADDRESS SIZE KEY" def __init__(self) -> None: super().__init__(prefix=True) return def do_invoke(self, _: List[str]) -> None: self.usage() return @register_command class XorMemoryDisplayCommand(GenericCommand): """Display a block of memory pointed by ADDRESS by xor-ing each byte with KEY. The key must be provided in hexadecimal format.""" _cmdline_ = "xor-memory display" _syntax_ = f"{_cmdline_} ADDRESS SIZE KEY" _example_ = f"{_cmdline_} $sp 16 41414141" @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: if len(argv) != 3: self.usage() return address = parse_address(argv[0]) length = int(argv[1], 0) key = argv[2] block = gef.memory.read(address, length) info(f"Displaying XOR-ing {address:#x}-{address + len(block):#x} with {key!r}") gef_print(titlify("Original block")) gef_print(hexdump(block, base=address)) gef_print(titlify("XOR-ed block")) gef_print(hexdump(xor(block, key), base=address)) return @register_command class XorMemoryPatchCommand(GenericCommand): """Patch a block of memory pointed by ADDRESS by xor-ing each byte with KEY. The key must be provided in hexadecimal format.""" _cmdline_ = "xor-memory patch" _syntax_ = f"{_cmdline_} ADDRESS SIZE KEY" _example_ = f"{_cmdline_} $sp 16 41414141" @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: if len(argv) != 3: self.usage() return address = parse_address(argv[0]) length = int(argv[1], 0) key = argv[2] block = gef.memory.read(address, length) info(f"Patching XOR-ing {address:#x}-{address + len(block):#x} with {key!r}") xored_block = xor(block, key) gef.memory.write(address, xored_block, length) return @register_command class TraceRunCommand(GenericCommand): """Create a runtime trace of all instructions executed from $pc to LOCATION specified. The trace is stored in a text file that can be next imported in IDA Pro to visualize the runtime path.""" _cmdline_ = "trace-run" _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") return @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: if len(argv) not in (1, 2): self.usage() return if len(argv) == 2 and argv[1].isdigit(): depth = int(argv[1]) else: depth = 1 try: loc_start = gef.arch.pc loc_end = parse_address(argv[0]) except gdb.error as e: err(f"Invalid location: {e}") return self.trace(loc_start, loc_end, depth) return def get_frames_size(self) -> int: n = 0 f = gdb.newest_frame() while f: n += 1 f = f.older() return n def trace(self, loc_start: int, loc_end: int, depth: int) -> None: info(f"Tracing from {loc_start:#x} to {loc_end:#x} (max depth={depth:d})") logfile = f"{self['tracefile_prefix']}{loc_start:#x}-{loc_end:#x}.txt" with RedirectOutputContext(to=logfile): hide_context() 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") 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") while loc_cur != loc_end: try: delta = self.get_frames_size() - frame_count_init if delta <= depth: gdb.execute("stepi") else: gdb.execute("finish") loc_cur = gef.arch.pc gdb.flush() except gdb.error as e: gef_print("#", f"# Execution interrupted at address {format_address(loc_cur)}", f"# Exception: {e}", "#\n", sep="\n") break return @register_command class PatternCommand(GenericCommand): """Generate or Search a De Bruijn Sequence of unique substrings of length N and a total length of LENGTH. The default value of N is set to match the currently loaded architecture.""" _cmdline_ = "pattern" _syntax_ = f"{_cmdline_} (create|search) ARGS" def __init__(self) -> None: super().__init__(prefix=True) self["length"] = (1024, "Default length of a cyclic buffer to generate") return def do_invoke(self, _: List[str]) -> None: self.usage() return @register_command class PatternCreateCommand(GenericCommand): """Generate a De Bruijn Sequence of unique substrings of length N and a total length of LENGTH. The default value of N is set to match the currently loaded architecture.""" _cmdline_ = "pattern create" _syntax_ = f"{_cmdline_} [-h] [-n N] [length]" _example_ = f"{_cmdline_} 4096" @parse_arguments({"length": 0}, {("-n", "--n"): 0}) def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = 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})") pattern_str = gef_pystring(generate_cyclic_pattern(length, n)) gef_print(pattern_str) ok(f"Saved as '{gef_convenience(pattern_str)}'") return @register_command class PatternSearchCommand(GenericCommand): """Search a De Bruijn Sequence of unique substrings of length N and a maximum total length of MAX_LENGTH. The default value of N is set to match the currently loaded architecture. The PATTERN argument can be a GDB symbol (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"\n{_cmdline_} $pc" f"\n{_cmdline_} 0x61616164" f"\n{_cmdline_} aaab") _aliases_ = ["pattern offset"] @only_if_gdb_running @parse_arguments({"pattern": ""}, {("-n", "--n"): 0, ("-l", "--max-length"): 0}) def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = kwargs["arguments"] max_length = args.max_length or gef.config["pattern.length"] n = args.n or gef.arch.ptrsize info(f"Searching for '{args.pattern}'") self.search(args.pattern, max_length, n) return def search(self, pattern: str, size: int, period: int) -> None: pattern_be, pattern_le = None, None # 1. check if it's a symbol (like "$sp" or "0x1337") symbol = safe_parse_and_eval(pattern) if symbol: addr = int(symbol) dereferenced_value = dereference(addr) # 1-bis. try to dereference if dereferenced_value: addr = int(dereferenced_value) struct_packsize = { 2: "H", 4: "I", 8: "Q", } pattern_be = struct.pack(f">{struct_packsize[gef.arch.ptrsize]}", addr) pattern_le = struct.pack(f"<{struct_packsize[gef.arch.ptrsize]}", addr) else: # 2. assume it's a plain string pattern_be = gef_pybytes(pattern) pattern_le = gef_pybytes(pattern[::-1]) cyclic_pattern = generate_cyclic_pattern(size, period) found = False 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 ''}") found = True 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 ''}") found = True if not found: err(f"Pattern '{pattern}' not found") return @register_command class ChecksecCommand(GenericCommand): """Checksec the security properties of the current executable or passed as argument. The command checks for the following protections: - PIE - NX - RelRO - Glibc Stack Canaries - Fortify Source""" _cmdline_ = "checksec" _syntax_ = f"{_cmdline_} [FILENAME]" _example_ = f"{_cmdline_} /bin/ls" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_FILENAME) return def do_invoke(self, argv: List[str]) -> None: argc = len(argv) if argc == 0: filename = get_filepath() if filename is None: warn("No executable/library specified") return elif argc == 1: filename = os.path.realpath(os.path.expanduser(argv[0])) if not os.access(filename, os.R_OK): err("Invalid filename") return else: self.usage() return info(f"{self._cmdline_} for '{filename}'") self.print_security_properties(filename) return def print_security_properties(self, filename: str) -> None: sec = checksec(filename) for prop in sec: 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)) if val and prop == "Canary" and is_alive(): canary = gef.session.canary[0] if gef.session.canary else 0 msg += f"(value: {canary:#x})" gef_print(f"{prop:<30s}: {msg}") if sec["Full RelRO"]: gef_print(f"{'RelRO':<30s}: {Color.greenify('Full')}") elif sec["Partial RelRO"]: gef_print(f"{'RelRO':<30s}: {Color.yellowify('Partial')}") else: gef_print(f"{'RelRO':<30s}: {Color.redify(Color.boldify(CROSS))}") return @register_command class GotCommand(GenericCommand): """Display current status of the got inside the process.""" _cmdline_ = "got" _syntax_ = f"{_cmdline_} [FUNCTION_NAME ...] " _example_ = "got read printf exit" 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") return def get_jmp_slots(self, readelf: str, filename: str) -> List[str]: cmd = [readelf, "--relocs", filename] lines = gef_execute_external(cmd, as_list=True) return [line for line in lines if "JUMP" in line] @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: try: readelf = gef.session.constants["readelf"] except OSError: err("Missing `readelf`") return # get the filtering parameter. func_names_filter = [] if argv: func_names_filter = argv # getting vmmap to understand the boundaries of the main binary # we will use this info to understand if a function has been resolved or not. vmmap = gef.memory.maps base_address = min(x.page_start for x in vmmap if x.path == get_filepath()) end_address = max(x.page_end for x in vmmap if x.path == get_filepath()) # get the checksec output. checksec_status = checksec(get_filepath()) relro_status = "Full RelRO" full_relro = checksec_status["Full RelRO"] pie = checksec_status["PIE"] # if pie we will have offset instead of abs address. if not full_relro: relro_status = "Partial RelRO" partial_relro = checksec_status["Partial RelRO"] if not partial_relro: relro_status = "No RelRO" # retrieve jump slots using readelf jmpslots = self.get_jmp_slots(readelf, get_filepath()) gef_print(f"\nGOT protection: {relro_status} | GOT functions: {len(jmpslots)}\n ") for line in jmpslots: address, _, _, _, name = line.split()[:5] # if we have a filter let's skip the entries that are not requested. if func_names_filter: if not any(map(lambda x: x in name, func_names_filter)): continue address_val = int(address, 16) # address_val is an offset from the base_address if we have PIE. if pie: address_val = base_address + address_val # read the address of the function. got_address = gef.memory.read_integer(address_val) # for the swag: different colors if the function has been resolved or not. if base_address < got_address < end_address: color = self["function_not_resolved"] else: color = self["function_resolved"] line = f"[{hex(address_val)}] " line += Color.colorify(f"{name} {RIGHT_ARROW} {hex(got_address)}", color) gef_print(line) return @register_command class HighlightCommand(GenericCommand): """Highlight user-defined text matches in GEF output universally.""" _cmdline_ = "highlight" _syntax_ = f"{_cmdline_} (add|remove|list|clear)" _aliases_ = ["hl"] def __init__(self) -> None: super().__init__(prefix=True) self["regex"] = (False, "Enable regex highlighting") def do_invoke(self, _: List[str]) -> None: return self.usage() @register_command class HighlightListCommand(GenericCommand): """Show the current highlight table with matches to colors.""" _cmdline_ = "highlight list" _aliases_ = ["highlight ls", "hll"] _syntax_ = _cmdline_ def print_highlight_table(self) -> None: if not gef.ui.highlight_table: err("no matches found") return 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)}") return def do_invoke(self, _: List[str]) -> None: return self.print_highlight_table() @register_command class HighlightClearCommand(GenericCommand): """Clear the highlight table, remove all matches.""" _cmdline_ = "highlight clear" _aliases_ = ["hlc"] _syntax_ = _cmdline_ def do_invoke(self, _: List[str]) -> None: return gef.ui.highlight_table.clear() @register_command class HighlightAddCommand(GenericCommand): """Add a match to the highlight table.""" _cmdline_ = "highlight add" _syntax_ = f"{_cmdline_} MATCH COLOR" _aliases_ = ["highlight set", "hla"] _example_ = f"{_cmdline_} 41414141 yellow" def do_invoke(self, argv: List[str]) -> None: if len(argv) < 2: return self.usage() match, color = argv gef.ui.highlight_table[match] = color return @register_command class HighlightRemoveCommand(GenericCommand): """Remove a match in the highlight table.""" _cmdline_ = "highlight remove" _syntax_ = f"{_cmdline_} MATCH" _aliases_ = [ "highlight delete", "highlight del", "highlight unset", "highlight rm", "hlr", ] _example_ = f"{_cmdline_} remove 41414141" def do_invoke(self, argv: List[str]) -> None: if not argv: return self.usage() gef.ui.highlight_table.pop(argv[0], None) return @register_command class FormatStringSearchCommand(GenericCommand): """Exploitable format-string helper: this command will set up specific breakpoints 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",] def do_invoke(self, _: List[str]) -> None: dangerous_functions = { "printf": 0, "sprintf": 1, "fprintf": 1, "snprintf": 2, "vsnprintf": 2, } nb_installed_breaks = 0 with RedirectOutputContext(to="/dev/null"): for function_name in dangerous_functions: argument_number = dangerous_functions[function_name] 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 ''}") return @register_command class HeapAnalysisCommand(GenericCommand): """Heap vulnerability analysis helper: this command aims to track dynamic heap allocation done through malloc()/free() to provide some insights on possible heap vulnerabilities. The following vulnerabilities are checked: - NULL free - 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.bp_malloc = None self.bp_calloc = None self.bp_free = None self.bp_realloc = None return @only_if_gdb_running @experimental_feature def do_invoke(self, argv: List[str]) -> None: if not argv: self.setup() return if argv[0] == "show": self.dump_tracked_allocations() return def setup(self) -> None: ok("Tracking malloc() & calloc()") self.bp_malloc = TraceMallocBreakpoint("__libc_malloc") self.bp_calloc = TraceMallocBreakpoint("__libc_calloc") ok("Tracking free()") self.bp_free = TraceFreeBreakpoint() ok("Tracking realloc()") self.bp_realloc = TraceReallocBreakpoint() 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 vulnerabity 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) return def dump_tracked_allocations(self) -> None: global gef if gef.session.heap_allocated_chunks: ok("Tracked as in-use chunks:") for addr, sz in gef.session.heap_allocated_chunks: gef_print(f"{CROSS} malloc({sz:d}) = {addr:#x}") else: ok("No malloc() chunk tracked") if gef.session.heap_freed_chunks: ok("Tracked as free-ed chunks:") for addr, sz in gef.session.heap_freed_chunks: gef_print(f"{TICK} free({sz:d}) = {addr:#x}") else: ok("No free() chunk tracked") return def clean(self, _: "gdb.Event") -> None: global gef ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - Cleaning up") for bp in [self.bp_malloc, self.bp_calloc, self.bp_free, self.bp_realloc]: if hasattr(bp, "retbp") and bp.retbp: try: bp.retbp.delete() except RuntimeError: # in some cases, gdb was found failing to correctly remove the retbp # but they can be safely ignored since the debugging session is over pass bp.delete() for wp in gef.session.heap_uaf_watchpoints: wp.delete() gef.session.heap_allocated_chunks = [] gef.session.heap_freed_chunks = [] gef.session.heap_uaf_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) return @register_command class IsSyscallCommand(GenericCommand): """Tells whether the next instruction is a system call.""" _cmdline_ = "is-syscall" _syntax_ = _cmdline_ def do_invoke(self, _: List[str]) -> None: insn = gef_current_instruction(gef.arch.pc) ok(f"Current instruction is{' ' if self.is_syscall(gef.arch, insn) else ' not '}a syscall") return def is_syscall(self, arch: Architecture, instruction: Instruction) -> bool: insn_str = instruction.mnemonic + " " + ", ".join(instruction.operands) return insn_str.strip() in arch.syscall_instructions @register_command class SyscallArgsCommand(GenericCommand): """Gets the syscall name and arguments based on the register values in the current state.""" _cmdline_ = "syscall-args" _syntax_ = _cmdline_ def __init__(self) -> None: super().__init__() path = pathlib.Path(gef.config["gef.tempdir"]) / "syscall-tables" self["path"] = (str(path.absolute()), "Path to store/load the syscall tables files") return def do_invoke(self, _: List[str]) -> None: path = self.get_settings_path() if not path: err(f"Cannot open '{self['path']}': check directory and/or " "`gef config syscall-args.path` setting.") return color = gef.config["theme.table_heading"] arch = gef.arch.__class__.__name__ syscall_table = self.get_syscall_table(arch) reg_value = gef.arch.register(gef.arch.syscall_register) if reg_value not in syscall_table: warn(f"There is no system call for {reg_value:#x}") return syscall_entry = syscall_table[reg_value] values = [] for param in syscall_entry.params: values.append(gef.arch.register(param.reg)) parameters = [s.param for s in syscall_entry.params] registers = [s.reg for s in syscall_entry.params] info(f"Detected syscall {Color.colorify(syscall_entry.name, color)}") gef_print(f" {syscall_entry.name}({', '.join(parameters)})") headers = ["Parameter", "Register", "Value"] param_names = [re.split(r" |\*", p)[-1] for p in parameters] info(Color.colorify("{:<20} {:<20} {}".format(*headers), color)) for name, register, value in zip(param_names, registers, values): line = f" {name:<20} {register:<20} {value:#x}" addrs = dereference_from(value) if len(addrs) > 1: sep = f" {RIGHT_ARROW} " line += sep line += sep.join(addrs[1:]) gef_print(line) return def get_syscall_table(self, modname: str) -> Dict[str, Any]: _mod = self.get_module(modname) return getattr(_mod, "syscall_table") def get_module(self, modname: str) -> Any: _fullname = self.get_filepath(modname).absolute() return importlib.machinery.SourceFileLoader(modname, _fullname).load_module(None) def get_filepath(self, x: str) -> Optional[pathlib.Path]: p = self.get_settings_path() if not p: return None return p / f"{x}.py" def get_settings_path(self) -> Optional[pathlib.Path]: path = pathlib.Path(self["path"]).expanduser() return path if path.is_dir() else None # # GDB Function declaration # class GenericFunction(gdb.Function, metaclass=abc.ABCMeta): """This is an abstract class for invoking convenience functions, should not be instantiated.""" _example_ = "" @abc.abstractproperty def _function_(self) -> str: pass @property def _syntax_(self) -> str: return f"${self._function_}([offset])" def __init__(self) -> None: super().__init__(self._function_) def invoke(self, *args: Any) -> int: if not is_alive(): 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: try: addr = args[index] return int(addr) if addr.address is None else int(addr.address) except IndexError: return default @abc.abstractmethod def do_invoke(self, args: List) -> int: pass @register_function class StackOffsetFunction(GenericFunction): """Return the current stack base address plus an optional offset.""" _function_ = "_stack" def do_invoke(self, args: List) -> int: base = get_section_base_address("[stack]") if not base: raise gdb.GdbError("Stack not found") return self.arg_to_long(args, 0) + base @register_function class HeapBaseFunction(GenericFunction): """Return the current heap base address plus an optional offset.""" _function_ = "_heap" def do_invoke(self, args: List) -> int: base = gef.heap.base_address if not base: base = get_section_base_address("[heap]") if not base: raise gdb.GdbError("Heap not found") return self.arg_to_long(args, 0) + base @register_function 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\\\")" def do_invoke(self, args: List) -> int: try: name = args[0].string() except IndexError: name = gef.session.file.name except gdb.error: err(f"Invalid arg: {args[0]}") return 0 try: addr = int(get_section_base_address(name)) except TypeError: err(f"Cannot find section {name}") return 0 return addr @register_function class BssBaseFunction(GenericFunction): """Return the current bss base address plus the given offset.""" _function_ = "_bss" _example_ = "deref $_bss(0x20)" def do_invoke(self, args: List) -> int: base = get_zone_base_address(".bss") if not base: raise gdb.GdbError("BSS not found") return self.arg_to_long(args, 0) + base @register_function class GotBaseFunction(GenericFunction): """Return the current GOT base address plus the given offset.""" _function_ = "_got" _example_ = "deref $_got(0x20)" def do_invoke(self, args: List) -> int: base = get_zone_base_address(".got") if not base: raise gdb.GdbError("GOT not found") return base + self.arg_to_long(args, 0) @register_command class GefFunctionsCommand(GenericCommand): """List the convenience functions provided by GEF.""" _cmdline_ = "functions" _syntax_ = _cmdline_ def __init__(self) -> None: super().__init__() self.docs = [] self.setup() return def setup(self) -> None: global gef for function in gef.gdb.loaded_functions: self.add_function_to_doc(function) self.__doc__ = "\n".join(sorted(self.docs)) return def add_function_to_doc(self, function) -> None: """Add function to documentation.""" doc = getattr(function, "__doc__", "").lstrip() doc = "\n ".join(doc.split("\n")) syntax = getattr(function, "_syntax_", "").lstrip() msg = f"{syntax:<25s} -- {Color.greenify(doc)}" example = getattr(function, "_example_", "").strip() if example: msg += f"\n {'':27s} example: {Color.yellowify(example)}" self.docs.append(msg) return 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(self.__doc__) return # # GEF internal command classes # 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)" 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.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.extra_plugins_dir"] = GefSetting("", str, "Autoload additional GEF commands from external directory") gef.config["gef.disable_color"] = GefSetting(False, bool, "Disable all colors in GEF") gef.config["gef.tempdir"] = GefSetting(GEF_TEMP_DIR, str, "Directory to use for temporary/cache content") gef.config["gef.show_deprecation_warnings"] = GefSetting(True, bool, "Toggle the display of the `deprecated` warnings") self.loaded_commands: List[Tuple[str, Type[GenericCommand], Any]] = [] self.loaded_functions: List[Type[GenericFunction]] = [] self.missing_commands: Dict[str, Exception] = {} return def setup(self) -> None: self.load(initial=True) # loading GEF sub-commands self.doc = GefHelpCommand(self.loaded_commands) self.cfg = GefConfigCommand(self.loaded_command_names) GefSaveCommand() GefRestoreCommand() GefMissingCommand() GefSetCommand() GefRunCommand() # load the saved settings gdb.execute("gef restore") # restore the autosave/autoreload breakpoints policy (if any) self.__reload_auto_breakpoints() # load plugins from `extra_plugins_dir` if self.__load_extra_plugins() > 0: # if here, at least one extra plugin was loaded, so we need to restore # the settings once more gdb.execute("gef restore quiet") return def __reload_auto_breakpoints(self) -> None: bkp_fname = gef.config["gef.autosave_breakpoints_file"] bkp_fname = bkp_fname[0] if bkp_fname else None if bkp_fname: # restore if existing if os.access(bkp_fname, os.R_OK): gdb.execute(f"source {bkp_fname}") # add hook for autosave breakpoints on quit command source = [ "define hook-quit", f" save breakpoints {bkp_fname}", "end", ] gef_execute_gdb_script("\n".join(source) + "\n") return def __load_extra_plugins(self) -> int: nb_added = -1 try: nb_inital = len(self.loaded_commands) directories = gef.config["gef.extra_plugins_dir"] if directories: for directory in directories.split(";"): directory = os.path.realpath(os.path.expanduser(directory)) if os.path.isdir(directory): sys.path.append(directory) for fname in os.listdir(directory): if not fname.endswith(".py"): continue fpath = f"{directory}/{fname}" if os.path.isfile(fpath): gdb.execute(f"source {fpath}") nb_added = len(self.loaded_commands) - nb_inital if nb_added > 0: ok(f"{Color.colorify(nb_added, 'bold green')} extra commands added from " f"'{Color.colorify(directories, 'bold blue')}'") except gdb.error as e: err(f"failed: {e}") return nb_added @property def loaded_command_names(self) -> List[str]: return [x[0] for x in self.loaded_commands] def invoke(self, args: Any, from_tty: bool) -> None: self.dont_repeat() gdb.execute("gef help") return def add_context_pane(self, pane_name: str, display_pane_function: Callable, pane_title_function: Callable) -> None: """Add a new context pane to ContextCommand.""" for _, _, class_instance in self.loaded_commands: if isinstance(class_instance, ContextCommand): context = class_instance break else: err("Cannot find ContextCommand") return # assure users can toggle the new context corrected_settings_name = pane_name.replace(" ", "_") gef.config["context.layout"] += f" {corrected_settings_name}" # overload the printing of pane title context.layout_mapping[corrected_settings_name] = (display_pane_function, pane_title_function) def load(self, initial: bool = False) -> None: """Load all the commands and functions defined by GEF into GDB.""" nb_missing = 0 self.commands = [(x._cmdline_, x) for x in __registered_commands__] # load all of the functions for function_class_name in __registered_functions__: self.loaded_functions.append(function_class_name()) def is_loaded(x: str) -> bool: return any(u for u in self.loaded_commands if x == u[0]) for cmd, class_obj in self.commands: if is_loaded(cmd): continue try: self.loaded_commands.append((cmd, class_obj, class_obj())) if hasattr(class_obj, "_aliases_"): aliases = getattr(class_obj, "_aliases_") for alias in aliases: GefAlias(alias, cmd) except Exception as reason: self.missing_commands[cmd] = reason nb_missing += 1 # sort by command name self.loaded_commands = sorted(self.loaded_commands, key=lambda x: x[1]._cmdline_) if initial: 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}" nb_cmds = len(self.loaded_commands) gef_print(f"{Color.colorify(nb_cmds, 'bold green')} commands loaded for " f"GDB {Color.colorify(gdb.VERSION, 'bold yellow')} " f"using Python engine {Color.colorify(ver, 'bold red')}") if nb_missing: warn(f"{Color.colorify(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_ def __init__(self, commands: List[Tuple[str, Any, Any]]) -> None: super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) self.docs = [] self.generate_help(commands) self.refresh() return def invoke(self, args: Any, from_tty: bool) -> None: self.dont_repeat() gef_print(titlify("GEF - GDB Enhanced Features")) gef_print(self.__doc__ or "") return def generate_help(self, commands: List[Tuple[str, Type[GenericCommand], Any]]) -> None: """Generate builtin commands documentation.""" for command in commands: self.add_command_to_doc(command) return def add_command_to_doc(self, command: Tuple[str, Type[GenericCommand], Any]) -> None: """Add command to GEF documentation.""" cmd, class_obj, _ = command if " " in cmd: # do not print subcommands in gef help return doc = getattr(class_obj, "__doc__", "").lstrip() doc = "\n ".join(doc.split("\n")) aliases = f" (alias: {', '.join(class_obj._aliases_)})" if hasattr(class_obj, "_aliases_") else "" msg = f"{cmd:<25s} -- {doc}{aliases}" self.docs.append(msg) return def refresh(self) -> None: """Refresh the documentation.""" self.__doc__ = "\n".join(sorted(self.docs)) return class GefConfigCommand(gdb.Command): """GEF configuration sub-command This command will help set/view GEF settings for the current debugging session. It is possible to make those changes permanent by running `gef save` (refer 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]" def __init__(self, loaded_commands: List[str]) -> None: super().__init__(self._cmdline_, gdb.COMMAND_NONE, prefix=False) self.loaded_commands = loaded_commands return def invoke(self, args: str, from_tty: bool) -> None: self.dont_repeat() argv = gdb.string_to_argv(args) argc = len(argv) if not (0 <= argc <= 2): err("Invalid number of arguments") return if argc == 0: gef_print(titlify("GEF configuration settings")) self.print_settings() return if argc == 1: prefix = argv[0] names = [x for x in gef.config.keys() if x.startswith(prefix)] if names: if len(names) == 1: 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) return self.set_setting(argv) return def print_setting(self, plugin_name: str, verbose: bool = False) -> None: res = gef.config.raw_entry(plugin_name) string_color = gef.config["theme.dereference_string"] misc_color = gef.config["theme.dereference_base_address"] if not res: return _setting = Color.colorify(plugin_name, "green") _type = res.type.__name__ if _type == "str": _value = f'"{Color.colorify(res.value, string_color)}"' else: _value = Color.colorify(res.value, misc_color) gef_print(f"{_setting} ({_type}) = {_value}") if verbose: gef_print(Color.colorify("\nDescription:", "bold underline")) gef_print(f"\t{res.description}") return def print_settings(self) -> None: for x in sorted(gef.config): self.print_setting(x) return def set_setting(self, argv: Tuple[str, Any]) -> None: global gef key, new_value = argv if "." not in key: err("Invalid command format") return loaded_commands = [ x[0] for x in gef.gdb.loaded_commands ] + ["gef"] plugin_name = key.split(".", 1)[0] if plugin_name not in loaded_commands: err(f"Unknown plugin '{plugin_name}'") return if key not in gef.config: err(f"'{key}' is not a valid configuration setting") return _type = gef.config.raw_entry(key).type try: if _type == bool: _newval = True if new_value.upper() in ("TRUE", "T", "1") else False else: _newval = new_value gef.config[key] = _newval except Exception: err(f"{key} expects type '{_type.__name__}'") return reset_all_caches() return def complete(self, text: str, word: str) -> List[str]: settings = sorted(gef.config) if text == "": # no prefix: example: `gef config TAB` return [s for s in settings if word in s] if "." not in text: # if looking for possible prefix return [s for s in settings if s.startswith(text.strip())] # finally, look for possible values for given prefix return [s.split(".", 1)[1] for s in settings if s.startswith(text.strip())] 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_ def __init__(self) -> None: super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) return def invoke(self, args: Any, from_tty: bool) -> None: self.dont_repeat() cfg = configparser.RawConfigParser() old_sect = None # save the configuration for key in sorted(gef.config): sect, optname = key.split(".", 1) value = gef.config[key] if old_sect != sect: cfg.add_section(sect) old_sect = sect cfg.set(sect, optname, value) # save the aliases cfg.add_section("aliases") for alias in gef.session.aliases: cfg.set("aliases", alias._alias, alias._command) with GEF_RC.open("w") as fd: cfg.write(fd) ok(f"Configuration saved to '{GEF_RC}'") return 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_ def __init__(self) -> None: super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) return def invoke(self, args: str, from_tty: bool) -> None: self.dont_repeat() if not os.access(GEF_RC, os.R_OK): return quiet = args.lower() == "quiet" cfg = configparser.ConfigParser() cfg.read(GEF_RC) for section in cfg.sections(): if section == "aliases": # load the aliases for key in cfg.options(section): try: GefAlias(key, cfg.get(section, key)) except: pass continue # load the other options for optname in cfg.options(section): key = f"{section}.{optname}" try: setting = gef.config.raw_entry(key) except Exception as e: warn(f"Invalid setting '{key}': {e}") continue new_value = cfg.get(section, optname) if setting.type == bool: new_value = True if new_value.upper() in ("TRUE", "T", "1") else False setting.value = setting.type(new_value) # ensure that the temporary directory always exists gef_makedirs(gef.config["gef.tempdir"]) if not quiet: ok(f"Configuration from '{Color.colorify(str(GEF_RC), 'bold blue')}' restored") return class GefMissingCommand(gdb.Command): """GEF missing sub-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_ def __init__(self) -> None: super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) return def invoke(self, args: Any, from_tty: bool) -> None: self.dont_repeat() missing_commands = gef.gdb.missing_commands.keys() if not missing_commands: ok("No missing command") return for missing_command in missing_commands: reason = gef.gdb.missing_commands[missing_command] warn(f"Command `{missing_command}` is missing, reason {RIGHT_ARROW} {reason}") return class GefSetCommand(gdb.Command): """Override GDB set commands with the context from GEF.""" _cmdline_ = "gef set" _syntax_ = f"{_cmdline_} [GDB_SET_ARGUMENTS]" def __init__(self) -> None: 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],] for p in args[1:]: if p.startswith("$_gef"): c = gdb.parse_and_eval(p) cmd.append(c.string()) else: cmd.append(p) gdb.execute(" ".join(cmd)) return 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]" def __init__(self) -> None: super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_FILENAME, False) return def invoke(self, args: Any, from_tty: bool) -> None: self.dont_repeat() if is_alive(): gdb.execute("continue") return argv = args.split() gdb.execute(f"gef set args {' '.join(argv)}") gdb.execute("run") return 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: p = command.split() if not p: return if any(x for x in gef.session.aliases if x._alias == alias): return self._command = command self._alias = alias c = command.split()[0] r = self.lookup_command(c) self.__doc__ = f"Alias for '{Color.greenify(command)}'" if r is not None: _instance = r[2] self.__doc__ += f": {_instance.__doc__}" if hasattr(_instance, "complete"): self.complete = _instance.complete super().__init__(alias, command_class, completer_class=completer_class) gef.session.aliases.append(self) return 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, Type, Any]]: global gef for _name, _class, _instance in gef.gdb.loaded_commands: if cmd == _name: return _name, _class, _instance return None @register_command class AliasesCommand(GenericCommand): """Base command to add, remove, or list aliases.""" _cmdline_ = "aliases" _syntax_ = f"{_cmdline_} (add|rm|ls)" def __init__(self) -> None: super().__init__(prefix=True) return def do_invoke(self, _: List[str]) -> None: self.usage() return @register_command class AliasesAddCommand(AliasesCommand): """Command to add aliases.""" _cmdline_ = "aliases add" _syntax_ = f"{_cmdline_} [ALIAS] [COMMAND]" _example_ = f"{_cmdline_} scope telescope" def __init__(self) -> None: super().__init__() return def do_invoke(self, argv: List[str]) -> None: if len(argv) < 2: self.usage() return GefAlias(argv[0], " ".join(argv[1:])) return @register_command class AliasesRmCommand(AliasesCommand): """Command to remove aliases.""" _cmdline_ = "aliases rm" _syntax_ = f"{_cmdline_} [ALIAS]" def __init__(self) -> None: super().__init__() return def do_invoke(self, argv: List[str]) -> None: global gef if len(argv) != 1: self.usage() return try: 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.") return gef_print("You must reload GEF for alias removals to apply.") return @register_command class AliasesListCommand(AliasesCommand): """Command to list aliases.""" _cmdline_ = "aliases ls" _syntax_ = _cmdline_ def __init__(self) -> None: super().__init__() return 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}") return class GefTmuxSetup(gdb.Command): """Setup a confortable tmux debugging environment.""" def __init__(self) -> None: super().__init__("tmux-setup", gdb.COMMAND_NONE, gdb.COMPLETE_NONE) GefAlias("screen-setup", "tmux-setup") return def invoke(self, args: Any, from_tty: bool) -> None: self.dont_repeat() tmux = os.getenv("TMUX") if tmux: self.tmux_setup() return screen = os.getenv("TERM") if screen is not None and screen == "screen": self.screen_setup() return warn("Not in a tmux/screen session") return def tmux_setup(self) -> None: """Prepare the tmux environment by vertically splitting the current pane, and forcing the context to be redirected there.""" tmux = which("tmux") ok("tmux session found, splitting window...") old_ptses = set(os.listdir("/dev/pts")) gdb.execute(f"! {tmux} split-window -h 'clear ; cat'") gdb.execute(f"! {tmux} select-pane -L") new_ptses = set(os.listdir("/dev/pts")) pty = list(new_ptses - old_ptses)[0] pty = f"/dev/pts/{pty}" ok(f"Setting `context.redirect` to '{pty}'...") gdb.execute(f"gef config context.redirect {pty}") ok("Done!") return def screen_setup(self) -> None: """Hackish equivalent of the tmux_setup() function for screen.""" screen = which("screen") sty = os.getenv("STY") ok("screen session found, splitting window...") fd_script, script_path = tempfile.mkstemp() fd_tty, tty_path = tempfile.mkstemp() os.close(fd_tty) with os.fdopen(fd_script, "w") as f: f.write("startup_message off\n") f.write("split -v\n") f.write("focus right\n") f.write(f"screen bash -c 'tty > {tty_path}; clear; cat'\n") f.write("focus left\n") gdb.execute(f"! {screen} -r {sty} -m -d -X source {script_path}") # artificial delay to make sure `tty_path` is populated time.sleep(0.25) with open(tty_path, "r") as f: pty = f.read().strip() ok(f"Setting `context.redirect` to '{pty}'...") gdb.execute(f"gef config context.redirect {pty}") ok("Done!") os.unlink(script_path) os.unlink(tty_path) return # # GEF internal classes # def __gef_prompt__(current_prompt: Any) -> 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 is_alive(): return GEF_PROMPT_ON return GEF_PROMPT_OFF class GefManager(metaclass=abc.ABCMeta): def reset_caches(self) -> None: """Reset the LRU-cached attributes""" for attr in dir(self): try: obj = getattr(self, attr) 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 continue return class GefMemoryManager(GefManager): """Class that manages memory access for gef.""" def __init__(self) -> None: self.reset_caches() return def reset_caches(self) -> None: super().reset_caches() self.__maps = None return def write(self, address: int, buffer: ByteString, length: int = 0x10) -> None: """Write `buffer` at address `address`.""" gdb.selected_inferior().write_memory(address, buffer, length) def read(self, addr: int, length: int = 0x10) -> bytes: """Return a `length` long byte array with the copy of the process memory at `addr`.""" return gdb.selected_inferior().read_memory(addr, length).tobytes() def read_integer(self, addr: int) -> int: """Return an integer read from memory.""" sz = gef.arch.ptrsize mem = self.read(addr, sz) unpack = u32 if sz == 4 else u64 return unpack(mem) def read_cstring(self, address: int, max_length: int = GEF_MAX_STRING_LENGTH, encoding: Optional[str] = None) -> str: """Return a C-string read from memory.""" encoding = encoding or "unicode-escape" length = min(address | (DEFAULT_PAGE_SIZE-1), max_length+1) try: res_bytes = self.read(address, length) except gdb.error: err(f"Can't read memory at '{address}'") return "" try: with warnings.catch_warnings(): # ignore DeprecationWarnings (see #735) warnings.simplefilter("ignore") res = res_bytes.decode(encoding, "strict") except UnicodeDecodeError: # latin-1 as fallback due to its single-byte to glyph mapping res = res_bytes.decode("latin-1", "replace") res = res.split("\x00", 1)[0] ustr = res.replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t") if max_length and len(res) > max_length: return f"{ustr[:max_length]}[...]" return ustr def read_ascii_string(self, address: int) -> Optional[str]: """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): return cstr return None @property def maps(self) -> List[Section]: if not self.__maps: self.__maps = self.__parse_maps() return self.__maps def __parse_maps(self) -> List[Section]: """Return the mapped memory sections""" try: return list(self.__parse_procfs_maps()) except FileNotFoundError: return list(self.__parse_gdb_info_sections()) def __parse_procfs_maps(self) -> Generator[Section, None, None]: """Get the memory mapping from procfs.""" def open_file(path: str, use_cache: bool = False) -> IO: """Attempt to open the given file, if remote debugging is active, download it first to the mirror in /tmp/.""" if is_remote_debug() and not gef.session.qemu_mode: lpath = download_file(path, use_cache) if not lpath: raise IOError(f"cannot open remote path {path}") path = lpath return open(path, "r") __process_map_file = f"/proc/{gef.session.pid}/maps" with open_file(__process_map_file, use_cache=False) as fd: for line in fd: line = line.strip() addr, perm, off, _, rest = line.split(" ", 4) rest = rest.split(" ", 1) if len(rest) == 1: inode = rest[0] pathname = "" else: inode = rest[0] pathname = rest[1].lstrip() addr_start, addr_end = [int(x, 16) for x in addr.split("-")] 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) return def __parse_gdb_info_sections(self) -> Generator[Section, None, None]: """Get the memory mapping from GDB's command `maintenance info sections` (limited info).""" stream = StringIO(gdb.execute("maintenance info sections", to_string=True)) for line in stream: if not line: break try: parts = [x for x in line.split()] addr_start, addr_end = [int(x, 16) for x in parts[1].split("->")] off = int(parts[3][:-1], 16) path = parts[4] perm = Permission.from_info_sections(parts[5:]) yield Section( page_start=addr_start, page_end=addr_end, offset=off, permission=perm, inode="", path=path ) except IndexError: continue except ValueError: continue return class GefHeapManager(GefManager): """Class managing session heap.""" def __init__(self) -> None: self.reset_caches() return def reset_caches(self) -> None: self.__libc_main_arena: Optional[GlibcArena] = None self.__libc_selected_arena: Optional[GlibcArena] = None self.__heap_base = None return @property def main_arena(self) -> Optional[GlibcArena]: if not self.__libc_main_arena: try: __main_arena_addr = search_for_main_arena() self.__libc_main_arena = GlibcArena(f"&{__main_arena_addr:#x}") # the initialization of `main_arena` also defined `selected_arena`, so # by default, `main_arena` == `selected_arena` self.selected_arena = self.__libc_main_arena except: # the search for arena can fail when the session is not started pass return self.__libc_main_arena @property def selected_arena(self) -> Optional[GlibcArena]: if not self.__libc_selected_arena: # `selected_arena` must default to `main_arena` self.__libc_selected_arena = self.__libc_main_arena return self.__libc_selected_arena @selected_arena.setter def selected_arena(self, value: GlibcArena) -> None: self.__libc_selected_arena = value return @property def arenas(self) -> Union[List, Iterator[GlibcArena]]: if not self.main_arena: return [] return iter(self.main_arena) @property def base_address(self) -> Optional[int]: if not self.__heap_base: base = 0 try: base = parse_address("mp_->sbrk_base") except gdb.error: # missing symbol, try again base = 0 if not base: base = get_section_base_address("[heap]") self.__heap_base = base return self.__heap_base @property def chunks(self) -> Union[List, Iterator]: if not self.base_address: return [] return iter(GlibcChunk(self.base_address, from_base=True)) class GefSetting: """Basic class for storing gef settings as objects""" def __init__(self, value: Any, cls: Optional[type] = None, description: Optional[str] = None) -> None: self.value = value self.type = cls or type(value) self.description = description or "" return 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: return dict.__getitem__(self, name).value def __setitem__(self, name: str, value: Any) -> None: # check if the key exists if dict.__contains__(self, name): # if so, update its value directly setting = dict.__getitem__(self, name) setting.value = setting.type(value) dict.__setitem__(self, name, setting) else: # if not, `value` must be a GefSetting if not isinstance(value, GefSetting): raise Exception("Invalid argument") if not value.type: raise Exception("Invalid type") if not value.description: raise Exception("Invalid description") dict.__setitem__(self, name, value) return def __delitem__(self, name: str) -> None: dict.__delitem__(self, name) return def raw_entry(self, name: str) -> GefSetting: return dict.__getitem__(self, name) class GefSessionManager(GefManager): """Class managing the runtime properties of GEF. """ def __init__(self) -> None: self.reset_caches() self.remote = None self.qemu_mode = False self.convenience_vars_index = 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.pie_counter = 1 self.aliases: List[GefAlias] = [] self.constants = {} # a dict for runtime constants (like 3rd party file paths) # add a few extra runtime constants to avoid lookups # those must be found, otherwise IOError will be raised for constant in ("python3", "readelf", "file", "ps"): self.constants[constant] = which(constant) return def reset_caches(self) -> None: super().reset_caches() self.__auxiliary_vector = None self.__pagesize = None self.__os = None self.__pid = None self.__file = None self.__canary = None return @property def auxiliary_vector(self) -> Optional[Dict[str, int]]: if not is_alive(): return None if not self.__auxiliary_vector: auxiliary_vector = {} auxv_info = gdb.execute("info auxv", to_string=True) if "failed" in auxv_info: err(auxv_info) # print GDB error return None for line in auxv_info.splitlines(): line = line.split('"')[0].strip() # remove the ending string (if any) line = line.split() # split the string by whitespace(s) if len(line) < 4: continue # a valid entry should have at least 4 columns __av_type = line[1] __av_value = line[-1] auxiliary_vector[__av_type] = int(__av_value, base=0) self.__auxiliary_vector = auxiliary_vector return self.__auxiliary_vector @property def os(self) -> str: """Return the current OS.""" if not self.__os: self.__os = platform.system().lower() return self.__os @property def pid(self) -> int: """Return the PID of the target process.""" if not self.__pid: pid = gdb.selected_inferior().pid if not gef.session.qemu_mode else gdb.selected_thread().ptid[1] if not pid: raise RuntimeError("cannot retrieve PID for target process") self.__pid = pid return self.__pid @property def file(self) -> pathlib.Path: """Return a Path object of the target process.""" if not self.__file: self.__file = pathlib.Path(gdb.current_progspace().filename) return self.__file @property def pagesize(self) -> int: """Get the system page size""" auxval = self.auxiliary_vector if not auxval: return DEFAULT_PAGE_SIZE self.__pagesize = auxval["AT_PAGESZ"] return self.__pagesize @property def canary(self) -> Optional[Tuple[int, int]]: """Returns a tuple of the canary address and value, read from the auxiliary vector.""" auxval = self.auxiliary_vector if not auxval: return None canary_location = auxval["AT_RANDOM"] canary = gef.memory.read_integer(canary_location) canary &= ~0xFF self.__canary = (canary, canary_location) return self.__canary class GefUiManager(GefManager): """Class managing UI settings.""" 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[str] = [] return class Gef: """The GEF root class, which serves as a entrypoint for all the debugging session attributes (architecture, memory, settings, etc.).""" def __init__(self) -> None: self.binary: Optional[Elf] = None self.arch: Architecture = GenericArchitecture() # see PR #516, will be reset by `new_objfile_handler` self.config = GefSettingsManager() self.ui = GefUiManager() return def reinitialize_managers(self) -> None: """Reinitialize the managers. Avoid calling this function directly, using `pi reset()` is preferred""" self.memory = GefMemoryManager() self.heap = GefHeapManager() self.session = GefSessionManager() return def setup(self) -> None: """Setup initialize the runtime setup, which may require for the `gef` to be not None.""" self.reinitialize_managers() self.gdb = GefCommand() self.gdb.setup() tempdir = self.config["gef.tempdir"] gef_makedirs(tempdir) gdb.execute(f"save gdb-index {tempdir}") return def reset_caches(self) -> None: """Recursively clean the cache of all the managers. Avoid calling this function directly, using `reset-cache` is preferred""" for mgr in (self.memory, self.heap, self.session, self.arch): mgr.reset_caches() return 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.") 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. " f"Consider updating to GDB {'.'.join(map(str, GDB_MIN_VERSION))} or higher " f"(with Python {'.'.join(map(str, PYTHON_MIN_VERSION))} or higher).") exit(1) try: pyenv = which("pyenv") PYENV_ROOT = gef_pystring(subprocess.check_output([pyenv, "root"]).strip()) PYENV_VERSION = gef_pystring(subprocess.check_output([pyenv, "version-name"]).strip()) site_packages_dir = os.path.join(PYENV_ROOT, "versions", PYENV_VERSION, "lib", f"python{PYENV_VERSION[:3]}", "site-packages") site.addsitedir(site_packages_dir) except FileNotFoundError: pass # 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 prompt gdb.prompt_hook = __gef_prompt__ # setup config gdb_initial_settings = ( "set confirm off", "set verbose off", "set pagination off", "set print elements 0", "set history save on", "set history filename ~/.gdb_history", "set output-radix 0x10", "set print pretty on", "set disassembly-flavor intel", "handle SIGALRM print nopass", ) for cmd in gdb_initial_settings: try: gdb.execute(cmd) except gdb.error: pass # load GEF reset() # gdb events configuration gef_on_continue_hook(continue_handler) gef_on_stop_hook(hook_stop_handler) gef_on_new_hook(new_objfile_handler) gef_on_exit_hook(exit_handler) gef_on_memchanged_hook(memchanged_handler) gef_on_regchanged_hook(regchanged_handler) if gdb.current_progspace().filename is not None: # if here, we are sourcing gef from a gdb session already attached # we must force a call to the new_objfile handler (see issue #278) new_objfile_handler(None) GefTmuxSetup()