Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
9984 lines (8024 sloc) 341 KB
# -*- 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.
#
# It has full support for both Python2 and Python3 and works on
# * x86-32 & x86-64
# * arm v5,v6,v7
# * aarch64 (armv8)
# * mips & mips64
# * powerpc & powerpc64
# * sparc & sparc64(v9)
#
# Requires GDB 7.x compiled with Python (2.x, or 3.x)
#
# To start: in gdb, type `source /path/to/gef.py`
#
#######################################################################################
#
# gef is distributed under the MIT License (MIT)
# Copyright (c) 2013-2019 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.
#
#
from __future__ import print_function, division, absolute_import
import abc
import binascii
import codecs
import collections
import ctypes
import fcntl
import functools
import getopt
import hashlib
import imp
import inspect
import itertools
import os
import platform
import re
import shutil
import site
import socket
import string
import struct
import subprocess
import sys
import tempfile
import termios
import time
import traceback
PYTHON_MAJOR = sys.version_info[0]
if PYTHON_MAJOR == 2:
from HTMLParser import HTMLParser #pylint: disable=import-error
from cStringIO import StringIO #pylint: disable=import-error
from urllib import urlopen #pylint: disable=no-name-in-module
import ConfigParser as configparser #pylint: disable=import-error
import xmlrpclib #pylint: disable=import-error
# Compat Py2/3 hacks
def range(*args):
"""Replace range() builtin with an iterator version."""
if len(args) < 1:
raise TypeError()
start, end, step = 0, args[0], 1
if len(args) == 2: start, end = args
if len(args) == 3: start, end, step = args
for n in itertools.count(start=start, step=step):
if (step>0 and n >= end) or (step<0 and n<=end): break
yield n
FileNotFoundError = IOError #pylint: disable=redefined-builtin
ConnectionRefusedError = socket.error #pylint: disable=redefined-builtin
LEFT_ARROW = "<-"
RIGHT_ARROW = "->"
DOWN_ARROW = "\\->"
HORIZONTAL_LINE = "-"
VERTICAL_LINE = "|"
CROSS = "x"
TICK = "v"
GEF_PROMPT = "gef> "
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)
elif PYTHON_MAJOR == 3:
from html.parser import HTMLParser #pylint: disable=import-error
from io import StringIO
from urllib.request import urlopen #pylint: disable=import-error,no-name-in-module
import configparser
import xmlrpc.client as xmlrpclib #pylint: disable=import-error
# Compat Py2/3 hack
long = int
unicode = str
LEFT_ARROW = " \u2190 "
RIGHT_ARROW = " \u2192 "
DOWN_ARROW = "\u21b3"
HORIZONTAL_LINE = "\u2500"
VERTICAL_LINE = "\u2502"
CROSS = "\u2718 "
TICK = "\u2713 "
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)
else:
raise Exception("WTF is this Python version??")
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. Return 0 on success,
1 on failure. """
gef_local = os.path.realpath(argv[0])
hash_gef_local = hashlib.sha512(open(gef_local, "rb").read()).digest()
gef_remote = "https://raw.githubusercontent.com/hugsy/gef/master/gef.py"
gef_remote_data = http_get(gef_remote)
if gef_remote_data is None:
print("[-] Failed to get remote gef")
return 1
hash_gef_remote = hashlib.sha512(gef_remote_data).digest()
if hash_gef_local == hash_gef_remote:
print("[-] No update")
else:
with open(gef_local, "wb") as f:
f.write(gef_remote_data)
print("[+] Updated")
return 0
try:
import gdb
except ImportError:
# if out of gdb, the only action allowed is to update gef.py
if len(sys.argv)==2 and sys.argv[1]=="--update":
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_default_main_arena__ = "main_arena"
__gef_int_stream_buffer__ = None
DEFAULT_PAGE_ALIGN_SHIFT = 12
DEFAULT_PAGE_SIZE = 1 << DEFAULT_PAGE_ALIGN_SHIFT
GEF_RC = 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_MAJOR, GDB_VERSION_MINOR = [int(_) for _ in re.search(r"(\d+)[^\d]+(\d+)", gdb.VERSION).groups()]
GDB_VERSION = (GDB_VERSION_MAJOR, GDB_VERSION_MINOR)
current_elf = None
current_arch = None
highlight_table = {}
ANSI_SPLIT_RE = "(\033\[[\d;]*m)"
if PYTHON_MAJOR==3:
lru_cache = functools.lru_cache #pylint: disable=no-member
else:
def lru_cache(maxsize = 128):
"""Port of the Python3 LRU cache mechanism provided by itertools."""
class GefLruCache(object):
"""Local LRU cache for Python2."""
def __init__(self, input_func, max_size):
self._input_func = input_func
self._max_size = max_size
self._caches_dict = {}
self._caches_info = {}
return
def cache_info(self, caller=None):
"""Return a string with statistics of cache usage."""
if caller not in self._caches_dict:
return ""
hits = self._caches_info[caller]["hits"]
missed = self._caches_info[caller]["missed"]
cursz = len(self._caches_dict[caller])
return "CacheInfo(hits={}, misses={}, maxsize={}, currsize={})".format(hits, missed, self._max_size, cursz)
def cache_clear(self, caller=None):
"""Clear a cache."""
if caller in self._caches_dict:
self._caches_dict[caller] = collections.OrderedDict()
return
def __get__(self, obj, objtype):
"""Cache getter."""
return_func = functools.partial(self._cache_wrapper, obj)
return_func.cache_clear = functools.partial(self.cache_clear, obj)
return functools.wraps(self._input_func)(return_func)
def __call__(self, *args, **kwargs):
"""Invoking the wrapped function, by attempting to get its value from cache if existing."""
return self._cache_wrapper(None, *args, **kwargs)
__call__.cache_clear = cache_clear
__call__.cache_info = cache_info
def _cache_wrapper(self, caller, *args, **kwargs):
"""Defines the caching mechanism."""
kwargs_key = "".join(map(lambda x : str(x) + str(type(kwargs[x])) + str(kwargs[x]), sorted(kwargs)))
key = "".join(map(lambda x : str(type(x)) + str(x) , args)) + kwargs_key
if caller not in self._caches_dict:
self._caches_dict[caller] = collections.OrderedDict()
self._caches_info[caller] = {"hits":0, "missed":0}
cur_caller_cache_dict = self._caches_dict[caller]
if key in cur_caller_cache_dict:
self._caches_info[caller]["hits"] += 1
return cur_caller_cache_dict[key]
self._caches_info[caller]["missed"] += 1
if self._max_size is not None:
if len(cur_caller_cache_dict) >= self._max_size:
cur_caller_cache_dict.popitem(False)
cur_caller_cache_dict[key] = self._input_func(caller, *args, **kwargs) if caller != None else self._input_func(*args, **kwargs)
return cur_caller_cache_dict[key]
return lambda input_func: functools.wraps(input_func)(GefLruCache(input_func, maxsize))
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."""
for mod in dir(sys.modules["__main__"]):
obj = getattr(sys.modules["__main__"], mod)
if hasattr(obj, "cache_clear"):
obj.cache_clear()
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__
if __gef_int_stream_buffer__:
return f(*args, **kwargs)
__gef_int_stream_buffer__ = StringIO()
try:
rv = f(*args, **kwargs)
finally:
sys.stdout.write(__gef_int_stream_buffer__.getvalue())
sys.stdout.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",
"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 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(long(self.value))
derefed = dereference(addr)
return None if derefed is None else long(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
X86_64 = 0x3e
X86_32 = 0x03
ARM = 0x28
MIPS = 0x08
POWERPC = 0x14
POWERPC64 = 0x15
SPARC = 0x02
SPARC64 = 0x2b
AARCH64 = 0xb7
RISCV = 0xf3
ET_EXEC = 2
ET_DYN = 3
ET_CORE = 4
e_magic = b"\x7fELF"
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
with open(elf, "rb") as fd:
# off 0x0
self.e_magic, self.e_class, self.e_endianness, self.e_eiversion = struct.unpack(">IBBB", fd.read(7))
# adjust endianness in bin reading
endian = "<" if self.e_endianness == Elf.LITTLE_ENDIAN else ">"
# off 0x7
self.e_osabi, self.e_abiversion = struct.unpack("{}BB".format(endian), fd.read(2))
# off 0x9
self.e_pad = fd.read(7)
# off 0x10
self.e_type, self.e_machine, self.e_version = struct.unpack("{}HHI".format(endian), fd.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), fd.read(24))
else:
# else arch 32bits
self.e_entry, self.e_phoff, self.e_shoff = struct.unpack("{}III".format(endian), fd.read(12))
self.e_flags, self.e_ehsize, self.e_phentsize, self.e_phnum = struct.unpack("{}HHHH".format(endian), fd.read(8))
self.e_shentsize, self.e_shnum, self.e_shstrndx = struct.unpack("{}HHH".format(endian), fd.read(6))
return
class Instruction:
"""GEF representation of a CPU instruction."""
def __init__(self, address, location, mnemo, operands):
self.address, self.location, self.mnemonic, self.operands = address, location, mnemo, operands
return
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_default_main_arena__
malloc_hook_addr = to_unsigned_long(gdb.parse_and_eval("(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_default_main_arena__ = "*0x{:x}".format(addr)
return addr
class MallocStateStruct(object):
"""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 = to_unsigned_long(gdb.parse_and_eval("&{}".format(addr)))
except gdb.error:
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)
if get_libc_version() >= (2, 26):
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 GlibcArena:
"""Glibc arena class
Ref: https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1671 """
TCACHE_MAX_BINS = 0x40
def __init__(self, addr, name=None):
self.__name = name or __gef_default_main_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 = long(arena.address)
except:
self.__arena = MallocStateStruct(addr)
self.__addr = self.__arena.addr
return
def __getitem__(self, item):
return self.__arena[item]
def __getattr__(self, item):
return self.__arena[item]
def __int__(self):
return self.__addr
def tcachebin(self, i):
"""Return head chunk in tcache[i]."""
heap_base = HeapBaseFunction.heap_base()
addr = dereference(heap_base + 2*current_arch.ptrsize + self.TCACHE_MAX_BINS + i*current_arch.ptrsize)
if not addr:
return None
return GlibcChunk(long(addr))
def fastbin(self, i):
"""Return head chunk in fastbinsY[i]."""
addr = dereference_as_long(self.fastbinsY[i])
if addr == 0:
return None
return GlibcChunk(addr + 2 * current_arch.ptrsize)
def bin(self, i):
idx = i * 2
fd = dereference_as_long(self.bins[idx])
bw = dereference_as_long(self.bins[idx + 1])
return fd, bw
def get_next(self):
addr_next = dereference_as_long(self.next)
arena_main = GlibcArena(self.__name)
if addr_next == arena_main.__addr:
return None
return GlibcArena("*{:#x} ".format(addr_next))
def __str__(self):
top = dereference_as_long(self.top)
last_remainder = dereference_as_long(self.last_remainder)
n = dereference_as_long(self.next)
nfree = dereference_as_long(self.next_free)
sysmem = long(self.system_mem)
fmt = "Arena (base={:#x}, top={:#x}, last_remainder={:#x}, next={:#x}, next_free={:#x}, system_mem={:#x})"
return fmt.format(self.__addr, top, last_remainder, n, nfree, sysmem)
class GlibcChunk:
"""Glibc chunk class.
Ref: https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/."""
def __init__(self, addr, from_base=False):
self.ptrsize = current_arch.ptrsize
if from_base:
self.chunk_base_address = addr
self.address = addr + 2 * self.ptrsize
else:
self.chunk_base_address = int(addr - 2 * self.ptrsize)
self.address = addr
self.size_addr = int(self.address - self.ptrsize)
self.prev_size_addr = self.chunk_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):
addr = self.address + self.get_chunk_size()
return GlibcChunk(addr)
# if free-ed functions
def get_fwd_ptr(self):
return read_int_from_memory(self.address)
@property
def fwd(self):
return self.get_fwd_ptr()
fd = fwd # for compat
def get_bkw_ptr(self):
return read_int_from_memory(self.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.chunk_base_address))
if failed:
msg.append(self.str_chunk_size_flag())
return "\n".join(msg)
def _str_pointers(self):
fwd = self.address
bkw = self.address + self.ptrsize
msg = []
try:
msg.append("Forward pointer: {0:#x}".format(self.get_fwd_ptr()))
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"),
long(self.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"
@lru_cache()
def get_libc_version():
sections = get_process_maps()
try:
for section in sections:
if "libc-" in section.path:
libc_version = tuple(int(_) for _ in
re.search(r"libc-(\d+)\.(\d+)\.so", section.path).groups())
break
else:
libc_version = 0, 0
except AttributeError:
libc_version = 0, 0
return libc_version
@lru_cache()
def get_main_arena():
try:
return GlibcArena(__gef_default_main_arena__)
except Exception as e:
err("Failed to get the main arena, heap commands may not work properly: {}".format(e))
return None
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))
__data = open(fname, "r").read().splitlines()
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(" 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}) on {:s}".format(platform.system(), platform.release(),
platform.architecture()[0],
" ".join(platform.dist()))) #pylint: disable=deprecated-method
gef_print(HORIZONTAL_LINE*80)
gef_print("")
return
def gef_pystring(x):
"""Python 2 & 3 compatibility function for strings handling."""
res = str(x, encoding="utf-8") if PYTHON_MAJOR == 3 else x
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):
"""Python 2 & 3 compatibility function for bytes handling."""
return bytes(str(x), encoding="utf-8") if PYTHON_MAJOR == 3 else x
@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, 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])
sym = gdb_get_location_from_symbol(base+i)
sym = "<{:s}+{:04x}>".format(*sym) if sym else ""
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)
reset_all_caches()
return
def gef_makedirs(path, mode=0o755):
"""Recursive mkdir() creation. If successful, return the absolute path of the directory created."""
abspath = os.path.realpath(path)
if os.path.isdir(abspath):
return abspath
if PYTHON_MAJOR == 3:
os.makedirs(path, mode=mode, exist_ok=True) #pylint: disable=unexpected-keyword-arg
else:
try:
os.makedirs(path, mode=mode)
except os.error:
pass
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 ""
yield Instruction(address, location, mnemo, operands)
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 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, count=n+1))
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 is correct
if len(insns)!=n+1: # we expect the current instruction plus the n before it
continue
# 2. check all instructions are valid
for insn in insns:
if not insn.is_valid():
continue
# 3. if cur_insn is at the end of the set
if insns[-1].address==cur_insn_addr:
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."""
count = nb_insn + 1 if nb_insn & 1 else 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=count):
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)
capstone = sys.modules["capstone"]
arch, mode = get_capstone_arch(arch=kwargs.get("arch", None), mode=kwargs.get("mode", None), endian=kwargs.get("endian", None))
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."""
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:
# architecture can be auto detected
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:
# architecture can be assumed
arch_str = arch_str.replace("The target architecture is assumed to be ", "")
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."""
if is_alive():
return get_elf_headers().e_endianness
if gdb.execute("show endian", to_string=True).strip().split()[7] == "little" :
return Elf.LITTLE_ENDIAN
raise EnvironmentError("Invalid endianess")
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(object):
"""Generic metaclass for the architecture supported by GEF."""
__metaclass__ = abc.ABCMeta
@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):
"""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", "$x4", "$t0", "$t1",
"$t2", "$fp", "$s1", "$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_register = "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_elf32():
if v & 0x80000000:
return v - 0x100000000
elif is_elf64():
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
# return b"\x00\x00\xa0\xe1" # mov r0,r0
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"]
@lru_cache()
def is_thumb(self):
"""Determine if the machine is currently in THUMB mode."""
return is_alive() and get_register("$cpsr") & (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"}
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"
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 r0, {:d}".format(addr),
"mov r1, {:d}".format(size),
"mov r2, {:d}".format(perm),
"mov r7, {:d}".format(_NR_mprotect),
"svc 0",
"pop {r0-r2, r7}",]
return "; ".join(insns)
class AARCH64(ARM):
arch = "ARM64"
mode = "ARM"
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):
raise OSError("Architecture {:s} not supported yet".format(cls.arch))
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
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 {"cbnz", "cbz", "tbnz", "tbz"}:
reg = 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(AARCH64, self).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):
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",
"mov rax, {:d}".format(_NR_mprotect),
"mov rdi, {:d}".format(addr),
"mov rsi, {:d}".format(size),
"mov rdx, {:d}".format(perm),
"syscall",
"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):
# TODO: rett?
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`."""
if PYTHON_MAJOR == 2: buffer = str(buffer)
return gdb.selected_inferior().write_memory(address, buffer, length)
def read_memory(addr, length=0x10):
"""Return a `length` long byte array with the copy of the process memory at `addr`."""
if PYTHON_MAJOR == 2:
return gdb.selected_inferior().read_memory(addr, length)
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)
fmt = "{}{}".format(endian_str(), "I" if sz==4 else "Q")
return struct.unpack(fmt, mem)[0]
def read_cstring_from_memory(address, max_length=GEF_MAX_STRING_LENGTH, encoding=None):
"""Return a C-string read from memory."""
if not encoding:
encoding = "unicode_escape" if PYTHON_MAJOR==3 else "ascii"
char_ptr = cached_lookup_type("char").pointer()
try:
res = gdb.Value(address).cast(char_ptr).string(encoding=encoding).strip()
except gdb.error:
length = min(address|(DEFAULT_PAGE_SIZE-1), max_length+1)
mem = bytes(read_memory(address, length)).decode("utf-8")
res = mem.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, unicode) and cstr and all([x in string.printable for x in cstr]):
return cstr
return None
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 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 use_stdtype():
if is_elf32(): return "uint32_t"
elif is_elf64(): return "uint64_t"
return "uint16_t"
def use_default_type():
if is_elf32(): return "unsigned int"
elif is_elf64(): return "unsigned long"
return "unsigned short"
def use_golang_type():
if is_elf32(): return "uint32"
elif is_elf64(): return "uint64"
return "uint16"
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."""
try:
value = gdb.parse_and_eval(regname)
return to_unsigned_long(value) if value.type.code == gdb.TYPE_CODE_INT else long(value)
except gdb.error:
value = gdb.selected_frame().read_register(regname)
return long(value)
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 get_pid():
"""Return the PID of the debuggee process."""
return gdb.selected_inferior().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 __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(get_filepath())
def download_file(target, use_cache=False, local_name=None):
"""Download filename `target` inside the mirror tree inside the GEF_TEMP_DIR.
The tree architecture must be GEF_TEMP_DIR/gef/<local_pid>/<remote_filepath>.
This allow a "chroot-like" tree format."""
try:
local_root = os.path.sep.join([GEF_TEMP_DIR, 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:
# gdb-stub compat
with open(local_name, "w") as f:
if is_elf32():
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():
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."""
for line in open_file(proc_map_file, use_cache=False):
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 = list(map(lambda x: long(x, 16), addr.split("-")))
off = long(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
@lru_cache()
def get_process_maps():
"""Parse the `/proc/pid/maps` file."""
sections = []
try:
pid = get_pid()
fpath = "/proc/{:d}/maps".format(pid)
sections = get_process_maps_linux(fpath)
return list(sections)
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.strip() for x in line.split()]
addr_start, addr_end = [long(x, 16) for x in parts[1].split("->")]
off = long(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 = long(blobs[0], 16)
addr_end = long(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
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
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
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
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)
if PYTHON_MAJOR == 2:
return b"".join([chr(ord(x) ^ ord(y)) for x, y in zip(data, itertools.cycle(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()
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 get_terminal_size():
"""Return the current terminal size."""
if is_debug():
return 600, 100
try:
cmd = struct.unpack("hh", fcntl.ioctl(1, termios.TIOCGWINSZ, "1234"))
tty_rows, tty_columns = int(cmd[0]), int(cmd[1])
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)
return get_generic_arch(keystone, "KS", arch, mode, endian, to_string)
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)
@lru_cache()
def is_elf64(filename=None):
"""Checks if `filename` is an ELF64."""
elf = current_elf or get_elf_headers(filename)
return elf.e_class == Elf.ELF_64_BITS
@lru_cache()
def is_elf32(filename=None):
"""Checks if `filename` is an ELF32."""
elf = current_elf or get_elf_headers(filename)
return elf.e_class == Elf.ELF_32_BITS
@lru_cache()
def is_x86_64(filename=None):
"""Checks if `filename` is an x86-64 ELF."""
elf = current_elf or get_elf_headers(filename)
return elf.e_machine == Elf.X86_64
@lru_cache()
def is_x86_32(filename=None):
"""Checks if `filename` is an x86-32 ELF."""
elf = current_elf or get_elf_headers(filename)
return elf.e_machine == Elf.X86_32
@lru_cache()
def is_x86(filename=None):
return is_x86_32(filename) or is_x86_64(filename)
@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 ELF header. 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,
"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()))
current_elf = current_elf or get_elf_headers()
try:
current_arch = arches[current_elf.e_machine]()
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_elf32():
return 4 if not in_bits else 32
elif is_elf64():
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."""
if not tty:
gdb.execute("shell clear")
return
with open(tty, "w") as f:
f.write("\x1b[H\x1b[J")
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