Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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