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

Bugfix gdb.debug: exe parameter now respected #2233

Merged
merged 25 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b586784
Bugfix gdb.debug: exe parameter now respected
goreil Jul 12, 2023
2aea417
Merge branch 'dev' into stable-gdb-exe
goreil Jul 12, 2023
0638284
Backintegration for python2
goreil Jul 12, 2023
77b6601
Update Changelog.md
goreil Jul 12, 2023
c8563a0
pwnlib gdb.py, try to pass these tests...
goreil Jul 12, 2023
12e782b
Encode script in ssh.process(run=False) for tests
goreil Jul 13, 2023
1dcc5d4
Re-trigger Pull request tests
goreil Jul 14, 2023
d03b8ba
Merge branch 'Gallopsled:dev' into stable-gdb-exe
goreil Jul 19, 2023
a70b171
gdb.py: easier script if argv[0] == exe
goreil Sep 14, 2023
dc430d4
gdb.py: Add test case for LD_Preload
goreil Sep 14, 2023
06c03e8
Add check that "=" not in misc.normalize_argv_env
goreil Sep 14, 2023
38c6025
gdb.py Correct handling of LD_ env-variables
goreil Sep 15, 2023
1e4c87b
Merge branch 'dev' into stable-gdb-exe
peace-maker Sep 15, 2023
40e908b
Merge branch 'Gallopsled:dev' into stable-gdb-exe
goreil Sep 21, 2023
f69971a
Update pwnlib/gdb.py
goreil Sep 21, 2023
cd84e32
gdb.py address comments
goreil Sep 21, 2023
e45f984
gdb.py: Fix Namedtempfile-prefix + Bugfix
goreil Sep 30, 2023
7461e8c
Restore prefix for gdbscript
goreil Sep 30, 2023
26c031c
Merge branch 'dev' into stable-gdb-exe
goreil Nov 23, 2023
4e2c68a
Unify execve wrapper script under one function
goreil Nov 26, 2023
f2c87da
Merge branch 'stable-gdb-exe' of github.com:Youheng-Lue/pwntools into…
goreil Nov 26, 2023
080a798
Merge branch 'dev' into stable-gdb-exe
goreil Nov 26, 2023
5e093fd
gdb.py, Remove leftover script
goreil Nov 26, 2023
50e6f90
Fix logging scope and ignore_environ argument
peace-maker Feb 16, 2024
fdabe18
Merge branch 'dev' into stable-gdb-exe
peace-maker Feb 16, 2024
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 @@ -71,11 +71,13 @@ The table below shows which release corresponds to each branch, and what date th
- [#2117][2117] Add -p (--prefix) and -s (--separator) arguments to `hex` command
- [#2221][2221] Add shellcraft.sleep template wrapping SYS_nanosleep
- [#2219][2219] Fix passing arguments on the stack in shellcraft syscall template
- [#2227][2227] Fix gdb.debug: exe parameter now respected

[2202]: https://github.com/Gallopsled/pwntools/pull/2202
[2117]: https://github.com/Gallopsled/pwntools/pull/2117
[2221]: https://github.com/Gallopsled/pwntools/pull/2221
[2219]: https://github.com/Gallopsled/pwntools/pull/2219
[2227]: https://github.com/Gallopsled/pwntools/pull/2227

## 4.11.0 (`beta`)

Expand Down
100 changes: 97 additions & 3 deletions pwnlib/gdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@

from contextlib import contextmanager
import os
import sys
import platform
import psutil
import random
Expand Down Expand Up @@ -249,7 +250,65 @@ def debug_shellcode(data, gdbscript=None, vma=None, api=False):

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

def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
def _execve_script(argv, executable, env, ssh):
"""_execve_script(argv, executable, env, ssh) -> str

Returns the filename of a python script that calls
execve the specified program with the specified arguments.
This script is suitable to call with gdbservers ``--wrapper`` option,
so we have more control over the environment of the debugged process.

Arguments:
argv(list): List of arguments to pass to the program
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

Returns:
The filename of the created script.
"""
# Make sure args are bytes, not str or bytearray.
argv = [bytes(packing._encode(arg)) for arg in argv]
executable = packing._encode(executable)
if ssh:
# ssh.process creates the script for us
return ssh.process(argv, executable=executable, env=env, run=False)

# Convert environment to a list of key=value strings
if env is None:
env_list = []
else:
env_list = [bytes(key) + b"=" + bytes(value) for key, value in env.items()]

# Create a python script that calls execve
# This script uses ctypes to call execve directly, instead of using os.execve
# since os.execve doesn't allow an empty argv[0]
exe = sys.executable
script = """
#!{exe!s}
import ctypes

# ctypes helper to convert a python list to a NULL-terminated C array
def to_carray(py_list):
py_list += [None] # NULL-terminated
return (ctypes.c_char_p * len(py_list))(*py_list)
c_argv = to_carray({argv!r})
c_env = to_carray({env_list!r})
# Call execve
execve = ctypes.CDLL(None).execve
execve({executable!r}, c_argv, c_env)""".format(**locals())
script = script.strip()
# Create a temporary file to hold the script
tmp = tempfile.NamedTemporaryFile(mode="w+t",prefix='pwn', suffix='.py', delete=False)
tmp.write(script)
# Make script executable
os.fchmod(tmp.fileno(), 0o755)
log.debug("Created execve wrapper script %s:\n%s", tmp.name, script)

return tmp.name


def _gdbserver_args(pid=None, path=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 @@ -260,6 +319,8 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
path(str): Process to launch
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
python_wrapper_script(str): Path to a python script to use with ``--wrapper``

Returns:
A list of arguments to invoke gdbserver.
Expand Down Expand Up @@ -296,7 +357,10 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
if pid:
gdbserver_args += ['--once', '--attach']

if env is not None:
if python_wrapper_script:
gdbserver_args += ['--wrapper', python_wrapper_script, '--']

elif env is not None:
env_args = []
for key in tuple(env):
if key.startswith(b'LD_'): # LD_PRELOAD / LD_LIBRARY_PATH etc.
Expand Down Expand Up @@ -458,6 +522,13 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=

>>> io.interactive() # doctest: +SKIP
>>> io.close()

Start a new process with modified argv[0]
>>> io = gdb.debug("/bin/sh", 'continue', argv=[b'\xde\xad\xbe\xef'])
>>> io.sendline(b"echo $0")
>>> io.recvline()
b'$ \xde\xad\xbe\xef\n'
>>> io.close()

Using GDB Python API:

Expand Down Expand Up @@ -509,6 +580,20 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
Interact with the process
>>> io.interactive() # doctest: +SKIP
>>> io.close()

Using a modified argv[0] on a remote process
>>> io = gdb.debug("/bin/sh", 'continue', argv=[b'\xde\xad\xbe\xef'], ssh=shell)
>>> io.sendline(b"echo $0")
>>> io.recvline()
b'$ \xde\xad\xbe\xef\n'
>>> io.close()

Using an empty argv[0] on a remote process
>>> io = gdb.debug("/bin/sh", 'continue', argv=[], ssh=shell)
>>> io.sendline(b"echo $0")
>>> io.recvline()
b'$ \n'
>>> io.close()
"""
if isinstance(args, six.integer_types + (tubes.process.process, tubes.ssh.ssh_channel)):
log.error("Use gdb.attach() to debug a running process")
Expand All @@ -529,12 +614,21 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
if env:
env = {bytes(k): bytes(v) for k, v in env}

exe = which(packing._decode(exe or args[0]))
if not exe:
log.error("Could not find executable %r" % exe)

if context.noptrace:
log.warn_once("Skipping debugger since context.noptrace==True")
return runner(args, executable=exe, env=env)

if ssh or context.native or (context.os == 'android'):
args = _gdbserver_args(args=args, which=which, env=env)
# 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)
else:
qemu_port = random.randint(1024, 65535)
qemu_user = qemu.user_path()
Expand Down
1 change: 1 addition & 0 deletions pwnlib/tubes/ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,7 @@ def is_exe(path):
self.chmod('+x', tmpfile)

self.info("Uploading execve script to %r" % tmpfile)
script = packing._encode(script)
self.upload_data(script, tmpfile)
return tmpfile

Expand Down