Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add port, gdb_args, and gdbserver_args to gdb.debug() #2382

Merged
merged 8 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ The table below shows which release corresponds to each branch, and what date th

## 4.14.0 (`dev`)

- [#2382][2382] added optional port, gdb_args and gdbserver_args parameters to gdb.debug()
- [#2360][2360] Add offline parameter for `search_by_hash` series function
- [#2356][2356] Add local libc database provider for libcdb
- [#2374][2374] libcdb.unstrip_libc: debug symbols are fetched only if not present
Expand Down
38 changes: 25 additions & 13 deletions pwnlib/gdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ def _execve_script(argv, executable, env, ssh):
return tmp.name


def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None, python_wrapper_script=None):
def _gdbserver_args(pid=None, path=None, port=0, gdbserver_args=None, args=None, which=None, env=None, python_wrapper_script=None):
"""_gdbserver_args(pid=None, path=None, args=None, which=None, env=None) -> list

Sets up a listening gdbserver, to either connect to the specified
Expand All @@ -292,6 +292,8 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None, python
Arguments:
pid(int): Process ID to attach to
path(str): Process to launch
port(int): Port to use for gdbserver
gdbserver_args(list): List of additional arguments to pass to gdbserver
args(list): List of arguments to provide on the debugger command line
which(callaable): Function to find the path of a binary.
env(dict): Environment variables to pass to the program
Expand All @@ -300,6 +302,11 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None, python
Returns:
A list of arguments to invoke gdbserver.
"""
if gdbserver_args is None:
gdbserver_args = list()
elif not isinstance(gdbserver_args, (list, tuple)):
gdbserver_args = [gdbserver_args]

if [pid, path, args].count(None) != 2:
log.error("Must specify exactly one of pid, path, or args")

Expand All @@ -323,7 +330,7 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None, python

orig_args = args

gdbserver_args = [gdbserver, '--multi']
gdbserver_args = [gdbserver, '--multi'] + gdbserver_args
if context.aslr:
gdbserver_args += ['--no-disable-randomization']
else:
Expand All @@ -347,7 +354,7 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None, python
elif env is not None:
gdbserver_args += ['--wrapper', which('env'), '-i'] + env_args + ['--']

gdbserver_args += ['localhost:0']
gdbserver_args += ['localhost:%d' % port]
gdbserver_args += args

return gdbserver_args
Expand Down Expand Up @@ -412,17 +419,20 @@ def _get_runner(ssh=None):
else: return tubes.process.process

@LocalContext
def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=False, **kwargs):
def debug(args, gdbscript=None, gdb_args=None, exe=None, ssh=None, env=None, port=0, gdbserver_args=None, sysroot=None, api=False, **kwargs):
r"""
Launch a GDB server with the specified command line,
and launches GDB to attach to it.

Arguments:
args(list): Arguments to the process, similar to :class:`.process`.
gdbscript(str): GDB script to run.
gdb_args(list): List of additional arguments to pass to GDB.
exe(str): Path to the executable on disk
env(dict): Environment to start the binary in
ssh(:class:`.ssh`): Remote ssh session to use to launch the process.
port(int): Gdb port to use
gdbserver_args(list): List of additional arguments to pass to gdbserver
sysroot(str): Set an alternate system root. The system root is used to
load absolute shared library symbol files. This is useful to instruct
gdb to load a local version of binaries/libraries instead of downloading
Expand Down Expand Up @@ -612,7 +622,7 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
gdbscript = gdbscript or ''

if api and runner is not tubes.process.process:
raise ValueError('GDB Python API is supported only for local processes')
log.warn('GDB Python API is supported only for local processes')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like changing this without tests that proove full support. Then we could just remove the warning altogether. Which runner did work for you other than a process?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ssh.process works for me.
This makes sense IMO, because gdb is still run on the host session therefore attaching the gdb api through a unix socket still works. Changing it into a warning for ssh.process and an error for everything else

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7305502: GDB Python API is now a warning for ssh.process and an error for everything else


args, env = misc.normalize_argv_env(args, env, log)
if env:
Expand All @@ -628,15 +638,15 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=

if ssh or context.native or (context.os == 'android'):
if len(args) > 0 and which(packing._decode(args[0])) == packing._decode(exe):
args = _gdbserver_args(args=args, which=which, env=env)
args = _gdbserver_args(gdbserver_args=gdbserver_args, args=args, port=port, which=which, env=env)

else:
# GDBServer is limited in it's ability to manipulate argv[0]
# but can use the ``--wrapper`` option to execute commands and catches
# ``execve`` calls.
# Therefore, we use a wrapper script to execute the target binary
script = _execve_script(args, executable=exe, env=env, ssh=ssh)
args = _gdbserver_args(args=args, which=which, env=env, python_wrapper_script=script)
args = _gdbserver_args(gdbserver_args=gdbserver_args, args=args, port=port, which=which, env=env, python_wrapper_script=script)
else:
qemu_port = random.randint(1024, 65535)
qemu_user = qemu.user_path()
Expand Down Expand Up @@ -667,17 +677,19 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
# Set the .executable on the process object.
gdbserver.executable = exe

# Find what port we need to connect to
if ssh or context.native or (context.os == 'android'):
port = _gdbserver_port(gdbserver, ssh)
else:
port = qemu_port
# if the port was set manually we won't need to find it
if not port:
# Find what port we need to connect to
if ssh or context.native or (context.os == 'android'):
port = _gdbserver_port(gdbserver, ssh)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should still parse the output even though the port is specified explicitly. There were problems with shells forking when gdbserver uses them causing stalls which were detected by a timeout on the message in _gdbserver_port recently. And this way we're sure gdbserver is up and running before trying to attach.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7305502: now using gdbserver_port to check if the correct port was set

else:
port = qemu_port

host = '127.0.0.1'
if not ssh and context.os == 'android':
host = context.adb_host

tmp = attach((host, port), exe=exe, gdbscript=gdbscript, ssh=ssh, sysroot=sysroot, api=api)
tmp = attach((host, port), exe=exe, gdbscript=gdbscript, gdb_args=gdb_args, ssh=ssh, sysroot=sysroot, api=api)
if api:
_, gdb = tmp
gdbserver.gdb = gdb
Expand Down