Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ The table below shows which release corresponds to each branch, and what date th
- [#2611][2611] Cleanup `pwnlib.lexer` exports and imports
- [#2610][2610] Fix `log.progress` ignoring `context.log_console`
- [#2615][2615] tube/process: Fix redirecting stderr to stdout on Windows
- [#2630][2630] support `preexec_fn` in `debug()`

[2598]: https://github.com/Gallopsled/pwntools/pull/2598
[2419]: https://github.com/Gallopsled/pwntools/pull/2419
Expand Down Expand Up @@ -128,6 +129,7 @@ The table below shows which release corresponds to each branch, and what date th
[2611]: https://github.com/Gallopsled/pwntools/pull/2611
[2610]: https://github.com/Gallopsled/pwntools/pull/2610
[2615]: https://github.com/Gallopsled/pwntools/pull/2615
[2630]: https://github.com/Gallopsled/pwntools/pull/2630

## 4.15.0 (`beta`)

Expand Down
35 changes: 29 additions & 6 deletions pwnlib/gdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,8 @@ def debug_shellcode(data, gdbscript=None, vma=None, api=False):

return debug(tmp_elf, gdbscript=gdbscript, arch=context.arch, api=api)

def _execve_script(argv, executable, env, ssh):
"""_execve_script(argv, executable, env, ssh) -> str
def _execve_script(argv, executable, env, ssh, preexec_fn, preexec_args):
"""_execve_script(argv, executable, env, ssh, preexec_fn, preexec_args) -> str

Returns the filename of a python script that calls
execve the specified program with the specified arguments.
Expand All @@ -258,6 +258,8 @@ def _execve_script(argv, executable, env, ssh):
executable(bytes): Path to the program to run
env(dict): Environment variables to pass to the program
ssh(ssh): SSH connection to use if we are debugging a remote process
preexec_fn(callable): Callable to invoke before exec()
preexec_args(tuple): Args to pass to callable

Returns:
The filename of the created script.
Expand All @@ -269,7 +271,8 @@ def _execve_script(argv, executable, env, ssh):
# ssh.process with run=false creates the script for us
return ssh.process(argv, executable=executable, env=env, run=False)

script = misc._create_execve_script(argv=argv, executable=executable, env=env, log=log)
script = misc._create_execve_script(argv=argv, executable=executable, env=env, log=log, preexec_fn=preexec_fn,
preexec_args=preexec_args)
script = script.strip()
# Create a temporary file to hold the script
tmp = tempfile.NamedTemporaryFile(mode="w+t",prefix='pwnlib-execve-', suffix='.py', delete=False)
Expand Down Expand Up @@ -417,7 +420,8 @@ def _get_runner(ssh=None):
else: return tubes.process.process

@LocalContext
def debug(args, gdbscript=None, gdb_args=None, exe=None, ssh=None, env=None, port=0, gdbserver_args=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,
preexec_fn=None, preexec_args=(), **kwargs):
r"""
Launch a GDB server with the specified command line,
and launches GDB to attach to it.
Expand All @@ -436,6 +440,14 @@ def debug(args, gdbscript=None, gdb_args=None, exe=None, ssh=None, env=None, por
gdb to load a local version of binaries/libraries instead of downloading
them from the gdbserver, which is faster
api(bool): Enable access to GDB Python API.
preexec_fn(callable):
Function which is executed on the remote side before execve().
This **MUST** be a self-contained function -- it must perform
all of its own imports, and cannot refer to variables outside
its scope.
preexec_args(object):
Argument passed to ``preexec_fn``.
This **MUST** only consist of native Python objects.

Returns:
:class:`.process` or :class:`.ssh_channel`: A tube connected to the target process.
Expand Down Expand Up @@ -537,6 +549,17 @@ def debug(args, gdbscript=None, gdb_args=None, exe=None, ssh=None, env=None, por
>>> io.close()
>>> os.remove("./local-libc.so") # cleanup

Use preexec_fn

>>> def dup2(from_, to):
... import os
... os.dup2(from_, to)
>>> p = gdb.debug(['python','-c','import os; print(os.read(2,1024).decode())'],
... preexec_fn=dup2, preexec_args=(0,2))
>>> p.sendline(b'hello')
>>> p.recvline()
b'hello\n'


Using SSH:

Expand Down Expand Up @@ -649,15 +672,15 @@ def debug(args, gdbscript=None, gdb_args=None, exe=None, ssh=None, env=None, por
return runner(args, executable=exe, env=env)

if ssh or context.native or (context.os == 'android'):
if len(args) > 0 and which(packing._decode(args[0])) == packing._decode(exe):
if len(args) > 0 and which(packing._decode(args[0])) == packing._decode(exe) and preexec_fn is None:
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)
script = _execve_script(args, executable=exe, env=env, ssh=ssh, preexec_fn=preexec_fn, preexec_args=preexec_args)
args = _gdbserver_args(gdbserver_args=gdbserver_args, args=args, port=port, which=which, env=env, python_wrapper_script=script)
else:
qemu_port = port if port != 0 else random.randint(1024, 65535)
Expand Down
Loading