summaryrefslogtreecommitdiffstats
path: root/gdb
diff options
context:
space:
mode:
authorMatt Strapp <matt@mattstrapp.net>2022-05-23 16:48:35 -0500
committerMatt Strapp <matt@mattstrapp.net>2022-05-23 16:48:35 -0500
commitb46d66e03e33842a61260520146a34768056561f (patch)
treecc2a890fe7cc2da0eef89b096d163a41b58f4680 /gdb
downloaddotfiles-b46d66e03e33842a61260520146a34768056561f.tar
dotfiles-b46d66e03e33842a61260520146a34768056561f.tar.gz
dotfiles-b46d66e03e33842a61260520146a34768056561f.tar.bz2
dotfiles-b46d66e03e33842a61260520146a34768056561f.tar.lz
dotfiles-b46d66e03e33842a61260520146a34768056561f.tar.xz
dotfiles-b46d66e03e33842a61260520146a34768056561f.tar.zst
dotfiles-b46d66e03e33842a61260520146a34768056561f.zip
Initial commit
Diffstat (limited to '')
-rw-r--r--gdb/.config/gdb/.gef-283690ae9bfcecbb3deb80cd275d327c46b276b5.py11629
-rw-r--r--gdb/.config/gdb/init1
2 files changed, 11630 insertions, 0 deletions
diff --git a/gdb/.config/gdb/.gef-283690ae9bfcecbb3deb80cd275d327c46b276b5.py b/gdb/.config/gdb/.gef-283690ae9bfcecbb3deb80cd275d327c46b276b5.py
new file mode 100644
index 0000000..8386154
--- /dev/null
+++ b/gdb/.config/gdb/.gef-283690ae9bfcecbb3deb80cd275d327c46b276b5.py
@@ -0,0 +1,11629 @@
+#######################################################################################
+# 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/<pid>). 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<<i) else value_table[i].lower()
+ flags.append(flag_str)
+ return f"[{' '.join(flags)}]"
+
+
+@lru_cache()
+def get_section_base_address(name: str) -> 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<<flags["zero"])), "Z"
+ elif mnemo.endswith("ne"): taken, reason = not val&(1<<flags["zero"]), "!Z"
+ elif mnemo.endswith("lt"):
+ taken, reason = bool(val&(1<<flags["negative"])) != bool(val&(1<<flags["overflow"])), "N!=V"
+ elif mnemo.endswith("le"):
+ taken, reason = val&(1<<flags["zero"]) or \
+ bool(val&(1<<flags["negative"])) != bool(val&(1<<flags["overflow"])), "Z || N!=V"
+ elif mnemo.endswith("gt"):
+ taken, reason = val&(1<<flags["zero"]) == 0 and \
+ bool(val&(1<<flags["negative"])) == bool(val&(1<<flags["overflow"])), "!Z && N==V"
+ elif mnemo.endswith("ge"):
+ taken, reason = bool(val&(1<<flags["negative"])) == bool(val&(1<<flags["overflow"])), "N==V"
+ elif mnemo.endswith("vs"): taken, reason = bool(val&(1<<flags["overflow"])), "V"
+ elif mnemo.endswith("vc"): taken, reason = not val&(1<<flags["overflow"]), "!V"
+ elif mnemo.endswith("mi"):
+ taken, reason = bool(val&(1<<flags["negative"])), "N"
+ elif mnemo.endswith("pl"):
+ taken, reason = not val&(1<<flags["negative"]), "N==0"
+ elif mnemo.endswith("hi"):
+ taken, reason = val&(1<<flags["carry"]) and not val&(1<<flags["zero"]), "C && !Z"
+ elif mnemo.endswith("ls"):
+ taken, reason = not val&(1<<flags["carry"]) or val&(1<<flags["zero"]), "!C || Z"
+ elif mnemo.endswith("cs"): taken, reason = bool(val&(1<<flags["carry"])), "C"
+ elif mnemo.endswith("cc"): taken, reason = not val&(1<<flags["carry"]), "!C"
+ return taken, reason
+
+ def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> 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<<i) != 0: taken, reason = True, f"{reg}&1<<{i}!=0"
+ else: taken, reason = False, f"{reg}&1<<{i}==0"
+ elif mnemo == "tbz":
+ # 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<<i) == 0: taken, reason = True, f"{reg}&1<<{i}==0"
+ else: taken, reason = False, f"{reg}&1<<{i}!=0"
+
+ if not reason:
+ taken, reason = super().is_branch_taken(insn)
+ return taken, reason
+
+
+@register_architecture
+class X86(Architecture):
+ aliases: Tuple[Union[str, Elf.Abi], ...] = ("X86", Elf.Abi.X86_32)
+ arch = "X86"
+ mode = "32"
+
+ nop_insn = b"\x90"
+ flag_register = "$eflags"
+ special_registers = ["$cs", "$ss", "$ds", "$es", "$fs", "$gs", ]
+ gpr_registers = ["$eax", "$ebx", "$ecx", "$edx", "$esp", "$ebp", "$esi", "$edi", "$eip", ]
+ all_registers = gpr_registers + [ flag_register, ] + special_registers
+ instruction_length = None
+ return_register = "$eax"
+ function_parameters = ["$esp", ]
+ flags_table = {
+ 6: "zero",
+ 0: "carry",
+ 2: "parity",
+ 4: "adjust",
+ 7: "sign",
+ 8: "trap",
+ 9: "interrupt",
+ 10: "direction",
+ 11: "overflow",
+ 16: "resume",
+ 17: "virtualx86",
+ 21: "identification",
+ }
+ syscall_register = "$eax"
+ syscall_instructions = ["sysenter", "int 0x80"]
+ ptrsize = 4
+ endianness = Endianness.LITTLE_ENDIAN
+
+ def flag_register_to_human(self, val: Optional[int] = None) -> 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<<flags["carry"]) and not val&(1<<flags["zero"]), "!C && !Z"
+ elif mnemo in ("jae", "jnb", "jnc"):
+ taken, reason = not val&(1<<flags["carry"]), "!C"
+ elif mnemo in ("jb", "jc", "jnae"):
+ taken, reason = val&(1<<flags["carry"]), "C"
+ elif mnemo in ("jbe", "jna"):
+ taken, reason = val&(1<<flags["carry"]) or val&(1<<flags["zero"]), "C || Z"
+ elif mnemo in ("jcxz", "jecxz", "jrcxz"):
+ cx = gef.arch.register("$rcx") if self.mode == 64 else gef.arch.register("$ecx")
+ taken, reason = cx == 0, "!$CX"
+ elif mnemo in ("je", "jz"):
+ taken, reason = val&(1<<flags["zero"]), "Z"
+ elif mnemo in ("jne", "jnz"):
+ taken, reason = not val&(1<<flags["zero"]), "!Z"
+ elif mnemo in ("jg", "jnle"):
+ taken, reason = not val&(1<<flags["zero"]) and bool(val&(1<<flags["overflow"])) == bool(val&(1<<flags["sign"])), "!Z && S==O"
+ elif mnemo in ("jge", "jnl"):
+ taken, reason = bool(val&(1<<flags["sign"])) == bool(val&(1<<flags["overflow"])), "S==O"
+ elif mnemo in ("jl", "jnge"):
+ taken, reason = val&(1<<flags["overflow"]) != val&(1<<flags["sign"]), "S!=O"
+ elif mnemo in ("jle", "jng"):
+ taken, reason = val&(1<<flags["zero"]) or bool(val&(1<<flags["overflow"])) != bool(val&(1<<flags["sign"])), "Z || S!=O"
+ elif mnemo in ("jo",):
+ taken, reason = val&(1<<flags["overflow"]), "O"
+ elif mnemo in ("jno",):
+ taken, reason = not val&(1<<flags["overflow"]), "!O"
+ elif mnemo in ("jpe", "jp"):
+ taken, reason = val&(1<<flags["parity"]), "P"
+ elif mnemo in ("jnp", "jpo"):
+ taken, reason = not val&(1<<flags["parity"]), "!P"
+ elif mnemo in ("js",):
+ taken, reason = val&(1<<flags["sign"]), "S"
+ elif mnemo in ("jns",):
+ taken, reason = not val&(1<<flags["sign"]), "!S"
+ return taken, reason
+
+ def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> 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<<flags["equal[7]"]), "E"
+ elif mnemo == "bne": taken, reason = val&(1<<flags["equal[7]"]) == 0, "!E"
+ elif mnemo == "ble": taken, reason = val&(1<<flags["equal[7]"]) or val&(1<<flags["less[7]"]), "E || L"
+ elif mnemo == "blt": taken, reason = val&(1<<flags["less[7]"]), "L"
+ elif mnemo == "bge": taken, reason = val&(1<<flags["equal[7]"]) or val&(1<<flags["greater[7]"]), "E || G"
+ elif mnemo == "bgt": taken, reason = val&(1<<flags["greater[7]"]), "G"
+ 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("$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<<flags["zero"]), "Z"
+ elif mnemo == "bne": taken, reason = val&(1<<flags["zero"]) == 0, "!Z"
+ elif mnemo == "bg": taken, reason = val&(1<<flags["zero"]) == 0 and (val&(1<<flags["negative"]) == 0 or val&(1<<flags["overflow"]) == 0), "!Z && (!N || !O)"
+ elif mnemo == "bge": taken, reason = val&(1<<flags["negative"]) == 0 or val&(1<<flags["overflow"]) == 0, "!N || !O"
+ elif mnemo == "bgu": taken, reason = val&(1<<flags["carry"]) == 0 and val&(1<<flags["zero"]) == 0, "!C && !Z"
+ elif mnemo == "bgeu": taken, reason = val&(1<<flags["carry"]) == 0, "!C"
+ elif mnemo == "bl": taken, reason = val&(1<<flags["negative"]) and val&(1<<flags["overflow"]), "N && O"
+ elif mnemo == "blu": taken, reason = val&(1<<flags["carry"]), "C"
+ elif mnemo == "ble": taken, reason = val&(1<<flags["zero"]) or (val&(1<<flags["negative"]) or val&(1<<flags["overflow"])), "Z || (N || O)"
+ elif mnemo == "bleu": taken, reason = val&(1<<flags["carry"]) or val&(1<<flags["zero"]), "C || Z"
+ elif mnemo == "bneg": taken, reason = val&(1<<flags["negative"]), "N"
+ elif mnemo == "bpos": taken, reason = val&(1<<flags["negative"]) == 0, "!N"
+ elif mnemo == "bvs": taken, reason = val&(1<<flags["overflow"]), "O"
+ elif mnemo == "bvc": taken, reason = val&(1<<flags["overflow"]) == 0, "!O"
+ elif mnemo == "bcs": taken, reason = val&(1<<flags["carry"]), "C"
+ elif mnemo == "bcc": taken, reason = val&(1<<flags["carry"]) == 0, "!C"
+ 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("$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/<local_pid>/<remote_filepath>`.
+ 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("<I", int(ip, 16))), int(port, 16)
+
+ def show_connections(self) -> 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 <StructureName>|show <StructureName>]|<StructureName> 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("<H", socket.htons(port)), 2)
+ gef.memory.write(stack_addr + 0x4, socket.inet_aton(address), 4)
+
+ info(f"Trying to connect to {new_output}")
+ res = gdb.execute(f"""call (int)connect({new_fd}, {stack_addr}, {16})""", to_string=True)
+
+ # recover stack state
+ gef.memory.write(stack_addr, original_contents, 8)
+
+ res = self.get_fd_from_result(res)
+ if res == -1:
+ err(f"Failed to connect to {address}:{port}")
+ return
+
+ info(f"Connected to {new_output}")
+ else:
+ res = gdb.execute(f"""call (int)open("{new_output}", 66, 0666)""", to_string=True)
+ new_fd = self.get_fd_from_result(res)
+
+ info(f"Opened '{new_output}' as fd #{new_fd:d}")
+ gdb.execute(f"""call (int)dup2({new_fd:d}, {old_fd:d})""", to_string=True)
+ info(f"Duplicated fd #{new_fd:d}{RIGHT_ARROW}#{old_fd:d}")
+ gdb.execute(f"""call (int)close({new_fd:d})""", to_string=True)
+ info(f"Closed extra fd #{new_fd:d}")
+ ok("Success")
+ return
+
+ def get_fd_from_result(self, res: str) -> 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/<pid> 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) == "<unavailable>":
+ 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 <id>` 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"&quot;", 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} (<void>)")
+ 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()
diff --git a/gdb/.config/gdb/init b/gdb/.config/gdb/init
new file mode 100644
index 0000000..dc69f24
--- /dev/null
+++ b/gdb/.config/gdb/init
@@ -0,0 +1 @@
+source ~/.config/gdb/.gef-283690ae9bfcecbb3deb80cd275d327c46b276b5.py