143
143
144
144
from contextlib import contextmanager
145
145
import os
146
+ import sys
146
147
import platform
147
148
import psutil
148
149
import random
@@ -249,7 +250,43 @@ def debug_shellcode(data, gdbscript=None, vma=None, api=False):
249
250
250
251
return debug (tmp_elf , gdbscript = gdbscript , arch = context .arch , api = api )
251
252
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 ):
253
290
"""_gdbserver_args(pid=None, path=None, args=None, which=None, env=None) -> list
254
291
255
292
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):
260
297
path(str): Process to launch
261
298
args(list): List of arguments to provide on the debugger command line
262
299
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``
263
302
264
303
Returns:
265
304
A list of arguments to invoke gdbserver.
@@ -296,13 +335,19 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
296
335
if pid :
297
336
gdbserver_args += ['--once' , '--attach' ]
298
337
338
+ env_args = []
299
339
if env is not None :
300
- env_args = []
301
340
for key in tuple (env ):
341
+ # Special case for LD_ environment variables, so gdbserver
342
+ # starts with the native libraries
302
343
if key .startswith (b'LD_' ): # LD_PRELOAD / LD_LIBRARY_PATH etc.
303
344
env_args .append (b'%s=%s' % (key , env .pop (key )))
304
345
else :
305
346
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 :
306
351
gdbserver_args += ['--wrapper' , which ('env' ), '-i' ] + env_args + ['--' ]
307
352
308
353
gdbserver_args += ['localhost:0' ]
@@ -465,6 +510,24 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
465
510
466
511
>>> io.interactive() # doctest: +SKIP
467
512
>>> 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
+
468
531
469
532
Using GDB Python API:
470
533
@@ -516,6 +579,20 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
516
579
Interact with the process
517
580
>>> io.interactive() # doctest: +SKIP
518
581
>>> 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()
519
596
"""
520
597
if isinstance (args , six .integer_types + (tubes .process .process , tubes .ssh .ssh_channel )):
521
598
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=
536
613
if env :
537
614
env = {bytes (k ): bytes (v ) for k , v in env }
538
615
616
+ exe = which (packing ._decode (exe or args [0 ]))
617
+ if not exe :
618
+ log .error ("Could not find executable %r" % exe )
619
+
539
620
if context .noptrace :
540
621
log .warn_once ("Skipping debugger since context.noptrace==True" )
541
622
return runner (args , executable = exe , env = env )
542
623
543
624
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 )
545
635
else :
546
636
qemu_port = random .randint (1024 , 65535 )
547
637
qemu_user = qemu .user_path ()
@@ -564,10 +654,6 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
564
654
if not which (args [0 ]):
565
655
log .error ("%s is not installed" % args [0 ])
566
656
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 )
571
657
572
658
# Start gdbserver/qemu
573
659
# (Note: We override ASLR here for the gdbserver process itself.)
@@ -1104,13 +1190,12 @@ def findexe():
1104
1190
gdbscript = pre + (gdbscript or '' )
1105
1191
1106
1192
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 )
1111
1197
1112
- tmp .write (gdbscript )
1113
- tmp .close ()
1198
+ tmp .write (gdbscript )
1114
1199
cmd += ['-x' , tmp .name ]
1115
1200
1116
1201
log .info ('running in new terminal: %s' , cmd )
0 commit comments