Files
gef/gef.py
T

11387 lines
395 KiB
Python

# -*- coding: utf-8 -*-
#
#
#######################################################################################
# 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-2021 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 ctypes
import functools
import hashlib
import importlib
import inspect
import itertools
import json
import os
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 configparser
import xmlrpc.client as xmlrpclib
import warnings
from functools import lru_cache
from io import StringIO
from urllib.request import urlopen
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 = "\001\033[1;32m\002{0:s}\001\033[0m\002".format(GEF_PROMPT)
GEF_PROMPT_OFF = "\001\033[1;31m\002{0:s}\001\033[0m\002".format(GEF_PROMPT)
def http_get(url):
"""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):
"""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("https://raw.githubusercontent.com/hugsy/gef/{}/scripts/gef.sh".format(ver,))
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)
__gef__ = None
__commands__ = []
__functions__ = []
__aliases__ = []
__config__ = {}
__watches__ = {}
__infos_files__ = []
__gef_convenience_vars_index__ = 0
__context_messages__ = []
__heap_allocated_list__ = []
__heap_freed_list__ = []
__heap_uaf_watchpoints__ = []
__pie_breakpoints__ = {}
__pie_counter__ = 1
__gef_remote__ = None
__gef_qemu_mode__ = False
__gef_current_arena__ = "main_arena"
__gef_int_stream_buffer__ = None
__gef_redirect_output_fd__ = None
DEFAULT_PAGE_ALIGN_SHIFT = 12
DEFAULT_PAGE_SIZE = 1 << DEFAULT_PAGE_ALIGN_SHIFT
GEF_RC = os.getenv("GEF_RC") or os.path.join(os.getenv("HOME"), ".gef.rc")
GEF_TEMP_DIR = os.path.join(tempfile.gettempdir(), "gef")
GEF_MAX_STRING_LENGTH = 50
GDB_MIN_VERSION = (7, 7)
GDB_VERSION = tuple(map(int, re.search(r"(\d+)[^\d]+(\d+)", gdb.VERSION).groups()))
current_elf = None
current_arch = None
libc_args_definitions = {}
highlight_table = {}
ANSI_SPLIT_RE = r"(\033\[[\d;]*m)"
def reset_all_caches():
"""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."""
global __gef_current_arena__
for mod in dir(sys.modules["__main__"]):
obj = getattr(sys.modules["__main__"], mod)
if hasattr(obj, "cache_clear"):
obj.cache_clear()
__gef_current_arena__ = "main_arena"
return
def highlight_text(text):
"""
Highlight text using highlight_table { match -> color } settings.
If RegEx is enabled it will create a match group around all items in the
highlight_table and wrap the specified color in the highlight_table
around those matches.
If RegEx is disabled, split by ANSI codes and 'colorify' each match found
within the specified string.
"""
if not highlight_table:
return text
if get_gef_setting("highlight.regex"):
for match, color in 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 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(x="", *args, **kwargs):
"""Wrapper around print(), using string buffering feature."""
x = highlight_text(x)
if __gef_int_stream_buffer__ and not is_debug():
return __gef_int_stream_buffer__.write(x + kwargs.get("end", "\n"))
return print(x, *args, **kwargs)
def bufferize(f):
"""Store the content to be printed for a function in memory, and flush it on function exit."""
@functools.wraps(f)
def wrapper(*args, **kwargs):
global __gef_int_stream_buffer__, __gef_redirect_output_fd__
if __gef_int_stream_buffer__:
return f(*args, **kwargs)
__gef_int_stream_buffer__ = StringIO()
try:
rv = f(*args, **kwargs)
finally:
redirect = get_gef_setting("context.redirect")
if redirect.startswith("/dev/pts/"):
if not __gef_redirect_output_fd__:
# if the FD has never been open, open it
fd = open(redirect, "wt")
__gef_redirect_output_fd__ = fd
elif redirect != __gef_redirect_output_fd__.name:
# if the user has changed the redirect setting during runtime, update the state
__gef_redirect_output_fd__.close()
fd = open(redirect, "wt")
__gef_redirect_output_fd__ = fd
else:
# otherwise, keep using it
fd = __gef_redirect_output_fd__
else:
fd = sys.stdout
__gef_redirect_output_fd__ = None
if __gef_redirect_output_fd__ and fd.closed:
# if the tty was closed, revert back to stdout
fd = sys.stdout
__gef_redirect_output_fd__ = None
set_gef_setting("context.redirect", "")
fd.write(__gef_int_stream_buffer__.getvalue())
fd.flush()
__gef_int_stream_buffer__ = None
return rv
return wrapper
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): return Color.colorify(msg, "red")
@staticmethod
def greenify(msg): return Color.colorify(msg, "green")
@staticmethod
def blueify(msg): return Color.colorify(msg, "blue")
@staticmethod
def yellowify(msg): return Color.colorify(msg, "yellow")
@staticmethod
def grayify(msg): return Color.colorify(msg, "gray")
@staticmethod
def light_grayify(msg):return Color.colorify(msg, "light_gray")
@staticmethod
def pinkify(msg): return Color.colorify(msg, "pink")
@staticmethod
def cyanify(msg): return Color.colorify(msg, "cyan")
@staticmethod
def boldify(msg): return Color.colorify(msg, "bold")
@staticmethod
def underlinify(msg): return Color.colorify(msg, "underline")
@staticmethod
def highlightify(msg): return Color.colorify(msg, "highlight")
@staticmethod
def blinkify(msg): return Color.colorify(msg, "blink")
@staticmethod
def colorify(text, attrs):
"""Color text according to the given attributes."""
if get_gef_setting("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, *args, **kwargs):
self.value = kwargs.get("value", 0)
self.section = kwargs.get("section", None)
self.info = kwargs.get("info", None)
self.valid = kwargs.get("valid", True)
return
def __str__(self):
value = format_address(self.value)
code_color = get_gef_setting("theme.address_code")
stack_color = get_gef_setting("theme.address_stack")
heap_color = get_gef_setting("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 is_in_text_segment(self):
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):
return hasattr(self.section, "path") and "[stack]" == self.section.path
def is_in_heap_segment(self):
return hasattr(self.section, "path") and "[heap]" == self.section.path
def dereference(self):
addr = align_address(int(self.value))
derefed = dereference(addr)
return None if derefed is None else int(derefed)
class Permission:
"""GEF representation of Linux permission."""
NONE = 0
READ = 1
WRITE = 2
EXECUTE = 4
ALL = READ | WRITE | EXECUTE
def __init__(self, **kwargs):
self.value = kwargs.get("value", 0)
return
def __or__(self, value):
return self.value | value
def __and__(self, value):
return self.value & value
def __xor__(self, value):
return self.value ^ value
def __eq__(self, value):
return self.value == value
def __ne__(self, value):
return self.value != value
def __str__(self):
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):
perm = Permission()
for arg in args:
if "READONLY" in arg:
perm.value += Permission.READ
if "DATA" in arg:
perm.value += Permission.WRITE
if "CODE" in arg:
perm.value += Permission.EXECUTE
return perm
@staticmethod
def from_process_maps(perm_str):
perm = Permission()
if perm_str[0] == "r":
perm.value += Permission.READ
if perm_str[1] == "w":
perm.value += Permission.WRITE
if perm_str[2] == "x":
perm.value += Permission.EXECUTE
return perm
class Section:
"""GEF representation of process memory sections."""
def __init__(self, *args, **kwargs):
self.page_start = kwargs.get("page_start")
self.page_end = kwargs.get("page_end")
self.offset = kwargs.get("offset")
self.permission = kwargs.get("permission")
self.inode = kwargs.get("inode")
self.path = kwargs.get("path")
return
def is_readable(self):
return self.permission.value and self.permission.value & Permission.READ
def is_writable(self):
return self.permission.value and self.permission.value & Permission.WRITE
def is_executable(self):
return self.permission.value and self.permission.value & Permission.EXECUTE
@property
def size(self):
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):
# when in a `gef-remote` session, realpath returns the path to the binary on the local disk, not remote
return self.path if __gef_remote__ is None else "/tmp/gef/{:d}/{:s}".format(__gef_remote__, self.path)
Zone = collections.namedtuple("Zone", ["name", "zone_start", "zone_end", "filename"])
class Elf:
"""Basic ELF parsing.
Ref:
- http://www.skyfree.org/linux/references/ELF_Format.pdf
- http://refspecs.freestandards.org/elf/elfspec_ppc.pdf
- http://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi.html
"""
LITTLE_ENDIAN = 1
BIG_ENDIAN = 2
ELF_32_BITS = 0x01
ELF_64_BITS = 0x02
ELF_MAGIC = 0x7f454c46
X86_64 = 0x3e
X86_32 = 0x03
ARM = 0x28
MIPS = 0x08
POWERPC = 0x14
POWERPC64 = 0x15
SPARC = 0x02
SPARC64 = 0x2b
AARCH64 = 0xb7
RISCV = 0xf3
IA64 = 0x32
ET_RELOC = 1
ET_EXEC = 2
ET_DYN = 3
ET_CORE = 4
OSABI_SYSTEMV = 0x00
OSABI_HPUX = 0x01
OSABI_NETBSD = 0x02
OSABI_LINUX = 0x03
OSABI_SOLARIS = 0x06
OSABI_AIX = 0x07
OSABI_IRIX = 0x08
OSABI_FREEBSD = 0x09
OSABI_OPENBSD = 0x0C
e_magic = ELF_MAGIC
e_class = ELF_32_BITS
e_endianness = LITTLE_ENDIAN
e_eiversion = None
e_osabi = None
e_abiversion = None
e_pad = None
e_type = ET_EXEC
e_machine = X86_32
e_version = None
e_entry = 0x00
e_phoff = None
e_shoff = None
e_flags = None
e_ehsize = None
e_phentsize = None
e_phnum = None
e_shentsize = None
e_shnum = None
e_shstrndx = None
def __init__(self, elf="", minimalist=False):
"""
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
if not os.access(elf, os.R_OK):
err("'{0}' not found/readable".format(elf))
err("Failed to get file debug information, most of gef features will not work")
return
self.fd = open(elf, "rb")
# off 0x0
self.e_magic, self.e_class, self.e_endianness, self.e_eiversion = struct.unpack(">IBBB", self.read(7))
# adjust endianness in bin reading
endian = endian_str()
# off 0x7
self.e_osabi, self.e_abiversion = struct.unpack("{}BB".format(endian), self.read(2))
# off 0x9
self.e_pad = self.read(7)
# off 0x10
self.e_type, self.e_machine, self.e_version = struct.unpack("{}HHI".format(endian), self.read(8))
# off 0x18
if self.e_class == Elf.ELF_64_BITS:
# if arch 64bits
self.e_entry, self.e_phoff, self.e_shoff = struct.unpack("{}QQQ".format(endian), self.read(24))
else:
# else arch 32bits
self.e_entry, self.e_phoff, self.e_shoff = struct.unpack("{}III".format(endian), self.read(12))
self.e_flags, self.e_ehsize, self.e_phentsize, self.e_phnum = struct.unpack("{}IHHH".format(endian), self.read(10))
self.e_shentsize, self.e_shnum, self.e_shstrndx = struct.unpack("{}HHH".format(endian), self.read(6))
self.phdrs = []
for i in range(self.e_phnum):
self.phdrs.append(Phdr(self, self.e_phoff + self.e_phentsize * i))
self.shdrs = []
for i in range(self.e_shnum):
self.shdrs.append(Shdr(self, self.e_shoff + self.e_shentsize * i))
if self.fd:
self.fd.close()
self.fd = None
return
def read(self, size):
return self.fd.read(size)
def seek(self, off):
self.fd.seek(off, 0)
def is_valid(self):
return self.e_magic == Elf.ELF_MAGIC
class Phdr:
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_LOSUNW = 0x6ffffffa
PT_SUNWBSS = 0x6ffffffa
PT_SUNWSTACK = 0x6ffffffb
PT_HISUNW = 0x6fffffff
PT_HIOS = 0x6fffffff
PT_LOPROC = 0x70000000
PT_HIPROC = 0x7fffffff
PF_X = 1
PF_W = 2
PF_R = 4
p_type = None
p_flags = None
p_offset = None
p_vaddr = None
p_paddr = None
p_filesz = None
p_memsz = None
p_align = None
def __init__(self, elf, off):
if not elf:
return None
elf.seek(off)
endian = endian_str()
if elf.e_class == Elf.ELF_64_BITS:
self.p_type, self.p_flags, self.p_offset = struct.unpack("{}IIQ".format(endian), elf.read(16))
self.p_vaddr, self.p_paddr = struct.unpack("{}QQ".format(endian), elf.read(16))
self.p_filesz, self.p_memsz, self.p_align = struct.unpack("{}QQQ".format(endian), elf.read(24))
else:
self.p_type, self.p_offset = struct.unpack("{}II".format(endian), elf.read(8))
self.p_vaddr, self.p_paddr = struct.unpack("{}II".format(endian), elf.read(8))
self.p_filesz, self.p_memsz, self.p_flags, self.p_align = struct.unpack("{}IIII".format(endian), elf.read(16))
class Shdr:
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_NUM = 19
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_HISUNW = 0x6fffffff
SHT_HIOS = 0x6fffffff
SHT_LOPROC = 0x70000000
SHT_HIPROC = 0x7fffffff
SHT_LOUSER = 0x80000000
SHT_HIUSER = 0x8fffffff
SHF_WRITE = 1
SHF_ALLOC = 2
SHF_EXECINSTR = 4
SHF_MERGE = 0x10
SHF_STRINGS = 0x20
SHF_INFO_LINK = 0x40
SHF_LINK_ORDER = 0x80
SHF_OS_NONCONFORMING = 0x100
SHF_GROUP = 0x200
SHF_TLS = 0x400
SHF_COMPRESSED = 0x800
SHF_RELA_LIVEPATCH = 0x00100000
SHF_RO_AFTER_INIT = 0x00200000
SHF_ORDERED = 0x40000000
SHF_EXCLUDE = 0x80000000
sh_name = None
sh_type = None
sh_flags = None
sh_addr = None
sh_offset = None
sh_size = None
sh_link = None
sh_info = None
sh_addralign = None
sh_entsize = None
def __init__(self, elf, off):
if elf is None:
return None
elf.seek(off)
endian = endian_str()
if elf.e_class == Elf.ELF_64_BITS:
self.sh_name, self.sh_type, self.sh_flags = struct.unpack("{}IIQ".format(endian), elf.read(16))
self.sh_addr, self.sh_offset = struct.unpack("{}QQ".format(endian), elf.read(16))
self.sh_size, self.sh_link, self.sh_info = struct.unpack("{}QII".format(endian), elf.read(16))
self.sh_addralign, self.sh_entsize = struct.unpack("{}QQ".format(endian), elf.read(16))
else:
self.sh_name, self.sh_type, self.sh_flags = struct.unpack("{}III".format(endian), elf.read(12))
self.sh_addr, self.sh_offset = struct.unpack("{}II".format(endian), elf.read(8))
self.sh_size, self.sh_link, self.sh_info = struct.unpack("{}III".format(endian), elf.read(12))
self.sh_addralign, self.sh_entsize = struct.unpack("{}II".format(endian), elf.read(8))
stroff = elf.e_shoff + elf.e_shentsize * elf.e_shstrndx
if elf.e_class == Elf.ELF_64_BITS:
elf.seek(stroff + 16 + 8)
offset = struct.unpack("{}Q".format(endian), elf.read(8))[0]
else:
elf.seek(stroff + 12 + 4)
offset = struct.unpack("{}I".format(endian), elf.read(4))[0]
elf.seek(offset + self.sh_name)
self.sh_name = ""
while True:
c = ord(elf.read(1))
if c == 0:
break
self.sh_name += chr(c)
return
class Instruction:
"""GEF representation of a CPU instruction."""
def __init__(self, address, location, mnemo, operands, opcodes):
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):
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("{:02x}".format(b) for b in self.opcodes[:opcodes_len])
if opcodes_len < len(self.opcodes):
opcodes_text += "..."
return "{:#10x} {:{:d}} {:16} {:6} {:s}".format(self.address,
opcodes_text,
opcodes_len * 2 + 3,
self.location,
self.mnemonic,
", ".join(self.operands))
def __str__(self):
return "{:#10x} {:16} {:6} {:s}".format(
self.address, self.location, self.mnemonic, ", ".join(self.operands)
)
def is_valid(self):
return "(bad)" not in self.mnemonic
@lru_cache()
def search_for_main_arena():
global __gef_current_arena__
malloc_hook_addr = parse_address("(void *)&__malloc_hook")
if is_x86():
addr = align_address_to_size(malloc_hook_addr + current_arch.ptrsize, 0x20)
elif is_arch(Elf.AARCH64) or is_arch(Elf.ARM):
addr = malloc_hook_addr - current_arch.ptrsize*2 - MallocStateStruct("*0").struct_size
else:
raise OSError("Cannot find main_arena for {}".format(current_arch.arch))
__gef_current_arena__ = "*0x{:x}".format(addr)
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):
try:
self.__addr = parse_address("&{}".format(addr))
except gdb.error:
warn("Could not parse address '&{}' when searching malloc_state struct, "
"using '&main_arena' instead".format(addr))
self.__addr = search_for_main_arena()
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 current_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):
return self.__addr
@property
def fastbins_addr(self):
return self.__addr + self.fastbin_offset
@property
def top_addr(self):
return self.fastbins_addr + self.num_fastbins * current_arch.ptrsize
@property
def last_remainder_addr(self):
return self.top_addr + current_arch.ptrsize
@property
def bins_addr(self):
return self.last_remainder_addr + current_arch.ptrsize
@property
def next_addr(self):
return self.bins_addr + self.num_bins * current_arch.ptrsize + self.int_size * 4
@property
def next_free_addr(self):
return self.next_addr + current_arch.ptrsize
@property
def system_mem_addr(self):
return self.next_free_addr + current_arch.ptrsize * 2
@property
def struct_size(self):
return self.system_mem_addr + current_arch.ptrsize * 2 - self.__addr
# struct members
@property
def fastbinsY(self):
return self.get_size_t_array(self.fastbins_addr, self.num_fastbins)
@property
def top(self):
return self.get_size_t_pointer(self.top_addr)
@property
def last_remainder(self):
return self.get_size_t_pointer(self.last_remainder_addr)
@property
def bins(self):
return self.get_size_t_array(self.bins_addr, self.num_bins)
@property
def next(self):
return self.get_size_t_pointer(self.next_addr)
@property
def next_free(self):
return self.get_size_t_pointer(self.next_free_addr)
@property
def system_mem(self):
return self.get_size_t(self.system_mem_addr)
# helper methods
def get_size_t(self, addr):
return dereference(addr).cast(self.size_t)
def get_size_t_pointer(self, addr):
size_t_pointer = self.size_t.pointer()
return dereference(addr).cast(size_t_pointer)
def get_size_t_array(self, addr, length):
size_t_array = self.size_t.array(length)
return dereference(addr).cast(size_t_array)
def __getitem__(self, item):
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):
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 current_arch.ptrsize == 8 else "unsigned int"
self.size_t = cached_lookup_type(ptr_type)
@property
def addr(self):
return self.__addr
@property
def ar_ptr_addr(self):
return self.addr
@property
def prev_addr(self):
return self.ar_ptr_addr + current_arch.ptrsize
@property
def size_addr(self):
return self.prev_addr + current_arch.ptrsize
@property
def mprotect_size_addr(self):
return self.size_addr + self.size_t.sizeof
@property
def ar_ptr(self):
return self._get_size_t_pointer(self.ar_ptr_addr)
@property
def prev(self):
return self._get_size_t_pointer(self.prev_addr)
@property
def size(self):
return self._get_size_t(self.size_addr)
@property
def mprotect_size(self):
return self._get_size_t(self.mprotect_size_addr)
# helper methods
def _get_size_t_pointer(self, addr):
size_t_pointer = self.size_t.pointer()
return dereference(addr).cast(size_t_pointer)
def _get_size_t(self, addr):
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, name=None):
self.__name = name or __gef_current_arena__
try:
arena = gdb.parse_and_eval(addr)
malloc_state_t = cached_lookup_type("struct malloc_state")
self.__arena = arena.cast(malloc_state_t)
self.__addr = int(arena.address)
self.struct_size = malloc_state_t.sizeof
except:
self.__arena = MallocStateStruct(addr)
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):
return self.__arena[item]
def __getattr__(self, item):
return self.__arena[item]
def __int__(self):
return self.__addr
def __iter__(self):
arena = self
while arena is not None:
yield arena
arena = arena.get_next()
def fastbin(self, i):
"""Return head chunk in fastbinsY[i]."""
addr = int(self.fastbinsY[i])
if addr == 0:
return None
return GlibcChunk(addr + 2 * current_arch.ptrsize)
def bin(self, i):
idx = i * 2
fd = int(self.bins[idx])
bw = int(self.bins[idx + 1])
return fd, bw
def get_next(self):
addr_next = int(self.next)
arena_main = GlibcArena(self.__name)
if addr_next == arena_main.__addr:
return None
return GlibcArena("*{:#x} ".format(addr_next))
def is_main_arena(self):
return int(self) == parse_address("&main_arena")
def heap_addr(self, allow_unaligned=False):
if self.is_main_arena():
heap_section = HeapBaseFunction.heap_base()
if not heap_section:
err("Heap not initialized")
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):
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):
"""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):
fmt = "{:s}(base={:#x}, top={:#x}, last_remainder={:#x}, next={:#x}, next_free={:#x}, system_mem={:#x})"
return fmt.format(
Color.colorify("Arena", "blue bold underline"),
self.__addr, self.top, self.last_remainder, self.n, self.nfree, self.sysmem
)
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, from_base=False, allow_unaligned=True):
self.ptrsize = current_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):
return read_int_from_memory(self.size_addr) & (~0x07)
@property
def size(self):
return self.get_chunk_size()
def get_usable_size(self):
# 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):
return self.get_usable_size()
def get_prev_chunk_size(self):
return read_int_from_memory(self.prev_size_addr)
def get_next_chunk(self, allow_unaligned=False):
addr = self.get_next_chunk_addr()
return GlibcChunk(addr, allow_unaligned=allow_unaligned)
def get_next_chunk_addr(self):
return self.data_address + self.get_chunk_size()
# if free-ed functions
def get_fwd_ptr(self, sll):
# Not a single-linked-list (sll) or no Safe-Linking support yet
if not sll or get_libc_version() < (2, 32):
return read_int_from_memory(self.data_address)
# Unmask ("reveal") the Safe-Linking pointer
else:
return read_int_from_memory(self.data_address) ^ (self.data_address >> 12)
@property
def fwd(self):
return self.get_fwd_ptr(False)
fd = fwd # for compat
def get_bkw_ptr(self):
return read_int_from_memory(self.data_address + self.ptrsize)
@property
def bck(self):
return self.get_bkw_ptr()
bk = bck # for compat
# endif free-ed functions
def has_p_bit(self):
return read_int_from_memory(self.size_addr) & 0x01
def has_m_bit(self):
return read_int_from_memory(self.size_addr) & 0x02
def has_n_bit(self):
return read_int_from_memory(self.size_addr) & 0x04
def is_used(self):
"""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):
msg = []
msg.append("PREV_INUSE flag: {}".format(Color.greenify("On") if self.has_p_bit() else Color.redify("Off")))
msg.append("IS_MMAPPED flag: {}".format(Color.greenify("On") if self.has_m_bit() else Color.redify("Off")))
msg.append("NON_MAIN_ARENA flag: {}".format(Color.greenify("On") if self.has_n_bit() else Color.redify("Off")))
return "\n".join(msg)
def _str_sizes(self):
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("Chunk size: Cannot read at {:#x} (corrupted?)".format(self.size_addr))
try:
msg.append("Previous chunk size: {0:d} ({0:#x})".format(self.get_prev_chunk_size()))
failed = True
except gdb.MemoryError:
msg.append("Previous chunk size: Cannot read at {:#x} (corrupted?)".format(self.base_address))
if failed:
msg.append(self.str_chunk_size_flag())
return "\n".join(msg)
def _str_pointers(self):
fwd = self.data_address
bkw = self.data_address + self.ptrsize
msg = []
try:
msg.append("Forward pointer: {0:#x}".format(self.get_fwd_ptr(False)))
except gdb.MemoryError:
msg.append("Forward pointer: {0:#x} (corrupted?)".format(fwd))
try:
msg.append("Backward pointer: {0:#x}".format(self.get_bkw_ptr()))
except gdb.MemoryError:
msg.append("Backward pointer: {0:#x} (corrupted?)".format(bkw))
return "\n".join(msg)
def str_as_alloced(self):
return self._str_sizes()
def str_as_freed(self):
return "{}\n\n{}".format(self._str_sizes(), self._str_pointers())
def flags_as_string(self):
flags = []
if self.has_p_bit():
flags.append(Color.colorify("PREV_INUSE", "red 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):
msg = "{:s}(addr={:#x}, size={:#x}, flags={:s})".format(Color.colorify("Chunk", "yellow bold underline"),
int(self.data_address),
self.get_chunk_size(),
self.flags_as_string())
return msg
def psprint(self):
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"
pattern_libc_ver = re.compile(rb"glibc (\d+)\.(\d+)")
@lru_cache()
def get_libc_version():
sections = get_process_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_ver, data)
if match:
return tuple(int(_) for _ in match.groups())
return 0, 0
@lru_cache()
def get_glibc_arena(addr=None):
try:
addr = "*{}".format(addr) if addr else __gef_current_arena__
return GlibcArena(addr)
except Exception as e:
err("Failed to get the glibc arena, heap commands may not work properly: {}".format(e))
return None
def get_glibc_arenas(addr=None, get_all=True):
arena = get_glibc_arena(addr)
return [arena for arena in iter(arena)] if get_all else [arena]
def titlify(text, color=None, msg_color=None):
"""Print a centered title."""
cols = get_terminal_size()[1]
nb = (cols - len(text) - 2) // 2
if color is None:
color = __config__.get("theme.default_title_line")[0]
if msg_color is None:
msg_color = __config__.get("theme.default_title_message")[0]
msg = []
msg.append(Color.colorify("{} ".format(HORIZONTAL_LINE * nb), color))
msg.append(Color.colorify(text, msg_color))
msg.append(Color.colorify(" {}".format(HORIZONTAL_LINE * nb), color))
return "".join(msg)
def err(msg): return gef_print("{} {}".format(Color.colorify("[!]", "bold red"), msg))
def warn(msg): return gef_print("{} {}".format(Color.colorify("[*]", "bold yellow"), msg))
def ok(msg): return gef_print("{} {}".format(Color.colorify("[+]", "bold green"), msg))
def info(msg): return gef_print("{} {}".format(Color.colorify("[+]", "bold blue"), msg))
def push_context_message(level, message):
"""Push the message to be displayed the next time the context is invoked."""
global __context_messages__
if level not in ("error", "warn", "ok", "info"):
err("Invalid level '{}', discarding message".format(level))
return
__context_messages__.append((level, message))
return
def show_last_exception():
"""Display the last Python exception."""
def _show_code_line(fname, idx):
fname = os.path.expanduser(os.path.expandvars(fname))
with open(fname, "r") as f:
__data = f.readlines()
return __data[idx - 1] if 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("{}: {}".format(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("""{} File "{}", line {:d}, in {}()""".format(DOWN_ARROW, Color.yellowify(filename),
lineno, Color.greenify(method)))
gef_print(" {} {}".format(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("* GDB: {}".format(gdb.VERSION))
gef_print("* Python: {:d}.{:d}.{:d} - {:s}".format(sys.version_info.major, sys.version_info.minor,
sys.version_info.micro, sys.version_info.releaselevel))
gef_print("* OS: {:s} - {:s} ({:s})".format(platform.system(), platform.release(), platform.machine()))
try:
lsb_release = which("lsb_release")
gdb.execute("!{} -a".format(lsb_release,))
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):
"""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):
"""Returns an immutable bytes list from the string given as input."""
return bytes(str(x), encoding="utf-8")
@lru_cache()
def which(program):
"""Locate a command on the filesystem."""
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
fpath = os.path.split(program)[0]
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
path = path.strip('"')
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
raise FileNotFoundError("Missing file `{:s}`".format(program))
def style_byte(b, color=True):
style = {
"nonprintable": "yellow",
"printable": "white",
"00": "gray",
"0a": "blue",
"ff": "green",
}
sbyte = "{:02x}".format(b)
if not color or get_gef_setting("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, length=0x10, separator=".", show_raw=False, show_symbol=True, base=0x00):
"""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 = get_memory_alignment() * 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("{addr:#0{aw}x} {sym} {data:<{dw}} {text}".format(aw=align,
addr=base+i,
sym=sym,
dw=3*length,
data=hexa,
text=text))
return "\n".join(result)
def is_debug():
"""Check if debug mode is enabled."""
return get_gef_setting("gef.debug") is True
context_hidden = False
def hide_context():
global context_hidden
context_hidden = True
def unhide_context():
global context_hidden
context_hidden = False
def enable_redirect_output(to_file="/dev/null"):
"""Redirect all GDB output to `to_file` parameter. By default, `to_file` redirects to `/dev/null`."""
gdb.execute("set logging overwrite")
gdb.execute("set logging file {:s}".format(to_file))
gdb.execute("set logging redirect on")
gdb.execute("set logging on")
return
def disable_redirect_output():
"""Disable the output redirection, if any."""
gdb.execute("set logging off")
gdb.execute("set logging redirect off")
return
@lru_cache()
def get_gef_setting(name):
"""Read global gef settings.
Return None if not found. A valid config setting can never return None,
but False, 0 or ""."""
global __config__
setting = __config__.get(name, None)
if not setting:
return None
return setting[0]
def set_gef_setting(name, value, _type=None, _desc=None):
"""Set global gef settings.
Raise ValueError if `name` doesn't exist and `type` and `desc`
are not provided."""
global __config__
if name not in __config__:
# create new setting
if _type is None or _desc is None:
raise ValueError("Setting '{}' is undefined, need to provide type and description".format(name))
__config__[name] = [_type(value), _type, _desc]
return
# set existing setting
func = __config__[name][1]
__config__[name][0] = func(value)
get_gef_setting.cache_clear()
return
def gef_makedirs(path, mode=0o755):
"""Recursive mkdir() creation. If successful, return the absolute path of the directory created."""
abspath = os.path.expanduser(path)
abspath = os.path.realpath(abspath)
if os.path.isdir(abspath):
return abspath
os.makedirs(abspath, mode=mode, exist_ok=True)
return abspath
@lru_cache()
def gdb_lookup_symbol(sym):
"""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):
"""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
name = None
sym = gdb.execute("info symbol {:#x}".format(address), 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, **kwargs):
"""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 = read_memory(insn["addr"], insn["length"])
yield Instruction(address, location, mnemo, operands, opcodes)
def gdb_get_nth_previous_instruction_address(addr, n):
"""Return the address (Integer) of the `n`-th instruction before `addr`."""
# fixed-length ABI
if current_arch.instruction_length:
return max(0, addr - n * current_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, n):
"""Return the address (Integer) of the `n`-th instruction after `addr`."""
# fixed-length ABI
if current_arch.instruction_length:
return addr + n * current_arch.instruction_length
# variable-length ABI
insn = list(gdb_disassemble(addr, count=n))[-1]
return insn.address
def gef_instruction_n(addr, n):
"""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):
"""Return the full Instruction found at the specified address."""
insn = next(gef_disassemble(addr, 1))
return insn
def gef_current_instruction(addr):
"""Return the current instruction as an Instruction object."""
return gef_instruction_n(addr, 0)
def gef_next_instruction(addr):
"""Return the next instruction as an Instruction object."""
return gef_instruction_n(addr, 1)
def gef_disassemble(addr, nb_insn, nb_prev=0):
"""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, nb_insn, **kwargs):
"""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):
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 = current_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", read_memory(location, gef_getpagesize() - offset - 1))
code = bytes(code)
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, as_list=False, *args, **kwargs):
"""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):
"""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()
if os.access(fname, os.R_OK):
gdb.execute("source {:s}".format(fname))
os.unlink(fname)
return
@lru_cache(32)
def checksec(filename):
"""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."""
if is_macho(filename):
return {
"Canary": False,
"NX": False,
"PIE": False,
"Fortify": False,
"Partial RelRO": False,
}
try:
readelf = which("readelf")
except IOError:
err("Missing `readelf`")
return
def __check_security_property(opt, filename, pattern):
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():
"""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("Unknown architecture: {}".format(arch_str))
return arch_str
@lru_cache()
def get_endian():
"""Return the binary endianness."""
endian = gdb.execute("show endian", to_string=True).strip().lower()
if "little endian" in endian:
return Elf.LITTLE_ENDIAN
if "big endian" in endian:
return Elf.BIG_ENDIAN
raise EnvironmentError("Invalid endianness")
@lru_cache()
def get_entry_point():
"""Return the binary entry point."""
for line in gdb.execute("info target", to_string=True).split("\n"):
if "Entry point:" in line:
return int(line.strip().split(" ")[-1], 16)
return None
def is_pie(fpath):
return checksec(fpath)["PIE"]
def is_big_endian(): return get_endian() == Elf.BIG_ENDIAN
def is_little_endian(): return not is_big_endian()
def flags_to_human(reg_value, value_table):
"""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 "[{}]".format(" ".join(flags))
class Architecture(metaclass=abc.ABCMeta):
"""Generic metaclass for the architecture supported by GEF."""
@abc.abstractproperty
def all_registers(self): pass
@abc.abstractproperty
def instruction_length(self): pass
@abc.abstractproperty
def nop_insn(self): pass
@abc.abstractproperty
def return_register(self): pass
@abc.abstractproperty
def flag_register(self): pass
@abc.abstractproperty
def flags_table(self): pass
@abc.abstractproperty
def function_parameters(self): pass
@abc.abstractmethod
def flag_register_to_human(self, val=None): pass
@abc.abstractmethod
def is_call(self, insn): pass
@abc.abstractmethod
def is_ret(self, insn): pass
@abc.abstractmethod
def is_conditional_branch(self, insn): pass
@abc.abstractmethod
def is_branch_taken(self, insn): pass
@abc.abstractmethod
def get_ra(self, insn, frame): pass
special_registers = []
@property
def pc(self):
return get_register("$pc")
@property
def sp(self):
return get_register("$sp")
@property
def fp(self):
return get_register("$fp")
@property
def ptrsize(self):
return get_memory_alignment()
def get_ith_parameter(self, i, in_func=True):
"""Retrieves the correct parameter used for the current function call."""
reg = self.function_parameters[i]
val = get_register(reg)
key = reg
return key, val
class RISCV(Architecture):
arch = "RISCV"
mode = "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):
return 4
def is_call(self, insn):
return insn.mnemonic == "call"
def is_ret(self, insn):
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, size, perm):
raise OSError("Architecture {:s} not supported yet".format(cls.arch))
def is_conditional_branch(self, insn):
return insn.mnemonic.startswith("b")
def is_branch_taken(self, insn):
def long_to_twos_complement(v):
"""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 = get_register(insn.operands[0])
rs2 = get_register("$zero")
condition = condition[:-1]
elif len(insn.operands) > 2:
# r2 is populated with the second operand
rs1 = get_register(insn.operands[0])
rs2 = get_register(insn.operands[1])
else:
raise OSError("RISC-V: Failed to get rs1 and rs2 for instruction: `{}`".format(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, "{}={}".format(rs1, rs2)
else: taken, reason = False, "{}!={}".format(rs1, rs2)
elif condition == "ne":
if rs1 != rs2: taken, reason = True, "{}!={}".format(rs1, rs2)
else: taken, reason = False, "{}={}".format(rs1, rs2)
elif condition == "lt":
if rs1 < rs2: taken, reason = True, "{}<{}".format(rs1, rs2)
else: taken, reason = False, "{}>={}".format(rs1, rs2)
elif condition == "ge":
if rs1 < rs2: taken, reason = True, "{}>={}".format(rs1, rs2)
else: taken, reason = False, "{}<{}".format(rs1, rs2)
else:
raise OSError("RISC-V: Conditional instruction `{:s}` not supported yet".format(insn))
return taken, reason
def get_ra(self, insn, frame):
ra = None
if self.is_ret(insn):
ra = get_register("$ra")
elif frame.older():
ra = frame.older().pc()
return ra
class ARM(Architecture):
arch = "ARM"
all_registers = ["$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6",
"$r7", "$r8", "$r9", "$r10", "$r11", "$r12", "$sp",
"$lr", "$pc", "$cpsr",]
# http://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"]
def is_thumb(self):
"""Determine if the machine is currently in THUMB mode."""
return is_alive() and get_register(self.flag_register) & (1 << 5)
@property
def pc(self):
pc = get_register("$pc")
if self.is_thumb():
pc += 1
return pc
@property
def mode(self):
return "THUMB" if self.is_thumb() else "ARM"
@property
def instruction_length(self):
# Thumb instructions have variable-length (2 or 4-byte)
return None if self.is_thumb() else 4
def is_call(self, insn):
mnemo = insn.mnemonic
call_mnemos = {"bl", "blx"}
return mnemo in call_mnemos
def is_ret(self, insn):
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
def flag_register_to_human(self, val=None):
# http://www.botskool.com/user-pages/tutorials/electronics/arm-7-tutorial-part-1
if val is None:
reg = self.flag_register
val = get_register(reg)
return flags_to_human(val, self.flags_table)
def is_conditional_branch(self, insn):
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):
mnemo = insn.mnemonic
# ref: http://www.davespace.co.uk/arm/introduction-to-arm/conditional.html
flags = dict((self.flags_table[k], k) for k in self.flags_table)
val = get_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, frame):
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 = current_arch.sp + (len(insn.operands)-1) * get_memory_alignment()
ra = to_unsigned_long(dereference(ra_addr))
elif insn.mnemonic == "ldr":
return to_unsigned_long(dereference(current_arch.sp))
else: # 'bx lr' or 'add pc, lr, #0'
return get_register("$lr")
elif frame.older():
ra = frame.older().pc()
return ra
@classmethod
def mprotect_asm(cls, addr, size, perm):
_NR_mprotect = 125
insns = [
"push {r0-r2, r7}",
"mov r1, {:d}".format(addr & 0xffff),
"mov r0, {:d}".format((addr & 0xffff0000) >> 16),
"lsl r0, r0, 16",
"add r0, r0, r1",
"mov r1, {:d}".format(size & 0xffff),
"mov r2, {:d}".format(perm & 0xff),
"mov r7, {:d}".format(_NR_mprotect),
"svc 0",
"pop {r0-r2, r7}",
]
return "; ".join(insns)
class AARCH64(ARM):
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"]
def is_call(self, insn):
mnemo = insn.mnemonic
call_mnemos = {"bl", "blr"}
return mnemo in call_mnemos
def flag_register_to_human(self, val=None):
# http://events.linuxfoundation.org/sites/events/files/slides/KoreaLinuxForum-2014.pdf
reg = self.flag_register
if not val:
val = get_register(reg)
return flags_to_human(val, self.flags_table)
@classmethod
def mprotect_asm(cls, addr, size, perm):
_NR_mprotect = 226
insns = [
"str x8, [sp, -16]!",
"str x0, [sp, -16]!",
"str x1, [sp, -16]!",
"str x2, [sp, -16]!",
"mov x8, {:d}".format(_NR_mprotect),
"movz x0, 0x{:x}".format(addr & 0xFFFF),
"movk x0, 0x{:x}, lsl 16".format((addr >> 16) & 0xFFFF),
"movk x0, 0x{:x}, lsl 32".format((addr >> 32) & 0xFFFF),
"movk x0, 0x{:x}, lsl 48".format((addr >> 48) & 0xFFFF),
"movz x1, 0x{:x}".format(size & 0xFFFF),
"movk x1, 0x{:x}, lsl 16".format((size >> 16) & 0xFFFF),
"mov x2, {:d}".format(perm),
"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):
# 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):
mnemo, operands = insn.mnemonic, insn.operands
taken, reason = False, ""
if mnemo in {"cbnz", "cbz", "tbnz", "tbz"}:
reg = "${}".format(operands[0])
op = get_register(reg)
if mnemo == "cbnz":
if op!=0: taken, reason = True, "{}!=0".format(reg)
else: taken, reason = False, "{}==0".format(reg)
elif mnemo == "cbz":
if op == 0: taken, reason = True, "{}==0".format(reg)
else: taken, reason = False, "{}!=0".format(reg)
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, "{}&1<<{}!=0".format(reg, i)
else: taken, reason = False, "{}&1<<{}==0".format(reg, i)
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, "{}&1<<{}==0".format(reg, i)
else: taken, reason = False, "{}&1<<{}!=0".format(reg, i)
if not reason:
taken, reason = super().is_branch_taken(insn)
return taken, reason
class X86(Architecture):
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"]
def flag_register_to_human(self, val=None):
reg = self.flag_register
if not val:
val = get_register(reg)
return flags_to_human(val, self.flags_table)
def is_call(self, insn):
mnemo = insn.mnemonic
call_mnemos = {"call", "callq"}
return mnemo in call_mnemos
def is_ret(self, insn):
return insn.mnemonic == "ret"
def is_conditional_branch(self, insn):
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):
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 = get_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 = get_register("$rcx") if self.mode == 64 else get_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, frame):
ra = None
if self.is_ret(insn):
ra = to_unsigned_long(dereference(current_arch.sp))
if frame.older():
ra = frame.older().pc()
return ra
@classmethod
def mprotect_asm(cls, addr, size, perm):
_NR_mprotect = 125
insns = [
"pushad",
"mov eax, {:d}".format(_NR_mprotect),
"mov ebx, {:d}".format(addr),
"mov ecx, {:d}".format(size),
"mov edx, {:d}".format(perm),
"int 0x80",
"popad",
]
return "; ".join(insns)
def get_ith_parameter(self, i, in_func=True):
if in_func:
i += 1 # Account for RA being at the top of the stack
sp = current_arch.sp
sz = current_arch.ptrsize
loc = sp + (i * sz)
val = read_int_from_memory(loc)
key = "[sp + {:#x}]".format(i * sz)
return key, val
class X86_64(X86):
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
@classmethod
def mprotect_asm(cls, addr, size, perm):
_NR_mprotect = 10
insns = [
"push rax",
"push rdi",
"push rsi",
"push rdx",
"push rcx",
"push r11",
"mov rax, {:d}".format(_NR_mprotect),
"mov rdi, {:d}".format(addr),
"mov rsi, {:d}".format(size),
"mov rdx, {:d}".format(perm),
"syscall",
"pop r11",
"pop rcx",
"pop rdx",
"pop rsi",
"pop rdi",
"pop rax",
]
return "; ".join(insns)
class PowerPC(Architecture):
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" # http://www.ibm.com/developerworks/library/l-ppc/index.html
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=None):
# http://www.cebix.net/downloads/bebox/pem32b.pdf (% 2.1.3)
if not val:
reg = self.flag_register
val = get_register(reg)
return flags_to_human(val, self.flags_table)
def is_call(self, insn):
return False
def is_ret(self, insn):
return insn.mnemonic == "blr"
def is_conditional_branch(self, insn):
mnemo = insn.mnemonic
branch_mnemos = {"beq", "bne", "ble", "blt", "bgt", "bge"}
return mnemo in branch_mnemos
def is_branch_taken(self, insn):
mnemo = insn.mnemonic
flags = dict((self.flags_table[k], k) for k in self.flags_table)
val = get_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, frame):
ra = None
if self.is_ret(insn):
ra = get_register("$lr")
elif frame.older():
ra = frame.older().pc()
return ra
@classmethod
def mprotect_asm(cls, addr, size, perm):
# Ref: http://www.ibm.com/developerworks/library/l-ppc/index.html
_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)",
"li 0, {:d}".format(_NR_mprotect),
"lis 3, {:#x}@h".format(addr),
"ori 3, 3, {:#x}@l".format(addr),
"lis 4, {:#x}@h".format(size),
"ori 4, 4, {:#x}@l".format(size),
"li 5, {:d}".format(perm),
"sc",
"lwz 0, 0(1)",
"lwz 3, 4(1)",
"lwz 4, 8(1)",
"lwz 5, 12(1)",
"addi 1, 1, 16",
]
return ";".join(insns)
class PowerPC64(PowerPC):
arch = "PPC"
mode = "PPC64"
class SPARC(Architecture):
""" Refs:
- http://www.cse.scu.edu/~atkinson/teaching/sp05/259/sparc.pdf
"""
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=None):
# http://www.gaisler.com/doc/sparcv8.pdf
reg = self.flag_register
if not val:
val = get_register(reg)
return flags_to_human(val, self.flags_table)
def is_call(self, insn):
return False
def is_ret(self, insn):
return insn.mnemonic == "ret"
def is_conditional_branch(self, insn):
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):
mnemo = insn.mnemonic
flags = dict((self.flags_table[k], k) for k in self.flags_table)
val = get_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, frame):
ra = None
if self.is_ret(insn):
ra = get_register("$o7")
elif frame.older():
ra = frame.older().pc()
return ra
@classmethod
def mprotect_asm(cls, addr, size, perm):
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 ]",
"sethi %hi({}), %o0".format(hi),
"or %o0, {}, %o0".format(lo),
"clr %o1",
"clr %o2",
"mov {}, %g1".format(_NR_mprotect),
"t 0x10",
"ld [ %sp ], %g1", "ld [ %sp + 4 ], %o0",
"ld [ %sp + 8 ], %o1", "ld [ %sp + 12 ], %o2",
"add %sp, 16, %sp",]
return "; ".join(insns)
class SPARC64(SPARC):
"""Refs:
- http://math-atlas.sourceforge.net/devel/assembly/abi_sysV_sparc.pdf
- https://cr.yp.to/2005-590/sparcv9.pdf
"""
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, size, perm):
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 ]",
"sethi %hi({}), %o0".format(hi),
"or %o0, {}, %o0".format(lo),
"clr %o1",
"clr %o2",
"mov {}, %g1".format(_NR_mprotect),
"t 0x6d",
"ld [ %sp ], %g1", "ld [ %sp + 4 ], %o0",
"ld [ %sp + 8 ], %o1", "ld [ %sp + 12 ], %o2",
"add %sp, 16, %sp",]
return "; ".join(insns)
class MIPS(Architecture):
arch = "MIPS"
mode = "MIPS32"
# http://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
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=None):
return Color.colorify("No flag register", "yellow underline")
def is_call(self, insn):
return False
def is_ret(self, insn):
return insn.mnemonic == "jr" and insn.operands[0] == "ra"
def is_conditional_branch(self, insn):
mnemo = insn.mnemonic
branch_mnemos = {"beq", "bne", "beqz", "bnez", "bgtz", "bgez", "bltz", "blez"}
return mnemo in branch_mnemos
def is_branch_taken(self, insn):
mnemo, ops = insn.mnemonic, insn.operands
taken, reason = False, ""
if mnemo == "beq":
taken, reason = get_register(ops[0]) == get_register(ops[1]), "{0[0]} == {0[1]}".format(ops)
elif mnemo == "bne":
taken, reason = get_register(ops[0]) != get_register(ops[1]), "{0[0]} != {0[1]}".format(ops)
elif mnemo == "beqz":
taken, reason = get_register(ops[0]) == 0, "{0[0]} == 0".format(ops)
elif mnemo == "bnez":
taken, reason = get_register(ops[0]) != 0, "{0[0]} != 0".format(ops)
elif mnemo == "bgtz":
taken, reason = get_register(ops[0]) > 0, "{0[0]} > 0".format(ops)
elif mnemo == "bgez":
taken, reason = get_register(ops[0]) >= 0, "{0[0]} >= 0".format(ops)
elif mnemo == "bltz":
taken, reason = get_register(ops[0]) < 0, "{0[0]} < 0".format(ops)
elif mnemo == "blez":
taken, reason = get_register(ops[0]) <= 0, "{0[0]} <= 0".format(ops)
return taken, reason
def get_ra(self, insn, frame):
ra = None
if self.is_ret(insn):
ra = get_register("$ra")
elif frame.older():
ra = frame.older().pc()
return ra
@classmethod
def mprotect_asm(cls, addr, size, perm):
_NR_mprotect = 4125
insns = ["addi $sp, $sp, -16",
"sw $v0, 0($sp)", "sw $a0, 4($sp)",
"sw $a3, 8($sp)", "sw $a3, 12($sp)",
"li $v0, {:d}".format(_NR_mprotect),
"li $a0, {:d}".format(addr),
"li $a1, {:d}".format(size),
"li $a2, {:d}".format(perm),
"syscall",
"lw $v0, 0($sp)", "lw $a1, 4($sp)",
"lw $a3, 8($sp)", "lw $a3, 12($sp)",
"addi $sp, $sp, 16",]
return "; ".join(insns)
def write_memory(address, buffer, length=0x10):
"""Write `buffer` at address `address`."""
return gdb.selected_inferior().write_memory(address, buffer, length)
@lru_cache()
def read_memory(addr, length=0x10):
"""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_int_from_memory(addr):
"""Return an integer read from memory."""
sz = current_arch.ptrsize
mem = read_memory(addr, sz)
unpack = u32 if sz == 4 else u64
return unpack(mem)
def read_cstring_from_memory(address, max_length=GEF_MAX_STRING_LENGTH, encoding=None):
"""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 = bytes(read_memory(address, length))
except gdb.error:
err("Can't read memory at '{}'".format(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 "{}[...]".format(ustr[:max_length])
return ustr
def read_ascii_string(address):
"""Read an ASCII string from memory"""
cstr = read_cstring_from_memory(address)
if isinstance(cstr, str) and cstr and all([x in string.printable for x in cstr]):
return cstr
return None
def p8(x: int, s: bool = False) -> bytes:
"""Pack one byte respecting the current architecture endianness."""
return struct.pack("{}B".format(endian_str()), x) if not s else struct.pack("{}b".format(endian_str()), x)
def p16(x: int, s: bool = False) -> bytes:
"""Pack one word respecting the current architecture endianness."""
return struct.pack("{}H".format(endian_str()), x) if not s else struct.pack("{}h".format(endian_str()), x)
def p32(x: int, s: bool = False) -> bytes:
"""Pack one dword respecting the current architecture endianness."""
return struct.pack("{}I".format(endian_str()), x) if not s else struct.pack("{}i".format(endian_str()), x)
def p64(x: int, s: bool = False) -> bytes:
"""Pack one qword respecting the current architecture endianness."""
return struct.pack("{}Q".format(endian_str()), x) if not s else struct.pack("{}q".format(endian_str()), x)
def u8(x: bytes, s: bool = False) -> int:
"""Unpack one byte respecting the current architecture endianness."""
return struct.unpack("{}B".format(endian_str()), x)[0] if not s else struct.unpack("{}b".format(endian_str()), x)[0]
def u16(x: bytes, s: bool = False) -> int:
"""Unpack one word respecting the current architecture endianness."""
return struct.unpack("{}H".format(endian_str()), x)[0] if not s else struct.unpack("{}h".format(endian_str()), x)[0]
def u32(x: bytes, s: bool = False) -> int:
"""Unpack one dword respecting the current architecture endianness."""
return struct.unpack("{}I".format(endian_str()), x)[0] if not s else struct.unpack("{}i".format(endian_str()), x)[0]
def u64(x: bytes, s: bool = False) -> int:
"""Unpack one qword respecting the current architecture endianness."""
return struct.unpack("{}Q".format(endian_str()), x)[0] if not s else struct.unpack("{}q".format(endian_str()), x)[0]
def is_ascii_string(address):
"""Helper function to determine if the buffer pointed by `address` is an ASCII string (in GDB)"""
try:
return read_ascii_string(address) is not None
except Exception:
return False
def is_alive():
"""Check if GDB is running."""
try:
return gdb.selected_inferior().pid > 0
except Exception:
return False
return False
def only_if_gdb_running(f):
"""Decorator wrapper to check if GDB is running."""
@functools.wraps(f)
def wrapper(*args, **kwargs):
if is_alive():
return f(*args, **kwargs)
else:
warn("No debugging session active")
return wrapper
def only_if_gdb_target_local(f):
"""Decorator wrapper to check if GDB is running locally (target not remote)."""
@functools.wraps(f)
def wrapper(*args, **kwargs):
if not is_remote_debug():
return f(*args, **kwargs)
else:
warn("This command cannot work for remote sessions.")
return wrapper
def obsolete_command(f):
"""Decorator to add a warning when a command is obsolete and will be removed."""
@functools.wraps(f)
def wrapper(*args, **kwargs):
warn("This command is obsolete and will be removed in a feature release.")
return f(*args, **kwargs)
return wrapper
def experimental_feature(f):
"""Decorator to add a warning when a feature is experimental."""
@functools.wraps(f)
def wrapper(*args, **kwargs):
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):
"""Decorator to check whether current GDB version requirements."""
def wrapper(f):
def inner_f(*args, **kwargs):
if GDB_VERSION >= required_gdb_version:
f(*args, **kwargs)
else:
reason = "GDB >= {} for this command".format(required_gdb_version)
raise EnvironmentError(reason)
return inner_f
return wrapper
def only_if_current_arch_in(valid_architectures):
"""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):
def inner_f(*args, **kwargs):
if current_arch in valid_architectures:
f(*args, **kwargs)
else:
reason = "This command cannot work for the '{}' architecture".format(current_arch.arch)
raise EnvironmentError(reason)
return inner_f
return wrapper
def FakeExit(*args, **kwargs):
raise RuntimeWarning
sys.exit = FakeExit
def parse_arguments(required_arguments, optional_arguments):
"""Argument parsing decorator."""
def int_wrapper(x): return int(x, 0)
def decorator(f):
def wrapper(*args, **kwargs):
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
def copy_to_clipboard(data):
"""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():
if is_32bit(): return "uint32_t"
elif is_64bit(): return "uint64_t"
return "uint16_t"
def use_default_type():
if is_32bit(): return "unsigned int"
elif is_64bit(): return "unsigned long"
return "unsigned short"
def use_golang_type():
if is_32bit(): return "uint32"
elif is_64bit(): return "uint64"
return "uint16"
def use_rust_type():
if is_32bit(): return "u32"
elif is_64bit(): return "u64"
return "u16"
def to_unsigned_long(v):
"""Cast a gdb.Value to unsigned long."""
mask = (1 << 64) - 1
return int(v.cast(gdb.Value(mask).type)) & mask
def get_register(regname):
"""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 __get_register_for_selected_frame(regname, key)
@lru_cache()
def __get_register_for_selected_frame(regname, hash_key):
try:
return parse_address(regname)
except gdb.error:
assert regname[0] == "$"
regname = regname[1:]
try:
value = gdb.selected_frame().read_register(regname)
return int(value)
except ValueError:
return None
except gdb.error:
return None
def get_path_from_info_proc():
for x in gdb.execute("info proc", to_string=True).splitlines():
if x.startswith("exe = "):
return x.split(" = ")[1].replace("'", "")
return None
@lru_cache()
def get_os():
"""Return the current OS."""
return platform.system().lower()
@lru_cache()
def is_qemu():
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():
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():
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_pid():
"""Return the PID of the target process."""
pid = gdb.selected_inferior().pid if not __gef_qemu_mode__ else gdb.selected_thread().ptid[1]
if not pid:
raise RuntimeError("cannot retrieve PID for target process")
return pid
@lru_cache()
def get_filepath():
"""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 = get_pid()
if pid > 0:
return download_file("/proc/{:d}/exe".format(pid), 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_remote__ is not None:
return "/tmp/gef/{:d}/{:s}".format(__gef_remote__, 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()
@lru_cache()
def get_filename():
"""Return the full filename of the file currently debugged."""
return os.path.basename(gdb.current_progspace().filename)
@lru_cache()
def inferior_is_macho():
"""Return True if the current file is a Mach-O binary."""
for x in gdb.execute("info files", to_string=True).splitlines():
if "file type mach-o" in x:
return True
return False
@lru_cache()
def is_macho(filename):
"""Return True if the specified file is a Mach-O binary."""
file_bin = which("file")
cmd = [file_bin, filename]
out = gef_execute_external(cmd)
if "Mach-O" in out:
return True
return False
def download_file(target, use_cache=False, local_name=None):
"""Download filename `target` inside the mirror tree inside the get_gef_setting("gef.tempdir").
The tree architecture must be get_gef_setting("gef.tempdir")/gef/<local_pid>/<remote_filepath>.
This allow a "chroot-like" tree format."""
try:
local_root = os.path.sep.join([get_gef_setting("gef.tempdir"), str(get_pid())])
if local_name is None:
local_path = os.path.sep.join([local_root, os.path.dirname(target)])
local_name = os.path.sep.join([local_path, os.path.basename(target)])
else:
local_path = os.path.sep.join([local_root, os.path.dirname(local_name)])
local_name = os.path.sep.join([local_path, os.path.basename(local_name)])
if use_cache and os.access(local_name, os.R_OK):
return local_name
gef_makedirs(local_path)
gdb.execute("remote get {0:s} {1:s}".format(target, local_name))
except gdb.error:
# fallback memory view
with open(local_name, "w") as f:
if is_32bit():
f.write("00000000-ffffffff rwxp 00000000 00:00 0 {}\n".format(get_filepath()))
else:
f.write("0000000000000000-ffffffffffffffff rwxp 00000000 00:00 0 {}\n".format(get_filepath()))
except Exception as e:
err("download_file() failed: {}".format(str(e)))
local_name = None
return local_name
def open_file(path, use_cache=False):
"""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_qemu_mode__:
lpath = download_file(path, use_cache)
if not lpath:
raise IOError("cannot open remote path {:s}".format(path))
path = lpath
return open(path, "r")
def get_function_length(sym):
"""Attempt to get the length of the raw bytes of a function."""
dis = gdb.execute("disassemble {:s}".format(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
def get_process_maps_linux(proc_map_file):
"""Parse the Linux process `/proc/pid/maps` file."""
with open_file(proc_map_file, use_cache=False) as f:
file = f.readlines()
for line in file:
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)
yield Section(page_start=addr_start,
page_end=addr_end,
offset=off,
permission=perm,
inode=inode,
path=pathname)
return
def get_mach_regions():
sp = current_arch.sp
for line in gdb.execute("info mach-regions", to_string=True).splitlines():
line = line.strip()
addr, perm, _ = line.split(" ", 2)
addr_start, addr_end = [int(x, 16) for x in addr.split("-")]
perm = Permission.from_process_maps(perm.split("/")[0])
zone = file_lookup_address(addr_start)
if zone:
path = zone.filename
else:
path = "[stack]" if sp >= addr_start and sp < addr_end else ""
yield Section(page_start=addr_start,
page_end=addr_end,
offset=0,
permission=perm,
inode=None,
path=path)
return
@lru_cache()
def get_process_maps():
"""Return the mapped memory sections"""
if inferior_is_macho():
return list(get_mach_regions())
try:
pid = get_pid()
fpath = "/proc/{:d}/maps".format(pid)
return list(get_process_maps_linux(fpath))
except FileNotFoundError as e:
warn("Failed to read /proc/<PID>/maps, using GDB sections info: {}".format(e))
return list(get_info_sections())
@lru_cache()
def get_info_sections():
"""Retrieve the debuggee sections."""
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]
inode = ""
perm = Permission.from_info_sections(parts[5:])
yield Section(page_start=addr_start,
page_end=addr_end,
offset=off,
permission=perm,
inode=inode,
path=path)
except IndexError:
continue
except ValueError:
continue
return
@lru_cache()
def get_info_files():
"""Retrieve all the files loaded by debuggee."""
lines = gdb.execute("info files", to_string=True).splitlines()
if len(lines) < len(__infos_files__):
return __infos_files__
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()
info = Zone(section_name, addr_start, addr_end, filename)
__infos_files__.append(info)
return __infos_files__
def process_lookup_address(address):
"""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 get_process_maps():
if sect.page_start <= address < sect.page_end:
return sect
return None
@lru_cache()
def process_lookup_path(name, perm=Permission.ALL):
"""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 get_process_maps():
if name in sect.path and sect.permission.value & perm:
return sect
return None
@lru_cache()
def file_lookup_name_path(name, path):
"""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):
"""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):
"""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, key):
"""Return `data` xor-ed with `key`."""
key = key.lstrip("0x")
key = binascii.unhexlify(key)
return bytearray([x ^ y for x, y in zip(data, itertools.cycle(key))])
def is_hex(pattern):
"""Return whether provided string is a hexadecimal value."""
if not pattern.startswith("0x") and not pattern.startswith("0X"):
return False
return len(pattern) % 2 == 0 and all(c in string.hexdigits for c in pattern[2:])
def ida_synchronize_handler(event):
gdb.execute("ida-interact sync", from_tty=True)
return
def continue_handler(event):
"""GDB event handler for new object continue cases."""
return
def hook_stop_handler(event):
"""GDB event handler for stop cases."""
reset_all_caches()
gdb.execute("context")
return
def new_objfile_handler(event):
"""GDB event handler for new object file cases."""
reset_all_caches()
set_arch()
load_libc_args()
return
def exit_handler(event):
"""GDB event handler for exit cases."""
global __gef_remote__, __gef_qemu_mode__
reset_all_caches()
__gef_qemu_mode__ = False
if __gef_remote__ and get_gef_setting("gef-remote.clean_on_exit") is True:
shutil.rmtree("/tmp/gef/{:d}".format(__gef_remote__))
__gef_remote__ = None
return
def memchanged_handler(event):
"""GDB event handler for mem changes cases."""
reset_all_caches()
def regchanged_handler(event):
"""GDB event handler for reg changes cases."""
reset_all_caches()
def load_libc_args():
# load libc function arguments' definitions
if not get_gef_setting("context.libc_args"):
return
path = get_gef_setting("context.libc_args_path")
if path is None:
warn("Config `context.libc_args_path` not set but `context.libc_args` is True. Make sure you have `gef-extras` installed")
return
path = os.path.realpath(os.path.expanduser(path))
if not os.path.isdir(path):
warn("Config `context.libc_args_path` set but it's not a directory")
return
_arch_mode = "{}_{}".format(current_arch.arch.lower(), current_arch.mode)
_libc_args_file = "{}/{}.json".format(path, _arch_mode)
global libc_args_definitions
# current arch and mode already loaded
if _arch_mode in libc_args_definitions:
return
libc_args_definitions[_arch_mode] = {}
try:
with open(_libc_args_file) as _libc_args:
libc_args_definitions[_arch_mode] = json.load(_libc_args)
except FileNotFoundError:
del(libc_args_definitions[_arch_mode])
warn("Config context.libc_args is set but definition cannot be loaded: file {} not found".format(_libc_args_file))
except json.decoder.JSONDecodeError as e:
del(libc_args_definitions[_arch_mode])
warn("Config context.libc_args is set but definition cannot be loaded from file {}: {}".format(_libc_args_file, e))
return
def get_terminal_size():
"""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, prefix, arch, mode, big_endian, to_string=False):
"""
Retrieves architecture and mode from the arguments for use for the holy
{cap,key}stone/unicorn trinity.
"""
if to_string:
arch = "{:s}.{:s}_ARCH_{:s}".format(module.__name__, prefix, arch)
if mode:
mode = "{:s}.{:s}_MODE_{:s}".format(module.__name__, prefix, str(mode))
else:
mode = ""
if is_big_endian():
mode += " + {:s}.{:s}_MODE_BIG_ENDIAN".format(module.__name__, prefix)
else:
mode += " + {:s}.{:s}_MODE_LITTLE_ENDIAN".format(module.__name__, prefix)
else:
arch = getattr(module, "{:s}_ARCH_{:s}".format(prefix, arch))
if mode:
mode = getattr(module, "{:s}_MODE_{:s}".format(prefix, mode))
else:
mode = 0
if big_endian:
mode |= getattr(module, "{:s}_MODE_BIG_ENDIAN".format(prefix))
else:
mode |= getattr(module, "{:s}_MODE_LITTLE_ENDIAN".format(prefix))
return arch, mode
def get_generic_running_arch(module, prefix, to_string=False):
"""
Retrieves architecture and mode from the current context.
"""
if not is_alive():
return None, None
if current_arch is not None:
arch, mode = current_arch.arch, current_arch.mode
else:
raise OSError("Emulation not supported for your OS")
return get_generic_arch(module, prefix, arch, mode, is_big_endian(), to_string)
def get_unicorn_arch(arch=None, mode=None, endian=None, to_string=False):
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=None, mode=None, endian=None, to_string=False):
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.POWERPC64):
raise OSError("Capstone not supported for PPC64 yet.")
if is_alive() and is_arch(Elf.POWERPC):
arch = "PPC"
mode = "32"
endian = is_big_endian()
return get_generic_arch(capstone, "CS",
arch or current_arch.arch,
mode or current_arch.mode,
endian or is_big_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 current_arch.arch,
mode or current_arch.mode,
endian or is_big_endian(),
to_string)
def get_keystone_arch(arch=None, mode=None, endian=None, to_string=False):
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 += "|{}".format(_mode)
mode = mode[1:]
return arch, mode
def get_unicorn_registers(to_string=False):
"Return a dict matching the Unicorn identifier for a specific register."
unicorn = sys.modules["unicorn"]
regs = {}
if current_arch is not None:
arch = current_arch.arch.lower()
else:
raise OSError("Oops")
const = getattr(unicorn, "{}_const".format(arch))
for reg in current_arch.all_registers:
regname = "UC_{:s}_REG_{:s}".format(arch.upper(), reg[1:].upper())
if to_string:
regs[reg] = "{:s}.{:s}".format(const.__name__, regname)
else:
regs[reg] = getattr(const, regname)
return regs
def keystone_assemble(code, arch, mode, *args, **kwargs):
"""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("Keystone assembler error: {:s}".format(str(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=None):
"""Return an Elf object with info from `filename`. If not provided, will return
the currently debugged file."""
if filename is None:
filename = get_filepath()
if filename.startswith("target:"):
warn("Your file is remote, you should try using `gef-remote` instead")
return
return Elf(filename)
def _ptr_width():
void = cached_lookup_type("void")
if void is None:
uintptr_t = cached_lookup_type("uintptr_t")
return uintptr_t.sizeof
else:
return void.pointer().sizeof
@lru_cache()
def is_64bit():
"""Checks if current target is 64bit."""
return _ptr_width() == 8
@lru_cache()
def is_32bit():
"""Checks if current target is 32bit."""
return _ptr_width() == 4
@lru_cache()
def is_x86_64():
"""Checks if current target is x86-64"""
return get_arch() == "i386:x86-64"
@lru_cache()
def is_x86_32():
"""Checks if current target is an x86-32"""
return get_arch() == "i386"
@lru_cache()
def is_x86():
return is_x86_32() or is_x86_64()
@lru_cache()
def is_arch(arch):
elf = current_elf or get_elf_headers()
return elf.e_machine == arch
def set_arch(arch=None, default=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.
Return the selected arch, or raise an OSError.
"""
arches = {
"ARM": ARM, Elf.ARM: ARM,
"AARCH64": AARCH64, "ARM64": AARCH64, Elf.AARCH64: AARCH64,
"X86": X86, Elf.X86_32: X86,
"X86_64": X86_64, Elf.X86_64: X86_64, "i386:x86-64": X86_64,
"PowerPC": PowerPC, "PPC": PowerPC, Elf.POWERPC: PowerPC,
"PowerPC64": PowerPC64, "PPC64": PowerPC64, Elf.POWERPC64: PowerPC64,
"RISCV": RISCV, Elf.RISCV: RISCV,
"SPARC": SPARC, Elf.SPARC: SPARC,
"SPARC64": SPARC64, Elf.SPARC64: SPARC64,
"MIPS": MIPS, Elf.MIPS: MIPS,
}
global current_arch, current_elf
if arch:
try:
current_arch = arches[arch.upper()]()
return current_arch
except KeyError:
raise OSError("Specified arch {:s} is not supported".format(arch.upper()))
if not current_elf:
elf = get_elf_headers()
current_elf = elf if elf.is_valid() else None
arch_name = current_elf.e_machine if current_elf else get_arch()
try:
current_arch = arches[arch_name]()
except KeyError:
if default:
try:
current_arch = arches[default.upper()]()
except KeyError:
raise OSError("CPU not supported, neither is default {:s}".format(default.upper()))
else:
raise OSError("CPU type is currently not supported: {:s}".format(get_arch()))
return current_arch
@lru_cache()
def cached_lookup_type(_type):
try:
return gdb.lookup_type(_type).strip_typedefs()
except RuntimeError:
return None
@lru_cache()
def get_memory_alignment(in_bits=False):
"""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."""
if is_32bit():
return 4 if not in_bits else 32
elif is_64bit():
return 8 if not in_bits else 64
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 EnvironmentError("GEF is running under an unsupported mode")
def clear_screen(tty=""):
"""Clear the screen."""
global __gef_redirect_output_fd__
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_redirect_output_fd__ = None
set_gef_setting("context.redirect", "")
return
def format_address(addr):
"""Format the address according to its size."""
memalign_size = get_memory_alignment()
addr = align_address(addr)
if memalign_size == 4:
return "0x{:08x}".format(addr)
return "0x{:016x}".format(addr)
def format_address_spaces(addr, left=True):
"""Format the address according to its size, but with spaces instead of zeroes."""
width = get_memory_alignment() * 2 + 2
addr = align_address(addr)
if not left:
return "0x{:x}".format(addr).rjust(width)
return "0x{:x}".format(addr).ljust(width)
def align_address(address):
"""Align the provided address to the process's native length."""
if get_memory_alignment() == 4:
return address & 0xFFFFFFFF
return address & 0xFFFFFFFFFFFFFFFF
def align_address_to_size(address, align):
"""Align the address to the given size."""
return address + ((align - (address % align)) % align)
def align_address_to_page(address):
"""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):
"""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 * current_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):
"""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):
address = align_address(address)
memalign = get_memory_alignment(in_bits=True) - 1
return (address >> memalign) == 0xF
@lru_cache()
def endian_str():
return "<" if is_little_endian() else ">"
@lru_cache()
def is_remote_debug():
""""Return True is the current debugging session is running through GDB remote session."""
return __gef_remote__ is not None or "remote" in gdb.execute("maintenance print target-stack", to_string=True)
def de_bruijn(alphabet, n):
"""De Bruijn sequence for alphabet and subsequences of length n (for compat. w/ pwnlib)."""
k = len(alphabet)
a = [0] * k * n
def db(t, p):
if t > n:
if n % p == 0:
for j in range(1, p + 1):
yield alphabet[a[j]]
else:
a[t] = a[t - p]
for c in db(t + 1, p):
yield c
for j in range(a[t - p] + 1, k):
a[t] = j
for c in db(t + 1, t):
yield c
return db(1, 1)
def generate_cyclic_pattern(length, cycle=4):
"""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):
"""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):
"""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):
"""Defines a new convenience value."""
global __gef_convenience_vars_index__
var_name = "$_gef{:d}".format(__gef_convenience_vars_index__)
__gef_convenience_vars_index__ += 1
gdb.execute("""set {:s} = "{:s}" """.format(var_name, value))
return var_name
def parse_string_range(s):
"""Parses an address range (e.g. 0x400000-0x401000)"""
addrs = s.split("-")
return map(lambda x: int(x, 16), addrs)
@lru_cache()
def gef_get_auxiliary_values():
"""Retrieve the ELF auxiliary values of the current execution. This
information is provided by the operating system to transfer some kernel
level information to the user process. Return None if not found, or a
dict() of values as: {aux_vect_name: int(aux_vect_value)}."""
if not is_alive():
return None
__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)
return __auxiliary_vector
def gef_read_canary():
"""Read the canary of a running process using Auxiliary Vector. Return a tuple of (canary, location)
if found, None otherwise."""
auxval = gef_get_auxiliary_values()
if not auxval:
return None
canary_location = auxval["AT_RANDOM"]
canary = read_int_from_memory(canary_location)
canary &= ~0xFF
return canary, canary_location
def gef_get_pie_breakpoint(num):
global __pie_breakpoints__
return __pie_breakpoints__[num]
@lru_cache()
def gef_getpagesize():
"""Get the page size from auxiliary values."""
auxval = gef_get_auxiliary_values()
if not auxval:
return DEFAULT_PAGE_SIZE
return auxval["AT_PAGESZ"]
def only_if_events_supported(event_type):
"""Checks if GDB supports events without crashing."""
def wrap(f):
def wrapped_f(*args, **kwargs):
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
#
# Event hooking
#
@only_if_events_supported("cont")
def gef_on_continue_hook(func):
return gdb.events.cont.connect(func)
@only_if_events_supported("cont")
def gef_on_continue_unhook(func):
return gdb.events.cont.disconnect(func)
@only_if_events_supported("stop")
def gef_on_stop_hook(func):
return gdb.events.stop.connect(func)
@only_if_events_supported("stop")
def gef_on_stop_unhook(func):
return gdb.events.stop.disconnect(func)
@only_if_events_supported("exited")
def gef_on_exit_hook(func):
return gdb.events.exited.connect(func)
@only_if_events_supported("exited")
def gef_on_exit_unhook(func):
return gdb.events.exited.disconnect(func)
@only_if_events_supported("new_objfile")
def gef_on_new_hook(func):
return gdb.events.new_objfile.connect(func)
@only_if_events_supported("new_objfile")
def gef_on_new_unhook(func):
return gdb.events.new_objfile.disconnect(func)
@only_if_events_supported("memory_changed")
def gef_on_memchanged_hook(func):
return gdb.events.memory_changed.connect(func)
@only_if_events_supported("memory_changed")
def gef_on_memchanged_unhook(func):
return gdb.events.memory_changed.disconnect(func)
@only_if_events_supported("register_changed")
def gef_on_regchanged_hook(func):
return gdb.events.register_changed.connect(func)
@only_if_events_supported("register_changed")
def gef_on_regchanged_unhook(func):
return gdb.events.register_changed.disconnect(func)
#
# Virtual breakpoints
#
class PieVirtualBreakpoint:
"""PIE virtual breakpoint (not real breakpoint)."""
def __init__(self, set_func, vbp_num, addr):
# 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 = hex(addr)
else:
self.addr = addr
return
def instantiate(self, base):
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):
if not self.bp_num:
err("Destroy PIE breakpoint not even set")
return
gdb.execute("delete {}".format(self.bp_num))
self.bp_num = 0
return
#
# Breakpoints
#
class FormatStringBreakpoint(gdb.Breakpoint):
"""Inspect stack for format string."""
def __init__(self, spec, num_args):
super().__init__(spec, type=gdb.BP_BREAKPOINT, internal=False)
self.num_args = num_args
self.enabled = True
return
def stop(self):
reset_all_caches()
msg = []
ptr, addr = current_arch.get_ith_parameter(self.num_args)
addr = lookup_address(addr)
if not addr.valid:
return False
if addr.section.permission.value & Permission.WRITE:
content = read_cstring_from_memory(addr.value)
name = addr.info.name if addr.info else addr.section.path
msg.append(Color.colorify("Format string helper", "yellow bold"))
msg.append("Possible insecure format string: {:s}('{:s}' {:s} {:#x}: '{:s}')".format(self.location, ptr, RIGHT_ARROW, addr.value, content))
msg.append("Reason: Call to '{:s}()' with format string argument in position "
"#{:d} is in page {:#x} ({:s}) that has write permission".format(self.location, self.num_args, addr.section.page_start, name))
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, retval):
super().__init__(func, gdb.BP_BREAKPOINT, internal=False)
self.func = func
self.retval = retval
m = "All calls to '{:s}' will be skipped".format(self.func)
if self.retval is not None:
m += " (with return value set to {:#x})".format(self.retval)
info(m)
return
def stop(self):
m = "Ignoring call to '{:s}' ".format(self.func)
m += "(setting return value to {:#x})".format(self.retval)
gdb.execute("return (unsigned int){:#x}".format(self.retval))
ok(m)
return False
class ChangePermissionBreakpoint(gdb.Breakpoint):
"""When hit, this temporary breakpoint will restore the original code, and position
$pc correctly."""
def __init__(self, loc, code, pc):
super().__init__(loc, gdb.BP_BREAKPOINT, internal=False)
self.original_code = code
self.original_pc = pc
return
def stop(self):
info("Restoring original context")
write_memory(self.original_pc, self.original_code, len(self.original_code))
info("Restoring $pc")
gdb.execute("set $pc = {:#x}".format(self.original_pc))
return True
class TraceMallocBreakpoint(gdb.Breakpoint):
"""Track allocations done with malloc() or calloc()."""
def __init__(self, name):
super().__init__(name, gdb.BP_BREAKPOINT, internal=True)
self.silent = True
self.name = name
return
def stop(self):
reset_all_caches()
_, size = current_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, name):
super().__init__(gdb.newest_frame(), internal=True)
self.size = size
self.name = name
self.silent = True
return
def stop(self):
global __heap_uaf_watchpoints__, __heap_freed_list__, __heap_allocated_list__
if self.return_value:
loc = int(self.return_value)
else:
loc = parse_address(current_arch.return_register)
size = self.size
ok("{} - {}({})={:#x}".format(Color.colorify("Heap-Analysis", "yellow bold"), self.name, size, loc))
check_heap_overlap = get_gef_setting("heap-analysis-helper.check_heap_overlap")
# pop from free-ed list if it was in it
if __heap_freed_list__:
idx = 0
for item in __heap_freed_list__:
addr = item[0]
if addr == loc:
__heap_freed_list__.remove(item)
continue
idx += 1
# pop from uaf watchlist
if __heap_uaf_watchpoints__:
idx = 0
for wp in __heap_uaf_watchpoints__:
wp_addr = wp.address
if loc <= wp_addr < loc + size:
__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 = get_memory_alignment()
for chunk_addr, _ in __heap_allocated_list__:
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("Reason {} new allocated chunk {:#x} (of size {:d}) overlaps in-used chunk {:#x} (of size {:#x})".format(RIGHT_ARROW, loc, size, chunk_addr, current_chunk_size))
msg.append("Writing {0:d} bytes from {1:#x} will reach chunk {2:#x}".format(offset, chunk_addr, loc))
msg.append("Payload example for chunk {1:#x} (to overwrite {0:#x} headers):".format(loc, chunk_addr))
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
__heap_allocated_list__.append(item)
return False
class TraceReallocBreakpoint(gdb.Breakpoint):
"""Track re-allocations done with realloc()."""
def __init__(self):
super().__init__("__libc_realloc", gdb.BP_BREAKPOINT, internal=True)
self.silent = True
return
def stop(self):
_, ptr = current_arch.get_ith_parameter(0)
_, size = current_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, size):
super().__init__(gdb.newest_frame(), internal=True)
self.ptr = ptr
self.size = size
self.silent = True
return
def stop(self):
global __heap_uaf_watchpoints__, __heap_freed_list__, __heap_allocated_list__
if self.return_value:
newloc = int(self.return_value)
else:
newloc = parse_address(current_arch.return_register)
if newloc != self:
ok("{} - realloc({:#x}, {})={}".format(Color.colorify("Heap-Analysis", "yellow bold"),
self.ptr, self.size,
Color.colorify("{:#x}".format(newloc), "green"),))
else:
ok("{} - realloc({:#x}, {})={}".format(Color.colorify("Heap-Analysis", "yellow bold"),
self.ptr, self.size,
Color.colorify("{:#x}".format(newloc), "red"),))
item = (newloc, self.size)
try:
# check if item was in alloc-ed list
idx = [x for x, y in __heap_allocated_list__].index(self.ptr)
# if so pop it out
item = __heap_allocated_list__.pop(idx)
except ValueError:
if is_debug():
warn("Chunk {:#x} was not in tracking list".format(self.ptr))
finally:
# add new item to alloc-ed list
__heap_allocated_list__.append(item)
return False
class TraceFreeBreakpoint(gdb.Breakpoint):
"""Track calls to free() and attempts to detect inconsistencies."""
def __init__(self):
super().__init__("__libc_free", gdb.BP_BREAKPOINT, internal=True)
self.silent = True
return
def stop(self):
reset_all_caches()
_, addr = current_arch.get_ith_parameter(0)
msg = []
check_free_null = get_gef_setting("heap-analysis-helper.check_free_null")
check_double_free = get_gef_setting("heap-analysis-helper.check_double_free")
check_weird_free = get_gef_setting("heap-analysis-helper.check_weird_free")
check_uaf = get_gef_setting("heap-analysis-helper.check_uaf")
ok("{} - free({:#x})".format(Color.colorify("Heap-Analysis", "yellow bold"), addr))
if addr == 0:
if check_free_null:
msg.append(Color.colorify("Heap-Analysis", "yellow bold"))
msg.append("Attempting to free(NULL) at {:#x}".format(current_arch.pc))
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 __heap_freed_list__]:
if check_double_free:
msg.append(Color.colorify("Heap-Analysis", "yellow bold"))
msg.append("Double-free detected {} free({:#x}) is called at {:#x} but is already in the free-ed list".format(RIGHT_ARROW, addr, current_arch.pc))
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 __heap_allocated_list__].index(addr)
item = __heap_allocated_list__.pop(idx)
except ValueError:
if check_weird_free:
msg.append(Color.colorify("Heap-Analysis", "yellow bold"))
msg.append("Heap inconsistency detected:")
msg.append("Attempting to free an unknown value: {:#x}".format(addr))
push_context_message("warn", "\n".join(msg))
return True
return False
# 2. add it to free-ed list
__heap_freed_list__.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):
super().__init__(gdb.newest_frame(), internal=True)
self.silent = True
self.addr = addr
return
def stop(self):
reset_all_caches()
wp = UafWatchpoint(self.addr)
__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):
super().__init__("*{:#x}".format(addr), gdb.BP_WATCHPOINT, internal=True)
self.address = addr
self.silent = True
self.enabled = True
return
def stop(self):
"""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(current_arch.pc, 2)
insn = gef_current_instruction(pc)
msg = []
msg.append(Color.colorify("Heap-Analysis", "yellow bold"))
msg.append("Possible Use-after-Free in '{:s}': pointer {:#x} was freed, but is attempted to be used at {:#x}"
.format(get_filepath(), self.address, pc))
msg.append("{:#x} {:s} {:s}".format(insn.address, 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):
super().__init__(location, gdb.BP_BREAKPOINT, internal=True, temporary=True)
self.silent = True
return
def stop(self):
reset_all_caches()
return True
class NamedBreakpoint(gdb.Breakpoint):
"""Breakpoint which shows a specified name, when hit."""
def __init__(self, location, name):
super().__init__(spec=location, type=gdb.BP_BREAKPOINT, internal=False, temporary=False)
self.name = name
self.loc = location
return
def stop(self):
reset_all_caches()
push_context_message("info", "Hit breakpoint {} ({})".format(self.loc, Color.colorify(self.name, "red bold")))
return True
#
# Context Panes
#
def register_external_context_pane(pane_name, display_pane_function, pane_title_function):
"""
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__.add_context_pane(pane_name, display_pane_function, pane_title_function)
#
# Commands
#
def register_external_command(obj):
"""Registering function for new GEF (sub-)command to GDB."""
global __commands__, __gef__
cls = obj.__class__
__commands__.append(cls)
__gef__.load(initial=False)
__gef__.doc.add_command_to_doc((cls._cmdline_, cls, None))
__gef__.doc.refresh()
return cls
def register_command(cls):
"""Decorator for registering new GEF (sub-)command to GDB."""
global __commands__
__commands__.append(cls)
return cls
def register_priority_command(cls):
"""Decorator for registering new command with priority, meaning that it must
loaded before the other generic commands."""
global __commands__
__commands__.insert(0, cls)
return cls
def register_function(cls):
"""Decorator for registering a new convenience function to GDB."""
global __functions__
__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, **kwargs):
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, from_tty):
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("Command '{:s}' failed to execute properly, reason: {:s}".format(self._cmdline_, str(e)))
return
def usage(self):
err("Syntax\n{}".format(self._syntax_))
return
@abc.abstractproperty
def _cmdline_(self): pass
@abc.abstractproperty
def _syntax_(self): pass
@abc.abstractproperty
def _example_(self): return ""
@abc.abstractmethod
def do_invoke(self, argv): pass
def pre_load(self): pass
def post_load(self): pass
def __get_setting_name(self, name):
def __sanitize_class_name(clsname):
if " " not in clsname:
return clsname
return "-".join(clsname.split())
class_name = __sanitize_class_name(self.__class__._cmdline_)
return "{:s}.{:s}".format(class_name, name)
@property
def settings(self):
"""Return the list of settings for this command."""
return [ x.split(".", 1)[1] for x in __config__
if x.startswith("{:s}.".format(self._cmdline_)) ]
def get_setting(self, name):
key = self.__get_setting_name(name)
setting = __config__[key]
return setting[1](setting[0])
def has_setting(self, name):
key = self.__get_setting_name(name)
return key in __config__
def update_setting(self, name, value, description=None):
"""Update the value of a setting without destroying the description"""
# 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)
__config__[key][0] = value
__config__[key][1] = type(value)
if description:
__config__[key][3] = description
get_gef_setting.cache_clear()
return
def add_setting(self, name, value, description=""):
# 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)
__config__[key] = [value, type(value), description]
get_gef_setting.cache_clear()
return
def del_setting(self, name):
key = self.__get_setting_name(name)
del __config__[key]
get_gef_setting.cache_clear()
return
def __set_repeat_count(self, argv, from_tty):
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
# Copy/paste this template for new command
# @register_command
# class TemplateCommand(GenericCommand):
# """TemplateCommand: description here will be seen in the help menu for the command."""
# _cmdline_ = "template-fake"
# _syntax_ = "{:s}".format(_cmdline_)
# _aliases_ = ["tpl-fk",]
# def __init__(self):
# super().__init__(complete=gdb.COMPLETE_FILENAME)
# return
# def do_invoke(self, argv):
# return
@register_command
class VersionCommand(GenericCommand):
"""Display GEF version info."""
_cmdline_ = "version"
_syntax_ = "{:s}".format(_cmdline_)
_example_ = "{:s}".format(_cmdline_)
def do_invoke(self, argv):
gef_fpath = os.path.abspath(os.path.expanduser(inspect.stack()[0][1]))
gef_dir = os.path.dirname(gef_fpath)
with open(gef_fpath, "rb") as f:
gef_hash = hashlib.sha1(f.read()).hexdigest()
if os.access("{}/.git".format(gef_dir), 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("GEF: rev:{} (Git - {})".format(ver, extra))
else:
gef_blob_hash = subprocess.check_output("git hash-object {}".format(gef_fpath), shell=True).decode().strip()
gef_print("GEF: (Standalone)")
gef_print("Blob Hash({}): {}".format(gef_fpath, gef_blob_hash))
gef_print("SHA1({}): {}".format(gef_fpath, gef_hash))
gef_print("GDB: {}".format(gdb.VERSION, ))
py_ver = "{:d}.{:d}".format(sys.version_info.major, sys.version_info.minor)
gef_print("GDB-Python: {}".format(py_ver, ))
if "full" in argv:
gef_print("Loaded commands: {}".format(", ".join(__gef__.loaded_command_names)))
return
@register_command
class PrintFormatCommand(GenericCommand):
"""Print bytes format in high level languages."""
format_matrix = {
8: (endian_str() + "B", "char", "db"),
16: (endian_str() + "H", "short", "dw"),
32: (endian_str() + "I", "int", "dd"),
64: (endian_str() + "Q", "long long", "dq"),
}
valid_formats = ["py", "c", "js", "asm"]
_cmdline_ = "print-format"
_aliases_ = ["pf",]
_syntax_ = """{} [--lang LANG] [--bitlen SIZE] [(--length,-l) LENGTH] [--clip] LOCATION
\t--lang LANG specifies the output format for programming language (available: {}, default 'py').
\t--bitlen SIZE specifies size of bit (possible values: {}, 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.""".format(_cmdline_, str(valid_formats), str(format_matrix.keys()))
_example_ = "{} --lang py -l 16 $rsp".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION)
return
@only_if_gdb_running
@parse_arguments({"location": "$pc", }, {("--length", "-l"): 256, "--bitlen": 0, "--lang": "py", "--clip": True,})
def do_invoke(self, argv, *args, **kwargs):
"""Default value for print-format command."""
args = kwargs["arguments"]
args.bitlen = args.bitlen or current_arch.ptrsize * 2
valid_bitlens = self.format_matrix.keys()
if args.bitlen not in valid_bitlens:
err("Size of bit must be in: {}".format(str(valid_bitlens)))
return
if args.lang not in self.valid_formats:
err("Language must be in: {}".format(str(self.valid_formats)))
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, read_memory(addr, size))[0]
data += [value]
sdata = ", ".join(map(hex, data))
if args.lang == "py":
out = "buf = [{}]".format(sdata)
elif args.lang == "c":
c_type = self.format_matrix[args.bitlen][1]
out = "unsigned {0} buf[{1}] = {{{2}}};".format(c_type, args.length, sdata)
elif args.lang == "js":
out = "var buf = [{}]".format(sdata)
elif args.lang == "asm":
asm_type = self.format_matrix[args.bitlen][2]
out = "buf {0} {1}".format(asm_type, sdata)
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_ = "{:s} (breakpoint|info|delete|run|attach|remote)".format(_cmdline_)
def __init__(self):
super().__init__(prefix=True)
return
def do_invoke(self, argv):
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_ = "{:s} OFFSET".format(_cmdline_)
@parse_arguments({"offset": ""}, {})
def do_invoke(self, argv, *args, **kwargs):
global __pie_counter__, __pie_breakpoints__
args = kwargs["arguments"]
if not args.offset:
self.usage()
return
addr = parse_address(args.offset)
self.set_pie_breakpoint(lambda base: "b *{}".format(base + addr), addr)
# When the process is already on, set real breakpoints immediately
if is_alive():
vmmap = get_process_maps()
base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0]
for bp_ins in __pie_breakpoints__.values():
bp_ins.instantiate(base_address)
return
@staticmethod
def set_pie_breakpoint(set_func, addr):
global __pie_counter__, __pie_breakpoints__
__pie_breakpoints__[__pie_counter__] = PieVirtualBreakpoint(set_func, __pie_counter__, addr)
__pie_counter__ += 1
return
@register_command
class PieInfoCommand(GenericCommand):
"""Display breakpoint info."""
_cmdline_ = "pie info"
_syntax_ = "{:s} BREAKPOINT".format(_cmdline_)
@parse_arguments({"breakpoints": [-1,]}, {})
def do_invoke(self, argv, *args, **kwargs):
global __pie_breakpoints__
args = kwargs["arguments"]
if args.breakpoints[0] == -1:
# No breakpoint info needed
bps = [__pie_breakpoints__[x] for x in __pie_breakpoints__]
else:
bps = [__pie_breakpoints__[x] for x in args.breakpoints]
lines = []
lines.append("VNum\tNum\tAddr")
lines += [
"{}\t{}\t{}".format(x.vbp_num, x.bp_num if x.bp_num else "N/A", 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_ = "{:s} [BREAKPOINT]".format(_cmdline_)
@parse_arguments({"breakpoints": [-1,]}, {})
def do_invoke(self, argv, *args, **kwargs):
global __pie_breakpoints__
args = kwargs["arguments"]
if args.breakpoints[0] == -1:
# no arg, delete all
to_delete = [__pie_breakpoints__[x] for x in __pie_breakpoints__]
self.delete_bp(to_delete)
else:
self.delete_bp([__pie_breakpoints__[x] for x in args.breakpoints])
return
@staticmethod
def delete_bp(breakpoints):
global __pie_breakpoints__
for bp in breakpoints:
# delete current real breakpoints if exists
if bp.bp_num:
gdb.execute("delete {}".format(bp.bp_num))
# delete virtual breakpoints
del __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):
global __pie_breakpoints__
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("The file '{}' is not executable.".format(fpath))
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("run {}".format(" ".join(argv)))
unhide_context()
gdb.execute("set stop-on-solib-events 0")
vmmap = get_process_maps()
base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0]
info("base address {}".format(hex(base_address)))
# modify all breakpoints
for bp_ins in __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_ = "{:s} PID".format(_cmdline_)
def do_invoke(self, argv):
try:
gdb.execute("attach {}".format(" ".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 = get_process_maps()
base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0]
for bp_ins in __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_ = "{:s} REMOTE".format(_cmdline_)
def do_invoke(self, argv):
try:
gdb.execute("gef-remote {}".format(" ".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 = get_process_maps()
base_address = [x.page_start for x in vmmap if x.realpath == get_filepath()][0]
for bp_ins in __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_ = "{0:s} EXPR\n{0:s} ADDRESS1 ADDRESS2".format(_cmdline_)
_example_ = "\n{0:s} $pc+1\n{0:s} 0x00007ffff7a10000 0x00007ffff7bce000".format(_cmdline_)
def do_invoke(self, argv):
argc = len(argv)
if argc == 1:
self.evaluate(argv)
return
if argc == 2:
self.distance(argv)
return
def evaluate(self, expr):
def show_as_int(i):
off = current_arch.ptrsize*8
def comp2_x(x): return "{:x}".format((x + (1 << off)) % (1 << off))
def comp2_b(x): return "{:b}".format((x + (1 << off)) % (1 << off))
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("{:d}".format(i))
gef_print("0x" + comp2_x(res))
gef_print("0b" + comp2_b(res))
gef_print("{}".format(binascii.unhexlify(s_i)))
gef_print("{}".format(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("{:d}".format(xp))
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("{}".format(res))
except SyntaxError:
gef_print(" ".join(parsed_expr))
return
def distance(self, args):
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("{}".format(abs(x - y)))
except ValueError:
warn("Distance requires 2 numbers: {} 0 0xffff".format(self._cmdline_))
return
@register_command
class CanaryCommand(GenericCommand):
"""Shows the canary value of the current process. Apply the techique detailed in
https://www.elttam.com.au/blog/playing-with-canaries/ to show the canary."""
_cmdline_ = "canary"
_syntax_ = _cmdline_
@only_if_gdb_running
def do_invoke(self, argv):
self.dont_repeat()
has_canary = checksec(get_filepath())["Canary"]
if not has_canary:
warn("This binary was not compiled with SSP.")
return
res = gef_read_canary()
if not res:
err("Failed to get the canary")
return
canary, location = res
info("Found AT_RANDOM at {:#x}, reading {} bytes".format(location, current_arch.ptrsize))
info("The canary of process {} is {:#x}".format(get_pid(), canary))
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):
super().__init__(complete=gdb.COMPLETE_NONE)
return
@only_if_gdb_running
@only_if_gdb_target_local
def do_invoke(self, argv):
self.show_info_proc()
self.show_ancestor()
self.show_descendants()
self.show_fds()
self.show_connections()
return
def get_state_of(self, pid):
res = {}
with open("/proc/{}/status".format(pid), "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):
with open("/proc/{}/cmdline".format(pid), "r") as f:
return f.read().replace("\x00", "\x20").strip()
def get_process_path_of(self, pid):
return os.readlink("/proc/{}/exe".format(pid))
def get_children_pids(self, pid):
ps = which("ps")
cmd = [ps, "-o", "pid", "--ppid", "{}".format(pid), "--noheaders"]
try:
return [int(x) for x in gef_execute_external(cmd, as_list=True)]
except Exception:
return []
def show_info_proc(self):
info("Process Information")
pid = get_pid()
cmdline = self.get_cmdline_of(pid)
gef_print("\tPID {} {}".format(RIGHT_ARROW, pid))
gef_print("\tExecutable {} {}".format(RIGHT_ARROW, self.get_process_path_of(pid)))
gef_print("\tCommand line {} '{}'".format(RIGHT_ARROW, cmdline))
return
def show_ancestor(self):
info("Parent Process Information")
ppid = int(self.get_state_of(get_pid())["PPid"])
state = self.get_state_of(ppid)
cmdline = self.get_cmdline_of(ppid)
gef_print("\tParent PID {} {}".format(RIGHT_ARROW, state["Pid"]))
gef_print("\tCommand line {} '{}'".format(RIGHT_ARROW, cmdline))
return
def show_descendants(self):
info("Children Process Information")
children = self.get_children_pids(get_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("\tPID {} {} (Name: '{}', CmdLine: '{}')".format(RIGHT_ARROW,
pid,
self.get_process_path_of(pid),
self.get_cmdline_of(pid)))
return
def show_fds(self):
pid = get_pid()
path = "/proc/{:d}/fd".format(pid)
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("\t{:s} {:s} {:s}".format (fullpath, RIGHT_ARROW, os.readlink(fullpath)))
return
def list_sockets(self, pid):
sockets = []
path = "/proc/{:d}/fd".format(pid)
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):
ip, port = addr.split(":")
return socket.inet_ntoa(struct.pack("<I", int(ip, 16))), int(port, 16)
def show_connections(self):
# 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 = get_pid()
sockets = self.list_sockets(pid)
if not sockets:
gef_print("\tNo open connections")
return
entries = dict()
with open("/proc/{:d}/net/tcp".format(pid), "r") as tcp:
entries["TCP"] = [x.split() for x in tcp.readlines()[1:]]
with open("/proc/{:d}/net/udp".format(pid), "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("\t{}:{} {} {}:{} ({})".format(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_ = "{:s} [KEY [VALUE]]".format(_cmdline_)
def __init__(self, *args, **kwargs):
super().__init__(self._cmdline_)
self.add_setting("context_title_line", "gray", "Color of the borders in context window")
self.add_setting("context_title_message", "cyan", "Color of the title in context window")
self.add_setting("default_title_line", "gray", "Default color of borders")
self.add_setting("default_title_message", "cyan", "Default color of title")
self.add_setting("table_heading", "blue", "Color of the column headings to tables (e.g. vmmap)")
self.add_setting("old_context", "gray", "Color to use to show things such as code that is not immediately relevant")
self.add_setting("disassemble_current_instruction", "green", "Color to use to highlight the current $pc when disassembling")
self.add_setting("dereference_string", "yellow", "Color of dereferenced string")
self.add_setting("dereference_code", "gray", "Color of dereferenced code")
self.add_setting("dereference_base_address", "cyan", "Color of dereferenced address")
self.add_setting("dereference_register_value", "bold blue", "Color of dereferenced register")
self.add_setting("registers_register_name", "blue", "Color of the register name in the register window")
self.add_setting("registers_value_changed", "bold red", "Color of the changed register in the register window")
self.add_setting("address_stack", "pink", "Color to use when a stack address is found")
self.add_setting("address_heap", "green", "Color to use when a heap address is found")
self.add_setting("address_code", "red", "Color to use when a code address is found")
self.add_setting("source_current_line", "green", "Color to use for the current code line in the source window")
return
def do_invoke(self, args):
self.dont_repeat()
argc = len(args)
if argc == 0:
for setting in sorted(self.settings):
value = self.get_setting(setting)
value = Color.colorify(value, value)
gef_print("{:40s}: {:s}".format(setting, value))
return
setting = args[0]
if not self.has_setting(setting):
err("Invalid key")
return
if argc == 1:
value = self.get_setting(setting)
value = Color.colorify(value, value)
gef_print("{:40s}: {:s}".format(setting, value))
return
val = [x for x in args[1:] if x in Color.colors]
self.add_setting(setting, " ".join(val))
return
@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_ = "{:s} [list|edit <StructureName>|show <StructureName>]|<StructureName> 0xADDRESS]".format(_cmdline_)
def __init__(self):
super().__init__(prefix=True)
self.add_setting("struct_path", os.sep.join([get_gef_setting("gef.tempdir"), "structs"]), "Path to store/load the structure ctypes files")
self.add_setting("max_depth", 4, "Maximum level of recursion supported")
self.add_setting("structure_name", "bold blue", "Color of the structure name")
self.add_setting("structure_type", "bold red", "Color of the attribute type")
self.add_setting("structure_size", "green", "Color of the attribute size")
return
def do_invoke(self, argv):
argc = len(argv)
if argc == 0:
gdb.execute("pcustom list")
return
modname, structname = self.get_modulename_structname_from_arg(argv[0])
if argc == 1:
gdb.execute("pcustom show {}".format(structname))
else:
try:
address = parse_address(argv[1])
except gdb.error:
err("Failed to parse '{:s}'".format(argv[1]))
return
self.apply_structure_to_address(modname, structname, address)
return
def get_pcustom_absolute_root_path(self):
path = os.path.expanduser(get_gef_setting("pcustom.struct_path"))
path = os.path.realpath(path)
if not os.path.isdir(path):
raise RuntimeError("setting `struct_path` must be set correctly")
return path
def get_pcustom_filepath_for_structure(self, structure_name):
structure_files = self.enumerate_structures()
fpath = None
for fname in structure_files:
if structure_name in structure_files[fname]:
fpath = fname
break
if not fpath:
raise FileNotFoundError("no file for structure '{}'".format(structure_name))
return fpath
def is_valid_struct(self, structure_name):
structure_files = self.enumerate_structures()
all_structures = set()
for fname in structure_files:
all_structures |= structure_files[fname]
return structure_name in all_structures
def get_modulename_structname_from_arg(self, arg):
modname, structname = arg.split(":", 1) if ":" in arg else (arg, arg)
structname = structname.split(".", 1)[0] if "." in structname else structname
return (modname, structname)
def deserialize(self, struct, data):
length = min(len(data), ctypes.sizeof(struct))
ctypes.memmove(ctypes.addressof(struct), data, length)
return
def get_structure_class(self, modname, classname):
"""
Returns a tuple of (class, instance) if modname!classname exists
"""
_fpath = self.get_pcustom_filepath_for_structure(modname)
_mod = self.load_module(_fpath)
_class = getattr(_mod, classname)
return _class, _class()
@only_if_gdb_running
def apply_structure_to_address(self, mod_name, struct_name, addr, depth=0):
if not self.is_valid_struct(mod_name):
err("Invalid structure name '{:s}'".format(struct_name))
return
if depth >= self.get_setting("max_depth"):
warn("maximum recursion level reached")
return
try:
_class, _struct = self.get_structure_class(mod_name, struct_name)
data = read_memory(addr, ctypes.sizeof(_struct))
except gdb.MemoryError:
err("{}Cannot reach memory {:#x}".format(" " * depth, addr))
return
self.deserialize(_struct, data)
_regsize = get_memory_alignment()
for field in _struct._fields_:
_name, _type = field
_value = getattr(_struct, _name)
_offset = getattr(_class, _name).offset
if (_regsize == 4 and _type is ctypes.c_uint32) \
or (_regsize == 8 and _type is ctypes.c_uint64) \
or (_regsize == 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 = []
line += " " * depth
line += ("{:#x}+0x{:04x} {} : ".format(addr, _offset, _name)).ljust(40)
line += "{} ({})".format(_value, _type.__name__)
parsed_value = self.get_ctypes_value(_struct, _name, _value)
if parsed_value:
line += " {} {}".format(RIGHT_ARROW, parsed_value)
gef_print("".join(line))
if issubclass(_type, ctypes.Structure):
self.apply_structure_to_address(mod_name, _type.__name__, addr + _offset, depth + 1)
elif _type.__name__.startswith("LP_"): # hack
__sub_type_name = _type.__name__.replace("LP_", "")
__deref = u64( read_memory(addr + _offset, 8) )
self.apply_structure_to_address(mod_name, __sub_type_name, __deref, depth + 1)
return
def get_ctypes_value(self, struct, item, value):
if not hasattr(struct, "_values_"): return ""
values_list = getattr(struct, "_values_")
default = ""
for name, values in values_list:
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:
err("Error while trying to obtain values from _values_[\"{}\"]".format(name))
return default
def enumerate_structure_files(self):
"""
Return a list of all the files in the pcustom directory
"""
module_files = []
root = self.get_pcustom_absolute_root_path()
for filen in os.listdir(root):
name, ext = os.path.splitext(filen)
if ext != ".py": continue
if name == "__init__": continue
fpath = os.sep.join([root, filen])
module_files.append( os.path.realpath(fpath) )
return module_files
def enumerate_structures(self):
"""
Return a hash of all the structures, with the key set the to filepath
"""
structures = {}
files = self.enumerate_structure_files()
for module_path in files:
module = self.load_module(module_path)
structures[module_path] = self.enumerate_structures_from_module(module)
return structures
def load_module(self, file_path):
"""Load a custom module, and return it"""
module_name = file_path.split(os.sep)[-1].replace(".py", "")
spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module
def enumerate_structures_from_module(self, module):
_invalid = {"BigEndianStructure", "LittleEndianStructure", "Structure"}
_structs = {x for x in dir(module) \
if inspect.isclass(getattr(module, x)) \
and issubclass(getattr(module, x), ctypes.Structure)}
return _structs - _invalid
@register_command
class PCustomListCommand(PCustomCommand):
"""PCustom: list available structures"""
_cmdline_ = "pcustom list"
_syntax_ = "{:s}".format(_cmdline_)
def __init__(self):
super().__init__()
return
def do_invoke(self, argv):
self.__list_custom_structures()
return
def __list_custom_structures(self):
"""Dump the list of all the structures and their respective."""
path = self.get_pcustom_absolute_root_path()
info("Listing custom structures from '{:s}'".format(path))
structures = self.enumerate_structures()
struct_color = get_gef_setting("pcustom.structure_type")
filename_color = get_gef_setting("pcustom.structure_name")
for filename in structures:
__modules = ", ".join([Color.colorify(x, struct_color) for x in structures[filename]])
__filename = Color.colorify(filename, filename_color)
gef_print("{:s} {:s} ({:s})".format(RIGHT_ARROW, __filename, __modules))
return
@register_command
class PCustomShowCommand(PCustomCommand):
"""PCustom: show the content of a given structure"""
_cmdline_ = "pcustom show"
_syntax_ = "{:s} StructureName".format(_cmdline_)
__aliases__ = ["pcustom create", "pcustom update"]
def __init__(self):
super().__init__()
return
def do_invoke(self, argv):
if len(argv) == 0:
self.usage()
return
modname, structname = self.get_modulename_structname_from_arg(argv[0])
self.__dump_structure(modname, structname)
return
def __dump_structure(self, mod_name, struct_name):
# If it's a builtin or defined in the ELF use gdb's `ptype`
try:
gdb.execute("ptype struct {:s}".format(struct_name))
return
except gdb.error:
pass
self.__dump_custom_structure(mod_name, struct_name)
return
def __dump_custom_structure(self, mod_name, struct_name):
if not self.is_valid_struct(mod_name):
err("Invalid structure name '{:s}'".format(struct_name))
return
_class, _struct = self.get_structure_class(mod_name, struct_name)
for _name, _type in _struct._fields_:
_size = ctypes.sizeof(_type)
__name = Color.colorify(_name, get_gef_setting("pcustom.structure_name"))
__type = Color.colorify(_type.__name__, get_gef_setting("pcustom.structure_type"))
__size = Color.colorify(hex(_size), get_gef_setting("pcustom.structure_size"))
__offset = Color.boldify("{:04x}".format(getattr(_class, _name).offset))
gef_print("{:s} {:32s} {:16s} /* size={:s} */".format(__offset, __name, __type, __size))
return
@register_command
class PCustomEditCommand(PCustomCommand):
"""PCustom: edit the content of a given structure"""
_cmdline_ = "pcustom edit"
_syntax_ = "{:s} StructureName".format(_cmdline_)
__aliases__ = ["pcustom create", "pcustom new", "pcustom update"]
def __init__(self):
super().__init__()
return
def do_invoke(self, argv):
if len(argv) == 0:
self.usage()
return
modname, structname = self.get_modulename_structname_from_arg(argv[0])
self.__create_or_edit_structure(modname, structname)
return
def __create_or_edit_structure(self, mod_name, struct_name):
root = self.get_pcustom_absolute_root_path()
if root is None:
err("Invalid struct path")
return
try:
fullname = self.get_pcustom_filepath_for_structure(mod_name)
info("Editing '{:s}'".format(fullname))
except FileNotFoundError:
fullname = os.sep.join([root, struct_name + ".py"])
ok("Creating '{:s}' from template".format(fullname))
self.__create_new_structure_template(struct_name, fullname)
cmd = (os.getenv("EDITOR") or "nano").split()
cmd.append(fullname)
return subprocess.call(cmd)
def __create_new_structure_template(self, structname, fullname):
template = [
"from ctypes import *",
"",
"class ", structname, "(Structure):",
" _fields_ = []",
"",
" _values_ = []",
""
]
with open(fullname, "w") as f:
f.write(os.sep.join(template))
return
@register_command
class ChangeFdCommand(GenericCommand):
"""ChangeFdCommand: redirect file descriptor during runtime."""
_cmdline_ = "hijack-fd"
_syntax_ = "{:s} FD_NUM NEW_OUTPUT".format(_cmdline_)
_example_ = "{:s} 2 /tmp/stderr_output.txt".format(_cmdline_)
@only_if_gdb_running
@only_if_gdb_target_local
def do_invoke(self, argv):
if len(argv) != 2:
self.usage()
return
if not os.access("/proc/{:d}/fd/{:s}".format(get_pid(), 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("""call (int)socket({}, {}, 0)""".format(AF_INET, SOCK_STREAM), 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 = get_process_maps()
stack_addr = [entry.page_start for entry in vmmap if entry.path == "[stack]"][0]
original_contents = read_memory(stack_addr, 8)
write_memory(stack_addr, "\x02\x00", 2)
write_memory(stack_addr + 0x2, struct.pack("<H", socket.htons(port)), 2)
write_memory(stack_addr + 0x4, socket.inet_aton(address), 4)
info("Trying to connect to {}".format(new_output))
res = gdb.execute("""call (int)connect({}, {}, {})""".format(new_fd, stack_addr, 16), to_string=True)
# recover stack state
write_memory(stack_addr, original_contents, 8)
res = self.get_fd_from_result(res)
if res == -1:
err("Failed to connect to {}:{}".format(address, port))
return
info("Connected to {}".format(new_output))
else:
res = gdb.execute("""call (int)open("{:s}", 66, 0666)""".format(new_output), to_string=True)
new_fd = self.get_fd_from_result(res)
info("Opened '{:s}' as fd #{:d}".format(new_output, new_fd))
gdb.execute("""call (int)dup2({:d}, {:d})""".format(new_fd, old_fd), to_string=True)
info("Duplicated fd #{:d}{:s}#{:d}".format(new_fd, RIGHT_ARROW, old_fd))
gdb.execute("""call (int)close({:d})""".format(new_fd), to_string=True)
info("Closed extra fd #{:d}".format(new_fd))
ok("Success")
return
def get_fd_from_result(self, res):
# Output example: $1 = 3
res = int(res.split()[2], 0)
res = gdb.execute("""p/d {}""".format(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_ = "{:s} METHOD [ARGS]".format(_cmdline_)
_aliases_ = ["binaryninja-interact", "bn", "binja"]
_example_ = "\n{0:s} Jump $pc\n{0:s} SetColor $pc ff00ff".format(_cmdline_)
def __init__(self):
super().__init__(prefix=False)
host, port = "127.0.0.1", 1337
self.add_setting("host", host, "IP address to use connect to IDA/Binary Ninja script")
self.add_setting("port", port, "Port to use connect to IDA/Binary Ninja script")
self.add_setting("sync_cursor", False, "Enable real-time $pc synchronisation")
self.sock = None
self.version = ("", "")
self.old_bps = set()
return
def is_target_alive(self, host, port):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(1)
s.connect((host, port))
s.close()
except socket.error:
return False
return True
def connect(self, host=None, port=None):
"""Connect to the XML-RPC service."""
host = host or self.get_setting("host")
port = port or self.get_setting("port")
try:
sock = xmlrpclib.ServerProxy("http://{:s}:{:d}".format(host, port))
gef_on_stop_hook(ida_synchronize_handler)
gef_on_continue_hook(ida_synchronize_handler)
self.version = sock.version()
except ConnectionRefusedError:
err("Failed to connect to '{:s}:{:d}'".format(host, port))
sock = None
self.sock = sock
return
def disconnect(self):
gef_on_stop_unhook(ida_synchronize_handler)
gef_on_continue_unhook(ida_synchronize_handler)
self.sock = None
return
@obsolete_command
def do_invoke(self, argv):
def parsed_arglist(arglist):
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("{:#x}".format(argval,))
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("Enhancing {:s} with {:s} (SDK {:s})".format(Color.greenify("gef"),
Color.redify(self.version[0]),
Color.yellowify(self.version[1])))
return
if not is_alive():
main_base_address = main_end_address = 0
else:
vmmap = get_process_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.get_setting("sync_cursor") is True:
jump = getattr(self.sock, "jump")
jump(hex(current_arch.pc-main_base_address),)
except socket.error:
self.disconnect()
return
def synchronize(self):
"""Submit all active breakpoint addresses to IDA/BN."""
pc = current_arch.pc
vmmap = get_process_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("{:#x}".format(pc-base_address), 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("*{:#x}".format(location), 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=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):
if self.version[0] != "IDA Pro":
return
path = get_gef_setting("pcustom.struct_path")
if path is None:
return
if not os.path.isdir(path):
gef_makedirs(path)
for struct_name in structs:
fullpath = os.path.join(path, "{}.py".format(struct_name))
with open(fullpath, "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 = "c_byte * {}".format(size)
m = ' (\"{}\", {}),\n'.format(name, csize)
f.write(m)
f.write("]\n")
ok("Success, {:d} structure{:s} imported".format(len(structs), "s" if len(structs)>1 else ""))
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_ = "{:s} HAYSTACK NEEDLE".format(_cmdline_)
_aliases_ = ["lookup",]
_example_ = "\n{0:s} stack libc".format(_cmdline_)
@only_if_gdb_running
def do_invoke(self, argv):
if len(argv) != 2:
self.usage()
return
haystack = argv[0]
needle = argv[1]
info("Searching for addresses in '{:s}' that point to '{:s}'"
.format(Color.yellowify(haystack), 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 get_process_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 = current_arch.ptrsize
unpack = u32 if step == 4 else u64
for hstart, hend, hname in haystack_sections:
try:
mem = read_memory(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("{:s}: {:s}".format(name, deref))
else:
gef_print(" {:s}".format(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_ = "{:s} PATTERN [little|big] [section]".format(_cmdline_)
_aliases_ = ["grep", "xref"]
_example_ = "\n{0:s} AAAAAAAA\n{0:s} 0x555555554000 little stack\n{0:s} AAAA 0x600000-0x601000".format(_cmdline_)
def print_section(self, section):
title = "In "
if section.path:
title += "'{}'".format(Color.blueify(section.path))
title += "({:#x}-{:#x})".format(section.page_start, section.page_end)
title += ", permission={}".format(section.permission)
ok(title)
return
def print_loc(self, loc):
gef_print(""" {:#x} - {:#x} {} "{}" """.format(loc[0], loc[1], RIGHT_ARROW, Color.pinkify(loc[2]),))
return
def search_pattern_by_address(self, pattern, start_address, end_address):
"""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 = read_memory(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 = 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, section_name):
"""Search a pattern within the whole userland memory."""
for section in get_process_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):
argc = len(argv)
if argc < 1:
self.usage()
return
pattern = argv[0]
endian = get_endian()
if argc >= 2:
if argv[1].lower() == "big": endian = Elf.BIG_ENDIAN
elif argv[1].lower() == "little": endian = Elf.LITTLE_ENDIAN
if is_hex(pattern):
if endian == Elf.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("Searching '{:s}' in {:s}".format(Color.yellowify(pattern), 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("Searching '{:s}' in memory".format(Color.yellowify(pattern)))
self.search_pattern(pattern, "")
return
@register_command
class FlagsCommand(GenericCommand):
"""Edit flags in a human friendly way."""
_cmdline_ = "edit-flags"
_syntax_ = "{:s} [(+|-|~)FLAGNAME ...]".format(_cmdline_)
_aliases_ = ["flags",]
_example_ = "\n{0:s}\n{0:s} +zero # sets ZERO flag".format(_cmdline_)
def do_invoke(self, argv):
for flag in argv:
if len(flag) < 2:
continue
action = flag[0]
name = flag[1:].lower()
if action not in ("+", "-", "~"):
err("Invalid action for flag '{:s}'".format(flag))
continue
if name not in current_arch.flags_table.values():
err("Invalid flag name '{:s}'".format(flag[1:]))
continue
for off in current_arch.flags_table:
if current_arch.flags_table[off] == name:
old_flag = get_register(current_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("set ({:s}) = {:#x}".format(current_arch.flag_register, new_flags))
gef_print(current_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_ = "{:s} 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".format(_cmdline_)
_aliases_ = ["mprotect"]
_example_ = "{:s} $sp 7".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION)
return
def pre_load(self):
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):
if len(argv) not in (1, 2):
err("Incorrect syntax")
self.usage()
return
if len(argv) == 2:
perm = int(argv[1])
else:
perm = Permission.READ | Permission.WRITE | Permission.EXECUTE
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 = current_arch.pc
info("Generating sys_mprotect({:#x}, {:#x}, '{:s}') stub for arch {:s}"
.format(sect.page_start, size, str(Permission(value=perm)), 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 = read_memory(original_pc, len(stub))
bp_loc = "*{:#x}".format(original_pc + len(stub))
info("Setting a restore breakpoint at {:s}".format(bp_loc))
ChangePermissionBreakpoint(bp_loc, original_code, original_pc)
info("Overwriting current memory at {:#x} ({:d} bytes)".format(loc, len(stub)))
write_memory(original_pc, stub, len(stub))
info("Resuming execution")
gdb.execute("continue")
return
def get_stub_by_arch(self, addr, size, perm):
code = current_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_ = """{:s} [--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`
""".format(_cmdline_)
_aliases_ = ["emulate",]
_example_ = "{0:s} --start $pc 10 --output-file /tmp/my-gef-emulation.py".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION)
self.add_setting("verbose", False, "Set unicorn-engine in verbose mode")
self.add_setting("show_disassembly", False, "Show every instruction executed")
return
def pre_load(self):
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, *args, **kwargs):
args = kwargs["arguments"]
start_address = parse_address(str(args.start or current_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, nb):
dis = list(gef_disassemble(start_addr, nb + 1))
last_insn = dis[-1]
return last_insn.address
def run_unicorn(self, start_insn_addr, end_insn_addr, *args, **kwargs):
verbose = self.get_setting("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 = get_filename()
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(["'%s': %s" % (k.strip(), unicorn_registers[k]) for k in unicorn_registers]),
verbose="True" if verbose else "False",
syscall_reg=current_arch.syscall_register,
cs_arch=cs_arch, cs_mode=cs_mode,
ptrsize=current_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 current_arch.all_registers:
gregval = get_register(r)
content += " emu.reg_write({}, {:#x})\n".format(unicorn_registers[r], gregval)
vmmap = get_process_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 += " # Mapping {}: {:#x}-{:#x}\n".format(sect.path, page_start, page_end)
content += " emu.mem_map({:#x}, {:#x}, {})\n".format(page_start, size, oct(perm.value))
if perm & Permission.READ:
code = read_memory(page_start, size)
loc = "/tmp/gef-{}-{:#x}.raw".format(fname, page_start)
with open(loc, "wb") as f:
f.write(bytes(code))
content += " emu.mem_write({:#x}, open('{}', 'rb').read())\n".format(page_start, loc)
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("Unicorn script generated as '{}'".format(tmp_filename))
os.chmod(tmp_filename, 0o700)
if skip_emulation:
return
ok("Starting emulation: {:#x} {} {:#x}".format(start_insn_addr, RIGHT_ARROW, end_insn_addr))
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_ = "{:s} [OPTIONS] TARGET".format(_cmdline_)
_example_ = "\n{0:s} --pid 6789 localhost:1234"\
"\n{0:s} --qemu-mode localhost:4444 # when using qemu-user".format(_cmdline_)
def __init__(self):
super().__init__(prefix=False)
self.handler_connected = False
self.add_setting("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, argv, *args, **kwargs):
global __gef_remote__
if __gef_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 get_pid()
if args.is_extended_remote:
ok("Attaching to {:d}".format(pid))
hide_context()
gdb.execute("attach {:d}".format(pid))
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 = get_process_maps()
success = 0
for sect in vmmap:
if sect.path.startswith("/"):
_file = download_file(sect.path)
if _file is None:
err("Failed to download {:s}".format(sect.path))
else:
success += 1
ok("Downloaded {:d} files".format(success))
elif args.download_lib:
_file = download_file(args.download_lib)
if _file is None:
err("Failed to download remote file")
return
ok("Download success: {:s} {:s} {:s}".format(args.download_lib, RIGHT_ARROW, _file))
if args.update_solib:
self.refresh_shared_library_path()
# refresh the architecture setting
set_arch()
__gef_remote__ = pid
return
def new_objfile_handler(self, event):
"""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("Download success: {:s} {:s} {:s}".format(remote_lib, RIGHT_ARROW, local_lib))
return
def setup_remote_environment(self, pid, update_solib=False):
"""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("Failed to load memory map of '{:s}'".format(i))
return
exepath = get_path_from_info_proc()
infos["exe"] = download_file("/proc/{:d}/exe".format(pid), 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([get_gef_setting("gef.tempdir"), str(get_pid())])
# gdb.execute("file {:s}".format(infos["exe"]))
self.add_setting("root", directory, "Path to store the remote data")
ok("Remote information loaded to temporary path '{:s}'".format(directory))
return
def connect_target(self, target, is_extended_remote):
"""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 = "target {} {}".format("extended-remote" if is_extended_remote else "remote", target)
gdb.execute(cmd)
ok("Connected to '{}'".format(target))
ret = True
except Exception as e:
err("Failed to connect to {:s}: {:s}".format(target, str(e)))
ret = False
unhide_context()
return ret
def load_from_remote_proc(self, pid, info):
"""Download one item from /proc/pid."""
remote_name = "/proc/{:d}/{:s}".format(pid, info)
return download_file(remote_name, use_cache=False)
def refresh_shared_library_path(self):
dirs = [r for r, d, f in os.walk(self.get_setting("root"))]
path = ":".join(dirs)
gdb.execute("set solib-search-path {:s}".format(path,))
return
def usage(self):
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):
global current_arch, current_elf, __gef_qemu_mode__
reset_all_caches()
arch = get_arch()
current_elf = Elf(minimalist=True)
if arch.startswith("arm"):
current_elf.e_machine = Elf.ARM
current_arch = ARM()
elif arch.startswith("aarch64"):
current_elf.e_machine = Elf.AARCH64
current_arch = AARCH64()
elif arch.startswith("i386:intel"):
current_elf.e_machine = Elf.X86_32
current_arch = X86()
elif arch.startswith("i386:x86-64"):
current_elf.e_machine = Elf.X86_64
current_elf.e_class = Elf.ELF_64_BITS
current_arch = X86_64()
elif arch.startswith("mips"):
current_elf.e_machine = Elf.MIPS
current_arch = MIPS()
elif arch.startswith("powerpc"):
current_elf.e_machine = Elf.POWERPC
current_arch = PowerPC()
elif arch.startswith("sparc"):
current_elf.e_machine = Elf.SPARC
current_arch = SPARC()
else:
raise RuntimeError("unsupported architecture: {}".format(arch))
ok("Setting Qemu-user stub for '{}' (memory mapping may be wrong)".format(current_arch.arch))
hide_context()
gdb.execute("target remote {}".format(target))
unhide_context()
if get_pid() == 1 and "ENABLE=1" in gdb.execute("maintenance packet Qqemu.sstepbits", to_string=True, from_tty=False):
__gef_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")
get_process_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_ = """{:s} [LOCATION] [--nb NUM_BYTES]
LOCATION\taddress/symbol to patch
--nb NUM_BYTES\tInstead of writing one instruction, patch the specified number of bytes""".format(_cmdline_)
_example_ = "{:s} $pc".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION)
return
def get_insn_size(self, addr):
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, argv, *args, **kwargs):
args = kwargs["arguments"]
address = parse_address(args.address) if args.address else current_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, num_bytes):
size = self.get_insn_size(loc) if num_bytes == 0 else num_bytes
nops = current_arch.nop_insn
if len(nops) > size:
m = "Cannot patch instruction at {:#x} (nop_size is:{:d},insn_size is:{:d})".format(loc, len(nops), size)
err(m)
return
while len(nops) < size:
nops += current_arch.nop_insn
if len(nops) != size:
err("Cannot patch instruction at {:#x} (nop instruction does not evenly fit in requested size)"
.format(loc))
return
ok("Patching {:d} bytes from {:s}".format(size, format_address(loc)))
write_memory(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_ = """{:s} [--retval RETVAL] [address]
\taddress\taddress/symbol to stub out
\t--retval RETVAL\tSet the return value""".format(_cmdline_)
_example_ = "{:s} --retval 0 fork".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION)
return
@only_if_gdb_running
@parse_arguments({"address": ""}, {("-r", "--retval"): 0})
def do_invoke(self, argv, *args, **kwargs):
args = kwargs["arguments"]
loc = args.address if args.address else "*{:#x}".format(current_arch.pc)
StubBreakpoint(loc, args.retval)
return
@register_command
class CapstoneDisassembleCommand(GenericCommand):
"""Use capstone disassembly framework to disassemble code."""
_cmdline_ = "capstone-disassemble"
_syntax_ = "{:s} [-h] [--show-opcodes] [--length LENGTH] [LOCATION]".format(_cmdline_)
_aliases_ = ["cs-dis"]
_example_ = "{:s} --length 50 $pc".format(_cmdline_)
def pre_load(self):
try:
__import__("capstone")
except ImportError:
msg = "Missing `capstone` package for Python. Install with `pip install capstone`."
raise ImportWarning(msg)
return
def __init__(self):
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, argv, *args, **kwargs):
args = kwargs["arguments"]
show_opcodes = args.show_opcodes
length = args.length or get_gef_setting("context.nb_lines_code")
location = parse_address(args.location)
if not location:
info("Can't find address for {}".format(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 = "{{:{}o}}".format(opcodes_len) if show_opcodes else "{}"
text_insn = insn_fmt.format(insn)
msg = ""
if insn.address == current_arch.pc:
msg = Color.colorify("{} {}".format(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 = "{} {}".format(" " * 5, text_insn)
gef_print(msg)
return
def capstone_analyze_pc(self, insn, nb_insn):
if current_arch.is_conditional_branch(insn):
is_taken, reason = current_arch.is_branch_taken(insn)
if is_taken:
reason = "[Reason: {:s}]".format(reason) if reason else ""
msg = Color.colorify("\tTAKEN {:s}".format(reason), "bold green")
else:
reason = "[Reason: !({:s})]".format(reason) if reason else ""
msg = Color.colorify("\tNOT taken {:s}".format(reason), "bold red")
return (is_taken, msg)
if current_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(" {} {}".format(DOWN_ARROW if i == 0 else " ", str(new_insn)))
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_ = "{:s} (chunk|chunks|bins|arenas|set-arena)".format(_cmdline_)
def __init__(self):
super().__init__(prefix=True)
return
@only_if_gdb_running
def do_invoke(self, argv):
self.usage()
return
@register_command
class GlibcHeapSetArenaCommand(GenericCommand):
"""Display information on a heap chunk."""
_cmdline_ = "heap set-arena"
_syntax_ = "{:s} address".format(_cmdline_)
_example_ = "{:s} 0x001337001337".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION)
return
@only_if_gdb_running
def do_invoke(self, argv):
global __gef_current_arena__
if not argv:
ok("Current arena set to: '{}'".format(__gef_current_arena__))
return
new_arena = safe_parse_and_eval(argv[0])
if new_arena is None:
err("Invalid address")
return
if argv[0].startswith("0x"):
new_arena = Address(value=to_unsigned_long(new_arena))
if new_arena is None or not new_arena.valid:
err("Invalid address")
return
__gef_current_arena__ = "*{:s}".format(format_address(new_arena.value))
else:
__gef_current_arena__ = argv[0]
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, argv):
arenas = get_glibc_arenas()
for arena in 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_ = "{:s} [-h] [--allow-unaligned] address".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION)
return
@parse_arguments({"address": ""}, {"--allow-unaligned": True})
@only_if_gdb_running
def do_invoke(self, *args, **kwargs):
args = kwargs["arguments"]
if not args.address:
err("Missing chunk address")
self.usage()
return
if get_glibc_arena() is None:
return
addr = parse_address(args.address)
chunk = GlibcChunk(addr, allow_unaligned=args.allow_unaligned)
gef_print(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_ = "{0} [-h] [--all] [--allow-unaligned] [arena_address]".format(_cmdline_)
_example_ = "\n{0}\n{0} 0x555555775000".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION)
self.add_setting("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, *args, **kwargs):
args = kwargs["arguments"]
arenas = get_glibc_arenas(addr=args.arena_address, get_all=args.all)
for arena in arenas:
self.dump_chunks_arena(arena, print_arena=args.all, allow_unaligned=args.allow_unaligned)
def dump_chunks_arena(self, arena, print_arena=False, allow_unaligned=False):
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, until=None, top=None, allow_unaligned=False):
nb = self.get_setting("peek_nb_byte")
current_chunk = GlibcChunk(start, from_base=True, allow_unaligned=allow_unaligned)
while True:
if current_chunk.base_address == top:
gef_print("{} {} {}".format(str(current_chunk), LEFT_ARROW, Color.greenify("top chunk")))
break
if current_chunk.size == 0:
break
line = str(current_chunk)
if nb:
line += "\n [{}]".format(hexdump(read_memory(current_chunk.data_address, nb), nb, base=current_chunk.data_address))
gef_print(line)
next_chunk_addr = current_chunk.get_next_chunk_addr()
if until and next_chunk_addr >= until:
break
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
@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_ = "{:s} [{:s}]".format(_cmdline_, "|".join(_bin_types_))
def __init__(self):
super().__init__(prefix=True, complete=gdb.COMPLETE_LOCATION)
return
@only_if_gdb_running
def do_invoke(self, argv):
if not argv:
for bin_t in GlibcHeapBinsCommand._bin_types_:
gdb.execute("heap bins {:s}".format(bin_t))
return
bin_t = argv[0]
if bin_t not in GlibcHeapBinsCommand._bin_types_:
self.usage()
return
gdb.execute("heap bins {}".format(bin_t))
return
@staticmethod
def pprint_bin(arena_addr, index, _type=""):
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("{}bins[{:d}]: fw={:#x}, bk={:#x}".format(_type, index, fw, bk))
m = []
while fw != head:
chunk = GlibcChunk(fw, from_base=True)
m.append("{:s} {:s}".format(RIGHT_ARROW, str(chunk)))
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_ = "{:s} [all] [thread_ids...]".format(_cmdline_)
TCACHE_MAX_BINS = 0x40
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION)
return
@only_if_gdb_running
def do_invoke(self, argv):
# 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("Uninitialized tcache for thread {:d}".format(thread.num))
continue
gef_print(titlify("Tcachebins for thread {:d}".format(thread.num)))
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("{:s} {:s} ".format(LEFT_ARROW, str(chunk)))
if chunk.data_address in chunks:
msg.append("{:s} [loop detected]".format(RIGHT_ARROW))
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("{:s} [Corrupted chunk at {:#x}]".format(LEFT_ARROW, chunk.data_address))
break
if msg:
tcache_empty = False
gef_print("Tcachebins[idx={:d}, size={:#x}] count={:d} ".format(i, (i+2)*(current_arch.ptrsize)*2, count), end="")
gef_print("".join(msg))
if tcache_empty:
gef_print("All tcachebins are empty")
current_thread.switch()
return
@staticmethod
def find_tcache():
"""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 = HeapBaseFunction.heap_base()
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):
"""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("Invalid thread id {:s}".format(tid))
continue
if tid in existing_tids:
valid_tids.add(tid)
else:
err("Unknown thread {}".format(tid))
return list(valid_tids)
@staticmethod
def tcachebin(tcache_base, i):
"""Return the head chunk in tcache[i] and the number of chunks in the bin."""
assert i < GlibcHeapTcachebinsCommand.TCACHE_MAX_BINS, "index should be less then TCACHE_MAX_BINS"
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 * current_arch.ptrsize)
if tcache_chunk.usable_size < new_tcache_min_size:
tcache_count_size = 1
count = ord(read_memory(tcache_base + tcache_count_size*i, 1))
else:
tcache_count_size = 2
count = u16(read_memory(tcache_base + tcache_count_size*i, 2))
chunk = dereference(tcache_base + tcache_count_size*GlibcHeapTcachebinsCommand.TCACHE_MAX_BINS + i*current_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_ = "{:s} [ARENA_ADDRESS]".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION)
return
@only_if_gdb_running
def do_invoke(self, argv):
def fastbin_index(sz):
return (sz >> 4) - 2 if SIZE_SZ == 8 else (sz >> 3) - 2
SIZE_SZ = current_arch.ptrsize
MAX_FAST_SIZE = 80 * SIZE_SZ // 4
NFASTBINS = fastbin_index(MAX_FAST_SIZE) - 1
arena = GlibcArena("*{:s}".format(argv[0])) if len(argv) == 1 else get_glibc_arena()
if arena is None:
err("Invalid Glibc arena")
return
gef_print(titlify("Fastbins for arena {:#x}".format(int(arena))))
for i in range(NFASTBINS):
gef_print("Fastbins[idx={:d}, size={:#x}] ".format(i, (i+2)*SIZE_SZ*2), end="")
chunk = arena.fastbin(i)
chunks = set()
while True:
if chunk is None:
gef_print("0x00", end="")
break
try:
gef_print("{:s} {:s} ".format(LEFT_ARROW, str(chunk)), end="")
if chunk.data_address in chunks:
gef_print("{:s} [loop detected]".format(RIGHT_ARROW), 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("{:s} [Corrupted chunk at {:#x}]".format(LEFT_ARROW, chunk.data_address), 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_ = "{:s} [ARENA_ADDRESS]".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION)
return
@only_if_gdb_running
def do_invoke(self, argv):
if get_glibc_arena() is None:
err("Invalid Glibc arena")
return
arena_addr = "*{:s}".format(argv[0]) if len(argv) == 1 else __gef_current_arena__
gef_print(titlify("Unsorted Bin for arena '{:s}'".format(arena_addr)))
nb_chunk = GlibcHeapBinsCommand.pprint_bin(arena_addr, 0, "unsorted_")
if nb_chunk >= 0:
info("Found {:d} chunks in unsorted bin.".format(nb_chunk))
return
@register_command
class GlibcHeapSmallBinsCommand(GenericCommand):
"""Convenience command for viewing small bins."""
_cmdline_ = "heap bins small"
_syntax_ = "{:s} [ARENA_ADDRESS]".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION)
return
@only_if_gdb_running
def do_invoke(self, argv):
if get_glibc_arena() is None:
err("Invalid Glibc arena")
return
arena_addr = "*{:s}".format(argv[0]) if len(argv) == 1 else __gef_current_arena__
gef_print(titlify("Small Bins for arena '{:s}'".format(arena_addr)))
bins = {}
for i in range(1, 63):
nb_chunk = GlibcHeapBinsCommand.pprint_bin(arena_addr, i, "small_")
if nb_chunk < 0:
break
if nb_chunk > 0:
bins[i] = nb_chunk
info("Found {:d} chunks in {:d} small non-empty bins.".format(sum(bins.values()), len(bins)))
return
@register_command
class GlibcHeapLargeBinsCommand(GenericCommand):
"""Convenience command for viewing large bins."""
_cmdline_ = "heap bins large"
_syntax_ = "{:s} [ARENA_ADDRESS]".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION)
return
@only_if_gdb_running
def do_invoke(self, argv):
if get_glibc_arena() is None:
err("Invalid Glibc arena")
return
arena_addr = "*{:s}".format(argv[0]) if len(argv) == 1 else __gef_current_arena__
gef_print(titlify("Large Bins for arena '{:s}'".format(arena_addr)))
bins = {}
for i in range(63, 126):
nb_chunk = GlibcHeapBinsCommand.pprint_bin(arena_addr, i, "large_")
if nb_chunk < 0:
break
if nb_chunk > 0:
bins[i] = nb_chunk
info("Found {:d} chunks in {:d} large non-empty bins.".format(sum(bins.values()), len(bins)))
return
@register_command
class SolveKernelSymbolCommand(GenericCommand):
"""Solve kernel symbols from kallsyms table."""
_cmdline_ = "ksymaddr"
_syntax_ = "{:s} SymbolToSearch".format(_cmdline_)
_example_ = "{:s} prepare_creds".format(_cmdline_)
@parse_arguments({"symbol": ""}, {})
def do_invoke(self, *args, **kwargs):
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("Found matching symbol for '{:s}' at {:#x} (type={:s})".format(name, addr, sym_t))
else:
warn("Found partial match for '{:s}' at {:#x} (type={:s}): {:s}".format(sym, addr, sym_t, name))
if not matches:
err("No match for '{:s}'".format(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_ = "{:s} [[Register1][Register2] ... [RegisterN]]".format(_cmdline_)
_example_ = "\n{0:s}\n{0:s} $eax $eip $esp".format(_cmdline_)
@only_if_gdb_running
@parse_arguments({"registers": [""]}, {})
def do_invoke(self, argv, *args, **kwargs):
unchanged_color = get_gef_setting("theme.registers_register_name")
changed_color = get_gef_setting("theme.registers_value_changed")
string_color = get_gef_setting("theme.dereference_string")
regs = current_arch.all_registers
args = kwargs["arguments"]
if args.registers and args.registers[0]:
required_regs = set(args.registers)
valid_regs = [reg for reg in current_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("invalid registers for architecture: {}".format(", ".join(invalid_regs)))
memsize = current_arch.ptrsize
endian = endian_str()
charset = string.printable
widest = max(map(len, current_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>":
line = "{}: ".format(Color.colorify(padreg, unchanged_color))
line += Color.colorify("no value", "yellow underline")
gef_print(line)
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 current_arch.special_registers:
special_line += "{}: ".format(Color.colorify(regname, color))
special_line += "0x{:04x} ".format(get_register(regname))
continue
line = "{}: ".format(Color.colorify(padreg, color))
if regname == current_arch.flag_register:
line += current_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 = " {:s} ".format(RIGHT_ARROW)
line += sep
line += sep.join(addrs[1:])
# check to see if reg value is ascii
try:
fmt = "{}{}".format(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 += ' ("{:s}"?)'.format(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_ = "{:s} (search|get)".format(_cmdline_)
def __init__(self):
super().__init__(prefix=True)
return
def do_invoke(self, argv):
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_ = "{:s} PATTERN1 PATTERN2".format(_cmdline_)
_aliases_ = ["sc-search",]
api_base = "http://shell-storm.org"
search_url = "{}/api/?s=".format(api_base)
def do_invoke(self, argv):
if not argv:
err("Missing pattern to search")
self.usage()
return
self.search_shellcode(argv)
return
def search_shellcode(self, search_options):
# 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_ = "{:s} SHELLCODE_ID".format(_cmdline_)
_aliases_ = ["sc-get",]
api_base = "http://shell-storm.org"
get_url = "{}/shellcode/files/shellcode-{{:d}}.php".format(api_base)
def do_invoke(self, argv):
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):
info("Downloading shellcode id={:d}".format(sid))
res = http_get(self.get_url.format(sid))
if res is None:
err("Failed to fetch shellcode #{:d}".format(sid))
return
ok("Downloaded, written to disk...")
tempdir = get_gef_setting("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("Shellcode written to '{:s}'".format(fname))
return
@register_command
class RopperCommand(GenericCommand):
"""Ropper (http://scoding.de/ropper) plugin."""
_cmdline_ = "ropper"
_syntax_ = "{:s} [ROPPER_OPTIONS]".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_NONE)
return
def pre_load(self):
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):
ropper = sys.modules["ropper"]
if "--file" not in argv:
path = get_filepath()
sect = next(filter(lambda x: x.path == path, get_process_maps()))
argv.append("--file")
argv.append(path)
argv.append("-I")
argv.append("{:#x}".format(sect.page_start))
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_ = "{:s} [-h] [--list-archs] [--mode MODE] [--arch ARCH] [--overwrite-location LOCATION] [--endian ENDIAN] [--as-shellcode] instruction;[instruction;...instruction;])".format(_cmdline_)
_aliases_ = ["asm",]
_example_ = "\n{0:s} -a x86 -m 32 nop ; nop ; inc eax ; int3\n{0:s} -a arm -m arm add r0, r0, 1".format(_cmdline_)
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):
super().__init__()
self.add_setting("default_architecture", "X86", "Specify the default architecture to use when assembling")
self.add_setting("default_mode", "64", "Specify the default architecture to use when assembling")
return
def pre_load(self):
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):
super().usage()
gef_print("")
self.list_archs()
return
def list_archs(self):
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("- {}".format(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(" * {:<7} ({})".format(mode, endianness))
return
@parse_arguments({"instructions": [""]}, {"--mode": "", "--arch": "", "--overwrite-location": 0, "--endian": "little", "--list-archs": True, "--as-shellcode": True})
def do_invoke(self, argv, *args, **kwargs):
arch_s, mode_s, endian_s = self.get_setting("default_architecture"), self.get_setting("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 = current_arch.arch, current_arch.mode
endian_s = "big" if is_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("invalid arch '{}'".format(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("invalid mode '{}' for arch '{}'".format(mode_s, 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("invalid endianness '{}' for arch/mode '{}:{}'".format(endian_s, 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("Assembling {} instruction(s) for {}:{}".format(len(insns), 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 = """sc+="{0:s}" """.format(res)
gef_print("{0:60s} # {1}".format(res, insn))
if args.overwrite_location:
l = len(raw)
info("Overwriting {:d} bytes at {:s}".format(l, format_address(args.overwrite_location)))
write_memory(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_ = "{:s} [-h] [--attach] [--smart-scan] [REGEX_PATTERN]".format(_cmdline_)
_aliases_ = ["ps"]
_example_ = "{:s} gdb.*".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION)
ps = which("ps")
self.add_setting("ps_command", "{:s} auxww".format(ps), "`ps` command to get process information")
return
@parse_arguments({"pattern": ""}, {"--attach": True, "--smart-scan": True})
def do_invoke(self, argv, *args, **kwargs):
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("Attaching to process='{:s}' pid={:d}".format(process["command"], pid))
gdb.execute("attach {:d}".format(pid))
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):
output = gef_execute_external(self.get_setting("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_ = "{:s} [FILE]".format(_cmdline_)
_example_ = "{:s} /bin/ls".format(_cmdline_)
def __init__(self, *args, **kwargs):
super().__init__(complete=gdb.COMPLETE_LOCATION)
return
@parse_arguments({}, {"--filename": ""})
def do_invoke(self, argv, *args, **kwargs):
args = kwargs["arguments"]
if is_qemu_system():
err("Unsupported")
return
# http://www.sco.com/developers/gabi/latest/ch4.eheader.html
classes = {
Elf.ELF_32_BITS : "32-bit",
Elf.ELF_64_BITS : "64-bit",
}
endianness = {
Elf.LITTLE_ENDIAN : "Little-Endian",
Elf.BIG_ENDIAN : "Big-Endian",
}
osabi = {
Elf.OSABI_SYSTEMV : "System V",
Elf.OSABI_HPUX : "HP-UX",
Elf.OSABI_NETBSD : "NetBSD",
Elf.OSABI_LINUX : "Linux",
Elf.OSABI_SOLARIS : "Solaris",
Elf.OSABI_AIX : "AIX",
Elf.OSABI_IRIX : "IRIX",
Elf.OSABI_FREEBSD : "FreeBSD",
Elf.OSABI_OPENBSD : "OpenBSD",
}
types = {
Elf.ET_RELOC : "Relocatable",
Elf.ET_EXEC : "Executable",
Elf.ET_DYN : "Shared",
Elf.ET_CORE : "Core"
}
machines = {
Elf.X86_64 : "x86-64",
Elf.X86_32 : "x86",
Elf.ARM : "ARM",
Elf.MIPS : "MIPS",
Elf.POWERPC : "PowerPC",
Elf.POWERPC64 : "PowerPC64",
Elf.SPARC : "SPARC",
Elf.SPARC64 : "SPARC64",
Elf.AARCH64 : "AArch64",
Elf.RISCV : "RISC-V",
Elf.IA64 : "IA-64",
}
filename = args.filename or get_filepath()
if filename is None:
return
elf = get_elf_headers(filename)
if elf is None:
return
data = [
("Magic", "{0!s}".format(hexdump(struct.pack(">I", elf.e_magic), show_raw=True))),
("Class", "{0:#x} - {1}".format(elf.e_class, classes[elf.e_class])),
("Endianness", "{0:#x} - {1}".format(elf.e_endianness, endianness[elf.e_endianness])),
("Version", "{:#x}".format(elf.e_eiversion)),
("OS ABI", "{0:#x} - {1}".format(elf.e_osabi, osabi[elf.e_osabi])),
("ABI Version", "{:#x}".format(elf.e_abiversion)),
("Type", "{0:#x} - {1}".format(elf.e_type, types[elf.e_type])),
("Machine", "{0:#x} - {1}".format(elf.e_machine, machines[elf.e_machine])),
("Program Header Table", "{}".format(format_address(elf.e_phoff))),
("Section Header Table", "{}".format(format_address(elf.e_shoff))),
("Header Table", "{}".format(format_address(elf.e_phoff))),
("ELF Version", "{:#x}".format(elf.e_version)),
("Header size", "{0} ({0:#x})".format(elf.e_ehsize)),
("Entry point", "{}".format(format_address(elf.e_entry))),
]
for title, content in data:
gef_print("{}: {}".format(Color.boldify("{:<22}".format(title)), content))
ptype = {
Phdr.PT_NULL: "NULL",
Phdr.PT_LOAD: "LOAD",
Phdr.PT_DYNAMIC: "DYNAMIC",
Phdr.PT_INTERP: "INTERP",
Phdr.PT_NOTE: "NOTE",
Phdr.PT_SHLIB: "SHLIB",
Phdr.PT_PHDR: "PHDR",
Phdr.PT_TLS: "TLS",
Phdr.PT_LOOS: "LOOS",
Phdr.PT_GNU_EH_FRAME: "GNU_EH_FLAME",
Phdr.PT_GNU_STACK: "GNU_STACK",
Phdr.PT_GNU_RELRO: "GNU_RELRO",
Phdr.PT_LOSUNW: "LOSUNW",
Phdr.PT_SUNWBSS: "SUNWBSS",
Phdr.PT_SUNWSTACK: "SUNWSTACK",
Phdr.PT_HISUNW: "HISUNW",
Phdr.PT_HIOS: "HIOS",
Phdr.PT_LOPROC: "LOPROC",
Phdr.PT_HIPROC: "HIPROC",
}
pflags = {
0: Permission.NONE,
Phdr.PF_X: Permission.EXECUTE,
Phdr.PF_W: Permission.WRITE,
Phdr.PF_R: Permission.READ,
Phdr.PF_W|Phdr.PF_X: Permission.WRITE|Permission.EXECUTE,
Phdr.PF_R|Phdr.PF_X: Permission.READ|Permission.EXECUTE,
Phdr.PF_R|Phdr.PF_W: Permission.READ|Permission.WRITE,
Phdr.PF_R|Phdr.PF_W|Phdr.PF_X: Permission.ALL,
}
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 = ptype[p.p_type] if p.p_type in ptype else "UNKNOWN"
p_flags = Permission(value=pflags[p.p_flags]) if p.p_flags in pflags 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, str(p_flags), p.p_align))
stype = {
Shdr.SHT_NULL: "NULL",
Shdr.SHT_PROGBITS: "PROGBITS",
Shdr.SHT_SYMTAB: "SYMTAB",
Shdr.SHT_STRTAB: "STRTAB",
Shdr.SHT_RELA: "RELA",
Shdr.SHT_HASH: "HASH",
Shdr.SHT_DYNAMIC: "DYNAMIC",
Shdr.SHT_NOTE: "NOTE",
Shdr.SHT_NOBITS: "NOBITS",
Shdr.SHT_REL: "REL",
Shdr.SHT_SHLIB: "SHLIB",
Shdr.SHT_DYNSYM: "DYNSYM",
Shdr.SHT_NUM: "NUM",
Shdr.SHT_INIT_ARRAY: "INIT_ARRAY",
Shdr.SHT_FINI_ARRAY: "FINI_ARRAY",
Shdr.SHT_PREINIT_ARRAY: "PREINIT_ARRAY",
Shdr.SHT_GROUP: "GROUP",
Shdr.SHT_SYMTAB_SHNDX: "SYMTAB_SHNDX",
Shdr.SHT_NUM: "NUM",
Shdr.SHT_LOOS: "LOOS",
Shdr.SHT_GNU_ATTRIBUTES:"GNU_ATTRIBUTES",
Shdr.SHT_GNU_HASH: "GNU_HASH",
Shdr.SHT_GNU_LIBLIST: "GNU_LIBLIST",
Shdr.SHT_CHECKSUM: "CHECKSUM",
Shdr.SHT_LOSUNW: "LOSUNW",
Shdr.SHT_SUNW_move: "SUNW_move",
Shdr.SHT_SUNW_COMDAT: "SUNW_COMDAT",
Shdr.SHT_SUNW_syminfo: "SUNW_syminfo",
Shdr.SHT_GNU_verdef: "GNU_verdef",
Shdr.SHT_GNU_verneed: "GNU_verneed",
Shdr.SHT_GNU_versym: "GNU_versym",
Shdr.SHT_HISUNW: "HISUNW",
Shdr.SHT_HIOS: "HIOS",
Shdr.SHT_LOPROC: "LOPROC",
Shdr.SHT_HIPROC: "HIPROC",
Shdr.SHT_LOUSER: "LOUSER",
Shdr.SHT_HIUSER: "HIUSER",
}
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 = stype[s.sh_type] if s.sh_type in stype else "UNKNOWN"
sh_flags = ""
if s.sh_flags & Shdr.SHF_WRITE: sh_flags += "W"
if s.sh_flags & Shdr.SHF_ALLOC: sh_flags += "A"
if s.sh_flags & Shdr.SHF_EXECINSTR: sh_flags += "X"
if s.sh_flags & Shdr.SHF_MERGE: sh_flags += "M"
if s.sh_flags & Shdr.SHF_STRINGS: sh_flags += "S"
if s.sh_flags & Shdr.SHF_INFO_LINK: sh_flags += "I"
if s.sh_flags & Shdr.SHF_LINK_ORDER: sh_flags += "L"
if s.sh_flags & Shdr.SHF_OS_NONCONFORMING: sh_flags += "O"
if s.sh_flags & Shdr.SHF_GROUP: sh_flags += "G"
if s.sh_flags & Shdr.SHF_TLS: sh_flags += "T"
if s.sh_flags & Shdr.SHF_EXCLUDE: sh_flags += "E"
if s.sh_flags & Shdr.SHF_COMPRESSED: sh_flags += "C"
gef_print(" [{:2d}] {:20s} {:>15s} {:#10x} {:#8x} {:#8x} {:#8x} {:5s} {:#4x} {:#4x} {:#8x}".format(
i, s.sh_name, sh_type, s.sh_addr, s.sh_offset, s.sh_size, s.sh_entsize, sh_flags, s.sh_link, s.sh_info, s.sh_addralign))
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, *args, **kwargs):
super().__init__()
self.add_setting("entrypoint_symbols", "main _main __libc_start_main __uClibc_main start _start", "Possible symbols for entry points")
return
def do_invoke(self, argv):
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("The file '{}' is not executable.".format(fpath))
return
if is_alive() and not __gef_qemu_mode__:
warn("gdb is already running")
return
bp = None
entrypoints = self.get_setting("entrypoint_symbols").split()
for sym in entrypoints:
try:
value = parse_address(sym)
info("Breaking at '{:s}'".format(str(value)))
bp = EntryBreakBreakpoint(sym)
gdb.execute("run {}".format(" ".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 = get_entry_point()
if entry is None:
return
if is_pie(fpath):
self.set_init_tbreak_pie(entry, argv)
gdb.execute("continue")
return
self.set_init_tbreak(entry)
gdb.execute("run {}".format(" ".join(argv)))
return
def set_init_tbreak(self, addr):
info("Breaking at entry-point: {:#x}".format(addr))
bp = EntryBreakBreakpoint("*{:#x}".format(addr))
return bp
def set_init_tbreak_pie(self, addr, argv):
warn("PIC binary detected, retrieving text base address")
gdb.execute("set stop-on-solib-events 1")
hide_context()
gdb.execute("run {}".format(" ".join(argv)))
unhide_context()
gdb.execute("set stop-on-solib-events 0")
vmmap = get_process_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_ = "{:s} name [address]".format(_cmdline_)
_aliases_ = ["nb",]
_example = "{:s} main *0x4008a9"
def __init__(self, *args, **kwargs):
super().__init__()
return
@parse_arguments({"name": "", "address": "*$pc"}, {})
def do_invoke(self, *args, **kwargs):
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_ = "{:s} [legend|regs|stack|code|args|memory|source|trace|threads|extra]".format(_cmdline_)
_aliases_ = ["ctx",]
old_registers = {}
def __init__(self):
super().__init__()
self.add_setting("enable", True, "Enable/disable printing the context when breaking")
self.add_setting("show_source_code_variable_values", True, "Show extra PC context info in the source code")
self.add_setting("show_stack_raw", False, "Show the stack pane as raw hexdump (no dereference)")
self.add_setting("show_registers_raw", False, "Show the registers pane with raw values (no dereference)")
self.add_setting("show_opcodes_size", 0, "Number of bytes of opcodes to display next to the disassembly")
self.add_setting("peek_calls", True, "Peek into calls")
self.add_setting("peek_ret", True, "Peek at return address")
self.add_setting("nb_lines_stack", 8, "Number of line in the stack pane")
self.add_setting("grow_stack_down", False, "Order of stack downward starts at largest down to stack pointer")
self.add_setting("nb_lines_backtrace", 10, "Number of line in the backtrace pane")
self.add_setting("nb_lines_backtrace_before", 2, "Number of line in the backtrace pane before selected frame")
self.add_setting("nb_lines_threads", -1, "Number of line in the threads pane")
self.add_setting("nb_lines_code", 6, "Number of instruction after $pc")
self.add_setting("nb_lines_code_prev", 3, "Number of instruction before $pc")
self.add_setting("ignore_registers", "", "Space-separated list of registers not to display (e.g. '$cs $ds $gs')")
self.add_setting("clear_screen", True, "Clear the screen before printing the context")
self.add_setting("layout", "legend regs stack code args source memory threads trace extra", "Change the order/presence of the context sections")
self.add_setting("redirect", "", "Redirect the context information to another TTY")
self.add_setting("libc_args", False, "Show libc function call args description")
self.add_setting("libc_args_path", "", "Path to libc function call args json files, provided via gef-extras")
if "capstone" in list(sys.modules.keys()):
self.add_setting("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):
gef_on_continue_hook(self.update_registers)
gef_on_continue_hook(self.empty_extra_messages)
return
def show_legend(self):
if get_gef_setting("gef.disable_color") is True:
return
str_color = get_gef_setting("theme.dereference_string")
code_addr_color = get_gef_setting("theme.address_code")
stack_addr_color = get_gef_setting("theme.address_stack")
heap_addr_color = get_gef_setting("theme.address_heap")
changed_register_color = get_gef_setting("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):
if not self.get_setting("enable") or 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.get_setting("layout").strip().split()
if not current_layout:
return
self.tty_rows, self.tty_columns = get_terminal_size()
redirect = self.get_setting("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.get_setting("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):
# allow for not displaying a title line
if m is None:
return
line_color = get_gef_setting("theme.context_title_line")
msg_color = get_gef_setting("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):
self.context_title("registers")
ignored_registers = set(self.get_setting("ignore_registers").split())
if self.get_setting("show_registers_raw") is False:
regs = set(current_arch.all_registers)
printable_registers = " ".join(list(regs - ignored_registers))
gdb.execute("registers {}".format(printable_registers))
return
widest = l = max(map(len, current_arch.all_registers))
l += 5
l += current_arch.ptrsize * 2
nb = get_terminal_size()[1] // l
i = 1
line = ""
changed_color = get_gef_setting("theme.registers_value_changed")
regname_color = get_gef_setting("theme.registers_register_name")
for reg in current_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 = get_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 += "{}: ".format(Color.colorify(padreg, regname_color))
else:
line += "{}: ".format(Color.colorify(padreg, changed_color))
if new_value_type_flag:
line += "{:s} ".format(format_address_spaces(value))
else:
addr = lookup_address(align_address(int(value)))
if addr.valid:
line += "{:s} ".format(str(addr))
else:
line += "{:s} ".format(format_address_spaces(value))
if i % nb == 0:
gef_print(line)
line = ""
i += 1
if line:
gef_print(line)
gef_print("Flags: {:s}".format(current_arch.flag_register_to_human()))
return
def context_stack(self):
self.context_title("stack")
show_raw = self.get_setting("show_stack_raw")
nb_lines = self.get_setting("nb_lines_stack")
try:
sp = current_arch.sp
if show_raw is True:
mem = read_memory(sp, 0x10 * nb_lines)
gef_print(hexdump(mem, base=sp))
else:
gdb.execute("dereference -l {:d} {:#x}".format(nb_lines, sp))
except gdb.MemoryError:
err("Cannot read memory from $SP (corrupted stack pointer?)")
return
def addr_has_breakpoint(self, address, bp_locations):
return any(hex(address) in b for b in bp_locations)
def context_code(self):
nb_insn = self.get_setting("nb_lines_code")
nb_insn_prev = self.get_setting("nb_lines_code_prev")
use_capstone = self.has_setting("use_capstone") and self.get_setting("use_capstone")
show_opcodes_size = self.has_setting("show_opcodes_size") and self.get_setting("show_opcodes_size")
past_insns_color = get_gef_setting("theme.old_context")
cur_insn_color = get_gef_setting("theme.disassemble_current_instruction")
pc = current_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 = "{}:{}".format(current_arch.arch.lower(), current_arch.mode)
self.context_title("code:{}".format(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 = "{{:{}o}}".format(show_opcodes_size)
text = insn_fmt.format(insn)
if insn.address < pc:
line += "{} {}".format(bp_prefix, Color.colorify(text, past_insns_color))
elif insn.address == pc:
line += "{}{}".format(bp_prefix, Color.colorify("{:s}{:s}".format(RIGHT_ARROW[1:], text), cur_insn_color))
if current_arch.is_conditional_branch(insn):
is_taken, reason = current_arch.is_branch_taken(insn)
if is_taken:
target = insn.operands[-1].split()[0]
reason = "[Reason: {:s}]".format(reason) if reason else ""
line += Color.colorify("\tTAKEN {:s}".format(reason), "bold green")
else:
reason = "[Reason: !({:s})]".format(reason) if reason else ""
line += Color.colorify("\tNOT taken {:s}".format(reason), "bold red")
elif current_arch.is_call(insn) and self.get_setting("peek_calls") is True:
target = insn.operands[-1].split()[0]
elif current_arch.is_ret(insn) and self.get_setting("peek_ret") is True:
target = current_arch.get_ra(insn, frame)
else:
line += "{} {}".format(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= " {} {}".format (DOWN_ARROW if i == 0 else " ", str(tinsn))
gef_print(text)
break
except gdb.MemoryError:
err("Cannot disassemble from $PC")
return
def context_args(self):
insn = gef_current_instruction(current_arch.pc)
if not current_arch.is_call(insn):
return
self.size2type = {
1: "BYTE",
2: "WORD",
4: "DWORD",
8: "QWORD",
}
if insn.operands[-1].startswith(self.size2type[current_arch.ptrsize]+" PTR"):
target = "*" + insn.operands[-1].split()[-1]
elif "$"+insn.operands[0] in current_arch.all_registers:
target = "*{:#x}".format(get_register("$"+insn.operands[0]))
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("Symbol '{}' is not a function: type={}".format(target, sym.type.code))
return
self.print_arguments_from_symbol(target, sym)
return
def print_arguments_from_symbol(self, function_name, symbol):
"""If symbols were found, parse them and print the argument adequately."""
args = []
for i, f in enumerate(symbol.type.fields()):
_value = current_arch.get_ith_parameter(i, in_func=False)[1]
_value = RIGHT_ARROW.join(dereference_from(_value))
_name = f.name or "var_{}".format(i)
_type = f.type.name or self.size2type[f.type.sizeof]
args.append("{} {} = {}".format(_type, _name, _value))
self.context_title("arguments")
if not args:
gef_print("{} (<void>)".format(function_name))
return
gef_print("{} (".format(function_name))
gef_print(" " + ",\n ".join(args))
gef_print(")")
return
def print_guessed_arguments(self, function_name):
"""When no symbol, read the current basic block and look for "interesting" instructions."""
def __get_current_block_start_address():
pc = current_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 = current_arch.pc
block_start = __get_current_block_start_address()
if not block_start:
return
use_capstone = self.has_setting("use_capstone") and self.get_setting("use_capstone")
instruction_iterator = capstone_disassemble if use_capstone else gef_disassemble
function_parameters = current_arch.function_parameters
arg_key_color = get_gef_setting("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 = "{}_{}".format(current_arch.arch.lower(), current_arch.mode)
_function_name = None
if function_name.endswith("@plt"):
_function_name = function_name.split("@")[0]
try:
nb_argument = len(libc_args_definitions[_arch_mode][_function_name])
except KeyError:
pass
if not nb_argument:
if not parameter_set:
nb_argument = 0
elif is_x86_32():
nb_argument = len(parameter_set)
else:
nb_argument = max(function_parameters.index(p)+1 for p in parameter_set)
args = []
for i in range(nb_argument):
_key, _values = current_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,
libc_args_definitions[_arch_mode][_function_name][_key]))
except KeyError:
args.append("{} = {}".format(Color.colorify(_key, arg_key_color), _values))
self.context_title("arguments (guessed)")
gef_print("{} (".format(function_name))
if args:
gef_print(" " + ",\n ".join(args))
gef_print(")")
return
def line_has_breakpoint(self, file_name, line_number, bp_locations):
filename_line = "{}:{}".format(file_name, line_number)
return any(filename_line in loc for loc in bp_locations)
def context_source(self):
try:
pc = current_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 = get_gef_setting("theme.old_context")
nb_line = self.get_setting("nb_lines_code")
fn = symtab.filename
if len(fn) > 20:
fn = "{}[...]{}".format(fn[:15], os.path.splitext(fn)[1])
title = "source:{}+{}".format(fn, line_num + 1)
cur_line_color = get_gef_setting("theme.source_current_line")
self.context_title(title)
show_extra_info = self.get_setting("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(" {:4d}\t {:s}".format(i + 1, lines[i],), past_lines_color)))
if i == line_num:
prefix = "{}{}{:4d}\t ".format(bp_prefix, RIGHT_ARROW[1:], i + 1)
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("{}{}".format(" "*(len(prefix) + leading), extra_info))
gef_print(Color.colorify("{}{:s}".format(prefix, lines[i]), cur_line_color))
if i > line_num:
try:
gef_print("{} {:4d}\t {:s}".format(bp_prefix, i + 1, lines[i],))
except IndexError:
break
return
def get_pc_context_info(self, pc, line):
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(r"\W{}\W".format(symbol), 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 = " {:s} ".format(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(["{}={}".format(Color.yellowify(a), b) for a, b in m.items()])
except Exception:
pass
return ""
def context_trace(self):
self.context_title("trace")
nb_backtrace = self.get_setting("nb_lines_backtrace")
if nb_backtrace <= 0:
return
# backward compat for gdb (gdb < 7.10)
if not hasattr(gdb, "FrameDecorator"):
gdb.execute("backtrace {:d}".format(nb_backtrace))
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.get_setting("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("{:#x}".format(pc))
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 = " <{}+{:x}> ".format(sym_name, offset)
items.append(Color.redify("{}{} {}".format(symbol, insn.mnemonic, ", ".join(insn.operands))))
gef_print("[{}] {}".format(Color.colorify("#{}".format(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):
def reason():
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.get_setting("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 = """[{:s}] Id {:d}, """.format(Color.colorify("#{:d}".format(i), "bold green" if thread == selected_thread else "bold pink"), thread.num)
if thread.name:
line += """Name: "{:s}", """.format(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 = "<{}+{:x}>".format(sym_name, offset)
line += " {:s} in {:s} ()".format(Color.colorify("{:#x}".format(frame.pc()), "blue"), Color.colorify(frame_name or "??", "bold yellow"))
line += ", reason: {}".format(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):
if not __context_messages__:
return
self.context_title("extra")
for level, text in __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):
global __watches__
for address, opt in sorted(__watches__.items()):
sz, fmt = opt[0:2]
self.context_title("memory:{:#x}".format(address))
if fmt == "pointers":
gdb.execute("dereference -l {size:d} {address:#x}".format(
address=address,
size=sz,
))
else:
gdb.execute("hexdump {fmt:s} -s {size:d} {address:#x}".format(
address=address,
size=sz,
fmt=fmt,
))
@classmethod
def update_registers(cls, event):
for reg in current_arch.all_registers:
try:
cls.old_registers[reg] = get_register(reg)
except Exception:
cls.old_registers[reg] = 0
return
def empty_extra_messages(self, event):
global __context_messages__
__context_messages__ = []
return
@register_command
class MemoryCommand(GenericCommand):
"""Add or remove address ranges to the memory view."""
_cmdline_ = "memory"
_syntax_ = "{:s} (watch|unwatch|reset|list)".format(_cmdline_)
def __init__(self):
super().__init__(prefix=True)
return
@only_if_gdb_running
def do_invoke(self, argv):
self.usage()
return
@register_command
class MemoryWatchCommand(GenericCommand):
"""Adds address ranges to the memory view."""
_cmdline_ = "memory watch"
_syntax_ = "{:s} ADDRESS [SIZE] [(qword|dword|word|byte|pointers)]".format(_cmdline_)
_example_ = "\n\t{0:s} 0x603000 0x100 byte\n\t{0:s} $sp".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION)
return
@only_if_gdb_running
def do_invoke(self, argv):
global __watches__
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("Unexpected grouping '{}'".format(group))
self.usage()
return
else:
if current_arch.ptrsize == 4:
group = "dword"
elif current_arch.ptrsize == 8:
group = "qword"
__watches__[address] = (size, group)
ok("Adding memwatch to {:#x}".format(address))
return
@register_command
class MemoryUnwatchCommand(GenericCommand):
"""Removes address ranges to the memory view."""
_cmdline_ = "memory unwatch"
_syntax_ = "{:s} ADDRESS".format(_cmdline_)
_example_ = "\n\t{0:s} 0x603000\n\t{0:s} $sp".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION)
return
@only_if_gdb_running
def do_invoke(self, argv):
global __watches__
if not argv:
self.usage()
return
address = parse_address(argv[0])
res = __watches__.pop(address, None)
if not res:
warn("You weren't watching {:#x}".format(address))
else:
ok("Removed memwatch of {:#x}".format(address))
return
@register_command
class MemoryWatchResetCommand(GenericCommand):
"""Removes all watchpoints."""
_cmdline_ = "memory reset"
_syntax_ = "{:s}".format(_cmdline_)
@only_if_gdb_running
def do_invoke(self, argv):
global __watches__
__watches__.clear()
ok("Memory watches cleared")
return
@register_command
class MemoryWatchListCommand(GenericCommand):
"""Lists all watchpoints to display in context layout."""
_cmdline_ = "memory list"
_syntax_ = "{:s}".format(_cmdline_)
@only_if_gdb_running
def do_invoke(self, argv):
global __watches__
if not __watches__:
info("No memory watches")
return
info("Memory watches:")
for address, opt in sorted(__watches__.items()):
gef_print("- {:#x} ({}, {})".format(address, 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_ = "{:s} (qword|dword|word|byte) [LOCATION] [--size SIZE] [--reverse]".format(_cmdline_)
_example_ = "{:s} byte $rsp --size 16 --reverse".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION, prefix=True)
self.add_setting("always_show_ascii", False, "If true, hexdump will always display the ASCII dump")
self.format = None
self.__last_target = "$sp"
return
@only_if_gdb_running
@parse_arguments({"address": "",}, {("--reverse", "-r"): True, ("--size", "-s"): 0})
def do_invoke(self, argv, *args, **kwargs):
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 = read_memory(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, length, arrange_as, offset=0):
endianness = endian_str()
base_address_color = get_gef_setting("theme.dereference_base_address")
show_ascii = get_gef_setting("hexdump.always_show_ascii")
formats = {
"qword": ("Q", 8),
"dword": ("I", 4),
"word": ("H", 2),
}
r, l = formats[arrange_as]
fmt_str = "{{base}}{v}+{{offset:#06x}} {{sym}}{{val:#0{prec}x}} {{text}}".format(v=VERTICAL_LINE, prec=l*2+2)
fmt_pack = endianness + 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 = read_memory(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_ = "{:s} [ADDRESS] [[L][SIZE]] [REVERSE]".format(_cmdline_)
_example_ = "{:s} qword $rsp L16 REVERSE".format(_cmdline_)
def __init__(self):
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_ = "{:s} [ADDRESS] [[L][SIZE]] [REVERSE]".format(_cmdline_)
_example_ = "{:s} $esp L16 REVERSE".format(_cmdline_)
def __init__(self):
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_ = "{:s} [ADDRESS] [[L][SIZE]] [REVERSE]".format(_cmdline_)
_example_ = "{:s} $esp L16 REVERSE".format(_cmdline_)
def __init__(self):
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_ = "{:s} [ADDRESS] [[L][SIZE]] [REVERSE]".format(_cmdline_)
_example_ = "{:s} $rsp L16".format(_cmdline_)
def __init__(self):
super().__init__()
self.format = "byte"
return
@register_command
class PatchCommand(GenericCommand):
"""Write specified values to the specified address."""
_cmdline_ = "patch"
_syntax_ = ("{0:s} (qword|dword|word|byte) LOCATION VALUES\n"
"{0:s} string LOCATION \"double-escaped string\"".format(_cmdline_))
SUPPORTED_SIZES = {
"qword": (8, "Q"),
"dword": (4, "L"),
"word": (2, "H"),
"byte": (1, "B"),
}
def __init__(self):
super().__init__(prefix=True, complete=gdb.COMPLETE_LOCATION)
self.format = None
return
@only_if_gdb_running
@parse_arguments({"location": "", "values": ["", ]}, {})
def do_invoke(self, argv, *args, **kwargs):
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 = endian_str()
for value in args.values:
value = parse_address(value) & ((1 << size * 8) - 1)
vstr = struct.pack(d + fcode, value)
write_memory(addr, vstr, length=size)
addr += size
return
@register_command
class PatchQwordCommand(PatchCommand):
"""Write specified QWORD to the specified address."""
_cmdline_ = "patch qword"
_syntax_ = "{0:s} LOCATION QWORD1 [QWORD2 [QWORD3..]]".format(_cmdline_)
_example_ = "{:s} $rip 0x4141414141414141".format(_cmdline_)
def __init__(self):
super().__init__()
self.format = "qword"
return
@register_command
class PatchDwordCommand(PatchCommand):
"""Write specified DWORD to the specified address."""
_cmdline_ = "patch dword"
_syntax_ = "{0:s} LOCATION DWORD1 [DWORD2 [DWORD3..]]".format(_cmdline_)
_example_ = "{:s} $rip 0x41414141".format(_cmdline_)
def __init__(self):
super().__init__()
self.format = "dword"
return
@register_command
class PatchWordCommand(PatchCommand):
"""Write specified WORD to the specified address."""
_cmdline_ = "patch word"
_syntax_ = "{0:s} LOCATION WORD1 [WORD2 [WORD3..]]".format(_cmdline_)
_example_ = "{:s} $rip 0x4141".format(_cmdline_)
def __init__(self):
super().__init__()
self.format = "word"
return
@register_command
class PatchByteCommand(PatchCommand):
"""Write specified WORD to the specified address."""
_cmdline_ = "patch byte"
_syntax_ = "{0:s} LOCATION BYTE1 [BYTE2 [BYTE3..]]".format(_cmdline_)
_example_ = "{:s} $rip 0x41 0x41 0x41 0x41 0x41".format(_cmdline_)
def __init__(self):
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_ = "{:s} ADDRESS \"double backslash-escaped string\"".format(_cmdline_)
_example_ = "{:s} $sp \"GEFROCKS\"".format(_cmdline_)
@only_if_gdb_running
def do_invoke(self, argv):
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("Could not decode '\\xXX' encoded string \"{}\"".format(s))
return
write_memory(addr, s, len(s))
return
@lru_cache()
def dereference_from(addr):
if not is_alive():
return [format_address(addr),]
code_color = get_gef_setting("theme.dereference_code")
string_color = get_gef_setting("theme.dereference_string")
max_recursion = get_gef_setting("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 = "{} {} {}".format(insn.location, insn.mnemonic, ", ".join(insn.operands))
msg.append(Color.colorify(insn_str, code_color))
break
elif addr.section.permission.value & Permission.READ:
if is_ascii_string(addr.value):
s = read_cstring_from_memory(addr.value)
if len(s) < get_memory_alignment():
txt = '{:s} ("{:s}"?)'.format(format_address(deref), Color.colorify(s, string_color))
elif len(s) > 50:
txt = Color.colorify('"{:s}[...]"'.format(s[:50]), string_color)
else:
txt = Color.colorify('"{:s}"'.format(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=(current_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_ = "{:s} [-h] [--length LENGTH] [--reference REFERENCE] [address]".format(_cmdline_)
_aliases_ = ["telescope", ]
_example_ = "{:s} --length 20 --reference $sp+0x10 $sp".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION)
self.add_setting("max_recursion", 7, "Maximum level of pointer recursion")
return
@staticmethod
def pprint_dereferenced(addr, idx, base_offset=0):
base_address_color = get_gef_setting("theme.dereference_base_address")
registers_color = get_gef_setting("theme.dereference_register_value")
sep = " {:s} ".format(RIGHT_ARROW)
memalign = current_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 += "{:s}{:s}{:+#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 current_arch.all_registers:
regvalue = get_register(regname)
if current_address == regvalue:
register_hints.append(regname)
if register_hints:
m = "\t{:s}{:s}".format(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, *args, **kwargs):
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("Unmapped address: '{}'".format(target))
return
if process_lookup_address(ref_addr) is None:
err("Unmapped address: '{}'".format(reference))
return
if get_gef_setting("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_ = "{:s} [(on|off)]".format(_cmdline_)
def do_invoke(self, argv):
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, argv):
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_ = "{:s} [FILTER]".format(_cmdline_)
_example_ = "{:s} libc".format(_cmdline_)
@only_if_gdb_running
def do_invoke(self, argv):
vmmap = get_process_maps()
if not vmmap:
err("No address mapping information found")
return
if not get_gef_setting("gef.disable_color"):
self.show_legend()
color = get_gef_setting("theme.table_heading")
headers = ["Start", "End", "Offset", "Perm", "Path"]
gef_print(Color.colorify("{:<{w}s}{:<{w}s}{:<{w}s}{:<4s} {:s}".format(*headers, w=get_memory_alignment()*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):
line_color = ""
if entry.path == "[stack]":
line_color = get_gef_setting("theme.address_stack")
elif entry.path == "[heap]":
line_color = get_gef_setting("theme.address_heap")
elif entry.permission.value & Permission.READ and entry.permission.value & Permission.EXECUTE:
line_color = get_gef_setting("theme.address_code")
l = []
l.append(Color.colorify(format_address(entry.page_start), line_color))
l.append(Color.colorify(format_address(entry.page_end), line_color))
l.append(Color.colorify(format_address(entry.offset), line_color))
if entry.permission.value == (Permission.READ|Permission.WRITE|Permission.EXECUTE):
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):
code_addr_color = get_gef_setting("theme.address_code")
stack_addr_color = get_gef_setting("theme.address_stack")
heap_addr_color = get_gef_setting("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):
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_ = "{:s} [FILE [NAME]]".format(_cmdline_)
_example_ = "\n{0:s} libc\n{0:s} libc IO_vtables".format(_cmdline_)
@only_if_gdb_running
def do_invoke(self, argv):
color = get_gef_setting("theme.table_heading")
headers = ["Start", "End", "Name", "File"]
gef_print(Color.colorify("{:<{w}s}{:<{w}s}{:<21s} {:s}".format(*headers, w=get_memory_alignment()*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 = []
l.append(format_address(xfile.zone_start))
l.append(format_address(xfile.zone_end))
l.append("{:<21s}".format(xfile.name))
l.append(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_ = "{:s} LOCATION".format(_cmdline_)
_example_ = "{:s} $pc".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_LOCATION)
return
@only_if_gdb_running
def do_invoke(self, argv):
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("xinfo: {:#x}".format(addr)))
self.infos(addr)
except gdb.error as gdb_err:
err("{:s}".format(str(gdb_err)))
return
def infos(self, address):
addr = lookup_address(address)
if not addr.valid:
warn("Cannot reach {:#x} in memory space".format(address))
return
sect = addr.section
info = addr.info
if sect:
gef_print("Page: {:s} {:s} {:s} (size={:#x})".format(format_address(sect.page_start),
RIGHT_ARROW,
format_address(sect.page_end),
sect.page_end-sect.page_start))
gef_print("Permissions: {}".format(sect.permission))
gef_print("Pathname: {:s}".format(sect.path))
gef_print("Offset (from page): {:#x}".format(addr.value-sect.page_start))
gef_print("Inode: {:s}".format(sect.inode))
if info:
gef_print("Segment: {:s} ({:s}-{:s})".format(info.name,
format_address(info.zone_start),
format_address(info.zone_end)))
gef_print("Offset (from segment): {:#x}".format(addr.value-info.zone_start))
sym = gdb_get_location_from_symbol(address)
if sym:
name, offset = sym
msg = "Symbol: {:s}".format(name)
if offset:
msg+= "+{:d}".format(offset)
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_ = "{:s} (display|patch) ADDRESS SIZE KEY".format(_cmdline_)
def __init__(self):
super().__init__(prefix=True)
return
def do_invoke(self, argv):
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_ = "{:s} ADDRESS SIZE KEY".format(_cmdline_)
_example_ = "{:s} $sp 16 41414141".format(_cmdline_)
@only_if_gdb_running
def do_invoke(self, argv):
if len(argv) != 3:
self.usage()
return
address = parse_address(argv[0])
length = int(argv[1], 0)
key = argv[2]
block = read_memory(address, length)
info("Displaying XOR-ing {:#x}-{:#x} with {:s}".format(address, address + len(block), repr(key)))
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_ = "{:s} ADDRESS SIZE KEY".format(_cmdline_)
_example_ = "{:s} $sp 16 41414141".format(_cmdline_)
@only_if_gdb_running
def do_invoke(self, argv):
if len(argv) != 3:
self.usage()
return
address = parse_address(argv[0])
length = int(argv[1], 0)
key = argv[2]
block = read_memory(address, length)
info("Patching XOR-ing {:#x}-{:#x} with '{:s}'".format(address, address + len(block), key))
xored_block = xor(block, key)
write_memory(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_ = "{:s} LOCATION [MAX_CALL_DEPTH]".format(_cmdline_)
_example_ = "{:s} 0x555555554610".format(_cmdline_)
def __init__(self):
super().__init__(self._cmdline_, complete=gdb.COMPLETE_LOCATION)
self.add_setting("max_tracing_recursion", 1, "Maximum depth of tracing")
self.add_setting("tracefile_prefix", "./gef-trace-", "Specify the tracing output file prefix")
return
@only_if_gdb_running
def do_invoke(self, argv):
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 = current_arch.pc
loc_end = parse_address(argv[0])
except gdb.error as e:
err("Invalid location: {:s}".format(e))
return
self.trace(loc_start, loc_end, depth)
return
def get_frames_size(self):
n = 0
f = gdb.newest_frame()
while f:
n += 1
f = f.older()
return n
def trace(self, loc_start, loc_end, depth):
info("Tracing from {:#x} to {:#x} (max depth={:d})".format(loc_start, loc_end, depth))
logfile = "{:s}{:#x}-{:#x}.txt".format(self.get_setting("tracefile_prefix"), loc_start, loc_end)
enable_redirect_output(to_file=logfile)
hide_context()
self.start_tracing(loc_start, loc_end, depth)
unhide_context()
disable_redirect_output()
ok("Done, logfile stored as '{:s}'".format(logfile))
info("Hint: import logfile with `ida_color_gdb_trace.py` script in IDA to visualize path")
return
def start_tracing(self, loc_start, loc_end, depth):
loc_cur = loc_start
frame_count_init = self.get_frames_size()
gef_print("#")
gef_print("# Execution tracing of {:s}".format(get_filepath()))
gef_print("# Start address: {:s}".format(format_address(loc_start)))
gef_print("# End address: {:s}".format(format_address(loc_end)))
gef_print("# Recursion level: {:d}".format(depth))
gef_print("# automatically generated by gef.py")
gef_print("#\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 = current_arch.pc
gdb.flush()
except gdb.error as e:
gef_print("#")
gef_print("# Execution interrupted at address {:s}".format(format_address(loc_cur)))
gef_print("# Exception: {:s}".format(e))
gef_print("#\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_ = "{:s} (create|search) ARGS".format(_cmdline_)
def __init__(self):
super().__init__(prefix=True)
self.add_setting("length", 1024, "Default length of a cyclic buffer to generate")
return
def do_invoke(self, argv):
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_ = "{:s} [-h] [-n N] [length]".format(_cmdline_)
_example_ = "{:s} 4096".format(_cmdline_)
@parse_arguments({"length": 0}, {("-n", "--n"): 0})
def do_invoke(self, *args, **kwargs):
args = kwargs["arguments"]
length = args.length or get_gef_setting("pattern.length")
n = args.n or current_arch.ptrsize
info("Generating a pattern of {:d} bytes (n={:d})".format(length, n))
pattern_str = gef_pystring(generate_cyclic_pattern(length, n))
gef_print(pattern_str)
ok("Saved as '{:s}'".format(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_ = "{:s} [-h] [-n N] [--max-length MAX_LENGTH] [pattern]".format(_cmdline_)
_example_ = "\n{0:s} $pc\n{0:s} 0x61616164\n{0:s} aaab".format(_cmdline_)
_aliases_ = ["pattern offset"]
@only_if_gdb_running
@parse_arguments({"pattern": ""}, {("-n", "--n"): 0, ("-l", "--max-length"): 0})
def do_invoke(self, *args, **kwargs):
args = kwargs["arguments"]
max_length = args.max_length or get_gef_setting("pattern.length")
n = args.n or current_arch.ptrsize
info("Searching for '{:s}'".format(args.pattern))
self.search(args.pattern, max_length, n)
return
def search(self, pattern, size, period):
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(">{}".format(struct_packsize[current_arch.ptrsize]), addr)
pattern_le = struct.pack("<{}".format(struct_packsize[current_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("Found at offset {:d} (little-endian search) {:s}".format(off, Color.colorify("likely", "bold red") if is_little_endian() else ""))
found = True
off = cyclic_pattern.find(pattern_be)
if off >= 0:
ok("Found at offset {:d} (big-endian search) {:s}".format(off, Color.colorify("likely", "bold green") if is_big_endian() else ""))
found = True
if not found:
err("Pattern '{}' not found".format(pattern))
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_ = "{:s} [FILENAME]".format(_cmdline_)
_example_ = "{} /bin/ls".format(_cmdline_)
def __init__(self):
super().__init__(complete=gdb.COMPLETE_FILENAME)
return
def pre_load(self):
which("readelf")
return
def do_invoke(self, argv):
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("{:s} for '{:s}'".format(self._cmdline_, filename))
self.print_security_properties(filename)
return
def print_security_properties(self, filename):
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_read_canary()[0]
msg+= "(value: {:#x})".format(canary)
gef_print("{:<30s}: {:s}".format(prop, msg))
if sec["Full RelRO"]:
gef_print("{:<30s}: {:s}".format("RelRO", Color.greenify("Full")))
elif sec["Partial RelRO"]:
gef_print("{:<30s}: {:s}".format("RelRO", Color.yellowify("Partial")))
else:
gef_print("{:<30s}: {:s}".format("RelRO", Color.redify(Color.boldify(CROSS))))
return
@register_command
class GotCommand(GenericCommand):
"""Display current status of the got inside the process."""
_cmdline_ = "got"
_syntax_ = "{:s} [FUNCTION_NAME ...] ".format(_cmdline_)
_example_ = "got read printf exit"
def __init__(self, *args, **kwargs):
super().__init__()
self.add_setting("function_resolved", "green", "Line color of the got command output if the function has "
"been resolved")
self.add_setting("function_not_resolved", "yellow", "Line color of the got command output if the function has "
"not been resolved")
return
def pre_load(self):
which("readelf")
return
def get_jmp_slots(self, readelf, filename):
output = []
cmd = [readelf, "--relocs", filename]
lines = gef_execute_external(cmd, as_list=True)
for line in lines:
if "JUMP" in line:
output.append(line)
return output
@only_if_gdb_running
def do_invoke(self, argv):
try:
readelf = which("readelf")
except IOError:
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 = get_process_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("\nGOT protection: {} | GOT functions: {}\n ".format(relro_status, len(jmpslots)))
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 = read_int_from_memory(address_val)
# for the swag: different colors if the function has been resolved or not.
if base_address < got_address < end_address:
color = self.get_setting("function_not_resolved") # function hasn't already been resolved
else:
color = self.get_setting("function_resolved") # function has already been resolved
line = "[{}] ".format(hex(address_val))
line += Color.colorify("{} {} {}".format(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_ = "{} (add|remove|list|clear)".format(_cmdline_)
_aliases_ = ["hl"]
def __init__(self):
super().__init__(prefix=True)
self.add_setting("regex", False, "Enable regex highlighting")
def do_invoke(self, argv):
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):
if not highlight_table:
return err("no matches found")
left_pad = max(map(len, highlight_table.keys()))
for match, color in sorted(highlight_table.items()):
print("{} {} {}".format(Color.colorify(match.ljust(left_pad), color), VERTICAL_LINE, Color.colorify(color, color)))
return
def do_invoke(self, argv):
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, argv):
return highlight_table.clear()
@register_command
class HighlightAddCommand(GenericCommand):
"""Add a match to the highlight table."""
_cmdline_ = "highlight add"
_syntax_ = "{} MATCH COLOR".format(_cmdline_)
_aliases_ = ["highlight set", "hla"]
_example_ = "{} 41414141 yellow".format(_cmdline_)
def do_invoke(self, argv):
if len(argv) < 2:
return self.usage()
match, color = argv
highlight_table[match] = color
return
@register_command
class HighlightRemoveCommand(GenericCommand):
"""Remove a match in the highlight table."""
_cmdline_ = "highlight remove"
_syntax_ = "{} MATCH".format(_cmdline_)
_aliases_ = [
"highlight delete",
"highlight del",
"highlight unset",
"highlight rm",
"hlr",
]
_example_ = "{} remove 41414141".format(_cmdline_)
def do_invoke(self, argv):
if not argv:
return self.usage()
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, argv):
dangerous_functions = {
"printf": 0,
"sprintf": 1,
"fprintf": 1,
"snprintf": 2,
"vsnprintf": 2,
}
enable_redirect_output("/dev/null")
for func_name, num_arg in dangerous_functions.items():
FormatStringBreakpoint(func_name, num_arg)
disable_redirect_output()
ok("Enabled {:d} FormatStringBreakpoint".format(len(dangerous_functions)))
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, *args, **kwargs):
super().__init__(complete=gdb.COMPLETE_NONE)
self.add_setting("check_free_null", False, "Break execution when a free(NULL) is encountered")
self.add_setting("check_double_free", True, "Break execution when a double free is encountered")
self.add_setting("check_weird_free", True, "Break execution when free() is called against a non-tracked pointer")
self.add_setting("check_uaf", True, "Break execution when a possible Use-after-Free condition is found")
self.add_setting("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):
if not argv:
self.setup()
return
if argv[0] == "show":
self.dump_tracked_allocations()
return
def setup(self):
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("{}: The heap analysis slows down the execution noticeably.".format(
Color.colorify("Note", "bold underline yellow")))
# when inferior quits, we need to clean everything for a next execution
gef_on_exit_hook(self.clean)
return
def dump_tracked_allocations(self):
global __heap_allocated_list__, __heap_freed_list__, __heap_uaf_watchpoints__
if __heap_allocated_list__:
ok("Tracked as in-use chunks:")
for addr, sz in __heap_allocated_list__: gef_print("{} malloc({:d}) = {:#x}".format(CROSS, sz, addr))
else:
ok("No malloc() chunk tracked")
if __heap_freed_list__:
ok("Tracked as free-ed chunks:")
for addr, sz in __heap_freed_list__: gef_print("{} free({:d}) = {:#x}".format(TICK, sz, addr))
else:
ok("No free() chunk tracked")
return
def clean(self, event):
global __heap_allocated_list__, __heap_freed_list__, __heap_uaf_watchpoints__
ok("{} - Cleaning up".format(Color.colorify("Heap-Analysis", "yellow bold"),))
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 __heap_uaf_watchpoints__:
wp.delete()
__heap_allocated_list__ = []
__heap_freed_list__ = []
__heap_uaf_watchpoints__ = []
ok("{} - Re-enabling hardware watchpoints".format(Color.colorify("Heap-Analysis", "yellow bold"),))
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, argv):
insn = gef_current_instruction(current_arch.pc)
ok("Current instruction is{}a syscall".format(" " if self.is_syscall(current_arch, insn) else " not "))
return
def is_syscall(self, arch, instruction):
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):
super().__init__()
self.add_setting("path", os.path.join(get_gef_setting("gef.tempdir"), "syscall-tables"),
"Path to store/load the syscall tables files")
return
def do_invoke(self, argv):
color = get_gef_setting("theme.table_heading")
path = self.get_settings_path()
if path is None:
err("Cannot open '{0}': check directory and/or `gef config {0}` setting, "
"currently: '{1}'".format("syscall-args.path", self.get_setting("path")))
info("This setting can be configured by running gef-extras' install script.")
return
arch = current_arch.__class__.__name__
syscall_table = self.get_syscall_table(arch)
reg_value = get_register(current_arch.syscall_register)
if reg_value not in syscall_table:
warn("There is no system call for {:#x}".format(reg_value))
return
syscall_entry = syscall_table[reg_value]
values = []
for param in syscall_entry.params:
values.append(get_register(param.reg))
parameters = [s.param for s in syscall_entry.params]
registers = [s.reg for s in syscall_entry.params]
info("Detected syscall {}".format(Color.colorify(syscall_entry.name, color)))
gef_print(" {}({})".format(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 = " {:<20} {:<20} 0x{:x}".format(name, register, value)
addrs = dereference_from(value)
if len(addrs) > 1:
sep = " {:s} ".format(RIGHT_ARROW)
line += sep
line += sep.join(addrs[1:])
gef_print(line)
return
def get_filepath(self, x):
p = self.get_settings_path()
if not p: return None
return os.path.join(p, "{}.py".format(x))
def get_module(self, modname):
_fullname = self.get_filepath(modname)
return importlib.machinery.SourceFileLoader(modname, _fullname).load_module(None)
def get_syscall_table(self, modname):
_mod = self.get_module(modname)
return getattr(_mod, "syscall_table")
def get_settings_path(self):
path = os.path.expanduser(self.get_setting("path"))
path = os.path.realpath(path)
return path if os.path.isdir(path) else None
@lru_cache()
def get_section_base_address(name):
section = process_lookup_path(name)
if section:
return section.page_start
return None
@lru_cache()
def get_zone_base_address(name):
zone = file_lookup_name_path(name, get_filepath())
if zone:
return zone.zone_start
return None
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): pass
@property
def _syntax_(self):
return "${}([offset])".format(self._function_)
def __init__ (self):
super().__init__(self._function_)
def invoke(self, *args):
if not is_alive():
raise gdb.GdbError("No debugging session active")
return int(self.do_invoke(args))
def arg_to_long(self, args, index, default=0):
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): pass
@register_function
class StackOffsetFunction(GenericFunction):
"""Return the current stack base address plus an optional offset."""
_function_ = "_stack"
def do_invoke(self, args):
return self.arg_to_long(args, 0) + get_section_base_address("[stack]")
@register_function
class HeapBaseFunction(GenericFunction):
"""Return the current heap base address plus an optional offset."""
_function_ = "_heap"
def do_invoke(self, args):
base = HeapBaseFunction.heap_base()
if not base:
raise gdb.GdbError("Heap not found")
return self.arg_to_long(args, 0) + base
@staticmethod
def heap_base():
try:
base = parse_address("mp_->sbrk_base")
if base != 0:
return base
except gdb.error:
pass
return get_section_base_address("[heap]")
@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):
try:
name = args[0].string()
except IndexError:
name = get_filename()
except gdb.error:
err("Invalid arg: {}".format(args[0]))
return 0
try:
addr = int(get_section_base_address(name))
except TypeError:
err("Cannot find section {}".format(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):
return self.arg_to_long(args, 0) + get_zone_base_address(".bss")
@register_function
class GotBaseFunction(GenericFunction):
"""Return the current bss base address plus the given offset."""
_function_ = "_got"
def do_invoke(self, args):
return self.arg_to_long(args, 0) + get_zone_base_address(".got")
@register_command
class GefFunctionsCommand(GenericCommand):
"""List the convenience functions provided by GEF."""
_cmdline_ = "functions"
_syntax_ = _cmdline_
def __init__(self):
super().__init__()
self.docs = []
self.setup()
return
def setup(self):
global __gef__
for function in __gef__.loaded_functions:
self.add_function_to_doc(function)
self.__doc__ = "\n".join(sorted(self.docs))
return
def add_function_to_doc(self, function):
"""Add function to documentation."""
doc = getattr(function, "__doc__", "").lstrip()
doc = "\n ".join(doc.split("\n"))
syntax = getattr(function, "_syntax_", "").lstrip()
msg = "{syntax:<25s} -- {help:s}".format(syntax=syntax, help=Color.greenify(doc))
example = getattr(function, "_example_", "").strip()
if example:
msg += "\n {padding:27s} example: {example:s}".format(
padding="", example=Color.yellowify(example))
self.docs.append(msg)
return
def do_invoke(self, argv):
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
class GefCommand(gdb.Command):
"""GEF main command: view all new commands by typing `gef`."""
_cmdline_ = "gef"
_syntax_ = "{:s} (missing|config|save|restore|set|run)".format(_cmdline_)
def __init__(self):
super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, True)
set_gef_setting("gef.follow_child", True, bool, "Automatically set GDB to follow child when forking")
set_gef_setting("gef.readline_compat", False, bool, "Workaround for readline SOH/ETX issue (SEGV)")
set_gef_setting("gef.debug", False, bool, "Enable debug mode for gef")
set_gef_setting("gef.autosave_breakpoints_file", "", str, "Automatically save and restore breakpoints")
set_gef_setting("gef.extra_plugins_dir", "", str, "Autoload additional GEF commands from external directory")
set_gef_setting("gef.disable_color", False, bool, "Disable all colors in GEF")
set_gef_setting("gef.tempdir", GEF_TEMP_DIR, str, "Directory to use for temporary/cache content")
self.loaded_commands = []
self.loaded_functions = []
self.missing_commands = {}
return
def setup(self):
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):
bkp_fname = __config__.get("gef.autosave_breakpoints_file", None)
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("source {:s}".format(bkp_fname))
# add hook for autosave breakpoints on quit command
source = [
"define hook-quit",
" save breakpoints {:s}".format(bkp_fname),
"end",
]
gef_execute_gdb_script("\n".join(source) + "\n")
return
def __load_extra_plugins(self):
nb_added = -1
try:
nb_inital = len(self.loaded_commands)
directories = get_gef_setting("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 = "{:s}/{:s}".format(directory, fname)
if os.path.isfile(fpath):
gdb.execute("source {:s}".format(fpath))
nb_added = len(self.loaded_commands) - nb_inital
if nb_added > 0:
ok("{:s} extra commands added from '{:s}'".format(Color.colorify(nb_added, "bold green"),
Color.colorify(directories, "bold blue")))
except gdb.error as e:
err("failed: {}".format(str(e)))
return nb_added
@property
def loaded_command_names(self):
return [x[0] for x in self.loaded_commands]
def invoke(self, args, from_tty):
self.dont_repeat()
gdb.execute("gef help")
return
def add_context_pane(self, pane_name, display_pane_function, pane_title_function):
"""Add a new context pane to ContextCommand."""
for _, _, class_obj in self.loaded_commands:
if isinstance(class_obj, ContextCommand):
context_obj = class_obj
break
# assure users can toggle the new context
corrected_settings_name = pane_name.replace(" ", "_")
layout_settings = context_obj.get_setting("layout")
context_obj.update_setting("layout", "{} {}".format(layout_settings, corrected_settings_name))
# overload the printing of pane title
context_obj.layout_mapping[corrected_settings_name] = (display_pane_function, pane_title_function)
def load(self, initial=False):
"""Load all the commands and functions defined by GEF into GDB."""
nb_missing = 0
self.commands = [(x._cmdline_, x) for x in __commands__]
# load all of the functions
for function_class_name in __functions__:
self.loaded_functions.append(function_class_name())
def is_loaded(x):
return any(u for u in self.loaded_commands if x == u[0])
for cmd, class_name in self.commands:
if is_loaded(cmd):
continue
try:
self.loaded_commands.append((cmd, class_name, class_name()))
if hasattr(class_name, "_aliases_"):
aliases = getattr(class_name, "_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("{:s} for {:s} ready, type `{:s}' to start, `{:s}' to configure"
.format(Color.greenify("GEF"), get_os(),
Color.colorify("gef", "underline yellow"),
Color.colorify("gef config", "underline pink")))
ver = "{:d}.{:d}".format(sys.version_info.major, sys.version_info.minor)
nb_cmds = len(self.loaded_commands)
gef_print("{:s} commands loaded for GDB {:s} using Python engine {:s}"
.format(Color.colorify(nb_cmds, "bold green"),
Color.colorify(gdb.VERSION, "bold yellow"),
Color.colorify(ver, "bold red")))
if nb_missing:
warn("{:s} command{} could not be loaded, run `{:s}` to know why."
.format(Color.colorify(nb_missing, "bold red"),
"s" if nb_missing > 1 else "",
Color.colorify("gef missing", "underline pink")))
return
class GefHelpCommand(gdb.Command):
"""GEF help sub-command."""
_cmdline_ = "gef help"
_syntax_ = _cmdline_
def __init__(self, commands, *args, **kwargs):
super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False)
self.docs = []
self.generate_help(commands)
self.refresh()
return
def invoke(self, args, from_tty):
self.dont_repeat()
gef_print(titlify("GEF - GDB Enhanced Features"))
gef_print(self.__doc__)
return
def generate_help(self, commands):
"""Generate builtin commands documentation."""
for command in commands:
self.add_command_to_doc(command)
return
def add_command_to_doc(self, command):
"""Add command to GEF documentation."""
cmd, class_name, _ = command
if " " in cmd:
# do not print subcommands in gef help
return
doc = getattr(class_name, "__doc__", "").lstrip()
doc = "\n ".join(doc.split("\n"))
aliases = " (alias: {:s})".format(", ".join(class_name._aliases_)) if hasattr(class_name, "_aliases_") else ""
msg = "{cmd:<25s} -- {help:s}{aliases:s}".format(cmd=cmd, help=doc, aliases=aliases)
self.docs.append(msg)
return
def refresh(self):
"""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_ = "{:s} [setting_name] [setting_value]".format(_cmdline_)
def __init__(self, loaded_commands, *args, **kwargs):
super().__init__(self._cmdline_, gdb.COMMAND_NONE, prefix=False)
self.loaded_commands = loaded_commands
return
def invoke(self, args, from_tty):
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 __config__.keys() if x.startswith(prefix)]
if names:
if len(names) == 1:
gef_print(titlify("GEF configuration setting: {:s}".format(names[0])))
self.print_setting(names[0], verbose=True)
else:
gef_print(titlify("GEF configuration settings matching '{:s}'".format(argv[0])))
for name in names: self.print_setting(name)
return
self.set_setting(argc, argv)
return
def print_setting(self, plugin_name, verbose=False):
res = __config__.get(plugin_name)
string_color = get_gef_setting("theme.dereference_string")
misc_color = get_gef_setting("theme.dereference_base_address")
if not res:
return
_value, _type, _desc = res
_setting = Color.colorify(plugin_name, "green")
_type = _type.__name__
if _type == "str":
_value = '"{:s}"'.format(Color.colorify(_value, string_color))
else:
_value = Color.colorify(_value, misc_color)
gef_print("{:s} ({:s}) = {:s}".format(_setting, _type, _value))
if verbose:
gef_print(Color.colorify("\nDescription:", "bold underline"))
gef_print("\t{:s}".format(_desc))
return
def print_settings(self):
for x in sorted(__config__):
self.print_setting(x)
return
def set_setting(self, argc, argv):
global __gef__
if "." not in argv[0]:
err("Invalid command format")
return
loaded_commands = [ x[0] for x in __gef__.loaded_commands ] + ["gef"]
plugin_name = argv[0].split(".", 1)[0]
if plugin_name not in loaded_commands:
err("Unknown plugin '{:s}'".format(plugin_name))
return
_type = __config__.get(argv[0], [None, None, None])[1]
if _type is None:
err("Failed to get '{:s}' config setting".format(argv[0],))
return
try:
if _type == bool:
_newval = True if argv[1].upper() in ("TRUE", "T", "1") else False
else:
_newval = _type(argv[1])
except Exception:
err("{} expects type '{}'".format(argv[0], _type.__name__))
return
reset_all_caches()
__config__[argv[0]][0] = _newval
get_gef_setting.cache_clear()
return
def complete(self, text, word):
settings = sorted(__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, *args, **kwargs):
super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False)
return
def invoke(self, args, from_tty):
self.dont_repeat()
cfg = configparser.RawConfigParser()
old_sect = None
# save the configuration
for key in sorted(__config__):
sect, optname = key.split(".", 1)
value = __config__.get(key, None)
value = value[0] if value else None
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 __aliases__:
cfg.set("aliases", alias._alias, alias._command)
with open(GEF_RC, "w") as fd:
cfg.write(fd)
ok("Configuration saved to '{:s}'".format(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, *args, **kwargs):
super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False)
return
def invoke(self, args, from_tty):
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):
try:
key = "{:s}.{:s}".format(section, optname)
_type = __config__.get(key)[1]
new_value = cfg.get(section, optname)
if _type == bool:
new_value = True if new_value == "True" else False
else:
new_value = _type(new_value)
__config__[key][0] = new_value
except Exception:
pass
# ensure that the temporary directory always exists
gef_makedirs(__config__["gef.tempdir"][0])
if not quiet:
ok("Configuration from '{:s}' restored".format(Color.colorify(GEF_RC, "bold blue")))
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, *args, **kwargs):
super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False)
return
def invoke(self, args, from_tty):
self.dont_repeat()
missing_commands = __gef__.missing_commands.keys()
if not missing_commands:
ok("No missing command")
return
for missing_command in missing_commands:
reason = __gef__.missing_commands[missing_command]
warn("Command `{}` is missing, reason {} {}".format(missing_command, RIGHT_ARROW, reason))
return
class GefSetCommand(gdb.Command):
"""Override GDB set commands with the context from GEF."""
_cmdline_ = "gef set"
_syntax_ = "{:s} [GDB_SET_ARGUMENTS]".format(_cmdline_)
def __init__(self, *args, **kwargs):
super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_SYMBOL, False)
return
def invoke(self, args, from_tty):
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_ = "{:s} [GDB_RUN_ARGUMENTS]".format(_cmdline_)
def __init__(self, *args, **kwargs):
super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_FILENAME, False)
return
def invoke(self, args, from_tty):
self.dont_repeat()
if is_alive():
gdb.execute("continue")
return
argv = args.split()
gdb.execute("gef set args {:s}".format(" ".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, command, completer_class=gdb.COMPLETE_NONE, command_class=gdb.COMMAND_NONE):
p = command.split()
if not p:
return
if any(x for x in __aliases__ if x._alias == alias):
return
self._command = command
self._alias = alias
c = command.split()[0]
r = self.lookup_command(c)
self.__doc__ = "Alias for '{}'".format(Color.greenify(command))
if r is not None:
_instance = r[2]
self.__doc__ += ": {}".format(_instance.__doc__)
if hasattr(_instance, "complete"):
self.complete = _instance.complete
super().__init__(alias, command_class, completer_class=completer_class)
__aliases__.append(self)
return
def invoke(self, args, from_tty):
gdb.execute("{} {}".format(self._command, args), from_tty=from_tty)
return
def lookup_command(self, cmd):
global __gef__
for _name, _class, _instance in __gef__.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_ = "{:s} (add|rm|ls)".format(_cmdline_)
def __init__(self):
super().__init__(prefix=True)
return
def do_invoke(self, argv):
self.usage()
return
@register_command
class AliasesAddCommand(AliasesCommand):
"""Command to add aliases."""
_cmdline_ = "aliases add"
_syntax_ = "{0} [ALIAS] [COMMAND]".format(_cmdline_)
_example_ = "{0} scope telescope".format(_cmdline_)
def __init__(self):
super().__init__()
return
def do_invoke(self, argv):
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_ = "{0} [ALIAS]".format(_cmdline_)
def __init__(self):
super().__init__()
return
def do_invoke(self, argv):
global __aliases__
if len(argv) != 1:
self.usage()
return
try:
alias_to_remove = next(filter(lambda x: x._alias == argv[0], __aliases__))
__aliases__.remove(alias_to_remove)
except (ValueError, StopIteration):
err("{0} not found in aliases.".format(argv[0]))
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):
super().__init__()
return
def do_invoke(self, argv):
ok("Aliases defined:")
for a in __aliases__:
gef_print("{:30s} {} {}".format(a._alias, RIGHT_ARROW, a._command))
return
class GefTmuxSetup(gdb.Command):
"""Setup a confortable tmux debugging environment."""
def __init__(self):
super().__init__("tmux-setup", gdb.COMMAND_NONE, gdb.COMPLETE_NONE)
GefAlias("screen-setup", "tmux-setup")
return
def invoke(self, args, from_tty):
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):
"""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("! {} split-window -h 'clear ; cat'".format(tmux))
gdb.execute("! {} select-pane -L".format(tmux))
new_ptses = set(os.listdir("/dev/pts"))
pty = list(new_ptses - old_ptses)[0]
pty = "/dev/pts/{}".format(pty)
ok("Setting `context.redirect` to '{}'...".format(pty))
gdb.execute("gef config context.redirect {}".format(pty))
ok("Done!")
return
def screen_setup(self):
"""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("screen bash -c 'tty > {}; clear; cat'\n".format(tty_path))
f.write("focus left\n")
gdb.execute("""! {} -r {} -m -d -X source {}""".format(screen, sty, 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("Setting `context.redirect` to '{}'...".format(pty))
gdb.execute("gef config context.redirect {}".format(pty))
ok("Done!")
os.unlink(script_path)
os.unlink(tty_path)
return
def __gef_prompt__(current_prompt):
"""GEF custom prompt function."""
if get_gef_setting("gef.readline_compat") is True: return GEF_PROMPT
if get_gef_setting("gef.disable_color") is True: return GEF_PROMPT
if is_alive(): return GEF_PROMPT_ON
return GEF_PROMPT_OFF
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.")
elif GDB_VERSION < GDB_MIN_VERSION:
err("You're using an old version of GDB. GEF will not work correctly. "
"Consider updating to GDB {} or higher.".format(".".join(map(str, GDB_MIN_VERSION))))
else:
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",
"python{}".format(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.execute("set confirm off")
gdb.execute("set verbose off")
gdb.execute("set pagination off")
gdb.execute("set print elements 0")
# gdb history
gdb.execute("set history save on")
gdb.execute("set history filename ~/.gdb_history")
# gdb input and output bases
gdb.execute("set output-radix 0x10")
# pretty print
gdb.execute("set print pretty on")
try:
# this will raise a gdb.error unless we're on x86
gdb.execute("set disassembly-flavor intel")
except gdb.error:
# we can safely ignore this
pass
# SIGALRM will simply display a message, but gdb won't forward the signal to the process
gdb.execute("handle SIGALRM print nopass")
# load GEF
__gef__ = GefCommand()
__gef__.setup()
tempdir = get_gef_setting("gef.tempdir")
gef_makedirs(tempdir)
gdb.execute("save gdb-index {}".format(tempdir))
# 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()