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
+```
+
+
+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:
+
+
+
+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:
+, 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 ...]]
+```
+
+
+
+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.
+
+
+
+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.
+
+
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
+```
+
+
+
+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` 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,
+
+
+
+Simply run
+
+```
+gef➤ mprotect 0xfffdd000
+```
+
+Et voilà! GEF will use the memory runtime information to correctly adjust the
+permissions of the entire section.
+
+
+
+Or for a full demo video on an AARCH64 VM:
+
+[](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:
+
+
+
+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 @@
+
+
+
+
+
-## Extra goodies for [`GEF`](https://github.com/hugsy/gef)
+## Extra goodies for [`GEF`](https://github.com/hugsy/gef)
-| **Documentation** | **Community** | **Try it** |
-|--|--|--|
-| [](https://gef-extras.readthedocs.io/en/latest/?badge=latest) | [](https://discordapp.com/channels/705160148813086841/705160148813086843) | [](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`) |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| [](https://github.com/hugsy/gef-extras/actions/workflows/generate-docs.yml) | [](https://github.com/hugsy/gef-extras/blob/master/LICENSE) | [](https://github.com/hugsy/gef-extras/) | [](https://github.com/hugsy/gef-extras/actions/workflows/run-tests.yml) | [](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 @@
+
+
+
+
+
-## Extra goodies for [`GEF`](https://github.com/hugsy/gef)
-
-| **Documentation** | **Community** | **Try it** |
-|--|--|--|
-| [](https://gef-extras.readthedocs.io/en/latest/?badge=latest) | [](https://discordapp.com/channels/705160148813086841/705160148813086843) | [](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 ###
+[  ](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
+```
+
+
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:
+
+[](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:
+
+[](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
```
-
+
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