mirror of https://github.com/pwndbg/pwndbg.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
661 lines
22 KiB
Python
661 lines
22 KiB
Python
import argparse
|
|
import functools
|
|
import io
|
|
from enum import Enum
|
|
from typing import Dict
|
|
from typing import List
|
|
|
|
import gdb
|
|
|
|
import pwndbg.exception
|
|
import pwndbg.gdblib.kernel
|
|
import pwndbg.gdblib.regs
|
|
import pwndbg.heap
|
|
from pwndbg.color import message
|
|
from pwndbg.heap.ptmalloc import DebugSymsHeap
|
|
from pwndbg.heap.ptmalloc import HeuristicHeap
|
|
from pwndbg.heap.ptmalloc import SymbolUnresolvableError
|
|
|
|
commands = [] # type: List[Command]
|
|
command_names = set()
|
|
|
|
|
|
class CommandCategory(str, Enum):
|
|
START = "Start"
|
|
NEXT = "Step/Next/Continue"
|
|
CONTEXT = "Context"
|
|
HEAP = "Heap"
|
|
BREAKPOINT = "Breakpoint"
|
|
MEMORY = "Memory"
|
|
STACK = "Stack"
|
|
REGISTER = "Register"
|
|
PROCESS = "Process"
|
|
LINUX = "Linux/libc/ELF"
|
|
DISASS = "Disassemble"
|
|
MISC = "Misc"
|
|
KERNEL = "Kernel"
|
|
INTEGRATIONS = "Integrations"
|
|
WINDBG = "WinDbg"
|
|
PWNDBG = "pwndbg"
|
|
SHELL = "Shell"
|
|
|
|
|
|
def list_current_commands():
|
|
current_pagination = gdb.execute("show pagination", to_string=True)
|
|
current_pagination = current_pagination.split()[-1].rstrip(
|
|
"."
|
|
) # Take last word and skip period
|
|
|
|
gdb.execute("set pagination off")
|
|
command_list = gdb.execute("help all", to_string=True).strip().split("\n")
|
|
existing_commands = set()
|
|
for line in command_list:
|
|
line = line.strip()
|
|
# Skip non-command entries
|
|
if (
|
|
not line
|
|
or line.startswith("Command class:")
|
|
or line.startswith("Unclassified commands")
|
|
):
|
|
continue
|
|
command = line.split()[0]
|
|
existing_commands.add(command)
|
|
gdb.execute("set pagination %s" % current_pagination) # Restore original setting
|
|
return existing_commands
|
|
|
|
|
|
GDB_BUILTIN_COMMANDS = list_current_commands()
|
|
|
|
# Set in `reload` command so that we can skip double checking for registration
|
|
# of an already existing command when re-registering GDB CLI commands
|
|
# (there is no way to unregister a command in GDB 12.x)
|
|
pwndbg_is_reloading = getattr(gdb, "pwndbg_is_reloading", False)
|
|
|
|
|
|
class Command(gdb.Command):
|
|
"""Generic command wrapper"""
|
|
|
|
builtin_override_whitelist = {"up", "down", "search", "pwd", "start", "ignore"}
|
|
history = {} # type: Dict[int,str]
|
|
|
|
def __init__(
|
|
self,
|
|
function,
|
|
prefix=False,
|
|
command_name=None,
|
|
shell=False,
|
|
is_alias=False,
|
|
aliases=[],
|
|
category=CommandCategory.MISC,
|
|
) -> None:
|
|
self.is_alias: bool = is_alias
|
|
self.aliases = aliases
|
|
self.category = category
|
|
self.shell: bool = shell
|
|
|
|
if command_name is None:
|
|
command_name = function.__name__
|
|
|
|
super().__init__(command_name, gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION, prefix=prefix)
|
|
self.function = function
|
|
|
|
if command_name in command_names:
|
|
raise Exception("Cannot add command %s: already exists." % command_name)
|
|
if (
|
|
command_name in GDB_BUILTIN_COMMANDS
|
|
and command_name not in self.builtin_override_whitelist
|
|
and not pwndbg_is_reloading
|
|
):
|
|
raise Exception('Cannot override non-whitelisted built-in command "%s"' % command_name)
|
|
|
|
command_names.add(command_name)
|
|
commands.append(self)
|
|
|
|
functools.update_wrapper(self, function)
|
|
self.__name__ = command_name
|
|
|
|
self.repeat = False
|
|
|
|
def split_args(self, argument):
|
|
"""Split a command-line string from the user into arguments.
|
|
|
|
Returns:
|
|
A ``(tuple, dict)``, in the form of ``*args, **kwargs``.
|
|
The contents of the tuple/dict are undefined.
|
|
"""
|
|
return gdb.string_to_argv(argument), {}
|
|
|
|
def invoke(self, argument, from_tty):
|
|
"""Invoke the command with an argument string"""
|
|
try:
|
|
args, kwargs = self.split_args(argument)
|
|
except SystemExit:
|
|
# Raised when the usage is printed by an ArgparsedCommand
|
|
return
|
|
except (TypeError, gdb.error):
|
|
pwndbg.exception.handle(self.function.__name__)
|
|
return
|
|
|
|
try:
|
|
self.repeat = self.check_repeated(argument, from_tty)
|
|
return self(*args, **kwargs)
|
|
finally:
|
|
self.repeat = False
|
|
|
|
def check_repeated(self, argument, from_tty) -> bool:
|
|
"""Keep a record of all commands which come from the TTY.
|
|
|
|
Returns:
|
|
True if this command was executed by the user just hitting "enter".
|
|
"""
|
|
# Don't care unless it's interactive use
|
|
if not from_tty:
|
|
return False
|
|
|
|
lines = gdb.execute("show commands", from_tty=False, to_string=True)
|
|
lines = lines.splitlines()
|
|
|
|
# No history
|
|
if not lines:
|
|
return False
|
|
|
|
last_line = lines[-1]
|
|
number_str, command = last_line.split(maxsplit=1)
|
|
try:
|
|
number = int(number_str)
|
|
except ValueError:
|
|
# Workaround for a GDB 8.2 bug when show commands return error value
|
|
# See issue #523
|
|
return False
|
|
|
|
# A new command was entered by the user
|
|
if number not in Command.history:
|
|
Command.history[number] = command
|
|
return False
|
|
|
|
# Somehow the command is different than we got before?
|
|
if not command.endswith(argument):
|
|
return False
|
|
|
|
return True
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
try:
|
|
return self.function(*args, **kwargs)
|
|
except TypeError as te:
|
|
print("%r: %s" % (self.function.__name__.strip(), self.function.__doc__.strip()))
|
|
pwndbg.exception.handle(self.function.__name__)
|
|
except Exception:
|
|
pwndbg.exception.handle(self.function.__name__)
|
|
|
|
|
|
def fix(arg, sloppy=False, quiet=True, reraise=False):
|
|
"""Fix a single command-line argument coming from the GDB CLI.
|
|
|
|
Arguments:
|
|
arg(str): Original string representation (e.g. '0', '$rax', '$rax+44')
|
|
sloppy(bool): If ``arg`` cannot be evaluated, return ``arg``. (default: False)
|
|
quiet(bool): If an error occurs, suppress it. (default: True)
|
|
reraise(bool): If an error occurs, raise the exception. (default: False)
|
|
|
|
Returns:
|
|
Ideally ``gdb.Value`` object. May return a ``str`` if ``sloppy==True``.
|
|
May return ``None`` if ``sloppy == False and reraise == False``.
|
|
"""
|
|
if isinstance(arg, gdb.Value):
|
|
return arg
|
|
|
|
try:
|
|
parsed = gdb.parse_and_eval(arg)
|
|
return parsed
|
|
except Exception:
|
|
pass
|
|
|
|
try:
|
|
arg = pwndbg.gdblib.regs.fix(arg)
|
|
return gdb.parse_and_eval(arg)
|
|
except Exception as e:
|
|
if not quiet:
|
|
print(e)
|
|
if reraise:
|
|
raise e
|
|
|
|
if sloppy:
|
|
return arg
|
|
|
|
return None
|
|
|
|
|
|
def fix_int(*a, **kw):
|
|
return int(fix(*a, **kw))
|
|
|
|
|
|
def fix_int_reraise(*a, **kw):
|
|
# Type error likely due to https://github.com/python/mypy/issues/6799
|
|
return fix(*a, reraise=True, **kw) # type: ignore[misc]
|
|
|
|
|
|
def OnlyWithFile(function):
|
|
@functools.wraps(function)
|
|
def _OnlyWithFile(*a, **kw):
|
|
if pwndbg.gdblib.proc.exe:
|
|
return function(*a, **kw)
|
|
else:
|
|
if pwndbg.gdblib.qemu.is_qemu():
|
|
print(message.error("Could not determine the target binary on QEMU."))
|
|
else:
|
|
print(message.error("%s: There is no file loaded." % function.__name__))
|
|
|
|
return _OnlyWithFile
|
|
|
|
|
|
def OnlyWhenQemuKernel(function):
|
|
@functools.wraps(function)
|
|
def _OnlyWhenQemuKernel(*a, **kw):
|
|
if pwndbg.gdblib.qemu.is_qemu_kernel():
|
|
return function(*a, **kw)
|
|
else:
|
|
print(
|
|
"%s: This command may only be run when debugging the Linux kernel in QEMU."
|
|
% function.__name__
|
|
)
|
|
|
|
return _OnlyWhenQemuKernel
|
|
|
|
|
|
def OnlyWithArch(arch_names: List[str]):
|
|
"""Decorates function to work only with the specified archictectures."""
|
|
for arch in arch_names:
|
|
if arch not in pwndbg.gdblib.arch_mod.ARCHS:
|
|
raise ValueError(
|
|
f"OnlyWithArch used with unsupported arch={arch}. Must be one of {', '.join(arch_names)}"
|
|
)
|
|
|
|
def decorator(function):
|
|
@functools.wraps(function)
|
|
def _OnlyWithArch(*a, **kw):
|
|
if pwndbg.gdblib.arch.name in arch_names:
|
|
return function(*a, **kw)
|
|
else:
|
|
arches_str = ", ".join(arch_names)
|
|
print(
|
|
f"%s: This command may only be run on the {arches_str} architecture(s)"
|
|
% function.__name__
|
|
)
|
|
|
|
return _OnlyWithArch
|
|
|
|
return decorator
|
|
|
|
|
|
def OnlyWithKernelDebugSyms(function):
|
|
@functools.wraps(function)
|
|
def _OnlyWithKernelDebugSyms(*a, **kw):
|
|
if pwndbg.gdblib.kernel.has_debug_syms():
|
|
return function(*a, **kw)
|
|
else:
|
|
print(
|
|
"%s: This command may only be run when debugging a Linux kernel with debug symbols."
|
|
% function.__name__
|
|
)
|
|
|
|
return _OnlyWithKernelDebugSyms
|
|
|
|
|
|
def OnlyWhenPagingEnabled(function):
|
|
@functools.wraps(function)
|
|
def _OnlyWhenPagingEnabled(*a, **kw):
|
|
if pwndbg.gdblib.kernel.paging_enabled():
|
|
return function(*a, **kw)
|
|
else:
|
|
print("%s: This command may only be run when paging is enabled." % function.__name__)
|
|
|
|
return _OnlyWhenPagingEnabled
|
|
|
|
|
|
def OnlyWhenRunning(function):
|
|
@functools.wraps(function)
|
|
def _OnlyWhenRunning(*a, **kw):
|
|
if pwndbg.gdblib.proc.alive:
|
|
return function(*a, **kw)
|
|
else:
|
|
print("%s: The program is not being run." % function.__name__)
|
|
|
|
return _OnlyWhenRunning
|
|
|
|
|
|
def OnlyWithTcache(function):
|
|
@functools.wraps(function)
|
|
def _OnlyWithTcache(*a, **kw):
|
|
if pwndbg.heap.current.has_tcache():
|
|
return function(*a, **kw)
|
|
else:
|
|
print(
|
|
"%s: This version of GLIBC was not compiled with tcache support."
|
|
% function.__name__
|
|
)
|
|
|
|
return _OnlyWithTcache
|
|
|
|
|
|
def OnlyWhenHeapIsInitialized(function):
|
|
@functools.wraps(function)
|
|
def _OnlyWhenHeapIsInitialized(*a, **kw):
|
|
if pwndbg.heap.current.is_initialized():
|
|
return function(*a, **kw)
|
|
else:
|
|
print("%s: Heap is not initialized yet." % function.__name__)
|
|
|
|
return _OnlyWhenHeapIsInitialized
|
|
|
|
|
|
# TODO/FIXME: Move this elsewhere? Have better logic for that? Maybe caching?
|
|
def _is_statically_linked() -> bool:
|
|
out = gdb.execute("info dll", to_string=True)
|
|
return "No shared libraries loaded at this time." in out
|
|
|
|
|
|
def _try2run_heap_command(function, a, kw):
|
|
e = lambda s: print(message.error(s))
|
|
w = lambda s: print(message.warn(s))
|
|
# Note: We will still raise the error for developers when exception-* is set to "on"
|
|
try:
|
|
return function(*a, **kw)
|
|
except SymbolUnresolvableError as err:
|
|
e(f"{function.__name__}: Fail to resolve the symbol: `{err.symbol}`")
|
|
if "thread_arena" == err.symbol:
|
|
w(
|
|
"You are probably debugging a multi-threaded target without debug symbols, so we failed to determine which arena is used by the current thread.\n"
|
|
"To resolve this issue, you can use the `arenas` command to list all arenas, and use `set thread-arena <addr>` to set the current thread's arena address you think is correct.\n"
|
|
)
|
|
else:
|
|
w(
|
|
f"You can try to determine the libc symbols addresses manually and set them appropriately. For this, see the `heap_config` command output and set the config for `{err.symbol}`."
|
|
)
|
|
if pwndbg.gdblib.config.exception_verbose or pwndbg.gdblib.config.exception_debugger:
|
|
raise err
|
|
else:
|
|
pwndbg.exception.inform_verbose_and_debug()
|
|
except Exception as err:
|
|
e(f"{function.__name__}: An unknown error occurred when running this command.")
|
|
if isinstance(pwndbg.heap.current, HeuristicHeap):
|
|
w(
|
|
"Maybe you can try to determine the libc symbols addresses manually, set them appropriately and re-run this command. For this, see the `heap_config` command output and set the `main_arena`, `mp_`, `global_max_fast`, `tcache` and `thread_arena` addresses."
|
|
)
|
|
else:
|
|
w("You can try `set resolve-heap-via-heuristic force` and re-run this command.\n")
|
|
if pwndbg.gdblib.config.exception_verbose or pwndbg.gdblib.config.exception_debugger:
|
|
raise err
|
|
else:
|
|
pwndbg.exception.inform_verbose_and_debug()
|
|
|
|
|
|
def OnlyWithResolvedHeapSyms(function):
|
|
@functools.wraps(function)
|
|
def _OnlyWithResolvedHeapSyms(*a, **kw):
|
|
e = lambda s: print(message.error(s))
|
|
w = lambda s: print(message.warn(s))
|
|
if (
|
|
isinstance(pwndbg.heap.current, HeuristicHeap)
|
|
and pwndbg.gdblib.config.resolve_heap_via_heuristic == "auto"
|
|
and DebugSymsHeap().can_be_resolved()
|
|
):
|
|
# In auto mode, we will try to use the debug symbols if possible
|
|
pwndbg.heap.current = DebugSymsHeap()
|
|
if pwndbg.heap.current.can_be_resolved():
|
|
return _try2run_heap_command(function, a, kw)
|
|
else:
|
|
if (
|
|
isinstance(pwndbg.heap.current, DebugSymsHeap)
|
|
and pwndbg.gdblib.config.resolve_heap_via_heuristic == "auto"
|
|
):
|
|
# In auto mode, if the debug symbols are not enough, we will try to use the heuristic if possible
|
|
heuristic_heap = HeuristicHeap()
|
|
if heuristic_heap.can_be_resolved():
|
|
pwndbg.heap.current = heuristic_heap
|
|
w(
|
|
"pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.\n"
|
|
"This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.\n"
|
|
)
|
|
return _try2run_heap_command(function, a, kw)
|
|
elif _is_statically_linked():
|
|
e(
|
|
"Can't find GLIBC version required for this command to work since this is a statically linked binary"
|
|
)
|
|
w(
|
|
"Please set the GLIBC version you think the target binary was compiled (using `set glibc <version>` command; e.g. 2.32) and re-run this command."
|
|
)
|
|
else:
|
|
e(
|
|
"Can't find GLIBC version required for this command to work, maybe is because GLIBC is not loaded yet."
|
|
)
|
|
w(
|
|
"If you believe the GLIBC is loaded or this is a statically linked binary. "
|
|
"Please set the GLIBC version you think the target binary was compiled (using `set glibc <version>` command; e.g. 2.32) and re-run this command"
|
|
)
|
|
elif (
|
|
isinstance(pwndbg.heap.current, DebugSymsHeap)
|
|
and pwndbg.gdblib.config.resolve_heap_via_heuristic == "force"
|
|
):
|
|
e(
|
|
"You are forcing to resolve the heap symbols via heuristic, but we cannot resolve the heap via the debug symbols."
|
|
)
|
|
w("Use `set resolve-heap-via-heuristic auto` and re-run this command.")
|
|
elif pwndbg.glibc.get_version() is None:
|
|
if _is_statically_linked():
|
|
e("Can't resolve the heap since the GLIBC version is not set.")
|
|
w(
|
|
"Please set the GLIBC version you think the target binary was compiled (using `set glibc <version>` command; e.g. 2.32) and re-run this command."
|
|
)
|
|
else:
|
|
e(
|
|
"Can't find GLIBC version required for this command to work, maybe is because GLIBC is not loaded yet."
|
|
)
|
|
w(
|
|
"If you believe the GLIBC is loaded or this is a statically linked binary. "
|
|
"Please set the GLIBC version you think the target binary was compiled (using `set glibc <version>` command; e.g. 2.32) and re-run this command"
|
|
)
|
|
else:
|
|
# Note: Should not see this error, but just in case
|
|
e("An unknown error occurred when resolved the heap.")
|
|
pwndbg.exception.inform_report_issue(
|
|
"An unknown error occurred when resolved the heap"
|
|
)
|
|
|
|
return _OnlyWithResolvedHeapSyms
|
|
|
|
|
|
class _ArgparsedCommand(Command):
|
|
def __init__(
|
|
self,
|
|
parser,
|
|
function,
|
|
command_name=None,
|
|
*a,
|
|
**kw,
|
|
) -> None:
|
|
self.parser = parser
|
|
if command_name is None:
|
|
self.parser.prog = function.__name__
|
|
else:
|
|
self.parser.prog = command_name
|
|
|
|
file = io.StringIO()
|
|
self.parser.print_help(file)
|
|
file.seek(0)
|
|
self.__doc__ = file.read()
|
|
# Note: function.__doc__ is used in the `pwndbg [filter]` command display
|
|
function.__doc__ = self.parser.description.strip()
|
|
|
|
# Type error likely due to https://github.com/python/mypy/issues/6799
|
|
super().__init__( # type: ignore[misc]
|
|
function,
|
|
command_name=command_name,
|
|
*a,
|
|
**kw,
|
|
)
|
|
|
|
def split_args(self, argument):
|
|
argv = gdb.string_to_argv(argument)
|
|
return tuple(), vars(self.parser.parse_args(argv))
|
|
|
|
|
|
class ArgparsedCommand:
|
|
"""Adds documentation and offloads parsing for a Command via argparse"""
|
|
|
|
def __init__(
|
|
self, parser_or_desc, aliases=[], command_name=None, category=CommandCategory.MISC
|
|
) -> None:
|
|
"""
|
|
:param parser_or_desc: `argparse.ArgumentParser` instance or `str`
|
|
"""
|
|
if isinstance(parser_or_desc, str):
|
|
self.parser = argparse.ArgumentParser(description=parser_or_desc)
|
|
else:
|
|
self.parser = parser_or_desc
|
|
self.aliases = aliases
|
|
self._command_name = command_name
|
|
self.category = category
|
|
# We want to run all integer and otherwise-unspecified arguments
|
|
# through fix() so that GDB parses it.
|
|
for action in self.parser._actions:
|
|
if isinstance(action, argparse._SubParsersAction):
|
|
action.type = str
|
|
if action.dest == "help":
|
|
continue
|
|
if action.type in (int, None):
|
|
action.type = fix_int_reraise
|
|
if action.default is not None:
|
|
action.help += " (default: %(default)s)"
|
|
|
|
def __call__(self, function):
|
|
for alias in self.aliases:
|
|
_ArgparsedCommand(
|
|
self.parser, function, command_name=alias, is_alias=True, category=self.category
|
|
)
|
|
return _ArgparsedCommand(
|
|
self.parser,
|
|
function,
|
|
command_name=self._command_name,
|
|
aliases=self.aliases,
|
|
category=self.category,
|
|
)
|
|
|
|
|
|
# We use a 64-bit max value literal here instead of pwndbg.gdblib.arch.current
|
|
# as realistically its ok to pull off the biggest possible type here
|
|
# We cache its GDB value type which is 'unsigned long long'
|
|
_mask = 0xFFFFFFFFFFFFFFFF
|
|
_mask_val_type = gdb.Value(_mask).type
|
|
|
|
|
|
def sloppy_gdb_parse(s):
|
|
"""
|
|
This function should be used as ``argparse.ArgumentParser`` .add_argument method's `type` helper.
|
|
|
|
This makes the type being parsed as gdb value and if that parsing fails,
|
|
a string is returned.
|
|
|
|
:param s: String.
|
|
:return: Whatever gdb.parse_and_eval returns or string.
|
|
"""
|
|
try:
|
|
val = gdb.parse_and_eval(s)
|
|
# We can't just return int(val) because GDB may return:
|
|
# "Python Exception <class 'gdb.error'> Cannot convert value to long."
|
|
# e.g. for:
|
|
# pwndbg> pi int(gdb.parse_and_eval('__libc_start_main'))
|
|
#
|
|
# Here, the _mask_val.type should be `unsigned long long`
|
|
return int(val.cast(_mask_val_type))
|
|
except (TypeError, gdb.error):
|
|
return s
|
|
|
|
|
|
def AddressExpr(s):
|
|
"""
|
|
Parses an address expression. Returns an int.
|
|
"""
|
|
val = sloppy_gdb_parse(s)
|
|
|
|
if not isinstance(val, int):
|
|
raise argparse.ArgumentTypeError("Incorrect address (or GDB expression): %s" % s)
|
|
|
|
return val
|
|
|
|
|
|
def HexOrAddressExpr(s):
|
|
"""
|
|
Parses string as hexadecimal int or an address expression. Returns an int.
|
|
(e.g. '1234' will return 0x1234)
|
|
"""
|
|
try:
|
|
return int(s, 16)
|
|
except ValueError:
|
|
return AddressExpr(s)
|
|
|
|
|
|
def load_commands() -> None:
|
|
# pylint: disable=import-outside-toplevel
|
|
import pwndbg.commands.ai
|
|
import pwndbg.commands.argv
|
|
import pwndbg.commands.aslr
|
|
import pwndbg.commands.attachp
|
|
import pwndbg.commands.auxv
|
|
import pwndbg.commands.canary
|
|
import pwndbg.commands.checksec
|
|
import pwndbg.commands.comments
|
|
import pwndbg.commands.config
|
|
import pwndbg.commands.context
|
|
import pwndbg.commands.cpsr
|
|
import pwndbg.commands.cyclic
|
|
import pwndbg.commands.cymbol
|
|
import pwndbg.commands.distance
|
|
import pwndbg.commands.dt
|
|
import pwndbg.commands.dumpargs
|
|
import pwndbg.commands.elf
|
|
import pwndbg.commands.flags
|
|
import pwndbg.commands.ghidra
|
|
import pwndbg.commands.got
|
|
import pwndbg.commands.heap
|
|
import pwndbg.commands.hexdump
|
|
import pwndbg.commands.ida
|
|
import pwndbg.commands.ignore
|
|
import pwndbg.commands.ipython_interactive
|
|
import pwndbg.commands.kbase
|
|
import pwndbg.commands.kchecksec
|
|
import pwndbg.commands.kcmdline
|
|
import pwndbg.commands.kconfig
|
|
import pwndbg.commands.kversion
|
|
import pwndbg.commands.leakfind
|
|
import pwndbg.commands.memoize
|
|
import pwndbg.commands.misc
|
|
import pwndbg.commands.mprotect
|
|
import pwndbg.commands.nearpc
|
|
import pwndbg.commands.next
|
|
import pwndbg.commands.p2p
|
|
import pwndbg.commands.patch
|
|
import pwndbg.commands.peda
|
|
import pwndbg.commands.pie
|
|
import pwndbg.commands.probeleak
|
|
import pwndbg.commands.procinfo
|
|
import pwndbg.commands.radare2
|
|
import pwndbg.commands.reload
|
|
import pwndbg.commands.rizin
|
|
import pwndbg.commands.rop
|
|
import pwndbg.commands.ropper
|
|
import pwndbg.commands.search
|
|
import pwndbg.commands.segments
|
|
import pwndbg.commands.shell
|
|
import pwndbg.commands.slab
|
|
import pwndbg.commands.stack
|
|
import pwndbg.commands.start
|
|
import pwndbg.commands.telescope
|
|
import pwndbg.commands.tls
|
|
import pwndbg.commands.valist
|
|
import pwndbg.commands.version
|
|
import pwndbg.commands.vmmap
|
|
import pwndbg.commands.windbg
|
|
import pwndbg.commands.xinfo
|
|
import pwndbg.commands.xor
|