From 236def24240bac2e18e90be7b0e076f271d72800 Mon Sep 17 00:00:00 2001 From: hugsy Date: Thu, 3 Feb 2022 18:28:57 -0800 Subject: [PATCH 01/39] - updated plugins for new gef api - improved `ida_interact` + docs --- docs/ida-rpyc.md | 53 +++++++ mkdocs.yml | 3 +- os/pe.py | 316 +++++++++++++++++++++++--------------- scripts/bincompare.py | 5 +- scripts/bytearray.py | 5 +- scripts/error.py | 9 +- scripts/ftrace.py | 7 +- scripts/ida_interact.py | 232 +++++++++++++++------------- scripts/peekpointers.py | 5 +- scripts/retdec.py | 33 ++-- scripts/skel.py | 10 +- scripts/stack.py | 11 +- scripts/v8-dereference.py | 26 ++-- scripts/visualize_heap.py | 4 +- scripts/windbg.py | 4 +- scripts/xref-telescope.py | 11 +- 16 files changed, 428 insertions(+), 306 deletions(-) create mode 100644 docs/ida-rpyc.md diff --git a/docs/ida-rpyc.md b/docs/ida-rpyc.md new file mode 100644 index 0000000..de0e236 --- /dev/null +++ b/docs/ida-rpyc.md @@ -0,0 +1,53 @@ +## Command ida-interact ## + +`gef` provides a simple XML-RPC client designed to communicate with a server +running inside a specific IDA Python plugin, called `ida_gef.py` (which +can be downloaded freely +[here](https://raw.githubusercontent.com/hugsy/gef/master/scripts/ida_gef.py)). + +Simply download this script, and run it inside IDA. When the server is running, +you should see some output: + +``` +[+] Creating new thread for XMLRPC server: Thread-1 +[+] Starting XMLRPC server: 0.0.0.0:1337 +[+] Registered 12 functions. +``` + +This indicates that IDA is ready to work with `gef`! + +`gef` can interact with it via the command `ida-interact` (alias `ida`). This +command expects the name of the function to execute as the first argument, all the +other arguments are the arguments of the remote function. + +To enumerate the functions available, simply run +``` +gef➤ ida-interact -h +``` +![gef-ida-help](https://i.imgur.com/JFNBfjY.png) + +Now, to execute an RPC, invoke the command `ida-interact` on the desired method, +with its arguments (if required). + +For example: +``` +gef➤ ida setcolor 0x40061E +``` +will edit the remote IDB and set the background color of the location 0x40061E +with the color 0x005500 (default value). + +Another convenient example is to add comment inside IDA directly from `gef`: +``` +gef➤ ida makecomm 0x40060C "<<<--- stack overflow" +[+] Success +``` + +Result: + +![gef-ida-example](https://i.imgur.com/jZ2eWG4.png) + +Please use the `-h` argument to see all the methods available and their syntax. + +It is also note-worthy that [Binary Ninja](https://binary.ninja) support has be added: +![](https://pbs.twimg.com/media/CzSso9bUAAArL1f.jpg:large), by using the +Binary Ninja plugin [`gef-binja.py`](https://github.com/hugsy/gef-binja). diff --git a/mkdocs.yml b/mkdocs.yml index edd3611..751e2b0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,4 +9,5 @@ nav: - RetDec: retdec.md - Skeleton: skel.md - WinDbg: windbg.md -- Error: error.md \ No newline at end of file +- Error: error.md +- ida-rpyc: ida_rpyc.md diff --git a/os/pe.py b/os/pe.py index e8b3cbd..60eb82a 100644 --- a/os/pe.py +++ b/os/pe.py @@ -1,9 +1,22 @@ -import struct -import os +""" +PE format support + +To use: +``` +gef➤ source /path/to/gef-extras/os/pe.py +gef➤ pi gef.binary = PE(get_filepath()) +gef➤ pi reset_architecture() +``` +""" -current_pe = None +import enum +import os +import pathlib +import struct +from functools import lru_cache +from typing import Dict, Tuple, Any, Optional class PE: """Basic PE parsing. @@ -11,135 +24,194 @@ class PE: - https://hshrzd.wordpress.com/pe-bear/ - https://blog.kowalczyk.info/articles/pefileformat.html """ - X86_64 = 0x8664 - X86_32 = 0x14c - ARM = 0x1c0 - ARM64 = 0xaa64 - ARMNT = 0x1c4 - AM33 = 0x1d3 - IA64 = 0x200 - EFI = 0xebc - MIPS = 0x166 - MIPS16 = 0x266 - MIPSFPU = 0x366 - MIPSFPU16 = 0x466 - WCEMIPSV2 = 0x169 - POWERPC = 0x1f0 - POWERPCFP = 0x1f1 - SH3 = 0x1a2 - SH3DSP = 0x1a3 - SH4 = 0x1a6 - SH5 = 0x1a8 - THUMP = 0x1c2 - RISCV32 = 0x5032 - RISCV64 = 0x5064 - RISCV128 = 0x5128 - M32R = 0x9041 - - dos_magic = b'MZ' - ptr_to_pe_header = None - pe_magic = b'PE' - machine = X86_32 - num_of_sections = None - size_of_opt_header = None - dll_charac = None - opt_magic = b'\x02\x0b' - entry_point = None - base_of_code = None - image_base = None - - - def __init__(self, pe=""): - if not os.access(pe, os.R_OK): - err("'{0}' not found/readable".format(pe)) - err("Failed to get file debug information, most of gef features will not work") - return - - with open(pe, "rb") as fd: - # off 0x0 - self.dos_magic = fd.read(2) - if self.dos_magic != PE.dos_magic: - self.machine = None - return - - # off 0x3c - fd.seek(0x3c) - self.ptr_to_pe_header, = struct.unpack(" str: + return super().__str__().lstrip(self.__class__.__name__+".") + + dos : DosHeader + file_header : ImageFileHeader + optional_header: OptionalHeader + entry_point : int + + def __init__(self, pe: str) -> None: + self.fpath = pathlib.Path(pe).expanduser().resolve() + + if not os.access(self.fpath, os.R_OK): + raise FileNotFoundError(f"'{self.fpath}' not found/readable, most gef features will not work") + + endian = gef.arch.endianness + with self.fpath.open("rb") as self.fd: + # Parse IMAGE_DOS_HEADER + self.dos = self.DosHeader() + + self.dos.e_magic = self.read_and_unpack("!H")[0] + if self.dos.e_magic != PE.Constants.DOS_MAGIC: + raise Exception(f"Corrupted PE file (bad DOS magic, expected '{PE.Constants.DOS_MAGIC:x}', got '{self.dos.e_magic:x}'") + + self.fd.seek(0x3c, 0) + self.dos.e_lfanew = u32(self.fd.read(4)) + + self.fd.seek(self.dos.e_lfanew, 0) + pe_magic = u32(self.fd.read(4)) + if pe_magic != PE.Constants.NT_MAGIC: + raise Exception("Corrupted PE file (bad PE magic)") + + # Parse IMAGE_FILE_HEADER + self.file_header = self.ImageFileHeader() + + machine, self.file_header.NumberOfSections = self.read_and_unpack(f"{endian}HH") + self.file_header.Machine = PE.MachineType(machine) + + self.file_header.TimeDateStamp, self.file_header.PointerToSymbolTable, \ + self.file_header.NumberOfSymbols, self.file_header.SizeOfOptionalHeader, \ + dll_characteristics = self.read_and_unpack(f"{endian}IIIHH") + + self.file_header.Characteristics = PE.DllCharacteristics(dll_characteristics) + + # Parse IMAGE_OPTIONAL_HEADER + self.optional_header = self.OptionalHeader() + + self.fd.seek(0x10, 1) + + self.optional_header.AddressOfEntryPoint, self.optional_header.BaseOfCode,\ + self.optional_header.BaseOfData, self.optional_header.ImageBase = self.read_and_unpack(f"{endian}IIII") + + self.entry_point = self.optional_header.AddressOfEntryPoint return - def is_valid(self): - return self.dos_magic == PE.DOS_MAGIC and self.pe_magic == PE.pe_magic - - def get_machine_name(self): - return { - 0x14c: "X86", - 0x166: "MIPS", - 0x169: "WCEMIPSV2", - 0x1a2: "SH3", - 0x1a3: "SH3DSP", - 0x1a6: "SH4", - 0x1a8: "SH5", - 0x1c0: "ARM", - 0x1c2: "THUMP", - 0x1c4: "ARMNT", - 0x1d3: "AM33", - 0x1f0: "PowerPC", - 0x1f1: "PowerPCFP", - 0x200: "IA64", - 0x266: "MIPS16", - 0x366: "MIPSFPU", - 0x466: "MIPSFPU16", - 0xebc: "EFI", - 0x5032: "RISCV32", - 0x5064: "RISCV64", - 0x5128: "RISCV128", - 0x8664: "X86_64", - 0x9041: "M32R", - 0xaa64: "ARM64", - None: None - }[self.machine] - + def __str__(self) -> str: + return f"PE('{self.fpath.absolute()}', {self.file_header.Machine.name}, {str(self.file_header.Characteristics)})" + def read_and_unpack(self, fmt: str) -> Tuple[Any, ...]: + size = struct.calcsize(fmt) + data = self.fd.read(size) + return struct.unpack(fmt, data) +# +# redirect calls +# @lru_cache() -def get_pe_headers(filename=None): +def get_elf_headers(filename: str = None) -> PE: """Return an PE object with info from `filename`. If not provided, will return the currently debugged file.""" - if filename is None: - filename = get_filepath() + return PE(filename) if filename else PE(str(gef.session.file.absolute())) - if filename.startswith("target:"): - warn("Your file is remote, you should try using `gef-remote` instead") - return - return PE(filename) +def checksec(filename: str) -> Dict[str, bool]: + warn("`checksec` doesn't apply for PE files") + return {"Canary": False, "NX": False, "PIE": False, "Fortify": False} -@lru_cache() -def is_pe64(filename=None): - """Checks if `filename` is an PE64.""" - pe = current_pe or get_pe_headers(filename) - return pe.machine == PE.X86_64 +elf_reset_architecture = reset_architecture +def pe_reset_architecture(arch: Optional[str] = None, default: Optional[str] = None) -> None: + if gef.binary.file_header.Machine == PE.MachineType.X86_32: + gef.arch = __registered_architectures__.get(Elf.Abi.X86_32)() + elif gef.binary.file_header.Machine == PE.MachineType.X86_64: + gef.arch = __registered_architectures__.get(Elf.Abi.X86_64)() + else: + raise Exception("unknown architecture") + return + +def reset_architecture(arch: Optional[str] = None, default: Optional[str] = None) -> None: + if isinstance(gef.binary, PE): + pe_reset_architecture(arch, default) + elif isinstance(gef.binary, ELF): + elf_reset_architecture(arch, default) + else: + raise Exception("unknown architecture") + return -@lru_cache() -def is_pe32(filename=None): - """Checks if `filename` is an PE32.""" - pe = current_pe or get_pe_headers(filename) - return pe.machine == PE.X86_32 \ No newline at end of file diff --git a/scripts/bincompare.py b/scripts/bincompare.py index f1fad73..f8e82f7 100644 --- a/scripts/bincompare.py +++ b/scripts/bincompare.py @@ -15,7 +15,7 @@ import gdb import os - +@register_external_command class BincompareCommand(GenericCommand): """BincompareCommand: compare an binary file with the memory position looking for badchars.""" _cmdline_ = "bincompare" @@ -136,6 +136,3 @@ def print_line(self, line, data, label): .format(line, l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15], label)) - -if __name__ == "__main__": - register_external_command(BincompareCommand()) diff --git a/scripts/bytearray.py b/scripts/bytearray.py index 5d96ff5..51ff851 100644 --- a/scripts/bytearray.py +++ b/scripts/bytearray.py @@ -15,7 +15,7 @@ import gdb import re - +@register_external_command class BytearrayCommand(GenericCommand): """BytearrayCommand: Generate a bytearray to be compared with possible badchars. Function ported from mona.py""" @@ -163,6 +163,3 @@ def permitted_char(self, s): else: return False - -if __name__ == "__main__": - register_external_command(BytearrayCommand()) diff --git a/scripts/error.py b/scripts/error.py index ca75726..dcc3dd2 100644 --- a/scripts/error.py +++ b/scripts/error.py @@ -4,7 +4,7 @@ import gdb from ctypes import (CDLL, c_char_p, c_int32,) - +@register_external_command class ErrorCommand(GenericCommand): """windbg !error-like command""" @@ -20,7 +20,7 @@ def __init__(self): def do_invoke(self, argv): argc = len(argv) if not argc and is_alive(): - value = get_register(current_arch.return_register) + value = gef.arch.register(gef.arch.return_register) elif argv[0].isdigit(): value = int(argv[0]) else: @@ -30,9 +30,6 @@ def do_invoke(self, argv): __libc.strerror.restype = c_char_p __libc.strerror.argtypes = [c_int32, ] c_s = __libc.strerror(value).decode("utf8") - gef_print("{0:d} ({0:#x}) : {1:s}".format(value, Color.greenify(c_s))) + info("{0:d} ({0:#x}) : {1:s}".format(value, Color.greenify(c_s))) return - -if __name__ == "__main__": - register_external_command(ErrorCommand()) diff --git a/scripts/ftrace.py b/scripts/ftrace.py index 7fa1e58..884a72c 100644 --- a/scripts/ftrace.py +++ b/scripts/ftrace.py @@ -33,7 +33,7 @@ def stop(self): else: retval = get_register(current_arch.return_register) - output = get_gef_setting("ftrace.output") + output = gef.config["ftrace.output"] use_color = False if output is None: @@ -53,6 +53,7 @@ def stop(self): return False +@register_external_command class FtraceCommand(GenericCommand): """Tracks a function given in parameter for arguments and return code.""" _cmdline_ = "ftrace" @@ -80,7 +81,3 @@ def cleanup(self, events): bp.delete() gdb.events.exited.disconnect(self.cleanup) return - - -if __name__ == "__main__": - register_external_command(FtraceCommand()) diff --git a/scripts/ida_interact.py b/scripts/ida_interact.py index 5a72d5c..3df08d0 100644 --- a/scripts/ida_interact.py +++ b/scripts/ida_interact.py @@ -1,26 +1,72 @@ import functools +from typing import Any, List, Set, Dict, Optional import gdb import rpyc import pprint + __AUTHOR__ = "hugsy" -__VERSION__ = 0.1 +__VERSION__ = 0.2 __DESCRIPTION_ = """Control headlessly IDA from GEF using RPyC""" +class RemoteDecompilerSession: + sock: Optional[int] = None + breakpoints: Set[str] = set() + old_colors: Dict[int, str] = {} + + # IDA aliases + @property + def idc(self): + return self.sock.root.idc + + @property + def idaapi(self): + return self.sock.root.idaapi + + def reconnect(self) -> bool: + try: + host = gef.config["ida-rpyc.host"] + port = gef.config["ida-rpyc.port"] + self.sock = rpyc.connect(host, port) + gef_on_stop_hook(ida_rpyc_resync) + gef_on_continue_hook(ida_rpyc_resync) + return False + except ConnectionRefusedError: + self.sock = None + gef_on_stop_unhook(ida_rpyc_resync) + gef_on_continue_unhook(ida_rpyc_resync) + return False + + def print_info(self) -> None: + connection_status = "Connection status to "\ + f"{gef.config['ida-rpyc.host']}:{gef.config['ida-rpyc.port']} ... " + if self.sock is None: + warn(f"{connection_status} {Color.redify('DISCONNECTED')}") + return + + ok(f"{connection_status} {Color.greenify('CONNECTED')}") + + major, minor = self.idaapi.IDA_SDK_VERSION // 100, self.idaapi.IDA_SDK_VERSION % 100 + info(f"Version: {Color.boldify('IDA Pro')} v{major}.{minor}") + + info("Breakpoints") + gef_print(str(self.breakpoints)) + + info("Colors") + gef_print(str(self.old_colors)) + return + -sess = { - "sock": None, - "breakpoints": set(), - "old_colors": {}, -} +sess = RemoteDecompilerSession() def is_current_elf_pie(): - return checksec(get_filepath())["PIE"] + sp = checksec(str(gef.session.file)) + return sp["PIE"] == True def get_rva(addr): - base_address = [x.page_start for x in get_process_maps() if x.path == get_filepath()][0] + base_address = [x.page_start for x in gef.memory.maps if x.path == get_filepath()][0] return addr - base_address @@ -28,33 +74,17 @@ def ida_rpyc_resync(evt): return gdb.execute("ida-rpyc synchronize", from_tty=True) -def reconnect(): - try: - host = get_gef_setting("ida-rpyc.host") - port = get_gef_setting("ida-rpyc.port") - sock = rpyc.connect(host, port) - - gef_on_stop_hook(ida_rpyc_resync) - gef_on_continue_hook(ida_rpyc_resync) - except ConnectionRefusedError: - sock = None - gef_on_stop_unhook(ida_rpyc_resync) - gef_on_continue_unhook(ida_rpyc_resync) - return sock - - def only_if_active_rpyc_session(f): """Decorator wrapper to check if the RPyC session is running.""" @functools.wraps(f) def wrapper(*args, **kwargs): global sess - - for i in range(2): - if sess["sock"]: + for _ in range(2): + if sess.sock: return f(*args, **kwargs) - sess["sock"] = reconnect() + sess.reconnect() - if not sess["sock"]: + if not sess.sock: warn("No RPyC session running") return wrapper @@ -68,9 +98,9 @@ class RpycIdaCommand(GenericCommand): def __init__(self): global sess super(RpycIdaCommand, self).__init__(prefix=True) - self.add_setting("host", "127.0.0.1", "IDA host IP address") - self.add_setting("port", 18812, "IDA host port") - self.add_setting("sync_cursor", False, "Enable real-time $pc synchronisation") + self["host"] = ("127.0.0.1", "IDA host IP address") + self["port"] = (18812, "IDA host port") + self["sync_cursor"] = (False, "Enable real-time $pc synchronisation") self.last_hl_ea = -1 return @@ -81,25 +111,24 @@ def do_invoke(self, argv): self.usage() return - if argv[0] == "synchronize" or self.get_setting("sync_cursor"): + if argv[0] == "synchronize" or self["sync_cursor"]: self.synchronize() return def synchronize(self): """Submit all active breakpoint addresses to IDA/BN.""" - pc = current_arch.pc - vmmap = get_process_maps() + pc = gef.arch.pc + vmmap = gef.memory.maps base_address = min([x.page_start for x in vmmap if x.path == get_filepath()]) end_address = max([x.page_end for x in vmmap if x.path == get_filepath()]) if not (base_address <= pc < end_address): return if self.last_hl_ea >= 0: - gdb.execute("ida-rpyc highlight del {:#x}".format(self.last_hl_ea)) - - gdb.execute("ida-rpyc jump {:#x}".format(pc)) + gdb.execute(f"ida-rpyc highlight del {self.last_hl_ea:#x}") - gdb.execute("ida-rpyc highlight add {:#x}".format(pc)) + gdb.execute(f"ida-rpyc jump {pc:#x}") + gdb.execute(f"ida-rpyc highlight add {pc:#x}") self.last_hl_ea = pc return @@ -134,20 +163,15 @@ def __init__(self): @only_if_gdb_running @only_if_active_rpyc_session - def do_invoke(self, argv): - global sess - if not argv: - self.usage() - return - - addr = current_arch.pc if not argv else parse_address(argv[0]) - if is_current_elf_pie(): - addr = get_rva(addr) - - color = int(argv[1], 0) if len(argv) > 1 else 0x00ff00 - ok("highlight ea={:#x} as {:#x}".format(addr, color)) - sess["old_colors"][addr] = sess["sock"].root.idc.get_color(addr, sess["sock"].root.idc.CIC_ITEM) - sess["sock"].root.idc.set_color(addr, sess["sock"].root.idc.CIC_ITEM, color) + @parse_arguments({"location": "$pc", }, {"--color": 0x00ff00}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + ea = parse_address(args.location) + if is_current_elf_pie(): ea = get_rva(ea) + color = args.color + ok("highlight ea={:#x} as {:#x}".format(ea, color)) + sess.old_colors[ea] = sess.idc.get_color(ea, sess.idc.CIC_ITEM) + sess.idc.set_color(ea, sess.idc.CIC_ITEM, color) return @@ -164,23 +188,19 @@ def __init__(self): @only_if_gdb_running @only_if_active_rpyc_session - def do_invoke(self, argv): - global sess - if not argv: - self.usage() - return - - addr = current_arch.pc if not argv else parse_address(argv[0]) - if is_current_elf_pie(): - addr = get_rva(addr) - - if addr not in sess["old_colors"]: - warn("{:#x} was not highlighted".format(addr)) + @parse_arguments({"location": "$pc",}, {}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + ea = parse_address(args.location) + if is_current_elf_pie(): ea = get_rva(ea) + + if ea not in sess.old_colors: + warn("{:#x} was not highlighted".format(ea)) return - color = sess["old_colors"].pop(addr) - ok("unhighlight ea={:#x} back to {:#x}".format(addr, color)) - sess["sock"].root.idc.set_color(addr, sess["sock"].root.idc.CIC_ITEM, color) + color = sess.old_colors.pop(ea) + ok("unhighlight ea={:#x} back to {:#x}".format(ea, color)) + sess.idc.set_color(ea, sess.idc.CIC_ITEM, color) return @@ -197,7 +217,7 @@ def __init__(self): @only_if_gdb_running @only_if_active_rpyc_session - def do_invoke(self, argv): + def do_invoke(self, _): pass @@ -205,7 +225,7 @@ class RpycIdaBreakpointListCommand(RpycIdaBreakpointCommand): """RPyC IDA: breakpoint list command""" _cmdline_ = "ida-rpyc breakpoints list" _syntax_ = "{:s}".format(_cmdline_) - _aliases_ = [] + _aliases_ = ["ida-rpyc bl", ] _example_ = "{:s}".format(_cmdline_) def __init__(self): @@ -217,7 +237,7 @@ def __init__(self): def do_invoke(self, argv): if not argv: self.usage() - pprint.pprint(sess["breakpoints"]) + pprint.pprint(sess.breakpoints) return @@ -235,11 +255,7 @@ def __init__(self): @only_if_gdb_running @only_if_active_rpyc_session def do_invoke(self, argv): - if not argv: - self.usage() - return - - pprint.pprint(sess) + sess.print_info() return @@ -256,12 +272,12 @@ def __init__(self): @only_if_gdb_running @only_if_active_rpyc_session - def do_invoke(self, argv): - addr = current_arch.pc if not argv else parse_address(argv[0]) - if is_current_elf_pie(): - addr = get_rva(addr) - # ok("jumping to {:#x}".format(addr)) - sess["sock"].root.idaapi.jumpto(addr) + @parse_arguments({"location": "$pc", }, {}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + ea = parse_address(args.location) + if is_current_elf_pie(): ea = get_rva(ea) + sess.idaapi.jumpto(ea) return @@ -278,16 +294,16 @@ def __init__(self): @only_if_gdb_running @only_if_active_rpyc_session - def do_invoke(self, argv): - return + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + pass class RpycIdaCommentAddCommand(RpycIdaCommentCommand): """RPyCIda add comment command""" _cmdline_ = "ida-rpyc comments add" - _syntax_ = "{:s} \"My comment\" [*0xaddress|register|symbol]".format(_cmdline_) + _syntax_ = "{:s} \"My comment\" --location [*0xaddress|register|symbol]".format(_cmdline_) _aliases_ = [] - _example_ = "{:s} \"I was here\" $pc".format(_cmdline_) + _example_ = "{:s} \"I was here\" --location $pc".format(_cmdline_) def __init__(self): super(RpycIdaCommand, self).__init__(complete=gdb.COMPLETE_SYMBOL) #pylint: disable=bad-super-call @@ -295,44 +311,54 @@ def __init__(self): @only_if_gdb_running @only_if_active_rpyc_session - def do_invoke(self, argv): + @parse_arguments({"comment": ""}, {"--location": "$pc", }) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + ea = parse_address(args.location) + if is_current_elf_pie(): ea = get_rva(ea) + comment = args.comment repeatable_comment = 1 + sess.idc.set_cmt(ea, comment, repeatable_comment) + return - if not argv: - self.usage() - return - - comment = argv[0] - ea = current_arch.pc - if len(argv) > 1: - ea = parse_address(argv[1]) +class RpycIdaCommentDeleteCommand(RpycIdaCommentCommand): + """RPyCIda delete comment command""" + _cmdline_ = "ida-rpyc comments del" + _syntax_ = "{:s} [LOCATION]".format(_cmdline_) + _aliases_ = [] + _example_ = "{:s} $pc".format(_cmdline_) - if is_current_elf_pie(): - ea = get_rva(ea) + def __init__(self): + super(RpycIdaCommand, self).__init__(complete=gdb.COMPLETE_SYMBOL) #pylint: disable=bad-super-call + return - idc = sess["sock"].root.idc - idc.set_cmt(ea, comment, repeatable_comment) + @only_if_gdb_running + @only_if_active_rpyc_session + @parse_arguments({"location": "$pc", }, {}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + ea = parse_address(args.location) + if is_current_elf_pie(): ea = get_rva(ea) + repeatable_comment = 1 + sess.idc.set_cmt(ea, "", repeatable_comment) return if __name__ == "__main__": cmds = [ RpycIdaCommand, - RpycIdaInfoSessionCommand, RpycIdaJumpCommand, - RpycIdaBreakpointCommand, RpycIdaBreakpointListCommand, - RpycIdaCommentCommand, RpycIdaCommentAddCommand, - + RpycIdaCommentDeleteCommand, RpycIdaHighlightCommand, RpycIdaHighlightAddCommand, RpycIdaHighlightDeleteCommand, ] for cmd in cmds: - register_external_command(cmd()) + register_external_command(cmd) diff --git a/scripts/peekpointers.py b/scripts/peekpointers.py index e58df81..1c02ad5 100644 --- a/scripts/peekpointers.py +++ b/scripts/peekpointers.py @@ -1,3 +1,4 @@ +@register_external_command class PeekPointers(GenericCommand): """Command to help find pointers belonging to other memory regions helpful in case of OOB Read when looking for specific pointers""" @@ -55,7 +56,3 @@ def do_invoke(self, argv): addr = lookup_address(addr.value + current_arch.ptrsize) return - - -if __name__ == "__main__": - register_external_command(PeekPointers()) diff --git a/scripts/retdec.py b/scripts/retdec.py index e756f46..a7becee 100644 --- a/scripts/retdec.py +++ b/scripts/retdec.py @@ -11,6 +11,7 @@ from pygments.lexers import CLexer from pygments.formatters import Terminal256Formatter +@register_external_command class RetDecCommand(GenericCommand): """Decompile code from GDB context using RetDec API.""" @@ -21,19 +22,19 @@ class RetDecCommand(GenericCommand): def __init__(self): super(RetDecCommand, self).__init__(complete=gdb.COMPLETE_SYMBOL) - self.add_setting("path", GEF_TEMP_DIR, "Path to store the decompiled code") - self.add_setting("retdec_path", "", "Path to the retdec installation") - self.add_setting("theme", "default", "Theme for pygments syntax highlighting") + self["path"] = (GEF_TEMP_DIR, "Path to store the decompiled code") + self["retdec_path"] = ("", "Path to the retdec installation") + self["theme"] = ("default", "Theme for pygments syntax highlighting") return @only_if_gdb_running def do_invoke(self, argv): - arch = current_arch.arch.lower() + arch = gef.arch.arch.lower() if not arch: err("RetDec does not decompile '{:s}'".format(get_arch())) return - retdec_path = self.get_setting("retdec_path").strip() + retdec_path = self["retdec_path"].strip() if not retdec_path: msg = "Path to retdec installation not provided, use `gef config` to set the path" err(msg) @@ -59,7 +60,7 @@ def do_invoke(self, argv): for opt, arg in opts: if opt == "-r": range_from, range_to = map(lambda x: int(x, 16), arg.split("-", 1)) - fd, filename = tempfile.mkstemp(dir=self.get_setting("path")) + fd, filename = tempfile.mkstemp(dir=self["path"]) with os.fdopen(fd, "wb") as f: length = range_to - range_from f.write(read_memory(range_from, length)) @@ -74,7 +75,7 @@ def do_invoke(self, argv): err("No symbol named '{:s}'".format(arg)) return range_from = int(value.address) - fd, filename = tempfile.mkstemp(dir=self.get_setting("path")) + fd, filename = tempfile.mkstemp(dir=self["path"]) with os.fdopen(fd, "wb") as f: f.write(read_memory(range_from, get_function_length(arg))) params["mode"] = "raw" @@ -89,8 +90,8 @@ def do_invoke(self, argv): return # Set up variables - path = self.get_setting("path") - theme = self.get_setting("theme") + path = self["path"] + theme = self["theme"] params["input_file"] = filename fname = "{}/{}.{}".format(path, os.path.basename(params["input_file"]), params["target_language"]) logfile = "{}/{}.log".format(path, os.path.basename(params["input_file"])) @@ -101,7 +102,7 @@ def do_invoke(self, argv): "-e", params["raw_endian"], "-f", "plain", "-a", params["architecture"], - "-o", fname, + "-o", fname, "-l", params["target_language"], params["input_file"], "--cleanup" @@ -109,10 +110,10 @@ def do_invoke(self, argv): else: cmd = [ - retdec_decompiler, - "-m", params["mode"], - "--raw-section-vma", params["raw_section_vma"], - "--raw-entry-point", params["raw_entry_point"], + retdec_decompiler, + "-m", params["mode"], + "--raw-section-vma", params["raw_section_vma"], + "--raw-entry-point", params["raw_entry_point"], "-e", params["raw_endian"], "-f", "plain", "-a", params["architecture"], @@ -152,7 +153,3 @@ def send_to_retdec(self, params, cmd, logfile): return False return True - - -if __name__ == "__main__": - register_external_command(RetDecCommand()) diff --git a/scripts/skel.py b/scripts/skel.py index 964d1e4..9bddd36 100644 --- a/scripts/skel.py +++ b/scripts/skel.py @@ -1,5 +1,5 @@ __AUTHOR__ = "hugsy" -__VERSION__ = 0.2 +__VERSION__ = 0.3 import os import tempfile @@ -44,6 +44,7 @@ def exploit(r): """ +@register_external_command class ExploitTemplateCommand(GenericCommand): """Generates a exploit template.""" _cmdline_ = "exploit-template" @@ -74,8 +75,8 @@ def do_invoke(self, args): temp = TEMPLATE.format( target=target, port=port, - arch="amd64" if "x86-64" in get_arch() else "i386", - endian="big" if is_big_endian() else "little", + arch="amd64" if "x86-64" in gef.arch.arch else "i386", + endian="big" if gef.arch.endianness == Endianness.BIG_ENDIAN else "little", filepath=get_filepath(), bkps=bkps ) @@ -86,6 +87,3 @@ def do_invoke(self, args): ok("Exploit generated in '{:s}'".format(fname)) return - -if __name__ == "__main__": - register_external_command(ExploitTemplateCommand()) diff --git a/scripts/stack.py b/scripts/stack.py index 2dd586d..9e1f5d6 100644 --- a/scripts/stack.py +++ b/scripts/stack.py @@ -2,6 +2,7 @@ __VERSION__ = 0.1 +@register_external_command class CurrentFrameStack(GenericCommand): """Show the entire stack of the current frame.""" _cmdline_ = "current-stack-frame" @@ -11,7 +12,7 @@ class CurrentFrameStack(GenericCommand): @only_if_gdb_running def do_invoke(self, argv): - ptrsize = current_arch.ptrsize + ptrsize = gef.arch.ptrsize frame = gdb.selected_frame() if not frame.older(): @@ -23,7 +24,7 @@ def do_invoke(self, argv): saved_ip = frame.older().pc() stack_hi = align_address(int(frame.older().read_register("sp"))) stack_lo = align_address(int(frame.read_register("sp"))) - should_stack_grow_down = get_gef_setting("context.grow_stack_down") == True + should_stack_grow_down = gef.config["context.grow_stack_down"] == True results = [] for offset, address in enumerate(range(stack_lo, stack_hi, ptrsize)): @@ -38,8 +39,7 @@ def do_invoke(self, argv): else: gef_print(titlify("Stack bottom (lower address)")) - for res in results: - gef_print(res) + gef_print(results) if should_stack_grow_down: gef_print(titlify("Stack bottom (lower address)")) @@ -47,6 +47,3 @@ def do_invoke(self, argv): gef_print(titlify("Stack top (higher address)")) return - -if __name__ == "__main__": - register_external_command(CurrentFrameStack()) diff --git a/scripts/v8-dereference.py b/scripts/v8-dereference.py index b87ddd3..f0c1f86 100644 --- a/scripts/v8-dereference.py +++ b/scripts/v8-dereference.py @@ -32,11 +32,11 @@ def del_isolate_root(event): isolate_root = None def format_compressed(addr): - heap_color = get_gef_setting("theme.address_heap") + heap_color = gef.config["theme.address_heap"] return "{:s}{:s}".format(Color.colorify("0x{:08x}".format(addr>>32), "gray"), Color.colorify("{:08x}".format(addr&0xffffffff), heap_color)) - -@register_command + +@register_external_command class V8DereferenceCommand(GenericCommand): """(v8) Dereference recursively from an address and display information. Handles v8 specific values like tagged and compressed pointers""" @@ -47,14 +47,14 @@ class V8DereferenceCommand(GenericCommand): def __init__(self): super(V8DereferenceCommand, self).__init__(complete=gdb.COMPLETE_LOCATION) - self.add_setting("max_recursion", 7, "Maximum level of pointer recursion") + self["max_recursion"] = (7, "Maximum level of pointer recursion") gef_on_exit_hook(del_isolate_root) return @staticmethod def pprint_dereferenced(addr, off): - base_address_color = get_gef_setting("theme.dereference_base_address") - registers_color = get_gef_setting("theme.dereference_register_value") + base_address_color = gef.config["theme.dereference_base_address"] + registers_color = gef.config["theme.dereference_register_value"] regs = [(k, get_register(k)) for k in current_arch.all_registers] @@ -137,7 +137,7 @@ def do_invoke(self, argv): err("Unmapped address") return - if get_gef_setting("context.grow_stack_down") is True: + if gef.config["context.grow_stack_down"] is True: from_insnum = nb * (self.repeat_count + 1) - 1 to_insnum = self.repeat_count * nb - 1 insnum_step = -1 @@ -159,9 +159,9 @@ def dereference_from(addr): if not is_alive(): return ([format_address(addr),], None) - 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 + code_color = gef.config["theme.dereference_code"] + string_color = gef.config["theme.dereference_string"] + max_recursion = gef.config["dereference.max_recursion"] or 10 addr = lookup_address(align_address(int(addr))) msg = ([format_address(addr.value),], []) seen_addrs = set()#tuple(set(), set()) @@ -192,8 +192,8 @@ def dereference_from(addr): else: msg[i].append(" {:#0{ma}x}".format( val, ma=( 10 )) ) return msg - - + + while addr.section and max_recursion: if addr.value in seen_addrs: msg[0].append("[loop detected]") @@ -243,5 +243,3 @@ def dereference_from(addr): break return msg - -register_external_command(V8DereferenceCommand()) diff --git a/scripts/visualize_heap.py b/scripts/visualize_heap.py index c259323..d9aeaa9 100644 --- a/scripts/visualize_heap.py +++ b/scripts/visualize_heap.py @@ -88,6 +88,7 @@ def collect_known_ranges()->list: return result +@register_external_command class VisualizeHeapChunksCommand(GenericCommand): """Visual helper for glibc heap chunks""" @@ -184,6 +185,3 @@ def do_invoke(self, argv): idx += 1 return - -if __name__ == "__main__": - register_external_command(VisualizeHeapChunksCommand()) diff --git a/scripts/windbg.py b/scripts/windbg.py index 09562dc..153396d 100644 --- a/scripts/windbg.py +++ b/scripts/windbg.py @@ -363,7 +363,7 @@ def __windbg_prompt__(current_prompt): def __default_prompt__(x): - if get_gef_setting("gef.use-windbg-prompt") is True: + if gef.config["gef.use-windbg-prompt"] is True: return __windbg_prompt__(x) else: return __gef_prompt__(x) @@ -415,4 +415,4 @@ def __default_prompt__(x): ] for _ in windbg_commands: - register_external_command(_()) + register_external_command(_) diff --git a/scripts/xref-telescope.py b/scripts/xref-telescope.py index 0cca083..c0c94b2 100644 --- a/scripts/xref-telescope.py +++ b/scripts/xref-telescope.py @@ -1,4 +1,5 @@ -@register_command + +@register_external_command class XRefTelescopeCommand(SearchPatternCommand): """Recursively search for cross-references to a pattern in memory""" @@ -13,13 +14,13 @@ def xref_telescope_(self, pattern, depth, tree_heading): return if is_hex(pattern): - if get_endian() == Elf.BIG_ENDIAN: + if gef.arch.endianness == Endianness.BIG_ENDIAN: pattern = "".join(["\\x" + pattern[i:i + 2] for i in range(2, len(pattern), 2)]) else: pattern = "".join(["\\x" + pattern[i:i + 2] for i in range(len(pattern) - 2, 0, -2)]) locs = [] - for section in get_process_maps(): + for section in gef.memory.maps: if not section.permission & Permission.READ: continue if section.path == "[vvar]": @@ -68,7 +69,3 @@ def do_invoke(self, argv): info("Recursively searching '{:s}' in memory" .format(Color.yellowify(pattern))) self.xref_telescope(pattern, depth) - - -if __name__ == "__main__": - register_external_command(XRefTelescopeCommand()) From eec08474deacf5616a0ac76de65d4774f7240e31 Mon Sep 17 00:00:00 2001 From: Boris-Chengbiao Zhou Date: Mon, 14 Feb 2022 18:46:05 +0100 Subject: [PATCH 02/39] Update ARM-M architecture for new supports_gdb_arch() callback --- archs/arm-cortex-m.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/archs/arm-cortex-m.py b/archs/arm-cortex-m.py index d731e7e..8965a38 100644 --- a/archs/arm-cortex-m.py +++ b/archs/arm-cortex-m.py @@ -22,3 +22,7 @@ class ARM_M(ARM): 28: "overflow", 24: "thumb", } + + @staticmethod + def supports_gdb_arch(gdb_arch: str) -> Optional[bool]: + return bool(re.search("^armv.*-m$", gdb_arch)) From 6052ae46b02de8c3bb13ab526ed92330004948bd Mon Sep 17 00:00:00 2001 From: Boris-Chengbiao Zhou Date: Mon, 14 Feb 2022 18:47:26 +0100 Subject: [PATCH 03/39] Update M68K arch --- archs/m68k.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/archs/m68k.py b/archs/m68k.py index 899cab3..ce669ae 100644 --- a/archs/m68k.py +++ b/archs/m68k.py @@ -7,8 +7,10 @@ Author: zhuyifei1999 """ +@register_architecture class M68K(Architecture): arch = "M68K" + aliases = ("M68K", ) mode = "" nop_insn = b"\x4e\x71" @@ -105,5 +107,3 @@ def get_ra(self, insn, frame): @classmethod def mprotect_asm(cls, addr, size, perm): raise NotImplementedError() - -SUPPORTED_ARCHITECTURES["M68K"] = (M68K, Elf.M68K: M68K) From 4869692c1c7255bc9eb1f29e772c033b5c3a1cda Mon Sep 17 00:00:00 2001 From: ebubekirtrkr Date: Sat, 26 Feb 2022 15:13:32 +0300 Subject: [PATCH 04/39] fix `visualize_heap` command for new gef api --- scripts/visualize_heap.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/visualize_heap.py b/scripts/visualize_heap.py index d9aeaa9..1a431a9 100644 --- a/scripts/visualize_heap.py +++ b/scripts/visualize_heap.py @@ -6,17 +6,17 @@ def fastbin_index(sz): - return (sz >> 4) - 2 if current_arch.ptrsize == 8 else (sz >> 3) - 2 + return (sz >> 4) - 2 if gef.arch.ptrsize == 8 else (sz >> 3) - 2 def nfastbins(): - return fastbin_index( (80 * current_arch.ptrsize // 4)) - 1 + return fastbin_index( (80 * gef.arch.ptrsize // 4)) - 1 def get_tcache_count(): if get_libc_version() < (2, 27): return 0 - count_addr = HeapBaseFunction.heap_base() + 2*current_arch.ptrsize + count_addr = gef.heap.base_address + 2*gef.arch.ptrsize count = p8(count_addr) if get_libc_version() < (2, 30) else p16(count_addr) return count @@ -103,8 +103,8 @@ def __init__(self): @only_if_gdb_running def do_invoke(self, argv): - ptrsize = current_arch.ptrsize - heap_base_address = HeapBaseFunction.heap_base() + ptrsize = gef.arch.ptrsize + heap_base_address = gef.heap.base_address arena = get_glibc_arena() if not arena.top: err("The heap has not been initialized") @@ -125,8 +125,8 @@ def do_invoke(self, argv): aggregate_nuls = 0 if base == top: - gef_print("{} {} {}".format(format_address(addr), format_address(read_int_from_memory(addr)) , Color.colorify(LEFT_ARROW + "Top Chunk", "red bold"))) - gef_print("{} {} {}".format(format_address(addr+ptrsize), format_address(read_int_from_memory(addr+ptrsize)) , Color.colorify(LEFT_ARROW + "Top Chunk Size", "red bold"))) + gef_print("{} {} {}".format(format_address(addr), format_address(gef.memory.read_integer(addr)) , Color.colorify(LEFT_ARROW + "Top Chunk", "red bold"))) + gef_print("{} {} {}".format(format_address(addr+ptrsize), format_address(gef.memory.read_integer(addr+ptrsize)) , Color.colorify(LEFT_ARROW + "Top Chunk Size", "red bold"))) break if cur.size == 0: @@ -135,7 +135,7 @@ def do_invoke(self, argv): for off in range(0, cur.size, cur.ptrsize): addr = base + off - value = read_int_from_memory(addr) + value = gef.memory.read_integer(addr) if value == 0: if off != 0 and off != cur.size - cur.ptrsize: aggregate_nuls += 1 @@ -149,7 +149,7 @@ def do_invoke(self, argv): aggregate_nuls = 0 - text = "".join([chr(b) if 0x20 <= b < 0x7F else "." for b in read_memory(addr, cur.ptrsize)]) + text = "".join([chr(b) if 0x20 <= b < 0x7F else "." for b in gef.memory.read(addr, cur.ptrsize)]) line = "{} {}".format(format_address(addr), Color.colorify(format_address(value), colors[idx % len(colors)])) line+= " {}".format(text) derefs = dereference_from(addr) From 97ced690e3b5a2bac491037857709c749bab1609 Mon Sep 17 00:00:00 2001 From: Dylan Brotherston Date: Fri, 4 Mar 2022 05:31:01 +1100 Subject: [PATCH 05/39] fix incorrect type being passed to gef_print() in `current-stack-frame` command --- scripts/stack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/stack.py b/scripts/stack.py index 9e1f5d6..9b01887 100644 --- a/scripts/stack.py +++ b/scripts/stack.py @@ -39,7 +39,7 @@ def do_invoke(self, argv): else: gef_print(titlify("Stack bottom (lower address)")) - gef_print(results) + gef_print("\n".join(results)) if should_stack_grow_down: gef_print(titlify("Stack bottom (lower address)")) From e2aaba1ff266bcaaa831893890559495c22b6faf Mon Sep 17 00:00:00 2001 From: jodojodo Date: Sun, 13 Mar 2022 19:27:51 +0000 Subject: [PATCH 06/39] Fix gdb error "read_memory not defined" --- scripts/bincompare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/bincompare.py b/scripts/bincompare.py index f8e82f7..ee919fb 100644 --- a/scripts/bincompare.py +++ b/scripts/bincompare.py @@ -69,7 +69,7 @@ def do_invoke(self, argv): return try: - memory_data = read_memory(start_addr, size) + memory_data = gdb.selected_inferior().read_memory(start_addr, size).tobytes() except gdb.MemoryError: err("Cannot reach memory {:#x}".format(start_addr)) return From 3136d288a93ab0660b474b60df028d176022f84e Mon Sep 17 00:00:00 2001 From: jodojodo Date: Wed, 16 Mar 2022 23:10:23 +0000 Subject: [PATCH 07/39] Use the gef provided function The gef.memory.read(start_addr, size) directly provides bytes. --- scripts/bincompare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/bincompare.py b/scripts/bincompare.py index ee919fb..12aeb5f 100644 --- a/scripts/bincompare.py +++ b/scripts/bincompare.py @@ -69,7 +69,7 @@ def do_invoke(self, argv): return try: - memory_data = gdb.selected_inferior().read_memory(start_addr, size).tobytes() + memory_data = gef.memory.read(start_addr, size) except gdb.MemoryError: err("Cannot reach memory {:#x}".format(start_addr)) return From 031b50e64577b38625edad5c0b16d54c71daa9b3 Mon Sep 17 00:00:00 2001 From: ChinaNuke Date: Thu, 21 Apr 2022 22:09:15 +0800 Subject: [PATCH 08/39] Fix 'set_gef_setting' is deprecated warning Replace `set_gef_setting("key", value)` with `gef.config[key] = value` --- scripts/windbg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/windbg.py b/scripts/windbg.py index 153396d..dd0cbca 100644 --- a/scripts/windbg.py +++ b/scripts/windbg.py @@ -58,12 +58,12 @@ def do_invoke(self, argv): def windbg_execute_until(cnt, cmd, stop_condition): while cnt: cnt -= 1 - set_gef_setting("context.enable", False) + gef.config["context.enable"] = False gdb.execute(cmd) insn = gef_current_instruction(gef.arch.pc) if stop_condition(insn): break - set_gef_setting("context.enable", True) + gef.config["context.enable"] = True return From 9b09eb0266fcd3ff0d7438762648cde21b450762 Mon Sep 17 00:00:00 2001 From: Alan Li <61896187+lebr0nli@users.noreply.github.com> Date: Sun, 22 May 2022 22:36:27 +0800 Subject: [PATCH 09/39] Fix the broken link for libc argument doc (#60) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7d30aaa..e575ad1 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ gef➤ gef config context.libc_args True gef➤ gef config context.libc_args_path /path/to/gef-extras/glibc-function-args ``` -Check out the [complete doc](glibc_function_args.md) on libc argument support. +Check out the [complete doc](docs/glibc_function_args.md) on libc argument support. Now run and enjoy all the fun! From 2d4d0695fb738dd46632feabcffbde9f20b37c8d Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 22 May 2022 07:37:40 -0700 Subject: [PATCH 10/39] fix `visualize_heap` for new api --- scripts/visualize_heap.py | 61 ++++++++++++++++++++++----------------- structs/malloc_arena_t.py | 49 +++++++++++++++++++------------ 2 files changed, 65 insertions(+), 45 deletions(-) diff --git a/scripts/visualize_heap.py b/scripts/visualize_heap.py index d9aeaa9..d4675e4 100644 --- a/scripts/visualize_heap.py +++ b/scripts/visualize_heap.py @@ -1,33 +1,33 @@ __AUTHOR__ = "hugsy" -__VERSION__ = 0.1 +__VERSION__ = 0.2 import os import gdb def fastbin_index(sz): - return (sz >> 4) - 2 if current_arch.ptrsize == 8 else (sz >> 3) - 2 + return (sz >> 4) - 2 if gef.arch.ptrsize == 8 else (sz >> 3) - 2 def nfastbins(): - return fastbin_index( (80 * current_arch.ptrsize // 4)) - 1 + return fastbin_index( (80 * gef.arch.ptrsize // 4)) - 1 def get_tcache_count(): - if get_libc_version() < (2, 27): + if gef.libc.version < (2, 27): return 0 - count_addr = HeapBaseFunction.heap_base() + 2*current_arch.ptrsize - count = p8(count_addr) if get_libc_version() < (2, 30) else p16(count_addr) + count_addr = gef.heap.base + 2*gef.arch.ptrsize + count = p8(count_addr) if gef.libc.version < (2, 30) else p16(count_addr) return count @lru_cache(128) def collect_known_values() -> dict: - arena = get_glibc_arena() + arena = gef.heap.main_arena result = {} # format is { 0xaddress : "name" ,} # tcache - if get_libc_version() >= (2, 27): + if gef.libc.version() >= (2, 27): tcache_addr = GlibcHeapTcachebinsCommand.find_tcache() for i in range(GlibcHeapTcachebinsCommand.TCACHE_MAX_BINS): chunk, _ = GlibcHeapTcachebinsCommand.tcachebin(tcache_addr, i) @@ -80,7 +80,7 @@ def collect_known_values() -> dict: @lru_cache(128) def collect_known_ranges()->list: result = [] - for entry in get_process_maps(): + for entry in gef.memory.maps: if not entry.path: continue path = os.path.basename(entry.path) @@ -103,9 +103,9 @@ def __init__(self): @only_if_gdb_running def do_invoke(self, argv): - ptrsize = current_arch.ptrsize - heap_base_address = HeapBaseFunction.heap_base() - arena = get_glibc_arena() + ptrsize = gef.arch.ptrsize + heap_base_address = gef.heap.base_address + arena = gef.heap.main_arena if not arena.top: err("The heap has not been initialized") return @@ -125,8 +125,10 @@ def do_invoke(self, argv): aggregate_nuls = 0 if base == top: - gef_print("{} {} {}".format(format_address(addr), format_address(read_int_from_memory(addr)) , Color.colorify(LEFT_ARROW + "Top Chunk", "red bold"))) - gef_print("{} {} {}".format(format_address(addr+ptrsize), format_address(read_int_from_memory(addr+ptrsize)) , Color.colorify(LEFT_ARROW + "Top Chunk Size", "red bold"))) + gef_print( + "{} {} {}".format(format_address(addr), format_address(read_int_from_memory(addr)) , Color.colorify(LEFT_ARROW + "Top Chunk", "red bold")), + "{} {} {}".format(format_address(addr+ptrsize), format_address(read_int_from_memory(addr+ptrsize)) , Color.colorify(LEFT_ARROW + "Top Chunk Size", "red bold")) + ) break if cur.size == 0: @@ -135,7 +137,7 @@ def do_invoke(self, argv): for off in range(0, cur.size, cur.ptrsize): addr = base + off - value = read_int_from_memory(addr) + value = gef.memory.read_integer(addr) if value == 0: if off != 0 and off != cur.size - cur.ptrsize: aggregate_nuls += 1 @@ -143,32 +145,37 @@ def do_invoke(self, argv): continue if aggregate_nuls > 2: - gef_print(" ↓") - gef_print(" [...]") - gef_print(" ↓") + gef_print( + " ↓", + " [...]", + " ↓" + ) aggregate_nuls = 0 - - text = "".join([chr(b) if 0x20 <= b < 0x7F else "." for b in read_memory(addr, cur.ptrsize)]) - line = "{} {}".format(format_address(addr), Color.colorify(format_address(value), colors[idx % len(colors)])) - line+= " {}".format(text) + text = "".join([chr(b) if 0x20 <= b < 0x7F else "." for b in gef.memory.read(addr, cur.ptrsize)]) + line = f"{format_address(addr)} {Color.colorify(format_address(value), colors[idx % len(colors)])}" + line+= f" {text}" derefs = dereference_from(addr) if len(derefs) > 2: - line+= " [{}{}]".format(LEFT_ARROW, derefs[-1]) + line+= f" [{LEFT_ARROW}{derefs[-1]}]" if off == 0: - line+= " Chunk[{}]".format(idx) + line+= f" Chunk[{idx}]" if off == cur.ptrsize: - line+= " {}{}{}{}".format(value&~7, "|NON_MAIN_ARENA" if value&4 else "", "|IS_MMAPED" if value&2 else "", "|PREV_INUSE" if value&1 else "") + + line+= f" {value&~7}" \ + f"{'|NON_MAIN_ARENA' if value&4 else ''}" \ + f"{'|IS_MMAPED' if value&2 else ''}" \ + f"{'PREV_INUSE' if value&1 else ''}" # look in mapping for x in known_ranges: if value in x[0]: - line+= " (in {})".format(Color.redify(x[1])) + line+= f" (in {Color.redify(x[1])})" # look in known values if value in known_values: - line += "{}{}".format(RIGHT_ARROW, Color.cyanify(known_values[value])) + line += f"{RIGHT_ARROW}{Color.cyanify(known_values[value])}" gef_print(line) diff --git a/structs/malloc_arena_t.py b/structs/malloc_arena_t.py index 22b2bd5..d2b8e81 100644 --- a/structs/malloc_arena_t.py +++ b/structs/malloc_arena_t.py @@ -1,26 +1,39 @@ import ctypes +# +# 2.31 -> https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L1655 +# 2.34 -> https://elixir.bootlin.com/glibc/glibc-2.34/source/malloc/malloc.c#L1831 +# + NFASTBINS = 10 -NBINS = 128 -BINMAPSHIFT = 5 -BITSPERMAP = 1 << BINMAPSHIFT -BINMAPSIZE = NBINS / BITSPERMAP +NBINS = 254 +BINMAPSIZE = 0x10 -mfastbinptr = ctypes.c_uint64 -mchunkptr = ctypes.c_uint64 +def malloc_state64_t(gef = None): + pointer = ctypes.c_uint64 if gef and gef.arch.ptrsize == 8 else ctypes.c_uint32 -class malloc_arena_t(ctypes.Structure): - _fields_ = [ + fields = [ ("mutex", ctypes.c_uint32), - ("flags", ctypes.c_uint32), - ("fastbinsY", 10 * mfastbinptr), - ("top", mchunkptr), - ("last_remainder", mchunkptr), - ("bins", 256 * mchunkptr), - ("next", ctypes.c_uint64), - ("next_free", ctypes.c_uint64), - ("attached_threads", ctypes.c_uint64), - ("system_mem", ctypes.c_uint64), - ("max_system_mem", ctypes.c_uint64), + ("have_fastchunks", ctypes.c_uint32), + ] + + if gef and gef.libc.version and gef.libc.version >= (2, 27): + fields += [("fastbinsY", NFASTBINS * pointer), ] + + fields += [ + ("top", pointer), + ("last_remainder", pointer), + ("bins", NBINS * pointer), + ("binmap", BINMAPSIZE * ctypes.c_uint32), + ("next", pointer), + ("next_free", pointer), + ("attached_threads", pointer), + ("system_mem", pointer), + ("max_system_mem", pointer), ] + + class malloc_state64_cls(ctypes.Structure): + _fields_ = fields + + return malloc_state64_cls From c28ab60cbe7e7ddadaa2faf3ffbf4cf27c8e2240 Mon Sep 17 00:00:00 2001 From: hugsy Date: Mon, 13 Jun 2022 13:37:55 -0700 Subject: [PATCH 11/39] Added 3rd party to gef-extras (#61) * moved capstone, keystone, unicorn here * moved tests to extras * CI fix * moved docs * [unicorn] missing imports * Create requirements.txt * Update run-tests.yml * Create set-permission.c * Create utils.h --- .github/workflows/run-tests.yml | 84 +++ .gitignore | 3 + .pylintrc | 886 +++++++++++++++++++++++++ Makefile | 60 ++ docs/assemble.md | 67 ++ docs/capstone-disassemble.md | 26 + docs/ropper.md | 9 + docs/set-permission.md | 51 ++ docs/unicorn-emulate.md | 44 ++ mkdocs.yml | 21 +- pytest.ini | 15 + requirements.txt | 4 + TEMPLATE => scripts/TEMPLATE | 2 +- scripts/ropper.py | 48 ++ scripts/trinity/__init__.py | 46 ++ scripts/trinity/capstone.py | 145 ++++ scripts/trinity/keystone.py | 198 ++++++ scripts/trinity/mprotect.py | 77 +++ scripts/trinity/unicorn.py | 276 ++++++++ tests/__init__.py | 0 tests/binaries/Makefile | 51 ++ tests/binaries/default.c | 16 + tests/binaries/set-permission.c | 34 + tests/binaries/unicorn.c | 14 + tests/binaries/utils.h | 48 ++ tests/commands/capstone_disassemble.py | 53 ++ tests/commands/keystone_assemble.py | 57 ++ tests/commands/ropper.py | 31 + tests/commands/set_permission.py | 74 +++ tests/commands/unicorn_emulate.py | 46 ++ tests/requirements.txt | 5 + tests/utils.py | 333 ++++++++++ 32 files changed, 2815 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/run-tests.yml create mode 100644 .pylintrc create mode 100644 Makefile create mode 100644 docs/assemble.md create mode 100644 docs/capstone-disassemble.md create mode 100644 docs/ropper.md create mode 100644 docs/set-permission.md create mode 100644 docs/unicorn-emulate.md create mode 100644 pytest.ini rename TEMPLATE => scripts/TEMPLATE (91%) create mode 100644 scripts/ropper.py create mode 100644 scripts/trinity/__init__.py create mode 100644 scripts/trinity/capstone.py create mode 100644 scripts/trinity/keystone.py create mode 100644 scripts/trinity/mprotect.py create mode 100644 scripts/trinity/unicorn.py create mode 100644 tests/__init__.py create mode 100644 tests/binaries/Makefile create mode 100644 tests/binaries/default.c create mode 100644 tests/binaries/set-permission.c create mode 100644 tests/binaries/unicorn.c create mode 100644 tests/binaries/utils.h create mode 100644 tests/commands/capstone_disassemble.py create mode 100644 tests/commands/keystone_assemble.py create mode 100644 tests/commands/ropper.py create mode 100644 tests/commands/set_permission.py create mode 100644 tests/commands/unicorn_emulate.py create mode 100644 tests/requirements.txt create mode 100644 tests/utils.py diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..f1ef46c --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,84 @@ +name: CI Test for GEF-EXTRAS + +on: + push: + branches: + - master + - dev + + pull_request: + branches: + - master + - dev + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: + - ubuntu-20.04 + - ubuntu-18.04 + # - [self-hosted, linux, ARM64] + # - [self-hosted, linux, ARM] + name: "Run Unit tests on ${{ matrix.os }}" + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash + + steps: + - uses: actions/checkout@v2 + + - name: Install python and toolchain + run: | + sudo apt-get update + sudo apt-get install -y gdb-multiarch python3-dev python3-pip python3-wheel python3-setuptools git cmake gcc g++ pkg-config libglib2.0-dev gdbserver + sudo python3 -m pip install --upgrade pip + + - name: Set architecture specific properties + id: set-arch-properties + run: | + echo "::set-output name=arch::$(uname --processor)" + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(python3 -m pip cache dir)" + + - name: Cache dependencies + uses: actions/cache@v2 + id: cache-deps + env: + cache-name: cache-deps + with: + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + path: | + ${{ steps.pip-cache.outputs.dir }} + restore-keys: + ${{ runner.os }}-pip-${{ env.cache-name }}- + ${{ runner.os }}-pip- + ${{ runner.os }}-${{ env.cache-name }}- + ${{ runner.os }}- + + - name: Install requirements + run: | + mkdir -p ${{ steps.pip-cache.outputs.dir }} + python3 -m pip install --user --upgrade -r ./requirements.txt + python3 -m pip install --user --upgrade -r ./tests/requirements.txt + + - name: Setup GEF + run: | + echo "source $(pwd)/gef.py" > ~/.gdbinit + gdb -q -ex 'gef missing' -ex 'gef help' -ex 'gef config' -ex start -ex continue -ex quit /bin/pwd + + - name: Run Tests + env: + GEF_CI_ARCH: ${{ steps.set-arch-properties.outputs.arch }} + run: | + make test + + - name: Run linter + run: | + make lint + diff --git a/.gitignore b/.gitignore index bde8593..4871af6 100644 --- a/.gitignore +++ b/.gitignore @@ -89,3 +89,6 @@ ENV/ .ropeproject .vscode +.benchmarks +.pytest_cache + diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..8d6c695 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,886 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10.0 + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the ignore-list. The +# regex matches against paths and can be in Posix or Windows format. +ignore-paths= + +# Files or directories matching the regex patterns are skipped. The regex +# matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=0 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.6 + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +; disable=invalid-name, +; disallowed-name, +; empty-docstring, +; missing-module-docstring, +; missing-class-docstring, +; missing-function-docstring, +; unidiomatic-typecheck, +; non-ascii-name, +; consider-using-enumerate, +; consider-iterating-dictionary, +; bad-classmethod-argument, +; bad-mcs-method-argument, +; bad-mcs-classmethod-argument, +; single-string-used-for-slots, +; consider-using-dict-items, +; use-maxsplit-arg, +; use-sequence-for-iteration, +; too-many-lines, +; missing-final-newline, +; trailing-newlines, +; superfluous-parens, +; mixed-line-endings, +; unexpected-line-ending-format, +; wrong-spelling-in-comment, +; wrong-spelling-in-docstring, +; invalid-characters-in-docstring, +; multiple-imports, +; wrong-import-order, +; ungrouped-imports, +; wrong-import-position, +; useless-import-alias, +; import-outside-toplevel, +; use-implicit-booleaness-not-len, +; use-implicit-booleaness-not-comparison, +; raw-checker-failed, +; bad-inline-option, +; locally-disabled, +; file-ignored, +; suppressed-message, +; useless-suppression, +; deprecated-pragma, +; use-symbolic-message-instead, +; c-extension-no-member, +; literal-comparison, +; comparison-with-itself, +; no-self-use, +; no-classmethod-decorator, +; no-staticmethod-decorator, +; useless-object-inheritance, +; property-with-parameters, +; cyclic-import, +; consider-using-from-import, +; duplicate-code, +; too-many-ancestors, +; too-many-instance-attributes, +; too-few-public-methods, +; too-many-public-methods, +; too-many-return-statements, +; too-many-branches, +; too-many-arguments, +; too-many-locals, +; too-many-statements, +; too-many-boolean-expressions, +; consider-merging-isinstance, +; too-many-nested-blocks, +; simplifiable-if-statement, +; redefined-argument-from-local, +; no-else-return, +; consider-using-ternary, +; trailing-comma-tuple, +; stop-iteration-return, +; simplify-boolean-expression, +; inconsistent-return-statements, +; useless-return, +; consider-swap-variables, +; consider-using-join, +; consider-using-in, +; consider-using-get, +; chained-comparison, +; consider-using-dict-comprehension, +; consider-using-set-comprehension, +; simplifiable-if-expression, +; no-else-raise, +; unnecessary-comprehension, +; consider-using-sys-exit, +; no-else-break, +; no-else-continue, +; super-with-arguments, +; simplifiable-condition, +; condition-evals-to-constant, +; consider-using-generator, +; use-a-generator, +; consider-using-min-builtin, +; consider-using-max-builtin, +; consider-using-with, +; unnecessary-dict-index-lookup, +; use-list-literal, +; use-dict-literal, +; pointless-statement, +; pointless-string-statement, +; expression-not-assigned, +; unnecessary-pass, +; unnecessary-lambda, +; assign-to-new-keyword, +; useless-else-on-loop, +; exec-used, +; eval-used, +; confusing-with-statement, +; using-constant-test, +; missing-parentheses-for-call-in-test, +; self-assigning-variable, +; redeclared-assigned-name, +; assert-on-string-literal, +; comparison-with-callable, +; lost-exception, +; nan-comparison, +; assert-on-tuple, +; attribute-defined-outside-init, +; bad-staticmethod-argument, +; protected-access, +; arguments-differ, +; signature-differs, +; abstract-method, +; super-init-not-called, +; no-init, +; non-parent-init-called, +; useless-super-delegation, +; invalid-overridden-method, +; arguments-renamed, +; unused-private-member, +; overridden-final-method, +; subclassed-final-class, +; bad-indentation, +; wildcard-import, +; deprecated-module, +; reimported, +; import-self, +; preferred-module, +; misplaced-future, +; fixme, +; global-variable-undefined, +; global-statement, +; global-at-module-level, +; unused-argument, +; unused-wildcard-import, +; redefined-outer-name, +; redefined-builtin, +; undefined-loop-variable, +; unbalanced-tuple-unpacking, +; cell-var-from-loop, +; possibly-unused-variable, +; self-cls-assignment, +; bare-except, +; broad-except, +; duplicate-except, +; try-except-raise, +; raise-missing-from, +; raising-format-tuple, +; wrong-exception-operation, +; keyword-arg-before-vararg, +; arguments-out-of-order, +; non-str-assignment-to-dunder-name, +; isinstance-second-argument-not-valid-type, +; logging-not-lazy, +; logging-format-interpolation, +; logging-fstring-interpolation, +; bad-format-string-key, +; unused-format-string-key, +; missing-format-argument-key, +; unused-format-string-argument, +; format-combined-specification, +; missing-format-attribute, +; invalid-format-index, +; duplicate-string-formatting-argument, +; f-string-without-interpolation, +; useless-with-lock + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +; enable=unneeded-not, +; format-string-without-interpolation, +; anomalous-unicode-escape-in-string, +; implicit-str-concat, +; inconsistent-quotes, +; redundant-u-string-prefix, +; boolean-datetime, +; redundant-unittest-assert, +; deprecated-method, +; bad-thread-instantiation, +; shallow-copy-environ, +; invalid-envvar-default, +; subprocess-popen-preexec-fn, +; subprocess-run-check, +; deprecated-argument, +; deprecated-class, +; deprecated-decorator, +; unspecified-encoding, +; forgotten-debug-statement, +; using-f-string-in-unsupported-version, +; using-final-decorator-in-unsupported-version, +; singleton-comparison, +; consider-using-f-string, +; line-too-long, +; trailing-whitespace, +; multiple-statements, +; syntax-error, +; unrecognized-inline-option, +; bad-option-value, +; bad-plugin-value, +; bad-configuration-section, +; init-is-generator, +; return-in-init, +; function-redefined, +; not-in-loop, +; return-outside-function, +; yield-outside-function, +; return-arg-in-generator, +; nonexistent-operator, +; duplicate-argument-name, +; abstract-class-instantiated, +; bad-reversed-sequence, +; too-many-star-expressions, +; invalid-star-assignment-target, +; star-needs-assignment-target, +; nonlocal-and-global, +; continue-in-finally, +; nonlocal-without-binding, +; used-prior-global-declaration, +; misplaced-format-function, +; method-hidden, +; access-member-before-definition, +; no-method-argument, +; no-self-argument, +; invalid-slots-object, +; assigning-non-slot, +; invalid-slots, +; inherit-non-class, +; inconsistent-mro, +; duplicate-bases, +; class-variable-slots-conflict, +; invalid-class-object, +; non-iterator-returned, +; unexpected-special-method-signature, +; invalid-length-returned, +; invalid-bool-returned, +; invalid-index-returned, +; invalid-repr-returned, +; invalid-str-returned, +; invalid-bytes-returned, +; invalid-hash-returned, +; invalid-length-hint-returned, +; invalid-format-returned, +; invalid-getnewargs-returned, +; invalid-getnewargs-ex-returned, +; import-error, +; relative-beyond-top-level, +; used-before-assignment, +; undefined-variable, +; undefined-all-variable, +; invalid-all-object, +; invalid-all-format, +; no-name-in-module, +; unpacking-non-sequence, +; bad-except-order, +; raising-bad-type, +; bad-exception-context, +; misplaced-bare-raise, +; raising-non-exception, +; notimplemented-raised, +; catching-non-exception, +; bad-super-call, +; no-member, +; not-callable, +; assignment-from-no-return, +; no-value-for-parameter, +; too-many-function-args, +; unexpected-keyword-arg, +; redundant-keyword-arg, +; missing-kwoa, +; invalid-sequence-index, +; invalid-slice-index, +; assignment-from-none, +; not-context-manager, +; invalid-unary-operand-type, +; unsupported-binary-operation, +; repeated-keyword, +; not-an-iterable, +; not-a-mapping, +; unsupported-membership-test, +; unsubscriptable-object, +; unsupported-assignment-operation, +; unsupported-delete-operation, +; invalid-metaclass, +; unhashable-dict-key, +; dict-iter-missing-items, +; await-outside-async, +; logging-unsupported-format, +; logging-format-truncated, +; logging-too-many-args, +; logging-too-few-args, +; bad-format-character, +; truncated-format-string, +; mixed-format-string, +; format-needs-mapping, +; missing-format-string-key, +; too-many-format-args, +; too-few-format-args, +; bad-string-format-type, +; bad-str-strip-call, +; invalid-envvar-value, +; yield-inside-async-function, +; not-async-context-manager, +; fatal, +; astroid-error, +; parse-error, +; config-parse-error, +; method-check-failed, +; unreachable, +; dangerous-default-value, +; duplicate-key, +; unnecessary-semicolon, +; global-variable-not-assigned, +; unused-import, +; unused-variable, +; binary-op-exception, +; bad-format-string, +; anomalous-backslash-in-string, +; bad-open-mode + +enable = F,E,unreachable,duplicate-key,unnecessary-semicolon,unused-variable,binary-op-exception,bad-format-string,anomalous-backslash-in-string,bad-open-mode,dangerous-default-value,trailing-whitespace,unneeded-not,singleton-comparison,unused-import,line-too-long,multiple-statements,consider-using-f-string,global-variable-not-assigned +disable = all + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format=LF + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=200 + +# Maximum number of lines in a module. +max-module-lines=15000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the 'python-enchant' package. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear and the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=no + +# Signatures are removed from the similarity computation +ignore-signatures=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# class is considered mixin if its name matches the mixin-class-rgx option. +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins ignore-mixin- +# members is set to 'yes' +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1b4a20f --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) +NB_CORES := $(shell grep --count '^processor' /proc/cpuinfo) +PYLINT_RC := $(ROOT_DIR)/.pylintrc +PYLINT_DISABLE:= all +PYLINT_JOBS := $(NB_CORES) +PYLINT_SUGGEST_FIX := y +PYLINT_PY_VERSION := 3.6 +PYLINT_PARAMETERS := --jobs=$(PYLINT_JOBS) --suggestion-mode=$(PYLINT_SUGGEST_FIX) --py-version=$(PYLINT_PY_VERSION) --rcfile=$(PYLINT_RC) +TARGET := $(shell lscpu | head -1 | sed -e 's/Architecture:\s*//g') +COVERAGE_DIR ?= /tmp/cov +GDBINIT_TMP = $(shell mktemp) +GDBINIT_BACKUP = $(GDBINIT_TMP) +GEFRC_TMP = $(shell mktemp) +GEFRC_BACKUP = $(GEFRC_TMP) +TMPDIR ?= $(shell mktemp -d) +GEF_PATH := $(TMPDIR)/gef.py +WORKING_DIR := $(TMPDIR) +PYTEST_PARAMETERS := --verbose --forked --numprocesses=$(NB_CORES) +BRANCH := $(shell git rev-parse --abbrev-ref HEAD) + + +.PHONY: test test_% Test% testbins clean lint + + +test: setup testbins + WORKING_DIR=$(WORKING_DIR) GEF_PATH=$(GEF_PATH) python3 -m pytest $(PYTEST_PARAMETERS) -k "not benchmark" + +test_%: setup testbins + WORKING_DIR=$(WORKING_DIR) GEF_PATH=$(GEF_PATH) python3 -m pytest $(PYTEST_PARAMETERS) -k $@ + +testbins: $(wildcard tests/binaries/*.c) + @WORKING_DIR=$(WORKING_DIR) $(MAKE) -j $(NB_CORES) -C tests/binaries TARGET=$(TARGET) all + +clean: + WORKING_DIR=$(WORKING_DIR) $(MAKE) -j $(NB_CORES) -C tests/binaries clean + @rm -rf $(WORKING_DIR) || true + +restore: + @mv $(GDBINIT_BACKUP) ~/.gdbinit || true + @mv $(GEFRC_BACKUP) ~/.gef.rc || true + +lint: + python3 -m pylint $(PYLINT_PARAMETERS) $(wildcard $(ROOT_DIR)/scripts/*.py) + python3 -m pylint $(PYLINT_PARAMETERS) $(wildcard tests/commands/*.py) + +coverage: + @! ( [ -d $(COVERAGE_DIR) ] && echo "COVERAGE_DIR=$(COVERAGE_DIR) exists already") + @mkdir -p $(COVERAGE_DIR) + @COVERAGE_DIR=$(COVERAGE_DIR) $(MAKE) test + @coverage combine $(COVERAGE_DIR)/* + @coverage html --include $(TMPGEF) "/$(ROOT_DIR)/scripts/*.py" + @rm -rf $(COVERAGE_DIR) + +setup: + wget -O $(GEF_PATH) -q https://gef.blah.cat/py + mv ~/.gdbinit $(GDBINIT_BACKUP) || true + mv ~/.gef.rc $(GEFRC_BACKUP) || true + echo source $(GEF_PATH) > ~/.gdbinit + echo gef config gef.extra_plugins_dir $(ROOT_DIR)/scripts >> ~/.gdbinit + diff --git a/docs/assemble.md b/docs/assemble.md new file mode 100644 index 0000000..f52de86 --- /dev/null +++ b/docs/assemble.md @@ -0,0 +1,67 @@ +## Command assemble ## + +If you have installed [`keystone`](https://www.keystone-engine.org/), then `gef` +will provide a convenient command to assemble native instructions directly to +opcodes of the architecture you are currently debugging. + +Call it via `assemble` or its alias `asm`: + +``` +gef➤ asm [INSTRUCTION [; INSTRUCTION ...]] +``` + +![gef-assemble](https://i.imgur.com/ShuPF6h.png) + +By setting the `--arch ARCH` and `--mode MODE` the target platform for the +assembly can be changed. Available architectures and modes can be displayed +with `--list-archs`. + +``` +gef➤ asm --list-archs +Available architectures/modes (with endianness): +- ARM + * ARM (little, big) + * THUMB (little, big) + * ARMV8 (little, big) + * THUMBV8 (little, big) +- ARM64 + * AARCH64 (little) +- MIPS + * MIPS32 (little, big) + * MIPS64 (little, big) +- PPC + * PPC32 (big) + * PPC64 (little, big) +- SPARC + * SPARC32 (little, big) + * SPARC64 (big) +- SYSTEMZ + * SYSTEMZ (little, big) +- X86 + * 16 (little) + * 32 (little) + * 64 (little) +``` + +``` +gef➤ asm --arch x86 --mode 32 [INSTRUCTION [; INSTRUCTION ...]] +gef➤ asm --arch arm [INSTRUCTION [; INSTRUCTION ...]] +``` + +To choose the endianness use `--endian ENDIANNESS` (by default, `little`): + +``` +gef➤ asm --endian big [INSTRUCTION [; INSTRUCTION ...]] +``` + +Using the `--overwrite-location LOCATION` option, `gef` will write the assembly +code generated by `keystone` directly to the memory location specified. This +makes it extremely convenient to simply overwrite opcodes. + +![gef-assemble-overwrite](https://i.imgur.com/BsbGXNC.png) + +Another convenient option is `--as-shellcode` which outputs the generated +shellcode as an escaped python string. It can then easily be used in your +python scripts. + +![gef-assemble-shellcode](https://i.imgur.com/E2fpFuH.png) diff --git a/docs/capstone-disassemble.md b/docs/capstone-disassemble.md new file mode 100644 index 0000000..16aa193 --- /dev/null +++ b/docs/capstone-disassemble.md @@ -0,0 +1,26 @@ +## Command capstone-disassemble ## + +If you have installed the [`capstone`](http://capstone-engine.org) library and +its Python bindings, you can use it to disassemble any memory in your debugging +session. This plugin was created to offer an alternative to `GDB`'s disassemble +function which sometimes gets things mixed up. + +You can use its alias `cs-disassemble` or just `cs` with the location to +disassemble at. If not specified, it will use `$pc`. + +``` +gef➤ cs main+0x10 +``` + +![cs-disassemble](https://i.imgur.com/JG7aVRP.png) + +Disassemble more instructions + +``` +gef➤ cs --length 20 +``` + +Show opcodes next to disassembly +``` +gef➤ cs --show-opcodes +``` diff --git a/docs/ropper.md b/docs/ropper.md new file mode 100644 index 0000000..624705e --- /dev/null +++ b/docs/ropper.md @@ -0,0 +1,9 @@ +## Command ropper + +`ropper` is a gadget finding tool, easily installable via `pip`. It provides a +very convenient `--search` function to search gadgets from a regular +expression: + +![ropper](https://pbs.twimg.com/media/Cm4f4i5VIAAP-E2.jpg:large) + +`ropper` comes with a full set of options, all documented from the `--help` menu. diff --git a/docs/set-permission.md b/docs/set-permission.md new file mode 100644 index 0000000..fcd53e6 --- /dev/null +++ b/docs/set-permission.md @@ -0,0 +1,51 @@ +## Command set-permission ## + +This command was added to facilitate the exploitation process, by changing the +permissions on a specific memory page directly from the debugger. + +By default, GDB does not allow you to do that, so the command will modify a +code section of the binary being debugged, and add a native `mprotect` syscall +stub. For example, for x86, the following stub will be inserted: + +``` +pushad +mov eax, mprotect_syscall_num +mov ebx, address_of_the_page +mov ecx, size_of_the_page +mov edx, permission_to_set +int 0x80 +popad +``` + +A breakpoint is added following this stub, which when hit will restore the +original context, allowing you to resume execution. + +The usage is + +``` +gef➤ set-permission address [permission] +``` + +The `permission` can be set using a bitmask as integer with read (1), write (2) +and execute (4). For combinations of these permissions they can just be added: +Read and Execute permission would be 1 + 4 = 5. + +`mprotect` is an alias for `set-permission`. As an example, to set the `stack` +as READ|WRITE|EXECUTE on this binary, + +![mprotect-before](https://i.imgur.com/RRYHxzW.png) + +Simply run + +``` +gef➤ mprotect 0xfffdd000 +``` + +Et voilà! GEF will use the memory runtime information to correctly adjust the +permissions of the entire section. + +![mprotect-after](https://i.imgur.com/9MvyQi8.png) + +Or for a full demo video on an AARCH64 VM: + +[![set-permission-aarch64](https://img.youtube.com/vi/QqmfxIGzbmM/0.jpg)](https://www.youtube.com/watch?v=QqmfxIGzbmM) diff --git a/docs/unicorn-emulate.md b/docs/unicorn-emulate.md new file mode 100644 index 0000000..be981d4 --- /dev/null +++ b/docs/unicorn-emulate.md @@ -0,0 +1,44 @@ +## Command unicorn-emulate ## + +If you have installed [`unicorn`](http://unicorn-engine.org) emulation engine +and its Python bindings, GEF integrates a new command to emulate instructions +of your current debugging context ! + +This `unicorn-emulate` command (or its alias `emu`) will replicate the current +memory mapping (including the page permissions) for you, and by default (i.e. +without any additional argument), it will emulate the execution of the +instruction about to be executed (i.e. the one pointed by `$pc`). Furthermore +the command will print out the state of the registers before and after the +emulation. + +Use `-h` for help: + +``` +gef➤ emu -h +``` + +For example, the following command will emulate only the next 2 instructions: + +``` +gef➤ emu 2 +``` + +And show this: + +![emu](https://i.imgur.com/n4Oy5D0.png) + +In this example, we can see that after executing + +``` +0x555555555171 sub rsp, 0x10 +0x555555555175 mov edi, 0x100 +``` + +The registers `rsp` and `rdi` are tainted (modified). + +A convenient option is `--output-file /path/to/file.py` that will generate a +pure Python script embedding your current execution context, ready to be re-used +outside GEF!! This can be useful for dealing with obfuscation or solve crackmes +if powered with a SMT for instance. + + diff --git a/mkdocs.yml b/mkdocs.yml index 751e2b0..ae5aa52 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,12 +2,17 @@ site_name: GEF Extras - Extra goodies for GEF by and for the community theme: readthedocs nav: - Home: index.md -- BinCompare: bincompare.md -- ByteArray: bytearray.md -- FTrace: ftrace.md -- PeekPointers: peekpointers.md -- RetDec: retdec.md -- Skeleton: skel.md -- WinDbg: windbg.md -- Error: error.md +- assemble: assemble.md +- bincompare: bincompare.md +- bytearray: bytearray.md +- capstone-disassemble: capstone-disassemble.md +- error: error.md +- ftrace: ftrace.md - ida-rpyc: ida_rpyc.md +- peekpointers: peekpointers.md +- retdec: retdec.md +- ropper: ropper.md +- set-permission: set-permission.md +- exploit-template: skel.md +- unicorn-emulate: unicorn-emulate.md +- windbg: windbg.md diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..72f4744 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,15 @@ +[pytest] +log_level = INFO +minversion = 6.0 +required_plugins = + pytest-xdist + pytest-benchmark +python_functions = + test_* + time_* +python_files = *.py +testpaths = + tests +markers = + slow: flag test as slow (deselect with '-m "not slow"') + online: flag test as requiring internet to work (deselect with '-m "not online"') diff --git a/requirements.txt b/requirements.txt index f63f857..e3f2441 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,7 @@ retdec-python pygments rpyc +keystone-engine +capstone +unicorn +ropper diff --git a/TEMPLATE b/scripts/TEMPLATE similarity index 91% rename from TEMPLATE rename to scripts/TEMPLATE index 22482f7..a7eb778 100644 --- a/TEMPLATE +++ b/scripts/TEMPLATE @@ -16,4 +16,4 @@ class MyCommand(GenericCommand): def do_invoke(self, argv): return -register_external_command(MyCommand()) +register(MyCommand) diff --git a/scripts/ropper.py b/scripts/ropper.py new file mode 100644 index 0000000..3ad2405 --- /dev/null +++ b/scripts/ropper.py @@ -0,0 +1,48 @@ +__AUTHOR__ = "hugsy" +__VERSION__ = 0.1 +__NAME__ = "ropper" + +import sys +import readline + +import gdb +import ropper + + +@register +class RopperCommand(GenericCommand): + """Ropper (https://scoding.de/ropper/) plugin.""" + + _cmdline_ = "ropper" + _syntax_ = f"{_cmdline_} [ROPPER_OPTIONS]" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_NONE) + return + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + ropper = sys.modules["ropper"] + if "--file" not in argv: + path = gef.session.file + if not path: + err("No file provided") + return + sect = next(filter(lambda x: x.path == path, gef.memory.maps)) + argv.append("--file") + argv.append(path) + argv.append("-I") + argv.append(f"{sect.page_start:#x}") + + # 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 diff --git a/scripts/trinity/__init__.py b/scripts/trinity/__init__.py new file mode 100644 index 0000000..4368b7e --- /dev/null +++ b/scripts/trinity/__init__.py @@ -0,0 +1,46 @@ + + +def get_generic_arch(module: ModuleType, prefix: str, arch: str, mode: Optional[str], big_endian: Optional[bool], to_string: bool = False) -> Tuple[str, Union[int, str]]: + """ + Retrieves architecture and mode from the arguments for use for the holy + {cap,key}stone/unicorn trinity. + """ + if to_string: + arch = f"{module.__name__}.{prefix}_ARCH_{arch}" + if mode: + mode = f"{module.__name__}.{prefix}_MODE_{mode}" + else: + mode = "" + if gef.arch.endianness == Endianness.BIG_ENDIAN: + mode += f" + {module.__name__}.{prefix}_MODE_BIG_ENDIAN" + else: + mode += f" + {module.__name__}.{prefix}_MODE_LITTLE_ENDIAN" + + else: + arch = getattr(module, f"{prefix}_ARCH_{arch}") + if mode: + mode = getattr(module, f"{prefix}_MODE_{mode}") + else: + mode = 0 + if big_endian: + mode |= getattr(module, f"{prefix}_MODE_BIG_ENDIAN") + else: + mode |= getattr(module, f"{prefix}_MODE_LITTLE_ENDIAN") + + return arch, mode + + +def get_generic_running_arch(module: ModuleType, prefix: str, to_string: bool = False) -> Union[Tuple[None, None], Tuple[str, Union[int, str]]]: + """ + Retrieves architecture and mode from the current context. + """ + + if not is_alive(): + return None, None + + if gef.arch is not None: + arch, mode = gef.arch.arch, gef.arch.mode + else: + raise OSError("Emulation not supported for your OS") + + return get_generic_arch(module, prefix, arch, mode, gef.arch.endianness == Endianness.BIG_ENDIAN, to_string) diff --git a/scripts/trinity/capstone.py b/scripts/trinity/capstone.py new file mode 100644 index 0000000..c6e14db --- /dev/null +++ b/scripts/trinity/capstone.py @@ -0,0 +1,145 @@ +import capstone + +import sys + +def disassemble(location: int, nb_insn: int, **kwargs: Any) -> Generator[Instruction, None, None]: + """Disassemble `nb_insn` instructions after `addr` and `nb_prev` before + `addr` using the Capstone-Engine disassembler, if available. + Return an iterator of Instruction objects.""" + + def cs_insn_to_gef_insn(cs_insn: "capstone.CsInsn") -> Instruction: + sym_info = gdb_get_location_from_symbol(cs_insn.address) + loc = "<{}+{}>".format(*sym_info) if sym_info else "" + ops = [] + cs_insn.op_str.split(", ") + return Instruction(cs_insn.address, loc, cs_insn.mnemonic, ops, cs_insn.bytes) + + capstone = sys.modules["capstone"] + arch, mode = get_capstone_arch(arch=kwargs.get("arch"), mode=kwargs.get("mode"), endian=kwargs.get("endian")) + cs = capstone.Cs(arch, mode) + cs.detail = True + + page_start = align_address_to_page(location) + offset = location - page_start + pc = gef.arch.pc + + skip = int(kwargs.get("skip", 0)) + nb_prev = int(kwargs.get("nb_prev", 0)) + if nb_prev > 0: + location = gdb_get_nth_previous_instruction_address(pc, nb_prev) + nb_insn += nb_prev + + code = kwargs.get("code", gef.memory.read(location, gef.session.pagesize - offset - 1)) + for insn in cs.disasm(code, location): + if skip: + skip -= 1 + continue + nb_insn -= 1 + yield cs_insn_to_gef_insn(insn) + if nb_insn == 0: + break + return + + +def get_arch(arch: Optional[str] = None, mode: Optional[str] = None, endian: Optional[bool] = None, to_string: bool = False) -> Union[Tuple[None, None], Tuple[str, Union[int, str]]]: + capstone = sys.modules["capstone"] + + # hacky patch to unify capstone/ppc syntax with keystone & unicorn: + # CS_MODE_PPC32 does not exist (but UC_MODE_32 & KS_MODE_32 do) + if is_arch(Elf.Abi.POWERPC64): + raise OSError("Capstone not supported for PPC64 yet.") + + if is_alive() and is_arch(Elf.Abi.POWERPC): + + arch = "PPC" + mode = "32" + endian = (gef.arch.endianness == Endianness.BIG_ENDIAN) + return get_generic_arch(capstone, "CS", + arch or gef.arch.arch, + mode or gef.arch.mode, + endian, + to_string) + + if (arch, mode, endian) == (None, None, None): + return get_generic_running_arch(capstone, "CS", to_string) + return get_generic_arch(capstone, "CS", + arch or gef.arch.arch, + mode or gef.arch.mode, + endian or gef.arch.endianness == Endianness.BIG_ENDIAN, + to_string) + + +@register +class CapstoneDisassembleCommand(GenericCommand): + """Use capstone disassembly framework to disassemble code.""" + + _cmdline_ = "capstone-disassemble" + _syntax_ = f"{_cmdline_} [-h] [--show-opcodes] [--length LENGTH] [LOCATION]" + _aliases_ = ["cs-dis"] + _example_ = f"{_cmdline_} --length 50 $pc" + + def pre_load(self) -> None: + try: + __import__("capstone") + except ImportError: + msg = "Missing `capstone` package for Python. Install with `pip install capstone`." + raise ImportWarning(msg) + return + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + @only_if_gdb_running + @parse_arguments({("location"): "$pc"}, {("--show-opcodes", "-s"): True, "--length": 0}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + show_opcodes = args.show_opcodes + length = args.length or gef.config["context.nb_lines_code"] + location = parse_address(args.location) + if not location: + info(f"Can't find address for {args.location}") + return + + insns = [] + opcodes_len = 0 + for insn in capstone_disassemble(location, length, skip=length * self.repeat_count, **kwargs): + insns.append(insn) + opcodes_len = max(opcodes_len, len(insn.opcodes)) + + for insn in insns: + insn_fmt = f"{{:{opcodes_len}o}}" if show_opcodes else "{}" + text_insn = insn_fmt.format(insn) + msg = "" + + if insn.address == gef.arch.pc: + msg = Color.colorify(f"{RIGHT_ARROW} {text_insn}", "bold red") + valid, reason = self.capstone_analyze_pc(insn, length) + if valid: + gef_print(msg) + gef_print(reason) + break + else: + msg = f" {text_insn}" + + gef_print(msg) + return + + def capstone_analyze_pc(self, insn: Instruction, nb_insn: int) -> Tuple[bool, str]: + if gef.arch.is_conditional_branch(insn): + is_taken, reason = gef.arch.is_branch_taken(insn) + if is_taken: + reason = f"[Reason: {reason}]" if reason else "" + msg = Color.colorify(f"\tTAKEN {reason}", "bold green") + else: + reason = f"[Reason: !({reason})]" if reason else "" + msg = Color.colorify(f"\tNOT taken {reason}", "bold red") + return (is_taken, msg) + + if gef.arch.is_call(insn): + target_address = int(insn.operands[-1].split()[0], 16) + msg = [] + for i, new_insn in enumerate(capstone_disassemble(target_address, nb_insn)): + msg.append(f" {DOWN_ARROW if i == 0 else ' '} {new_insn!s}") + return (True, "\n".join(msg)) + + return (False, "") diff --git a/scripts/trinity/keystone.py b/scripts/trinity/keystone.py new file mode 100644 index 0000000..f49cfd3 --- /dev/null +++ b/scripts/trinity/keystone.py @@ -0,0 +1,198 @@ +__AUTHOR__ = "hugsy" +__VERSION__ = 0.1 +__NAME__ = "assemble" + + +import keystone +import gdb + + +@register +class AssembleCommand(GenericCommand): + """Inline code assemble. Architecture can be set in GEF runtime config. """ + + _cmdline_ = "assemble" + _syntax_ = f"{_cmdline_} [-h] [--list-archs] [--mode MODE] [--arch ARCH] [--overwrite-location LOCATION] [--endian ENDIAN] [--as-shellcode] instruction;[instruction;...instruction;])" + _aliases_ = ["asm",] + _example_ = (f"\n{_cmdline_} -a x86 -m 32 nop ; nop ; inc eax ; int3" + f"\n{_cmdline_} -a arm -m arm add r0, r0, 1") + + valid_arch_modes = { + # Format: ARCH = [MODES] with MODE = (NAME, HAS_LITTLE_ENDIAN, HAS_BIG_ENDIAN) + "ARM": [("ARM", True, True), ("THUMB", True, True), + ("ARMV8", True, True), ("THUMBV8", True, True)], + "ARM64": [("0", True, False)], + "MIPS": [("MIPS32", True, True), ("MIPS64", True, True)], + "PPC": [("PPC32", False, True), ("PPC64", True, True)], + "SPARC": [("SPARC32", True, True), ("SPARC64", False, True)], + "SYSTEMZ": [("SYSTEMZ", True, True)], + "X86": [("16", True, False), ("32", True, False), + ("64", True, False)] + } + valid_archs = valid_arch_modes.keys() + valid_modes = [_ for sublist in valid_arch_modes.values() for _ in sublist] + + def __init__(self) -> None: + super().__init__() + self["default_architecture"] = ("X86", "Specify the default architecture to use when assembling") + self["default_mode"] = ("64", "Specify the default architecture to use when assembling") + return + + def pre_load(self) -> None: + try: + __import__("keystone") + except ImportError: + msg = "Missing `keystone-engine` package for Python, install with: `pip install keystone-engine`." + raise ImportWarning(msg) + return + + def usage(self) -> None: + super().usage() + gef_print("") + self.list_archs() + return + + def list_archs(self) -> None: + gef_print("Available architectures/modes (with endianness):") + # for updates, see https://github.com/keystone-engine/keystone/blob/master/include/keystone/keystone.h + for arch in self.valid_arch_modes: + gef_print(f"- {arch}") + for mode, le, be in self.valid_arch_modes[arch]: + if le and be: + endianness = "little, big" + elif le: + endianness = "little" + elif be: + endianness = "big" + gef_print(f" * {mode:<7} ({endianness})") + return + + @parse_arguments({"instructions": [""]}, {"--mode": "", "--arch": "", "--overwrite-location": 0, "--endian": "little", "--list-archs": True, "--as-shellcode": True}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + arch_s, mode_s, endian_s = self["default_architecture"], self["default_mode"], "" + + args = kwargs["arguments"] + if args.list_archs: + self.list_archs() + return + + if not args.instructions: + err("No instruction given.") + return + + if is_alive(): + arch_s, mode_s = gef.arch.arch, gef.arch.mode + endian_s = "big" if gef.arch.endianness == Endianness.BIG_ENDIAN else "" + + if args.arch: + arch_s = args.arch + arch_s = arch_s.upper() + + if args.mode: + mode_s = args.mode + mode_s = mode_s.upper() + + if args.endian == "big": + endian_s = "big" + endian_s = endian_s.upper() + + if arch_s not in self.valid_arch_modes: + raise AttributeError(f"invalid arch '{arch_s}'") + + valid_modes = self.valid_arch_modes[arch_s] + try: + mode_idx = [m[0] for m in valid_modes].index(mode_s) + except ValueError: + raise AttributeError(f"invalid mode '{mode_s}' for arch '{arch_s}'") + + if endian_s == "little" and not valid_modes[mode_idx][1] or endian_s == "big" and not valid_modes[mode_idx][2]: + raise AttributeError(f"invalid endianness '{endian_s}' for arch/mode '{arch_s}:{mode_s}'") + + arch, mode = get_keystone_arch(arch=arch_s, mode=mode_s, endian=endian_s) + insns = [x.strip() for x in " ".join(args.instructions).split(";") if x] + info(f"Assembling {len(insns)} instruction(s) for {arch_s}:{mode_s}") + + if args.as_shellcode: + gef_print("""sc="" """) + + raw = b"" + for insn in insns: + res = keystone_assemble(insn, arch, mode, raw=True) + if res is None: + gef_print("(Invalid)") + continue + + if args.overwrite_location: + raw += res + continue + + s = binascii.hexlify(res) + res = b"\\x" + b"\\x".join([s[i:i + 2] for i in range(0, len(s), 2)]) + res = res.decode("utf-8") + + if args.as_shellcode: + res = f"""sc+="{res}" """ + + gef_print(f"{res!s:60s} # {insn}") + + if args.overwrite_location: + l = len(raw) + info(f"Overwriting {l:d} bytes at {format_address(args.overwrite_location)}") + gef.memory.write(args.overwrite_location, raw, l) + return + + +def get_arch(arch: Optional[str] = None, mode: Optional[str] = None, endian: Optional[bool] = None, to_string: bool = False) -> Union[Tuple[None, None], Tuple[str, Union[int, str]]]: + keystone = sys.modules["keystone"] + if (arch, mode, endian) == (None, None, None): + return get_generic_running_arch(keystone, "KS", to_string) + + if arch in ["ARM64", "SYSTEMZ"]: + modes = [None] + elif arch == "ARM" and mode == "ARMV8": + modes = ["ARM", "V8"] + elif arch == "ARM" and mode == "THUMBV8": + modes = ["THUMB", "V8"] + else: + modes = [mode] + a = arch + if not to_string: + mode = 0 + for m in modes: + arch, _mode = get_generic_arch(keystone, "KS", a, m, endian, to_string) + mode |= _mode + else: + mode = "" + for m in modes: + arch, _mode = get_generic_arch(keystone, "KS", a, m, endian, to_string) + mode += f"|{_mode}" + mode = mode[1:] + return arch, mode + + + +def assemble(code_str: str, arch: int, mode: int, **kwargs: Any) -> Optional[Union[str, bytearray]]: + """Assembly encoding function based on keystone.""" + keystone = sys.modules["keystone"] + code = gef_pybytes(code_str) + addr = kwargs.get("addr", 0x1000) + + try: + ks = keystone.Ks(arch, mode) + enc, cnt = ks.asm(code, addr) + except keystone.KsError as e: + err(f"Keystone assembler error: {e}") + return None + + if cnt == 0: + return "" + + enc = bytearray(enc) + if "raw" not in kwargs: + s = binascii.hexlify(enc) + enc = b"\\x" + b"\\x".join([s[i : i + 2] for i in range(0, len(s), 2)]) + enc = enc.decode("utf-8") + + return enc + + diff --git a/scripts/trinity/mprotect.py b/scripts/trinity/mprotect.py new file mode 100644 index 0000000..785e1c3 --- /dev/null +++ b/scripts/trinity/mprotect.py @@ -0,0 +1,77 @@ +__AUTHOR__ = "hugsy" +__NAME__ = "mprotect" +__VERSION__ = 0.1 + + +import gdb +import keystone + + +@register +class ChangePermissionCommand(GenericCommand): + """Change a page permission. By default, it will change it to 7 (RWX).""" + + _cmdline_ = "set-permission" + _syntax_ = (f"{_cmdline_} address [permission]\n" + "\taddress\t\tan address within the memory page for which the permissions should be changed\n" + "\tpermission\ta 3-bit bitmask with read=1, write=2 and execute=4 as integer") + _aliases_ = ["mprotect"] + _example_ = f"{_cmdline_} $sp 7" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + if len(argv) not in (1, 2): + err("Incorrect syntax") + self.usage() + return + + if len(argv) == 2: + perm = Permission(int(argv[1])) + else: + perm = Permission.ALL + + loc = safe_parse_and_eval(argv[0]) + if loc is None: + err("Invalid address") + return + + loc = int(loc) + sect = process_lookup_address(loc) + if sect is None: + err("Unmapped address") + return + + size = sect.page_end - sect.page_start + original_pc = gef.arch.pc + + info(f"Generating sys_mprotect({sect.page_start:#x}, {size:#x}, " + f"'{perm!s}') stub for arch {get_arch()}") + stub = self.get_stub_by_arch(sect.page_start, size, perm) + if stub is None: + err("Failed to generate mprotect opcodes") + return + + info("Saving original code") + original_code = gef.memory.read(original_pc, len(stub)) + + bp_loc = f"*{original_pc + len(stub):#x}" + info(f"Setting a restore breakpoint at {bp_loc}") + ChangePermissionBreakpoint(bp_loc, original_code, original_pc) + + info(f"Overwriting current memory at {loc:#x} ({len(stub)} bytes)") + gef.memory.write(original_pc, stub, len(stub)) + + info("Resuming execution") + gdb.execute("continue") + return + + def get_stub_by_arch(self, addr: int, size: int, perm: Permission) -> Union[str, bytearray, None]: + code = gef.arch.mprotect_asm(addr, size, perm) + arch, mode = get_keystone_arch() + raw_insns = keystone_assemble(code, arch, mode, raw=True) + return raw_insns + diff --git a/scripts/trinity/unicorn.py b/scripts/trinity/unicorn.py new file mode 100644 index 0000000..7213d1e --- /dev/null +++ b/scripts/trinity/unicorn.py @@ -0,0 +1,276 @@ + +import sys +import os +from typing import Dict, Optional, Tuple, Union + +import capstone +import unicorn + + +def get_arch(arch: Optional[str] = None, mode: Optional[str] = None, endian: Optional[bool] = None, to_string: bool = False) -> Union[Tuple[None, None], Tuple[str, Union[int, str]]]: + unicorn = sys.modules["unicorn"] + if (arch, mode, endian) == (None, None, None): + return get_generic_running_arch(unicorn, "UC", to_string) + return get_generic_arch(unicorn, "UC", arch, mode, endian, to_string) + + +def get_registers(to_string: bool = False) -> Union[Dict[str, int], Dict[str, str]]: + "Return a dict matching the Unicorn identifier for a specific register." + unicorn = sys.modules["unicorn"] + regs = {} + + if gef.arch is not None: + arch = gef.arch.arch.lower() + else: + raise OSError("Oops") + + const = getattr(unicorn, f"{arch}_const") + for reg in gef.arch.all_registers: + regname = f"UC_{arch.upper()}_REG_{reg[1:].upper()}" + if to_string: + regs[reg] = f"{const.__name__}.{regname}" + else: + regs[reg] = getattr(const, regname) + return regs + + +class UnicornEmulateCommand(GenericCommand): + """Use Unicorn-Engine to emulate the behavior of the binary, without affecting the GDB runtime. + By default the command will emulate only the next instruction, but location and number of + instruction can be changed via arguments to the command line. By default, it will emulate + the next instruction from current PC.""" + + _cmdline_ = "unicorn-emulate" + _syntax_ = (f"{_cmdline_} [--start LOCATION] [--until LOCATION] [--skip-emulation] [--output-file PATH] [NB_INSTRUCTION]" + "\n\t--start LOCATION specifies the start address of the emulated run (default $pc)." + "\t--until LOCATION specifies the end address of the emulated run." + "\t--skip-emulation\t do not execute the script once generated." + "\t--output-file /PATH/TO/SCRIPT.py writes the persistent Unicorn script into this file." + "\tNB_INSTRUCTION indicates the number of instructions to execute" + "\nAdditional options can be setup via `gef config unicorn-emulate`") + _aliases_ = ["emulate", ] + _example_ = f"{_cmdline_} --start $pc 10 --output-file /tmp/my-gef-emulation.py" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + self["verbose"] = (False, "Set unicorn-engine in verbose mode") + self["show_disassembly"] = (False, "Show every instruction executed") + return + + @only_if_gdb_running + @parse_arguments({"nb": 1}, {"--start": "", "--until": "", "--skip-emulation": True, "--output-file": ""}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + start_address = parse_address(str(args.start or gef.arch.pc)) + end_address = parse_address( + str(args.until or self.get_unicorn_end_addr(start_address, args.nb))) + self.run_unicorn(start_address, end_address, + skip_emulation=args.skip_emulation, to_file=args.output_file) + return + + def get_unicorn_end_addr(self, start_addr: int, nb: int) -> int: + dis = list(gef_disassemble(start_addr, nb + 1)) + last_insn = dis[-1] + return last_insn.address + + def run_unicorn(self, start_insn_addr: int, end_insn_addr: int, **kwargs: Any) -> None: + verbose = self["verbose"] or False + skip_emulation = kwargs.get("skip_emulation", False) + arch, mode = get_unicorn_arch(to_string=True) + unicorn_registers = get_unicorn_registers(to_string=True) + cs_arch, cs_mode = get_capstone_arch(to_string=True) + fname = gef.session.file.name + to_file = kwargs.get("to_file", None) + emulate_segmentation_block = "" + context_segmentation_block = "" + + if to_file: + tmp_filename = to_file + to_file = open(to_file, "w") + tmp_fd = to_file.fileno() + else: + tmp_fd, tmp_filename = tempfile.mkstemp( + suffix=".py", prefix="gef-uc-") + + if is_x86(): + # need to handle segmentation (and pagination) via MSR + emulate_segmentation_block = """ +# from https://github.com/unicorn-engine/unicorn/blob/master/tests/regress/x86_64_msr.py +SCRATCH_ADDR = 0xf000 +SEGMENT_FS_ADDR = 0x5000 +SEGMENT_GS_ADDR = 0x6000 +FSMSR = 0xC0000100 +GSMSR = 0xC0000101 + +def set_msr(uc, msr, value, scratch=SCRATCH_ADDR): + buf = b"\\x0f\\x30" # x86: wrmsr + uc.mem_map(scratch, 0x1000) + uc.mem_write(scratch, buf) + uc.reg_write(unicorn.x86_const.UC_X86_REG_RAX, value & 0xFFFFFFFF) + uc.reg_write(unicorn.x86_const.UC_X86_REG_RDX, (value >> 32) & 0xFFFFFFFF) + uc.reg_write(unicorn.x86_const.UC_X86_REG_RCX, msr & 0xFFFFFFFF) + uc.emu_start(scratch, scratch+len(buf), count=1) + uc.mem_unmap(scratch, 0x1000) + return + +def set_gs(uc, addr): return set_msr(uc, GSMSR, addr) +def set_fs(uc, addr): return set_msr(uc, FSMSR, addr) + +""" + + context_segmentation_block = """ + emu.mem_map(SEGMENT_FS_ADDR-0x1000, 0x3000) + set_fs(emu, SEGMENT_FS_ADDR) + set_gs(emu, SEGMENT_GS_ADDR) +""" + + content = """#!{pythonbin} -i +# +# Emulation script for "{fname}" from {start:#x} to {end:#x} +# +# Powered by gef, unicorn-engine, and capstone-engine +# +# @_hugsy_ +# +import collections +import capstone, unicorn + +registers = collections.OrderedDict(sorted({{{regs}}}.items(), key=lambda t: t[0])) +uc = None +verbose = {verbose} +syscall_register = "{syscall_reg}" + +def disassemble(code, addr): + cs = capstone.Cs({cs_arch}, {cs_mode}) + for i in cs.disasm(code, addr): + return i + +def hook_code(emu, address, size, user_data): + code = emu.mem_read(address, size) + insn = disassemble(code, address) + print(">>> {{:#x}}: {{:s}} {{:s}}".format(insn.address, insn.mnemonic, insn.op_str)) + return + +def code_hook(emu, address, size, user_data): + code = emu.mem_read(address, size) + insn = disassemble(code, address) + print(">>> {{:#x}}: {{:s}} {{:s}}".format(insn.address, insn.mnemonic, insn.op_str)) + return + +def intr_hook(emu, intno, data): + print(" \\-> interrupt={{:d}}".format(intno)) + return + +def syscall_hook(emu, user_data): + sysno = emu.reg_read(registers[syscall_register]) + print(" \\-> syscall={{:d}}".format(sysno)) + return + +def print_regs(emu, regs): + for i, r in enumerate(regs): + print("{{:7s}} = {{:#0{ptrsize}x}} ".format(r, emu.reg_read(regs[r])), end="") + if (i % 4 == 3) or (i == len(regs)-1): print("") + return + +{emu_block} + +def reset(): + emu = unicorn.Uc({arch}, {mode}) + +{context_block} +""".format(pythonbin=PYTHONBIN, fname=fname, start=start_insn_addr, end=end_insn_addr, + regs=",".join( + [f"'{k.strip()}': {unicorn_registers[k]}" for k in unicorn_registers]), + verbose="True" if verbose else "False", + syscall_reg=gef.arch.syscall_register, + cs_arch=cs_arch, cs_mode=cs_mode, + ptrsize=gef.arch.ptrsize * 2 + 2, # two hex chars per byte plus "0x" prefix + emu_block=emulate_segmentation_block if is_x86() else "", + arch=arch, mode=mode, + context_block=context_segmentation_block if is_x86() else "") + + if verbose: + info("Duplicating registers") + + for r in gef.arch.all_registers: + gregval = gef.arch.register(r) + content += f" emu.reg_write({unicorn_registers[r]}, {gregval:#x})\n" + + vmmap = gef.memory.maps + if not vmmap: + warn("An error occurred when reading memory map.") + return + + if verbose: + info("Duplicating memory map") + + for sect in vmmap: + if sect.path == "[vvar]": + # this section is for GDB only, skip it + continue + + page_start = sect.page_start + page_end = sect.page_end + size = sect.size + perm = sect.permission + + content += f" # Mapping {sect.path}: {page_start:#x}-{page_end:#x}\n" + content += f" emu.mem_map({page_start:#x}, {size:#x}, {perm.value:#o})\n" + + if perm & Permission.READ: + code = gef.memory.read(page_start, size) + loc = f"/tmp/gef-{fname}-{page_start:#x}.raw" + with open(loc, "wb") as f: + f.write(bytes(code)) + + content += f" emu.mem_write({page_start:#x}, open('{loc}', 'rb').read())\n" + content += "\n" + + content += " emu.hook_add(unicorn.UC_HOOK_CODE, code_hook)\n" + content += " emu.hook_add(unicorn.UC_HOOK_INTR, intr_hook)\n" + if is_x86_64(): + content += " emu.hook_add(unicorn.UC_HOOK_INSN, syscall_hook, None, 1, 0, unicorn.x86_const.UC_X86_INS_SYSCALL)\n" + content += " return emu\n" + + content += """ +def emulate(emu, start_addr, end_addr): + print("========================= Initial registers =========================") + print_regs(emu, registers) + + try: + print("========================= Starting emulation =========================") + emu.emu_start(start_addr, end_addr) + except Exception as e: + emu.emu_stop() + print("========================= Emulation failed =========================") + print("[!] Error: {{}}".format(e)) + + print("========================= Final registers =========================") + print_regs(emu, registers) + return + + +uc = reset() +emulate(uc, {start:#x}, {end:#x}) + +# unicorn-engine script generated by gef +""".format(start=start_insn_addr, end=end_insn_addr) + + os.write(tmp_fd, gef_pybytes(content)) + os.close(tmp_fd) + + if kwargs.get("to_file", None): + info(f"Unicorn script generated as '{tmp_filename}'") + os.chmod(tmp_filename, 0o700) + + if skip_emulation: + return + + ok(f"Starting emulation: {start_insn_addr:#x} {RIGHT_ARROW} {end_insn_addr:#x}") + + res = gef_execute_external([PYTHONBIN, tmp_filename], as_list=True) + gef_print("\n".join(res)) + + if not kwargs.get("to_file", None): + os.unlink(tmp_filename) + return diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/binaries/Makefile b/tests/binaries/Makefile new file mode 100644 index 0000000..009d20a --- /dev/null +++ b/tests/binaries/Makefile @@ -0,0 +1,51 @@ +CC = gcc +DEBUG = 1 +CFLAGS += -Wall +SOURCES = $(wildcard *.c) +LINKED = $(SOURCES:.c=.out) +LDFLAGS = +EXTRA_FLAGS = +TMPDIR ?= /tmp + +ifeq ($(TARGET), i686) +CFLAGS += -m32 +endif + +ifeq ($(DEBUG), 1) +CFLAGS += -DDEBUG=1 -ggdb -O0 +else +CFLAGS += -O1 +endif + + +.PHONY : all clean + +all: $(LINKED) + + +%.out : %.c + @echo "[+] Building '$@'" + @$(CC) $(CFLAGS) $(EXTRA_FLAGS) -o $(TMPDIR)/$@ $? $(LDFLAGS) + +clean : + @echo "[+] Cleaning stuff" + @cd $(TMPDIR) && rm -f $(LINKED) + +format-string-helper.out: EXTRA_FLAGS := -Wno-format-security + +checksec-no-canary.out: EXTRA_FLAGS := -fno-stack-protector + +# NOTE: If compiling with a older GCC (older than 4.8.4 maybe?) then use `-fno-pie` +checksec-no-pie.out: EXTRA_FLAGS := -no-pie + +checksec-no-nx.out: EXTRA_FLAGS := -z execstack + +pattern.out: EXTRA_FLAGS := -D_FORTIFY_SOURCE=0 -fno-stack-protector + +canary.out: EXTRA_FLAGS := -fstack-protector-all + +heap-non-main.out heap-tcache.out heap-multiple-heaps.out: EXTRA_FLAGS := -pthread + +heap-bins.out: EXTRA_FLAGS := -Wno-unused-result + +default.out: EXTRA_FLAGS := -fstack-protector-all -fpie -pie diff --git a/tests/binaries/default.c b/tests/binaries/default.c new file mode 100644 index 0000000..ffa4826 --- /dev/null +++ b/tests/binaries/default.c @@ -0,0 +1,16 @@ +/** + * default.c + * -*- mode: c -*- + * -*- coding: utf-8 -*- + */ + +#include +#include +#include + + +int main(int argc, char** argv, char** envp) +{ + printf("Hello World!\n"); + return EXIT_SUCCESS; +} diff --git a/tests/binaries/set-permission.c b/tests/binaries/set-permission.c new file mode 100644 index 0000000..49d5029 --- /dev/null +++ b/tests/binaries/set-permission.c @@ -0,0 +1,34 @@ +/** + * -*- mode: c -*- + * -*- coding: utf-8 -*- + * + * set-permission.c + * + * @author: @_hugsy_ + * @licence: WTFPL v.2 + */ + +#include +#include +#include +#include +#include + +#include "utils.h" + +int main(int argc, char** argv, char** envp) +{ + void *p = mmap((void *)0x1337000, + getpagesize(), + PROT_READ|PROT_WRITE, + MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED, + -1, + 0); + + if( p == (void *)-1) + return EXIT_FAILURE; + + DebugBreak(); + + return EXIT_SUCCESS; +} diff --git a/tests/binaries/unicorn.c b/tests/binaries/unicorn.c new file mode 100644 index 0000000..1e4c3c9 --- /dev/null +++ b/tests/binaries/unicorn.c @@ -0,0 +1,14 @@ +#include +#include + +int function1() +{ + const char* a1 = "PATH"; + const char* a2 = "WHATEVER"; + return strcmp( getenv(a1), a2); +} + +int main() +{ + return function1(); +} \ No newline at end of file diff --git a/tests/binaries/utils.h b/tests/binaries/utils.h new file mode 100644 index 0000000..095e66a --- /dev/null +++ b/tests/binaries/utils.h @@ -0,0 +1,48 @@ +#include + +/** + * Provide an cross-architecture way to break into the debugger. + * On some architectures, we resort to `raise(SIGINT)` which is not + * optimal, as it adds an extra frame to the stack. + */ + +/* Intel x64 (x86_64) */ +#if defined(__x86_64__) || defined(__amd64__) +#define DebugBreak() __asm__("int $3") + +/* Intel x32 (i686) */ +#elif defined(__i386) || defined(i386) || defined(__i386__) +#define DebugBreak() __asm__("int $3") + +/* AARCH64 (aarch64) */ +#elif defined(__aarch64__) +#define DebugBreak() { raise( SIGINT ) ; } + +/* ARM (armv7le*/ +#elif defined(__arm__) || defined(__arm) +#define DebugBreak() { raise( SIGINT ) ; } + +/* MIPS */ +/* MIPS64 (mips64el) */ +#elif defined(mips) || defined(__mips__) || defined(__mips) +#define DebugBreak() { raise( SIGINT ) ; } + +/* PowerPC */ +/* PowerPC64 (ppc64le) */ +#elif defined(__powerpc) || defined(__powerpc__) || defined(__powerpc64__) || defined(__POWERPC__) || defined(__ppc__) || defined(__PPC__) || defined(_ARCH_PPC) +#define DebugBreak() { raise( SIGINT ) ; } + +/* SPARC */ +/* SPARC64 */ +// #elif defined(__sparc) || defined(__sparc64__) || defined(__sparc__) +// #define DebugBreak() { raise( SIGINT ) ; } + +/* RISC V */ +#elif defined(__riscv) +#define DebugBreak() { raise( SIGINT ) ; } + +/* the rest */ +#else +#error "Unsupported architecture" +// #define DebugBreak() __builtin_trap() +#endif diff --git a/tests/commands/capstone_disassemble.py b/tests/commands/capstone_disassemble.py new file mode 100644 index 0000000..86e8615 --- /dev/null +++ b/tests/commands/capstone_disassemble.py @@ -0,0 +1,53 @@ +""" +capstone-disassemble command test module +""" + +import pytest +import capstone + + +from tests.utils import ( + ARCH, + gdb_start_silent_cmd, + gdb_run_silent_cmd, + gdb_run_cmd, + GefUnitTestGeneric, + removeuntil, +) + + +@pytest.mark.skipif(ARCH in ("mips64el", "ppc64le", "riscv64"), reason=f"Skipped for {ARCH}") +class CapstoneDisassembleCommand(GefUnitTestGeneric): + """`capstone-disassemble` command test module""" + + def test_cmd_capstone_disassemble(self): + self.assertNotIn("capstone", gdb_run_silent_cmd("gef missing")) + self.assertFailIfInactiveSession(gdb_run_cmd("capstone-disassemble")) + res = gdb_start_silent_cmd("capstone-disassemble") + self.assertNoException(res) + self.assertTrue(len(res.splitlines()) > 1) + + self.assertFailIfInactiveSession(gdb_run_cmd("cs --show-opcodes")) + res = gdb_start_silent_cmd("cs --show-opcodes --length 5 $pc") + self.assertNoException(res) + self.assertTrue(len(res.splitlines()) >= 5) + # jump to the output buffer + res = removeuntil("→ ", res, included=True) + addr, opcode, symbol, *_ = [x.strip() + for x in res.splitlines()[2].strip().split()] + # match the correct output format: [] mnemonic [operands,] + # gef➤ cs --show-opcodes --length 5 $pc + # → 0xaaaaaaaaa840 80000090 adrp x0, #0xaaaaaaaba000 + # 0xaaaaaaaaa844 00f047f9 ldr x0, [x0, #0xfe0] + # 0xaaaaaaaaa848 010040f9 ldr x1, [x0] + # 0xaaaaaaaaa84c e11f00f9 str x1, [sp, #0x38] + # 0xaaaaaaaaa850 010080d2 movz x1, #0 + + self.assertTrue(addr.startswith("0x")) + self.assertTrue(int(addr, 16)) + self.assertTrue(int(opcode, 16)) + self.assertTrue(symbol.startswith("<") and symbol.endswith(">")) + + res = gdb_start_silent_cmd("cs --show-opcodes main") + self.assertNoException(res) + self.assertTrue(len(res.splitlines()) > 1) diff --git a/tests/commands/keystone_assemble.py b/tests/commands/keystone_assemble.py new file mode 100644 index 0000000..dabf957 --- /dev/null +++ b/tests/commands/keystone_assemble.py @@ -0,0 +1,57 @@ +""" +keystone-assemble command test module +""" + +import pytest + +from tests.utils import ( + ARCH, + GefUnitTestGeneric, + gdb_run_silent_cmd, + gdb_start_silent_cmd, +) + + + +@pytest.mark.skipif(ARCH in ("mips64el", "ppc64le", "riscv64"), reason=f"Skipped for {ARCH}") +class KeystoneAssembleCommand(GefUnitTestGeneric): + """`keystone-assemble` command test module""" + + def setUp(self) -> None: + try: + import keystone # pylint: disable=W0611 + except ImportError: + pytest.skip("keystone-engine not available", allow_module_level=True) + return super().setUp() + + + def test_cmd_keystone_assemble(self): + self.assertNotIn("keystone", gdb_run_silent_cmd("gef missing")) + cmds = [ + "assemble --arch arm --mode arm add r0, r1, r2", + "assemble --arch arm --mode arm --endian big add r0, r1, r2", + "assemble --arch arm --mode thumb add r0, r1, r2", + "assemble --arch arm --mode thumb --endian big add r0, r1, r2", + "assemble --arch arm --mode armv8 add r0, r1, r2", + "assemble --arch arm --mode armv8 --endian big add r0, r1, r2", + "assemble --arch arm --mode thumbv8 add r0, r1, r2", + "assemble --arch arm --mode thumbv8 --endian big add r0, r1, r2", + "assemble --arch arm64 --mode 0 add x29, sp, 0; mov w0, 0; ret", + "assemble --arch mips --mode mips32 add $v0, 1", + "assemble --arch mips --mode mips32 --endian big add $v0, 1", + "assemble --arch mips --mode mips64 add $v0, 1", + "assemble --arch mips --mode mips64 --endian big add $v0, 1", + "assemble --arch ppc --mode ppc32 --endian big ori 0, 0, 0", + "assemble --arch ppc --mode ppc64 ori 0, 0, 0", + "assemble --arch ppc --mode ppc64 --endian big ori 0, 0, 0", + "assemble --arch sparc --mode sparc32 set 0, %o0", + "assemble --arch sparc --mode sparc32 --endian big set 0, %o0", + "assemble --arch sparc --mode sparc64 --endian big set 0, %o0", + "assemble --arch x86 --mode 16 mov ax, 0x42", + "assemble --arch x86 --mode 32 mov eax, 0x42", + "assemble --arch x86 --mode 64 mov rax, 0x42", + ] + for cmd in cmds: + res = gdb_start_silent_cmd(cmd) + self.assertNoException(res) + self.assertGreater(len(res.splitlines()), 1) diff --git a/tests/commands/ropper.py b/tests/commands/ropper.py new file mode 100644 index 0000000..21dcda7 --- /dev/null +++ b/tests/commands/ropper.py @@ -0,0 +1,31 @@ +""" +`ropper` command test module +""" + + +import pytest + +from tests.utils import ARCH, GefUnitTestGeneric, gdb_run_cmd, gdb_run_silent_cmd + + +class RopperCommand(GefUnitTestGeneric): + """`ropper` command test module""" + + def setUp(self) -> None: + try: + import ropper # pylint: disable=W0611 + except ImportError: + pytest.skip("ropper not available", allow_module_level=True) + return super().setUp() + + + @pytest.mark.skipif(ARCH not in ["x86_64", "i686"], reason=f"Skipped for {ARCH}") + def test_cmd_ropper(self): + cmd = "ropper" + self.assertFailIfInactiveSession(gdb_run_cmd(cmd)) + cmd = "ropper --search \"pop %; pop %; ret\"" + res = gdb_run_silent_cmd(cmd) + self.assertNoException(res) + self.assertNotIn(": error:", res) + self.assertTrue(len(res.splitlines()) > 2) + diff --git a/tests/commands/set_permission.py b/tests/commands/set_permission.py new file mode 100644 index 0000000..5786ad7 --- /dev/null +++ b/tests/commands/set_permission.py @@ -0,0 +1,74 @@ +""" +set_permission command test module +""" + +import pytest +import re + +from tests.utils import ( + ARCH, + GefUnitTestGeneric, + _target, + gdb_run_cmd, + gdb_start_silent_cmd, +) + + +@pytest.mark.skipif(ARCH not in ("i686", "x86_64", "armv7l", "aarch64"), + reason=f"Skipped for {ARCH}") +class SetPermissionCommand(GefUnitTestGeneric): + """`set_permission` command test module""" + + def setUp(self) -> None: + try: + import keystone # pylint: disable=W0611 + except ImportError: + pytest.skip("keystone-engine not available", allow_module_level=True) + return super().setUp() + + + def test_cmd_set_permission(self): + self.assertFailIfInactiveSession(gdb_run_cmd("set-permission")) + target = _target("set-permission") + + # get the initial stack address + res = gdb_start_silent_cmd("vmmap", target=target) + self.assertNoException(res) + stack_line = [l.strip() for l in res.splitlines() if "[stack]" in l][0] + stack_address = int(stack_line.split()[0], 0) + + # compare the new permissions + res = gdb_start_silent_cmd(f"set-permission {stack_address:#x}", + after=[f"xinfo {stack_address:#x}"], target=target) + self.assertNoException(res) + line = [l.strip() for l in res.splitlines() if l.startswith("Permissions: ")][0] + self.assertEqual(line.split()[1], "rwx") + + res = gdb_start_silent_cmd("set-permission 0x1338000", target=target) + self.assertNoException(res) + self.assertIn("Unmapped address", res) + + # Make sure set-permission command doesn't clobber any register + before = [ + "gef config context.clear_screen False", + "gef config context.layout '-code -stack'", + "entry-break", + "printf \"match_before\\n\"", + "info registers all", + "printf \"match_before\\n\"" + ] + after = [ + "printf \"match_after\\n\"", + "info registers all", + "printf \"match_after\\n\"" + ] + res = gdb_run_cmd("set-permission $sp", before=before, after=after, target=target) + matches = re.match(r"(?:.*match_before)(.+)(?:match_before.*)", res, flags=re.DOTALL) + if not matches: + raise Exception("Unexpected output") + regs_before = matches[1] + matches = re.match(r"(?:.*match_after)(.+)(?:match_after.*)", res, flags=re.DOTALL) + if not matches: + raise Exception("Unexpected output") + regs_after = matches[1] + self.assertEqual(regs_before, regs_after) diff --git a/tests/commands/unicorn_emulate.py b/tests/commands/unicorn_emulate.py new file mode 100644 index 0000000..c362a11 --- /dev/null +++ b/tests/commands/unicorn_emulate.py @@ -0,0 +1,46 @@ +""" +unicorn-emulate command test module +""" + + +import pytest +from tests.utils import ( + ARCH, + GefUnitTestGeneric, + _target, + gdb_run_silent_cmd, +) + + +@pytest.mark.skipif(ARCH not in ("i686", "x86_64", "armv7l", "aarch64"), + reason=f"Skipped for {ARCH}") +class UnicornEmulateCommand(GefUnitTestGeneric): + """`unicorn-emulate` command test module""" + + def setUp(self) -> None: + try: + import unicorn # pylint: disable=W0611 + except ImportError: + pytest.skip("unicorn-engine not available", allow_module_level=True) + return super().setUp() + + + @pytest.mark.skipif(ARCH not in ["x86_64"], reason=f"Skipped for {ARCH}") + def test_cmd_unicorn_emulate(self): + nb_insn = 4 + cmd = f"emu {nb_insn}" + res = gdb_run_silent_cmd(cmd) + self.assertFailIfInactiveSession(res) + + target = _target("unicorn") + before = ["break function1"] + after = ["si"] + start_marker = "= Starting emulation =" + end_marker = "Final registers" + res = gdb_run_silent_cmd(cmd, target=target, before=before, after=after) + self.assertNoException(res) + self.assertNotIn("Emulation failed", res) + self.assertIn(start_marker, res) + self.assertIn(end_marker, res) + insn_executed = len(res[res.find(start_marker):res.find(end_marker)].splitlines()[1:-1]) + self.assertTrue(insn_executed >= nb_insn) diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..9180d02 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,5 @@ +pylint +pytest +pytest-xdist +pytest-benchmark +coverage diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..a329508 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,333 @@ +""" +Utility functions for testing +""" + +import os +import pathlib +import platform +import re +import subprocess +import tempfile +import unittest +from urllib.request import urlopen +import warnings +import enum + +from typing import Iterable, Union, List, Optional + +TMPDIR = pathlib.Path(tempfile.gettempdir()) +ARCH = (os.getenv("GEF_CI_ARCH") or platform.machine()).lower() +BIN_SH = pathlib.Path("/bin/sh") +CI_VALID_ARCHITECTURES_32B = ("i686", "armv7l") +CI_VALID_ARCHITECTURES_64B = ("x86_64", "aarch64", "mips64el", "ppc64le", "riscv64") +CI_VALID_ARCHITECTURES = CI_VALID_ARCHITECTURES_64B + CI_VALID_ARCHITECTURES_32B +COVERAGE_DIR = os.getenv("COVERAGE_DIR", "") +DEFAULT_CONTEXT = "-code -stack" +DEFAULT_TARGET = TMPDIR / "default.out" +GEF_DEFAULT_PROMPT = "gef➤ " +GEF_DEFAULT_TEMPDIR = "/tmp/gef" +GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "gef.py")) +STRIP_ANSI_DEFAULT = True + + +CommandType = Union[str, Iterable[str]] + +class Color(enum.Enum): + """Used to colorify terminal output.""" + NORMAL = "\x1b[0m" + GRAY = "\x1b[1;38;5;240m" + LIGHT_GRAY = "\x1b[0;37m" + RED = "\x1b[31m" + GREEN = "\x1b[32m" + YELLOW = "\x1b[33m" + BLUE = "\x1b[34m" + PINK = "\x1b[35m" + CYAN = "\x1b[36m" + BOLD = "\x1b[1m" + UNDERLINE = "\x1b[4m" + UNDERLINE_OFF = "\x1b[24m" + HIGHLIGHT = "\x1b[3m" + HIGHLIGHT_OFF = "\x1b[23m" + BLINK = "\x1b[5m" + BLINK_OFF = "\x1b[25m" + + +class GdbAssertionError(AssertionError): + pass + + +class GefUnitTestGeneric(unittest.TestCase): + """Generic class for command testing, that defines all helpers""" + + @staticmethod + def assertException(buf): + """Assert that GEF raised an Exception.""" + if not ("Python Exception <" in buf + or "Traceback" in buf + or "'gdb.error'" in buf + or "Exception raised" in buf + or "failed to execute properly, reason:" in buf): + raise GdbAssertionError("GDB Exception expected, not raised") + + @staticmethod + def assertNoException(buf): + """Assert that no Exception was raised from GEF.""" + if ("Python Exception <" in buf + or "Traceback" in buf + or "'gdb.error'" in buf + or "Exception raised" in buf + or "failed to execute properly, reason:" in buf): + raise GdbAssertionError(f"Unexpected GDB Exception raised in {buf}") + + if "is deprecated and will be removed in a feature release." in buf: + lines = [l for l in buf.splitlines() + if "is deprecated and will be removed in a feature release." in l] + deprecated_api_names = {x.split()[1] for x in lines} + warnings.warn( + UserWarning(f"Use of deprecated API(s): {', '.join(deprecated_api_names)}") + ) + + @staticmethod + def assertFailIfInactiveSession(buf): + if "No debugging session active" not in buf: + raise AssertionError("No debugging session inactive warning") + + +def is_64b() -> bool: + return ARCH in CI_VALID_ARCHITECTURES_64B + + +def is_32b() -> bool: + return ARCH in CI_VALID_ARCHITECTURES_32B + + +def ansi_clean(s: str) -> str: + ansi_escape = re.compile(r"(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]") + return ansi_escape.sub("", s) + + +def _add_command(commands: CommandType) -> List[str]: + if isinstance(commands, str): + commands = [commands] + return [_str for cmd in commands for _str in ["-ex", cmd]] + + +def gdb_run_cmd(cmd: CommandType, before: CommandType = (), after: CommandType = (), + target: pathlib.Path = DEFAULT_TARGET, + strip_ansi: bool = STRIP_ANSI_DEFAULT) -> str: + """Execute a command inside GDB. `before` and `after` are lists of commands to be executed + before (resp. after) the command to test.""" + command = ["gdb", "-q", "-nx"] + if COVERAGE_DIR: + coverage_file = pathlib.Path(COVERAGE_DIR) / os.getenv("PYTEST_XDIST_WORKER", "gw0") + command += _add_command([ + "pi from coverage import Coverage", + f"pi cov = Coverage(data_file=\"{coverage_file}\"," + "auto_data=True, branch=True)", + "pi cov.start()", + ]) + command += _add_command([ + f"source {GEF_PATH}", + "gef config gef.debug True", + ]) + command += _add_command(before) + command += _add_command(cmd) + command += _add_command(after) + if COVERAGE_DIR: + command += _add_command(["pi cov.stop()", "pi cov.save()"]) + command += ["-ex", "quit", "--", str(target)] + + lines = subprocess.check_output(command, stderr=subprocess.STDOUT).strip().splitlines() + output = b"\n".join(lines) + result = None + + # The following is necessary because ANSI escape sequences might have been + # added in the middle of multibyte characters, e.g. \x1b[H\x1b[2J is added + # into the middle of \xe2\x94\x80 to become \xe2\x1b[H\x1b[2J\x94\x80 which + # causes a UnicodeDecodeError when trying to decode \xe2. Such broken + # multibyte characters would need to be removed, otherwise the test will + # result in an error. + while not result: + try: + result = output.decode("utf-8") + except UnicodeDecodeError as ude: + faulty_idx_start = int(ude.start) + faulty_idx_end = int(ude.end) + output = output[:faulty_idx_start] + output[faulty_idx_end:] + + if strip_ansi: + result = ansi_clean(result) + + return result + + +def gdb_run_silent_cmd(cmd: CommandType, before: CommandType = (), after: CommandType = (), + target: pathlib.Path = DEFAULT_TARGET, + strip_ansi: bool = STRIP_ANSI_DEFAULT) -> str: + """Disable the output and run entirely the `target` binary.""" + before = [*before, "gef config context.clear_screen False", + "gef config context.layout '-code -stack'", + "run"] + return gdb_run_cmd(cmd, before, after, target, strip_ansi) + + +def gdb_run_cmd_last_line(cmd: CommandType, before: CommandType = (), after: CommandType = (), + target: pathlib.Path = DEFAULT_TARGET, + strip_ansi: bool = STRIP_ANSI_DEFAULT) -> str: + """Execute a command in GDB, and return only the last line of its output.""" + return gdb_run_cmd(cmd, before, after, target, strip_ansi).splitlines()[-1] + + +def gdb_start_silent_cmd(cmd: CommandType, before: CommandType = (), after: CommandType = (), + target: pathlib.Path = DEFAULT_TARGET, + strip_ansi: bool = STRIP_ANSI_DEFAULT, + context: str = DEFAULT_CONTEXT) -> str: + """Execute a command in GDB by starting an execution context. This command + disables the `context` and sets a tbreak at the most convenient entry + point.""" + before = [*before, "gef config context.clear_screen False", + f"gef config context.layout '{context}'", + "entry-break"] + return gdb_run_cmd(cmd, before, after, target, strip_ansi) + + +def gdb_start_silent_cmd_last_line(cmd: CommandType, before: CommandType = (), + after: CommandType = (), + target: pathlib.Path = DEFAULT_TARGET, + strip_ansi=STRIP_ANSI_DEFAULT) -> str: + """Execute `gdb_start_silent_cmd()` and return only the last line of its output.""" + return gdb_start_silent_cmd(cmd, before, after, target, strip_ansi).splitlines()[-1] + + +def gdb_test_python_method(meth: str, before: str = "", after: str = "", + target: pathlib.Path = DEFAULT_TARGET, + strip_ansi: bool = STRIP_ANSI_DEFAULT) -> str: + brk = before + ";" if before else "" + cmd = f"pi {brk}print({meth});{after}" + return gdb_start_silent_cmd(cmd, target=target, strip_ansi=strip_ansi) + + +def gdb_time_python_method(meth: str, setup: str, + py_before: str = "", py_after: str = "", + before: CommandType = (), after: CommandType = (), + target: pathlib.Path = DEFAULT_TARGET, + strip_ansi: bool = STRIP_ANSI_DEFAULT, number: int = 1000) -> float: + brk = py_before + ";" if py_before else "" + cmd = f"""pi import timeit;{brk}print(timeit.timeit("{meth}", """\ + f"""setup="{setup}", number={number}));{py_after}""" + lines = gdb_run_cmd(cmd, before=before, after=after, + target=target, strip_ansi=strip_ansi).splitlines() + return float(lines[-1]) + + +def _target(name: str, extension: str = ".out") -> pathlib.Path: + target = TMPDIR / f"{name}{extension}" + if not target.exists(): + raise FileNotFoundError(f"Could not find file '{target}'") + return target + + +def start_gdbserver(exe: Union[str, pathlib.Path] = _target("default"), + port: int = 1234) -> subprocess.Popen: + """Start a gdbserver on the target binary. + + Args: + exe (str, optional): the binary to execute. Defaults to _target("default"). + port (int, optional): the port to make gdbserver listen on. Defaults to 1234. + + Returns: + subprocess.Popen: a Popen object for the gdbserver process. + """ + return subprocess.Popen(["gdbserver", f":{port}", exe], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + +def stop_gdbserver(gdbserver: subprocess.Popen) -> None: + """Stop the gdbserver and wait until it is terminated if it was + still running. Needed to make the used port available again. + + Args: + gdbserver (subprocess.Popen): the gdbserver process to stop. + """ + if gdbserver.poll() is None: + gdbserver.kill() + gdbserver.wait() + + +def findlines(substring: str, buffer: str) -> List[str]: + """Extract the lines from the buffer which contains the pattern + `substring` + + Args: + substring (str): the pattern to look for + buffer (str): the buffer to look into + + Returns: + List[str] + """ + return [ + line.strip() + for line in buffer.splitlines() + if substring in line.strip() + ] + + +def removeafter(substring: str, buffer: str, included: bool = False) -> str: + """Returns a copy of `buffer` truncated after `substring` is found. If + `included` is True, the result also includes the subtring. + + Args: + substring (str) + buffer (str) + buffer (bool) + + Returns: + str + """ + idx = buffer.find(substring) + if idx < 0: + return buffer + + if not included: + idx += len(substring) + + return buffer[:idx] + + +def removeuntil(substring: str, buffer: str, included: bool = False) -> str: + """Returns a copy of `buffer` truncated until `substring` is found. If + `included` is True, the result also includes the subtring. + + Args: + substring (str) + buffer (str) + buffer (bool) + + Returns: + str + """ + idx = buffer.find(substring) + if idx < 0: + return buffer + + if not included: + idx += len(substring) + + return buffer[idx:] + + + +def download_file(url: str) -> Optional[bytes]: + """Download a file from the internet. + + Args: + url (str) + + Returns: + Optional[bytes] + """ + try: + http = urlopen(url) + return http.read() if http.getcode() == 200 else None + except Exception: + return None From 0cc226981b5429b9ed0d7de3b2b936d939d8a0cd Mon Sep 17 00:00:00 2001 From: hugsy Date: Thu, 16 Jun 2022 18:21:47 -0700 Subject: [PATCH 12/39] Get rid of rtfd, use ghpages instead (#63) --- .github/ISSUE_TEMPLATE/bug_report.md | 64 +++++ .github/ISSUE_TEMPLATE/feature_request.md | 21 ++ .github/workflows/docs-link-check.yml | Bin 0 -> 1028 bytes .github/workflows/generate-docs.yml | 27 ++ .readthedocs.yml | 15 -- README.md | 78 ++---- docs/{ => commands}/assemble.md | 0 docs/{ => commands}/bincompare.md | 266 ++++++++++---------- docs/{ => commands}/bytearray.md | 84 +++---- docs/{ => commands}/capstone-disassemble.md | 0 docs/{ => commands}/error.md | 0 docs/{ => commands}/ftrace.md | 34 +-- docs/{ => commands}/glibc_function_args.md | 0 docs/{ => commands}/ida-rpyc.md | 0 docs/{ => commands}/peekpointers.md | 0 docs/{ => commands}/retdec.md | 0 docs/{ => commands}/ropper.md | 0 docs/{ => commands}/set-permission.md | 0 docs/{ => commands}/skel.md | 34 +-- docs/{ => commands}/unicorn-emulate.md | 0 docs/{ => commands}/windbg.md | 90 +++---- docs/index.md | 85 +++---- docs/install.md | 97 +++++++ docs/requirements.txt | 3 +- mkdocs.yml | 42 ++-- 25 files changed, 541 insertions(+), 399 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/workflows/docs-link-check.yml create mode 100644 .github/workflows/generate-docs.yml delete mode 100644 .readthedocs.yml rename docs/{ => commands}/assemble.md (100%) rename docs/{ => commands}/bincompare.md (98%) rename docs/{ => commands}/bytearray.md (98%) rename docs/{ => commands}/capstone-disassemble.md (100%) rename docs/{ => commands}/error.md (100%) rename docs/{ => commands}/ftrace.md (93%) rename docs/{ => commands}/glibc_function_args.md (100%) rename docs/{ => commands}/ida-rpyc.md (100%) rename docs/{ => commands}/peekpointers.md (100%) rename docs/{ => commands}/retdec.md (100%) rename docs/{ => commands}/ropper.md (100%) rename docs/{ => commands}/set-permission.md (100%) rename docs/{ => commands}/skel.md (94%) rename docs/{ => commands}/unicorn-emulate.md (100%) rename docs/{ => commands}/windbg.md (96%) create mode 100644 docs/install.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..70bb162 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,64 @@ +--- +name: Bug report +about: Help us improve GEF by filling up this report correctly +title: '' +labels: triage +assignees: '' + +--- + + +* [ ] Did you use the latest version of GEF from `dev` branch? +* [ ] Is your bug specific to GEF (not GDB)? - Try to reproduce it running `gdb -nx` +* [ ] Did you search through the [documentation](https://github.com/hugsy/gef/) first? +* [ ] Did you check [issues](https://github.com/hugsy/gef/issues) (including + the closed ones) - and the [PR](https://github.com/hugsy/gef/pulls)? + + +### Step 1: Describe your environment + +* Operating System / Distribution: +* Architecture: +* GEF version (including the Python library version) run `version` in GEF. + + +### Step 2: Describe your problem + +#### Steps to reproduce + +1. + +#### Minimalist test case + + + +```c +// compile with gcc -fPIE -pic -o my_issue.out my_issue.c +int main(){ return 0; } +``` + +#### Observed Results + +* What happened? This could be a description, log output, etc. + + +#### Expected results + +* What did you expect to happen? + +#### Traces + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..3c87173 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,21 @@ +--- +name: Feature request +about: Had an idea of a new useful feature for GEF, but can't implement it? Here's + your chance +title: '' +labels: enhancement, new feature +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/docs-link-check.yml b/.github/workflows/docs-link-check.yml new file mode 100644 index 0000000000000000000000000000000000000000..15ad50688e3cffdb3caf5f3023ed1189a88259aa GIT binary patch literal 1028 zcmZ`&O-sW-6r6K^MGk@>Y-`0+K@dMsQS?&1N|MG}P1D$b`HyT zxfavmmdo{81|535dLnBR`SiE2%fIE?9s7Q@Uf>EtT;d)loQWas zaE)`^S`T*c#gKk#o!6Xq|3$2@+M@o>`>i=W;f#g)F`38YmFI?$;e%>9JsI)cG?Sh; zS=~y?{gRwV?0BCYJ6yqOG2EYX9ouR(TM>52qyMVzaF3ziE?Gy+v{MvT^h9?jtUwj% LJtsEf*$#dIu2q*Y literal 0 HcmV?d00001 diff --git a/.github/workflows/generate-docs.yml b/.github/workflows/generate-docs.yml new file mode 100644 index 0000000..d45bc54 --- /dev/null +++ b/.github/workflows/generate-docs.yml @@ -0,0 +1,27 @@ +name: Generate GithubPages + +on: + push: + branches: + - dev + - main + - master + +jobs: + deploy: + runs-on: ubuntu-latest + if: github.event.repository.fork == false + steps: + - uses: actions/checkout@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + - name: Install pre-requisite + run: | + sudo apt install gdb-multiarch python3 python3-dev python3-wheel -y + version=$(gdb -q -nx -ex 'pi print(f"{sys.version_info.major}.{sys.version_info.minor}", end="")' -ex quit) + python${version} -m pip install --requirement docs/requirements.txt --upgrade + - name: Build and publish the docs + run: | + git config --global user.name "hugsy" + git config --global user.email "hugsy@users.noreply.github.com" + mkdocs gh-deploy --force diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index 1e050ae..0000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: 2 - -mkdocs: - configuration: mkdocs.yml - fail_on_warning: true - -formats: all - -build: - image: latest - -python: - version: 3.7 - install: - - requirements: docs/requirements.txt \ No newline at end of file diff --git a/README.md b/README.md index e575ad1..4e9af8b 100644 --- a/README.md +++ b/README.md @@ -2,86 +2,48 @@ logo

+

+ Discord + Docs + Try GEF +

-## Extra goodies for [`GEF`](https://github.com/hugsy/gef) +## Extra goodies for [`GEF`](https://github.com/hugsy/gef) -| **Documentation** | **Community** | **Try it** | -|--|--|--| -| [![Documentation Status](https://readthedocs.org/projects/gef-extras/badge/?version=latest&token=05e48c43fba3df26ad1ccf33353180e4b515681b727e2f3011013a915f953084)](https://gef-extras.readthedocs.io/en/latest/?badge=latest) | [![Discord](https://img.shields.io/badge/Discord-GDB--GEF-yellow)](https://discordapp.com/channels/705160148813086841/705160148813086843) | [![live](https://img.shields.io/badge/GEF-Live-brightgreen)](https://demo.gef.blah.cat) (`gef`/`gef-demo`) | This is an open repository of external scripts and structures to be used by [GDB Enhanced Features (GEF)](https://github.com/hugsy/gef). To use those scripts once `gef` is setup, simply clone this repository and update your GEF settings like this: -### How-to use ### -#### Run the install script #### -```bash -$ wget -q -O- https://github.com/hugsy/gef/raw/master/scripts/gef-extras.sh | sh -``` +## Get Started -#### Do it manually #### +Getting started with GEF-Extras couldn't be easier: make sure you have a working GDB and GEF already installed, then run the following command: -Start with cloning this repo: ```bash -$ git clone https://github.com/hugsy/gef-extras -``` - -Add the path to the external scripts to GEF's config: -``` -gef➤ gef config gef.extra_plugins_dir /path/to/gef-extras/scripts -``` - -And same for the structures (to be used by [`pcustom` command](https://gef.readthedocs.io/en/master/commands/pcustom/)): -``` -gef➤ gef config pcustom.struct_path /path/to/gef-extras/structs -``` - -And for the syscall tables: -``` -gef➤ gef config syscall-args.path /path/to/gef-extras/syscall-tables -``` - -And finally for the glibc function call args definition: -``` -gef➤ gef config context.libc_args True -gef➤ gef config context.libc_args_path /path/to/gef-extras/glibc-function-args +$ wget -q -O- https://github.com/hugsy/gef/raw/master/scripts/gef-extras.sh | sh ``` -Check out the [complete doc](docs/glibc_function_args.md) on libc argument support. - - -Now run and enjoy all the fun! -Note that it is possible to specify multiple directories, separating the paths with -a semi-colon: +## Documentation ## -``` -gef➤ gef config gef.extra_plugins_dir /path/to/dir1;/path/to/dir2 -``` - -And don't forget to save your settings. - -``` -gef➤ gef save -``` +Just like [GEF](https://hugsy.github.io/gef), GEF-Extras aims to have and keep to-date [a through documentation](https://hugsy.github.io/gef-extras/). Users are recommended to refer to it as it may help them in their attempts to use GEF. In particular, new users should navigate through it (see the [FAQ](https://hugsy.github.io/gef/faq/) for common installation problems), and the problem persists, try to reach out for help on the Discord channel or submit an issue. -### Contributions ### +## Current status ## -#### I can code! #### +| Documentation | License | Compatibility | CI Tests (`master`) | CI Tests (`dev`) | +| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [![Documentation](https://github.com/hugsy/gef-extras/actions/workflows/generate-docs.yml/badge.svg)](https://github.com/hugsy/gef-extras/actions/workflows/generate-docs.yml) | [![MIT](https://img.shields.io/packagist/l/doctrine/orm.svg?maxAge=2592000?style=plastic)](https://github.com/hugsy/gef-extras/blob/master/LICENSE) | [![Python 3](https://img.shields.io/badge/Python-3-green.svg)](https://github.com/hugsy/gef-extras/) | [![CI Test for GEF-Extras](https://github.com/hugsy/gef-extras/actions/workflows/run-tests.yml/badge.svg)](https://github.com/hugsy/gef-extras/actions/workflows/run-tests.yml) | [![CI Test for GEF-Extras](https://github.com/hugsy/gef-extras/actions/workflows/run-tests.yml/badge.svg?branch=dev)](https://github.com/hugsy/gef-extras/actions/workflows/run-tests.yml) | -Good for you! This repository is open to anyone, no filtering is done! Simply [drop a PR](https://github.com/hugsy/gef-scripts/pulls) with the command you want to share :smile: And useful scripts will eventually be integrated directly to GEF. -Check out [GEF API page](https://gef.readthedocs.io/en/latest/api/) to start writing powerful GDB commands using GEF! +## Contribute ## +To get involved, refer to the [Contribution documentation](https://hugsy.github.io/gef-extras/#contribution) and the [GEF guidelines](https://github.com/hugsy/gef/blob/dev/.github/CONTRIBUTING.md) to start. -#### I can't code 🤔 #### +## Sponsors ## -Well, that's ok! Just create an [Issue](https://github.com/hugsy/gef-extras/issues) -explaining what cool feature/idea/command you had in mind! Even better, write -the documentation (Markdown format) for your command. It'll make easier for -people who wants to integrate it! +Another way to contribute to keeping the project alive is by sponsoring it! Check out [the sponsoring documentation](https://hugsy.github.io/gef/#sponsors) for details so you can be part of the list of those [awesome sponsors](https://github.com/sponsors/hugsy). -### Enjoy and happy hacking ! ### +## Happy Hacking 🍻 ## \ No newline at end of file diff --git a/docs/assemble.md b/docs/commands/assemble.md similarity index 100% rename from docs/assemble.md rename to docs/commands/assemble.md diff --git a/docs/bincompare.md b/docs/commands/bincompare.md similarity index 98% rename from docs/bincompare.md rename to docs/commands/bincompare.md index 9f9deb2..24e9ac0 100644 --- a/docs/bincompare.md +++ b/docs/commands/bincompare.md @@ -1,134 +1,134 @@ -## Command bincompare - -The `bincompare` command will compare a provided binary file with process memory in order to find differences between the two. - -`bincompare` requires args: - -* `-f` (for `file`) - the full path of binary file to be compared. -* `-a` (for `address`) - the memory address to be compared with the file data. - -You can use the `bytearray` command to generate the binary file. - -Example without badchars: -``` -gef➤ bincompare -f bytearray.bin -a 0x56557008 -[+] Comparison result: - +-----------------------------------------------+ - 00 |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f| file - | | memory - 10 |10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f| file - | | memory - 20 |20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f| file - | | memory - 30 |30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f| file - | | memory - 40 |40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f| file - | | memory - 50 |50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f| file - | | memory - 60 |60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f| file - | | memory - 70 |70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f| file - | | memory - 80 |80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f| file - | | memory - 90 |90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f| file - | | memory - a0 |a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af| file - | | memory - b0 |b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf| file - | | memory - c0 |c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf| file - | | memory - d0 |d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df| file - | | memory - e0 |e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef| file - | | memory - f0 |f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff| file - | | memory - +-----------------------------------------------+ -[+] No badchars found! -``` - -Example with badchars and no truncateed buffer: -``` -gef➤ bincompare -f bytearray.bin -a 0x56557008 -[+] Comparison result: - +-----------------------------------------------+ - 00 |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f| file - | 10 | memory - 10 |10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f| file - | 10| memory - 20 |20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f| file - | | memory - 30 |30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f| file - | 2f| memory - 40 |40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f| file - | | memory - 50 |50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f| file - | | memory - 60 |60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f| file - | | memory - 70 |70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f| file - | | memory - 80 |80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f| file - | | memory - 90 |90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f| file - | | memory - a0 |a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af| file - | | memory - b0 |b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf| file - | | memory - c0 |c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf| file - | | memory - d0 |d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df| file - | | memory - e0 |e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef| file - | | memory - f0 |f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff| file - | | memory - +-----------------------------------------------+ -[+] Badchars found: 05, 1f, 3f -``` - -Example with badchars and truncated buffer: -``` -gef➤ bincompare -f bytearray.bin -a 0x56557008 -[+] Comparison result: - +-----------------------------------------------+ - 00 |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f| file - | 10 | memory - 10 |10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f| file - | 10| memory - 20 |20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f| file - | | memory - 30 |30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f| file - | 2f| memory - 40 |40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f| file - | 00 00 01 1b 03 3b 38 00 00 00 06 00 00 00| memory - 50 |50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f| file - |d4 ef ff ff 80 00 00 00 f4 ef ff ff a4 00 00 00| memory - 60 |60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f| file - |04 f0 ff ff 54 00 00 00 74 f1 ff ff b8 00 00 00| memory - 70 |70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f| file - |d4 f1 ff ff 04 01 00 00 d5 f1 ff ff 18 01 00 00| memory - 80 |80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f| file - |14 00 00 00 00 00 00 00 01 7a 52 00 01 7c 08 01| memory - 90 |90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f| file - |1b 0c 04 04 88 01 07 08 10 00 00 00 1c 00 00 00| memory - a0 |a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af| file - |a8 ef ff ff 36 00 00 00 00 00 00 00 14 00 00 00| memory - b0 |b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf| file - |00 00 00 00 01 7a 52 00 01 7c 08 01 1b 0c 04 04| memory - c0 |c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf| file - |88 01 00 00 20 00 00 00 1c 00 00 00 4c ef ff ff| memory - d0 |d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df| file - |20 00 00 00 00 0e 08 46 0e 0c 4a 0f 0b 74 04 78| memory - e0 |e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef| file - |00 3f 1a 3b 2a 32 24 22 10 00 00 00 40 00 00 00| memory - f0 |f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff| file - |48 ef ff ff 08 00 00 00 00 00 00 00 48 00 00 00| memory - +-----------------------------------------------+ -[+] Corruption after 66 bytes -[+] Badchars found: 05, 1f, 3f, 42, 43, 44, 45, 46, 47, 48, 49, 4a, 4b, 4c, 4d, 4e, 4f, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 5a, 5b, 5c, 5d, 5e, 5f, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 6a, 6b, 6c, 6d, 6e, 6f, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 7a, 7b, 7c, 7d, 7e, 7f, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 8a, 8b, 8c, 8d, 8e, 8f, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 9a, 9b, 9c, 9d, 9e, 9f, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ab, ac, ad, ae, af, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, ba, bb, bc, bd, be, bf, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, ca, cb, cc, cd, ce, cf, d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, da, db, dc, dd, de, df, e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, ea, eb, ec, ed, ee, ef, f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, fa, fb, fc, fd, fe, ff +## Command bincompare + +The `bincompare` command will compare a provided binary file with process memory in order to find differences between the two. + +`bincompare` requires args: + +* `-f` (for `file`) - the full path of binary file to be compared. +* `-a` (for `address`) - the memory address to be compared with the file data. + +You can use the `bytearray` command to generate the binary file. + +Example without badchars: +``` +gef➤ bincompare -f bytearray.bin -a 0x56557008 +[+] Comparison result: + +-----------------------------------------------+ + 00 |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f| file + | | memory + 10 |10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f| file + | | memory + 20 |20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f| file + | | memory + 30 |30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f| file + | | memory + 40 |40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f| file + | | memory + 50 |50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f| file + | | memory + 60 |60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f| file + | | memory + 70 |70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f| file + | | memory + 80 |80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f| file + | | memory + 90 |90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f| file + | | memory + a0 |a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af| file + | | memory + b0 |b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf| file + | | memory + c0 |c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf| file + | | memory + d0 |d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df| file + | | memory + e0 |e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef| file + | | memory + f0 |f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff| file + | | memory + +-----------------------------------------------+ +[+] No badchars found! +``` + +Example with badchars and no truncateed buffer: +``` +gef➤ bincompare -f bytearray.bin -a 0x56557008 +[+] Comparison result: + +-----------------------------------------------+ + 00 |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f| file + | 10 | memory + 10 |10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f| file + | 10| memory + 20 |20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f| file + | | memory + 30 |30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f| file + | 2f| memory + 40 |40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f| file + | | memory + 50 |50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f| file + | | memory + 60 |60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f| file + | | memory + 70 |70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f| file + | | memory + 80 |80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f| file + | | memory + 90 |90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f| file + | | memory + a0 |a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af| file + | | memory + b0 |b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf| file + | | memory + c0 |c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf| file + | | memory + d0 |d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df| file + | | memory + e0 |e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef| file + | | memory + f0 |f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff| file + | | memory + +-----------------------------------------------+ +[+] Badchars found: 05, 1f, 3f +``` + +Example with badchars and truncated buffer: +``` +gef➤ bincompare -f bytearray.bin -a 0x56557008 +[+] Comparison result: + +-----------------------------------------------+ + 00 |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f| file + | 10 | memory + 10 |10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f| file + | 10| memory + 20 |20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f| file + | | memory + 30 |30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f| file + | 2f| memory + 40 |40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f| file + | 00 00 01 1b 03 3b 38 00 00 00 06 00 00 00| memory + 50 |50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f| file + |d4 ef ff ff 80 00 00 00 f4 ef ff ff a4 00 00 00| memory + 60 |60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f| file + |04 f0 ff ff 54 00 00 00 74 f1 ff ff b8 00 00 00| memory + 70 |70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f| file + |d4 f1 ff ff 04 01 00 00 d5 f1 ff ff 18 01 00 00| memory + 80 |80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f| file + |14 00 00 00 00 00 00 00 01 7a 52 00 01 7c 08 01| memory + 90 |90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f| file + |1b 0c 04 04 88 01 07 08 10 00 00 00 1c 00 00 00| memory + a0 |a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af| file + |a8 ef ff ff 36 00 00 00 00 00 00 00 14 00 00 00| memory + b0 |b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf| file + |00 00 00 00 01 7a 52 00 01 7c 08 01 1b 0c 04 04| memory + c0 |c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf| file + |88 01 00 00 20 00 00 00 1c 00 00 00 4c ef ff ff| memory + d0 |d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df| file + |20 00 00 00 00 0e 08 46 0e 0c 4a 0f 0b 74 04 78| memory + e0 |e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef| file + |00 3f 1a 3b 2a 32 24 22 10 00 00 00 40 00 00 00| memory + f0 |f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff| file + |48 ef ff ff 08 00 00 00 00 00 00 00 48 00 00 00| memory + +-----------------------------------------------+ +[+] Corruption after 66 bytes +[+] Badchars found: 05, 1f, 3f, 42, 43, 44, 45, 46, 47, 48, 49, 4a, 4b, 4c, 4d, 4e, 4f, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 5a, 5b, 5c, 5d, 5e, 5f, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 6a, 6b, 6c, 6d, 6e, 6f, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 7a, 7b, 7c, 7d, 7e, 7f, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 8a, 8b, 8c, 8d, 8e, 8f, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 9a, 9b, 9c, 9d, 9e, 9f, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ab, ac, ad, ae, af, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, ba, bb, bc, bd, be, bf, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, ca, cb, cc, cd, ce, cf, d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, da, db, dc, dd, de, df, e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, ea, eb, ec, ed, ee, ef, f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, fa, fb, fc, fd, fe, ff ``` \ No newline at end of file diff --git a/docs/bytearray.md b/docs/commands/bytearray.md similarity index 98% rename from docs/bytearray.md rename to docs/commands/bytearray.md index 0e14810..85edfab 100644 --- a/docs/bytearray.md +++ b/docs/commands/bytearray.md @@ -1,43 +1,43 @@ -## Command bytearray - -The `bytearray` generate a binary with data between 0x01 and 0xff. In general the created file is used to compare (using the `bincompare` command) with a memory data in order to check badchars. - - -`bytearray` also accepts one option: - - * `-b` (for `badchar`) will exclude the bytes to generated byte array. - -Example without excluding bytes: -``` -gef➤ bytearray -[+] Generating table, excluding 0 bad chars... -[+] Dumping table to file -"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" -"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f" -"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f" -"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f" -"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f" -"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf" -"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf" -"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" -[+] Done, wrote 256 bytes to file bytearray.txt - -[+] Binary output saved in bytearray.bin -``` - -Example excluding bytes (0x00, 0x0a and 0x0d): -``` -gef➤ bytearray -b "\x00\x0a\x0d" -[+] Generating table, excluding 3 bad chars... -[+] Dumping table to file -"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22" -"\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42" -"\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62" -"\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82" -"\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2" -"\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2" -"\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2" -"\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" -[+] Done, wrote 253 bytes to file bytearray.txt -[+] Binary output saved in bytearray.bin +## Command bytearray + +The `bytearray` generate a binary with data between 0x01 and 0xff. In general the created file is used to compare (using the `bincompare` command) with a memory data in order to check badchars. + + +`bytearray` also accepts one option: + + * `-b` (for `badchar`) will exclude the bytes to generated byte array. + +Example without excluding bytes: +``` +gef➤ bytearray +[+] Generating table, excluding 0 bad chars... +[+] Dumping table to file +"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f" +"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f" +"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f" +"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f" +"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf" +"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf" +"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" +[+] Done, wrote 256 bytes to file bytearray.txt + +[+] Binary output saved in bytearray.bin +``` + +Example excluding bytes (0x00, 0x0a and 0x0d): +``` +gef➤ bytearray -b "\x00\x0a\x0d" +[+] Generating table, excluding 3 bad chars... +[+] Dumping table to file +"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22" +"\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42" +"\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62" +"\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82" +"\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2" +"\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2" +"\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2" +"\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" +[+] Done, wrote 253 bytes to file bytearray.txt +[+] Binary output saved in bytearray.bin ``` \ No newline at end of file diff --git a/docs/capstone-disassemble.md b/docs/commands/capstone-disassemble.md similarity index 100% rename from docs/capstone-disassemble.md rename to docs/commands/capstone-disassemble.md diff --git a/docs/error.md b/docs/commands/error.md similarity index 100% rename from docs/error.md rename to docs/commands/error.md diff --git a/docs/ftrace.md b/docs/commands/ftrace.md similarity index 93% rename from docs/ftrace.md rename to docs/commands/ftrace.md index 025bb92..d759414 100644 --- a/docs/ftrace.md +++ b/docs/commands/ftrace.md @@ -1,17 +1,17 @@ -## Command ftrace ## - - -A quick'n dirty function tracer scripts for GEF. - -To use: - -``` -gef➤ ftrace , , ... -``` - -Example: - -``` -gef➤ ftrace malloc,1 calloc,2 free,1 -``` - +## Command ftrace ## + + +A quick'n dirty function tracer scripts for GEF. + +To use: + +``` +gef➤ ftrace , , ... +``` + +Example: + +``` +gef➤ ftrace malloc,1 calloc,2 free,1 +``` + diff --git a/docs/glibc_function_args.md b/docs/commands/glibc_function_args.md similarity index 100% rename from docs/glibc_function_args.md rename to docs/commands/glibc_function_args.md diff --git a/docs/ida-rpyc.md b/docs/commands/ida-rpyc.md similarity index 100% rename from docs/ida-rpyc.md rename to docs/commands/ida-rpyc.md diff --git a/docs/peekpointers.md b/docs/commands/peekpointers.md similarity index 100% rename from docs/peekpointers.md rename to docs/commands/peekpointers.md diff --git a/docs/retdec.md b/docs/commands/retdec.md similarity index 100% rename from docs/retdec.md rename to docs/commands/retdec.md diff --git a/docs/ropper.md b/docs/commands/ropper.md similarity index 100% rename from docs/ropper.md rename to docs/commands/ropper.md diff --git a/docs/set-permission.md b/docs/commands/set-permission.md similarity index 100% rename from docs/set-permission.md rename to docs/commands/set-permission.md diff --git a/docs/skel.md b/docs/commands/skel.md similarity index 94% rename from docs/skel.md rename to docs/commands/skel.md index 2b44111..691e687 100644 --- a/docs/skel.md +++ b/docs/commands/skel.md @@ -1,17 +1,17 @@ -## Command skel ## - -`skel` prepares quickly a `pwntools` based exploit template for both local and remote targets, based on the currently debugged file. - -### How-To use ### - -#### With a local target - -``` -gef➤ skel local -``` - -#### With a remote target - -``` -gef➤ skel remote=TARGET:PORT -``` +## Command skel ## + +`skel` prepares quickly a `pwntools` based exploit template for both local and remote targets, based on the currently debugged file. + +### How-To use ### + +#### With a local target + +``` +gef➤ skel local +``` + +#### With a remote target + +``` +gef➤ skel remote=TARGET:PORT +``` diff --git a/docs/unicorn-emulate.md b/docs/commands/unicorn-emulate.md similarity index 100% rename from docs/unicorn-emulate.md rename to docs/commands/unicorn-emulate.md diff --git a/docs/windbg.md b/docs/commands/windbg.md similarity index 96% rename from docs/windbg.md rename to docs/commands/windbg.md index 43a73ed..22cdb65 100644 --- a/docs/windbg.md +++ b/docs/commands/windbg.md @@ -1,46 +1,46 @@ -## WinDbg compatibility layer ## - -This plugin is a set of commands, aliases and extensions to mimic some of the most common WinDbg commands into GEF. - -### Commands ### - - - `hh` - open GEF help in web browser - - `sxe` (set-exception-enable): break on loading libraries - - `tc` - trace to next call - - `pc` - run until call. - - `g` - go. - - `u` - disassemble. - - `x` - search symbol. - - `r` - register info - - -### Settings ### - - - `gef.use-windbg-prompt` - set to `True` to change the prompt like `0:000 ➤` - - -### Aliases ### - - - `da` : `display s` - - `dt` : `pcustom` - - `dq` : `hexdump qword` - - `dd` : `hexdump dword` - - `dw` : `hexdump word` - - `db` : `hexdump byte` - - `eq` : `patch qword` - - `ed` : `patch dword` - - `ew` : `patch word` - - `eb` : `patch byte` - - `ea` : `patch string` - - `dps` : `dereference` - - `bp` : `break` - - `bl` : "info`breakpoints` - - `bd` : "disable`breakpoints` - - `bc` : "delete`breakpoints` - - `be` : "enable`breakpoints` - - `tbp` : `tbreak` - - `s` : `grep` - - `pa` : `advance` - - `kp` : "info`stack` - - `ptc` : `finish` +## WinDbg compatibility layer ## + +This plugin is a set of commands, aliases and extensions to mimic some of the most common WinDbg commands into GEF. + +### Commands ### + + - `hh` - open GEF help in web browser + - `sxe` (set-exception-enable): break on loading libraries + - `tc` - trace to next call + - `pc` - run until call. + - `g` - go. + - `u` - disassemble. + - `x` - search symbol. + - `r` - register info + + +### Settings ### + + - `gef.use-windbg-prompt` - set to `True` to change the prompt like `0:000 ➤` + + +### Aliases ### + + - `da` : `display s` + - `dt` : `pcustom` + - `dq` : `hexdump qword` + - `dd` : `hexdump dword` + - `dw` : `hexdump word` + - `db` : `hexdump byte` + - `eq` : `patch qword` + - `ed` : `patch dword` + - `ew` : `patch word` + - `eb` : `patch byte` + - `ea` : `patch string` + - `dps` : `dereference` + - `bp` : `break` + - `bl` : "info`breakpoints` + - `bd` : "disable`breakpoints` + - `bc` : "delete`breakpoints` + - `be` : "enable`breakpoints` + - `tbp` : `tbreak` + - `s` : `grep` + - `pa` : `advance` + - `kp` : "info`stack` + - `ptc` : `finish` - `uf` : `disassemble` \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 7d30aaa..f0b2c55 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,86 +2,61 @@ logo

+

+ Discord + Docs + Try GEF +

-## Extra goodies for [`GEF`](https://github.com/hugsy/gef) - -| **Documentation** | **Community** | **Try it** | -|--|--|--| -| [![Documentation Status](https://readthedocs.org/projects/gef-extras/badge/?version=latest&token=05e48c43fba3df26ad1ccf33353180e4b515681b727e2f3011013a915f953084)](https://gef-extras.readthedocs.io/en/latest/?badge=latest) | [![Discord](https://img.shields.io/badge/Discord-GDB--GEF-yellow)](https://discordapp.com/channels/705160148813086841/705160148813086843) | [![live](https://img.shields.io/badge/GEF-Live-brightgreen)](https://demo.gef.blah.cat) (`gef`/`gef-demo`) | +## Extra goodies for [`GEF`](https://github.com/hugsy/gef) -This is an open repository of external scripts and structures to be used by [GDB Enhanced Features (GEF)](https://github.com/hugsy/gef). To use those scripts once `gef` is setup, simply clone this repository and update your GEF settings like this: +This is an open repository of external scripts and structures to be used by [GEF](https://github.com/hugsy/gef). As GEF aims to stay a one-file battery-included plugin for GDB, it doesn't allow by nature to be extended with external Python library. GEF-Extras remediates that providing some more extensibility to GEF through: + - more commands and functions + - publicly shared structures for the `pcustom` command + - more operating system support + - more file format support -### How-to use ### -#### Run the install script #### -```bash -$ wget -q -O- https://github.com/hugsy/gef/raw/master/scripts/gef-extras.sh | sh -``` +## Quick start -#### Do it manually #### +The biggest requirement for GEF-Extras to work is of course `GEF`. Please refer to GEF documentation to have it set up (spoiler alert: it's pretty easy 😉). Once GEF is up and running, you can install GEF-Extras. -Start with cloning this repo: -```bash -$ git clone https://github.com/hugsy/gef-extras -``` +### Automated installation -Add the path to the external scripts to GEF's config: -``` -gef➤ gef config gef.extra_plugins_dir /path/to/gef-extras/scripts -``` +Execute and run the installation script from GEF repository. -And same for the structures (to be used by [`pcustom` command](https://gef.readthedocs.io/en/master/commands/pcustom/)): -``` -gef➤ gef config pcustom.struct_path /path/to/gef-extras/structs -``` - -And for the syscall tables: -``` -gef➤ gef config syscall-args.path /path/to/gef-extras/syscall-tables -``` - -And finally for the glibc function call args definition: -``` -gef➤ gef config context.libc_args True -gef➤ gef config context.libc_args_path /path/to/gef-extras/glibc-function-args +```bash +$ wget -q -O- https://github.com/hugsy/gef/raw/master/scripts/gef-extras.sh | sh ``` -Check out the [complete doc](glibc_function_args.md) on libc argument support. +The script will download (via git) GEF-Extras, and set up your `~/.gef.rc` file so that you can start straight away. +Refer to the [installation page](install.md) for more installation methods. -Now run and enjoy all the fun! -Note that it is possible to specify multiple directories, separating the paths with -a semi-colon: +## Contribution -``` -gef➤ gef config gef.extra_plugins_dir /path/to/dir1;/path/to/dir2 -``` +### Through Pull-Requests -And don't forget to save your settings. +This repository is open for anyone to contribute! Simply [drop a PR](https://github.com/hugsy/gef-scripts/pulls) with the new command/function/feature. One thing to note, GEF and GEF-Extras have become what they are today thanks to an up-to-date documentation, so considering attaching a simple Markdown file to the `docs` folder explaining your update. **IF** your code is complex and/or requires further scrutiny, adding CI tests would also be asked during the review process of your PR. -``` -gef➤ gef save -``` +For a complete rundown of the commands/functions GEF allows to use out of the box, check out [GEF API page](https://gef.github.io/gef/api/) to start writing powerful GDB commands using GEF! +As a reward, your Github avatar will be immortalize in the list below of contributors to GEF-Extras -### Contributions ### +[ ![contributors-img](https://contrib.rocks/image?repo=hugsy/gef-extras) ](https://github.com/hugsy/gef-extras/graphs/contributors) -#### I can code! #### -Good for you! This repository is open to anyone, no filtering is done! Simply [drop a PR](https://github.com/hugsy/gef-scripts/pulls) with the command you want to share :smile: And useful scripts will eventually be integrated directly to GEF. +### Feature requests -Check out [GEF API page](https://gef.readthedocs.io/en/latest/api/) to start writing powerful GDB commands using GEF! +Well, that's ok! Just create an [Issue](https://github.com/hugsy/gef-extras/issues)explaining what cool feature/idea/command you had in mind! Even better, writethe documentation (Markdown format) for your command. It'll make easier forpeople who wants to integrate it! -#### I can't code 🤔 #### +### Sponsoring -Well, that's ok! Just create an [Issue](https://github.com/hugsy/gef-extras/issues) -explaining what cool feature/idea/command you had in mind! Even better, write -the documentation (Markdown format) for your command. It'll make easier for -people who wants to integrate it! +Sponsoring is another way to help projects to thrive. You can sponsor GEF and GEF-Extras by following [this link](https://github.com/sponsors/hugsy). -### Enjoy and happy hacking ! ### +## Happy hacking 🍻 diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 0000000..1aa66ec --- /dev/null +++ b/docs/install.md @@ -0,0 +1,97 @@ +# Installing GEF-Extras + +## Prerequisites + +### GDB + +Only [GDB 8 and higher](https://www.gnu.org/s/gdb) is required. It must be compiled with Python 3.6 or higher support. For most people, simply using your distribution package manager should be enough. + +GEF will then only work for Python 3. If you absolutely require GDB + Python 2, please use [GEF-Legacy](https://github.com/hugsy/gef-legacy) instead. Note that `gef-legacy` won't provide new features, and only functional bugs will be handled. + +You can verify it with the following command: + +```bash +$ gdb -nx -ex 'pi print(sys.version)' -ex quit +``` + +This should display your version of Python compiled with `gdb`. + +```bash +$ gdb -nx -ex 'pi print(sys.version)' -ex quit +3.6.9 (default, Nov 7 2019, 10:44:02) +[GCC 8.3.0] +``` + +### GEF + +For a quick installation of GEF, you can get started with the following commands: + +```bash +# via the install script +## using curl +$ bash -c "$(curl -fsSL https://gef.blah.cat/sh)" + +## using wget +$ bash -c "$(wget https://gef.blah.cat/sh -O -)" +``` + +For more advanced installation methods, refer [the installation chapter of the GEF documentation](https://hugsy.github.io/gef/install). + +### Python dependencies + +Because GEF-Extras allows external dependencies, you must make sure to have the adequate Python libraries installed before you can use the features. + +Thankfully this is easily done in Python, as such: + +``` +wget -O /tmp/requirements.txt https://raw.githubusercontent.com/hugsy/gef-extras/dev/requirements.txt +python -m pip install --user --upgrade /tmp/requirements.txt +``` + + +### Installation using Git + +Start with cloning this repo: +```bash +$ git clone https://github.com/hugsy/gef-extras +``` + +Add the path to the external scripts to GEF's config: +``` +gef➤ gef config gef.extra_plugins_dir /path/to/gef-extras/scripts +``` + +And same for the structures (to be used by [`pcustom` command](https://hugsy.github.io/gef/commands/pcustom/)): +``` +gef➤ gef config pcustom.struct_path /path/to/gef-extras/structs +``` + +And for the syscall tables: +``` +gef➤ gef config syscall-args.path /path/to/gef-extras/syscall-tables +``` + +And finally for the glibc function call args definition: +``` +gef➤ gef config context.libc_args True +gef➤ gef config context.libc_args_path /path/to/gef-extras/glibc-function-args +``` + +Check out the [complete documentation](commands/glibc_function_args.md) on libc argument support. + + +Now run and enjoy all the fun! + + +Note that it is possible to specify multiple directories, separating the paths with +a semi-colon: + +``` +gef➤ gef config gef.extra_plugins_dir /path/to/dir1;/path/to/dir2 +``` + +And don't forget to save your settings. + +``` +gef➤ gef save +``` diff --git a/docs/requirements.txt b/docs/requirements.txt index ad4f889..c505a73 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1,2 @@ -mkdocs>=1.2.3 +mkdocs-material +lazydocs \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index ae5aa52..3e4c6f8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,18 +1,28 @@ -site_name: GEF Extras - Extra goodies for GEF by and for the community -theme: readthedocs +site_name: GEF - GDB Enhanced Features documentation +theme: + name: material + font: + text: Roboto + code: Roboto Mono + features: + - navigation.instant + - navigation.tabs + nav: - Home: index.md -- assemble: assemble.md -- bincompare: bincompare.md -- bytearray: bytearray.md -- capstone-disassemble: capstone-disassemble.md -- error: error.md -- ftrace: ftrace.md -- ida-rpyc: ida_rpyc.md -- peekpointers: peekpointers.md -- retdec: retdec.md -- ropper: ropper.md -- set-permission: set-permission.md -- exploit-template: skel.md -- unicorn-emulate: unicorn-emulate.md -- windbg: windbg.md +- Installation: install.md +- Commands: + - assemble: commands/assemble.md + - bincompare: commands/bincompare.md + - bytearray: commands/bytearray.md + - capstone: commands/capstone-disassemble.md + - emulate: commands/unicorn-emulate.md + - error: commands/error.md + - exploit-template: commands/skel.md + - ftrace: commands/ftrace.md + - ida-rpyc: commands/ida-rpyc.md + - peekpointers: commands/peekpointers.md + - retdec: commands/retdec.md + - ropper: commands/ropper.md + - set-permission: commands/set-permission.md + - windbg: commands/windbg.md From c3e5c9c8856c793a4297a5ccf0bc84a8dcf8e32c Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 19 Jun 2022 14:01:06 -0700 Subject: [PATCH 13/39] Apply the changes brought by hugsy/gef#839 (#62) * `register_external_commands` -> `register` + added gef interface file * Updated pyi for gef + added a pyi gdb + started fixing a few scripts * Updated pyi for gef + added a pyi gdb + started fixing a few scripts * fixed windbg * fixed bincompare * fixed `xref-telescope` * fixed `ftrace` * fixed `peekpointers` * fixed `remote` * fixed `error` * minor fixes in `ida-interact` and `xref-telescope` * fixed `assemble` * fixed `capstone-disassemble` * fixed `emulate` * fixed `mprotect` --- scripts/TEMPLATE | 18 +- scripts/__init__.py | 0 scripts/__init__.pyi | 1709 +++++++++++++++++ scripts/assemble.py | 257 +++ scripts/bincompare.py | 61 +- scripts/bytearray.py | 2 +- scripts/capstone.py | 171 ++ .../unicorn.py => emulate/__init__.py} | 620 +++--- scripts/error.py | 35 - scripts/error/__init__.py | 41 + scripts/ftrace.py | 71 +- scripts/gdb/__init__.pyi | 744 +++++++ scripts/ida_interact.py | 110 +- scripts/peekpointers.py | 40 +- scripts/remote.py | 28 +- scripts/retdec.py | 2 +- scripts/skel.py | 16 +- scripts/stack.py | 2 +- scripts/trinity/__init__.py | 46 - scripts/trinity/capstone.py | 145 -- scripts/trinity/keystone.py | 198 -- scripts/trinity/mprotect.py | 77 - scripts/v8-dereference.py | 114 +- scripts/visualize_heap.py | 2 +- scripts/windbg.py | 134 +- scripts/xref-telescope.py | 33 +- 26 files changed, 3646 insertions(+), 1030 deletions(-) create mode 100644 scripts/__init__.py create mode 100644 scripts/__init__.pyi create mode 100644 scripts/assemble.py create mode 100644 scripts/capstone.py rename scripts/{trinity/unicorn.py => emulate/__init__.py} (70%) delete mode 100644 scripts/error.py create mode 100644 scripts/error/__init__.py create mode 100644 scripts/gdb/__init__.pyi delete mode 100644 scripts/trinity/__init__.py delete mode 100644 scripts/trinity/capstone.py delete mode 100644 scripts/trinity/keystone.py delete mode 100644 scripts/trinity/mprotect.py diff --git a/scripts/TEMPLATE b/scripts/TEMPLATE index a7eb778..b2260d6 100644 --- a/scripts/TEMPLATE +++ b/scripts/TEMPLATE @@ -3,17 +3,21 @@ Describe thoroughly what your command does. In addition, complete the documentat in /docs/ and adding the reference in /mkdocs.yml """ -__AUTHOR__ = "your_name" -__VERSION__ = 0.1 -__LICENSE__ = "MIT" +__AUTHOR__ = "your_name" +__VERSION__ = 0.1 +__LICENSE__ = "MIT" +from typing import TYPE_CHECKING, List +if TYPE_CHECKING: + from . import * # this will allow linting for GEF and GDB + + +@register class MyCommand(GenericCommand): """Template of a new command.""" _cmdline_ = "my-command" - _syntax_ = "{:s}".format(_cmdline_) + _syntax_ = "{:s}".format(_cmdline_) - def do_invoke(self, argv): + def do_invoke(self, argv: List[str]): return - -register(MyCommand) diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/__init__.pyi b/scripts/__init__.pyi new file mode 100644 index 0000000..36c1069 --- /dev/null +++ b/scripts/__init__.pyi @@ -0,0 +1,1709 @@ +from io import StringIO, TextIOWrapper +from types import ModuleType +from typing import (IO, TYPE_CHECKING, Any, ByteString, Callable, Dict, + Generator, Iterator, List, Literal, Optional, Pattern, + Sequence, Tuple, Type, Union) + +from typing_extensions import Self + +if TYPE_CHECKING: + import abc + import enum + import pathlib + + from . import gdb + +GDB_MIN_VERSION: Tuple[int, int] = ... +GDB_VERSION: Tuple[int, int] = ... +PYTHON_MIN_VERSION: Tuple[int, int] = ... +PYTHON_VERSION: Tuple[int, int] = ... + +DEFAULT_PAGE_ALIGN_SHIFT: int = ... +DEFAULT_PAGE_SIZE: int = ... + +GEF_RC: pathlib.Path = ... +GEF_TEMP_DIR: str = ... +GEF_MAX_STRING_LENGTH: int = ... + +LIBC_HEAP_MAIN_ARENA_DEFAULT_NAME: str = ... +ANSI_SPLIT_RE: Literal = ... + +LEFT_ARROW: str = ... +RIGHT_ARROW: str = ... +DOWN_ARROW: str = ... +HORIZONTAL_LINE: str = ... +VERTICAL_LINE: str = ... +CROSS: str = ... +TICK: str = ... +BP_GLYPH: str = ... +GEF_PROMPT: str = ... +GEF_PROMPT_ON: str = ... +GEF_PROMPT_OFF: str = ... + +PATTERN_LIBC_VERSION: Pattern[bytes] = ... + +GEF_DEFAULT_BRANCH: str = ... +GEF_EXTRAS_DEFAULT_BRANCH: str = ... + + +def http_get(url: str) -> Optional[bytes]: ... +def update_gef(argv: List[str]) -> int: ... +def reset_all_caches() -> None: ... +def reset() -> None: ... +def highlight_text(text: str) -> str: ... +def gef_print(*args: str, end="\\\\\\\\n", sep=" ", **kwargs: Any) -> None: ... +def bufferize(f: Callable) -> Callable: ... +def p8(x: int, s: bool = False) -> bytes: ... +def p16(x: int, s: bool = False) -> bytes: ... +def p32(x: int, s: bool = False) -> bytes: ... +def p64(x: int, s: bool = False) -> bytes: ... +def u8(x: bytes, s: bool = False) -> int: ... +def u16(x: bytes, s: bool = False) -> int: ... +def u32(x: bytes, s: bool = False) -> int: ... +def u64(x: bytes, s: bool = False) -> int: ... +def is_ascii_string(address: int) -> bool: ... +def is_alive() -> bool: ... +def only_if_gdb_running(f: Callable) -> Callable: ... +def only_if_gdb_target_local(f: Callable) -> Callable: ... +def deprecated(solution: str = "") -> Callable: ... +def experimental_feature(f: Callable) -> Callable: ... +def only_if_events_supported(event_type: str) -> Callable: ... + + +class classproperty(property): + def __get__(self, cls, owner): ... + + +def FakeExit(*args: Any, **kwargs: Any) -> None: ... + + +def parse_arguments(required_arguments: Dict[Union[str, Tuple[str, str]], Any], + optional_arguments: Dict[Union[str, Tuple[str, str]], Any]) -> Callable: ... + + +class Color: + @staticmethod + def redify(msg: str) -> str: ... + @staticmethod + def greenify(msg: str) -> str: ... + @staticmethod + def blueify(msg: str) -> str: ... + @staticmethod + def yellowify(msg: str) -> str: ... + @staticmethod + def grayify(msg: str) -> str: ... + @staticmethod + def light_grayify(msg: str) -> str: ... + @staticmethod + def pinkify(msg: str) -> str: ... + @staticmethod + def cyanify(msg: str) -> str: ... + @staticmethod + def boldify(msg: str) -> str: ... + @staticmethod + def underlinify(msg: str) -> str: ... + @staticmethod + def highlightify(msg: str) -> str: ... + @staticmethod + def blinkify(msg: str) -> str: ... + @staticmethod + def colorify(text: str, attrs: str) -> str: ... + + +class Address: + value: int = ... + section: Section = ... + info: Zone = ... + def __init__(self, **kwargs: Any) -> None: ... + def __str__(self) -> str: ... + def __int__(self) -> int: ... + def is_in_text_segment(self) -> bool: ... + def is_in_stack_segment(self) -> bool: ... + def is_in_heap_segment(self) -> bool: ... + def dereference(self) -> Optional[int]: ... + def valid(self) -> bool: ... + + +class Permission(enum.Flag): + NONE: int + EXECUTE: int + WRITE: int + READ: int + ALL: int + def __str__(self) -> str: ... + @staticmethod + def from_info_sections(*args: str) -> "Permission": ... + @staticmethod + def from_process_maps(perm_str: str) -> "Permission": ... + + +class Section: + page_start: int + page_end: int + offset: int + permission: Permission + inode: int + path: str + def __init__(self, **kwargs: Any) -> None: ... + def is_readable(self) -> bool: ... + def is_writable(self) -> bool: ... + def is_executable(self) -> bool: ... + @property + def size(self) -> int: ... + @property + def realpath(self) -> str: ... + def __str__(self) -> str: ... + + +class Endianness(enum.Enum): + LITTLE_ENDIAN: int + BIG_ENDIAN: int + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __int__(self) -> int: ... + + +class Elf: + ELF_MAGIC: int + + class Class(enum.Enum): + ELF_32_BITS = 0x01 + ELF_64_BITS = 0x02 + + class Abi(enum.Enum): + X86_64: int + X86_32: int + ARM: int + MIPS: int + POWERPC: int + POWERPC64: int + SPARC: int + SPARC64: int + AARCH64: int + RISCV: int + IA64: int + M68K: int + + class Type(enum.Enum): + ET_RELOC: int + ET_EXEC: int + ET_DYN: int + ET_CORE: int + + class OsAbi(enum.Enum): + SYSTEMV: int + HPUX: int + NETBSD: int + LINUX: int + SOLARIS: int + AIX: int + IRIX: int + FREEBSD: int + OPENBSD: int + + e_magic: int + e_class: Elf.Class + e_endianness: Endianness + e_eiversion: int + e_osabi: Elf.OsAbi + e_abiversion: int + e_pad: bytes + e_type: Elf.Type + e_machine: Abi + e_version: int + e_entry: int + e_phoff: int + e_shoff: int + e_flags: int + e_ehsize: int + e_phentsize: int + e_phnum: int + e_shentsize: int + e_shnum: int + e_shstrndx: int + + def __init__(self, path: str = "", minimalist: bool = False) -> None: ... + def read(self, size: int) -> bytes: ... + def read_and_unpack(self, fmt: str) -> Tuple[Any, ...]: ... + def seek(self, off: int) -> None: ... + def __str__(self) -> str: ... + def entry_point(self) -> int: ... + @classmethod + def X86_64(cls) -> int: ... + @classmethod + def X86_32(cls) -> int: ... + @classmethod + def ARM(cls) -> int: ... + @classmethod + def MIPS(cls) -> int: ... + @classmethod + def POWERPC(cls) -> int: ... + @classmethod + def POWERPC64(cls) -> int: ... + @classmethod + def SPARC(cls) -> int: ... + @classmethod + def SPARC64(cls) -> int: ... + @classmethod + def AARCH64(cls) -> int: ... + @classmethod + def RISCV(cls) -> int: ... + + +class Phdr: + class Type(enum.IntEnum): + @classmethod + def _missing_(cls, _: int) -> Type: ... + + class Flags(enum.IntFlag): + ... + + def __init__(self, elf: Elf, off: int) -> None: ... + def __str__(self) -> str: ... + + +class Shdr: + class Type(enum.IntEnum): + @classmethod + def _missing_(cls, _: int) -> Type: ... + + class Flags(enum.IntFlag): + @classmethod + def _missing_(cls, _: int): ... + + def __init__(self, elf: Optional[Elf], off: int) -> None: ... + def __str__(self) -> str: ... + + +class Instruction: + address: int + location: int + mnemonic: str + operands: List[str] + opcodes: bytes + def __init__(self, address: int, location: str, mnemo: str, + operands: List[str], opcodes: bytes) -> None: ... + + def __format__(self, format_spec: str) -> str: ... + def __str__(self) -> str: ... + def is_valid(self) -> bool: ... + def size(self) -> int: ... + + +def search_for_main_arena() -> int: ... + + +class MallocStateStruct: + def __init__(self, addr: str) -> None: ... + def addr(self) -> int: ... + def fastbins_addr(self) -> int: ... + def top_addr(self) -> int: ... + def last_remainder_addr(self) -> int: ... + def bins_addr(self) -> int: ... + def next_addr(self) -> int: ... + def next_free_addr(self) -> int: ... + def system_mem_addr(self) -> int: ... + def struct_size(self) -> int: ... + def fastbinsY(self) -> gdb.Value: ... + def top(self) -> "gdb.Value": ... + def last_remainder(self) -> "gdb.Value": ... + def bins(self) -> "gdb.Value": ... + def next(self) -> "gdb.Value": ... + def next_free(self) -> "gdb.Value": ... + def system_mem(self) -> "gdb.Value": ... + def get_size_t(self, addr: int) -> "gdb.Value": ... + def get_size_t_pointer(self, addr: int) -> "gdb.Value": ... + def get_size_t_array(self, addr: int, length: int) -> "gdb.Value": ... + def __getitem__(self, item: str) -> Any: ... + + +class GlibcHeapInfo: + def __init__(self, addr: Union[int, str]) -> None: ... + def addr(self) -> int: ... + def ar_ptr_addr(self) -> int: ... + def prev_addr(self) -> int: ... + def size_addr(self) -> int: ... + def mprotect_size_addr(self) -> int: ... + def ar_ptr(self) -> "gdb.Value": ... + def prev(self) -> "gdb.Value": ... + def size(self) -> "gdb.Value": ... + def mprotect_size(self) -> "gdb.Value": ... + def _get_size_t_pointer(self, addr: int) -> "gdb.Value": ... + def _get_size_t(self, addr: int) -> "gdb.Value": ... + + +class GlibcArena: + def __init__(self, addr: str) -> None: ... + def __getitem__(self, item: Any) -> Any: ... + def __getattr__(self, item: Any) -> Any: ... + def __int__(self) -> int: ... + def __iter__(self) -> Generator[Self, None, None]: ... + def __eq__(self, other: "GlibcArena") -> bool: ... + def fastbin(self, i: int) -> Optional["GlibcChunk"]: ... + def bin(self, i: int) -> Tuple[int, int]: ... + def is_main_arena(self) -> bool: ... + def heap_addr(self, allow_unaligned: bool = False) -> Optional[int]: ... + def get_heap_info_list(self) -> Optional[List[GlibcHeapInfo]]: ... + @staticmethod + def get_heap_for_ptr(ptr: int) -> int: ... + def __str__(self) -> str: ... + def addr(self) -> int: ... + + +class GlibcChunk: + def __init__(self, addr: int, from_base: bool = False, + allow_unaligned: bool = True) -> None: ... + + def get_chunk_size(self) -> int: ... + def size(self) -> int: ... + def get_usable_size(self) -> int: ... + def usable_size(self) -> int: ... + def get_prev_chunk_size(self) -> int: ... + def __iter__(self) -> Generator["GlibcChunk", None, None]: ... + def get_next_chunk( + self, allow_unaligned: bool = False) -> "GlibcChunk": ... + + def get_next_chunk_addr(self) -> int: ... + def get_fwd_ptr(self, sll: bool) -> int: ... + def fwd(self) -> int: ... + def get_bkw_ptr(self) -> int: ... + def bck(self) -> int: ... + def has_p_bit(self) -> bool: ... + def has_m_bit(self) -> bool: ... + def has_n_bit(self) -> bool: ... + def is_used(self) -> bool: ... + def str_chunk_size_flag(self) -> str: ... + def _str_sizes(self) -> str: ... + def _str_pointers(self) -> str: ... + def str_as_alloced(self) -> str: ... + def str_as_freed(self) -> str: ... + def flags_as_string(self) -> str: ... + def __str__(self) -> str: ... + def psprint(self) -> str: ... + + +def get_libc_version() -> Tuple[int, ...]: ... +def titlify(text: str, color: Optional[str] = None, + msg_color: Optional[str] = None) -> str: ... + + +def err(msg: str) -> None: ... +def warn(msg: str) -> None: ... +def ok(msg: str) -> None: ... +def info(msg: str) -> None: ... +def push_context_message(level: str, message: str) -> None: ... + + +def show_last_exception() -> None: + def _show_code_line(fname: str, idx: int) -> str: ... + + +def gef_pystring(x: bytes) -> str: ... +def gef_pybytes(x: str) -> bytes: ... +def which(program: str) -> pathlib.Path: ... +def style_byte(b: int, color: bool = True) -> str: ... +def hexdump(source: ByteString, length: int = 0x10, separator: str = ".", + show_raw: bool = False, show_symbol: bool = True, base: int = 0x00) -> str: ... + + +def is_debug() -> bool: ... +def hide_context() -> bool: ... +def unhide_context() -> bool: ... + + +class RedirectOutputContext(): + def __init__(self, to: str = "/dev/null") -> None: ... + def __enter__(self) -> None: ... + def __exit__(self, *exc: Any) -> None: ... + + +def enable_redirect_output(to_file: str = "/dev/null") -> None: ... +def disable_redirect_output() -> None: ... +def gef_makedirs(path: str, mode: int = 0o755) -> pathlib.Path: ... + + +def gdb_lookup_symbol(sym: str) -> Optional[Tuple[Optional[str], + Optional[Tuple[gdb.Symtab_and_line, ...]]]]: ... + + +def gdb_get_location_from_symbol( + address: int) -> Optional[Tuple[str, int]]: ... + + +def gdb_disassemble( + start_pc: int, **kwargs: int) -> Generator[Instruction, None, None]: ... +def gdb_get_nth_previous_instruction_address( + addr: int, n: int) -> Optional[int]: ... + + +def gdb_get_nth_next_instruction_address(addr: int, n: int) -> int: ... +def gef_instruction_n(addr: int, n: int) -> Instruction: ... +def gef_get_instruction_at(addr: int) -> Instruction: ... +def gef_current_instruction(addr: int) -> Instruction: ... +def gef_next_instruction(addr: int) -> Instruction: ... + + +def gef_disassemble(addr: int, nb_insn: int, + nb_prev: int = 0) -> Generator[Instruction, None, None]: ... + + +def gef_execute_external( + command: Sequence[str], as_list: bool = False, **kwargs: Any) -> Union[str, List[str]]: ... + + +def gef_execute_gdb_script(commands: str) -> None: ... + + +def checksec(filename: str) -> Dict[str, bool]: + def __check_security_property( + opt: str, filename: str, pattern: str) -> bool: ... + + +def get_arch() -> str: ... +def get_entry_point() -> Optional[int]: ... +def is_pie(fpath: str) -> bool: ... +def is_big_endian() -> bool: ... +def is_little_endian() -> bool: ... +def flags_to_human(reg_value: int, value_table: Dict[int, str]) -> str: ... +def get_section_base_address(name: str) -> Optional[int]: ... +def get_zone_base_address(name: str) -> Optional[int]: ... + + +def register_architecture( + cls: Type["Architecture"]) -> Type["Architecture"]: ... + + +class ArchitectureBase: + def __init_subclass__(cls: Type["ArchitectureBase"], **kwargs): ... + + +class Architecture(ArchitectureBase): + arch: str + mode: str + all_registers: Union[Tuple[()], Tuple[str, ...]] + nop_insn: bytes + return_register: str + flag_register: Optional[str] + instruction_length: Optional[int] + flags_table: Dict[int, str] + syscall_register: Optional[str] + syscall_instructions: Union[Tuple[()], Tuple[str, ...]] + function_parameters: Union[Tuple[()], Tuple[str, ...]] + + def __init_subclass__(cls, **kwargs): ... + @staticmethod + def supports_gdb_arch(gdb_arch: str) -> Optional[bool]: ... + def flag_register_to_human(self, val: Optional[int] = None) -> str: ... + def is_call(self, insn: Instruction) -> bool: ... + def is_ret(self, insn: Instruction) -> bool: ... + def is_conditional_branch(self, insn: Instruction) -> bool: ... + def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: ... + def get_ra(self, insn: Instruction, + frame: "gdb.Frame") -> Optional[int]: ... + + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: ... + def reset_caches(self) -> None: ... + def __get_register(self, regname: str) -> int: ... + def __get_register_for_selected_frame( + self, regname: str, hash_key: int) -> int: ... + + def register(self, name: str) -> int: ... + @property + def registers(self) -> Generator[str, None, None]: ... + @property + def pc(self) -> int: ... + @property + def sp(self) -> int: ... + @property + def fp(self) -> int: ... + @property + def ptrsize(self) -> int: ... + @property + def endianness(self) -> Endianness: ... + def get_ith_parameter( + self, i: int, in_func: bool = True) -> Tuple[str, Optional[int]]: ... + + +class GenericArchitecture(Architecture): + ... + + +class RISCV(Architecture): + def instruction_length(self) -> int: ... + def is_call(self, insn: Instruction) -> bool: ... + def is_ret(self, insn: Instruction) -> bool: ... + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: ... + def ptrsize(self) -> int: ... + def is_conditional_branch(self, insn: Instruction) -> bool: ... + + def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + def long_to_twos_complement(v: int) -> int: ... + + def get_ra(self, insn: Instruction, + frame: "gdb.Frame") -> Optional[int]: ... + + +class ARM(Architecture): + def is_thumb(self) -> bool: ... + def pc(self) -> Optional[int]: ... + def mode(self) -> str: ... + def instruction_length(self) -> Optional[int]: ... + def ptrsize(self) -> int: ... + def is_call(self, insn: Instruction) -> bool: ... + def is_ret(self, insn: Instruction) -> bool: ... + def flag_register_to_human(self, val: Optional[int] = None) -> str: ... + def is_conditional_branch(self, insn: Instruction) -> bool: ... + def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: ... + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int: ... + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: ... + + +class AARCH64(ARM): + def is_call(self, insn: Instruction) -> bool: ... + def flag_register_to_human(self, val: Optional[int] = None) -> str: ... + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: ... + def is_conditional_branch(self, insn: Instruction) -> bool: ... + def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: ... + + +class X86(Architecture): + def flag_register_to_human(self, val: Optional[int] = None) -> str: ... + def is_call(self, insn: Instruction) -> bool: ... + def is_ret(self, insn: Instruction) -> bool: ... + def is_conditional_branch(self, insn: Instruction) -> bool: ... + def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: ... + def get_ra(self, insn: Instruction, + frame: "gdb.Frame") -> Optional[int]: ... + + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: ... + def get_ith_parameter( + self, i: int, in_func: bool = True) -> Tuple[str, Optional[int]]: ... + + +class X86_64(X86): + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: ... + + +class PowerPC(Architecture): + def flag_register_to_human(self, val: Optional[int] = None) -> str: ... + def is_call(self, insn: Instruction) -> bool: ... + def is_ret(self, insn: Instruction) -> bool: ... + def is_conditional_branch(self, insn: Instruction) -> bool: ... + def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: ... + def get_ra(self, insn: Instruction, + frame: "gdb.Frame") -> Optional[int]: ... + + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: ... + + +class PowerPC64(PowerPC): + ... + + +class SPARC(Architecture): + def flag_register_to_human(self, val: Optional[int] = None) -> str: ... + def is_call(self, insn: Instruction) -> bool: ... + def is_ret(self, insn: Instruction) -> bool: ... + def is_conditional_branch(self, insn: Instruction) -> bool: ... + def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: ... + def get_ra(self, insn: Instruction, + frame: "gdb.Frame") -> Optional[int]: ... + + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: ... + + +class SPARC64(SPARC): + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: ... + + +class MIPS(Architecture): + def flag_register_to_human(self, val: Optional[int] = None) -> str: ... + def is_call(self, insn: Instruction) -> bool: ... + def is_ret(self, insn: Instruction) -> bool: ... + def is_conditional_branch(self, insn: Instruction) -> bool: ... + def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: ... + def get_ra(self, insn: Instruction, + frame: "gdb.Frame") -> Optional[int]: ... + + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: ... + + +class MIPS64(MIPS): + @staticmethod + def supports_gdb_arch(gdb_arch: str) -> Optional[bool]: ... + + +class Zone: + ... + + +def copy_to_clipboard(data: bytes) -> None: ... +def use_stdtype() -> str: ... +def use_default_type() -> str: ... +def use_golang_type() -> str: ... +def use_rust_type() -> str: ... +def to_unsigned_long(v: gdb.Value) -> int: ... +def get_path_from_info_proc() -> Optional[str]: ... +def get_os() -> str: ... +def is_qemu() -> bool: ... +def is_qemu_usermode() -> bool: ... +def is_qemu_system() -> bool: ... +def get_filepath() -> Optional[str]: ... +def download_file(remote_path: str, use_cache: bool = False, + local_name: Optional[str] = None) -> Optional[str]: ... + + +def get_function_length(sym: str) -> int: ... +def get_info_files() -> List[Zone]: ... +def process_lookup_address(address: int) -> Optional[Section]: ... +def process_lookup_path( + name: str, perm: Permission = Permission.ALL) -> Optional[Section]: ... # type: ignore + + +def file_lookup_name_path(name: str, path: str) -> Optional[Zone]: ... +def file_lookup_address(address: int) -> Optional[Zone]: ... +def lookup_address(address: int) -> Address: ... +def xor(data: ByteString, key: str) -> bytearray: ... +def is_hex(pattern: str) -> bool: ... +def continue_handler(_: gdb.Event) -> None: ... +def hook_stop_handler(_: gdb.Event) -> None: ... +def new_objfile_handler(_: gdb.Event) -> None: ... +def exit_handler(_: gdb.Event) -> None: ... +def memchanged_handler(_: gdb.Event) -> None: ... +def regchanged_handler(_: gdb.Event) -> None: ... +def load_libc_args() -> bool: ... +def get_terminal_size() -> Tuple[int, int]: ... + + +def get_elf_headers(filename: Optional[str] = None) -> Elf: ... +def is_64bit() -> bool: ... +def is_32bit() -> bool: ... +def is_x86_64() -> bool: ... +def is_x86_32(): ... +def is_x86() -> bool: ... +def is_arch(arch: Elf.Abi) -> bool: ... +def reset_architecture(arch: Optional[str] = None) -> None: ... +def cached_lookup_type(_type: str) -> Optional[gdb.Type]: ... +def get_memory_alignment(in_bits: bool = False) -> int: ... +def clear_screen(tty: str = "") -> None: ... +def format_address(addr: int) -> str: ... +def format_address_spaces(addr: int, left: bool = True) -> str: ... +def align_address(address: int) -> int: ... +def align_address_to_size(address: int, align: int) -> int: ... +def align_address_to_page(address: int) -> int: ... +def parse_address(address: str) -> int: ... +def is_in_x86_kernel(address: int) -> bool: ... +def is_remote_debug() -> bool: ... + + +def de_bruijn(alphabet: bytes, n: int) -> Generator[str, None, None]: + def db(t: int, p: int) -> Generator[str, None, None]: ... + + +def generate_cyclic_pattern(length: int, cycle: int = 4) -> bytearray: ... +def safe_parse_and_eval(value: str) -> Optional["gdb.Value"]: ... +def dereference(addr: int) -> Optional["gdb.Value"]: ... +def gef_convenience(value: str) -> str: ... +def parse_string_range(s: str) -> Iterator[int]: ... +def is_syscall(instruction: Union[Instruction, int]) -> bool: ... +def gef_get_pie_breakpoint(num: int) -> "PieVirtualBreakpoint": ... +def endian_str() -> str: ... + + +def gef_on_continue_hook(func: Callable[[gdb.Event], None]) -> None: ... +def gef_on_continue_unhook(func: Callable[[gdb.Event], None]) -> None: ... +def gef_on_stop_hook(func: Callable[[gdb.Event], None]) -> None: ... +def gef_on_stop_unhook(func: Callable[[gdb.Event], None]) -> None: ... +def gef_on_exit_hook(func: Callable[[gdb.Event], None]) -> None: ... +def gef_on_exit_unhook(func: Callable[[gdb.Event], None]) -> None: ... +def gef_on_new_hook(func: Callable[[gdb.Event], None]) -> None: ... +def gef_on_new_unhook(func: Callable[[gdb.Event], None]) -> None: ... +def gef_on_memchanged_hook(func: Callable[[gdb.Event], None]) -> None: ... +def gef_on_memchanged_unhook(func: Callable[[gdb.Event], None]) -> None: ... +def gef_on_regchanged_hook(func: Callable[[gdb.Event], None]) -> None: ... +def gef_on_regchanged_unhook(func: Callable[[gdb.Event], None]) -> None: ... + + +class PieVirtualBreakpoint: + def __init__(self, set_func: Callable[[ + int], str], vbp_num: int, addr: int) -> None: ... + + def instantiate(self, base: int) -> None: ... + def destroy(self) -> None: ... + + +class FormatStringBreakpoint(gdb.Breakpoint): + def __init__(self, spec: str, num_args: int) -> None: ... + def stop(self) -> bool: ... + + +class StubBreakpoint(gdb.Breakpoint): + def __init__(self, func: str, retval: Optional[int]) -> None: ... + def stop(self) -> bool: ... + + +class ChangePermissionBreakpoint(gdb.Breakpoint): + def __init__(self, loc: str, code: ByteString, pc: int) -> None: ... + def stop(self) -> bool: ... + + +class TraceMallocBreakpoint(gdb.Breakpoint): + def __init__(self, name: str) -> None: ... + def stop(self) -> bool: ... + + +class TraceMallocRetBreakpoint(gdb.FinishBreakpoint): + def __init__(self, size: int, name: str) -> None: ... + def stop(self) -> bool: ... + + +class TraceReallocBreakpoint(gdb.Breakpoint): + def __init__(self) -> None: ... + def stop(self) -> bool: ... + + +class TraceReallocRetBreakpoint(gdb.FinishBreakpoint): + def __init__(self, ptr: int, size: int) -> None: ... + def stop(self) -> bool: ... + + +class TraceFreeBreakpoint(gdb.Breakpoint): + def __init__(self) -> None: ... + def stop(self) -> bool: ... + + +class TraceFreeRetBreakpoint(gdb.FinishBreakpoint): + def __init__(self, addr: int) -> None: ... + def stop(self) -> bool: ... + + +class UafWatchpoint(gdb.Breakpoint): + def __init__(self, addr: int) -> None: ... + def stop(self) -> bool: ... + + +class EntryBreakBreakpoint(gdb.Breakpoint): + def __init__(self, location: str) -> None: ... + def stop(self) -> bool: ... + + +class NamedBreakpoint(gdb.Breakpoint): + def __init__(self, location: str, name: str) -> None: ... + def stop(self) -> bool: ... + + +def register_external_context_pane(pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], Optional[str]]) -> None: + def display_pane(): ... + def pane_title(): ... + + +def register(cls: Type["GenericCommand"]) -> Type["GenericCommand"]: ... + + +class GenericCommandBase: + def __init_subclass__(cls: Type["GenericCommandBase"], **kwargs): ... + + +class GenericExternalCommandBase(GenericCommandBase): + def __init_subclass__( + cls: Type["GenericExternalCommandBase"], **kwargs): ... + + +class GenericCommand(gdb.Command, GenericCommandBase): + def __init_subclass__(cls, **kwargs): ... + def __init__(self, *args: Any, **kwargs: Any) -> None: ... + def invoke(self, args: str, from_tty: bool) -> None: ... + def usage(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + def pre_load(self) -> None: ... + def post_load(self) -> None: ... + def __get_setting_name(self, name: str) -> str: ... + def __iter__(self) -> Generator[str, None, None]: ... + def settings(self) -> List[str]: ... + def get_setting(self, name: str) -> Any: ... + def __getitem__(self, name: str) -> Any: ... + def has_setting(self, name: str) -> bool: ... + def __contains__(self, name: str) -> bool: ... + + def add_setting( + self, name: str, value: Tuple[Any, type, str], description: str = "") -> None: ... + def __setitem__(self, name: str, + value: Union[Any, Tuple[Any, str]]) -> None: ... + + def del_setting(self, name: str) -> None: ... + def __delitem__(self, name: str) -> None: ... + def __set_repeat_count(self, argv: List[str], from_tty: bool) -> None: ... + + +class VersionCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + + +class PrintFormatCommand(GenericCommand): + def __init__(self) -> None: ... + def format_matrix(self) -> Dict[int, Tuple[str, str, str]]: ... + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + + +class PieCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + + +class PieBreakpointCommand(GenericCommand): + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + + @staticmethod + def set_pie_breakpoint( + set_func: Callable[[int], str], addr: int) -> None: ... + + +class PieInfoCommand(GenericCommand): + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + + +class PieDeleteCommand(GenericCommand): + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + @staticmethod + def delete_bp(breakpoints: List[PieVirtualBreakpoint]) -> None: ... + + +class PieRunCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + + +class PieAttachCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + + +class PieRemoteCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + + +class SmartEvalCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + + def evaluate(self, expr: List[str]) -> None: + def show_as_int(i: int) -> None: + def comp2_x(x: Any) -> str: ... + def comp2_b(x: Any) -> str: ... + + def distance(self, args: Tuple[str, str]) -> None: ... + + +class CanaryCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + + +class ProcessStatusCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + def get_state_of(self, pid: int) -> Dict[str, str]: ... + def get_cmdline_of(self, pid: int) -> str: ... + def get_process_path_of(self, pid: int) -> str: ... + def get_children_pids(self, pid: int) -> List[int]: ... + def show_info_proc(self) -> None: ... + def show_ancestor(self) -> None: ... + def show_descendants(self) -> None: ... + def show_fds(self) -> None: ... + def list_sockets(self, pid: int) -> List[int]: ... + def parse_ip_port(self, addr: str) -> Tuple[str, int]: ... + def show_connections(self) -> None: ... + + +class GefThemeCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, args: List[str]) -> None: ... + + +class ExternalStructureManager: + class Structure: + def __init__(self, manager: "ExternalStructureManager", + mod_path: pathlib.Path, struct_name: str) -> None: ... + + def __str__(self) -> str: ... + def pprint(self) -> None: ... + def __get_structure_class(self) -> Type: ... + def apply_at(self, address: int, max_depth: int, + depth: int = 0) -> None: ... + + def __get_ctypes_value(self, struct, item, value) -> str: ... + + class Module(dict): + def __init__(self, manager: "ExternalStructureManager", + path: pathlib.Path) -> None: ... + + def __load(self) -> ModuleType: ... + def __str__(self) -> str: ... + def __iter__(self) -> Generator[str, None, None]: ... + + class Modules(dict): + def __init__(self, manager: "ExternalStructureManager") -> None: ... + def __contains__(self, structure_name: str) -> bool: ... + + def __init__(self) -> None: ... + def clear_caches(self) -> None: ... + def modules(self) -> "ExternalStructureManager.Modules": ... + def path(self) -> pathlib.Path: ... + + def structures(self) -> Generator[Tuple["ExternalStructureManager.Module", + "ExternalStructureManager.Structure"], None, None]: ... + def find(self, structure_name: str) -> Optional[Tuple["ExternalStructureManager.Module", + "ExternalStructureManager.Structure"]]: ... + + +class PCustomCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, *_: Any, **kwargs: Dict[str, Any]) -> None: ... + def explode_type(self, arg: str) -> Tuple[str, str]: ... + + +class PCustomListCommand(PCustomCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List) -> None: ... + + +class PCustomShowCommand(PCustomCommand): + def __init__(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + + +class PCustomEditCommand(PCustomCommand): + def __init__(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + + def __create_or_edit_structure( + self, mod_name: str, struct_name: str) -> int: ... + def __create_template(self, structname: str, + fpath: pathlib.Path) -> None: ... + + +class ChangeFdCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + def get_fd_from_result(self, res: str) -> int: ... + + +class IdaInteractCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + + +class ScanSectionCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + + +class SearchPatternCommand(GenericCommand): + def print_section(self, section: Section) -> None: ... + def print_loc(self, loc: Tuple[int, int, str]) -> None: ... + def search_pattern_by_address(self, pattern: str, start_address: int, + end_address: int) -> List[Tuple[int, int, Optional[str]]]: ... + + def search_pattern(self, pattern: str, section_name: str) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + + +class FlagsCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + + +class ChangePermissionCommand(GenericCommand): + def __init__(self) -> None: ... + def pre_load(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + def get_stub_by_arch(self, addr: int, size: int, + perm: Permission) -> Union[str, bytearray, None]: ... + + +class UnicornEmulateCommand(GenericCommand): + def __init__(self) -> None: ... + def pre_load(self) -> None: ... + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + def get_unicorn_end_addr(self, start_addr: int, nb: int) -> int: ... + def run_unicorn(self, start_insn_addr: int, + end_insn_addr: int, **kwargs: Any) -> None: ... + + +class RemoteCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + def new_objfile_handler(self, event: gdb.Event) -> None: ... + + def setup_remote_environment( + self, pid: int, update_solib: bool = False) -> None: ... + def connect_target(self, target: str, + is_extended_remote: bool) -> bool: ... + + def load_from_remote_proc(self, pid: int, info: str) -> Optional[str]: ... + def refresh_shared_library_path(self) -> None: ... + def usage(self) -> None: ... + def prepare_qemu_stub(self, target: str) -> None: ... + + +class NopCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + + +class StubCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + + +class CapstoneDisassembleCommand(GenericCommand): + def pre_load(self) -> None: ... + def __init__(self) -> None: ... + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + def capstone_analyze_pc(self, insn: Instruction, + nb_insn: int) -> Tuple[bool, str]: ... + + +class GlibcHeapCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List[str]) -> None: ... + + +class GlibcHeapSetArenaCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + + +class GlibcHeapArenaCommand(GenericCommand): + def do_invoke(self, _: List[str]) -> None: ... + + +class GlibcHeapChunkCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + + +class GlibcHeapChunksCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + + def dump_chunks_arena(self, arena: GlibcArena, print_arena: bool = False, + allow_unaligned: bool = False) -> None: ... + def dump_chunks_heap(self, start: int, until: Optional[int] = None, + top: Optional[int] = None, allow_unaligned: bool = False) -> None: ... + + +class GlibcHeapBinsCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + @staticmethod + def pprint_bin(arena_addr: str, index: int, _type: str = "") -> int: ... + + +class GlibcHeapTcachebinsCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + @staticmethod + def find_tcache() -> int: ... + @staticmethod + def check_thread_ids(tids: List[int]) -> List[int]: ... + + @staticmethod + def tcachebin(tcache_base: int, + i: int) -> Tuple[Optional[GlibcChunk], int]: ... + + +class GlibcHeapFastbinsYCommand(GenericCommand): + def __init__(self) -> None: ... + + def do_invoke(self, *_: Any, **kwargs: Any) -> None: + def fastbin_index(sz: int) -> int: ... + + +class GlibcHeapUnsortedBinsCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, *_: Any, **kwargs: Any) -> None: ... + + +class GlibcHeapSmallBinsCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, *_: Any, **kwargs: Any) -> None: ... + + +class GlibcHeapLargeBinsCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, *_: Any, **kwargs: Any) -> None: ... + + +class SolveKernelSymbolCommand(GenericCommand): + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def hex_to_int(num): ... + + +class DetailRegistersCommand(GenericCommand): + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + + +class ShellcodeCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List[str]) -> None: ... + + +class ShellcodeSearchCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + def search_shellcode(self, search_options: List) -> None: ... + + +class ShellcodeGetCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + def get_shellcode(self, sid: int) -> None: ... + + +class RopperCommand(GenericCommand): + def __init__(self) -> None: ... + def pre_load(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + + +class AssembleCommand(GenericCommand): + def __init__(self) -> None: ... + def pre_load(self) -> None: ... + def usage(self) -> None: ... + def list_archs(self) -> None: ... + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + + +class ProcessListingCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List, **kwargs: Any) -> None: ... + def get_processes(self) -> Generator[Dict[str, str], None, None]: ... + + +class ElfInfoCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + + +class EntryPointBreakCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + def set_init_tbreak(self, addr: int) -> EntryBreakBreakpoint: ... + def set_init_tbreak_pie( + self, addr: int, argv: List[str]) -> EntryBreakBreakpoint: ... + + +class NamedBreakpointCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + + +class ContextCommand(GenericCommand): + instruction_iterator: Callable + def __init__(self) -> None: ... + def post_load(self) -> None: ... + def show_legend(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + def context_title(self, m: Optional[str]) -> None: ... + def context_regs(self) -> None: ... + def context_stack(self) -> None: ... + def addr_has_breakpoint( + self, address: int, bp_locations: List[str]) -> bool: ... + + def context_code(self) -> None: ... + def context_args(self) -> None: ... + + def print_arguments_from_symbol( + self, function_name: str, symbol: "gdb.Symbol") -> None: ... + + def print_guessed_arguments(self, function_name: str) -> None: + def __get_current_block_start_address() -> Optional[int]: ... + + def line_has_breakpoint( + self, file_name: str, line_number: int, bp_locations: List[str]) -> bool: ... + + def context_source(self) -> None: ... + def get_pc_context_info(self, pc: int, line: str) -> str: ... + def context_trace(self) -> None: ... + + def context_threads(self) -> None: + def reason() -> str: ... + + def context_additional_information(self) -> None: ... + def context_memory(self) -> None: ... + @classmethod + def update_registers(cls, _) -> None: ... + def empty_extra_messages(self, _) -> None: ... + + +class MemoryCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + + +class MemoryWatchCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + + +class MemoryUnwatchCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + + +class MemoryWatchResetCommand(GenericCommand): + def do_invoke(self, _: List[str]) -> None: ... + + +class MemoryWatchListCommand(GenericCommand): + def do_invoke(self, _: List[str]) -> None: ... + + +class HexdumpCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + def _hexdump(self, start_addr: int, length: int, + arrange_as: str, offset: int = 0) -> List[str]: ... + + +class HexdumpQwordCommand(HexdumpCommand): + def __init__(self) -> None: ... + + +class HexdumpDwordCommand(HexdumpCommand): + def __init__(self) -> None: ... + + +class HexdumpWordCommand(HexdumpCommand): + def __init__(self) -> None: ... + + +class HexdumpByteCommand(HexdumpCommand): + def __init__(self) -> None: ... + + +class PatchCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + + +class PatchQwordCommand(PatchCommand): + def __init__(self) -> None: ... + + +class PatchDwordCommand(PatchCommand): + def __init__(self) -> None: ... + + +class PatchWordCommand(PatchCommand): + def __init__(self) -> None: ... + + +class PatchByteCommand(PatchCommand): + def __init__(self) -> None: ... + + +class PatchStringCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + + +def dereference_from(addr: int) -> List[str]: ... + + +class DereferenceCommand(GenericCommand): + def __init__(self) -> None: ... + + @staticmethod + def pprint_dereferenced(addr: int, idx: int, + base_offset: int = 0) -> str: ... + + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + + +class ASLRCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + + +class ResetCacheCommand(GenericCommand): + def do_invoke(self, _: List[str]) -> None: ... + + +class VMMapCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + def print_entry(self, entry: Section) -> None: ... + def show_legend(self) -> None: ... + def is_integer(self, n: str) -> bool: ... + + +class XFilesCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + + +class XAddressInfoCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + def infos(self, address: int) -> None: ... + + +class XorMemoryCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List[str]) -> None: ... + + +class XorMemoryDisplayCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + + +class XorMemoryPatchCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + + +class TraceRunCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + def get_frames_size(self) -> int: ... + def trace(self, loc_start: int, loc_end: int, depth: int) -> None: ... + def start_tracing(self, loc_start: int, loc_end: int, + depth: int) -> None: ... + + +class PatternCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List[str]) -> None: ... + + +class PatternCreateCommand(GenericCommand): + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + + +class PatternSearchCommand(GenericCommand): + def do_invoke(self, _: List[str], **kwargs: Any) -> None: ... + def search(self, pattern: str, size: int, period: int) -> None: ... + + +class ChecksecCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + def print_security_properties(self, filename: str) -> None: ... + + +class GotCommand(GenericCommand): + def __init__(self): ... + def get_jmp_slots(self, readelf: str, filename: str) -> List[str]: ... + def do_invoke(self, argv: List[str]) -> None: ... + + +class HighlightCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List[str]) -> None: ... + + +class HighlightListCommand(GenericCommand): + def print_highlight_table(self) -> None: ... + def do_invoke(self, _: List[str]) -> None: ... + + +class HighlightClearCommand(GenericCommand): + def do_invoke(self, _: List[str]) -> None: ... + + +class HighlightAddCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + + +class HighlightRemoveCommand(GenericCommand): + def do_invoke(self, argv: List[str]) -> None: ... + + +class FormatStringSearchCommand(GenericCommand): + def do_invoke(self, _: List[str]) -> None: ... + + +class HeapAnalysisCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + def setup(self) -> None: ... + def dump_tracked_allocations(self) -> None: ... + def clean(self, _: gdb.Event) -> None: ... + + +class IsSyscallCommand(GenericCommand): + def do_invoke(self, _: List[str]) -> None: ... + + +class SyscallArgsCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List[str]) -> None: ... + + def __get_syscall_table(self, modname: str) -> Dict[str, Any]: + def get_filepath(x: str) -> Optional[pathlib.Path]: ... + def load_module(modname: str) -> Any: ... + + +def register_function(cls: Type["GenericFunction"] + ) -> Type["GenericFunction"]: ... + + +class GenericFunctionBase: + def __init_subclass__(cls: Type["GenericFunctionBase"], **kwargs): ... + + +class GenericFunction(gdb.Function, GenericFunctionBase): + def __init__(self) -> None: ... + def invoke(self, *args: Any) -> int: ... + def arg_to_long(self, args: List, index: int, default: int = 0) -> int: ... + def do_invoke(self, args: Any) -> int: ... + + +class StackOffsetFunction(GenericFunction): + def do_invoke(self, args: List) -> int: ... + + +class HeapBaseFunction(GenericFunction): + def do_invoke(self, args: List) -> int: ... + + +class SectionBaseFunction(GenericFunction): + def do_invoke(self, args: List) -> int: ... + + +class BssBaseFunction(GenericFunction): + def do_invoke(self, args: List) -> int: ... + + +class GotBaseFunction(GenericFunction): + def do_invoke(self, args: List) -> int: ... + + +class GefFunctionsCommand(GenericCommand): + def __init__(self) -> None: ... + def setup(self) -> None: ... + def add_function_to_doc(self, function) -> None: ... + def do_invoke(self, argv) -> None: ... + + +class GefCommand(gdb.Command): + commands: Dict[str, GenericCommand] + def __init__(self) -> None: ... + def setup(self) -> None: ... + def load_extra_plugins(self) -> int: ... + def loaded_command_names(self) -> List[str]: ... + def invoke(self, args: Any, from_tty: bool) -> None: ... + + def add_context_pane(self, pane_name: str, display_pane_function: Callable, + pane_title_function: Callable) -> None: ... + + def load(self, initial: bool = False) -> None: + def is_loaded(x: str) -> bool: ... + + +class GefHelpCommand(gdb.Command): + def __init__(self, commands: List[Tuple[str, Any, Any]]) -> None: ... + def invoke(self, args: Any, from_tty: bool) -> None: ... + + def generate_help( + self, commands: List[Tuple[str, Type[GenericCommand], Any]]) -> None: ... + def add_command_to_doc( + self, command: Tuple[str, Type[GenericCommand], Any]) -> None: ... + + def refresh(self) -> None: ... + + +class GefConfigCommand(gdb.Command): + def __init__(self, loaded_commands: List[str]) -> None: ... + def invoke(self, args: str, from_tty: bool) -> None: ... + def print_setting(self, plugin_name: str, + verbose: bool = False) -> None: ... + + def print_settings(self) -> None: ... + def set_setting(self, argv: Tuple[str, Any]) -> None: ... + def complete(self, text: str, word: str) -> List[str]: ... + + +class GefSaveCommand(gdb.Command): + def __init__(self) -> None: ... + def invoke(self, args: Any, from_tty: bool) -> None: ... + + +class GefRestoreCommand(gdb.Command): + def __init__(self) -> None: ... + def invoke(self, args: str, from_tty: bool) -> None: ... + + +class GefMissingCommand(gdb.Command): + def __init__(self) -> None: ... + def invoke(self, args: Any, from_tty: bool) -> None: ... + + +class GefSetCommand(gdb.Command): + def __init__(self) -> None: ... + def invoke(self, args: Any, from_tty: bool) -> None: ... + + +class GefRunCommand(gdb.Command): + def __init__(self) -> None: ... + def invoke(self, args: Any, from_tty: bool) -> None: ... + + +class GefAlias(gdb.Command): + def __init__(self, alias: str, command: str, completer_class: int = gdb.COMPLETE_NONE, + command_class: int = gdb.COMMAND_NONE) -> None: ... + + def invoke(self, args: Any, from_tty: bool) -> None: ... + def lookup_command(self, cmd: str) -> Optional[Tuple[str, Type, Any]]: ... + + +class AliasesCommand(GenericCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List[str]) -> None: ... + + +class AliasesAddCommand(AliasesCommand): + def __init__(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + + +class AliasesRmCommand(AliasesCommand): + def __init__(self) -> None: ... + def do_invoke(self, argv: List[str]) -> None: ... + + +class AliasesListCommand(AliasesCommand): + def __init__(self) -> None: ... + def do_invoke(self, _: List[str]) -> None: ... + + +class GefTmuxSetup(gdb.Command): + def __init__(self) -> None: ... + def invoke(self, args: Any, from_tty: bool) -> None: ... + def tmux_setup(self) -> None: ... + def screen_setup(self) -> None: ... + + +class GefInstallExtraScriptCommand(gdb.Command): + def __init__(self) -> None: ... + def invoke(self, args: str, from_tty: bool) -> None: ... + def __install_extras_script(self, script: str) -> bool: ... + + +def __gef_prompt__(current_prompt: Any) -> str: ... + + +class GefManager(metaclass=abc.ABCMeta): + def reset_caches(self) -> None: ... + + +class GefMemoryManager(GefManager): + def __init__(self) -> None: ... + def reset_caches(self) -> None: ... + def write(self, address: int, buffer: ByteString, + length: int = 0x10) -> None: ... + + def read(self, addr: int, length: int = 0x10) -> bytes: ... + def read_integer(self, addr: int) -> int: ... + def read_ascii_string(self, address: int) -> Optional[str]: ... + def read_cstring(self, address: int, max_length: int = ..., + encoding: Optional[str] = None) -> str: ... + + @property + def maps(self) -> List[Section]: ... + def __parse_maps(self) -> List[Section]: ... + + def __parse_procfs_maps(self) -> Generator[Section, None, None]: + def open_file(path: str, use_cache: bool = False) -> IO: ... + + def __parse_gdb_info_sections(self) -> Generator[Section, None, None]: ... + + +class GefHeapManager(GefManager): + def __init__(self) -> None: ... + def reset_caches(self) -> None: ... + def main_arena(self) -> Optional[GlibcArena]: ... + @property + def selected_arena(self) -> Optional[GlibcArena]: ... + @selected_arena.setter + def selected_arena(self, value: GlibcArena) -> None: ... + def arenas(self) -> Union[List, Iterator[GlibcArena]]: ... + def base_address(self) -> Optional[int]: ... + def chunks(self) -> Union[List, Iterator]: ... + def min_chunk_size(self) -> int: ... + def malloc_alignment(self) -> int: ... + def csize2tidx(self, size: int) -> int: ... + def tidx2size(self, idx: int) -> int: ... + def malloc_align_address(self, address: int) -> int: ... + + +class GefSetting: + def __init__(self, value: Any, + cls: Optional[type] = None, description: Optional[str] = None) -> None: ... + + +class GefSettingsManager(dict): + def __getitem__(self, name: str) -> Any: ... + def __setitem__(self, name: str, value: Any) -> None: ... + def __delitem__(self, name: str) -> None: ... + def raw_entry(self, name: str) -> GefSetting: ... + + +class GefSessionManager(GefManager): + remote: Optional[bool] = ... + qemu_mode: bool = ... + convenience_vars_index: int = ... + heap_allocated_chunks: List[Tuple[int, int]] = ... + heap_freed_chunks: List[Tuple[int, int]] = ... + heap_uaf_watchpoints: List[UafWatchpoint] = ... + pie_breakpoints: Dict[int, PieVirtualBreakpoint] = ... + pie_counter: int = ... + aliases: List[GefAlias] = ... + modules: List[Elf] = ... + constants: Dict[str, pathlib.Path] = ... + def __init__(self) -> None: ... + def reset_caches(self) -> None: ... + @property + def auxiliary_vector(self) -> Optional[Dict[str, int]]: ... + @property + def os(self) -> str: ... + @property + def pid(self) -> int: ... + @property + def file(self) -> pathlib.Path: ... + @property + def pagesize(self) -> int: ... + @property + def canary(self) -> Optional[Tuple[int, int]]: ... + + +class GefUiManager(GefManager): + redirect_fd: Optional[TextIOWrapper] = ... + context_hidden: bool + stream_buffer: Optional[StringIO] = ... + highlight_table: Dict[str, str] = ... + libc_args_table: Dict[str, Dict[str, Dict[str, str]]] = ... + watches: Dict[int, Tuple[int, str]] = ... + context_messages: List[str] = ... + def __init__(self) -> None: ... + + +class GefLibcManager(GefManager): + def __init__(self) -> None: ... + def version(self) -> Optional[Tuple[int, int]]: ... + + +class Gef: + binary: Optional[Elf] = ... + arch: Architecture = ... + config: GefSettingsManager = ... + ui: GefUiManager = ... + libc: GefLibcManager = ... + memory: GefMemoryManager = ... + heap: GefHeapManager = ... + session: GefSessionManager = ... + gdb: GefCommand = ... + def __init__(self) -> None: ... + def reinitialize_managers(self) -> None: ... + def setup(self) -> None: ... + def reset_caches(self) -> None: ... + + +gef: Gef = ... diff --git a/scripts/assemble.py b/scripts/assemble.py new file mode 100644 index 0000000..9844cc2 --- /dev/null +++ b/scripts/assemble.py @@ -0,0 +1,257 @@ + +__AUTHOR__ = "hugsy" +__VERSION__ = 0.2 +__LICENSE__ = "MIT" + +import binascii +from typing import TYPE_CHECKING, Any, List, Optional, Union + +import keystone + +if TYPE_CHECKING: + from . import * + from . import gdb + +PLUGIN_ASSEMBLE_DEFAULT_ADDRESS = 0x4000 + +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] + +__ks: Optional[keystone.Ks] = None + + +@register +class AssembleCommand(GenericCommand): + """Inline code assemble. Architecture can be set in GEF runtime config. """ + + _cmdline_ = "assemble" + _syntax_ = f"{_cmdline_} [-h] [--list-archs] [--mode MODE] [--arch ARCH] [--overwrite-location LOCATION] [--endian ENDIAN] [--as-shellcode] instruction;[instruction;...instruction;])" + _aliases_ = ["asm", ] + _example_ = (f"{_cmdline_} --arch x86 --mode 32 nop ; nop ; inc eax ; int3", + f"{_cmdline_} --arch arm --mode arm add r0, r0, 1") + + def __init__(self) -> None: + super().__init__() + self["default_architecture"] = ( + "X86", "Specify the default architecture to use when assembling") + self["default_mode"] = ( + "64", "Specify the default architecture to use when assembling") + return + + def pre_load(self) -> None: + try: + __import__("keystone") + except ImportError: + msg = "Missing `keystone-engine` package for Python, install with: `pip install keystone-engine`." + raise ImportWarning(msg) + return + + def usage(self) -> None: + super().usage() + gef_print("") + self.list_archs() + return + + def list_archs(self) -> None: + gef_print("Available architectures/modes (with endianness):") + # for updates, see https://github.com/keystone-engine/keystone/blob/master/include/keystone/keystone.h + for arch in VALID_ARCH_MODES: + endianness = "" + gef_print(f"- {arch}") + for mode, le, be in VALID_ARCH_MODES[arch]: + if le and be: + endianness = "little, big" + elif le: + endianness = "little" + elif be: + endianness = "big" + gef_print(f" * {mode:<7} ({endianness})") + return + + @parse_arguments({"instructions": [""]}, {"--mode": "", "--arch": "", "--overwrite-location": "", "--endian": "little", "--list-archs": True, "--as-shellcode": True}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + arch_s, mode_s, endian_s = self["default_architecture"], self["default_mode"], "" + + args = kwargs["arguments"] + if args.list_archs: + self.list_archs() + return + + if not args.instructions: + err("No instruction given.") + return + + if is_alive(): + arch_s, mode_s = gef.arch.arch, gef.arch.mode + endian_s = "big" if gef.arch.endianness == Endianness.BIG_ENDIAN else "" + + if not args.arch: + err("An architecture must be provided") + return + + if not args.mode: + err("A mode must be provided") + return + + arch_s = args.arch.upper() + mode_s = args.mode.upper() + endian_s = args.endian.upper() + + if arch_s not in VALID_ARCH_MODES: + raise AttributeError(f"invalid arch '{arch_s}'") + + valid_modes = VALID_ARCH_MODES[arch_s] + try: + mode_idx = [m[0] for m in valid_modes].index(mode_s) + except ValueError: + raise AttributeError( + f"invalid mode '{mode_s}' for arch '{arch_s}'") + + if endian_s == "little" and not valid_modes[mode_idx][1] or endian_s == "big" and not valid_modes[mode_idx][2]: + raise AttributeError( + f"invalid endianness '{endian_s}' for arch/mode '{arch_s}:{mode_s}'") + + ks_arch: int = getattr(keystone, f"KS_ARCH_{arch_s}") + ks_mode: int = getattr(keystone, f"KS_MODE_{mode_s}") + ks_endian: int = getattr( + keystone, f"KS_MODE_{endian_s}_ENDIAN") + insns = [x.strip() + for x in " ".join(args.instructions).split(";") if x] + info(f"Assembling {len(insns)} instruction(s) for {arch_s}:{mode_s}") + + if args.as_shellcode: + gef_print("""sc="" """) + + raw = b"" + for insn in insns: + res = ks_assemble(insn, ks_arch, ks_mode | ks_endian) + if res is None: + err("(Invalid)") + return + + if args.overwrite_location: + raw += res + + s = binascii.hexlify(res) + res = b"\\x" + b"\\x".join([s[i:i + 2] + for i in range(0, len(s), 2)]) + res = res.decode("utf-8") + + if args.as_shellcode: + res = f"""sc+="{res}" """ + + gef_print(f"{res!s:60s} # {insn}") + + if args.overwrite_location: + if not is_alive(): + warn( + "The debugging session is not active, cannot overwrite location. Skipping...") + return + + address = parse_address(args.overwrite_location) + info( + f"Overwriting {len(raw):d} bytes at {format_address(address)}") + gef.memory.write(address, raw, len(raw)) + return + + +def ks_assemble(code: str, arch: int, mode: int, address: int = PLUGIN_ASSEMBLE_DEFAULT_ADDRESS) -> Optional[bytes]: + """Assembly encoding function based on keystone.""" + global __ks + + if not __ks: + __ks = keystone.Ks(arch, mode) + + try: + enc, cnt = __ks.asm(code, address) + except keystone.KsError as e: + err(f"Keystone assembler error: {e}") + return None + + if cnt == 0 or not enc: + return None + + return bytes(enc) + + +@register +class ChangePermissionCommand(GenericCommand): + """Change a page permission. By default, it will change it to 7 (RWX).""" + + _cmdline_ = "set-permission" + _syntax_ = (f"{_cmdline_} address [permission]\n" + "\taddress\t\tan address within the memory page for which the permissions should be changed\n" + "\tpermission\ta 3-bit bitmask with read=1, write=2 and execute=4 as integer") + _aliases_ = ["mprotect"] + _example_ = f"{_cmdline_} $sp 7" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + if len(argv) not in (1, 2): + err("Incorrect syntax") + self.usage() + return + + if len(argv) == 2: + perm = Permission(int(argv[1])) + else: + perm = Permission.ALL + + loc = safe_parse_and_eval(argv[0]) + if loc is None: + err("Invalid address") + return + + loc = int(loc) + sect = process_lookup_address(loc) + if sect is None: + err("Unmapped address") + return + + size = sect.page_end - sect.page_start + original_pc = gef.arch.pc + + info(f"Generating sys_mprotect({sect.page_start:#x}, {size:#x}, " + f"'{perm!s}') stub for arch {get_arch()}") + stub = self.get_stub_by_arch(sect.page_start, size, perm) + if stub is None: + err("Failed to generate mprotect opcodes") + return + + info("Saving original code") + original_code = gef.memory.read(original_pc, len(stub)) + + bp_loc = f"*{original_pc + len(stub):#x}" + info(f"Setting a restore breakpoint at {bp_loc}") + ChangePermissionBreakpoint(bp_loc, original_code, original_pc) + + info(f"Overwriting current memory at {loc:#x} ({len(stub)} bytes)") + gef.memory.write(original_pc, stub, len(stub)) + + info("Resuming execution") + gdb.execute("continue") + return + + def get_stub_by_arch(self, addr: int, size: int, perm: Permission) -> Union[str, bytearray, None]: + code = gef.arch.mprotect_asm(addr, size, perm) + arch, mode = get_keystone_arch() + raw_insns = ks_assemble(code, arch, mode, raw=True) + return raw_insns diff --git a/scripts/bincompare.py b/scripts/bincompare.py index 12aeb5f..2291d08 100644 --- a/scripts/bincompare.py +++ b/scripts/bincompare.py @@ -11,57 +11,56 @@ # gef> bincompare -f /path/to/bytearray.bin -a memory_address # -import getopt +import pathlib +from typing import TYPE_CHECKING, Any, List + import gdb -import os -@register_external_command +if TYPE_CHECKING: + from . import * + +__AUTHOR__ = "@helviojunior" +__VERSION__ = 0.2 +__LICENSE__ = "MIT" + + +@register class BincompareCommand(GenericCommand): - """BincompareCommand: compare an binary file with the memory position looking for badchars.""" + """Compare an binary file with the memory position looking for badchars.""" _cmdline_ = "bincompare" - _syntax_ = "{:s} -f FILE -a MEMORY_ADDRESS [-h]".format(_cmdline_) + _syntax_ = f"{_cmdline_} MEMORY_ADDRESS FILE" def __init__(self): - super(BincompareCommand, self).__init__(complete=gdb.COMPLETE_FILENAME) + super().__init__(complete=gdb.COMPLETE_FILENAME) return def usage(self): - h = self._syntax_ - h += "\n\t-f FILE specifies the binary file to be compared.\n" - h += "\t-a MEMORY_ADDRESS sepecifies the memory address.\n" + h = (self._syntax_ + "\n" + + "\tMEMORY_ADDRESS sepecifies the memory address.\n" + + "\tFILE specifies the binary file to be compared.") info(h) return @only_if_gdb_running - def do_invoke(self, argv): - filename = None - start_addr = None + @parse_arguments({"address": "", "filename": ""}, {}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: size = 0 file_data = None memory_data = None - opts, args = getopt.getopt(argv, "f:a:ch") - for o, a in opts: - if o == "-f": - filename = a - elif o == "-a": - start_addr = int(gdb.parse_and_eval(a)) - elif o == "-h": - self.usage() - return - - if not filename or not start_addr: + args = kwargs["arguments"] + if not args.address or not args.filename: err("No file and/or address specified") return - if not os.path.isfile(filename): - err("Especified file '{:s}' not exists".format(filename)) - return + start_addr = parse_address(args.address) + filename = pathlib.Path(args.filename) - f = open(filename, "rb") - file_data = f.read() - f.close() + if not filename.exists(): + err(f"Specified file '{filename}' not exists") + return + file_data = filename.open("rb").read() size = len(file_data) if size < 8: @@ -87,7 +86,8 @@ def do_invoke(self, argv): result_table.append((hexchar, " ")) corrupted = -1 else: - result_table.append((hexchar, "{:02x}".format(memory_data[cnt]))) + result_table.append( + (hexchar, "{:02x}".format(memory_data[cnt]))) if len(badchars) == 0: badchars = hexchar else: @@ -135,4 +135,3 @@ def print_line(self, line, data, label): gef_print(" {:s} |{:s} {:s} {:s} {:s} {:s} {:s} {:s} {:s} {:s} {:s} {:s} {:s} {:s} {:s} {:s} {:s}| {:s}" .format(line, l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15], label)) - diff --git a/scripts/bytearray.py b/scripts/bytearray.py index 51ff851..33f4fba 100644 --- a/scripts/bytearray.py +++ b/scripts/bytearray.py @@ -15,7 +15,7 @@ import gdb import re -@register_external_command +@register class BytearrayCommand(GenericCommand): """BytearrayCommand: Generate a bytearray to be compared with possible badchars. Function ported from mona.py""" diff --git a/scripts/capstone.py b/scripts/capstone.py new file mode 100644 index 0000000..ff5adbe --- /dev/null +++ b/scripts/capstone.py @@ -0,0 +1,171 @@ + +__AUTHOR__ = "hugsy" +__VERSION__ = 0.2 +__LICENSE__ = "MIT" + +import sys +from typing import TYPE_CHECKING, Any, Generator, List, Optional, Tuple + +import capstone + +if TYPE_CHECKING: + from . import * + from . import gdb + + +__cs: Optional[capstone.Cs] = None + + +def gef_to_cs_arch() -> Tuple[str, str, str]: + if gef.arch.arch == "ARM": + if isinstance(gef.arch, ARM): + if gef.arch.is_thumb(): + return "CS_ARCH_ARM", "CS_MODE_THUMB", f"CS_MODE_{repr(gef.arch.endianness).upper()}" + return "CS_ARCH_ARM", "CS_MODE_ARM", f"CS_MODE_{repr(gef.arch.endianness).upper()}" + + if gef.arch.arch == "ARM64": + return "CS_ARCH_ARM64", "0", f"CS_MODE_{repr(gef.arch.endianness).upper()}" + + if gef.arch.arch == "X86": + if gef.arch.mode == "32": + return "CS_ARCH_X86", "CS_MODE_32", f"CS_MODE_{repr(gef.arch.endianness).upper()}" + if gef.arch.mode == "64": + return "CS_ARCH_X86", "CS_MODE_64", f"CS_MODE_{repr(gef.arch.endianness).upper()}" + + if gef.arch.arch == "PPC": + if gef.arch.mode == "PPC32": + return "CS_ARCH_PPC", "CS_MODE_PPC32", f"CS_MODE_{repr(gef.arch.endianness).upper()}" + if gef.arch.mode == "PPC64": + return "CS_ARCH_PPC", "CS_MODE_PPC64", f"CS_MODE_{repr(gef.arch.endianness).upper()}" + + if gef.arch.arch == "MIPS": + if gef.arch.mode == "MIPS32": + return "CS_ARCH_MIPS", "CS_MODE_MIPS32", f"CS_MODE_{repr(gef.arch.endianness).upper()}" + if gef.arch.mode == "MIPS64": + return "CS_ARCH_MIPS32", "CS_MODE_MIPS64", f"CS_MODE_{repr(gef.arch.endianness).upper()}" + + raise ValueError + + +def cs_disassemble(location: int, nb_insn: int, **kwargs: Any) -> Generator[Instruction, None, None]: + """Disassemble `nb_insn` instructions after `addr` and `nb_prev` before + `addr` using the Capstone-Engine disassembler, if available. + Return an iterator of Instruction objects.""" + + def cs_insn_to_gef_insn(cs_insn: capstone.CsInsn) -> Instruction: + sym_info = gdb_get_location_from_symbol(cs_insn.address) + loc = "<{}+{}>".format(*sym_info) if sym_info else "" + ops = [] + cs_insn.op_str.split(", ") + return Instruction(cs_insn.address, loc, cs_insn.mnemonic, ops, cs_insn.bytes) + + arch_s, mode_s, endian_s = gef_to_cs_arch() + cs_arch: int = getattr(capstone, arch_s) + cs_mode: int = getattr(capstone, mode_s) + cs_endian: int = getattr(capstone, endian_s) + + cs = capstone.Cs(cs_arch, cs_mode | cs_endian) + cs.detail = True + page_start = align_address_to_page(location) + offset = location - page_start + + skip = int(kwargs.get("skip", 0)) + nb_prev = int(kwargs.get("nb_prev", 0)) + pc = gef.arch.pc + if nb_prev > 0: + location = gdb_get_nth_previous_instruction_address(pc, nb_prev) or -1 + if location < 0: + err(f"failed to read previous instruction") + return + nb_insn += nb_prev + + code = kwargs.get("code", gef.memory.read( + location, gef.session.pagesize - offset - 1)) + for insn in cs.disasm(code, location): + if skip: + skip -= 1 + continue + nb_insn -= 1 + yield cs_insn_to_gef_insn(insn) + if nb_insn == 0: + break + return + + +@register +class CapstoneDisassembleCommand(GenericCommand): + """Use capstone disassembly framework to disassemble code.""" + + _cmdline_ = "capstone-disassemble" + _syntax_ = f"{_cmdline_} [-h] [--show-opcodes] [--length LENGTH] [LOCATION]" + _aliases_ = ["cs-dis"] + _example_ = f"{_cmdline_} --length 50 $pc" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + self["use-capstone"] = (False, + "Replace the GDB disassembler in the `context` with Capstone") + return + + def post_load(self) -> None: + super().post_load() + if self["use-capstone"] is True: + ctx = gef.gdb.commands["context"] + assert isinstance(ctx, ContextCommand) + ctx.instruction_iterator = cs_disassemble + return + + @only_if_gdb_running + @parse_arguments({("location"): "$pc"}, {("--show-opcodes", "-s"): True, "--length": 0}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + show_opcodes = args.show_opcodes + length = args.length or gef.config["context.nb_lines_code"] + location = parse_address(args.location) + if not location: + info(f"Can't find address for {args.location}") + return + + insns = [] + opcodes_len = 0 + for insn in cs_disassemble(location, length, skip=length * self.repeat_count, **kwargs): + insns.append(insn) + opcodes_len = max(opcodes_len, len(insn.opcodes)) + + for insn in insns: + insn_fmt = f"{{:{opcodes_len}o}}" if show_opcodes else "{}" + text_insn = insn_fmt.format(insn) + msg = "" + + if insn.address == gef.arch.pc: + msg = Color.colorify( + f"{RIGHT_ARROW} {text_insn}", "bold red") + valid, reason = self.capstone_analyze_pc(insn, length) + if valid: + gef_print(msg) + gef_print(reason) + break + else: + msg = f" {text_insn}" + + gef_print(msg) + return + + def capstone_analyze_pc(self, insn: Instruction, nb_insn: int) -> Tuple[bool, str]: + if gef.arch.is_conditional_branch(insn): + is_taken, reason = gef.arch.is_branch_taken(insn) + if is_taken: + reason = f"[Reason: {reason}]" if reason else "" + msg = Color.colorify(f"\tTAKEN {reason}", "bold green") + else: + reason = f"[Reason: !({reason})]" if reason else "" + msg = Color.colorify(f"\tNOT taken {reason}", "bold red") + return (is_taken, msg) + + if gef.arch.is_call(insn): + target_address = int(insn.operands[-1].split()[0], 16) + msg = [] + for i, new_insn in enumerate(cs_disassemble(target_address, nb_insn)): + msg.append(f" {DOWN_ARROW if i == 0 else ' '} {new_insn!s}") + return (True, "\n".join(msg)) + + return (False, "") diff --git a/scripts/trinity/unicorn.py b/scripts/emulate/__init__.py similarity index 70% rename from scripts/trinity/unicorn.py rename to scripts/emulate/__init__.py index 7213d1e..a9cdea8 100644 --- a/scripts/trinity/unicorn.py +++ b/scripts/emulate/__init__.py @@ -1,276 +1,344 @@ - -import sys -import os -from typing import Dict, Optional, Tuple, Union - -import capstone -import unicorn - - -def get_arch(arch: Optional[str] = None, mode: Optional[str] = None, endian: Optional[bool] = None, to_string: bool = False) -> Union[Tuple[None, None], Tuple[str, Union[int, str]]]: - unicorn = sys.modules["unicorn"] - if (arch, mode, endian) == (None, None, None): - return get_generic_running_arch(unicorn, "UC", to_string) - return get_generic_arch(unicorn, "UC", arch, mode, endian, to_string) - - -def get_registers(to_string: bool = False) -> Union[Dict[str, int], Dict[str, str]]: - "Return a dict matching the Unicorn identifier for a specific register." - unicorn = sys.modules["unicorn"] - regs = {} - - if gef.arch is not None: - arch = gef.arch.arch.lower() - else: - raise OSError("Oops") - - const = getattr(unicorn, f"{arch}_const") - for reg in gef.arch.all_registers: - regname = f"UC_{arch.upper()}_REG_{reg[1:].upper()}" - if to_string: - regs[reg] = f"{const.__name__}.{regname}" - else: - regs[reg] = getattr(const, regname) - return regs - - -class UnicornEmulateCommand(GenericCommand): - """Use Unicorn-Engine to emulate the behavior of the binary, without affecting the GDB runtime. - By default the command will emulate only the next instruction, but location and number of - instruction can be changed via arguments to the command line. By default, it will emulate - the next instruction from current PC.""" - - _cmdline_ = "unicorn-emulate" - _syntax_ = (f"{_cmdline_} [--start LOCATION] [--until LOCATION] [--skip-emulation] [--output-file PATH] [NB_INSTRUCTION]" - "\n\t--start LOCATION specifies the start address of the emulated run (default $pc)." - "\t--until LOCATION specifies the end address of the emulated run." - "\t--skip-emulation\t do not execute the script once generated." - "\t--output-file /PATH/TO/SCRIPT.py writes the persistent Unicorn script into this file." - "\tNB_INSTRUCTION indicates the number of instructions to execute" - "\nAdditional options can be setup via `gef config unicorn-emulate`") - _aliases_ = ["emulate", ] - _example_ = f"{_cmdline_} --start $pc 10 --output-file /tmp/my-gef-emulation.py" - - def __init__(self) -> None: - super().__init__(complete=gdb.COMPLETE_LOCATION) - self["verbose"] = (False, "Set unicorn-engine in verbose mode") - self["show_disassembly"] = (False, "Show every instruction executed") - return - - @only_if_gdb_running - @parse_arguments({"nb": 1}, {"--start": "", "--until": "", "--skip-emulation": True, "--output-file": ""}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: - args = kwargs["arguments"] - start_address = parse_address(str(args.start or gef.arch.pc)) - end_address = parse_address( - str(args.until or self.get_unicorn_end_addr(start_address, args.nb))) - self.run_unicorn(start_address, end_address, - skip_emulation=args.skip_emulation, to_file=args.output_file) - return - - def get_unicorn_end_addr(self, start_addr: int, nb: int) -> int: - dis = list(gef_disassemble(start_addr, nb + 1)) - last_insn = dis[-1] - return last_insn.address - - def run_unicorn(self, start_insn_addr: int, end_insn_addr: int, **kwargs: Any) -> None: - verbose = self["verbose"] or False - skip_emulation = kwargs.get("skip_emulation", False) - arch, mode = get_unicorn_arch(to_string=True) - unicorn_registers = get_unicorn_registers(to_string=True) - cs_arch, cs_mode = get_capstone_arch(to_string=True) - fname = gef.session.file.name - to_file = kwargs.get("to_file", None) - emulate_segmentation_block = "" - context_segmentation_block = "" - - if to_file: - tmp_filename = to_file - to_file = open(to_file, "w") - tmp_fd = to_file.fileno() - else: - tmp_fd, tmp_filename = tempfile.mkstemp( - suffix=".py", prefix="gef-uc-") - - if is_x86(): - # need to handle segmentation (and pagination) via MSR - emulate_segmentation_block = """ -# from https://github.com/unicorn-engine/unicorn/blob/master/tests/regress/x86_64_msr.py -SCRATCH_ADDR = 0xf000 -SEGMENT_FS_ADDR = 0x5000 -SEGMENT_GS_ADDR = 0x6000 -FSMSR = 0xC0000100 -GSMSR = 0xC0000101 - -def set_msr(uc, msr, value, scratch=SCRATCH_ADDR): - buf = b"\\x0f\\x30" # x86: wrmsr - uc.mem_map(scratch, 0x1000) - uc.mem_write(scratch, buf) - uc.reg_write(unicorn.x86_const.UC_X86_REG_RAX, value & 0xFFFFFFFF) - uc.reg_write(unicorn.x86_const.UC_X86_REG_RDX, (value >> 32) & 0xFFFFFFFF) - uc.reg_write(unicorn.x86_const.UC_X86_REG_RCX, msr & 0xFFFFFFFF) - uc.emu_start(scratch, scratch+len(buf), count=1) - uc.mem_unmap(scratch, 0x1000) - return - -def set_gs(uc, addr): return set_msr(uc, GSMSR, addr) -def set_fs(uc, addr): return set_msr(uc, FSMSR, addr) - -""" - - context_segmentation_block = """ - emu.mem_map(SEGMENT_FS_ADDR-0x1000, 0x3000) - set_fs(emu, SEGMENT_FS_ADDR) - set_gs(emu, SEGMENT_GS_ADDR) -""" - - content = """#!{pythonbin} -i -# -# Emulation script for "{fname}" from {start:#x} to {end:#x} -# -# Powered by gef, unicorn-engine, and capstone-engine -# -# @_hugsy_ -# -import collections -import capstone, unicorn - -registers = collections.OrderedDict(sorted({{{regs}}}.items(), key=lambda t: t[0])) -uc = None -verbose = {verbose} -syscall_register = "{syscall_reg}" - -def disassemble(code, addr): - cs = capstone.Cs({cs_arch}, {cs_mode}) - for i in cs.disasm(code, addr): - return i - -def hook_code(emu, address, size, user_data): - code = emu.mem_read(address, size) - insn = disassemble(code, address) - print(">>> {{:#x}}: {{:s}} {{:s}}".format(insn.address, insn.mnemonic, insn.op_str)) - return - -def code_hook(emu, address, size, user_data): - code = emu.mem_read(address, size) - insn = disassemble(code, address) - print(">>> {{:#x}}: {{:s}} {{:s}}".format(insn.address, insn.mnemonic, insn.op_str)) - return - -def intr_hook(emu, intno, data): - print(" \\-> interrupt={{:d}}".format(intno)) - return - -def syscall_hook(emu, user_data): - sysno = emu.reg_read(registers[syscall_register]) - print(" \\-> syscall={{:d}}".format(sysno)) - return - -def print_regs(emu, regs): - for i, r in enumerate(regs): - print("{{:7s}} = {{:#0{ptrsize}x}} ".format(r, emu.reg_read(regs[r])), end="") - if (i % 4 == 3) or (i == len(regs)-1): print("") - return - -{emu_block} - -def reset(): - emu = unicorn.Uc({arch}, {mode}) - -{context_block} -""".format(pythonbin=PYTHONBIN, fname=fname, start=start_insn_addr, end=end_insn_addr, - regs=",".join( - [f"'{k.strip()}': {unicorn_registers[k]}" for k in unicorn_registers]), - verbose="True" if verbose else "False", - syscall_reg=gef.arch.syscall_register, - cs_arch=cs_arch, cs_mode=cs_mode, - ptrsize=gef.arch.ptrsize * 2 + 2, # two hex chars per byte plus "0x" prefix - emu_block=emulate_segmentation_block if is_x86() else "", - arch=arch, mode=mode, - context_block=context_segmentation_block if is_x86() else "") - - if verbose: - info("Duplicating registers") - - for r in gef.arch.all_registers: - gregval = gef.arch.register(r) - content += f" emu.reg_write({unicorn_registers[r]}, {gregval:#x})\n" - - vmmap = gef.memory.maps - if not vmmap: - warn("An error occurred when reading memory map.") - return - - if verbose: - info("Duplicating memory map") - - for sect in vmmap: - if sect.path == "[vvar]": - # this section is for GDB only, skip it - continue - - page_start = sect.page_start - page_end = sect.page_end - size = sect.size - perm = sect.permission - - content += f" # Mapping {sect.path}: {page_start:#x}-{page_end:#x}\n" - content += f" emu.mem_map({page_start:#x}, {size:#x}, {perm.value:#o})\n" - - if perm & Permission.READ: - code = gef.memory.read(page_start, size) - loc = f"/tmp/gef-{fname}-{page_start:#x}.raw" - with open(loc, "wb") as f: - f.write(bytes(code)) - - content += f" emu.mem_write({page_start:#x}, open('{loc}', 'rb').read())\n" - content += "\n" - - content += " emu.hook_add(unicorn.UC_HOOK_CODE, code_hook)\n" - content += " emu.hook_add(unicorn.UC_HOOK_INTR, intr_hook)\n" - if is_x86_64(): - content += " emu.hook_add(unicorn.UC_HOOK_INSN, syscall_hook, None, 1, 0, unicorn.x86_const.UC_X86_INS_SYSCALL)\n" - content += " return emu\n" - - content += """ -def emulate(emu, start_addr, end_addr): - print("========================= Initial registers =========================") - print_regs(emu, registers) - - try: - print("========================= Starting emulation =========================") - emu.emu_start(start_addr, end_addr) - except Exception as e: - emu.emu_stop() - print("========================= Emulation failed =========================") - print("[!] Error: {{}}".format(e)) - - print("========================= Final registers =========================") - print_regs(emu, registers) - return - - -uc = reset() -emulate(uc, {start:#x}, {end:#x}) - -# unicorn-engine script generated by gef -""".format(start=start_insn_addr, end=end_insn_addr) - - os.write(tmp_fd, gef_pybytes(content)) - os.close(tmp_fd) - - if kwargs.get("to_file", None): - info(f"Unicorn script generated as '{tmp_filename}'") - os.chmod(tmp_filename, 0o700) - - if skip_emulation: - return - - ok(f"Starting emulation: {start_insn_addr:#x} {RIGHT_ARROW} {end_insn_addr:#x}") - - res = gef_execute_external([PYTHONBIN, tmp_filename], as_list=True) - gef_print("\n".join(res)) - - if not kwargs.get("to_file", None): - os.unlink(tmp_filename) - return +__AUTHOR__ = "hugsy" +__VERSION__ = 0.2 +__LICENSE__ = "MIT" + +import os +import pathlib +import sys +import tempfile +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union + +import capstone +import unicorn + +if TYPE_CHECKING: + from .. import * + from .. import gdb + +PLUGIN_PATH = pathlib.Path(__file__).parent + + +def uc_registers(to_string: bool = False) -> Union[Dict[str, int], Dict[str, str]]: + "Return a dict matching the Unicorn identifier for a specific register." + unicorn = sys.modules["unicorn"] + regs = {} + + if gef.arch is not None: + arch = gef.arch.arch.lower() + else: + raise OSError("Oops") + + const = getattr(unicorn, f"{arch}_const") + for reg in gef.arch.all_registers: + regname = f"UC_{arch.upper()}_REG_{reg[1:].upper()}" + if to_string: + regs[reg] = f"{const.__name__}.{regname}" + else: + regs[reg] = getattr(const, regname) + return regs + + +def gef_to_uc_arch() -> Tuple[str, str, str]: + if gef.arch.arch == "ARM": + if isinstance(gef.arch, ARM): + if gef.arch.is_thumb(): + return "UC_ARCH_ARM", "UC_MODE_THUMB", f"UC_MODE_{repr(gef.arch.endianness).upper()}" + return "UC_ARCH_ARM", "UC_MODE_ARM", f"UC_MODE_{repr(gef.arch.endianness).upper()}" + + if gef.arch.arch == "ARM64": + return "UC_ARCH_ARM64", "0", f"UC_MODE_{repr(gef.arch.endianness).upper()}" + + if gef.arch.arch == "X86": + if gef.arch.mode == "32": + return "UC_ARCH_X86", "UC_MODE_32", f"UC_MODE_{repr(gef.arch.endianness).upper()}" + if gef.arch.mode == "64": + return "UC_ARCH_X86", "UC_MODE_64", f"UC_MODE_{repr(gef.arch.endianness).upper()}" + + if gef.arch.arch == "PPC": + if gef.arch.mode == "PPC32": + return "UC_ARCH_PPC", "UC_MODE_PPC32", f"UC_MODE_{repr(gef.arch.endianness).upper()}" + if gef.arch.mode == "PPC64": + return "UC_ARCH_PPC", "UC_MODE_PPC64", f"UC_MODE_{repr(gef.arch.endianness).upper()}" + + if gef.arch.arch == "MIPS": + if gef.arch.mode == "MIPS32": + return "UC_ARCH_MIPS", "UC_MODE_MIPS32", f"UC_MODE_{repr(gef.arch.endianness).upper()}" + if gef.arch.mode == "MIPS64": + return "UC_ARCH_MIPS32", "UC_MODE_MIPS64", f"UC_MODE_{repr(gef.arch.endianness).upper()}" + + raise ValueError + + +def gef_to_cs_arch() -> Tuple[str, str, str]: + if gef.arch.arch == "ARM": + if isinstance(gef.arch, ARM): + if gef.arch.is_thumb(): + return "CS_ARCH_ARM", "CS_MODE_THUMB", f"CS_MODE_{repr(gef.arch.endianness).upper()}" + return "CS_ARCH_ARM", "CS_MODE_ARM", f"CS_MODE_{repr(gef.arch.endianness).upper()}" + + if gef.arch.arch == "ARM64": + return "CS_ARCH_ARM64", "0", f"CS_MODE_{repr(gef.arch.endianness).upper()}" + + if gef.arch.arch == "X86": + if gef.arch.mode == "32": + return "CS_ARCH_X86", "CS_MODE_32", f"CS_MODE_{repr(gef.arch.endianness).upper()}" + if gef.arch.mode == "64": + return "CS_ARCH_X86", "CS_MODE_64", f"CS_MODE_{repr(gef.arch.endianness).upper()}" + + if gef.arch.arch == "PPC": + if gef.arch.mode == "PPC32": + return "CS_ARCH_PPC", "CS_MODE_PPC32", f"CS_MODE_{repr(gef.arch.endianness).upper()}" + if gef.arch.mode == "PPC64": + return "CS_ARCH_PPC", "CS_MODE_PPC64", f"CS_MODE_{repr(gef.arch.endianness).upper()}" + + if gef.arch.arch == "MIPS": + if gef.arch.mode == "MIPS32": + return "CS_ARCH_MIPS", "CS_MODE_MIPS32", f"CS_MODE_{repr(gef.arch.endianness).upper()}" + if gef.arch.mode == "MIPS64": + return "CS_ARCH_MIPS32", "CS_MODE_MIPS64", f"CS_MODE_{repr(gef.arch.endianness).upper()}" + + raise ValueError + + +@register +class UnicornEmulateCommand(GenericCommand): + """Use Unicorn-Engine to emulate the behavior of the binary, without affecting the GDB runtime. + By default the command will emulate only the next instruction, but location and number of + instruction can be changed via arguments to the command line. By default, it will emulate + the next instruction from current PC.""" + + _cmdline_ = "unicorn-emulate" + _syntax_ = (f"{_cmdline_} [--start LOCATION] [--until LOCATION] [--skip-emulation] [--output-file PATH] [NB_INSTRUCTION]" + "\n\t--start LOCATION specifies the start address of the emulated run (default $pc)." + "\t--until LOCATION specifies the end address of the emulated run." + "\t--skip-emulation\t do not execute the script once generated." + "\t--output-file /PATH/TO/SCRIPT.py writes the persistent Unicorn script into this file." + "\tNB_INSTRUCTION indicates the number of instructions to execute" + "\nAdditional options can be setup via `gef config unicorn-emulate`") + _aliases_ = ["emulate", ] + _example_ = f"{_cmdline_} --start $pc 10 --output-file /tmp/my-gef-emulation.py" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + self["verbose"] = (False, "Set unicorn-engine in verbose mode") + self["show_disassembly"] = (False, "Show every instruction executed") + return + + @only_if_gdb_running + @parse_arguments({"nb": 1}, {"--start": "", "--until": "", "--skip-emulation": True, "--output-file": ""}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + start_address = parse_address(str(args.start or gef.arch.pc)) + end_address = parse_address( + str(args.until or self.get_unicorn_end_addr(start_address, args.nb))) + self.run_unicorn(start_address, end_address, + skip_emulation=args.skip_emulation, to_file=args.output_file) + return + + def get_unicorn_end_addr(self, start_addr: int, nb: int) -> int: + dis = list(gef_disassemble(start_addr, nb + 1)) + last_insn = dis[-1] + return last_insn.address + + def run_unicorn(self, start_insn_addr: int, end_insn_addr: int, **kwargs: Any) -> None: + verbose = self["verbose"] or False + skip_emulation = kwargs.get("skip_emulation", False) + uc_arch, uc_mode, uc_endian = gef_to_uc_arch() + unicorn_registers = uc_registers(to_string=True) + cs_arch, cs_mode, cs_endian = gef_to_cs_arch() + fname = gef.session.file.name + to_file = kwargs.get("to_file", None) + emulate_segmentation_block = "" + context_segmentation_block = "" + + if to_file: + tmp_filename = to_file + to_file = open(to_file, "w") + tmp_fd = to_file.fileno() + else: + tmp_fd, tmp_filename = tempfile.mkstemp( + suffix=".py", prefix="gef-uc-") + + if is_x86(): + # need to handle segmentation (and pagination) via MSR + emulate_segmentation_block = """ +# from https://github.com/unicorn-engine/unicorn/blob/master/tests/regress/x86_64_msr.py +SCRATCH_ADDR = 0xf000 +SEGMENT_FS_ADDR = 0x5000 +SEGMENT_GS_ADDR = 0x6000 +FSMSR = 0xC0000100 +GSMSR = 0xC0000101 + +def set_msr(uc, msr, value, scratch=SCRATCH_ADDR): + buf = b"\\x0f\\x30" # x86: wrmsr + uc.mem_map(scratch, 0x1000) + uc.mem_write(scratch, buf) + uc.reg_write(unicorn.x86_const.UC_X86_REG_RAX, value & 0xFFFFFFFF) + uc.reg_write(unicorn.x86_const.UC_X86_REG_RDX, (value >> 32) & 0xFFFFFFFF) + uc.reg_write(unicorn.x86_const.UC_X86_REG_RCX, msr & 0xFFFFFFFF) + uc.emu_start(scratch, scratch+len(buf), count=1) + uc.mem_unmap(scratch, 0x1000) + return + +def set_gs(uc, addr): return set_msr(uc, GSMSR, addr) +def set_fs(uc, addr): return set_msr(uc, FSMSR, addr) + +""" + + context_segmentation_block = """ + emu.mem_map(SEGMENT_FS_ADDR-0x1000, 0x3000) + set_fs(emu, SEGMENT_FS_ADDR) + set_gs(emu, SEGMENT_GS_ADDR) +""" + + content = """#!{pythonbin} -i +# +# Emulation script for "{fname}" from {start:#x} to {end:#x} +# +# Powered by gef, unicorn-engine, and capstone-engine +# +# @_hugsy_ +# +import collections +import capstone, unicorn + +registers = collections.OrderedDict(sorted({{{regs}}}.items(), key=lambda t: t[0])) +uc = None +verbose = {verbose} +syscall_register = "{syscall_reg}" + +def disassemble(code, addr): + cs = capstone.Cs(capstone.{cs_arch}, capstone.{cs_mode}|capstone.{cs_endian}) + 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(unicorn.{arch}, unicorn.{mode}|unicorn.{endian}) + +{context_block} +""".format(pythonbin=gef.session.constants["python3"], fname=fname, start=start_insn_addr, end=end_insn_addr, + regs=",".join( + [f"'{k.strip()}': {unicorn_registers[k]}" for k in unicorn_registers]), + verbose="True" if verbose else "False", + syscall_reg=gef.arch.syscall_register, + cs_arch=cs_arch, cs_mode=cs_mode, cs_endian=cs_endian, + ptrsize=gef.arch.ptrsize * 2 + 2, # two hex chars per byte plus "0x" prefix + emu_block=emulate_segmentation_block if is_x86() else "", + arch=uc_arch, mode=uc_mode, endian=uc_endian, + context_block=context_segmentation_block if is_x86() else "") + + if verbose: + info("Duplicating registers") + + for r in gef.arch.all_registers: + gregval = gef.arch.register(r) + content += f" emu.reg_write({unicorn_registers[r]}, {gregval:#x})\n" + + vmmap = gef.memory.maps + if not vmmap: + warn("An error occurred when reading memory map.") + return + + if verbose: + info("Duplicating memory map") + + for sect in vmmap: + if sect.path == "[vvar]": + # this section is for GDB only, skip it + continue + + page_start = sect.page_start + page_end = sect.page_end + size = sect.size + perm = sect.permission + + content += f" # Mapping {sect.path}: {page_start:#x}-{page_end:#x}\n" + content += f" emu.mem_map({page_start:#x}, {size:#x}, {perm.value:#o})\n" + + if perm & Permission.READ: + code = gef.memory.read(page_start, size) + loc = f"/tmp/gef-{fname}-{page_start:#x}.raw" + with open(loc, "wb") as f: + f.write(bytes(code)) + + content += f" emu.mem_write({page_start:#x}, open('{loc}', 'rb').read())\n" + content += "\n" + + content += " emu.hook_add(unicorn.UC_HOOK_CODE, code_hook)\n" + content += " emu.hook_add(unicorn.UC_HOOK_INTR, intr_hook)\n" + if is_x86_64(): + content += " emu.hook_add(unicorn.UC_HOOK_INSN, syscall_hook, None, 1, 0, unicorn.x86_const.UC_X86_INS_SYSCALL)\n" + content += " return emu\n" + + content += """ +def emulate(emu, start_addr, end_addr): + print("========================= Initial registers =========================") + print_regs(emu, registers) + + try: + print("========================= Starting emulation =========================") + emu.emu_start(start_addr, end_addr) + except Exception as e: + emu.emu_stop() + print("========================= Emulation failed =========================") + print("[!] Error: {{}}".format(e)) + + print("========================= Final registers =========================") + print_regs(emu, registers) + return + + +uc = reset() +emulate(uc, {start:#x}, {end:#x}) + +# unicorn-engine script generated by gef +""".format(start=start_insn_addr, end=end_insn_addr) + + os.write(tmp_fd, gef_pybytes(content)) + os.close(tmp_fd) + + if kwargs.get("to_file", None): + info(f"Unicorn script generated as '{tmp_filename}'") + os.chmod(tmp_filename, 0o700) + + if skip_emulation: + return + + ok(f"Starting emulation: {start_insn_addr:#x} {RIGHT_ARROW} {end_insn_addr:#x}") + + cmd = [gef.session.constants["python3"], tmp_filename] + res = gef_execute_external(cmd, as_list=True) + gef_print("\n".join(res)) + + if not kwargs.get("to_file", None): + os.unlink(tmp_filename) + return diff --git a/scripts/error.py b/scripts/error.py deleted file mode 100644 index dcc3dd2..0000000 --- a/scripts/error.py +++ /dev/null @@ -1,35 +0,0 @@ -__AUTHOR__ = "hugsy" -__VERSION__ = 0.1 - -import gdb -from ctypes import (CDLL, c_char_p, c_int32,) - -@register_external_command -class ErrorCommand(GenericCommand): - """windbg !error-like command""" - - _cmdline_ = "error" - _syntax_ = "{:s}".format(_cmdline_) - _aliases_ = ["perror", ] - _example_ = "{:s}".format(_cmdline_) - - def __init__(self): - super(ErrorCommand, self).__init__(complete=gdb.COMPLETE_LOCATION) - return - - def do_invoke(self, argv): - argc = len(argv) - if not argc and is_alive(): - value = gef.arch.register(gef.arch.return_register) - elif argv[0].isdigit(): - value = int(argv[0]) - else: - value = int(gdb.parse_and_eval(argv[0])) - - __libc = CDLL("libc.so.6") - __libc.strerror.restype = c_char_p - __libc.strerror.argtypes = [c_int32, ] - c_s = __libc.strerror(value).decode("utf8") - info("{0:d} ({0:#x}) : {1:s}".format(value, Color.greenify(c_s))) - return - diff --git a/scripts/error/__init__.py b/scripts/error/__init__.py new file mode 100644 index 0000000..69dd6c2 --- /dev/null +++ b/scripts/error/__init__.py @@ -0,0 +1,41 @@ +__AUTHOR__ = "hugsy" +__VERSION__ = 0.1 + +import ctypes +from typing import TYPE_CHECKING + +import gdb + +if TYPE_CHECKING: + from .. import * + from .. import gdb + + +@register +class ErrorCommand(GenericCommand): + """WinDbg `!error` -like command""" + + _cmdline_ = "error" + _syntax_ = f"{_cmdline_:s}" + _aliases_ = ["perror", ] + _example_ = f"{_cmdline_:s}" + + def __init__(self): + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + def do_invoke(self, argv): + argc = len(argv) + if argc == 0 and is_alive(): + value = gef.arch.register(gef.arch.return_register) + elif argv[0].isdigit(): + value = int(argv[0]) + else: + value = parse_address(argv[0]) + + __libc = ctypes.CDLL("libc.so.6") + __libc.strerror.restype = ctypes.c_char_p + __libc.strerror.argtypes = [ctypes.c_int32, ] + c_s = __libc.strerror(value).decode("utf8") + info(f"{value:d} ({value:#x}) : {Color.greenify(c_s):s}") + return diff --git a/scripts/ftrace.py b/scripts/ftrace.py index 884a72c..d83838d 100644 --- a/scripts/ftrace.py +++ b/scripts/ftrace.py @@ -1,63 +1,86 @@ __AUTHOR__ = "hugsy" __VERSION__ = 0.1 +__AUTHOR__ = "your_name" +__VERSION__ = 0.1 +__LICENSE__ = "MIT" + import collections +import pathlib +from typing import TYPE_CHECKING, Optional + +if TYPE_CHECKING: + from . import * + from . import gdb + + +PLUGIN_FTRACE_DEFAULT_OUTPUT = "/dev/stderr" class FtraceEnterBreakpoint(gdb.Breakpoint): - def __init__(self, location, nb_args, *args, **kwargs): - super(FtraceEnterBreakpoint, self).__init__(location, gdb.BP_BREAKPOINT, internal=True) - self.silent = True - self.nb_args = nb_args - self.retbp = None + def __init__(self, location: str, nb_args: int): + super().__init__(location, gdb.BP_BREAKPOINT, internal=True) + self.silent: bool = True + self.nb_args: int = nb_args + self.retbp: Optional[FtraceExitBreakpoint] = None return def stop(self): regs = collections.OrderedDict() - for r in current_arch.function_parameters[:self.nb_args]: - regs[r] = get_register(r) - self.retbp = FtraceExitBreakpoint(location=self.location, regs=regs) + for idx, r in enumerate(gef.arch.function_parameters): + if idx >= self.nb_args: + break + regs[r] = gef.arch.register(r) + self.retbp = FtraceExitBreakpoint( + location=self.location, regs=regs) return False class FtraceExitBreakpoint(gdb.FinishBreakpoint): def __init__(self, *args, **kwargs): - super(FtraceExitBreakpoint, self).__init__(gdb.newest_frame(), internal=True) + super(FtraceExitBreakpoint, self).__init__( + gdb.newest_frame(), internal=True) self.silent = True self.args = kwargs return def stop(self): if self.return_value: - retval = "{:#x}".format(long(self.return_value)) + retval = format_address(abs(self.return_value)) else: - retval = get_register(current_arch.return_register) - - output = gef.config["ftrace.output"] - use_color = False + retval = gef.arch.register(gef.arch.return_register) - if output is None: - output = "/dev/stderr" - use_color = True + output = pathlib.Path(gef.config["ftrace.output"]) + use_color = PLUGIN_FTRACE_DEFAULT_OUTPUT == gef.config["ftrace.output"] - with open(output, "w") as fd: + with output.open("w") as fd: if use_color: - fd.write("{:s}() = {} {{\n".format(Color.yellowify(self.args["location"]), retval)) + fd.write("{:s}() = {} {{\n".format( + Color.yellowify(self.args["location"]), retval)) else: - fd.write("{:s}() = {} {{\n".format(self.args["location"], retval)) + fd.write("{:s}() = {} {{\n".format( + self.args["location"], retval)) for reg in self.args["regs"].keys(): regval = self.args["regs"][reg] - fd.write("\t{} {} {}\n".format(reg, RIGHT_ARROW, RIGHT_ARROW.join(dereference_from(regval)))) + fd.write("\t{} {} {}\n".format(reg, RIGHT_ARROW, + RIGHT_ARROW.join(dereference_from(regval)))) fd.write("}\n") fd.flush() return False -@register_external_command +@register class FtraceCommand(GenericCommand): """Tracks a function given in parameter for arguments and return code.""" _cmdline_ = "ftrace" - _syntax_ = "{:s} , [, ...]".format(_cmdline_) + _syntax_ = "{:s} , [, ...]".format( + _cmdline_) + + def __init__(self) -> None: + super().__init__() + self["output"] = (PLUGIN_FTRACE_DEFAULT_OUTPUT, + "Path to store/load the syscall tables files") + return def do_invoke(self, args): if len(args) < 1: @@ -74,7 +97,7 @@ def do_invoke(self, args): gdb.events.exited.connect(self.cleanup) return - def cleanup(self, events): + def cleanup(self, _: gdb.ExitedEvent): for bp in self.bkps: if bp.retbp: bp.retbp.delete() diff --git a/scripts/gdb/__init__.pyi b/scripts/gdb/__init__.pyi new file mode 100644 index 0000000..faf6c96 --- /dev/null +++ b/scripts/gdb/__init__.pyi @@ -0,0 +1,744 @@ +from typing import (Callable, Dict, Iterator, List, Optional, Tuple, Union, + overload) + +PYTHONDIR: str + + +def execute(command, from_tty=False, to_string=False) -> Union[None, str]: ... +def breakpoints() -> List[Breakpoint]: ... +def rbreak(regex, minsyms=False, throttle=None, + symtabs: List[Symtab] = []) -> List[Breakpoint]: ... + + +def parameter(parameter: str): ... +def history(number: int) -> Value: ... +def convenience_variable(name: str) -> Union[None, Value]: ... +def set_convenience_variable(name: str, value) -> None: ... +def parse_and_eval(expression: str) -> Value: ... +def find_pc_line(pc) -> Symtab_and_line: ... +def post_event(event: Callable) -> None: ... + + +GdbStream = int + +STDOUT: GdbStream +STDERR: GdbStream +STDLOG: GdbStream + + +def write(string: str, stream: GdbStream = ...): ... +def flush(stream: GdbStream = ...): ... +def target_charset() -> str: ... +def target_wide_charset() -> str: ... +def solib_name(address: int) -> str: ... +def decode_line(expression: Optional[str] = None) -> Tuple[str, + Union[None, List[Symtab_and_line]]]: ... + + +def prompt_hook(current_prompt: Callable[[Callable], str]) -> str: ... + + +class error(RuntimeError): + ... + + +class MemoryError(error): + ... + + +class GdbError(Exception): + ... + + +class Value: + address: Optional[Value] + is_optimized_out: bool + type: Type + is_lazy: bool + def __init__(self, value, type: Optional[Type] = None): ... + + def cast(self, type: Type) -> Value: ... + def defererence(self) -> Value: ... + def referenced_value(self) -> Value: ... + def reference_value(self) -> Value: ... + def const_value(self) -> Value: ... + def dynamic_cast(self, type: Type) -> Value: ... + def reinterpret_cast(self, type: Type) -> Value: ... + def format_string(self, *args) -> str: ... + + def string(self, encoding: Optional[str] = None, errors=None, + length: Optional[int] = None) -> str: ... + + def lazy_string( + self, encoding: Optional[str] = None, length: Optional[int] = None) -> str: ... + + def fetch_lazy(self) -> None: ... + + def __abs__(self) -> int: ... + + +def lookup_type(name, block: Optional[Block] = None) -> Type: ... + + +class Type: + alignof: int + code: TypeCode + name: Optional[str] + sizeof: int + tag: Optional[str] + objfile: Optional[Objfile] + def fields(self) -> List[Field]: ... + def array(self, n1: int, n2: Optional[int] = None) -> Type: ... + def vector(self, n1: int, n2: Optional[int] = None) -> Type: ... + def const(self) -> Type: ... + def volatile(self) -> Type: ... + def unqualified(self) -> Type: ... + def range(self) -> Tuple[Type, Type]: ... + def reference(self) -> Type: ... + def pointer(self) -> Type: ... + def strip_typedefs(self) -> Type: ... + def target(self) -> Type: ... + def template_argument( + self, n: int, block: Optional[Block] = None) -> Union[Value, Type]: ... + + def optimized_out(self) -> Value: ... + + +class Field: + bitpos: int + enumval: Optional[int] + name: Optional[str] + artificial: bool + is_base_class: bool + bitsize: int + type: Optional[Type] + parent_type: Optional[Type] + + +TypeCode = int + +TYPE_CODE_PTR: TypeCode +TYPE_CODE_ARRAY: TypeCode +TYPE_CODE_STRUCT: TypeCode +TYPE_CODE_UNION: TypeCode +TYPE_CODE_ENUM: TypeCode +TYPE_CODE_FLAGS: TypeCode +TYPE_CODE_FUNC: TypeCode +TYPE_CODE_INT: TypeCode +TYPE_CODE_FLT: TypeCode +TYPE_CODE_VOID: TypeCode +TYPE_CODE_SET: TypeCode +TYPE_CODE_RANGE: TypeCode +TYPE_CODE_STRING: TypeCode +TYPE_CODE_BITSTRING: TypeCode +TYPE_CODE_ERROR: TypeCode +TYPE_CODE_METHOD: TypeCode +TYPE_CODE_METHODPTR: TypeCode +TYPE_CODE_MEMBERPTR: TypeCode +TYPE_CODE_REF: TypeCode +TYPE_CODE_RVALUE_REF: TypeCode +TYPE_CODE_CHAR: TypeCode +TYPE_CODE_BOOL: TypeCode +TYPE_CODE_COMPLEX: TypeCode +TYPE_CODE_TYPEDEF: TypeCode +TYPE_CODE_NAMESPACE: TypeCode +TYPE_CODE_DECFLOAT: TypeCode +TYPE_CODE_INTERNAL_FUNCTION: TypeCode + + +def default_visualizer(value: Value): ... + + +pretty_printers: List + + +class FrameFilter: + def filter(self, iterator: Iterator): ... + name: str + enabled: bool + priority: int + + +def inferiors() -> List[Inferior]: ... +def selected_inferior() -> Inferior: ... + + +class Inferior: + num: ... + pid: int + was_attached: bool + progspace: Progspace + def is_valid(self) -> bool: ... + def threads(self) -> List[InferiorThread]: ... + def architecture(self) -> Architecture: ... + def read_memory(self, address: int, length: int) -> memoryview: ... + + def write_memory(self, address: int, + buffer: Union[str, bytes], length: int) -> None: ... + def search_memory(self, address: int, length: int, + pattern: Union[str, bytes]) -> int: ... + + def thread_from_handle(self, handle) -> InferiorThread: ... + + +class Event(dict): + ... + + +class ThreadEvent: + inferior_thread: Optional[InferiorThread] + + +class ContinueEvent(ThreadEvent): + ... + + +class ExitedEvent: + exit_code: Optional[int] + inferior: Inferior + + +class StopEvent(ThreadEvent): + pass + + +class SignalEvent(StopEvent): + stop_signal: str + + +class BreakpointEvent(SignalEvent): + breakpoints: List[Breakpoint] + breakpoint: Breakpoint + + +class ClearObjFilesEvent: + progspace: Progspace + + +class InferiorCallPreEvent: + tpid: int + address: int + + +class InferiorCallPostEvent: + tpid: int + address: int + + +class MemoryChangedEvent: + address: int + lenth: int + + +class RegisterChangedEvent: + frame: Frame + regnum: int + + +class NewInferiorEvent: + inferior: Inferior + + +class InferiorDeletedEvent: + inferior: Inferior + + +class NewThreadEvent(ThreadEvent): + inferior_thread: InferiorThread + + +class NewObjFileEvent: + new_objfile: Objfile + + +class EventRegistry: + def connect(self, object) -> None: ... + def disconnect(self, object) -> None: ... + + +class __events: + class __cont(EventRegistry): + def connect(self, object: Callable[[ContinueEvent], None]) -> None: ... + def disconnect( + self, object: Callable[[ContinueEvent], None]) -> None: ... + cont: __cont + + class __exited(EventRegistry): + def connect(self, object: Callable[[ExitedEvent], None]) -> None: ... + def disconnect( + self, object: Callable[[ExitedEvent], None]) -> None: ... + exited: __exited + + class __stop(EventRegistry): + def connect(self, object: Callable[[ + Union[SignalEvent, BreakpointEvent]], None]) -> None: ... + def disconnect(self, object: Callable[[ + Union[SignalEvent, BreakpointEvent]], None]) -> None: ... + stop: __stop + + class __new_objfile(EventRegistry): + def connect( + self, object: Callable[[NewObjFileEvent], None]) -> None: ... + def disconnect( + self, object: Callable[[NewObjFileEvent], None]) -> None: ... + new_objfile: __new_objfile + + class __clear_objfiles(EventRegistry): + def connect( + self, object: Callable[[ClearObjFilesEvent], None]) -> None: ... + def disconnect( + self, object: Callable[[ClearObjFilesEvent], None]) -> None: ... + clear_objfiles: __clear_objfiles + + class __inferior_call(EventRegistry): + def connect(self, object: Callable[[ + Union[InferiorCallPreEvent, InferiorCallPostEvent]], None]) -> None: ... + def disconnect(self, object: Callable[[ + Union[InferiorCallPreEvent, InferiorCallPostEvent]], None]) -> None: ... + inferior_call: __inferior_call + + class __memory_changed(EventRegistry): + def connect( + self, object: Callable[[MemoryChangedEvent], None]) -> None: ... + def disconnect( + self, object: Callable[[MemoryChangedEvent], None]) -> None: ... + memory_changed: __memory_changed + + class __breakpoint(EventRegistry): + def connect(self, object: Callable[[Breakpoint], None]) -> None: ... + def disconnect(self, object: Callable[[Breakpoint], None]) -> None: ... + breakpoint_created: __breakpoint + breakpoint_modified: __breakpoint + breakpoint_deleted: __breakpoint + + class __before_prompt(EventRegistry): + def connect(self, object: Callable[[], None]) -> None: ... + def disconnect(self, object: Callable[[], None]) -> None: ... + before_prompt: __before_prompt + + class __new_inferior(EventRegistry): + def connect( + self, object: Callable[[NewInferiorEvent], None]) -> None: ... + def disconnect( + self, object: Callable[[NewInferiorEvent], None]) -> None: ... + new_inferior: __new_inferior + + class __inferior_deleted(EventRegistry): + def connect( + self, object: Callable[[InferiorDeletedEvent], None]) -> None: ... + def disconnect( + self, object: Callable[[InferiorDeletedEvent], None]) -> None: ... + inferior_deleted: __inferior_deleted + + class __new_thread(EventRegistry): + def connect( + self, object: Callable[[NewThreadEvent], None]) -> None: ... + def disconnect( + self, object: Callable[[NewThreadEvent], None]) -> None: ... + new_thread: __new_thread + + +events: __events + + +def selected_thread() -> InferiorThread: ... + + +class InferiorThread: + name: Optional[str] + num: int + global_num: int + ptid: int + inferior: Inferior + def is_valid(self) -> bool: ... + def switch(self) -> None: ... + def is_stopped(self) -> bool: ... + def is_running(self) -> bool: ... + def is_exited(self) -> bool: ... + def handle(self) -> bytes: ... + + +def start_recording( + method: Optional[str] = None, format: Optional[str] = None) -> Record: ... + + +def current_recording() -> Optional[Record]: ... +def stop_recording() -> None: ... + + +class Record: + method: str + format: str + begin: Instruction + end: Instruction + replay_position: Optional[Instruction] + instruction_history: List[Instruction] + function_call_history: List[RecordFunctionSegment] + def goto(self, instruction: Instruction) -> None: ... + + +class Instruction: + pc: int + data: memoryview + decoded: str + size: int + + +class RecordInstruction(Instruction): + number: int + sal: Symtab_and_line + is_speculative: bool + + +class RecordGap: + number: int + error_code: int + error_string: str + + +class RecordFunctionSegment: + number: int + symbol: Symbol + level: Optional[int] + instructions: List[Union[RecordInstruction, RecordGap]] + up: Optional[RecordFunctionSegment] + prev: Optional[RecordFunctionSegment] + next: Optional[RecordFunctionSegment] + + +class Command: + repeat_count: int + def __init__(self, name: str, command_class: Optional[CommandClass] = None, + completer_class: Optional[CompleteClass] = None, prefix: Optional[bool] = None): ... + + def dont_repeat(self) -> None: ... + def invoke(self, argument: str, from_tty: bool): ... + def complete(self, text: str, word: str): ... + + +CommandClass = int + +COMMAND_NONE: CommandClass +COMMAND_RUNNING: CommandClass +COMMAND_DATA: CommandClass +COMMAND_STACK: CommandClass +COMMAND_FILES: CommandClass +COMMAND_SUPPORT: CommandClass +COMMAND_STATUS: CommandClass +COMMAND_BREAKPOINTS: CommandClass +COMMAND_TRACEPOINTS: CommandClass +COMMAND_USER: CommandClass +COMMAND_OBSCURE: CommandClass +COMMAND_MAINTENANCE: CommandClass + + +CompleteClass = int + +COMPLETE_NONE: CompleteClass +COMPLETE_FILENAME: CompleteClass +COMPLETE_LOCATION: CompleteClass +COMPLETE_COMMAND: CompleteClass +COMPLETE_SYMBOL: CompleteClass +COMPLETE_EXPRESSION: CompleteClass + + +class Parameter: + def __init__(self, name: str, command_class: CommandClass, + parameter_class: __parameter_class, enum_sequence: Optional[List[str]] = None): ... + set_doc: str + show_doc: str + value: ... + + def get_set_string(self) -> None: ... + def get_show_string(self, svalue: str) -> None: ... + + +class __parameter_class: + pass + + +PARAM_BOOLEAN: __parameter_class +PARAM_AUTO_BOOLEAN: __parameter_class +PARAM_UINTEGER: __parameter_class +PARAM_INTEGER: __parameter_class +PARAM_STRING: __parameter_class +PARAM_STRING_NOESCAPE: __parameter_class +PARAM_OPTIONAL_FILENAME: __parameter_class +PARAM_FILENAME: __parameter_class +PARAM_ZINTEGER: __parameter_class +PARAM_ZUINTEGER: __parameter_class +PARAM_ZUINTEGER_UNLIMITED: __parameter_class +PARAM_ENUM: __parameter_class + + +class Function: + def __init__(self, name: str): ... + def invoke(self, *args): ... + + +def current_progspace() -> Progspace: ... +def progspaces() -> List[Progspace]: ... + + +class Progspace: + filename: str + pretty_printers: list + type_printers: list + frame_filters: Dict[str, FrameFilter] + def block_for_pc(self, pc: int) -> Optional[Block]: ... + def find_pc_line(self, pc: int) -> Optional[Symtab_and_line]: ... + def is_valid(self) -> bool: ... + def objfiles(self) -> List[Objfile]: ... + def solid_name(self, address: int) -> str: ... + + +def current_objfile() -> Objfile: ... +def objfiles() -> List[Objfile]: ... + + +def lookup_objfile(name: Union[str, int], + by_build_id: Optional[bool] = None) -> Objfile: ... + + +class Objfile: + filename: Optional[str] + username: Optional[str] + owner: Optional[Objfile] + build_id: Optional[str] + progspace: Progspace + pretty_printers: list + type_printers: list + frame_filters: List[FrameFilter] + def is_valid(self) -> bool: ... + def add_separate_debug_file(self, file: str) -> None: ... + + def lookup_global_symbol( + self, name: str, domain: Optional[str] = None) -> Optional[Symbol]: ... + def lookup_static_symbol( + self, name: str, domain: Optional[str] = None) -> Optional[Symbol]: ... + + +def selected_frame() -> Frame: ... +def newest_frame() -> Frame: ... +def invalidate_cached_frames() -> None: ... + + +class Frame: + def is_valid(self) -> bool: ... + def name(self) -> str: ... + def architecture(self) -> Architecture: ... + def type(self) -> FrameType: ... + def unwind_stop_reason(self) -> FrameUnwindStopReason: ... + def pc(self) -> int: ... + def block(self) -> Block: ... + def function(self) -> Symbol: ... + def older(self) -> Frame: ... + def newer(self) -> Frame: ... + def find_sal(self) -> Symtab_and_line: ... + def read_register(self, register: str) -> Value: ... + def read_var(self, variable: Union[Symbol, str], + block: Optional[Block] = None) -> Symbol: ... + + def select(self) -> None: ... + + +FrameType = int + +NORMAL_FRAME: FrameType +DUMMY_FRAME: FrameType +INLINE_FRAME: FrameType +TAILCALL_FRAME: FrameType +SIGTRAMP_FRAME: FrameType +ARCH_FRAME: FrameType +SENTINEL_FRAME: FrameType + + +FrameUnwindStopReason = int + +FRAME_UNWIND_NO_REASON: FrameUnwindStopReason +FRAME_UNWIND_NULL_ID: FrameUnwindStopReason +FRAME_UNWIND_OUTERMOST: FrameUnwindStopReason +FRAME_UNWIND_UNAVAILABLE: FrameUnwindStopReason +FRAME_UNWIND_INNER_ID: FrameUnwindStopReason +FRAME_UNWIND_SAME_ID: FrameUnwindStopReason +FRAME_UNWIND_NO_SAVED_PC: FrameUnwindStopReason +FRAME_UNWIND_MEMORY_ERROR: FrameUnwindStopReason +FRAME_UNWIND_FIRST_ERROR: FrameUnwindStopReason + + +def frame_stop_reason_string(reason: FrameUnwindStopReason) -> str: ... + + +def block_for_pc(pc: int) -> Block: ... + + +class Block: + def is_valid(self) -> bool: ... + def __getitem__(self, idx: int) -> Symbol: ... + start: int + end: int + function: Symbol + superblock: Optional[Block] + global_block: Block + static_block: Block + is_global: bool + is_static: bool + + +def lookup_symbol(name: str, block: Optional[Block] = None, + domain: Optional[DomainCategory] = None) -> Symbol: ... + + +def lookup_global_symbol( + name: str, domain: Optional[DomainCategory] = None) -> Symbol: ... + + +def lookup_static_symbol( + name: str, domain: Optional[DomainCategory] = None) -> Symbol: ... + + +class Symbol: + type: Optional[Type] + symtab: Symtab + line: int + name: str + linkage_name: str + print_name: str + addr_class: SymbolAddress + needs_frame: bool + is_argument: bool + is_constant: bool + is_function: bool + is_variable: bool + + def is_valid(self) -> bool: ... + def value(self, frame: Optional[Frame] = None) -> Value: ... + + +DomainCategory = int + +SYMBOL_UNDEF_DOMAIN: DomainCategory +SYMBOL_VAR_DOMAIN: DomainCategory +SYMBOL_STRUCT_DOMAIN: DomainCategory +SYMBOL_LABEL_DOMAIN: DomainCategory +SYMBOL_MODULE_DOMAIN: DomainCategory +SYMBOL_COMMON_BLOCK_DOMAIN: DomainCategory + + +SymbolAddress = int + +SYMBOL_LOC_UNDEF: SymbolAddress +SYMBOL_LOC_CONST: SymbolAddress +SYMBOL_LOC_STATIC: SymbolAddress +SYMBOL_LOC_REGISTER: SymbolAddress +SYMBOL_LOC_ARG: SymbolAddress +SYMBOL_LOC_REF_ARG: SymbolAddress +SYMBOL_LOC_REGPARM_ADDR: SymbolAddress +SYMBOL_LOC_LOCAL: SymbolAddress +SYMBOL_LOC_TYPEDEF: SymbolAddress +SYMBOL_LOC_BLOCK: SymbolAddress +SYMBOL_LOC_CONST_BYTES: SymbolAddress +SYMBOL_LOC_UNRESOLVED: SymbolAddress +SYMBOL_LOC_OPTIMIZED_OUT: SymbolAddress +SYMBOL_LOC_COMPUTED: SymbolAddress +SYMBOL_LOC_COMPUTED: SymbolAddress + + +class Symtab_and_line: + symtab: Symtab + pc: int + last: int + line: int + def is_valid(self) -> bool: ... + + +class Symtab: + filename: str + objfile: Objfile + producer: str + def is_valid(self) -> bool: ... + def fullname(self) -> str: ... + + def global_block(self) -> Block: ... + def static_block(self) -> Block: ... + def linetable(self) -> LineTableEntry: ... + + +class LineTableEntry: + line: int + pc: int + + +class LineTable: + def line(self, line: int) -> List[LineTableEntry]: ... + def has_line(self, line: int) -> bool: ... + def source_lines(self) -> List[int]: ... + + +BreakpointType = int + +BP_BREAKPOINT: BreakpointType +BP_WATCHPOINT: BreakpointType +BP_HARDWARE_WATCHPOINT: BreakpointType +BP_READ_WATCHPOINT: BreakpointType +BP_ACCESS_WATCHPOINT: BreakpointType + + +WatchpointType = int + +WP_READ: WatchpointType +WP_WRITE: WatchpointType +WP_ACCESS: WatchpointType + + +class Breakpoint: + @overload + def __init__(self, spec: str, type: Optional[BreakpointType] = None, wp_class: Optional[WatchpointType] = ..., + internal: bool = False, temporary: bool = False, qualified: bool = False): ... + + @overload + def __init__(self, source: Optional[str] = None, function: Optional[str] = None, label: Optional[str] = None, line: Optional[int] = None, + internal: bool = False, temporary: bool = False, qualified: bool = False): ... + + def stop(self) -> None: ... + + def is_valid(self) -> bool: ... + def delete(self) -> None: ... + enabled: bool + silent: bool + pending: bool + + thread: Optional[int] + task: Optional[int] + ignore_count: int + number: int + type: BreakpointType + visible: bool + temporary: bool + location: Optional[str] + expression: Optional[str] + condition: Optional[str] + commands: Optional[str] + + +class FinishBreakpoint: + def __init__(self, frame: Optional[Frame] + = None, internal: bool = False): ... + + def out_of_scope(self): ... + return_value: Optional[Value] + + +class LazyString: + def value(self) -> Value: ... + address: int + length: int + encoding: str + type: Type + + +class Architecture: + def name(self) -> str: ... + + def disassemble(self, start_pc: int, end_pc: Optional[int] = None, + count: Optional[int] = None) -> dict: ... diff --git a/scripts/ida_interact.py b/scripts/ida_interact.py index 3df08d0..bb29dc0 100644 --- a/scripts/ida_interact.py +++ b/scripts/ida_interact.py @@ -1,13 +1,23 @@ +""" + +Script to control headlessly IDA from GEF using RPyC + +""" + import functools -from typing import Any, List, Set, Dict, Optional +import pprint +from typing import TYPE_CHECKING, Any, List, Set, Dict, Optional + import gdb import rpyc -import pprint +if TYPE_CHECKING: + from . import * + from . import gdb __AUTHOR__ = "hugsy" __VERSION__ = 0.2 -__DESCRIPTION_ = """Control headlessly IDA from GEF using RPyC""" + class RemoteDecompilerSession: sock: Optional[int] = None @@ -66,7 +76,8 @@ def is_current_elf_pie(): def get_rva(addr): - base_address = [x.page_start for x in gef.memory.maps if x.path == get_filepath()][0] + base_address = [ + x.page_start for x in gef.memory.maps if x.path == get_filepath()][0] return addr - base_address @@ -89,15 +100,17 @@ def wrapper(*args, **kwargs): return wrapper +@register class RpycIdaCommand(GenericCommand): """RPyCIda root command""" _cmdline_ = "ida-rpyc" - _syntax_ = "{:s} (breakpoints|comments|info|highlight|jump)".format(_cmdline_) + _syntax_ = "{:s} (breakpoints|comments|info|highlight|jump)".format( + _cmdline_) _example_ = "{:s}".format(_cmdline_) def __init__(self): global sess - super(RpycIdaCommand, self).__init__(prefix=True) + super().__init__(prefix=True) self["host"] = ("127.0.0.1", "IDA host IP address") self["port"] = (18812, "IDA host port") self["sync_cursor"] = (False, "Enable real-time $pc synchronisation") @@ -119,8 +132,10 @@ def synchronize(self): """Submit all active breakpoint addresses to IDA/BN.""" pc = gef.arch.pc vmmap = gef.memory.maps - base_address = min([x.page_start for x in vmmap if x.path == get_filepath()]) - end_address = max([x.page_end for x in vmmap if x.path == get_filepath()]) + 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): return @@ -133,6 +148,7 @@ def synchronize(self): return +@register class RpycIdaHighlightCommand(RpycIdaCommand): """RPyC IDA: highlight root command""" _cmdline_ = "ida-rpyc highlight" @@ -141,7 +157,8 @@ class RpycIdaHighlightCommand(RpycIdaCommand): _example_ = "{:s}".format(_cmdline_) def __init__(self): - super(RpycIdaCommand, self).__init__(prefix=True) #pylint: disable=bad-super-call + super(RpycIdaCommand, self).__init__( + prefix=True) # pylint: disable=bad-super-call return @only_if_gdb_running @@ -150,6 +167,7 @@ def do_invoke(self, argv): pass +@register class RpycIdaHighlightAddCommand(RpycIdaHighlightCommand): """RPyC IDA: highlight a specific line in the IDB""" _cmdline_ = "ida-rpyc highlight add" @@ -158,7 +176,8 @@ class RpycIdaHighlightAddCommand(RpycIdaHighlightCommand): _example_ = "{:s} main".format(_cmdline_) def __init__(self): - super(RpycIdaCommand, self).__init__(complete=gdb.COMPLETE_SYMBOL) #pylint: disable=bad-super-call + super(RpycIdaCommand, self).__init__( + complete=gdb.COMPLETE_SYMBOL) # pylint: disable=bad-super-call return @only_if_gdb_running @@ -167,7 +186,8 @@ def __init__(self): def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = kwargs["arguments"] ea = parse_address(args.location) - if is_current_elf_pie(): ea = get_rva(ea) + if is_current_elf_pie(): + ea = get_rva(ea) color = args.color ok("highlight ea={:#x} as {:#x}".format(ea, color)) sess.old_colors[ea] = sess.idc.get_color(ea, sess.idc.CIC_ITEM) @@ -175,6 +195,7 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: return +@register class RpycIdaHighlightDeleteCommand(RpycIdaHighlightCommand): """RPyC IDA: remove the highlighting of the given line in the IDB""" _cmdline_ = "ida-rpyc highlight del" @@ -183,16 +204,18 @@ class RpycIdaHighlightDeleteCommand(RpycIdaHighlightCommand): _example_ = "{:s} main".format(_cmdline_) def __init__(self): - super(RpycIdaCommand, self).__init__(complete=gdb.COMPLETE_SYMBOL) #pylint: disable=bad-super-call + super(RpycIdaCommand, self).__init__( + complete=gdb.COMPLETE_SYMBOL) # pylint: disable=bad-super-call return @only_if_gdb_running @only_if_active_rpyc_session - @parse_arguments({"location": "$pc",}, {}) + @parse_arguments({"location": "$pc", }, {}) def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = kwargs["arguments"] ea = parse_address(args.location) - if is_current_elf_pie(): ea = get_rva(ea) + if is_current_elf_pie(): + ea = get_rva(ea) if ea not in sess.old_colors: warn("{:#x} was not highlighted".format(ea)) @@ -204,6 +227,7 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: return +@register class RpycIdaBreakpointCommand(RpycIdaCommand): """RPyC IDA: breakpoint root command""" _cmdline_ = "ida-rpyc breakpoints" @@ -212,7 +236,8 @@ class RpycIdaBreakpointCommand(RpycIdaCommand): _example_ = "{:s}".format(_cmdline_) def __init__(self): - super(RpycIdaCommand, self).__init__(prefix=True) #pylint: disable=bad-super-call + super(RpycIdaCommand, self).__init__( + prefix=True) # pylint: disable=bad-super-call return @only_if_gdb_running @@ -221,6 +246,7 @@ def do_invoke(self, _): pass +@register class RpycIdaBreakpointListCommand(RpycIdaBreakpointCommand): """RPyC IDA: breakpoint list command""" _cmdline_ = "ida-rpyc breakpoints list" @@ -229,7 +255,8 @@ class RpycIdaBreakpointListCommand(RpycIdaBreakpointCommand): _example_ = "{:s}".format(_cmdline_) def __init__(self): - super(RpycIdaBreakpointCommand, self).__init__() #pylint: disable=bad-super-call + super(RpycIdaBreakpointCommand, self).__init__( + ) # pylint: disable=bad-super-call return @only_if_gdb_running @@ -241,6 +268,7 @@ def do_invoke(self, argv): return +@register class RpycIdaInfoSessionCommand(RpycIdaCommand): """RPyC IDA: display info about the current session""" _cmdline_ = "ida-rpyc info" @@ -249,7 +277,8 @@ class RpycIdaInfoSessionCommand(RpycIdaCommand): _example_ = "{:s}".format(_cmdline_) def __init__(self): - super(RpycIdaCommand, self).__init__() #pylint: disable=bad-super-call + super(RpycIdaCommand, self).__init__( + ) # pylint: disable=bad-super-call return @only_if_gdb_running @@ -259,6 +288,7 @@ def do_invoke(self, argv): return +@register class RpycIdaJumpCommand(RpycIdaCommand): """RPyC IDA: display info about the current session""" _cmdline_ = "ida-rpyc jump" @@ -267,7 +297,8 @@ class RpycIdaJumpCommand(RpycIdaCommand): _example_ = "{:s} main".format(_cmdline_) def __init__(self): - super(RpycIdaCommand, self).__init__(complete=gdb.COMPLETE_SYMBOL) #pylint: disable=bad-super-call + super(RpycIdaCommand, self).__init__( + complete=gdb.COMPLETE_SYMBOL) # pylint: disable=bad-super-call return @only_if_gdb_running @@ -276,11 +307,13 @@ def __init__(self): def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = kwargs["arguments"] ea = parse_address(args.location) - if is_current_elf_pie(): ea = get_rva(ea) + if is_current_elf_pie(): + ea = get_rva(ea) sess.idaapi.jumpto(ea) return +@register class RpycIdaCommentCommand(RpycIdaCommand): """RPyCIda comment root command""" _cmdline_ = "ida-rpyc comments" @@ -289,7 +322,8 @@ class RpycIdaCommentCommand(RpycIdaCommand): _example_ = "{:s}".format(_cmdline_) def __init__(self): - super(RpycIdaCommand, self).__init__(prefix=True) #pylint: disable=bad-super-call + super(RpycIdaCommand, self).__init__( + prefix=True) # pylint: disable=bad-super-call return @only_if_gdb_running @@ -298,15 +332,18 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: pass +@register class RpycIdaCommentAddCommand(RpycIdaCommentCommand): """RPyCIda add comment command""" _cmdline_ = "ida-rpyc comments add" - _syntax_ = "{:s} \"My comment\" --location [*0xaddress|register|symbol]".format(_cmdline_) + _syntax_ = "{:s} \"My comment\" --location [*0xaddress|register|symbol]".format( + _cmdline_) _aliases_ = [] _example_ = "{:s} \"I was here\" --location $pc".format(_cmdline_) def __init__(self): - super(RpycIdaCommand, self).__init__(complete=gdb.COMPLETE_SYMBOL) #pylint: disable=bad-super-call + super(RpycIdaCommand, self).__init__( + complete=gdb.COMPLETE_SYMBOL) # pylint: disable=bad-super-call return @only_if_gdb_running @@ -315,13 +352,15 @@ def __init__(self): def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = kwargs["arguments"] ea = parse_address(args.location) - if is_current_elf_pie(): ea = get_rva(ea) + if is_current_elf_pie(): + ea = get_rva(ea) comment = args.comment repeatable_comment = 1 sess.idc.set_cmt(ea, comment, repeatable_comment) return +@register class RpycIdaCommentDeleteCommand(RpycIdaCommentCommand): """RPyCIda delete comment command""" _cmdline_ = "ida-rpyc comments del" @@ -330,7 +369,8 @@ class RpycIdaCommentDeleteCommand(RpycIdaCommentCommand): _example_ = "{:s} $pc".format(_cmdline_) def __init__(self): - super(RpycIdaCommand, self).__init__(complete=gdb.COMPLETE_SYMBOL) #pylint: disable=bad-super-call + super(RpycIdaCommand, self).__init__( + complete=gdb.COMPLETE_SYMBOL) # pylint: disable=bad-super-call return @only_if_gdb_running @@ -339,26 +379,8 @@ def __init__(self): def do_invoke(self, _: List[str], **kwargs: Any) -> None: args = kwargs["arguments"] ea = parse_address(args.location) - if is_current_elf_pie(): ea = get_rva(ea) + if is_current_elf_pie(): + ea = get_rva(ea) repeatable_comment = 1 sess.idc.set_cmt(ea, "", repeatable_comment) return - - -if __name__ == "__main__": - cmds = [ - RpycIdaCommand, - RpycIdaInfoSessionCommand, - RpycIdaJumpCommand, - RpycIdaBreakpointCommand, - RpycIdaBreakpointListCommand, - RpycIdaCommentCommand, - RpycIdaCommentAddCommand, - RpycIdaCommentDeleteCommand, - RpycIdaHighlightCommand, - RpycIdaHighlightAddCommand, - RpycIdaHighlightDeleteCommand, - ] - - for cmd in cmds: - register_external_command(cmd) diff --git a/scripts/peekpointers.py b/scripts/peekpointers.py index 1c02ad5..1480577 100644 --- a/scripts/peekpointers.py +++ b/scripts/peekpointers.py @@ -1,13 +1,25 @@ -@register_external_command + +__AUTHOR__ = "bkth" +__VERSION__ = 0.2 +__LICENSE__ = "MIT" + +import pathlib +from typing import TYPE_CHECKING, List + +if TYPE_CHECKING: + from . import * + + +@register class PeekPointers(GenericCommand): """Command to help find pointers belonging to other memory regions helpful in case of OOB Read when looking for specific pointers""" _cmdline_ = "peek-pointers" - _syntax_ = "{:s} starting_address ".format(_cmdline_) + _syntax_ = f"{_cmdline_} starting_address " @only_if_gdb_running - def do_invoke(self, argv): + def do_invoke(self, argv: List[str]): argc = len(argv) if argc not in (1, 2, 3): self.usage() @@ -19,23 +31,26 @@ def do_invoke(self, argv): return unique = True if "all" not in argv else False - vmmap = get_process_maps() + vmmap = gef.memory.maps if argc >= 2: section_name = argv[1].lower() if section_name == "stack": - sections = [(s.path, s.page_start, s.page_end) for s in vmmap if s.path == "[stack]"] + sections = [(s.path, s.page_start, s.page_end) + for s in vmmap if s.path == "[stack]"] elif section_name == "heap": - sections = [(s.path, s.page_start, s.page_end) for s in vmmap if s.path == "[heap]"] + sections = [(s.path, s.page_start, s.page_end) + for s in vmmap if s.path == "[heap]"] elif section_name != "all": - sections = [(s.path, s.page_start, s.page_end) for s in vmmap if section_name in s.path] + sections = [(s.path, s.page_start, s.page_end) + for s in vmmap if section_name in s.path] else: sections = [(s.path, s.page_start, s.page_end) for s in vmmap] else: sections = [(s.path, s.page_start, s.page_end) for s in vmmap] while addr.valid: - addr_value = read_int_from_memory(addr.value) + addr_value = gef.memory.read_integer(addr.value) if lookup_address(addr_value): for i, section in enumerate(sections): @@ -44,15 +59,16 @@ def do_invoke(self, argv): sym = gdb_get_location_from_symbol(addr_value) sym = "<{:s}+{:04x}>".format(*sym) if sym else '' if name.startswith("/"): - name = os.path.basename(name) + name = pathlib.Path(name) elif not name: - name = get_filename() + name = gef.session.file - ok(" Found pointer at 0x{:x} to 0x{:x} {:s} ('{:s}', perm: {:s})".format(addr.value, addr_value, sym, name, str(addr.section.permission), )) + msg = f" Found pointer at 0x{addr.value:x} to 0x{addr_value:x} {sym} ('{name}', perm: {str(addr.section.permission)})" + ok(msg) if unique: del sections[i] break - addr = lookup_address(addr.value + current_arch.ptrsize) + addr = lookup_address(addr.value + gef.arch.ptrsize) return diff --git a/scripts/remote.py b/scripts/remote.py index 7cb46f9..aa869e6 100644 --- a/scripts/remote.py +++ b/scripts/remote.py @@ -4,22 +4,27 @@ gdb -ex 'source /path/to/gef-extras/scripts/remote.py' -ex rpyc-remote -ex quit """ -from typing import Any -import rpyc -import gdb -import sys import contextlib +import sys +from typing import TYPE_CHECKING, Any + +import gdb +import rpyc + +if TYPE_CHECKING: + from . import * + from . import gdb __AUTHOR__ = "hugsy" -__VERSION__ = 0.1 +__VERSION__ = 0.2 class GefRemoteService(rpyc.Service): """The RPYC service for interacting with GEF""" def exposed_gdb(self, cmd: str) -> str: - return gdb.execute(cmd, to_string=True) + return gdb.execute(cmd, to_string=True) or "" def exposed_gef(self, cmd: str) -> Any: return eval(cmd) @@ -27,6 +32,7 @@ def exposed_gef(self, cmd: str) -> Any: class DisableStreamBufferContext(contextlib.ContextDecorator): """Because stream buffering doesn't play well with rpyc""" + def __enter__(self) -> None: info("Backuping context") self.old_stream_buffer = gef.ui.stream_buffer @@ -42,13 +48,13 @@ def __exit__(self, _) -> bool: return False -@register_external_command +@register class GefRemoteCommand(GenericCommand): """A better way of remoting to GDB, using rpyc""" _cmdline_ = "rpyc-remote" _aliases_ = [] - _syntax_ = f"{_cmdline_:s}" + _syntax_ = f"{_cmdline_:s}" _example_ = f"{_cmdline_:s}" def __init__(self) -> None: @@ -59,8 +65,10 @@ def __init__(self) -> None: def do_invoke(self, _) -> None: with DisableStreamBufferContext(): - info(f"Listening on {self['host']}:{self['port']}, press Ctrl+C to stop") - server = rpyc.utils.server.ThreadedServer(GefRemoteService, port=12345) + info( + f"Listening on {self['host']}:{self['port']}, press Ctrl+C to stop") + server = rpyc.utils.server.ThreadedServer( + GefRemoteService, port=12345) try: server.start() except KeyboardInterrupt: diff --git a/scripts/retdec.py b/scripts/retdec.py index a7becee..9ce650e 100644 --- a/scripts/retdec.py +++ b/scripts/retdec.py @@ -11,7 +11,7 @@ from pygments.lexers import CLexer from pygments.formatters import Terminal256Formatter -@register_external_command +@register class RetDecCommand(GenericCommand): """Decompile code from GDB context using RetDec API.""" diff --git a/scripts/skel.py b/scripts/skel.py index 9bddd36..6939831 100644 --- a/scripts/skel.py +++ b/scripts/skel.py @@ -1,10 +1,15 @@ __AUTHOR__ = "hugsy" -__VERSION__ = 0.3 +__VERSION__ = 0.4 import os import tempfile +from typing import TYPE_CHECKING +import gdb -TEMPLATE="""#!/usr/bin/env python3 +if TYPE_CHECKING: + from gef import * + +TEMPLATE = """#!/usr/bin/env python3 import sys, os from pwn import * context.update( @@ -44,11 +49,11 @@ def exploit(r): """ -@register_external_command +@register class ExploitTemplateCommand(GenericCommand): """Generates a exploit template.""" _cmdline_ = "exploit-template" - _syntax_ = "{:s} [local|remote TARGET:PORT]".format(_cmdline_) + _syntax_ = f"{_cmdline_} [local|remote TARGET:PORT]" _aliases_ = ["skeleton", ] @only_if_gdb_running @@ -77,7 +82,7 @@ def do_invoke(self, args): port=port, arch="amd64" if "x86-64" in gef.arch.arch else "i386", endian="big" if gef.arch.endianness == Endianness.BIG_ENDIAN else "little", - filepath=get_filepath(), + filepath=gef.file.path, bkps=bkps ) fd, fname = tempfile.mkstemp(suffix='.py', prefix='gef_') @@ -86,4 +91,3 @@ def do_invoke(self, args): ok("Exploit generated in '{:s}'".format(fname)) return - diff --git a/scripts/stack.py b/scripts/stack.py index 9b01887..01bc813 100644 --- a/scripts/stack.py +++ b/scripts/stack.py @@ -2,7 +2,7 @@ __VERSION__ = 0.1 -@register_external_command +@register class CurrentFrameStack(GenericCommand): """Show the entire stack of the current frame.""" _cmdline_ = "current-stack-frame" diff --git a/scripts/trinity/__init__.py b/scripts/trinity/__init__.py deleted file mode 100644 index 4368b7e..0000000 --- a/scripts/trinity/__init__.py +++ /dev/null @@ -1,46 +0,0 @@ - - -def get_generic_arch(module: ModuleType, prefix: str, arch: str, mode: Optional[str], big_endian: Optional[bool], to_string: bool = False) -> Tuple[str, Union[int, str]]: - """ - Retrieves architecture and mode from the arguments for use for the holy - {cap,key}stone/unicorn trinity. - """ - if to_string: - arch = f"{module.__name__}.{prefix}_ARCH_{arch}" - if mode: - mode = f"{module.__name__}.{prefix}_MODE_{mode}" - else: - mode = "" - if gef.arch.endianness == Endianness.BIG_ENDIAN: - mode += f" + {module.__name__}.{prefix}_MODE_BIG_ENDIAN" - else: - mode += f" + {module.__name__}.{prefix}_MODE_LITTLE_ENDIAN" - - else: - arch = getattr(module, f"{prefix}_ARCH_{arch}") - if mode: - mode = getattr(module, f"{prefix}_MODE_{mode}") - else: - mode = 0 - if big_endian: - mode |= getattr(module, f"{prefix}_MODE_BIG_ENDIAN") - else: - mode |= getattr(module, f"{prefix}_MODE_LITTLE_ENDIAN") - - return arch, mode - - -def get_generic_running_arch(module: ModuleType, prefix: str, to_string: bool = False) -> Union[Tuple[None, None], Tuple[str, Union[int, str]]]: - """ - Retrieves architecture and mode from the current context. - """ - - if not is_alive(): - return None, None - - if gef.arch is not None: - arch, mode = gef.arch.arch, gef.arch.mode - else: - raise OSError("Emulation not supported for your OS") - - return get_generic_arch(module, prefix, arch, mode, gef.arch.endianness == Endianness.BIG_ENDIAN, to_string) diff --git a/scripts/trinity/capstone.py b/scripts/trinity/capstone.py deleted file mode 100644 index c6e14db..0000000 --- a/scripts/trinity/capstone.py +++ /dev/null @@ -1,145 +0,0 @@ -import capstone - -import sys - -def disassemble(location: int, nb_insn: int, **kwargs: Any) -> Generator[Instruction, None, None]: - """Disassemble `nb_insn` instructions after `addr` and `nb_prev` before - `addr` using the Capstone-Engine disassembler, if available. - Return an iterator of Instruction objects.""" - - def cs_insn_to_gef_insn(cs_insn: "capstone.CsInsn") -> Instruction: - sym_info = gdb_get_location_from_symbol(cs_insn.address) - loc = "<{}+{}>".format(*sym_info) if sym_info else "" - ops = [] + cs_insn.op_str.split(", ") - return Instruction(cs_insn.address, loc, cs_insn.mnemonic, ops, cs_insn.bytes) - - capstone = sys.modules["capstone"] - arch, mode = get_capstone_arch(arch=kwargs.get("arch"), mode=kwargs.get("mode"), endian=kwargs.get("endian")) - cs = capstone.Cs(arch, mode) - cs.detail = True - - page_start = align_address_to_page(location) - offset = location - page_start - pc = gef.arch.pc - - skip = int(kwargs.get("skip", 0)) - nb_prev = int(kwargs.get("nb_prev", 0)) - if nb_prev > 0: - location = gdb_get_nth_previous_instruction_address(pc, nb_prev) - nb_insn += nb_prev - - code = kwargs.get("code", gef.memory.read(location, gef.session.pagesize - offset - 1)) - for insn in cs.disasm(code, location): - if skip: - skip -= 1 - continue - nb_insn -= 1 - yield cs_insn_to_gef_insn(insn) - if nb_insn == 0: - break - return - - -def get_arch(arch: Optional[str] = None, mode: Optional[str] = None, endian: Optional[bool] = None, to_string: bool = False) -> Union[Tuple[None, None], Tuple[str, Union[int, str]]]: - capstone = sys.modules["capstone"] - - # hacky patch to unify capstone/ppc syntax with keystone & unicorn: - # CS_MODE_PPC32 does not exist (but UC_MODE_32 & KS_MODE_32 do) - if is_arch(Elf.Abi.POWERPC64): - raise OSError("Capstone not supported for PPC64 yet.") - - if is_alive() and is_arch(Elf.Abi.POWERPC): - - arch = "PPC" - mode = "32" - endian = (gef.arch.endianness == Endianness.BIG_ENDIAN) - return get_generic_arch(capstone, "CS", - arch or gef.arch.arch, - mode or gef.arch.mode, - endian, - to_string) - - if (arch, mode, endian) == (None, None, None): - return get_generic_running_arch(capstone, "CS", to_string) - return get_generic_arch(capstone, "CS", - arch or gef.arch.arch, - mode or gef.arch.mode, - endian or gef.arch.endianness == Endianness.BIG_ENDIAN, - to_string) - - -@register -class CapstoneDisassembleCommand(GenericCommand): - """Use capstone disassembly framework to disassemble code.""" - - _cmdline_ = "capstone-disassemble" - _syntax_ = f"{_cmdline_} [-h] [--show-opcodes] [--length LENGTH] [LOCATION]" - _aliases_ = ["cs-dis"] - _example_ = f"{_cmdline_} --length 50 $pc" - - def pre_load(self) -> None: - try: - __import__("capstone") - except ImportError: - msg = "Missing `capstone` package for Python. Install with `pip install capstone`." - raise ImportWarning(msg) - return - - def __init__(self) -> None: - super().__init__(complete=gdb.COMPLETE_LOCATION) - return - - @only_if_gdb_running - @parse_arguments({("location"): "$pc"}, {("--show-opcodes", "-s"): True, "--length": 0}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: - args = kwargs["arguments"] - show_opcodes = args.show_opcodes - length = args.length or gef.config["context.nb_lines_code"] - location = parse_address(args.location) - if not location: - info(f"Can't find address for {args.location}") - return - - insns = [] - opcodes_len = 0 - for insn in capstone_disassemble(location, length, skip=length * self.repeat_count, **kwargs): - insns.append(insn) - opcodes_len = max(opcodes_len, len(insn.opcodes)) - - for insn in insns: - insn_fmt = f"{{:{opcodes_len}o}}" if show_opcodes else "{}" - text_insn = insn_fmt.format(insn) - msg = "" - - if insn.address == gef.arch.pc: - msg = Color.colorify(f"{RIGHT_ARROW} {text_insn}", "bold red") - valid, reason = self.capstone_analyze_pc(insn, length) - if valid: - gef_print(msg) - gef_print(reason) - break - else: - msg = f" {text_insn}" - - gef_print(msg) - return - - def capstone_analyze_pc(self, insn: Instruction, nb_insn: int) -> Tuple[bool, str]: - if gef.arch.is_conditional_branch(insn): - is_taken, reason = gef.arch.is_branch_taken(insn) - if is_taken: - reason = f"[Reason: {reason}]" if reason else "" - msg = Color.colorify(f"\tTAKEN {reason}", "bold green") - else: - reason = f"[Reason: !({reason})]" if reason else "" - msg = Color.colorify(f"\tNOT taken {reason}", "bold red") - return (is_taken, msg) - - if gef.arch.is_call(insn): - target_address = int(insn.operands[-1].split()[0], 16) - msg = [] - for i, new_insn in enumerate(capstone_disassemble(target_address, nb_insn)): - msg.append(f" {DOWN_ARROW if i == 0 else ' '} {new_insn!s}") - return (True, "\n".join(msg)) - - return (False, "") diff --git a/scripts/trinity/keystone.py b/scripts/trinity/keystone.py deleted file mode 100644 index f49cfd3..0000000 --- a/scripts/trinity/keystone.py +++ /dev/null @@ -1,198 +0,0 @@ -__AUTHOR__ = "hugsy" -__VERSION__ = 0.1 -__NAME__ = "assemble" - - -import keystone -import gdb - - -@register -class AssembleCommand(GenericCommand): - """Inline code assemble. Architecture can be set in GEF runtime config. """ - - _cmdline_ = "assemble" - _syntax_ = f"{_cmdline_} [-h] [--list-archs] [--mode MODE] [--arch ARCH] [--overwrite-location LOCATION] [--endian ENDIAN] [--as-shellcode] instruction;[instruction;...instruction;])" - _aliases_ = ["asm",] - _example_ = (f"\n{_cmdline_} -a x86 -m 32 nop ; nop ; inc eax ; int3" - f"\n{_cmdline_} -a arm -m arm add r0, r0, 1") - - valid_arch_modes = { - # Format: ARCH = [MODES] with MODE = (NAME, HAS_LITTLE_ENDIAN, HAS_BIG_ENDIAN) - "ARM": [("ARM", True, True), ("THUMB", True, True), - ("ARMV8", True, True), ("THUMBV8", True, True)], - "ARM64": [("0", True, False)], - "MIPS": [("MIPS32", True, True), ("MIPS64", True, True)], - "PPC": [("PPC32", False, True), ("PPC64", True, True)], - "SPARC": [("SPARC32", True, True), ("SPARC64", False, True)], - "SYSTEMZ": [("SYSTEMZ", True, True)], - "X86": [("16", True, False), ("32", True, False), - ("64", True, False)] - } - valid_archs = valid_arch_modes.keys() - valid_modes = [_ for sublist in valid_arch_modes.values() for _ in sublist] - - def __init__(self) -> None: - super().__init__() - self["default_architecture"] = ("X86", "Specify the default architecture to use when assembling") - self["default_mode"] = ("64", "Specify the default architecture to use when assembling") - return - - def pre_load(self) -> None: - try: - __import__("keystone") - except ImportError: - msg = "Missing `keystone-engine` package for Python, install with: `pip install keystone-engine`." - raise ImportWarning(msg) - return - - def usage(self) -> None: - super().usage() - gef_print("") - self.list_archs() - return - - def list_archs(self) -> None: - gef_print("Available architectures/modes (with endianness):") - # for updates, see https://github.com/keystone-engine/keystone/blob/master/include/keystone/keystone.h - for arch in self.valid_arch_modes: - gef_print(f"- {arch}") - for mode, le, be in self.valid_arch_modes[arch]: - if le and be: - endianness = "little, big" - elif le: - endianness = "little" - elif be: - endianness = "big" - gef_print(f" * {mode:<7} ({endianness})") - return - - @parse_arguments({"instructions": [""]}, {"--mode": "", "--arch": "", "--overwrite-location": 0, "--endian": "little", "--list-archs": True, "--as-shellcode": True}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: - arch_s, mode_s, endian_s = self["default_architecture"], self["default_mode"], "" - - args = kwargs["arguments"] - if args.list_archs: - self.list_archs() - return - - if not args.instructions: - err("No instruction given.") - return - - if is_alive(): - arch_s, mode_s = gef.arch.arch, gef.arch.mode - endian_s = "big" if gef.arch.endianness == Endianness.BIG_ENDIAN else "" - - if args.arch: - arch_s = args.arch - arch_s = arch_s.upper() - - if args.mode: - mode_s = args.mode - mode_s = mode_s.upper() - - if args.endian == "big": - endian_s = "big" - endian_s = endian_s.upper() - - if arch_s not in self.valid_arch_modes: - raise AttributeError(f"invalid arch '{arch_s}'") - - valid_modes = self.valid_arch_modes[arch_s] - try: - mode_idx = [m[0] for m in valid_modes].index(mode_s) - except ValueError: - raise AttributeError(f"invalid mode '{mode_s}' for arch '{arch_s}'") - - if endian_s == "little" and not valid_modes[mode_idx][1] or endian_s == "big" and not valid_modes[mode_idx][2]: - raise AttributeError(f"invalid endianness '{endian_s}' for arch/mode '{arch_s}:{mode_s}'") - - arch, mode = get_keystone_arch(arch=arch_s, mode=mode_s, endian=endian_s) - insns = [x.strip() for x in " ".join(args.instructions).split(";") if x] - info(f"Assembling {len(insns)} instruction(s) for {arch_s}:{mode_s}") - - if args.as_shellcode: - gef_print("""sc="" """) - - raw = b"" - for insn in insns: - res = keystone_assemble(insn, arch, mode, raw=True) - if res is None: - gef_print("(Invalid)") - continue - - if args.overwrite_location: - raw += res - continue - - s = binascii.hexlify(res) - res = b"\\x" + b"\\x".join([s[i:i + 2] for i in range(0, len(s), 2)]) - res = res.decode("utf-8") - - if args.as_shellcode: - res = f"""sc+="{res}" """ - - gef_print(f"{res!s:60s} # {insn}") - - if args.overwrite_location: - l = len(raw) - info(f"Overwriting {l:d} bytes at {format_address(args.overwrite_location)}") - gef.memory.write(args.overwrite_location, raw, l) - return - - -def get_arch(arch: Optional[str] = None, mode: Optional[str] = None, endian: Optional[bool] = None, to_string: bool = False) -> Union[Tuple[None, None], Tuple[str, Union[int, str]]]: - keystone = sys.modules["keystone"] - if (arch, mode, endian) == (None, None, None): - return get_generic_running_arch(keystone, "KS", to_string) - - if arch in ["ARM64", "SYSTEMZ"]: - modes = [None] - elif arch == "ARM" and mode == "ARMV8": - modes = ["ARM", "V8"] - elif arch == "ARM" and mode == "THUMBV8": - modes = ["THUMB", "V8"] - else: - modes = [mode] - a = arch - if not to_string: - mode = 0 - for m in modes: - arch, _mode = get_generic_arch(keystone, "KS", a, m, endian, to_string) - mode |= _mode - else: - mode = "" - for m in modes: - arch, _mode = get_generic_arch(keystone, "KS", a, m, endian, to_string) - mode += f"|{_mode}" - mode = mode[1:] - return arch, mode - - - -def assemble(code_str: str, arch: int, mode: int, **kwargs: Any) -> Optional[Union[str, bytearray]]: - """Assembly encoding function based on keystone.""" - keystone = sys.modules["keystone"] - code = gef_pybytes(code_str) - addr = kwargs.get("addr", 0x1000) - - try: - ks = keystone.Ks(arch, mode) - enc, cnt = ks.asm(code, addr) - except keystone.KsError as e: - err(f"Keystone assembler error: {e}") - return None - - if cnt == 0: - return "" - - enc = bytearray(enc) - if "raw" not in kwargs: - s = binascii.hexlify(enc) - enc = b"\\x" + b"\\x".join([s[i : i + 2] for i in range(0, len(s), 2)]) - enc = enc.decode("utf-8") - - return enc - - diff --git a/scripts/trinity/mprotect.py b/scripts/trinity/mprotect.py deleted file mode 100644 index 785e1c3..0000000 --- a/scripts/trinity/mprotect.py +++ /dev/null @@ -1,77 +0,0 @@ -__AUTHOR__ = "hugsy" -__NAME__ = "mprotect" -__VERSION__ = 0.1 - - -import gdb -import keystone - - -@register -class ChangePermissionCommand(GenericCommand): - """Change a page permission. By default, it will change it to 7 (RWX).""" - - _cmdline_ = "set-permission" - _syntax_ = (f"{_cmdline_} address [permission]\n" - "\taddress\t\tan address within the memory page for which the permissions should be changed\n" - "\tpermission\ta 3-bit bitmask with read=1, write=2 and execute=4 as integer") - _aliases_ = ["mprotect"] - _example_ = f"{_cmdline_} $sp 7" - - def __init__(self) -> None: - super().__init__(complete=gdb.COMPLETE_LOCATION) - return - - @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: - if len(argv) not in (1, 2): - err("Incorrect syntax") - self.usage() - return - - if len(argv) == 2: - perm = Permission(int(argv[1])) - else: - perm = Permission.ALL - - loc = safe_parse_and_eval(argv[0]) - if loc is None: - err("Invalid address") - return - - loc = int(loc) - sect = process_lookup_address(loc) - if sect is None: - err("Unmapped address") - return - - size = sect.page_end - sect.page_start - original_pc = gef.arch.pc - - info(f"Generating sys_mprotect({sect.page_start:#x}, {size:#x}, " - f"'{perm!s}') stub for arch {get_arch()}") - stub = self.get_stub_by_arch(sect.page_start, size, perm) - if stub is None: - err("Failed to generate mprotect opcodes") - return - - info("Saving original code") - original_code = gef.memory.read(original_pc, len(stub)) - - bp_loc = f"*{original_pc + len(stub):#x}" - info(f"Setting a restore breakpoint at {bp_loc}") - ChangePermissionBreakpoint(bp_loc, original_code, original_pc) - - info(f"Overwriting current memory at {loc:#x} ({len(stub)} bytes)") - gef.memory.write(original_pc, stub, len(stub)) - - info("Resuming execution") - gdb.execute("continue") - return - - def get_stub_by_arch(self, addr: int, size: int, perm: Permission) -> Union[str, bytearray, None]: - code = gef.arch.mprotect_asm(addr, size, perm) - arch, mode = get_keystone_arch() - raw_insns = keystone_assemble(code, arch, mode, raw=True) - return raw_insns - diff --git a/scripts/v8-dereference.py b/scripts/v8-dereference.py index f0c1f86..7feb551 100644 --- a/scripts/v8-dereference.py +++ b/scripts/v8-dereference.py @@ -1,52 +1,75 @@ -def to_int32(v): +__AUTHOR__ = "lordidiot" +__VERSION__ = 0.2 +__LICENSE__ = "MIT" + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from . import * + from . import gdb + + +def __to_int32(v: gdb.Value): """Cast a gdb.Value to int32""" return int(v.cast(gdb.Value(2**32-1).type)) -def lookup_symbol_hack(symbol): + +def __lookup_symbol_hack(symbol): """Hacky way to lookup symbol's address, I've tried other options like parse_and_eval but they throw errors like `No symbol "v8" in current context.`. I would like to replace this function once I figure out the proper way. """ return int(gdb.execute("info address {}".format(symbol), to_string=True).split(" is at ")[1].split(" ")[0], 16) + isolate_root = None + + def get_isolate_root(): global isolate_root if isolate_root: return isolate_root else: try: - isolate_key_addr = lookup_symbol_hack("v8::internal::Isolate::isolate_key_") - isolate_key = to_int32(gdb.parse_and_eval("*(int *){}".format(isolate_key_addr))) + isolate_key_addr = __lookup_symbol_hack( + "v8::internal::Isolate::isolate_key_") + isolate_key = __to_int32(gdb.parse_and_eval( + "*(int *){}".format(isolate_key_addr))) except: err("Failed to get value of v8::internal::Isolate::isolate_key_") return None - getthreadlocal_addr = lookup_symbol_hack("v8::base::Thread::GetThreadLocal") - res = gdb.execute("call (void*){}({})".format(getthreadlocal_addr, isolate_key), to_string=True) + getthreadlocal_addr = __lookup_symbol_hack( + "v8::base::Thread::GetThreadLocal") + res = gdb.execute( + "call (void*){}({})".format(getthreadlocal_addr, isolate_key), to_string=True) isolate_root = int(res.split("0x")[1], 16) return isolate_root + def del_isolate_root(event): global isolate_root isolate_root = None + def format_compressed(addr): heap_color = gef.config["theme.address_heap"] - return "{:s}{:s}".format(Color.colorify("0x{:08x}".format(addr>>32), "gray"), - Color.colorify("{:08x}".format(addr&0xffffffff), heap_color)) + return "{:s}{:s}".format(Color.colorify("0x{:08x}".format(addr >> 32), "gray"), + Color.colorify("{:08x}".format(addr & 0xffffffff), heap_color)) -@register_external_command + +@register class V8DereferenceCommand(GenericCommand): """(v8) Dereference recursively from an address and display information. Handles v8 specific values like tagged and compressed pointers""" _cmdline_ = "vereference" - _syntax_ = "{:s} [LOCATION] [l[NB]]".format(_cmdline_) + _syntax_ = f"{_cmdline_} [LOCATION] [l[NB]]" _aliases_ = ["v8"] - _example_ = "{:s} $sp l20".format(_cmdline_) + _example_ = f"{_cmdline_} $sp l20" def __init__(self): - super(V8DereferenceCommand, self).__init__(complete=gdb.COMPLETE_LOCATION) + super().__init__( + complete=gdb.COMPLETE_LOCATION) self["max_recursion"] = (7, "Maximum level of pointer recursion") gef_on_exit_hook(del_isolate_root) return @@ -56,16 +79,16 @@ def pprint_dereferenced(addr, off): base_address_color = gef.config["theme.dereference_base_address"] registers_color = gef.config["theme.dereference_register_value"] - regs = [(k, get_register(k)) for k in current_arch.all_registers] + regs = [(k, gef.arch.register(k)) for k in gef.arch.registers] sep = " {:s} ".format(RIGHT_ARROW) - memalign = current_arch.ptrsize + memalign = gef.arch.ptrsize offset = off * memalign current_address = align_address(addr + offset) addrs = V8DereferenceCommand.dereference_from(current_address) if addrs[1]: - l = "" + l = "" addr_l0 = format_address(int(addrs[0][0], 16)) l += "{:s}{:s}+{:#06x}: {:{ma}s}".format(Color.colorify(addr_l0, base_address_color), VERTICAL_LINE, offset, @@ -92,7 +115,7 @@ def pprint_dereferenced(addr, off): offset += memalign pass else: - l = "" + l = "" addr_l = format_address(int(addrs[0][0], 16)) l += "{:s}{:s}+{:#06x}: {:{ma}s}".format(Color.colorify(addr_l, base_address_color), VERTICAL_LINE, offset, @@ -105,13 +128,13 @@ def pprint_dereferenced(addr, off): register_hints.append(regname) if register_hints: - m = "\t{:s}{:s}".format(LEFT_ARROW, ", ".join(list(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 def do_invoke(self, argv): target = "$sp" @@ -132,7 +155,7 @@ def do_invoke(self, argv): addr = int(addr) # Remove tagging (tagged pointers) - addr = addr & (2**(8*current_arch.ptrsize)-2) + addr = addr & (2**(8*gef.arch.ptrsize)-2) if process_lookup_address(addr) is None: err("Unmapped address") return @@ -153,47 +176,53 @@ def do_invoke(self, argv): return - @staticmethod def dereference_from(addr): if not is_alive(): - return ([format_address(addr),], None) + return ([format_address(addr), ], None) code_color = gef.config["theme.dereference_code"] string_color = gef.config["theme.dereference_string"] max_recursion = gef.config["dereference.max_recursion"] or 10 addr = lookup_address(align_address(int(addr))) - msg = ([format_address(addr.value),], []) - seen_addrs = set()#tuple(set(), set()) + msg = ([format_address(addr.value), ], []) + seen_addrs = set() # tuple(set(), set()) # Is this address pointing to a normal pointer? deref = addr.dereference() if deref is None: - pass # Regular execution if so + pass # Regular execution if so else: # Is this address pointing to compressed pointers instead? # Only for valid for 64-bit address space - if current_arch.ptrsize == 8: + if gef.arch.ptrsize == 8: isolate_root = get_isolate_root() - addr0 = lookup_address(align_address(isolate_root + (deref & 0xffffffff))) - addr1 = lookup_address(align_address(isolate_root + (deref >> 32))) + addr0 = lookup_address(align_address( + isolate_root + (deref & 0xffffffff))) + addr1 = lookup_address(align_address( + isolate_root + (deref >> 32))) compressed = [False, False] - compressed[0] = addr0.dereference() and addr0.value > isolate_root + 0x0c000 and addr0.value & 1 - compressed[1] = addr1.dereference() and addr1.value > isolate_root + 0x0c000 and addr1.value & 1 + compressed[0] = addr0.dereference( + ) and addr0.value > isolate_root + 0x0c000 and addr0.value & 1 + compressed[1] = addr1.dereference( + ) and addr1.value > isolate_root + 0x0c000 and addr1.value & 1 if True in compressed: msg[1].append(format_address(addr.value+4)) for i in range(2): if compressed[i]: - msg[i].append(format_compressed(addr0.value if not i else addr1.value)) + msg[i].append(format_compressed( + addr0.value if not i else addr1.value)) else: - val = int(deref & 0xffffffff) if not i else int(deref >> 32) - if not (val & 1): # Maybe SMI - msg[i].append(" {:#0{ma}x} (SMI: {:#x})".format( val, val >> 1, ma=( 10 )) ) + val = int(deref & 0xffffffff) if not i else int( + deref >> 32) + if not (val & 1): # Maybe SMI + msg[i].append(" {:#0{ma}x} (SMI: {:#x})".format( + val, val >> 1, ma=(10))) else: - msg[i].append(" {:#0{ma}x}".format( val, ma=( 10 )) ) + msg[i].append( + " {:#0{ma}x}".format(val, ma=(10))) return msg - while addr.section and max_recursion: if addr.value in seen_addrs: msg[0].append("[loop detected]") @@ -220,7 +249,8 @@ def dereference_from(addr): 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)) + insn_str = "{} {} {}".format( + insn.location, insn.mnemonic, ", ".join(insn.operands)) msg[0].append(Color.colorify(insn_str, code_color)) break @@ -228,17 +258,21 @@ def dereference_from(addr): 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)) + 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) + txt = Color.colorify( + '"{:s}[...]"'.format(s[:50]), string_color) else: - txt = Color.colorify('"{:s}"'.format(s), string_color) + txt = Color.colorify( + '"{:s}"'.format(s), string_color) msg[0].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)) + val = "{:#0{ma}x}".format( + int(deref & 0xFFFFFFFFFFFFFFFF), ma=(gef.arch.ptrsize * 2 + 2)) msg[0].append(val) break diff --git a/scripts/visualize_heap.py b/scripts/visualize_heap.py index d4675e4..28d0420 100644 --- a/scripts/visualize_heap.py +++ b/scripts/visualize_heap.py @@ -88,7 +88,7 @@ def collect_known_ranges()->list: return result -@register_external_command +@register class VisualizeHeapChunksCommand(GenericCommand): """Visual helper for glibc heap chunks""" diff --git a/scripts/windbg.py b/scripts/windbg.py index dd0cbca..ac72929 100644 --- a/scripts/windbg.py +++ b/scripts/windbg.py @@ -2,36 +2,50 @@ __VERSION__ = 0.2 +from typing import TYPE_CHECKING, Callable, List + +if TYPE_CHECKING: + from . import * + from . import gdb + import subprocess +import gdb + +GEF_DOCS_URL = "https://gef.readthedocs.io/en/master/" +SEARCH_URL = f"https://www.google.com/search?q=site:{GEF_DOCS_URL}" + class BreakOnLoadSharedLibrary(gdb.Breakpoint): def __init__(self, module_name): - super(BreakOnLoadSharedLibrary, self).__init__("dlopen", type=gdb.BP_BREAKPOINT, internal=False) + super().__init__("dlopen", type=gdb.BP_BREAKPOINT, internal=False) self.module_name = module_name self.silent = True self.enabled = True return def stop(self): + if len(gef.arch.function_parameters) == 0: + return False reg = gef.arch.function_parameters[0] - addr = lookup_address(get_register(reg)) + addr = lookup_address(gef.arch.register(reg)) if addr.value == 0: return False - path = gef.memory.read_cstring(addr.value, max_length=None) + path = gef.memory.read_cstring(addr.value) if path.endswith(self.module_name): return True return False +@register class WindbgSxeCommand(GenericCommand): """WinDBG compatibility layer: sxe (set-exception-enable): break on loading libraries.""" _cmdline_ = "sxe" - _syntax_ = "{:s} [ld,ud]:module".format(_cmdline_) - _example_ = "{:s} ld:mylib.so".format(_cmdline_) + _syntax_ = f"{_cmdline_} [ld,ud]:module" + _example_ = f"{_cmdline_} ld:mylib.so" def __init__(self): - super(WindbgSxeCommand, self).__init__(complete=gdb.COMPLETE_NONE) + super().__init__(complete=gdb.COMPLETE_NONE) self.breakpoints = [] return @@ -55,7 +69,7 @@ def do_invoke(self, argv): return -def windbg_execute_until(cnt, cmd, stop_condition): +def windbg_execute_until(cnt: int, cmd: str, stop_condition: Callable[[Instruction], bool]): while cnt: cnt -= 1 gef.config["context.enable"] = False @@ -67,10 +81,11 @@ def windbg_execute_until(cnt, cmd, stop_condition): return +@register class WindbgTcCommand(GenericCommand): """WinDBG compatibility layer: tc - trace until next call.""" _cmdline_ = "tc" - _syntax_ = "{:s} [COUNT]".format(_cmdline_) + _syntax_ = f"{_cmdline_} [COUNT]" @only_if_gdb_running def do_invoke(self, argv): @@ -80,10 +95,11 @@ def do_invoke(self, argv): return +@register class WindbgPcCommand(GenericCommand): """WinDBG compatibility layer: pc - run until next call.""" _cmdline_ = "pc" - _syntax_ = "{:s} [COUNT]".format(_cmdline_) + _syntax_ = f"{_cmdline_} [COUNT]" @only_if_gdb_running def do_invoke(self, argv): @@ -93,10 +109,11 @@ def do_invoke(self, argv): return +@register class WindbgTtCommand(GenericCommand): """WinDBG compatibility layer: tt - trace until next return.""" _cmdline_ = "tt" - _syntax_ = "{:s} [COUNT]".format(_cmdline_) + _syntax_ = f"{_cmdline_} [COUNT]" @only_if_gdb_running def do_invoke(self, argv): @@ -106,10 +123,11 @@ def do_invoke(self, argv): return +@register class WindbgPtCommand(GenericCommand): """WinDBG compatibility layer: pt - run until next return.""" _cmdline_ = "pt" - _syntax_ = "{:s} [COUNT]".format(_cmdline_) + _syntax_ = f"{_cmdline_} [COUNT]" @only_if_gdb_running def do_invoke(self, argv): @@ -119,30 +137,32 @@ def do_invoke(self, argv): return +@register class WindbgPtcCommand(GenericCommand): """WinDBG compatibility layer: ptc - run until next call or return.""" _cmdline_ = "ptc" - _syntax_ = "{:s} [COUNT]".format(_cmdline_) + _syntax_ = f"{_cmdline_} [COUNT]" @only_if_gdb_running def do_invoke(self, argv): cnt = int(argv[0]) if len(argv) else 0xffffffffffffffff - def fn(x): - gef.arch.is_ret(x) or gef.arch.is_call(x) + def fn(x) -> bool: + return gef.arch.is_ret(x) or gef.arch.is_call(x) windbg_execute_until(cnt, "nexti", fn) gdb.execute("context") return +@register class WindbgHhCommand(GenericCommand): """WinDBG compatibility layer: hh - open help in web browser.""" _cmdline_ = "hh" - _syntax_ = "{:s}".format(_cmdline_) + _syntax_ = f"{_cmdline_}" def do_invoke(self, argv): - url = "https://gef.readthedocs.io/en/master/" + url = GEF_DOCS_URL if len(argv): url += "search.html?q={}".format(argv[0]) subprocess.Popen(["xdg-open", url], @@ -152,55 +172,58 @@ def do_invoke(self, argv): return +@register class WindbgGoCommand(GenericCommand): """WinDBG compatibility layer: g - go.""" _cmdline_ = "g" - _syntax_ = "{:s}".format(_cmdline_) + _syntax_ = _cmdline_ def do_invoke(self, argv): if is_alive(): gdb.execute("continue") else: - gdb.execute("run {}".format(" ".join(argv))) + gdb.execute(f"run {' '.join(argv)}") return +@register class WindbgUCommand(GenericCommand): """WinDBG compatibility layer: u - disassemble.""" _cmdline_ = "u" - _syntax_ = "{:s}".format(_cmdline_) + _syntax_ = _cmdline_ def __init__(self): - super(WindbgUCommand, self).__init__(complete=gdb.COMPLETE_LOCATION) + super().__init__(complete=gdb.COMPLETE_LOCATION) return @only_if_gdb_running - def do_invoke(self, argv): + def do_invoke(self, argv: List[str]): length = 16 location = gef.arch.pc for arg in argv: if arg[0] in ("l", "L"): length = int(arg[1:]) else: - location = safe_parse_and_eval(arg) - if location is not None: - if hasattr(location, "address") and location.address is not None: - location = int(location.address, 0) + loc = safe_parse_and_eval(arg) + if loc: + if hasattr(loc, "address") and loc.address: + location = int(loc.address, 0) # type: ignore else: - location = int(location, 0) + location = int(loc, 0) # type: ignore for insn in gef_disassemble(location, length): - print(insn) + gef_print(str(insn)) return +@register class WindbgXCommand(GenericCommand): """WinDBG compatibility layer: x - search symbol.""" _cmdline_ = "xs" _syntax_ = "{:s} REGEX".format(_cmdline_) def __init__(self): - super(WindbgXCommand, self).__init__(complete=gdb.COMPLETE_LOCATION) + super().__init__(complete=gdb.COMPLETE_LOCATION) return def do_invoke(self, argv): @@ -217,10 +240,11 @@ def do_invoke(self, argv): return +@register class WindbgRCommand(GenericCommand): """WinDBG compatibility layer: r - register info""" _cmdline_ = "r" - _syntax_ = "{:s} [REGISTER[=VALUE]]".format(_cmdline_) + _syntax_ = f"{_cmdline_} [REGISTER[=VALUE]]" def print_regs(self, reg_list, width=16): n = self.arch_reg_width() @@ -231,9 +255,10 @@ def chunks(l, n): yield l[ii:ii + n] def print_reg(reg, width=16): - fmt = "%s=%0{}x".format(width) - regval = get_register("$" + reg) & ((1 << (current_arch.ptrsize * 8)) - 1) - print(fmt % (reg.rjust(max_reg_len), regval), end="") + fmt = f"%s=%0{width}x".format() + regval = gef.arch.register(f"${reg}") & ( + (1 << (gef.arch.ptrsize * 8)) - 1) + gef_print(fmt % (reg.rjust(max_reg_len), regval), end="") for regs in chunks(reg_list, n): for ii in range(0, len(regs)): @@ -243,9 +268,9 @@ def print_reg(reg, width=16): print_reg(reg, width) if ii + 1 != len(regs): - print(" ", end="") + gef_print(" ", end="") else: - print() + gef_print() def arch_reg_width(self): if get_arch().startswith("i386:x86-64"): @@ -337,7 +362,7 @@ def do_invoke(self, argv): if "=" in combined: (regstr, valstr) = combined.split("=") - reg = "$" + regstr + reg = f"${regstr}" val = int(valstr, 16) gdb.execute("set {:s} = {:#x}".format(reg, val)) else: @@ -347,14 +372,11 @@ def do_invoke(self, argv): return -def __windbg_prompt__(current_prompt): +def __windbg_prompt__(current_prompt: Callable) -> str: """WinDBG prompt function.""" - p = "0:000 " - p += "\u27a4 " - - if gef.config["gef.readline_compat"] is True or \ - gef.config["gef.disable_color"] is True: - return gef_prompt + p = "0:000 ➤ " + if gef.config["gef.disable_color"] is True: + return p if is_alive(): return Color.colorify(p, attrs="bold green") @@ -363,14 +385,16 @@ def __windbg_prompt__(current_prompt): def __default_prompt__(x): - if gef.config["gef.use-windbg-prompt"] is True: - return __windbg_prompt__(x) - else: + if gef.config["gef.use-windbg-prompt"] is False \ + or gef.config["gef.readline_compat"] is True: return __gef_prompt__(x) + return __windbg_prompt__(x) + # Prompt -gef.config["gef.use-windbg-prompt"] = GefSetting(False, description="Use WinDBG like prompt") +gef.config["gef.use-windbg-prompt"] = GefSetting( + False, description="Use WinDBG like prompt") gdb.prompt_hook = __default_prompt__ # Aliases @@ -398,21 +422,3 @@ def __default_prompt__(x): GefAlias("p", "nexti", completer_class=gdb.COMPLETE_LOCATION) GefAlias("kp", "info stack") GefAlias("uf", "disassemble") - -# Commands -windbg_commands = [ - WindbgTcCommand, - WindbgPcCommand, - WindbgTtCommand, - WindbgPtCommand, - WindbgPtcCommand, - WindbgHhCommand, - WindbgGoCommand, - WindbgXCommand, - WindbgUCommand, - WindbgSxeCommand, - WindbgRCommand, -] - -for _ in windbg_commands: - register_external_command(_) diff --git a/scripts/xref-telescope.py b/scripts/xref-telescope.py index c0c94b2..6f45c4e 100644 --- a/scripts/xref-telescope.py +++ b/scripts/xref-telescope.py @@ -1,11 +1,22 @@ +__AUTHOR__ = "io12" +__VERSION__ = 0.2 -@register_external_command +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from . import * + + +@register class XRefTelescopeCommand(SearchPatternCommand): """Recursively search for cross-references to a pattern in memory""" _cmdline_ = "xref-telescope" - _syntax_ = "{:s} PATTERN [depth]".format(_cmdline_) - _example_ = "\n{0:s} AAAAAAAA\n{0:s} 0x555555554000 15".format(_cmdline_) + _syntax_ = f"{_cmdline_} PATTERN [depth]" + _example_ = [ + f"{_cmdline_} AAAAAAAA", + f"{_cmdline_} 0x555555554000 15" + ] def xref_telescope_(self, pattern, depth, tree_heading): """Recursively search a pattern within the whole userland memory.""" @@ -15,9 +26,11 @@ def xref_telescope_(self, pattern, depth, tree_heading): if is_hex(pattern): if gef.arch.endianness == Endianness.BIG_ENDIAN: - pattern = "".join(["\\x" + pattern[i:i + 2] for i in range(2, len(pattern), 2)]) + pattern = "".join(["\\\\x" + pattern[i:i + 2] + for i in range(2, len(pattern), 2)]) else: - pattern = "".join(["\\x" + pattern[i:i + 2] for i in range(len(pattern) - 2, 0, -2)]) + pattern = "".join(["\\\\x" + pattern[i:i + 2] + for i in range(len(pattern) - 2, 0, -2)]) locs = [] for section in gef.memory.maps: @@ -43,10 +56,8 @@ def xref_telescope_(self, pattern, depth, tree_heading): tree_suffix_pre = " ├──" tree_suffix_post = " │ " - gef_print('{} {:#x} {} {} "{}"' - .format(tree_heading + tree_suffix_pre, - loc[0], Color.blueify(path), - perm, Color.pinkify(loc[2]))) + line = f'{tree_heading + tree_suffix_pre} {loc[0]:#x} {Color.blueify(path)} {perm} "{Color.pinkify(loc[2])}"' + gef_print(line) self.xref_telescope_(hex(loc[0]), depth - 1, tree_heading + tree_suffix_post) @@ -66,6 +77,6 @@ def do_invoke(self, argv): except (IndexError, ValueError): depth = 3 - info("Recursively searching '{:s}' in memory" - .format(Color.yellowify(pattern))) + info( + f"Recursively searching '{Color.yellowify(pattern):s}' in memory") self.xref_telescope(pattern, depth) From 57122d59e7d0949890fc8a8a92041e779b820197 Mon Sep 17 00:00:00 2001 From: Dreg Date: Mon, 20 Jun 2022 18:03:40 +0200 Subject: [PATCH 14/39] documented: keep x86 and x86_64 FLAGS Register when calls to mprotect (#64) --- docs/commands/set-permission.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/commands/set-permission.md b/docs/commands/set-permission.md index fcd53e6..4e32949 100644 --- a/docs/commands/set-permission.md +++ b/docs/commands/set-permission.md @@ -9,11 +9,13 @@ stub. For example, for x86, the following stub will be inserted: ``` pushad +pushfd mov eax, mprotect_syscall_num mov ebx, address_of_the_page mov ecx, size_of_the_page mov edx, permission_to_set int 0x80 +popfd popad ``` From a3417fbc7a45b6c4532c47b4fb63f46f29cac560 Mon Sep 17 00:00:00 2001 From: hugsy Date: Mon, 20 Jun 2022 12:53:35 -0700 Subject: [PATCH 15/39] `master` -> `main` (#65) --- .github/workflows/docs-link-check.yml | Bin 1028 -> 998 bytes .github/workflows/generate-docs.yml | 1 - .github/workflows/run-tests.yml | 4 ++-- README.md | 8 ++++---- docs/commands/ida-rpyc.md | 4 +--- docs/index.md | 2 +- scripts/windbg.py | 2 +- structs/README.md | 2 +- 8 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.github/workflows/docs-link-check.yml b/.github/workflows/docs-link-check.yml index 15ad50688e3cffdb3caf5f3023ed1189a88259aa..41acf0bb3ee522e7ba71c6901708fc020ff69cba 100644 GIT binary patch delta 10 RcmZqSc*Z_q#>Q=a%m5g;1X=(9 delta 38 kcmaFH-oi0qhO`0$0_p Date: Mon, 20 Jun 2022 13:01:44 -0700 Subject: [PATCH 16/39] Update discord-notify.yml Synced notifications between gef/extras --- .github/workflows/discord-notify.yml | 43 +++++++++++++++------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/.github/workflows/discord-notify.yml b/.github/workflows/discord-notify.yml index 8f1f2e1..2cdc594 100644 --- a/.github/workflows/discord-notify.yml +++ b/.github/workflows/discord-notify.yml @@ -1,5 +1,17 @@ name: "Discord Notification" -on: [push, pull_request, issues] + + +on: + push: + branches: + - main + - dev + + pull_request: + branches: + - main + - dev + env: DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} @@ -11,43 +23,37 @@ jobs: with: fetch-depth: 0 - - name: GEF Push Notification + - name: GEF-Extras Push Notification if: github.event_name == 'push' && github.repository_owner == 'hugsy' uses: sarisia/actions-status-discord@v1 with: nodetail: true title: "[${{ github.repository }}] ${{ github.actor }} pushed to `${{ github.ref }}`" description: | - **Commit delta**: `${{ github.event.before }}` → `${{ github.event.after }}` - --- - **Changes**: ${{ github.event.compare }} - --- **Commits**: ● ${{ join(github.event.commits.*.message, ' ● ') }} - + --- + **Link**: ${{ github.event.compare }} color: 0x0000ff username: ${{ github.actor }} via GithubBot avatar_url: ${{ github.actor.avatar_url }} - - name: GEF PR Notification - if: github.event_name == 'pull_request' && github.event.action == 'opened' && github.repository_owner == 'hugsy' + - name: GEF-Extras Pull Request Notification + if: github.event_name == 'pull_request' && github.event.action == 'opened' uses: sarisia/actions-status-discord@v1 with: nodetail: true title: "[${{ github.repository }}] ${{ github.actor }} created a new Pull Request (`#${{ github.event.pull_request.number }}`)" description: | **${{ github.event.pull_request.title }}** - - ${{ github.event.pull_request.body }} - --- - Link: ${{ github.event.pull_request.html_url }} - color: 0xff0000 + **Link**: ${{ github.event.pull_request.html_url }} + color: 0x00ff00 username: ${{ github.actor }} via GithubBot avatar_url: ${{ github.actor.avatar_url }} - - name: GEF Issue Notification + - name: GEF-Extras Issue Notification if: github.event_name == 'issues' && github.event.action == 'opened' && github.repository_owner == 'hugsy' uses: sarisia/actions-status-discord@v1 with: @@ -55,11 +61,8 @@ jobs: title: "[${{ github.repository }}] ${{ github.actor }} created a new Issue (`#${{ github.event.issue.number }}`)" description: | **${{ github.event.issue.title }}** - - ${{ github.event.issue.body }} - --- - Link: ${{ github.event.issue.html_url }} - color: 0x00ff00 + **Link**: ${{ github.event.issue.html_url }} + color: 0xff0000 username: ${{ github.actor }} via GithubBot avatar_url: ${{ github.actor.avatar_url }} From 4b88c87474a33e40f7cd8c4c7fd4d5b1f6962746 Mon Sep 17 00:00:00 2001 From: hugsy Date: Fri, 24 Jun 2022 06:55:14 -0700 Subject: [PATCH 17/39] Create PULL_REQUEST_TEMPLATE.md --- .github/PULL_REQUEST_TEMPLATE.md | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..7ce37a8 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,38 @@ + +## Description/Motivation/Screenshots + + + + + + + +## How Has This Been Tested ? + + + +| Architecture | Tested ? | Comments | +| ------------ | :----------------------: | ----------------------------------------- | +| x86-32 | :x: | | +| x86-64 | :x: | | +| ARM | :x: | | +| AARCH64 | :x: | | +| MIPS | :x: | | +| POWERPC | :x: | | +| SPARC | :x: | | +| RISC-V | :x: | | +| `make test` | :x: | | + +*Legend:* + - Yes `:white_check_mark:` → :white_check_mark: + - No `:x:` → :x: + +## Checklist + + + +- [ ] My PR was done against the `dev` branch, not `main`. +- [ ] My code follows the code style of this project. +- [ ] My change includes a change to the documentation, if required. +- [ ] If my change adds new code, [adequate tests](docs/testing.md) have been added. +- [ ] I have read and agree to the **CONTRIBUTING** document. From c5910ca337013e945eb1b152391704a93f83ff6d Mon Sep 17 00:00:00 2001 From: Noah Boegli <39568628+noahboegli@users.noreply.github.com> Date: Tue, 28 Jun 2022 18:35:45 +0200 Subject: [PATCH 18/39] Fix: stack-view command Stack bounds retrieval when in frame 0 (#67) --- scripts/stack.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/scripts/stack.py b/scripts/stack.py index 01bc813..3c94f69 100644 --- a/scripts/stack.py +++ b/scripts/stack.py @@ -15,21 +15,26 @@ def do_invoke(self, argv): ptrsize = gef.arch.ptrsize frame = gdb.selected_frame() - if not frame.older(): + if frame.older(): + saved_ip = frame.older().pc() + stack_hi = align_address(int(frame.older().read_register("sp"))) + # This ensures that frame.older() does not return None due to another error + elif frame.level() == 0: + saved_ip = None + stack_hi = align_address(int(frame.read_register("bp"))) + else: #reason = frame.unwind_stop_reason() reason_str = gdb.frame_stop_reason_string( frame.unwind_stop_reason() ) warn("Cannot determine frame boundary, reason: {:s}".format(reason_str)) return - - saved_ip = frame.older().pc() - stack_hi = align_address(int(frame.older().read_register("sp"))) + stack_lo = align_address(int(frame.read_register("sp"))) should_stack_grow_down = gef.config["context.grow_stack_down"] == True results = [] for offset, address in enumerate(range(stack_lo, stack_hi, ptrsize)): pprint_str = DereferenceCommand.pprint_dereferenced(stack_lo, offset) - if dereference(address) == saved_ip: + if saved_ip and dereference(address) == saved_ip: pprint_str += " " + Color.colorify("($savedip)", attrs="gray underline") results.append(pprint_str) From 9ca384ff75ac5034e28970324ce3d61b00c11cf7 Mon Sep 17 00:00:00 2001 From: hugsy Date: Thu, 30 Jun 2022 09:41:28 -0700 Subject: [PATCH 19/39] Update PULL_REQUEST_TEMPLATE.md --- .github/PULL_REQUEST_TEMPLATE.md | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7ce37a8..82eaa2b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -9,23 +9,16 @@ ## How Has This Been Tested ? - - -| Architecture | Tested ? | Comments | -| ------------ | :----------------------: | ----------------------------------------- | -| x86-32 | :x: | | -| x86-64 | :x: | | -| ARM | :x: | | -| AARCH64 | :x: | | -| MIPS | :x: | | -| POWERPC | :x: | | -| SPARC | :x: | | -| RISC-V | :x: | | -| `make test` | :x: | | - -*Legend:* - - Yes `:white_check_mark:` → :white_check_mark: - - No `:x:` → :x: +"Tested" indicates that the PR works *and* the unit test (i.e. `make test`) run passes without issue. + + * [ ] x86-32 + * [ ] x86-64 + * [ ] ARM + * [ ] AARCH64 + * [ ] MIPS + * [ ] POWERPC + * [ ] SPARC + * [ ] RISC-V ## Checklist @@ -34,5 +27,5 @@ - [ ] My PR was done against the `dev` branch, not `main`. - [ ] My code follows the code style of this project. - [ ] My change includes a change to the documentation, if required. -- [ ] If my change adds new code, [adequate tests](docs/testing.md) have been added. -- [ ] I have read and agree to the **CONTRIBUTING** document. +- [ ] If my change adds new code, [adequate tests](https://hugsy.github.io/gef/testing) have been added. +- [ ] I have read and agree to the [CONTRIBUTING](https://github.com/hugsy/gef/blob/main/.github/CONTRIBUTING.md) document. From 93e06dbb0a99c7fa511dd358671a65d30ff8829d Mon Sep 17 00:00:00 2001 From: hugsy Date: Thu, 30 Jun 2022 09:45:58 -0700 Subject: [PATCH 20/39] Quick fix for `ropper.py` (#73) Containing scope of `readline` module to the class itself to avoid breaking the autocomplete in gdb --- scripts/ropper.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/scripts/ropper.py b/scripts/ropper.py index 3ad2405..a5c2200 100644 --- a/scripts/ropper.py +++ b/scripts/ropper.py @@ -1,9 +1,8 @@ __AUTHOR__ = "hugsy" -__VERSION__ = 0.1 +__VERSION__ = 0.2 __NAME__ = "ropper" import sys -import readline import gdb import ropper @@ -18,10 +17,13 @@ class RopperCommand(GenericCommand): def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_NONE) + self.__readline = None return @only_if_gdb_running def do_invoke(self, argv: List[str]) -> None: + if not self.__readline: + self.__readline = __import__("readline") ropper = sys.modules["ropper"] if "--file" not in argv: path = gef.session.file @@ -35,14 +37,14 @@ def do_invoke(self, argv: List[str]) -> None: argv.append(f"{sect.page_start:#x}") # 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() + old_completer_delims = self.__readline.get_completer_delims() + old_completer = self.__readline.get_completer() try: ropper.start(argv) except RuntimeWarning: return - readline.set_completer(old_completer) - readline.set_completer_delims(old_completer_delims) + self.__readline.set_completer(old_completer) + self.__readline.set_completer_delims(old_completer_delims) return From 5b7d7ed855bf48c8fa3d89049d1273dc3349ce03 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 2 Jul 2022 13:53:47 -0700 Subject: [PATCH 21/39] Restored `heap-view` to a working state (#71) * [heap-view] ported to latest API + added test + added docs * Squashed commit of the following: commit bcb65677e5082883081626b4f2500cc3afa4941c Author: hugsy Date: Tue Jun 28 16:07:48 2022 -0700 [ci] visualize-heap fix compil unused-var warning commit 0d6d1eafed3a07beac4da7cc9785813fe8b52a3f Author: hugsy Date: Tue Jun 28 16:05:59 2022 -0700 [tests] using the same tempdir for gef and bins commit 4979e82e71d559cd00dabf147587e2976a56785b Author: hugsy Date: Tue Jun 28 15:59:13 2022 -0700 fixed `set-permission` commit 0b067bd98c4fbd85f7074236476163937f186d6a Author: hugsy Date: Tue Jun 28 15:46:28 2022 -0700 fixed `capstone-disassemble` commit 6baf7d7c01e36f06e9feb86c8044e92b18370ed9 Author: hugsy Date: Tue Jun 28 15:42:55 2022 -0700 fixed `keystone-assemble` --- Makefile | 37 ++++---- docs/commands/visualize_heap.md | 14 +++ mkdocs.yml | 3 +- scripts/__init__.pyi | 12 +++ scripts/assemble.py | 36 ++++++-- scripts/visualize_heap.py | 117 ++++++++++++++++--------- tests/binaries/Makefile | 2 +- tests/binaries/visualize_heap.c | 26 ++++++ tests/commands/capstone_disassemble.py | 27 +++--- tests/commands/keystone_assemble.py | 19 ++-- tests/commands/set_permission.py | 35 ++++---- tests/commands/unicorn_emulate.py | 20 ++--- tests/commands/visualize_heap.py | 27 ++++++ tests/utils.py | 67 ++++++++------ 14 files changed, 285 insertions(+), 157 deletions(-) create mode 100644 docs/commands/visualize_heap.md create mode 100644 tests/binaries/visualize_heap.c create mode 100644 tests/commands/visualize_heap.py diff --git a/Makefile b/Makefile index 1b4a20f..66b55d5 100644 --- a/Makefile +++ b/Makefile @@ -13,8 +13,8 @@ GDBINIT_BACKUP = $(GDBINIT_TMP) GEFRC_TMP = $(shell mktemp) GEFRC_BACKUP = $(GEFRC_TMP) TMPDIR ?= $(shell mktemp -d) -GEF_PATH := $(TMPDIR)/gef.py WORKING_DIR := $(TMPDIR) +GEF_PATH := $(WORKING_DIR)/gef.py PYTEST_PARAMETERS := --verbose --forked --numprocesses=$(NB_CORES) BRANCH := $(shell git rev-parse --abbrev-ref HEAD) @@ -22,39 +22,32 @@ BRANCH := $(shell git rev-parse --abbrev-ref HEAD) .PHONY: test test_% Test% testbins clean lint -test: setup testbins - WORKING_DIR=$(WORKING_DIR) GEF_PATH=$(GEF_PATH) python3 -m pytest $(PYTEST_PARAMETERS) -k "not benchmark" +setup: + @echo "[+] Downloading gef.py -> '$(GEF_PATH)'" + @wget -O $(GEF_PATH) -q https://gef.blah.cat/dev + +test: testbins setup + TMPDIR=$(WORKING_DIR) GEF_PATH=$(GEF_PATH) python3 -m pytest $(PYTEST_PARAMETERS) -k "not benchmark" -test_%: setup testbins - WORKING_DIR=$(WORKING_DIR) GEF_PATH=$(GEF_PATH) python3 -m pytest $(PYTEST_PARAMETERS) -k $@ +test_%: testbins setup + TMPDIR=$(WORKING_DIR) GEF_PATH=$(GEF_PATH) python3 -m pytest $(PYTEST_PARAMETERS) -k $@ testbins: $(wildcard tests/binaries/*.c) - @WORKING_DIR=$(WORKING_DIR) $(MAKE) -j $(NB_CORES) -C tests/binaries TARGET=$(TARGET) all + @TMPDIR=$(WORKING_DIR) $(MAKE) -j $(NB_CORES) -C tests/binaries TARGET=$(TARGET) TMPDIR=$(WORKING_DIR) all clean: - WORKING_DIR=$(WORKING_DIR) $(MAKE) -j $(NB_CORES) -C tests/binaries clean - @rm -rf $(WORKING_DIR) || true - -restore: - @mv $(GDBINIT_BACKUP) ~/.gdbinit || true - @mv $(GEFRC_BACKUP) ~/.gef.rc || true + TMPDIR=$(WORKING_DIR) $(MAKE) -j $(NB_CORES) -C tests/binaries clean + @rm -rf $(WORKING_DIR)/gef-* $(WORKING_DIR)/gef.py || true lint: - python3 -m pylint $(PYLINT_PARAMETERS) $(wildcard $(ROOT_DIR)/scripts/*.py) - python3 -m pylint $(PYLINT_PARAMETERS) $(wildcard tests/commands/*.py) + python3 -m pylint $(PYLINT_PARAMETERS) $(GEF_PATH) + python3 -m pylint $(PYLINT_PARAMETERS) $(wildcard tests/*/*.py) coverage: @! ( [ -d $(COVERAGE_DIR) ] && echo "COVERAGE_DIR=$(COVERAGE_DIR) exists already") @mkdir -p $(COVERAGE_DIR) @COVERAGE_DIR=$(COVERAGE_DIR) $(MAKE) test @coverage combine $(COVERAGE_DIR)/* - @coverage html --include $(TMPGEF) "/$(ROOT_DIR)/scripts/*.py" + @coverage html --include "*/gef.py" @rm -rf $(COVERAGE_DIR) -setup: - wget -O $(GEF_PATH) -q https://gef.blah.cat/py - mv ~/.gdbinit $(GDBINIT_BACKUP) || true - mv ~/.gef.rc $(GEFRC_BACKUP) || true - echo source $(GEF_PATH) > ~/.gdbinit - echo gef config gef.extra_plugins_dir $(ROOT_DIR)/scripts >> ~/.gdbinit - diff --git a/docs/commands/visualize_heap.md b/docs/commands/visualize_heap.md new file mode 100644 index 0000000..0c74672 --- /dev/null +++ b/docs/commands/visualize_heap.md @@ -0,0 +1,14 @@ +## `visualize-libc-heap-chunks` ## + +_Alias_: `heap-view` + + +This plugin aims to provide an ASCII-based simplistic representation of the heap layout. + +Currently only the glibc heap support is implemented. The command doesn't take argument, and display the heap layout. It also aggregates similar lines for better readability: + +``` +gef➤ visualize-libc-heap-chunks +``` + +![img](https://i.imgur.com/IKHlLlp.png) diff --git a/mkdocs.yml b/mkdocs.yml index 3e4c6f8..d41f6fd 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -25,4 +25,5 @@ nav: - retdec: commands/retdec.md - ropper: commands/ropper.md - set-permission: commands/set-permission.md - - windbg: commands/windbg.md + - visualize_heap: commands/visualize_heap.md + - windbg: commands/windbg.md \ No newline at end of file diff --git a/scripts/__init__.pyi b/scripts/__init__.pyi index 36c1069..9cc268a 100644 --- a/scripts/__init__.pyi +++ b/scripts/__init__.pyi @@ -351,10 +351,15 @@ class GlibcArena: class GlibcChunk: + base_address: int + data_address: int + size_addr: int + prev_size_addr: int def __init__(self, addr: int, from_base: bool = False, allow_unaligned: bool = True) -> None: ... def get_chunk_size(self) -> int: ... + @property def size(self) -> int: ... def get_usable_size(self) -> int: ... def usable_size(self) -> int: ... @@ -365,8 +370,10 @@ class GlibcChunk: def get_next_chunk_addr(self) -> int: ... def get_fwd_ptr(self, sll: bool) -> int: ... + @property def fwd(self) -> int: ... def get_bkw_ptr(self) -> int: ... + @property def bck(self) -> int: ... def has_p_bit(self) -> bool: ... def has_m_bit(self) -> bool: ... @@ -1103,6 +1110,7 @@ class GlibcHeapBinsCommand(GenericCommand): class GlibcHeapTcachebinsCommand(GenericCommand): + TCACHE_MAX_BINS: int def __init__(self) -> None: ... def do_invoke(self, argv: List[str]) -> None: ... @staticmethod @@ -1624,8 +1632,11 @@ class GefHeapManager(GefManager): def selected_arena(self) -> Optional[GlibcArena]: ... @selected_arena.setter def selected_arena(self, value: GlibcArena) -> None: ... + @property def arenas(self) -> Union[List, Iterator[GlibcArena]]: ... + @property def base_address(self) -> Optional[int]: ... + @property def chunks(self) -> Union[List, Iterator]: ... def min_chunk_size(self) -> int: ... def malloc_alignment(self) -> int: ... @@ -1687,6 +1698,7 @@ class GefUiManager(GefManager): class GefLibcManager(GefManager): def __init__(self) -> None: ... + @property def version(self) -> Optional[Tuple[int, int]]: ... diff --git a/scripts/assemble.py b/scripts/assemble.py index 9844cc2..21b320c 100644 --- a/scripts/assemble.py +++ b/scripts/assemble.py @@ -126,7 +126,12 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: f"invalid endianness '{endian_s}' for arch/mode '{arch_s}:{mode_s}'") ks_arch: int = getattr(keystone, f"KS_ARCH_{arch_s}") - ks_mode: int = getattr(keystone, f"KS_MODE_{mode_s}") + # manual fixups + # * aarch64 + if arch_s == "ARM64" and mode_s == "0": + ks_mode = 0 + else: + ks_mode: int = getattr(keystone, f"KS_MODE_{mode_s}") ks_endian: int = getattr( keystone, f"KS_MODE_{endian_s}_ENDIAN") insns = [x.strip() @@ -220,7 +225,7 @@ def do_invoke(self, argv: List[str]) -> None: err("Invalid address") return - loc = int(loc) + loc = int(abs(loc)) sect = process_lookup_address(loc) if sect is None: err("Unmapped address") @@ -231,7 +236,7 @@ def do_invoke(self, argv: List[str]) -> None: info(f"Generating sys_mprotect({sect.page_start:#x}, {size:#x}, " f"'{perm!s}') stub for arch {get_arch()}") - stub = self.get_stub_by_arch(sect.page_start, size, perm) + stub = self.get_arch_and_mode(sect.page_start, size, perm) if stub is None: err("Failed to generate mprotect opcodes") return @@ -250,8 +255,25 @@ def do_invoke(self, argv: List[str]) -> None: gdb.execute("continue") return - def get_stub_by_arch(self, addr: int, size: int, perm: Permission) -> Union[str, bytearray, None]: + def get_arch_and_mode(self, addr: int, size: int, perm: Permission) -> Union[bytes, None]: code = gef.arch.mprotect_asm(addr, size, perm) - arch, mode = get_keystone_arch() - raw_insns = ks_assemble(code, arch, mode, raw=True) - return raw_insns + + # arch, mode and endianness as seen by GEF + arch_s = gef.arch.arch.upper() + mode_s = gef.arch.mode.upper() + endian_s = repr(gef.arch.endianness).upper() + + if arch_s not in VALID_ARCH_MODES: + raise AttributeError(f"invalid arch '{arch_s}'") + + # convert them to arch, mode and endianness for keystone + ks_arch: int = getattr(keystone, f"KS_ARCH_{arch_s}") + if arch_s == "ARM64" and mode_s == "": + ks_mode = 0 + else: + ks_mode: int = getattr(keystone, f"KS_MODE_{mode_s}") + ks_endian: int = getattr( + keystone, f"KS_MODE_{endian_s}") + + addr = gef.arch.pc + return ks_assemble(code, ks_arch, ks_mode | ks_endian, addr) diff --git a/scripts/visualize_heap.py b/scripts/visualize_heap.py index 28d0420..80cc2f9 100644 --- a/scripts/visualize_heap.py +++ b/scripts/visualize_heap.py @@ -1,33 +1,56 @@ +""" +Provide an ascii-based graphical representation of the heap layout. + +""" __AUTHOR__ = "hugsy" -__VERSION__ = 0.2 +__VERSION__ = 0.3 +__LICENSE__ = "MIT" import os +from functools import lru_cache +from typing import TYPE_CHECKING, Dict, List, Tuple + import gdb +if TYPE_CHECKING: + from . import * + from . import gdb + def fastbin_index(sz): return (sz >> 4) - 2 if gef.arch.ptrsize == 8 else (sz >> 3) - 2 def nfastbins(): - return fastbin_index( (80 * gef.arch.ptrsize // 4)) - 1 + return fastbin_index((80 * gef.arch.ptrsize // 4)) - 1 def get_tcache_count(): - if gef.libc.version < (2, 27): + version = gef.libc.version + if version is None: + raise RuntimeError("Cannot get the libc version") + if version < (2, 27): return 0 - count_addr = gef.heap.base + 2*gef.arch.ptrsize - count = p8(count_addr) if gef.libc.version < (2, 30) else p16(count_addr) + base = gef.heap.base_address + if not base: + raise RuntimeError( + "Failed to get the heap base address. Heap not initialized?") + count_addr = base + 2*gef.arch.ptrsize + count = p8(count_addr) if version < (2, 30) else p16(count_addr) return count @lru_cache(128) -def collect_known_values() -> dict: +def collect_known_values() -> Dict[int, str]: arena = gef.heap.main_arena - result = {} # format is { 0xaddress : "name" ,} + result: Dict[int, str] = {} # format is { 0xaddress : "name" ,} + + version = gef.libc.version + if not version: + return result # tcache - if gef.libc.version() >= (2, 27): + if version >= (2, 27): tcache_addr = GlibcHeapTcachebinsCommand.find_tcache() for i in range(GlibcHeapTcachebinsCommand.TCACHE_MAX_BINS): chunk, _ = GlibcHeapTcachebinsCommand.tcachebin(tcache_addr, i) @@ -35,9 +58,11 @@ def collect_known_values() -> dict: while True: if chunk is None: break - result[chunk.data_address] = "tcachebins[{}/{}] (size={:#x})".format(i, j, (i+1)*0x10+0x10) + sz = (i+1)*0x10+0x10 + result[chunk.data_address] = f"tcachebins[{i}/{j}] (size={sz:#x})" next_chunk_address = chunk.get_fwd_ptr(True) - if not next_chunk_address: break + if not next_chunk_address: + break next_chunk = GlibcChunk(next_chunk_address) j += 1 chunk = next_chunk @@ -49,27 +74,33 @@ def collect_known_values() -> dict: while True: if chunk is None: break - result[chunk.data_address] = "fastbins[{}/{}]".format(i, j) + result[chunk.data_address] = f"fastbins[{i}/{j}]" next_chunk_address = chunk.get_fwd_ptr(True) - if not next_chunk_address: break + if not next_chunk_address: + break next_chunk = GlibcChunk(next_chunk_address) j += 1 chunk = next_chunk # other bins for name in ["unorderedbins", "smallbins", "largebins"]: - fw, bk = arena.bin(i) - if bk==0x00 and fw==0x00: continue + + fw, bk = arena.bin(j) + if bk == 0x00 and fw == 0x00: + continue head = GlibcChunk(bk, from_base=True).fwd - if head == fw: continue + if head == fw: + continue chunk = GlibcChunk(head, from_base=True) j = 0 while True: - if chunk is None: break - result[chunk.data_address] = "{}[{}/{}]".format(name, i, j) + if chunk is None: + break + result[chunk.data_address] = f"{name}[{i}/{j}]" next_chunk_address = chunk.get_fwd_ptr(True) - if not next_chunk_address: break + if not next_chunk_address: + break next_chunk = GlibcChunk(next_chunk_address, from_base=True) j += 1 chunk = next_chunk @@ -78,13 +109,13 @@ def collect_known_values() -> dict: @lru_cache(128) -def collect_known_ranges()->list: +def collect_known_ranges() -> List[Tuple[range, str]]: result = [] for entry in gef.memory.maps: if not entry.path: continue path = os.path.basename(entry.path) - result.append( (range(entry.page_start, entry.page_end), path) ) + result.append((range(entry.page_start, entry.page_end), path)) return result @@ -93,27 +124,27 @@ class VisualizeHeapChunksCommand(GenericCommand): """Visual helper for glibc heap chunks""" _cmdline_ = "visualize-libc-heap-chunks" - _syntax_ = "{:s}".format(_cmdline_) - _aliases_ = ["heap-view",] - _example_ = "{:s}".format(_cmdline_) + _syntax_ = f"{_cmdline_:s}" + _aliases_ = ["heap-view", ] + _example_ = f"{_cmdline_:s}" def __init__(self): - super(VisualizeHeapChunksCommand, self).__init__(complete=gdb.COMPLETE_SYMBOL) + super().__init__(complete=gdb.COMPLETE_SYMBOL) return @only_if_gdb_running - def do_invoke(self, argv): + def do_invoke(self, _): ptrsize = gef.arch.ptrsize heap_base_address = gef.heap.base_address arena = gef.heap.main_arena - if not arena.top: + if not arena.top or not heap_base_address: err("The heap has not been initialized") return - top = align_address(int(arena.top)) + top = align_address(int(arena.top)) base = align_address(heap_base_address) - colors = [ "cyan", "red", "yellow", "blue", "green" ] + colors = ["cyan", "red", "yellow", "blue", "green"] cur = GlibcChunk(base, from_base=True) idx = 0 @@ -122,24 +153,25 @@ def do_invoke(self, argv): while True: base = cur.base_address + addr = cur.data_address aggregate_nuls = 0 if base == top: gef_print( - "{} {} {}".format(format_address(addr), format_address(read_int_from_memory(addr)) , Color.colorify(LEFT_ARROW + "Top Chunk", "red bold")), - "{} {} {}".format(format_address(addr+ptrsize), format_address(read_int_from_memory(addr+ptrsize)) , Color.colorify(LEFT_ARROW + "Top Chunk Size", "red bold")) + f"{format_address(addr)} {format_address(gef.memory.read_integer(addr))} {Color.colorify(LEFT_ARROW + 'Top Chunk', 'red bold')}\n" + f"{format_address(addr+ptrsize)} {format_address(gef.memory.read_integer(addr+ptrsize))} {Color.colorify(LEFT_ARROW + 'Top Chunk Size', 'red bold')}" ) break if cur.size == 0: - warn("incorrect size, heap is corrupted") + warn("Unexpected size for chunk, cannot pursue. Corrupted heap?") break - for off in range(0, cur.size, cur.ptrsize): + for off in range(0, cur.size, ptrsize): addr = base + off value = gef.memory.read_integer(addr) if value == 0: - if off != 0 and off != cur.size - cur.ptrsize: + if off != 0 and off != cur.size - ptrsize: aggregate_nuls += 1 if aggregate_nuls > 1: continue @@ -152,26 +184,26 @@ def do_invoke(self, argv): ) aggregate_nuls = 0 - text = "".join([chr(b) if 0x20 <= b < 0x7F else "." for b in gef.memory.read(addr, cur.ptrsize)]) + text = "".join( + [chr(b) if 0x20 <= b < 0x7F else "." for b in gef.memory.read(addr, ptrsize)]) line = f"{format_address(addr)} {Color.colorify(format_address(value), colors[idx % len(colors)])}" - line+= f" {text}" + line += f" {text}" derefs = dereference_from(addr) if len(derefs) > 2: - line+= f" [{LEFT_ARROW}{derefs[-1]}]" + line += f" [{LEFT_ARROW}{derefs[-1]}]" if off == 0: - line+= f" Chunk[{idx}]" - if off == cur.ptrsize: - - line+= f" {value&~7}" \ + line += f" Chunk[{idx}]" + if off == ptrsize: + line += f" {value&~7 }" \ f"{'|NON_MAIN_ARENA' if value&4 else ''}" \ f"{'|IS_MMAPED' if value&2 else ''}" \ - f"{'PREV_INUSE' if value&1 else ''}" + f"{'|PREV_INUSE' if value&1 else ''}" # look in mapping for x in known_ranges: if value in x[0]: - line+= f" (in {Color.redify(x[1])})" + line += f" (in {Color.redify(x[1])})" # look in known values if value in known_values: @@ -191,4 +223,3 @@ def do_invoke(self, argv): cur = next_chunk idx += 1 return - diff --git a/tests/binaries/Makefile b/tests/binaries/Makefile index 009d20a..d0fd53a 100644 --- a/tests/binaries/Makefile +++ b/tests/binaries/Makefile @@ -24,7 +24,7 @@ all: $(LINKED) %.out : %.c - @echo "[+] Building '$@'" + @echo "[+] Building '$(TMPDIR)/$@'" @$(CC) $(CFLAGS) $(EXTRA_FLAGS) -o $(TMPDIR)/$@ $? $(LDFLAGS) clean : diff --git a/tests/binaries/visualize_heap.c b/tests/binaries/visualize_heap.c new file mode 100644 index 0000000..57a2c01 --- /dev/null +++ b/tests/binaries/visualize_heap.c @@ -0,0 +1,26 @@ +/** + * + */ + +#include +#include +#include +#include + +#include "utils.h" + +int main(int argc, char **argv) +{ + void *p = malloc(0x64); + (void)p; + void *r = malloc(0x28); + (void)r; + void *q = malloc(0x64); + (void)q; + + puts("Much space, so heap!"); + fflush(stdout); + + DebugBreak(); + return 0; +} diff --git a/tests/commands/capstone_disassemble.py b/tests/commands/capstone_disassemble.py index 86e8615..3984592 100644 --- a/tests/commands/capstone_disassemble.py +++ b/tests/commands/capstone_disassemble.py @@ -3,32 +3,33 @@ """ import pytest -import capstone - -from tests.utils import ( - ARCH, - gdb_start_silent_cmd, - gdb_run_silent_cmd, - gdb_run_cmd, - GefUnitTestGeneric, - removeuntil, -) +from tests.utils import (ARCH, GefUnitTestGeneric, gdb_run_cmd, + gdb_run_silent_cmd, gdb_start_silent_cmd, removeuntil) @pytest.mark.skipif(ARCH in ("mips64el", "ppc64le", "riscv64"), reason=f"Skipped for {ARCH}") class CapstoneDisassembleCommand(GefUnitTestGeneric): """`capstone-disassemble` command test module""" + def setUp(self) -> None: + try: + import capstone # pylint: disable=W0611 + except ImportError: + pytest.skip("capstone-engine not available", + allow_module_level=True) + return super().setUp() + def test_cmd_capstone_disassemble(self): - self.assertNotIn("capstone", gdb_run_silent_cmd("gef missing")) self.assertFailIfInactiveSession(gdb_run_cmd("capstone-disassemble")) res = gdb_start_silent_cmd("capstone-disassemble") self.assertNoException(res) self.assertTrue(len(res.splitlines()) > 1) - self.assertFailIfInactiveSession(gdb_run_cmd("cs --show-opcodes")) - res = gdb_start_silent_cmd("cs --show-opcodes --length 5 $pc") + self.assertFailIfInactiveSession( + gdb_run_cmd("capstone-disassemble --show-opcodes")) + res = gdb_start_silent_cmd( + "capstone-disassemble --show-opcodes --length 5 $pc") self.assertNoException(res) self.assertTrue(len(res.splitlines()) >= 5) # jump to the output buffer diff --git a/tests/commands/keystone_assemble.py b/tests/commands/keystone_assemble.py index dabf957..a7abed5 100644 --- a/tests/commands/keystone_assemble.py +++ b/tests/commands/keystone_assemble.py @@ -4,13 +4,8 @@ import pytest -from tests.utils import ( - ARCH, - GefUnitTestGeneric, - gdb_run_silent_cmd, - gdb_start_silent_cmd, -) - +from tests.utils import (ARCH, GefUnitTestGeneric, gdb_run_silent_cmd, + gdb_start_silent_cmd) @pytest.mark.skipif(ARCH in ("mips64el", "ppc64le", "riscv64"), reason=f"Skipped for {ARCH}") @@ -19,12 +14,12 @@ class KeystoneAssembleCommand(GefUnitTestGeneric): def setUp(self) -> None: try: - import keystone # pylint: disable=W0611 + import keystone # pylint: disable=W0611 except ImportError: - pytest.skip("keystone-engine not available", allow_module_level=True) + pytest.skip("keystone-engine not available", + allow_module_level=True) return super().setUp() - def test_cmd_keystone_assemble(self): self.assertNotIn("keystone", gdb_run_silent_cmd("gef missing")) cmds = [ @@ -32,10 +27,6 @@ def test_cmd_keystone_assemble(self): "assemble --arch arm --mode arm --endian big add r0, r1, r2", "assemble --arch arm --mode thumb add r0, r1, r2", "assemble --arch arm --mode thumb --endian big add r0, r1, r2", - "assemble --arch arm --mode armv8 add r0, r1, r2", - "assemble --arch arm --mode armv8 --endian big add r0, r1, r2", - "assemble --arch arm --mode thumbv8 add r0, r1, r2", - "assemble --arch arm --mode thumbv8 --endian big add r0, r1, r2", "assemble --arch arm64 --mode 0 add x29, sp, 0; mov w0, 0; ret", "assemble --arch mips --mode mips32 add $v0, 1", "assemble --arch mips --mode mips32 --endian big add $v0, 1", diff --git a/tests/commands/set_permission.py b/tests/commands/set_permission.py index 5786ad7..2c9ea31 100644 --- a/tests/commands/set_permission.py +++ b/tests/commands/set_permission.py @@ -1,32 +1,29 @@ """ -set_permission command test module +`set-permission` command test module """ -import pytest import re -from tests.utils import ( - ARCH, - GefUnitTestGeneric, - _target, - gdb_run_cmd, - gdb_start_silent_cmd, -) +import pytest + +from tests.utils import (ARCH, GefUnitTestGeneric, _target, gdb_run_cmd, + gdb_start_silent_cmd) @pytest.mark.skipif(ARCH not in ("i686", "x86_64", "armv7l", "aarch64"), reason=f"Skipped for {ARCH}") class SetPermissionCommand(GefUnitTestGeneric): - """`set_permission` command test module""" + """`set-permission` command test module""" def setUp(self) -> None: try: - import keystone # pylint: disable=W0611 + import keystone # pylint: disable=W0611 + import unicorn # pylint: disable=W0611 except ImportError: - pytest.skip("keystone-engine not available", allow_module_level=True) + pytest.skip("keystone-engine not available", + allow_module_level=True) return super().setUp() - def test_cmd_set_permission(self): self.assertFailIfInactiveSession(gdb_run_cmd("set-permission")) target = _target("set-permission") @@ -41,7 +38,8 @@ def test_cmd_set_permission(self): res = gdb_start_silent_cmd(f"set-permission {stack_address:#x}", after=[f"xinfo {stack_address:#x}"], target=target) self.assertNoException(res) - line = [l.strip() for l in res.splitlines() if l.startswith("Permissions: ")][0] + line = [l.strip() for l in res.splitlines() + if l.startswith("Permissions: ")][0] self.assertEqual(line.split()[1], "rwx") res = gdb_start_silent_cmd("set-permission 0x1338000", target=target) @@ -62,12 +60,15 @@ def test_cmd_set_permission(self): "info registers all", "printf \"match_after\\n\"" ] - res = gdb_run_cmd("set-permission $sp", before=before, after=after, target=target) - matches = re.match(r"(?:.*match_before)(.+)(?:match_before.*)", res, flags=re.DOTALL) + res = gdb_run_cmd("set-permission $sp", before=before, + after=after, target=target) + matches = re.match( + r"(?:.*match_before)(.+)(?:match_before.*)", res, flags=re.DOTALL) if not matches: raise Exception("Unexpected output") regs_before = matches[1] - matches = re.match(r"(?:.*match_after)(.+)(?:match_after.*)", res, flags=re.DOTALL) + matches = re.match( + r"(?:.*match_after)(.+)(?:match_after.*)", res, flags=re.DOTALL) if not matches: raise Exception("Unexpected output") regs_after = matches[1] diff --git a/tests/commands/unicorn_emulate.py b/tests/commands/unicorn_emulate.py index c362a11..83778e4 100644 --- a/tests/commands/unicorn_emulate.py +++ b/tests/commands/unicorn_emulate.py @@ -4,12 +4,8 @@ import pytest -from tests.utils import ( - ARCH, - GefUnitTestGeneric, - _target, - gdb_run_silent_cmd, -) + +from tests.utils import ARCH, GefUnitTestGeneric, _target, gdb_run_silent_cmd @pytest.mark.skipif(ARCH not in ("i686", "x86_64", "armv7l", "aarch64"), @@ -19,12 +15,12 @@ class UnicornEmulateCommand(GefUnitTestGeneric): def setUp(self) -> None: try: - import unicorn # pylint: disable=W0611 + import unicorn # pylint: disable=W0611 except ImportError: - pytest.skip("unicorn-engine not available", allow_module_level=True) + pytest.skip("unicorn-engine not available", + allow_module_level=True) return super().setUp() - @pytest.mark.skipif(ARCH not in ["x86_64"], reason=f"Skipped for {ARCH}") def test_cmd_unicorn_emulate(self): nb_insn = 4 @@ -37,10 +33,12 @@ def test_cmd_unicorn_emulate(self): after = ["si"] start_marker = "= Starting emulation =" end_marker = "Final registers" - res = gdb_run_silent_cmd(cmd, target=target, before=before, after=after) + res = gdb_run_silent_cmd( + cmd, target=target, before=before, after=after) self.assertNoException(res) self.assertNotIn("Emulation failed", res) self.assertIn(start_marker, res) self.assertIn(end_marker, res) - insn_executed = len(res[res.find(start_marker):res.find(end_marker)].splitlines()[1:-1]) + insn_executed = len( + res[res.find(start_marker):res.find(end_marker)].splitlines()[1:-1]) self.assertTrue(insn_executed >= nb_insn) diff --git a/tests/commands/visualize_heap.py b/tests/commands/visualize_heap.py new file mode 100644 index 0000000..d2abbe2 --- /dev/null +++ b/tests/commands/visualize_heap.py @@ -0,0 +1,27 @@ +""" +`visualize-libc-heap-chunks` command test module +""" + + +import pytest + +from tests.utils import (ARCH, GefUnitTestGeneric, _target, gdb_run_cmd, + gdb_run_silent_cmd) + + +class VisualizeLibcHeapChunksCommand(GefUnitTestGeneric): + """`visualize-libc-heap-chunks` command test module""" + + @pytest.mark.skipif(ARCH not in ["x86_64", "i686"], reason=f"Skipped for {ARCH}") + def test_cmd_heap_view(self): + target = _target("visualize_heap") + cmd = "visualize-libc-heap-chunks" + self.assertFailIfInactiveSession( + gdb_run_cmd(cmd, target=target, after=["gef"])) + + res = gdb_run_silent_cmd(f"{cmd}", target=target) + self.assertNoException(res) + + for i in range(4): + self.assertIn( + f"0x0000000000000000 ........ Chunk[{i}]", res) diff --git a/tests/utils.py b/tests/utils.py index a329508..5b6138b 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,6 +2,7 @@ Utility functions for testing """ +import enum import os import pathlib import platform @@ -9,47 +10,52 @@ import subprocess import tempfile import unittest -from urllib.request import urlopen import warnings -import enum - -from typing import Iterable, Union, List, Optional +from typing import Iterable, List, Optional, Union +from urllib.request import urlopen TMPDIR = pathlib.Path(tempfile.gettempdir()) ARCH = (os.getenv("GEF_CI_ARCH") or platform.machine()).lower() BIN_SH = pathlib.Path("/bin/sh") CI_VALID_ARCHITECTURES_32B = ("i686", "armv7l") -CI_VALID_ARCHITECTURES_64B = ("x86_64", "aarch64", "mips64el", "ppc64le", "riscv64") +CI_VALID_ARCHITECTURES_64B = ( + "x86_64", "aarch64", "mips64el", "ppc64le", "riscv64") CI_VALID_ARCHITECTURES = CI_VALID_ARCHITECTURES_64B + CI_VALID_ARCHITECTURES_32B COVERAGE_DIR = os.getenv("COVERAGE_DIR", "") +STRIP_ANSI_DEFAULT = True DEFAULT_CONTEXT = "-code -stack" DEFAULT_TARGET = TMPDIR / "default.out" GEF_DEFAULT_PROMPT = "gef➤ " GEF_DEFAULT_TEMPDIR = "/tmp/gef" -GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "gef.py")) -STRIP_ANSI_DEFAULT = True +GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "../gef/gef.py")) +GEF_EXTRAS_TEST_DIR_PATH = pathlib.Path(__file__).absolute().parent +GEF_EXTRAS_ROOT_PATH = GEF_EXTRAS_TEST_DIR_PATH.parent +GEF_EXTRAS_SCRIPTS_PATH = GEF_EXTRAS_ROOT_PATH / "scripts" +GEF_EXTRAS_OS_PATH = GEF_EXTRAS_ROOT_PATH / "os" +GEF_EXTRAS_STRUCTS_PATH = GEF_EXTRAS_ROOT_PATH / "structs" CommandType = Union[str, Iterable[str]] + class Color(enum.Enum): """Used to colorify terminal output.""" - NORMAL = "\x1b[0m" - GRAY = "\x1b[1;38;5;240m" - LIGHT_GRAY = "\x1b[0;37m" - RED = "\x1b[31m" - GREEN = "\x1b[32m" - YELLOW = "\x1b[33m" - BLUE = "\x1b[34m" - PINK = "\x1b[35m" - CYAN = "\x1b[36m" - BOLD = "\x1b[1m" - UNDERLINE = "\x1b[4m" - UNDERLINE_OFF = "\x1b[24m" - HIGHLIGHT = "\x1b[3m" - HIGHLIGHT_OFF = "\x1b[23m" - BLINK = "\x1b[5m" - BLINK_OFF = "\x1b[25m" + NORMAL = "\x1b[0m" + GRAY = "\x1b[1;38;5;240m" + LIGHT_GRAY = "\x1b[0;37m" + RED = "\x1b[31m" + GREEN = "\x1b[32m" + YELLOW = "\x1b[33m" + BLUE = "\x1b[34m" + PINK = "\x1b[35m" + CYAN = "\x1b[36m" + BOLD = "\x1b[1m" + UNDERLINE = "\x1b[4m" + UNDERLINE_OFF = "\x1b[24m" + HIGHLIGHT = "\x1b[3m" + HIGHLIGHT_OFF = "\x1b[23m" + BLINK = "\x1b[5m" + BLINK_OFF = "\x1b[25m" class GdbAssertionError(AssertionError): @@ -77,14 +83,16 @@ def assertNoException(buf): or "'gdb.error'" in buf or "Exception raised" in buf or "failed to execute properly, reason:" in buf): - raise GdbAssertionError(f"Unexpected GDB Exception raised in {buf}") + raise GdbAssertionError( + f"Unexpected GDB Exception raised in {buf}") if "is deprecated and will be removed in a feature release." in buf: lines = [l for l in buf.splitlines() if "is deprecated and will be removed in a feature release." in l] deprecated_api_names = {x.split()[1] for x in lines} warnings.warn( - UserWarning(f"Use of deprecated API(s): {', '.join(deprecated_api_names)}") + UserWarning( + f"Use of deprecated API(s): {', '.join(deprecated_api_names)}") ) @staticmethod @@ -119,7 +127,8 @@ def gdb_run_cmd(cmd: CommandType, before: CommandType = (), after: CommandType = before (resp. after) the command to test.""" command = ["gdb", "-q", "-nx"] if COVERAGE_DIR: - coverage_file = pathlib.Path(COVERAGE_DIR) / os.getenv("PYTEST_XDIST_WORKER", "gw0") + coverage_file = pathlib.Path( + COVERAGE_DIR) / os.getenv("PYTEST_XDIST_WORKER", "gw0") command += _add_command([ "pi from coverage import Coverage", f"pi cov = Coverage(data_file=\"{coverage_file}\"," @@ -129,6 +138,8 @@ def gdb_run_cmd(cmd: CommandType, before: CommandType = (), after: CommandType = command += _add_command([ f"source {GEF_PATH}", "gef config gef.debug True", + f"gef config gef.extra_plugins_dir {GEF_EXTRAS_SCRIPTS_PATH.absolute()}", + f"gef config pcustom.struct_path {GEF_EXTRAS_STRUCTS_PATH.absolute()}", ]) command += _add_command(before) command += _add_command(cmd) @@ -137,7 +148,8 @@ def gdb_run_cmd(cmd: CommandType, before: CommandType = (), after: CommandType = command += _add_command(["pi cov.stop()", "pi cov.save()"]) command += ["-ex", "quit", "--", str(target)] - lines = subprocess.check_output(command, stderr=subprocess.STDOUT).strip().splitlines() + lines = subprocess.check_output( + command, stderr=subprocess.STDOUT).strip().splitlines() output = b"\n".join(lines) result = None @@ -316,7 +328,6 @@ def removeuntil(substring: str, buffer: str, included: bool = False) -> str: return buffer[idx:] - def download_file(url: str) -> Optional[bytes]: """Download a file from the internet. From 4261e382648256fd36005391988c732c7e98f176 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 2 Jul 2022 13:56:48 -0700 Subject: [PATCH 22/39] Add `syscall-args` and `is-syscall` (#74) * [heap-view] ported to latest API + added test + added docs * Squashed commit of the following: commit bcb65677e5082883081626b4f2500cc3afa4941c Author: hugsy Date: Tue Jun 28 16:07:48 2022 -0700 [ci] visualize-heap fix compil unused-var warning commit 0d6d1eafed3a07beac4da7cc9785813fe8b52a3f Author: hugsy Date: Tue Jun 28 16:05:59 2022 -0700 [tests] using the same tempdir for gef and bins commit 4979e82e71d559cd00dabf147587e2976a56785b Author: hugsy Date: Tue Jun 28 15:59:13 2022 -0700 fixed `set-permission` commit 0b067bd98c4fbd85f7074236476163937f186d6a Author: hugsy Date: Tue Jun 28 15:46:28 2022 -0700 fixed `capstone-disassemble` commit 6baf7d7c01e36f06e9feb86c8044e92b18370ed9 Author: hugsy Date: Tue Jun 28 15:42:55 2022 -0700 fixed `keystone-assemble` * moved `is-syscall` and `syscall-args` to gef-extras * moved the docs for `is-syscall` and `syscall-args` to gef-extras --- docs/commands/is-syscall.md | 18 +++ docs/commands/syscall-args.md | 49 +++++++ mkdocs.yml | 3 + scripts/TEMPLATE | 9 ++ scripts/__init__.pyi | 4 +- scripts/capstone.py | 2 +- scripts/syscall_args/__init__.py | 134 ++++++++++++++++++ .../syscall_args/syscall-tables}/ARM.py | 0 .../syscall_args/syscall-tables}/ARM_OABI.py | 0 .../syscall_args/syscall-tables}/PowerPC.py | 0 .../syscall_args/syscall-tables}/PowerPC64.py | 0 .../syscall_args/syscall-tables}/SPARC.py | 0 .../syscall_args/syscall-tables}/SPARC64.py | 0 .../syscall_args/syscall-tables}/X86.py | 0 .../syscall_args/syscall-tables}/X86_64.py | 0 tests/binaries/syscall-args.c | 50 +++++++ tests/commands/syscall_args.py | 87 ++++++++++++ 17 files changed, 353 insertions(+), 3 deletions(-) create mode 100644 docs/commands/is-syscall.md create mode 100644 docs/commands/syscall-args.md create mode 100644 scripts/syscall_args/__init__.py rename {syscall-tables => scripts/syscall_args/syscall-tables}/ARM.py (100%) rename {syscall-tables => scripts/syscall_args/syscall-tables}/ARM_OABI.py (100%) rename {syscall-tables => scripts/syscall_args/syscall-tables}/PowerPC.py (100%) rename {syscall-tables => scripts/syscall_args/syscall-tables}/PowerPC64.py (100%) rename {syscall-tables => scripts/syscall_args/syscall-tables}/SPARC.py (100%) rename {syscall-tables => scripts/syscall_args/syscall-tables}/SPARC64.py (100%) rename {syscall-tables => scripts/syscall_args/syscall-tables}/X86.py (100%) rename {syscall-tables => scripts/syscall_args/syscall-tables}/X86_64.py (100%) create mode 100644 tests/binaries/syscall-args.c create mode 100644 tests/commands/syscall_args.py diff --git a/docs/commands/is-syscall.md b/docs/commands/is-syscall.md new file mode 100644 index 0000000..a288c5b --- /dev/null +++ b/docs/commands/is-syscall.md @@ -0,0 +1,18 @@ +## Command is-syscall ## + +`gef` can be used to determine whether the instruction to be executed next is a system call. + +To use it, simply run +``` +gef➤ is-syscall +``` + +If it is a system call, +``` +gef➤ is-syscall +[+] Current instruction is a syscall +``` + +Check this asciicast for visual example: + +[![asciicast](https://asciinema.org/a/BlrpsfzdLqNdycoxHuGkscYu6.png)](https://asciinema.org/a/BlrpsfzdLqNdycoxHuGkscYu6) diff --git a/docs/commands/syscall-args.md b/docs/commands/syscall-args.md new file mode 100644 index 0000000..9e670a2 --- /dev/null +++ b/docs/commands/syscall-args.md @@ -0,0 +1,49 @@ +## Command syscall-args ## + +Often it is troublesome to have to refer to syscall tables every time we +encounter a system call instruction. `gef` can be used to determine the system +call being invoked and the arguments being passed to it. Requires +[gef-extras](https://github.com/hugsy/gef-extras). + +To use it, simply run +``` +gef➤ syscall-args +``` + +For instance, +``` +───────────────────────────────────────────────────────────────────────────────────────────────────────[ registers ]──── +$rax : 0x0000000000000001 +$rbx : 0x0000000000000045 +$rcx : 0x00000000fbad2a84 +$rdx : 0x0000000000000045 +$rsp : 0x00007fffffffdbf8 → 0x00007ffff786f4bd → <_IO_file_write+45> test rax, rax +$rbp : 0x0000555555775510 → "alarm@192.168.0.100\t\t how2heap\t\t\t\t\t\t\t [...]" +$rsi : 0x0000555555775510 → "alarm@192.168.0.100\t\t how2heap\t\t\t\t\t\t\t [...]" +$rdi : 0x0000000000000001 +$rip : 0x00007ffff78de132 → syscall +$r8 : 0x0000555555783b44 → 0x0000000000000066 ("f"?) +$r9 : 0x0000000000000000 +$r10 : 0x0000000000002000 +$r11 : 0x00007fffffffb940 → 0x7669006666757473 ("stuff"?) +$r12 : 0x00007ffff7bab760 → 0x00000000fbad2a84 +$r13 : 0x0000000000000045 +$r14 : 0x00007ffff7ba6760 → 0x0000000000000000 +$r15 : 0x0000000000000045 +$eflags: [carry PARITY adjust ZERO sign trap INTERRUPT direction overflow resume virtualx86 identification] +$cs: 0x0033 $gs: 0x0000 $ss: 0x002b $es: 0x0000 $fs: 0x0000 $ds: 0x0000 + +... + +gef➤ syscall-args +[+] Detected syscall write + write(unsigned int fd, const char *buf, size_t count) +[+] Parameter Register Value + fd $rdi 0x1 + buf $rsi 0x555555775510 → "file1\t\t file2\t\t\t\t\t\t\t [...]" + count $rdx 0x45 +``` + +Check this asciicast for visual example: + +[![asciicast](https://asciinema.org/a/BlrpsfzdLqNdycoxHuGkscYu6.png)](https://asciinema.org/a/BlrpsfzdLqNdycoxHuGkscYu6) diff --git a/mkdocs.yml b/mkdocs.yml index d41f6fd..d8f3370 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,6 +12,7 @@ nav: - Home: index.md - Installation: install.md - Commands: + - assemble: commands/assemble.md - bincompare: commands/bincompare.md - bytearray: commands/bytearray.md @@ -21,9 +22,11 @@ nav: - exploit-template: commands/skel.md - ftrace: commands/ftrace.md - ida-rpyc: commands/ida-rpyc.md + - is-syscall: commands/is-syscall.md - peekpointers: commands/peekpointers.md - retdec: commands/retdec.md - ropper: commands/ropper.md - set-permission: commands/set-permission.md + - syscall-args: commands/syscall-args.md - visualize_heap: commands/visualize_heap.md - windbg: commands/windbg.md \ No newline at end of file diff --git a/scripts/TEMPLATE b/scripts/TEMPLATE index b2260d6..95b9c26 100644 --- a/scripts/TEMPLATE +++ b/scripts/TEMPLATE @@ -19,5 +19,14 @@ class MyCommand(GenericCommand): _cmdline_ = "my-command" _syntax_ = "{:s}".format(_cmdline_) + def pre_load(self) -> None: + super().pre_load() + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_NONE) + + def post_load(self) -> None: + super().post_load() + def do_invoke(self, argv: List[str]): return diff --git a/scripts/__init__.pyi b/scripts/__init__.pyi index 9cc268a..b36dec3 100644 --- a/scripts/__init__.pyi +++ b/scripts/__init__.pyi @@ -810,8 +810,8 @@ class NamedBreakpoint(gdb.Breakpoint): def register_external_context_pane(pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], Optional[str]]) -> None: - def display_pane(): ... - def pane_title(): ... + def display_pane() -> None: ... + def pane_title() -> str: ... def register(cls: Type["GenericCommand"]) -> Type["GenericCommand"]: ... diff --git a/scripts/capstone.py b/scripts/capstone.py index ff5adbe..8b91b6d 100644 --- a/scripts/capstone.py +++ b/scripts/capstone.py @@ -108,7 +108,7 @@ def __init__(self) -> None: def post_load(self) -> None: super().post_load() - if self["use-capstone"] is True: + if self["use-capstone"]: ctx = gef.gdb.commands["context"] assert isinstance(ctx, ContextCommand) ctx.instruction_iterator = cs_disassemble diff --git a/scripts/syscall_args/__init__.py b/scripts/syscall_args/__init__.py new file mode 100644 index 0000000..933d13f --- /dev/null +++ b/scripts/syscall_args/__init__.py @@ -0,0 +1,134 @@ +""" +If the $PC register is on a syscall, this command will display the arguments to that syscall from the known syscall definition. +""" + +__AUTHOR__ = "daniellimws" +__VERSION__ = 0.1 +__LICENSE__ = "MIT" + +import pathlib +import re +from importlib.machinery import SourceFileLoader +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +if TYPE_CHECKING: + from .. import * + +CURRENT_FILE = pathlib.Path(__file__) +CURRENT_DIRECTORY = CURRENT_FILE.parent +CONTEXT_PANE_INDEX = "syscall_args" +CONTEXT_PANE_DESCRIPTION = "Syscall Arguments" + + +@register +class IsSyscallCommand(GenericCommand): + """Tells whether the next instruction is a system call.""" + _cmdline_ = "is-syscall" + _syntax_ = _cmdline_ + + @only_if_gdb_running + def do_invoke(self, _: List[str]) -> None: + ok(f"Current instruction is{' ' if is_syscall(gef.arch.pc) else ' not '}a syscall") + return + + +@register +class SyscallArgsCommand(GenericCommand): + """Gets the syscall name and arguments based on the register values in the current state.""" + + _cmdline_ = "syscall-args" + _syntax_ = f"{_cmdline_:s}" + _example_ = f"{_cmdline_:s}" + + def __init__(self) -> None: + super().__init__(prefix=False, complete=gdb.COMPLETE_NONE) + self.__path: Optional[pathlib.Path] = None + path = CURRENT_DIRECTORY / "syscall-tables" + self["path"] = (str(path.absolute()), + "Path to store/load the syscall tables files") + return + + @property + def path(self) -> pathlib.Path: + if not self.__path: + path = pathlib.Path(self["path"]).expanduser() + if not path.is_dir(): + raise FileNotFoundError( + f"'{self.__path}' is not valid directory") + self.__path = path + return self.__path + + @only_if_gdb_running + def do_invoke(self, _: List[str]) -> None: + syscall_register = gef.arch.syscall_register + if not syscall_register: + err( + f"System call register not defined for architecture {gef.arch.arch}") + return + + color = gef.config["theme.table_heading"] + arch = gef.arch.__class__.__name__ + syscall_table = self.__get_syscall_table(arch) + + if is_syscall(gef.arch.pc): + # if $pc is before the `syscall` instruction is executed: + reg_value = gef.arch.register(syscall_register) + else: + # otherwise, try the previous instruction (case of using `catch syscall`) + previous_insn_addr = gdb_get_nth_previous_instruction_address( + gef.arch.pc, 1) + if not previous_insn_addr or not is_syscall(previous_insn_addr): + return + reg_value = gef.arch.register( + f"$orig_{syscall_register.lstrip('$')}") + + if reg_value not in syscall_table: + warn(f"There is no system call for {reg_value:#x}") + return + syscall_entry = syscall_table[reg_value] + + values = [gef.arch.register(param.reg) + for param in syscall_entry.params] + parameters = [s.param for s in syscall_entry.params] + registers = [s.reg for s in syscall_entry.params] + + info(f"Detected syscall {Color.colorify(syscall_entry.name, color)}") + gef_print(f" {syscall_entry.name}({', '.join(parameters)})") + + headers = ["Parameter", "Register", "Value"] + param_names = [re.split(r" |\*", p)[-1] for p in parameters] + info(Color.colorify("{:<20} {:<20} {}".format(*headers), color)) + for name, register, value in zip(param_names, registers, values): + line = f" {name:<20} {register:<20} {value:#x}" + addrs = dereference_from(value) + if len(addrs) > 1: + line += RIGHT_ARROW + RIGHT_ARROW.join(addrs[1:]) + gef_print(line) + return + + def __get_syscall_table(self, modname: str) -> Dict[str, Any]: + def load_module(modname: str) -> Any: + _fpath = self.path / f"{modname}.py" + if not _fpath.is_file(): + raise FileNotFoundError + _fullname = str(_fpath.absolute()) + return SourceFileLoader(modname, _fullname).load_module(None) + + _mod = load_module(modname) + return getattr(_mod, "syscall_table") + + +def pane_content() -> None: + gdb.execute("syscall-args") + return + + +def pane_title() -> str: + return CONTEXT_PANE_DESCRIPTION + + +# +# Register a callback to `syscall-args` to automatically detect when a syscall is hit +# +register_external_context_pane( + CONTEXT_PANE_INDEX, pane_content, pane_title_function=pane_title) diff --git a/syscall-tables/ARM.py b/scripts/syscall_args/syscall-tables/ARM.py similarity index 100% rename from syscall-tables/ARM.py rename to scripts/syscall_args/syscall-tables/ARM.py diff --git a/syscall-tables/ARM_OABI.py b/scripts/syscall_args/syscall-tables/ARM_OABI.py similarity index 100% rename from syscall-tables/ARM_OABI.py rename to scripts/syscall_args/syscall-tables/ARM_OABI.py diff --git a/syscall-tables/PowerPC.py b/scripts/syscall_args/syscall-tables/PowerPC.py similarity index 100% rename from syscall-tables/PowerPC.py rename to scripts/syscall_args/syscall-tables/PowerPC.py diff --git a/syscall-tables/PowerPC64.py b/scripts/syscall_args/syscall-tables/PowerPC64.py similarity index 100% rename from syscall-tables/PowerPC64.py rename to scripts/syscall_args/syscall-tables/PowerPC64.py diff --git a/syscall-tables/SPARC.py b/scripts/syscall_args/syscall-tables/SPARC.py similarity index 100% rename from syscall-tables/SPARC.py rename to scripts/syscall_args/syscall-tables/SPARC.py diff --git a/syscall-tables/SPARC64.py b/scripts/syscall_args/syscall-tables/SPARC64.py similarity index 100% rename from syscall-tables/SPARC64.py rename to scripts/syscall_args/syscall-tables/SPARC64.py diff --git a/syscall-tables/X86.py b/scripts/syscall_args/syscall-tables/X86.py similarity index 100% rename from syscall-tables/X86.py rename to scripts/syscall_args/syscall-tables/X86.py diff --git a/syscall-tables/X86_64.py b/scripts/syscall_args/syscall-tables/X86_64.py similarity index 100% rename from syscall-tables/X86_64.py rename to scripts/syscall_args/syscall-tables/X86_64.py diff --git a/tests/binaries/syscall-args.c b/tests/binaries/syscall-args.c new file mode 100644 index 0000000..2de7f36 --- /dev/null +++ b/tests/binaries/syscall-args.c @@ -0,0 +1,50 @@ +/** + * syscall-args.c + * -*- mode: c -*- + * -*- coding: utf-8 -*- + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +#define __NR_read 0 + +void openfile() +{ + int ret; + size_t size = 256; + char buf[256] = {0}; + int fd = openat(AT_FDCWD, "/etc/passwd", O_RDONLY); + if(fd != -1){ + close(fd); +#if defined(__x86_64__) || defined(__amd64__) || defined(__i386) || defined(i386) || defined(__i386__) + __asm__ volatile + ( +#if defined(__i386) || defined(i386) || defined(__i386__) + "int $0x80" +#else + "syscall" +#endif + : "=a" (ret) + : "0"(__NR_read), "b"(fd), "c"(buf), "d"(size) + : "memory" + ); +#else + DebugBreak(); +#endif + } +} + + +int main(int argc, char** argv, char** envp) +{ + openfile(); + return EXIT_SUCCESS; +} diff --git a/tests/commands/syscall_args.py b/tests/commands/syscall_args.py new file mode 100644 index 0000000..a97e7fb --- /dev/null +++ b/tests/commands/syscall_args.py @@ -0,0 +1,87 @@ +""" +`syscall-args` command test module +""" + +import pathlib +import tempfile + +import pytest + +from tests.utils import (ARCH, GEF_DEFAULT_TEMPDIR, GefUnitTestGeneric, + _target, download_file, gdb_run_cmd, + gdb_start_silent_cmd, removeafter, removeuntil) + + +@pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") +class SyscallArgsCommand(GefUnitTestGeneric): + """`syscall-args` command test module""" + + @pytest.mark.online + def setUp(self) -> None: + # + # `syscall-args.out` only work for x86_64 and i686 architectures + # + self.tempdirfd = tempfile.TemporaryDirectory( + prefix=GEF_DEFAULT_TEMPDIR) + self.tempdirpath = pathlib.Path(self.tempdirfd.name).absolute() + # download some syscall tables from gef-extras + base = "https://raw.githubusercontent.com/hugsy/gef-extras/main/syscall-tables" + # todo: maybe add "PowerPC", "PowerPC64", "SPARC", "SPARC64" + for arch in ("ARM", "ARM_OABI", "X86", "X86_64"): + url = f"{base}/{arch}.py" + data = download_file(url) + if not data: + raise Exception(f"Failed to download {arch}.py ({url})") + fpath = self.tempdirpath / f"{arch}.py" + with fpath.open("wb") as fd: + fd.write(data) + return super().setUp() + + def tearDown(self) -> None: + self.tempdirfd.cleanup() + return + + def test_cmd_syscall_args(self): + self.assertFailIfInactiveSession(gdb_run_cmd("syscall-args")) + before = ( + f"gef config syscall-args.path {self.tempdirpath.absolute()}",) + after = ("continue", "syscall-args") + res = gdb_start_silent_cmd("catch syscall openat", + before=before, + after=after, + target=_target("syscall-args"),) + self.assertNoException(res) + self.assertIn("Detected syscall open", res) + + +@pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") +class IsSyscallCommand(GefUnitTestGeneric): + """`is-syscall` command test module""" + + def setUp(self) -> None: + self.syscall_location = None + res = gdb_run_cmd("disassemble openfile", + target=_target("syscall-args")) + start_str = "Dump of assembler code for function main:\n" + end_str = "End of assembler dump." + disass_code = removeafter(end_str, res) + disass_code = removeuntil(start_str, disass_code) + lines = disass_code.splitlines() + for line in lines: + parts = [x.strip() for x in line.split(maxsplit=3)] + self.assertGreaterEqual(len(parts), 3) + if ARCH == "x86_64" and parts[2] == "syscall": + self.syscall_location = parts[1].lstrip('<').rstrip('>:') + break + if ARCH == "i686" and parts[2] == "int" and parts[3] == "0x80": + self.syscall_location = parts[1].lstrip('<').rstrip('>:') + break + return super().setUp() + + def test_cmd_is_syscall(self): + self.assertFailIfInactiveSession(gdb_run_cmd("is-syscall")) + bp_loc = f"*(openfile{self.syscall_location})" + res = gdb_run_cmd("is-syscall", target=_target("syscall-args"), + before=(f"break {bp_loc}", "run"),) + self.assertNoException(res) + self.assertIn("Current instruction is a syscall", res) From ab696266c46eda2a0da0cec0935072cca3d2b422 Mon Sep 17 00:00:00 2001 From: hugsy Date: Mon, 4 Jul 2022 20:52:02 -0700 Subject: [PATCH 23/39] Update run-tests.yml (#78) --- .github/workflows/run-tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 46cf656..cb423b6 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -19,8 +19,7 @@ jobs: os: - ubuntu-20.04 - ubuntu-18.04 - # - [self-hosted, linux, ARM64] - # - [self-hosted, linux, ARM] + - ubuntu-22.04 name: "Run Unit tests on ${{ matrix.os }}" runs-on: ${{ matrix.os }} defaults: From 2744d5924ba148dfe0c59c22aae4aa8e1a713f96 Mon Sep 17 00:00:00 2001 From: hugsy Date: Mon, 4 Jul 2022 20:56:10 -0700 Subject: [PATCH 24/39] Move `glibc-function-args` to GEF-Extras (#75) * make syscall args pane conditional * moved libc_args to gef-extras, made it a conditional context pane * fixed `cs-dis` too --- scripts/__init__.pyi | 4 +- scripts/capstone.py | 32 ++-- scripts/libc_function_args/__init__.py | 140 ++++++++++++++++++ .../tables}/generate_glibc_args_json.py | 0 .../libc_function_args/tables}/x86_32.json | 0 .../libc_function_args/tables}/x86_64.json | 0 scripts/syscall_args/__init__.py | 11 +- 7 files changed, 173 insertions(+), 14 deletions(-) create mode 100644 scripts/libc_function_args/__init__.py rename {glibc-function-args => scripts/libc_function_args/tables}/generate_glibc_args_json.py (100%) rename {glibc-function-args => scripts/libc_function_args/tables}/x86_32.json (100%) rename {glibc-function-args => scripts/libc_function_args/tables}/x86_64.json (100%) diff --git a/scripts/__init__.pyi b/scripts/__init__.pyi index b36dec3..755ef69 100644 --- a/scripts/__init__.pyi +++ b/scripts/__init__.pyi @@ -809,7 +809,7 @@ class NamedBreakpoint(gdb.Breakpoint): def stop(self) -> bool: ... -def register_external_context_pane(pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], Optional[str]]) -> None: +def register_external_context_pane(pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], Optional[str]], condition: Optional[Callable[[], bool]]) -> None: def display_pane() -> None: ... def pane_title() -> str: ... @@ -1647,7 +1647,7 @@ class GefHeapManager(GefManager): class GefSetting: def __init__(self, value: Any, - cls: Optional[type] = None, description: Optional[str] = None) -> None: ... + cls: Optional[type] = None, description: Optional[str] = None, hooks: Optional[Dict[str, Callable]] = None) -> None: ... class GefSettingsManager(dict): diff --git a/scripts/capstone.py b/scripts/capstone.py index 8b91b6d..8710263 100644 --- a/scripts/capstone.py +++ b/scripts/capstone.py @@ -3,8 +3,8 @@ __VERSION__ = 0.2 __LICENSE__ = "MIT" -import sys -from typing import TYPE_CHECKING, Any, Generator, List, Optional, Tuple +from typing import (TYPE_CHECKING, Any, Callable, Generator, List, Optional, + Tuple) import capstone @@ -102,16 +102,30 @@ class CapstoneDisassembleCommand(GenericCommand): def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION) - self["use-capstone"] = (False, - "Replace the GDB disassembler in the `context` with Capstone") + gef.config[f"{self._cmdline_}.use-capstone"] = GefSetting( + False, bool, "Replace the GDB disassembler in the `context` with Capstone", hooks={"on_write": self.switch_disassembler}) + self.__original_disassembler: Optional[Callable[[ + int, int, Any], Generator[Instruction, None, None]]] = None return - def post_load(self) -> None: - super().post_load() - if self["use-capstone"]: - ctx = gef.gdb.commands["context"] - assert isinstance(ctx, ContextCommand) + def switch_disassembler(self) -> None: + ctx = gef.gdb.commands["context"] + assert isinstance(ctx, ContextCommand) + if gef.config[f"{self._cmdline_}.use-capstone"] == True: + self.__original_disassembler = ctx.instruction_iterator ctx.instruction_iterator = cs_disassemble + else: + # `use-capstone` set to False + if ctx.instruction_iterator == cs_disassemble and self.__original_disassembler: + # restore the original + ctx.instruction_iterator = self.__original_disassembler + return + + def __del__(self): + ctx = gef.gdb.commands["context"] + assert isinstance(ctx, ContextCommand) + if ctx.instruction_iterator == cs_disassemble and self.__original_disassembler: + ctx.instruction_iterator = self.__original_disassembler return @only_if_gdb_running diff --git a/scripts/libc_function_args/__init__.py b/scripts/libc_function_args/__init__.py new file mode 100644 index 0000000..287131a --- /dev/null +++ b/scripts/libc_function_args/__init__.py @@ -0,0 +1,140 @@ +""" +Conditional pane that will be shown only when a call is hit: it will try to collect the +libc parameter names of the function, and display them +""" + +__AUTHOR__ = "daniellimws" +__VERSION__ = 0.1 +__LICENSE__ = "MIT" + +import json +import pathlib +import re +from typing import TYPE_CHECKING, Dict + +if TYPE_CHECKING: + from .. import * + + +GLIBC_FUNCTION_ARGS_CURRENT_FILE = pathlib.Path(__file__) +GLIBC_FUNCTION_ARGS_CURRENT_DIRECTORY = GLIBC_FUNCTION_ARGS_CURRENT_FILE.parent +GLIBC_FUNCTION_ARGS_CONTEXT_PANE_INDEX = "libc_function_args" +GLIBC_FUNCTION_ARGS_CONTEXT_PANE_DESCRIPTION = "Glibc Function Arguments" + + +class GlibcFunctionArguments: + # + # This table will be populate lazily + # + argument_table: Dict[str, Dict[str, Dict[str, str]]] = {} + + @staticmethod + def load_libc_args() -> bool: + """Load the LIBC function arguments. Returns `True` on success, `False` or an Exception otherwise.""" + global gef + + # load libc function arguments' definitions + path = pathlib.Path( + GLIBC_FUNCTION_ARGS_CURRENT_DIRECTORY).expanduser().absolute() + if not path.exists(): + raise RuntimeError( + "Config `context.libc_args_path` set but it's not a directory") + + _arch_mode = f"{gef.arch.arch.lower()}_{gef.arch.mode}" + _libc_args_file = path / f"tables/{_arch_mode}.json" + + # current arch and mode already loaded + if _arch_mode in GlibcFunctionArguments.argument_table: + return True + + GlibcFunctionArguments.argument_table[_arch_mode] = {} + try: + with _libc_args_file.open() as _libc_args: + GlibcFunctionArguments.argument_table[_arch_mode] = json.load( + _libc_args) + return True + except FileNotFoundError: + warn( + f"Config context.libc_args is set but definition cannot be loaded: file {_libc_args_file} not found") + except json.decoder.JSONDecodeError as e: + warn( + f"Config context.libc_args is set but definition cannot be loaded from file {_libc_args_file}: {e}") + GlibcFunctionArguments.argument_table[_arch_mode] = {} + return False + + @staticmethod + def only_if_call() -> bool: + insn = gef_current_instruction(gef.arch.pc) + return gef.arch.is_call(insn) + + @staticmethod + def pane_title() -> str: + return GLIBC_FUNCTION_ARGS_CONTEXT_PANE_DESCRIPTION + + @staticmethod + def pane_content() -> None: + function_name = GlibcFunctionArguments.extract_called_function_name() + + if not GlibcFunctionArguments.argument_table: + # + # The table has been populated, do it now + # + GlibcFunctionArguments.load_libc_args() + + nb_argument = None + _arch_mode = f"{gef.arch.arch.lower()}_{gef.arch.mode}" + function_basename = None + if not function_name.endswith("@plt") or function_name.endswith("@got.plt"): + return + function_basename = function_name.split("@")[0] + nb_argument = len( + GlibcFunctionArguments.argument_table[_arch_mode][function_basename]) + + args = [] + for i in range(nb_argument): + _key, _values = gef.arch.get_ith_parameter(i, in_func=False) + if not _values: + args.append(f"{_key}: ") + continue + _values = RIGHT_ARROW.join(dereference_from(_values)) + args.append( + f"\t{_key} = {_values} /* {GlibcFunctionArguments.argument_table[_arch_mode][function_basename][_key]}) */") + + gef_print(f"{function_name} (\n", + '\n'.join(args), + "\n)") + return + + @staticmethod + def extract_called_function_name() -> str: + pc = gef.arch.pc + insn = gef_current_instruction(pc) + if not gef.arch.is_call(insn): + raise RuntimeError("Not a call") + + size2type = { + 1: "BYTE", + 2: "WORD", + 4: "DWORD", + 8: "QWORD", + } + + if insn.operands[-1].startswith(size2type[gef.arch.ptrsize]+" PTR"): + function_name = "*" + insn.operands[-1].split()[-1] + elif "$"+insn.operands[0] in gef.arch.all_registers: + function_name = f"*{gef.arch.register('$' + insn.operands[0]):#x}" + else: + ops = " ".join(insn.operands) + if "<" in ops and ">" in ops: + function_name = re.sub(r".*<([^\(> ]*).*", r"\1", ops) + else: + function_name = re.sub(r".*(0x[a-fA-F0-9]*).*", r"\1", ops) + + return function_name + + +# +# Register the context pane +# +register_external_context_pane( + GLIBC_FUNCTION_ARGS_CONTEXT_PANE_INDEX, GlibcFunctionArguments.pane_content, pane_title_function=GlibcFunctionArguments.pane_title, condition=GlibcFunctionArguments.only_if_call) diff --git a/glibc-function-args/generate_glibc_args_json.py b/scripts/libc_function_args/tables/generate_glibc_args_json.py similarity index 100% rename from glibc-function-args/generate_glibc_args_json.py rename to scripts/libc_function_args/tables/generate_glibc_args_json.py diff --git a/glibc-function-args/x86_32.json b/scripts/libc_function_args/tables/x86_32.json similarity index 100% rename from glibc-function-args/x86_32.json rename to scripts/libc_function_args/tables/x86_32.json diff --git a/glibc-function-args/x86_64.json b/scripts/libc_function_args/tables/x86_64.json similarity index 100% rename from glibc-function-args/x86_64.json rename to scripts/libc_function_args/tables/x86_64.json diff --git a/scripts/syscall_args/__init__.py b/scripts/syscall_args/__init__.py index 933d13f..e661f57 100644 --- a/scripts/syscall_args/__init__.py +++ b/scripts/syscall_args/__init__.py @@ -118,12 +118,17 @@ def load_module(modname: str) -> Any: return getattr(_mod, "syscall_table") -def pane_content() -> None: +def __syscall_args_pane_condition() -> bool: + insn = gef_current_instruction(gef.arch.pc) + return is_syscall(insn) + + +def __syscall_args_pane_content() -> None: gdb.execute("syscall-args") return -def pane_title() -> str: +def __syscall_args_pane_title() -> str: return CONTEXT_PANE_DESCRIPTION @@ -131,4 +136,4 @@ def pane_title() -> str: # Register a callback to `syscall-args` to automatically detect when a syscall is hit # register_external_context_pane( - CONTEXT_PANE_INDEX, pane_content, pane_title_function=pane_title) + CONTEXT_PANE_INDEX, __syscall_args_pane_content, pane_title_function=__syscall_args_pane_title, condition=__syscall_args_pane_condition) From 35eaf9ddb1702d268cdf29d069e1422f26423c82 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 9 Jul 2022 20:28:24 -0700 Subject: [PATCH 25/39] Fix `visualize_heap` (#77) * fixed `visualize_heap` * using correct representation of chunk flags * docs update --- docs/commands/visualize_heap.md | 2 +- docs/commands/windbg.md | 12 +-- scripts/__init__.pyi | 3 +- scripts/visualize_heap.py | 163 ++++++++++++++++--------------- tests/commands/visualize_heap.py | 2 +- 5 files changed, 96 insertions(+), 86 deletions(-) diff --git a/docs/commands/visualize_heap.md b/docs/commands/visualize_heap.md index 0c74672..ead3f3b 100644 --- a/docs/commands/visualize_heap.md +++ b/docs/commands/visualize_heap.md @@ -11,4 +11,4 @@ Currently only the glibc heap support is implemented. The command doesn't take a gef➤ visualize-libc-heap-chunks ``` -![img](https://i.imgur.com/IKHlLlp.png) +![img](https://i.imgur.com/jQYaiyB.png) diff --git a/docs/commands/windbg.md b/docs/commands/windbg.md index 22cdb65..d71b5f7 100644 --- a/docs/commands/windbg.md +++ b/docs/commands/windbg.md @@ -34,13 +34,13 @@ This plugin is a set of commands, aliases and extensions to mimic some of the mo - `ea` : `patch string` - `dps` : `dereference` - `bp` : `break` - - `bl` : "info`breakpoints` - - `bd` : "disable`breakpoints` - - `bc` : "delete`breakpoints` - - `be` : "enable`breakpoints` + - `bl` : `info breakpoints` + - `bd` : `disable breakpoints` + - `bc` : `delete breakpoints` + - `be` : `enable breakpoints` - `tbp` : `tbreak` - `s` : `grep` - `pa` : `advance` - - `kp` : "info`stack` + - `kp` : `info stack` - `ptc` : `finish` - - `uf` : `disassemble` \ No newline at end of file + - `uf` : `disassemble` diff --git a/scripts/__init__.pyi b/scripts/__init__.pyi index 755ef69..c708965 100644 --- a/scripts/__init__.pyi +++ b/scripts/__init__.pyi @@ -1627,6 +1627,7 @@ class GefMemoryManager(GefManager): class GefHeapManager(GefManager): def __init__(self) -> None: ... def reset_caches(self) -> None: ... + @property def main_arena(self) -> Optional[GlibcArena]: ... @property def selected_arena(self) -> Optional[GlibcArena]: ... @@ -1637,7 +1638,7 @@ class GefHeapManager(GefManager): @property def base_address(self) -> Optional[int]: ... @property - def chunks(self) -> Union[List, Iterator]: ... + def chunks(self) -> Union[List[GlibcChunk], Iterator[GlibcChunk]]: ... def min_chunk_size(self) -> int: ... def malloc_alignment(self) -> int: ... def csize2tidx(self, size: int) -> int: ... diff --git a/scripts/visualize_heap.py b/scripts/visualize_heap.py index 80cc2f9..358cc10 100644 --- a/scripts/visualize_heap.py +++ b/scripts/visualize_heap.py @@ -1,9 +1,11 @@ """ Provide an ascii-based graphical representation of the heap layout. +Note: Mostly done for x64, other architectures were not throughly tested. """ + __AUTHOR__ = "hugsy" -__VERSION__ = 0.3 +__VERSION__ = 0.4 __LICENSE__ = "MIT" import os @@ -43,11 +45,14 @@ def get_tcache_count(): @lru_cache(128) def collect_known_values() -> Dict[int, str]: arena = gef.heap.main_arena - result: Dict[int, str] = {} # format is { 0xaddress : "name" ,} + if not arena: + raise RuntimeError version = gef.libc.version if not version: - return result + raise RuntimeError + + result: Dict[int, str] = {} # format is { 0xaddress : "name" ,} # tcache if version >= (2, 27): @@ -84,26 +89,28 @@ def collect_known_values() -> Dict[int, str]: # other bins for name in ["unorderedbins", "smallbins", "largebins"]: - - fw, bk = arena.bin(j) - if bk == 0x00 and fw == 0x00: - continue - head = GlibcChunk(bk, from_base=True).fwd - if head == fw: - continue - - chunk = GlibcChunk(head, from_base=True) - j = 0 + i = 0 while True: - if chunk is None: + (fw, bk) = arena.bin(i) + if (fw, bk) == (0, 0): break - result[chunk.data_address] = f"{name}[{i}/{j}]" - next_chunk_address = chunk.get_fwd_ptr(True) - if not next_chunk_address: - break - next_chunk = GlibcChunk(next_chunk_address, from_base=True) - j += 1 - chunk = next_chunk + + head = GlibcChunk(bk, from_base=True).fwd + if head == fw: + continue + + chunk = GlibcChunk(head, from_base=True) + j = 0 + while True: + if chunk is None: + break + result[chunk.data_address] = f"{name}[{i}/{j}]" + next_chunk_address = chunk.get_fwd_ptr(True) + if not next_chunk_address: + break + next_chunk = GlibcChunk(next_chunk_address, from_base=True) + j += 1 + chunk = next_chunk return result @@ -119,6 +126,21 @@ def collect_known_ranges() -> List[Tuple[range, str]]: return result +def is_corrupted(chunk: GlibcChunk, arena: GlibcArena) -> bool: + """Various checks to see if a chunk is corrupted""" + + if chunk.base_address > chunk.data_address: + return False + + if chunk.base_address > arena.top: + return True + + if chunk.size == 0: + return True + + return False + + @register class VisualizeHeapChunksCommand(GenericCommand): """Visual helper for glibc heap chunks""" @@ -134,92 +156,79 @@ def __init__(self): @only_if_gdb_running def do_invoke(self, _): - ptrsize = gef.arch.ptrsize - heap_base_address = gef.heap.base_address - arena = gef.heap.main_arena - if not arena.top or not heap_base_address: + if not gef.heap.main_arena or not gef.heap.base_address: err("The heap has not been initialized") return - top = align_address(int(arena.top)) - base = align_address(heap_base_address) + ptrsize = gef.arch.ptrsize + arena = gef.heap.main_arena - colors = ["cyan", "red", "yellow", "blue", "green"] - cur = GlibcChunk(base, from_base=True) - idx = 0 + colors = ("cyan", "red", "yellow", "blue", "green") + color_idx = 0 + chunk_idx = 0 known_ranges = collect_known_ranges() - known_values = collect_known_values() + known_values = [] # collect_known_values() + + for chunk in gef.heap.chunks: + if is_corrupted(chunk, arena): + err("Corrupted heap, cannot continue.") + return - while True: - base = cur.base_address - addr = cur.data_address aggregate_nuls = 0 + base = chunk.base_address - if base == top: + if base == arena.top: gef_print( - f"{format_address(addr)} {format_address(gef.memory.read_integer(addr))} {Color.colorify(LEFT_ARROW + 'Top Chunk', 'red bold')}\n" - f"{format_address(addr+ptrsize)} {format_address(gef.memory.read_integer(addr+ptrsize))} {Color.colorify(LEFT_ARROW + 'Top Chunk Size', 'red bold')}" + f"{format_address(base)} {format_address(gef.memory.read_integer(base))} {Color.colorify(LEFT_ARROW + 'Top Chunk', 'red bold')}\n" + f"{format_address(base+ptrsize)} {format_address(gef.memory.read_integer(base+ptrsize))} {Color.colorify(LEFT_ARROW + 'Top Chunk Size', 'red bold')}" ) break - if cur.size == 0: - warn("Unexpected size for chunk, cannot pursue. Corrupted heap?") - break - - for off in range(0, cur.size, ptrsize): - addr = base + off - value = gef.memory.read_integer(addr) + for current in range(base, base + chunk.size, ptrsize): + value = gef.memory.read_integer(current) if value == 0: - if off != 0 and off != cur.size - ptrsize: + if current != base and current != (base + chunk.size - ptrsize): + # Only aggregate null bytes that are not starting/finishing the chunk aggregate_nuls += 1 if aggregate_nuls > 1: continue if aggregate_nuls > 2: - gef_print( - " ↓", - " [...]", - " ↓" - ) + # If here, we have some aggregated null bytes, print a small thing to mention that + gef_print(" ↓ [...] ↓") aggregate_nuls = 0 - text = "".join( - [chr(b) if 0x20 <= b < 0x7F else "." for b in gef.memory.read(addr, ptrsize)]) - line = f"{format_address(addr)} {Color.colorify(format_address(value), colors[idx % len(colors)])}" - line += f" {text}" - derefs = dereference_from(addr) + # Read the context in a hexdump-like format + hexdump = "".join(map(lambda b: chr(b) if 0x20 <= b < 0x7F else ".", + gef.memory.read(current, ptrsize))) + + if gef.arch.endianness == Endianness.LITTLE_ENDIAN: + hexdump = hexdump[::-1] + + line = f"{format_address(current)} {Color.colorify(format_address(value), colors[color_idx])}" + line += f" {hexdump}" + derefs = dereference_from(current) if len(derefs) > 2: line += f" [{LEFT_ARROW}{derefs[-1]}]" - if off == 0: - line += f" Chunk[{idx}]" - if off == ptrsize: - line += f" {value&~7 }" \ - f"{'|NON_MAIN_ARENA' if value&4 else ''}" \ - f"{'|IS_MMAPED' if value&2 else ''}" \ - f"{'|PREV_INUSE' if value&1 else ''}" + # The first entry of the chunk gets added some extra info about the chunk itself + if current == base: + line += f" Chunk[{chunk_idx}], Flag={chunk.flags!s}" + chunk_idx += 1 - # look in mapping - for x in known_ranges: - if value in x[0]: - line += f" (in {Color.redify(x[1])})" + # Populate information for known ranges, if any + for _range, _value in known_ranges: + if value in _range: + line += f" (in {Color.redify(_value)})" - # look in known values + # Populate information from other chunks/bins, if any if value in known_values: line += f"{RIGHT_ARROW}{Color.cyanify(known_values[value])}" + # All good, print it out gef_print(line) - next_chunk = cur.get_next_chunk() - if next_chunk is None: - break - - next_chunk_addr = Address(value=next_chunk.data_address) - if not next_chunk_addr.valid: - warn("next chunk probably corrupted") - break + color_idx = (color_idx + 1) % len(colors) - cur = next_chunk - idx += 1 return diff --git a/tests/commands/visualize_heap.py b/tests/commands/visualize_heap.py index d2abbe2..d86fff7 100644 --- a/tests/commands/visualize_heap.py +++ b/tests/commands/visualize_heap.py @@ -24,4 +24,4 @@ def test_cmd_heap_view(self): for i in range(4): self.assertIn( - f"0x0000000000000000 ........ Chunk[{i}]", res) + f"0x0000000000000000 ........ Chunk[{i}]", res) From 7b7e5f36c35ececd52262dd52c21e7a616731ccf Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 9 Jul 2022 20:32:28 -0700 Subject: [PATCH 26/39] Update run-tests.yml Removing Ubuntu 18.04 from CI since Unicorn doesn't support Python3.6 correctly --- .github/workflows/run-tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index cb423b6..486f6b5 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -18,7 +18,6 @@ jobs: matrix: os: - ubuntu-20.04 - - ubuntu-18.04 - ubuntu-22.04 name: "Run Unit tests on ${{ matrix.os }}" runs-on: ${{ matrix.os }} From 4775e13a9226296e84670154c7cfa0b858dc9fcc Mon Sep 17 00:00:00 2001 From: hugsy Date: Mon, 10 Oct 2022 08:54:33 -0700 Subject: [PATCH 27/39] removing makefile --- .github/workflows/run-tests.yml | 14 ++- pytest.ini | 15 --- scripts/gdb/__init__.pyi | 4 +- scripts/stack.py | 16 ++- tests/commands/capstone_disassemble.py | 2 +- tests/utils.py | 165 +++++++++++++++++-------- 6 files changed, 137 insertions(+), 79 deletions(-) delete mode 100644 pytest.ini diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 486f6b5..e2658c8 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -34,6 +34,11 @@ jobs: sudo apt-get install -y gdb-multiarch python3-dev python3-pip python3-wheel python3-setuptools git cmake gcc g++ pkg-config libglib2.0-dev gdbserver sudo python3 -m pip install --upgrade pip + - name: Set runtime environment variables + run: | + echo PY_VER=`gdb -q -nx -ex "pi print('.'.join(map(str, sys.version_info[:2])))" -ex quit` >> $GITHUB_ENV + echo NB_CPU=`grep -c ^processor /proc/cpuinfo` >> $GITHUB_ENV + - name: Set architecture specific properties id: set-arch-properties run: | @@ -62,8 +67,8 @@ jobs: - name: Install requirements run: | mkdir -p ${{ steps.pip-cache.outputs.dir }} - python3 -m pip install --user --upgrade -r ./requirements.txt - python3 -m pip install --user --upgrade -r ./tests/requirements.txt + python${{ env.PY_VER }} -m pip install --user --upgrade -r ./requirements.txt + python${{ env.PY_VER }} -m pip install --user --upgrade -r ./tests/requirements.txt - name: Setup GEF run: | @@ -74,9 +79,10 @@ jobs: env: GEF_CI_ARCH: ${{ steps.set-arch-properties.outputs.arch }} run: | - make test + make -C tests/binaries -j ${{ env.NB_CPU }} + python${{ env.PY_VER }} -m pytest --forked -n ${{ env.NB_CPU }} -v -k "not benchmark" tests/ - name: Run linter run: | - make lint + python${{ env.PY_VER }} -m pylint --rcfile=$(pwd)/.pylintrc gef.py tests/*/*.py diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 72f4744..0000000 --- a/pytest.ini +++ /dev/null @@ -1,15 +0,0 @@ -[pytest] -log_level = INFO -minversion = 6.0 -required_plugins = - pytest-xdist - pytest-benchmark -python_functions = - test_* - time_* -python_files = *.py -testpaths = - tests -markers = - slow: flag test as slow (deselect with '-m "not slow"') - online: flag test as requiring internet to work (deselect with '-m "not online"') diff --git a/scripts/gdb/__init__.pyi b/scripts/gdb/__init__.pyi index faf6c96..23b5d20 100644 --- a/scripts/gdb/__init__.pyi +++ b/scripts/gdb/__init__.pyi @@ -2,7 +2,7 @@ from typing import (Callable, Dict, Iterator, List, Optional, Tuple, Union, overload) PYTHONDIR: str - +VERSION: str def execute(command, from_tty=False, to_string=False) -> Union[None, str]: ... def breakpoints() -> List[Breakpoint]: ... @@ -75,6 +75,7 @@ class Value: def fetch_lazy(self) -> None: ... def __abs__(self) -> int: ... + def __int__(self) -> int: ... def lookup_type(name, block: Optional[Block] = None) -> Type: ... @@ -534,6 +535,7 @@ class Frame: def function(self) -> Symbol: ... def older(self) -> Frame: ... def newer(self) -> Frame: ... + def level(self) -> int: ... def find_sal(self) -> Symtab_and_line: ... def read_register(self, register: str) -> Value: ... def read_var(self, variable: Union[Symbol, str], diff --git a/scripts/stack.py b/scripts/stack.py index 3c94f69..910da45 100644 --- a/scripts/stack.py +++ b/scripts/stack.py @@ -1,14 +1,20 @@ __AUTHOR__ = "hugsy" -__VERSION__ = 0.1 +__VERSION__ = 0.2 + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from . import * + from . import gdb @register class CurrentFrameStack(GenericCommand): """Show the entire stack of the current frame.""" _cmdline_ = "current-stack-frame" - _syntax_ = "{:s}".format(_cmdline_) + _syntax_ = f"{_cmdline_}" _aliases_ = ["stack-view",] - _example_ = "{:s}".format(_cmdline_) + _example_ = f"{_cmdline_}" @only_if_gdb_running def do_invoke(self, argv): @@ -25,9 +31,9 @@ def do_invoke(self, argv): else: #reason = frame.unwind_stop_reason() reason_str = gdb.frame_stop_reason_string( frame.unwind_stop_reason() ) - warn("Cannot determine frame boundary, reason: {:s}".format(reason_str)) + warn(f"Cannot determine frame boundary, reason: {reason_str}") return - + stack_lo = align_address(int(frame.read_register("sp"))) should_stack_grow_down = gef.config["context.grow_stack_down"] == True results = [] diff --git a/tests/commands/capstone_disassemble.py b/tests/commands/capstone_disassemble.py index 3984592..09e7d27 100644 --- a/tests/commands/capstone_disassemble.py +++ b/tests/commands/capstone_disassemble.py @@ -5,7 +5,7 @@ import pytest from tests.utils import (ARCH, GefUnitTestGeneric, gdb_run_cmd, - gdb_run_silent_cmd, gdb_start_silent_cmd, removeuntil) + gdb_start_silent_cmd, removeuntil) @pytest.mark.skipif(ARCH in ("mips64el", "ppc64le", "riscv64"), reason=f"Skipped for {ARCH}") diff --git a/tests/utils.py b/tests/utils.py index 5b6138b..e891640 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,6 +2,7 @@ Utility functions for testing """ +import contextlib import enum import os import pathlib @@ -14,48 +15,46 @@ from typing import Iterable, List, Optional, Union from urllib.request import urlopen -TMPDIR = pathlib.Path(tempfile.gettempdir()) -ARCH = (os.getenv("GEF_CI_ARCH") or platform.machine()).lower() -BIN_SH = pathlib.Path("/bin/sh") -CI_VALID_ARCHITECTURES_32B = ("i686", "armv7l") -CI_VALID_ARCHITECTURES_64B = ( - "x86_64", "aarch64", "mips64el", "ppc64le", "riscv64") -CI_VALID_ARCHITECTURES = CI_VALID_ARCHITECTURES_64B + CI_VALID_ARCHITECTURES_32B -COVERAGE_DIR = os.getenv("COVERAGE_DIR", "") -STRIP_ANSI_DEFAULT = True -DEFAULT_CONTEXT = "-code -stack" -DEFAULT_TARGET = TMPDIR / "default.out" -GEF_DEFAULT_PROMPT = "gef➤ " -GEF_DEFAULT_TEMPDIR = "/tmp/gef" -GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "../gef/gef.py")) -GEF_EXTRAS_TEST_DIR_PATH = pathlib.Path(__file__).absolute().parent -GEF_EXTRAS_ROOT_PATH = GEF_EXTRAS_TEST_DIR_PATH.parent -GEF_EXTRAS_SCRIPTS_PATH = GEF_EXTRAS_ROOT_PATH / "scripts" -GEF_EXTRAS_OS_PATH = GEF_EXTRAS_ROOT_PATH / "os" -GEF_EXTRAS_STRUCTS_PATH = GEF_EXTRAS_ROOT_PATH / "structs" - +TMPDIR = pathlib.Path(tempfile.gettempdir()) +ARCH = (os.getenv("GEF_CI_ARCH") or platform.machine()).lower() +BIN_SH = pathlib.Path("/bin/sh") +CI_VALID_ARCHITECTURES_32B = ("i686", "armv7l") +CI_VALID_ARCHITECTURES_64B = ("x86_64", "aarch64", "mips64el", "ppc64le", "riscv64") +CI_VALID_ARCHITECTURES = CI_VALID_ARCHITECTURES_64B + CI_VALID_ARCHITECTURES_32B +COVERAGE_DIR = os.getenv("COVERAGE_DIR", "") +DEFAULT_CONTEXT = "-code -stack" +DEFAULT_TARGET = TMPDIR / "default.out" +GEF_DEFAULT_PROMPT = "gef➤ " +GEF_DEFAULT_TEMPDIR = "/tmp/gef" +GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "../gef/gef.py")) +GEF_EXTRAS_TEST_DIR_PATH = pathlib.Path(__file__).absolute().parent +GEF_EXTRAS_ROOT_PATH = GEF_EXTRAS_TEST_DIR_PATH.parent +GEF_EXTRAS_SCRIPTS_PATH = GEF_EXTRAS_ROOT_PATH / "scripts" +GEF_EXTRAS_OS_PATH = GEF_EXTRAS_ROOT_PATH / "os" +GEF_EXTRAS_STRUCTS_PATH = GEF_EXTRAS_ROOT_PATH / "structs" +STRIP_ANSI_DEFAULT = True +GDBSERVER_DEFAULT_PORT = 1234 CommandType = Union[str, Iterable[str]] - class Color(enum.Enum): """Used to colorify terminal output.""" - NORMAL = "\x1b[0m" - GRAY = "\x1b[1;38;5;240m" - LIGHT_GRAY = "\x1b[0;37m" - RED = "\x1b[31m" - GREEN = "\x1b[32m" - YELLOW = "\x1b[33m" - BLUE = "\x1b[34m" - PINK = "\x1b[35m" - CYAN = "\x1b[36m" - BOLD = "\x1b[1m" - UNDERLINE = "\x1b[4m" - UNDERLINE_OFF = "\x1b[24m" - HIGHLIGHT = "\x1b[3m" - HIGHLIGHT_OFF = "\x1b[23m" - BLINK = "\x1b[5m" - BLINK_OFF = "\x1b[25m" + NORMAL = "\x1b[0m" + GRAY = "\x1b[1;38;5;240m" + LIGHT_GRAY = "\x1b[0;37m" + RED = "\x1b[31m" + GREEN = "\x1b[32m" + YELLOW = "\x1b[33m" + BLUE = "\x1b[34m" + PINK = "\x1b[35m" + CYAN = "\x1b[36m" + BOLD = "\x1b[1m" + UNDERLINE = "\x1b[4m" + UNDERLINE_OFF = "\x1b[24m" + HIGHLIGHT = "\x1b[3m" + HIGHLIGHT_OFF = "\x1b[23m" + BLINK = "\x1b[5m" + BLINK_OFF = "\x1b[25m" class GdbAssertionError(AssertionError): @@ -83,16 +82,14 @@ def assertNoException(buf): or "'gdb.error'" in buf or "Exception raised" in buf or "failed to execute properly, reason:" in buf): - raise GdbAssertionError( - f"Unexpected GDB Exception raised in {buf}") + raise GdbAssertionError(f"Unexpected GDB Exception raised in {buf}") if "is deprecated and will be removed in a feature release." in buf: lines = [l for l in buf.splitlines() if "is deprecated and will be removed in a feature release." in l] deprecated_api_names = {x.split()[1] for x in lines} warnings.warn( - UserWarning( - f"Use of deprecated API(s): {', '.join(deprecated_api_names)}") + UserWarning(f"Use of deprecated API(s): {', '.join(deprecated_api_names)}") ) @staticmethod @@ -114,21 +111,20 @@ def ansi_clean(s: str) -> str: return ansi_escape.sub("", s) -def _add_command(commands: CommandType) -> List[str]: - if isinstance(commands, str): - commands = [commands] - return [_str for cmd in commands for _str in ["-ex", cmd]] - - def gdb_run_cmd(cmd: CommandType, before: CommandType = (), after: CommandType = (), target: pathlib.Path = DEFAULT_TARGET, strip_ansi: bool = STRIP_ANSI_DEFAULT) -> str: """Execute a command inside GDB. `before` and `after` are lists of commands to be executed before (resp. after) the command to test.""" + + def _add_command(commands: CommandType) -> List[str]: + if isinstance(commands, str): + commands = [commands] + return [_str for cmd in commands for _str in ["-ex", cmd]] + command = ["gdb", "-q", "-nx"] if COVERAGE_DIR: - coverage_file = pathlib.Path( - COVERAGE_DIR) / os.getenv("PYTEST_XDIST_WORKER", "gw0") + coverage_file = pathlib.Path(COVERAGE_DIR) / os.getenv("PYTEST_XDIST_WORKER", "gw0") command += _add_command([ "pi from coverage import Coverage", f"pi cov = Coverage(data_file=\"{coverage_file}\"," @@ -148,8 +144,7 @@ def gdb_run_cmd(cmd: CommandType, before: CommandType = (), after: CommandType = command += _add_command(["pi cov.stop()", "pi cov.save()"]) command += ["-ex", "quit", "--", str(target)] - lines = subprocess.check_output( - command, stderr=subprocess.STDOUT).strip().splitlines() + lines = subprocess.check_output(command, stderr=subprocess.STDOUT).strip().splitlines() output = b"\n".join(lines) result = None @@ -235,12 +230,14 @@ def gdb_time_python_method(meth: str, setup: str, def _target(name: str, extension: str = ".out") -> pathlib.Path: target = TMPDIR / f"{name}{extension}" if not target.exists(): - raise FileNotFoundError(f"Could not find file '{target}'") + subprocess.run(["make", "-C", "tests/binaries", target.name]) + if not target.exists(): + raise FileNotFoundError(f"Could not find file '{target}'") return target def start_gdbserver(exe: Union[str, pathlib.Path] = _target("default"), - port: int = 1234) -> subprocess.Popen: + port: int = GDBSERVER_DEFAULT_PORT) -> subprocess.Popen: """Start a gdbserver on the target binary. Args: @@ -266,6 +263,67 @@ def stop_gdbserver(gdbserver: subprocess.Popen) -> None: gdbserver.wait() +@contextlib.contextmanager +def gdbserver_session(*args, **kwargs): + exe = kwargs.get("exe", "") or _target("default") + port = kwargs.get("port", 0) or GDBSERVER_DEFAULT_PORT + sess = start_gdbserver(exe, port) + try: + yield sess + finally: + stop_gdbserver(sess) + + +def start_qemuuser(exe: Union[str, pathlib.Path] = _target("default"), + port: int = GDBSERVER_DEFAULT_PORT) -> subprocess.Popen: + return subprocess.Popen(["qemu-x86_64", "-g", str(port), exe], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + +def stop_qemuuser(process: subprocess.Popen) -> None: + if process.poll() is None: + process.kill() + process.wait() + + +@contextlib.contextmanager +def qemuuser_session(*args, **kwargs): + exe = kwargs.get("exe", "") or _target("default") + port = kwargs.get("port", 0) or GDBSERVER_DEFAULT_PORT + sess = start_gdbserver(exe, port) + try: + yield sess + finally: + stop_gdbserver(sess) + + + +def find_symbol(binary: pathlib.Path, symbol: str) -> int: + """Find a symbol by name in a ELF binary using `objdump`. + The expect output syntax for `objdump` is: + SYMBOL TABLE: + 0000000000000318 l d .interp 0000000000000000 .interp + 0000000000000338 l d .note.gnu.property 0000000000000000 .note.gnu.property + 0000000000000358 l d .note.gnu.build-id 0000000000000000 .note.gnu.build-id + 000000000000037c l d .note.ABI-tag 0000000000000000 .note.ABI-tag + + Args: + binary (pathlib.Path): the ELF file to inspect + symbol (str): the name of the symbol to find + + Returns: + int the address/offset of the symbol + + Raises: + KeyError if the symbol is not found + """ + name = symbol.encode("utf8") + for line in [x.strip().split() for x in subprocess.check_output(["objdump", "-t", binary]).splitlines() if len(x.strip())]: + if line[-1] == name: + return int(line[0], 0x10) + raise KeyError(f"`{symbol}` not found in {binary}") + + def findlines(substring: str, buffer: str) -> List[str]: """Extract the lines from the buffer which contains the pattern `substring` @@ -328,6 +386,7 @@ def removeuntil(substring: str, buffer: str, included: bool = False) -> str: return buffer[idx:] + def download_file(url: str) -> Optional[bytes]: """Download a file from the internet. From 98ea0a87adb4634c172a22074192095c11e7f043 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Wed, 19 Oct 2022 15:14:41 -0700 Subject: [PATCH 28/39] Update skel.py --- scripts/skel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/skel.py b/scripts/skel.py index 6939831..af2e27a 100644 --- a/scripts/skel.py +++ b/scripts/skel.py @@ -82,7 +82,7 @@ def do_invoke(self, args): port=port, arch="amd64" if "x86-64" in gef.arch.arch else "i386", endian="big" if gef.arch.endianness == Endianness.BIG_ENDIAN else "little", - filepath=gef.file.path, + filepath=gef.binary.path, bkps=bkps ) fd, fname = tempfile.mkstemp(suffix='.py', prefix='gef_') From 4deb70501da808506f5c48e609b3df188ea8ae06 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Wed, 19 Oct 2022 15:19:41 -0700 Subject: [PATCH 29/39] Update skel.py --- scripts/skel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/skel.py b/scripts/skel.py index af2e27a..4b44ec4 100644 --- a/scripts/skel.py +++ b/scripts/skel.py @@ -1,5 +1,5 @@ __AUTHOR__ = "hugsy" -__VERSION__ = 0.4 +__VERSION__ = 0.4.2 import os import tempfile @@ -80,7 +80,7 @@ def do_invoke(self, args): temp = TEMPLATE.format( target=target, port=port, - arch="amd64" if "x86-64" in gef.arch.arch else "i386", + arch="amd64" if isinstance(gef.arch, X86_64) else "i386", endian="big" if gef.arch.endianness == Endianness.BIG_ENDIAN else "little", filepath=gef.binary.path, bkps=bkps From 2180b260691d468fed98b2bc84219e442e843a99 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Thu, 20 Oct 2022 08:21:34 -0700 Subject: [PATCH 30/39] Update skel.py --- scripts/skel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/skel.py b/scripts/skel.py index 4b44ec4..ae13680 100644 --- a/scripts/skel.py +++ b/scripts/skel.py @@ -1,5 +1,5 @@ __AUTHOR__ = "hugsy" -__VERSION__ = 0.4.2 +__VERSION__ = 1.0 import os import tempfile From ccc7fa6565e8b3b4388e17f65a5646b5a0a9b8ba Mon Sep 17 00:00:00 2001 From: hugsy Date: Fri, 21 Oct 2022 10:14:04 -0700 Subject: [PATCH 31/39] add gitattributes, editorconfig and replaced files with crlf with lf --- .editorconfig | 21 ++++++ .gitattributes | 2 + .github/FUNDING.yml | 4 ++ scripts/ropper.py | 100 +++++++++++++-------------- structs/io_file64_t.py | 138 ++++++++++++++++++------------------- tests/commands/__init__.py | 0 tests/commands/ropper.py | 5 +- tests/pytest.ini | 15 ++++ 8 files changed, 164 insertions(+), 121 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/FUNDING.yml create mode 100644 tests/commands/__init__.py create mode 100644 tests/pytest.ini diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..30d459c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties + +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.py] +indent_style = space +indent_size = 4 + +[Makefile] +indent_style = tab + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e65d516 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# https://help.github.com/articles/dealing-with-line-endings/ +* text eol=lf diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..2a00b2e --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: [hugsy,] + diff --git a/scripts/ropper.py b/scripts/ropper.py index a5c2200..e10c588 100644 --- a/scripts/ropper.py +++ b/scripts/ropper.py @@ -1,50 +1,50 @@ -__AUTHOR__ = "hugsy" -__VERSION__ = 0.2 -__NAME__ = "ropper" - -import sys - -import gdb -import ropper - - -@register -class RopperCommand(GenericCommand): - """Ropper (https://scoding.de/ropper/) plugin.""" - - _cmdline_ = "ropper" - _syntax_ = f"{_cmdline_} [ROPPER_OPTIONS]" - - def __init__(self) -> None: - super().__init__(complete=gdb.COMPLETE_NONE) - self.__readline = None - return - - @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: - if not self.__readline: - self.__readline = __import__("readline") - ropper = sys.modules["ropper"] - if "--file" not in argv: - path = gef.session.file - if not path: - err("No file provided") - return - sect = next(filter(lambda x: x.path == path, gef.memory.maps)) - argv.append("--file") - argv.append(path) - argv.append("-I") - argv.append(f"{sect.page_start:#x}") - - # ropper set up own autocompleter after which gdb/gef autocomplete don't work - old_completer_delims = self.__readline.get_completer_delims() - old_completer = self.__readline.get_completer() - - try: - ropper.start(argv) - except RuntimeWarning: - return - - self.__readline.set_completer(old_completer) - self.__readline.set_completer_delims(old_completer_delims) - return +__AUTHOR__ = "hugsy" +__VERSION__ = 0.2 +__NAME__ = "ropper" + +import sys + +import gdb +import ropper + + +@register +class RopperCommand(GenericCommand): + """Ropper (https://scoding.de/ropper/) plugin.""" + + _cmdline_ = "ropper" + _syntax_ = f"{_cmdline_} [ROPPER_OPTIONS]" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_NONE) + self.__readline = None + return + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + if not self.__readline: + self.__readline = __import__("readline") + ropper = sys.modules["ropper"] + if "--file" not in argv: + path = gef.session.file + if not path: + err("No file provided") + return + sect = next(filter(lambda x: x.path == path, gef.memory.maps)) + argv.append("--file") + argv.append(path) + argv.append("-I") + argv.append(f"{sect.page_start:#x}") + + # ropper set up own autocompleter after which gdb/gef autocomplete don't work + old_completer_delims = self.__readline.get_completer_delims() + old_completer = self.__readline.get_completer() + + try: + ropper.start(argv) + except RuntimeWarning: + return + + self.__readline.set_completer(old_completer) + self.__readline.set_completer_delims(old_completer_delims) + return diff --git a/structs/io_file64_t.py b/structs/io_file64_t.py index 5e3a157..800ff73 100644 --- a/structs/io_file64_t.py +++ b/structs/io_file64_t.py @@ -1,69 +1,69 @@ -import ctypes as ct -from ctypes import POINTER - -# http://sourceware.org/git/?p=glibc.git;a=blob;f=libio/libio.h;hb=765de945efc5d5602999b2999fe8abdf04881370#l67 -_IO_MAGIC = 0xFBAD0000 - - -class io_file64_plus_t(ct.Structure): - _fields_ = [ - ("_p1", ct.c_uint64), - ("_p2", ct.c_uint64), - ("_IO_file_finish", ct.c_uint64), - ("_IO_file_overflow", ct.c_uint64), - ("_IO_file_underflow", ct.c_uint64), - ("_IO_default_uflow", ct.c_uint64), - ("_IO_default_pbackfail", ct.c_uint64), - ("_IO_file_xsputn", ct.c_uint64), - ("_IO_Unk1", ct.c_uint64), - ("_IO_file_seekoff", ct.c_uint64), - ("_IO_Unk1", ct.c_uint64), - ("_IO_file_setbuf", ct.c_uint64), - ("_IO_file_sync", ct.c_uint64), - ("_IO_file_doallocate", ct.c_uint64), - ("_IO_file_read", ct.c_uint64), - ("_IO_file_write", ct.c_uint64), - ("_IO_file_seek", ct.c_uint64), - ("_IO_file_close", ct.c_uint64), - ("_IO_file_stat", ct.c_uint64), - ] - - _values_ = [] - - -class io_file64_t(ct.Structure): - # http://sourceware.org/git/?p=glibc.git;a=blob;f=libio/libio.h;h=3cf1712ea98d3c253f418feb1ef881c4a44649d5;hb=HEAD#l245 - _fields_ = [ - ("_flags", ct.c_uint16), - ("_magic", ct.c_uint16), # should be equal to _IO_MAGIC - ("_IO_read_ptr", ct.c_uint64), # /* Current read pointer */ - ("_IO_read_end", ct.c_uint64), # /* End of get area. */ - ("_IO_read_base", ct.c_uint64), # /* Start of putback+get area. */ - ("_IO_write_base", ct.c_uint64), # /* Start of put area. */ - ("_IO_write_ptr", ct.c_uint64), # /* Current put pointer. */ - ("_IO_write_end", ct.c_uint64), # /* End of put area. */ - ("_IO_buf_base", ct.c_uint64), # /* Start of reserve area. */ - ("_IO_buf_end", ct.c_uint64), # /* End of reserve area. */ - ( - "_IO_save_base", - ct.c_uint64, - ), # /* Pointer to start of non-current get area. */ - ( - "_IO_backup_base", - ct.c_uint64, - ), # /* Pointer to first valid character of backup area */ - ("_IO_save_end", ct.c_uint64), # /* Pointer to end of non-current get area. */ - ("_markers", ct.c_char_p), - ("_chain", POINTER(io_file64_plus_t)), - # TODO: some fields are missing, add them - ] - - _values_ = [ - ( - "_magic", - [ - (_IO_MAGIC >> 16, "Correct magic"), - (None, "Incorrect magic (corrupted?)"), - ], - ), - ] +import ctypes as ct +from ctypes import POINTER + +# http://sourceware.org/git/?p=glibc.git;a=blob;f=libio/libio.h;hb=765de945efc5d5602999b2999fe8abdf04881370#l67 +_IO_MAGIC = 0xFBAD0000 + + +class io_file64_plus_t(ct.Structure): + _fields_ = [ + ("_p1", ct.c_uint64), + ("_p2", ct.c_uint64), + ("_IO_file_finish", ct.c_uint64), + ("_IO_file_overflow", ct.c_uint64), + ("_IO_file_underflow", ct.c_uint64), + ("_IO_default_uflow", ct.c_uint64), + ("_IO_default_pbackfail", ct.c_uint64), + ("_IO_file_xsputn", ct.c_uint64), + ("_IO_Unk1", ct.c_uint64), + ("_IO_file_seekoff", ct.c_uint64), + ("_IO_Unk1", ct.c_uint64), + ("_IO_file_setbuf", ct.c_uint64), + ("_IO_file_sync", ct.c_uint64), + ("_IO_file_doallocate", ct.c_uint64), + ("_IO_file_read", ct.c_uint64), + ("_IO_file_write", ct.c_uint64), + ("_IO_file_seek", ct.c_uint64), + ("_IO_file_close", ct.c_uint64), + ("_IO_file_stat", ct.c_uint64), + ] + + _values_ = [] + + +class io_file64_t(ct.Structure): + # http://sourceware.org/git/?p=glibc.git;a=blob;f=libio/libio.h;h=3cf1712ea98d3c253f418feb1ef881c4a44649d5;hb=HEAD#l245 + _fields_ = [ + ("_flags", ct.c_uint16), + ("_magic", ct.c_uint16), # should be equal to _IO_MAGIC + ("_IO_read_ptr", ct.c_uint64), # /* Current read pointer */ + ("_IO_read_end", ct.c_uint64), # /* End of get area. */ + ("_IO_read_base", ct.c_uint64), # /* Start of putback+get area. */ + ("_IO_write_base", ct.c_uint64), # /* Start of put area. */ + ("_IO_write_ptr", ct.c_uint64), # /* Current put pointer. */ + ("_IO_write_end", ct.c_uint64), # /* End of put area. */ + ("_IO_buf_base", ct.c_uint64), # /* Start of reserve area. */ + ("_IO_buf_end", ct.c_uint64), # /* End of reserve area. */ + ( + "_IO_save_base", + ct.c_uint64, + ), # /* Pointer to start of non-current get area. */ + ( + "_IO_backup_base", + ct.c_uint64, + ), # /* Pointer to first valid character of backup area */ + ("_IO_save_end", ct.c_uint64), # /* Pointer to end of non-current get area. */ + ("_markers", ct.c_char_p), + ("_chain", POINTER(io_file64_plus_t)), + # TODO: some fields are missing, add them + ] + + _values_ = [ + ( + "_magic", + [ + (_IO_MAGIC >> 16, "Correct magic"), + (None, "Incorrect magic (corrupted?)"), + ], + ), + ] diff --git a/tests/commands/__init__.py b/tests/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/commands/ropper.py b/tests/commands/ropper.py index 21dcda7..93679e4 100644 --- a/tests/commands/ropper.py +++ b/tests/commands/ropper.py @@ -5,7 +5,8 @@ import pytest -from tests.utils import ARCH, GefUnitTestGeneric, gdb_run_cmd, gdb_run_silent_cmd +from tests.utils import (ARCH, GefUnitTestGeneric, gdb_run_cmd, + gdb_run_silent_cmd) class RopperCommand(GefUnitTestGeneric): @@ -13,7 +14,7 @@ class RopperCommand(GefUnitTestGeneric): def setUp(self) -> None: try: - import ropper # pylint: disable=W0611 + import ropper # pylint: disable=W0611 except ImportError: pytest.skip("ropper not available", allow_module_level=True) return super().setUp() diff --git a/tests/pytest.ini b/tests/pytest.ini new file mode 100644 index 0000000..3ed7602 --- /dev/null +++ b/tests/pytest.ini @@ -0,0 +1,15 @@ +[pytest] +log_level = INFO +minversion = 6.0 +required_plugins = + pytest-xdist + pytest-benchmark +python_functions = + test_* + time_* +python_files = *.py +testpaths = + . +markers = + slow: flag test as slow (deselect with '-m "not slow"') + online: flag test as requiring internet to work (deselect with '-m "not online"') From 8bc02eb6b6f16f470aca9b46246b629c6a119325 Mon Sep 17 00:00:00 2001 From: hugsy Date: Fri, 21 Oct 2022 10:56:36 -0700 Subject: [PATCH 32/39] deleted makefile, removed `set-output` directives --- .github/workflows/run-tests.yml | 29 ++++++++---------- Makefile | 53 --------------------------------- tests/pytest.ini | 3 +- tests/utils.py | 7 ++++- 4 files changed, 20 insertions(+), 72 deletions(-) delete mode 100644 Makefile diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index e2658c8..6d0c399 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -26,7 +26,7 @@ jobs: shell: bash steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install python and toolchain run: | @@ -38,26 +38,21 @@ jobs: run: | echo PY_VER=`gdb -q -nx -ex "pi print('.'.join(map(str, sys.version_info[:2])))" -ex quit` >> $GITHUB_ENV echo NB_CPU=`grep -c ^processor /proc/cpuinfo` >> $GITHUB_ENV - - - name: Set architecture specific properties - id: set-arch-properties - run: | - echo "::set-output name=arch::$(uname --processor)" - - - name: Get pip cache dir - id: pip-cache - run: | - echo "::set-output name=dir::$(python3 -m pip cache dir)" + echo GEF_CI_ARCH=`uname --processor` >> $GITHUB_ENV + echo GEF_CACHE_DIR=`python3 -m pip cache dir` >> $GITHUB_ENV + echo GEF_BRANCH=`git rev-parse --abbrev-ref HEAD` >> $GITHUB_ENV + echo GEF_PATH=`realpath ../gef` >> $GITHUB_ENV + echo GEF_SCRIPT=`realpath ../gef/gef.py` >> $GITHUB_ENV - name: Cache dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 id: cache-deps env: cache-name: cache-deps with: key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} path: | - ${{ steps.pip-cache.outputs.dir }} + ${{ env.GEF_CACHE_DIR }} restore-keys: ${{ runner.os }}-pip-${{ env.cache-name }}- ${{ runner.os }}-pip- @@ -66,18 +61,18 @@ jobs: - name: Install requirements run: | - mkdir -p ${{ steps.pip-cache.outputs.dir }} + mkdir -p ${{ env.GEF_CACHE_DIR }} python${{ env.PY_VER }} -m pip install --user --upgrade -r ./requirements.txt python${{ env.PY_VER }} -m pip install --user --upgrade -r ./tests/requirements.txt - name: Setup GEF run: | - echo "source $(pwd)/gef.py" > ~/.gdbinit + mkdir ${{ env.GEF_PATH }} + curl -fSsL https://raw.githubusercontent.com/hugsy/gef/${{ env.GEF_BRANCH }}/gef.py > ${{ env.GEF_SCRIPT }} + echo "source ${{ env.GEF_SCRIPT }}" > ~/.gdbinit gdb -q -ex 'gef missing' -ex 'gef help' -ex 'gef config' -ex start -ex continue -ex quit /bin/pwd - name: Run Tests - env: - GEF_CI_ARCH: ${{ steps.set-arch-properties.outputs.arch }} run: | make -C tests/binaries -j ${{ env.NB_CPU }} python${{ env.PY_VER }} -m pytest --forked -n ${{ env.NB_CPU }} -v -k "not benchmark" tests/ diff --git a/Makefile b/Makefile deleted file mode 100644 index 66b55d5..0000000 --- a/Makefile +++ /dev/null @@ -1,53 +0,0 @@ -ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) -NB_CORES := $(shell grep --count '^processor' /proc/cpuinfo) -PYLINT_RC := $(ROOT_DIR)/.pylintrc -PYLINT_DISABLE:= all -PYLINT_JOBS := $(NB_CORES) -PYLINT_SUGGEST_FIX := y -PYLINT_PY_VERSION := 3.6 -PYLINT_PARAMETERS := --jobs=$(PYLINT_JOBS) --suggestion-mode=$(PYLINT_SUGGEST_FIX) --py-version=$(PYLINT_PY_VERSION) --rcfile=$(PYLINT_RC) -TARGET := $(shell lscpu | head -1 | sed -e 's/Architecture:\s*//g') -COVERAGE_DIR ?= /tmp/cov -GDBINIT_TMP = $(shell mktemp) -GDBINIT_BACKUP = $(GDBINIT_TMP) -GEFRC_TMP = $(shell mktemp) -GEFRC_BACKUP = $(GEFRC_TMP) -TMPDIR ?= $(shell mktemp -d) -WORKING_DIR := $(TMPDIR) -GEF_PATH := $(WORKING_DIR)/gef.py -PYTEST_PARAMETERS := --verbose --forked --numprocesses=$(NB_CORES) -BRANCH := $(shell git rev-parse --abbrev-ref HEAD) - - -.PHONY: test test_% Test% testbins clean lint - - -setup: - @echo "[+] Downloading gef.py -> '$(GEF_PATH)'" - @wget -O $(GEF_PATH) -q https://gef.blah.cat/dev - -test: testbins setup - TMPDIR=$(WORKING_DIR) GEF_PATH=$(GEF_PATH) python3 -m pytest $(PYTEST_PARAMETERS) -k "not benchmark" - -test_%: testbins setup - TMPDIR=$(WORKING_DIR) GEF_PATH=$(GEF_PATH) python3 -m pytest $(PYTEST_PARAMETERS) -k $@ - -testbins: $(wildcard tests/binaries/*.c) - @TMPDIR=$(WORKING_DIR) $(MAKE) -j $(NB_CORES) -C tests/binaries TARGET=$(TARGET) TMPDIR=$(WORKING_DIR) all - -clean: - TMPDIR=$(WORKING_DIR) $(MAKE) -j $(NB_CORES) -C tests/binaries clean - @rm -rf $(WORKING_DIR)/gef-* $(WORKING_DIR)/gef.py || true - -lint: - python3 -m pylint $(PYLINT_PARAMETERS) $(GEF_PATH) - python3 -m pylint $(PYLINT_PARAMETERS) $(wildcard tests/*/*.py) - -coverage: - @! ( [ -d $(COVERAGE_DIR) ] && echo "COVERAGE_DIR=$(COVERAGE_DIR) exists already") - @mkdir -p $(COVERAGE_DIR) - @COVERAGE_DIR=$(COVERAGE_DIR) $(MAKE) test - @coverage combine $(COVERAGE_DIR)/* - @coverage html --include "*/gef.py" - @rm -rf $(COVERAGE_DIR) - diff --git a/tests/pytest.ini b/tests/pytest.ini index 3ed7602..726db8e 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -2,8 +2,9 @@ log_level = INFO minversion = 6.0 required_plugins = - pytest-xdist pytest-benchmark + pytest-cov + pytest-xdist python_functions = test_* time_* diff --git a/tests/utils.py b/tests/utils.py index e891640..91b556a 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -26,7 +26,7 @@ DEFAULT_TARGET = TMPDIR / "default.out" GEF_DEFAULT_PROMPT = "gef➤ " GEF_DEFAULT_TEMPDIR = "/tmp/gef" -GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "../gef/gef.py")) +GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "../gef/gef.py")).resolve() GEF_EXTRAS_TEST_DIR_PATH = pathlib.Path(__file__).absolute().parent GEF_EXTRAS_ROOT_PATH = GEF_EXTRAS_TEST_DIR_PATH.parent GEF_EXTRAS_SCRIPTS_PATH = GEF_EXTRAS_ROOT_PATH / "scripts" @@ -64,6 +64,11 @@ class GdbAssertionError(AssertionError): class GefUnitTestGeneric(unittest.TestCase): """Generic class for command testing, that defines all helpers""" + def setUp(self) -> None: + if not GEF_PATH.exists(): + raise FileNotFoundError(f"Missing gef.py (expected: '{GEF_PATH.absolute()}')") + return super().setUp() + @staticmethod def assertException(buf): """Assert that GEF raised an Exception.""" From 2f997a6c19bd4c21370d124c47b579a8e57a455a Mon Sep 17 00:00:00 2001 From: Boris-Chengbiao Zhou Date: Wed, 26 Oct 2022 03:32:27 +0200 Subject: [PATCH 33/39] Update architectures (#82) Update architectures to account for changes in gef. --- archs/arm-cortex-m.py | 3 +-- archs/m68k.py | 10 +++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/archs/arm-cortex-m.py b/archs/arm-cortex-m.py index 2746132..474c363 100644 --- a/archs/arm-cortex-m.py +++ b/archs/arm-cortex-m.py @@ -8,12 +8,11 @@ """ -@register_architecture class ARM_M(ARM): arch = "ARM-M" aliases = ("ARM-M", Elf.Abi.ARM) - all_registers = ARM.all_registers[:-1] + ["$xpsr", ] + all_registers = ARM.all_registers[:-1] + ("$xpsr",) flag_register = "$xpsr" flags_table = { 31: "negative", diff --git a/archs/m68k.py b/archs/m68k.py index ce669ae..c4b24e5 100644 --- a/archs/m68k.py +++ b/archs/m68k.py @@ -1,3 +1,4 @@ + """ M68K support for GEF @@ -7,7 +8,6 @@ Author: zhuyifei1999 """ -@register_architecture class M68K(Architecture): arch = "M68K" aliases = ("M68K", ) @@ -15,12 +15,12 @@ class M68K(Architecture): nop_insn = b"\x4e\x71" flag_register = "$ps" - all_registers = ["$d0", "$d1", "$d2", "$d3", "$d4", "$d5", "$d6", "$d7", + all_registers = ("$d0", "$d1", "$d2", "$d3", "$d4", "$d5", "$d6", "$d7", "$a0", "$a1", "$a2", "$a3", "$a4", "$a5", "$fp", "$sp", - "$ps", "$pc"] + "$ps", "$pc") instruction_length = None return_register = "$d0" - function_parameters = ["$sp", ] + function_parameters = ("$sp",) flags_table = { 0: "carry", 1: "overflow", @@ -31,7 +31,7 @@ class M68K(Architecture): 13: "supervisor", } syscall_register = "$d0" - syscall_instructions = ["trap #0"] + syscall_instructions = ("trap #0",) def flag_register_to_human(self, val=None): reg = self.flag_register From 6c31c184fd8e9e99de799887dcf5f901011278f7 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Tue, 8 Nov 2022 08:37:39 -0800 Subject: [PATCH 34/39] Update requirements.txt --- tests/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/requirements.txt b/tests/requirements.txt index 9180d02..2bf7753 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -2,4 +2,5 @@ pylint pytest pytest-xdist pytest-benchmark +pytest-forked coverage From 96c218a5fa8f6220a6a721ba51a474a964c9011d Mon Sep 17 00:00:00 2001 From: hugsy Date: Mon, 20 Mar 2023 13:55:54 -0700 Subject: [PATCH 35/39] [ropper -> 0.3] fixed bad type in path comparison --- scripts/ropper.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/scripts/ropper.py b/scripts/ropper.py index e10c588..b56ef48 100644 --- a/scripts/ropper.py +++ b/scripts/ropper.py @@ -1,11 +1,10 @@ -__AUTHOR__ = "hugsy" -__VERSION__ = 0.2 -__NAME__ = "ropper" - -import sys - -import gdb +from typing import List import ropper +import gdb +import sys +__AUTHOR__ = "hugsy" +__VERSION__ = 0.3 +__NAME__ = "ropper" @register @@ -13,7 +12,7 @@ class RopperCommand(GenericCommand): """Ropper (https://scoding.de/ropper/) plugin.""" _cmdline_ = "ropper" - _syntax_ = f"{_cmdline_} [ROPPER_OPTIONS]" + _syntax_ = f"{_cmdline_} [ROPPER_OPTIONS]" def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_NONE) @@ -30,6 +29,7 @@ def do_invoke(self, argv: List[str]) -> None: if not path: err("No file provided") return + path = str(path) sect = next(filter(lambda x: x.path == path, gef.memory.maps)) argv.append("--file") argv.append(path) @@ -41,6 +41,7 @@ def do_invoke(self, argv: List[str]) -> None: old_completer = self.__readline.get_completer() try: + print(argv) ropper.start(argv) except RuntimeWarning: return From 50e96ae97ae238b7bd568449b4b61d7265591dfa Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Tue, 21 Mar 2023 07:22:49 -0700 Subject: [PATCH 36/39] Update ropper.py --- scripts/ropper.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/ropper.py b/scripts/ropper.py index b56ef48..03ef5bb 100644 --- a/scripts/ropper.py +++ b/scripts/ropper.py @@ -41,7 +41,6 @@ def do_invoke(self, argv: List[str]) -> None: old_completer = self.__readline.get_completer() try: - print(argv) ropper.start(argv) except RuntimeWarning: return From 1fd89ef7892cde6cb21ca3281c721c2e61b88004 Mon Sep 17 00:00:00 2001 From: Dreg Date: Thu, 3 Aug 2023 02:41:11 +0200 Subject: [PATCH 37/39] missing space index.md (#86) * missing space index.md * Update docs/index.md Co-authored-by: crazy hugsy --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index faefdca..b9b4a73 100644 --- a/docs/index.md +++ b/docs/index.md @@ -51,7 +51,7 @@ As a reward, your Github avatar will be immortalize in the list below of contrib ### Feature requests -Well, that's ok! Just create an [Issue](https://github.com/hugsy/gef-extras/issues)explaining what cool feature/idea/command you had in mind! Even better, writethe documentation (Markdown format) for your command. It'll make easier forpeople who wants to integrate it! +Well, that's ok! Just create an [Issue](https://github.com/hugsy/gef-extras/issues) explaining what cool feature/idea/command you had in mind! Even better, write the documentation (Markdown format) for your command. It'll make easier for people who wants to integrate it! ### Sponsoring From 0a04af6baab31652c4bdbaafc6980239ed1df9af Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Thu, 3 Aug 2023 09:45:29 -0700 Subject: [PATCH 38/39] Missing `pytest-cov` for `tests/requirements.txt` --- tests/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/requirements.txt b/tests/requirements.txt index 2bf7753..c43a157 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,5 +1,6 @@ pylint pytest +pytest-cov pytest-xdist pytest-benchmark pytest-forked From 60230a38c60d5ab3a02fd34005ed186a6b818611 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Fri, 4 Aug 2023 11:45:16 -0700 Subject: [PATCH 39/39] [CI] Restore CI (#87) * Update run-tests.yml & .github/workflows/tests.yml * Renamed `run-tests` -> tests.yml, restored ci variables --- .github/workflows/run-tests.yml | 83 ---------------------- .github/workflows/tests.yml | 120 ++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 83 deletions(-) delete mode 100644 .github/workflows/run-tests.yml create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml deleted file mode 100644 index 6d0c399..0000000 --- a/.github/workflows/run-tests.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: CI Test for GEF-EXTRAS - -on: - push: - branches: - - main - - dev - - pull_request: - branches: - - main - - dev - -jobs: - build: - strategy: - fail-fast: false - matrix: - os: - - ubuntu-20.04 - - ubuntu-22.04 - name: "Run Unit tests on ${{ matrix.os }}" - runs-on: ${{ matrix.os }} - defaults: - run: - shell: bash - - steps: - - uses: actions/checkout@v3 - - - name: Install python and toolchain - run: | - sudo apt-get update - sudo apt-get install -y gdb-multiarch python3-dev python3-pip python3-wheel python3-setuptools git cmake gcc g++ pkg-config libglib2.0-dev gdbserver - sudo python3 -m pip install --upgrade pip - - - name: Set runtime environment variables - run: | - echo PY_VER=`gdb -q -nx -ex "pi print('.'.join(map(str, sys.version_info[:2])))" -ex quit` >> $GITHUB_ENV - echo NB_CPU=`grep -c ^processor /proc/cpuinfo` >> $GITHUB_ENV - echo GEF_CI_ARCH=`uname --processor` >> $GITHUB_ENV - echo GEF_CACHE_DIR=`python3 -m pip cache dir` >> $GITHUB_ENV - echo GEF_BRANCH=`git rev-parse --abbrev-ref HEAD` >> $GITHUB_ENV - echo GEF_PATH=`realpath ../gef` >> $GITHUB_ENV - echo GEF_SCRIPT=`realpath ../gef/gef.py` >> $GITHUB_ENV - - - name: Cache dependencies - uses: actions/cache@v3 - id: cache-deps - env: - cache-name: cache-deps - with: - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - path: | - ${{ env.GEF_CACHE_DIR }} - restore-keys: - ${{ runner.os }}-pip-${{ env.cache-name }}- - ${{ runner.os }}-pip- - ${{ runner.os }}-${{ env.cache-name }}- - ${{ runner.os }}- - - - name: Install requirements - run: | - mkdir -p ${{ env.GEF_CACHE_DIR }} - python${{ env.PY_VER }} -m pip install --user --upgrade -r ./requirements.txt - python${{ env.PY_VER }} -m pip install --user --upgrade -r ./tests/requirements.txt - - - name: Setup GEF - run: | - mkdir ${{ env.GEF_PATH }} - curl -fSsL https://raw.githubusercontent.com/hugsy/gef/${{ env.GEF_BRANCH }}/gef.py > ${{ env.GEF_SCRIPT }} - echo "source ${{ env.GEF_SCRIPT }}" > ~/.gdbinit - gdb -q -ex 'gef missing' -ex 'gef help' -ex 'gef config' -ex start -ex continue -ex quit /bin/pwd - - - name: Run Tests - run: | - make -C tests/binaries -j ${{ env.NB_CPU }} - python${{ env.PY_VER }} -m pytest --forked -n ${{ env.NB_CPU }} -v -k "not benchmark" tests/ - - - name: Run linter - run: | - python${{ env.PY_VER }} -m pylint --rcfile=$(pwd)/.pylintrc gef.py tests/*/*.py - diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..13da728 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,120 @@ +name: CI Test for GEF-EXTRAS + +env: + BRANCH: dev + +on: + push: + branches: + - main + - dev + + pull_request: + branches: + - main + - dev + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: + - ubuntu-20.04 + - ubuntu-22.04 + name: "Run Unit tests on ${{ matrix.os }}" + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash + + steps: + - uses: actions/checkout@v3 + + - name: Install python and toolchain + run: | + sudo apt-get update + sudo apt-get install -y wget gdb-multiarch python3-dev python3-pip python3-wheel python3-setuptools git cmake gcc g++ pkg-config libglib2.0-dev gdbserver + sudo python3 -m pip install --upgrade pip + + - name: Set runtime environment variables + run: | + echo PY_VER=`gdb -q -nx -ex "pi print('.'.join(map(str, sys.version_info[:2])))" -ex quit` >> $GITHUB_ENV + echo NB_CPU=`grep -c ^processor /proc/cpuinfo` >> $GITHUB_ENV + echo GEF_CI_ARCH=`uname --processor` >> $GITHUB_ENV + echo GEF_CACHE_DIR=`python3 -m pip cache dir` >> $GITHUB_ENV + echo GEF_PATH_DIR=${HOME}/gef >> $GITHUB_ENV + echo GEF_PATH="${HOME}/gef/gef.py" >> $GITHUB_ENV + + - name: Cache dependencies + uses: actions/cache@v3 + id: cache-deps + env: + cache-name: cache-deps + with: + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + path: | + ${{ env.GEF_CACHE_DIR }} + restore-keys: + ${{ runner.os }}-pip-${{ env.cache-name }}- + ${{ runner.os }}-pip- + ${{ runner.os }}-${{ env.cache-name }}- + ${{ runner.os }}- + + - name: Install requirements + run: | + mkdir -p ${{ env.GEF_CACHE_DIR }} + python${{ env.PY_VER }} -m pip install --user --upgrade -r ./requirements.txt -r ./tests/requirements.txt + + - name: Checkout GEF + run: | + mkdir -p ${{ env.GEF_PATH_DIR }} + wget -O ${{ env.GEF_PATH }} https://raw.githubusercontent.com/hugsy/gef/${{ env.BRANCH }}/gef.py + echo "source ${{ env.GEF_PATH }}" > ~/.gdbinit + gdb -q -ex 'gef missing' -ex 'gef help' -ex 'gef config' -ex start -ex continue -ex quit /bin/pwd + + - name: Build config file + run: | + gdb -q \ + -ex "gef config pcustom.struct_path '$(pwd)/structs'" \ + -ex "gef config syscall-args.path '$(pwd)/syscall-tables'" \ + -ex "gef config context.libc_args True" \ + -ex "gef config context.libc_args_path '$(pwd)/glibc-function-args'" \ + -ex 'gef save' \ + -ex quit + + - name: Run Tests + run: | + make -C tests/binaries -j ${{ env.NB_CPU }} + python${{ env.PY_VER }} -m pytest --forked -n ${{ env.NB_CPU }} -v -k "not benchmark" tests/ + + - name: Run linter + run: | + python${{ env.PY_VER }} -m pylint --rcfile=$(pwd)/.pylintrc gef.py tests/*/*.py + + standalone: + runs-on: ubuntu-latest + name: "Verify GEF-Extras install from gef/scripts" + steps: + - name: Install python and toolchain + run: | + sudo apt-get update + sudo apt-get install -y wget gdb-multiarch python3-dev python3-pip python3-wheel python3-setuptools git cmake gcc g++ pkg-config libglib2.0-dev gdbserver + sudo python3 -m pip install --upgrade pip + echo PY_VER=`gdb -q -nx -ex "pi print('.'.join(map(str, sys.version_info[:2])))" -ex quit` >> $GITHUB_ENV + echo NB_CPU=`grep -c ^processor /proc/cpuinfo` >> $GITHUB_ENV + echo GEF_CI_ARCH=`uname --processor` >> $GITHUB_ENV + echo GEF_CACHE_DIR=`python3 -m pip cache dir` >> $GITHUB_ENV + echo GEF_PATH_DIR=${HOME}/gef >> $GITHUB_ENV + echo GEF_EXTRAS_PATH=${HOME}/gef-extras >> $GITHUB_ENV + echo GEF_PATH="${HOME}/gef/gef.py" >> $GITHUB_ENV + + - name: Checkout GEF & GEF-Extras + run: | + mkdir -p ${{ env.GEF_PATH_DIR }} ${{ env.GEF_EXTRAS_PATH }} + wget -O ${{ env.GEF_PATH }} https://raw.githubusercontent.com/hugsy/gef/${{ env.BRANCH }}/gef.py + echo "source ${{ env.GEF_PATH }}" > ~/.gdbinit + wget -O ./gef-extras.sh https://github.com/hugsy/gef/raw/${{ env.BRANCH }}/scripts/gef-extras.sh + chmod +x ./gef-extras.sh + ./gef-extras.sh -b ${{ env.BRANCH }} -p ${HOME} + gdb -q -ex 'gef missing' -ex quit