143143
144144from contextlib import contextmanager
145145import os
146+ import sys
146147import platform
147148import psutil
148149import 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