Skip to content

Commit d9b3e17

Browse files
goreilpeace-maker
andauthored
gdb.debug: exe parameter now respected, allow empty argv (#2233)
* Bugfix gdb.debug: exe parameter now respected This commit now properly supports the exe parameter in `pwnlib/gdb.py:debug()`, allowing a different argv[0] than the executable. It achieves this by leveraging the gdbsever`--wrapper` argument with a python script that calls execve with the specified args. * Backintegration for python2 * Update Changelog.md * pwnlib gdb.py, try to pass these tests... * Encode script in ssh.process(run=False) for tests * Re-trigger Pull request tests * gdb.py: easier script if argv[0] == exe * gdb.py: Add test case for LD_Preload * Add check that "=" not in misc.normalize_argv_env This check checks prevents the use of "=" in the key of an environment variable, which is generally impossible. * gdb.py Correct handling of LD_ env-variables * Update pwnlib/gdb.py Co-authored-by: peace-maker <[email protected]> * gdb.py address comments 1. explicit ctypes.CDLL('libc.so.6'), handle execve failing 2. consistent namedTempFile 3. drop packing._encode() since it's done earlier 4. testcases solve argv-args confusion * gdb.py: Fix Namedtempfile-prefix + Bugfix * Restore prefix for gdbscript * Unify execve wrapper script under one function * gdb.py, Remove leftover script * Fix logging scope and ignore_environ argument --------- Co-authored-by: Youheng Lü <[email protected]> Co-authored-by: peace-maker <[email protected]> Co-authored-by: peace-maker <[email protected]>
1 parent c049837 commit d9b3e17

File tree

4 files changed

+344
-200
lines changed

4 files changed

+344
-200
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ The table below shows which release corresponds to each branch, and what date th
9797
- [#2341][2341] Launch GDB correctly in iTerm on Mac
9898
- [#2268][2268] Add a `flatten` argument to `ssh.libs`
9999
- [#2347][2347] Fix/workaround Unicorn Engine 1GB limit that calls exit()
100+
- [#2233][2233] Fix gdb.debug: exe parameter now respected, allow empty argv
100101

101102
[2242]: https://github.com/Gallopsled/pwntools/pull/2242
102103
[2277]: https://github.com/Gallopsled/pwntools/pull/2277
@@ -122,6 +123,7 @@ The table below shows which release corresponds to each branch, and what date th
122123
[2341]: https://github.com/Gallopsled/pwntools/pull/2341
123124
[2268]: https://github.com/Gallopsled/pwntools/pull/2268
124125
[2347]: https://github.com/Gallopsled/pwntools/pull/2347
126+
[2233]: https://github.com/Gallopsled/pwntools/pull/2233
125127

126128
## 4.12.0 (`beta`)
127129

pwnlib/gdb.py

+98-13
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@
143143

144144
from contextlib import contextmanager
145145
import os
146+
import sys
146147
import platform
147148
import psutil
148149
import random
@@ -249,7 +250,43 @@ def debug_shellcode(data, gdbscript=None, vma=None, api=False):
249250

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

252-
def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
253+
def _execve_script(argv, executable, env, ssh):
254+
"""_execve_script(argv, executable, env, ssh) -> str
255+
256+
Returns the filename of a python script that calls
257+
execve the specified program with the specified arguments.
258+
This script is suitable to call with gdbservers ``--wrapper`` option,
259+
so we have more control over the environment of the debugged process.
260+
261+
Arguments:
262+
argv(list): List of arguments to pass to the program
263+
executable(bytes): Path to the program to run
264+
env(dict): Environment variables to pass to the program
265+
ssh(ssh): SSH connection to use if we are debugging a remote process
266+
267+
Returns:
268+
The filename of the created script.
269+
"""
270+
# Make sure args are bytes not bytearray.
271+
argv = [bytes(arg) for arg in argv]
272+
executable = packing._encode(executable)
273+
if ssh:
274+
# ssh.process with run=false creates the script for us
275+
return ssh.process(argv, executable=executable, env=env, run=False)
276+
277+
script = misc._create_execve_script(argv=argv, executable=executable, env=env, log=log)
278+
script = script.strip()
279+
# Create a temporary file to hold the script
280+
tmp = tempfile.NamedTemporaryFile(mode="w+t",prefix='pwnlib-execve-', suffix='.py', delete=False)
281+
tmp.write(script)
282+
# Make script executable
283+
os.fchmod(tmp.fileno(), 0o755)
284+
log.debug("Created execve wrapper script %s:\n%s", tmp.name, script)
285+
286+
return tmp.name
287+
288+
289+
def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None, python_wrapper_script=None):
253290
"""_gdbserver_args(pid=None, path=None, args=None, which=None, env=None) -> list
254291
255292
Sets up a listening gdbserver, to either connect to the specified
@@ -260,6 +297,8 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
260297
path(str): Process to launch
261298
args(list): List of arguments to provide on the debugger command line
262299
which(callaable): Function to find the path of a binary.
300+
env(dict): Environment variables to pass to the program
301+
python_wrapper_script(str): Path to a python script to use with ``--wrapper``
263302
264303
Returns:
265304
A list of arguments to invoke gdbserver.
@@ -296,13 +335,19 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
296335
if pid:
297336
gdbserver_args += ['--once', '--attach']
298337

338+
env_args = []
299339
if env is not None:
300-
env_args = []
301340
for key in tuple(env):
341+
# Special case for LD_ environment variables, so gdbserver
342+
# starts with the native libraries
302343
if key.startswith(b'LD_'): # LD_PRELOAD / LD_LIBRARY_PATH etc.
303344
env_args.append(b'%s=%s' % (key, env.pop(key)))
304345
else:
305346
env_args.append(b'%s=%s' % (key, env[key]))
347+
348+
if python_wrapper_script is not None:
349+
gdbserver_args += ['--wrapper', python_wrapper_script, '--']
350+
elif env is not None:
306351
gdbserver_args += ['--wrapper', which('env'), '-i'] + env_args + ['--']
307352

308353
gdbserver_args += ['localhost:0']
@@ -465,6 +510,24 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
465510
466511
>>> io.interactive() # doctest: +SKIP
467512
>>> io.close()
513+
514+
Start a new process with modified argv[0]
515+
>>> io = gdb.debug(args=[b'\xde\xad\xbe\xef'], executable="/bin/sh")
516+
>>> io.sendline(b"echo $0")
517+
>>> io.recvline()
518+
b'$ \xde\xad\xbe\xef\n'
519+
>>> io.close()
520+
521+
Demonstrate that LD_PRELOAD is respected
522+
>>> io = process(["grep", "libc.so.6", "/proc/self/maps"])
523+
>>> real_libc_path = io.recvline().split()[-1]
524+
>>> import shutil
525+
>>> shutil.copy(real_libc_path, "./libc.so.6") # make a copy of libc to demonstrate that it is loaded
526+
>>> io = gdb.debug(["grep", "libc.so.6", "/proc/self/maps"], env={"LD_PRELOAD": "./libc.so.6"})
527+
>>> io.recvline().split()[-1]
528+
b"./libc.so.6"
529+
>>> os.remove("./libc.so.6") # cleanup
530+
468531
469532
Using GDB Python API:
470533
@@ -516,6 +579,20 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
516579
Interact with the process
517580
>>> io.interactive() # doctest: +SKIP
518581
>>> io.close()
582+
583+
Using a modified args[0] on a remote process
584+
>>> io = gdb.debug(args=[b'\xde\xad\xbe\xef'], gdbscript='continue', exe="/bin/sh", ssh=shell)
585+
>>> io.sendline(b"echo $0")
586+
>>> io.recvline()
587+
b'$ \xde\xad\xbe\xef\n'
588+
>>> io.close()
589+
590+
Using an empty args[0] on a remote process
591+
>>> io = gdb.debug(args=[], gdbscript='continue', exe="/bin/sh", ssh=shell)
592+
>>> io.sendline(b"echo $0")
593+
>>> io.recvline()
594+
b'$ \n'
595+
>>> io.close()
519596
"""
520597
if isinstance(args, six.integer_types + (tubes.process.process, tubes.ssh.ssh_channel)):
521598
log.error("Use gdb.attach() to debug a running process")
@@ -536,12 +613,25 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
536613
if env:
537614
env = {bytes(k): bytes(v) for k, v in env}
538615

616+
exe = which(packing._decode(exe or args[0]))
617+
if not exe:
618+
log.error("Could not find executable %r" % exe)
619+
539620
if context.noptrace:
540621
log.warn_once("Skipping debugger since context.noptrace==True")
541622
return runner(args, executable=exe, env=env)
542623

543624
if ssh or context.native or (context.os == 'android'):
544-
args = _gdbserver_args(args=args, which=which, env=env)
625+
if len(args) > 0 and which(packing._decode(args[0])) == packing._decode(exe):
626+
args = _gdbserver_args(args=args, which=which, env=env)
627+
628+
else:
629+
# GDBServer is limited in it's ability to manipulate argv[0]
630+
# but can use the ``--wrapper`` option to execute commands and catches
631+
# ``execve`` calls.
632+
# Therefore, we use a wrapper script to execute the target binary
633+
script = _execve_script(args, executable=exe, env=env, ssh=ssh)
634+
args = _gdbserver_args(args=args, which=which, env=env, python_wrapper_script=script)
545635
else:
546636
qemu_port = random.randint(1024, 65535)
547637
qemu_user = qemu.user_path()
@@ -564,10 +654,6 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
564654
if not which(args[0]):
565655
log.error("%s is not installed" % args[0])
566656

567-
if not ssh:
568-
exe = exe or which(orig_args[0])
569-
if not (exe and os.path.exists(exe)):
570-
log.error("%s does not exist" % exe)
571657

572658
# Start gdbserver/qemu
573659
# (Note: We override ASLR here for the gdbserver process itself.)
@@ -1104,13 +1190,12 @@ def findexe():
11041190
gdbscript = pre + (gdbscript or '')
11051191

11061192
if gdbscript:
1107-
tmp = tempfile.NamedTemporaryFile(prefix = 'pwn', suffix = '.gdb',
1108-
delete = False, mode = 'w+')
1109-
log.debug('Wrote gdb script to %r\n%s', tmp.name, gdbscript)
1110-
gdbscript = 'shell rm %s\n%s' % (tmp.name, gdbscript)
1193+
with tempfile.NamedTemporaryFile(prefix = 'pwnlib-gdbscript-', suffix = '.gdb',
1194+
delete = False, mode = 'w+') as tmp:
1195+
log.debug('Wrote gdb script to %r\n%s', tmp.name, gdbscript)
1196+
gdbscript = 'shell rm %s\n%s' % (tmp.name, gdbscript)
11111197

1112-
tmp.write(gdbscript)
1113-
tmp.close()
1198+
tmp.write(gdbscript)
11141199
cmd += ['-x', tmp.name]
11151200

11161201
log.info('running in new terminal: %s', cmd)

0 commit comments

Comments
 (0)