@@ -249,6 +249,89 @@ def debug_shellcode(data, gdbscript=None, vma=None, api=False):
249
249
250
250
return debug (tmp_elf , gdbscript = gdbscript , arch = context .arch , api = api )
251
251
252
+ def _find_python (which ):
253
+ """Finds the path to a Python interpreter."""
254
+ for py in ["python2.7" , "python2" , "python" , "python3" ]:
255
+ found = which (py )
256
+
257
+ if found is not None :
258
+ return found
259
+
260
+ return None
261
+
262
+ def _generate_execve_script (exe , args , env ):
263
+ """Generates a python script to execve() a binary.
264
+ This method uses `ctypes` to call `execve()` directly, since in
265
+ python3 os.execve() doesn't allow us to specify argv[0]=NULL or argv[0]=b''
266
+
267
+ This script might want to be called by `python -c`, to ensure it's safety,
268
+ we add typechecks to cast all user-controlled input into hexstrings.
269
+
270
+ Arguments:
271
+ exe(str): Path to the binary to execute
272
+ argv(list): List of arguments to pass to the binary
273
+ env(dict): Environment variables to pass to the binary
274
+
275
+ Returns:
276
+ The generated script as a string
277
+ """
278
+ if args is None :
279
+ args = []
280
+ if env is None :
281
+ env = {}
282
+
283
+ # Type checks
284
+ if type (exe ) not in [bytes , bytearray , str ]:
285
+ log .error ("exe must be a string or bytes" )
286
+
287
+ if isinstance (args , list ):
288
+ for arg in args :
289
+ if type (arg ) not in [bytes , bytearray , str ]:
290
+ log .error ("args must be a list of strings or bytes" )
291
+ else :
292
+ log .error ("args must be a list of strings or bytes" )
293
+
294
+ if isinstance (env , dict ):
295
+ for key , value in env .items ():
296
+ if type (key ) not in [bytes , bytearray , str ]:
297
+ log .error ("env keys must be strings or bytes" )
298
+ if type (value ) not in [bytes , bytearray , str ]:
299
+ log .error ("env values must be strings or bytes" )
300
+ else :
301
+ log .error ("env must be a dictionary of strings or bytes" )
302
+
303
+ # This script calls execve() directly using ctypes
304
+ script = """
305
+ import ctypes
306
+
307
+ exe = bytes.fromhex({executable!r})
308
+ argv = [bytes.fromhex(x) for x in {formatted_args!r}]
309
+ envp = [bytes.fromhex(x) for x in {formatted_env!r}]
310
+
311
+ def get_string_list(string_list):
312
+ #Transform a list of bytes into a ctypes array of char pointers
313
+ char_p_array = (ctypes.c_char_p * len(string_list))()
314
+ for i, string in enumerate(string_list):
315
+ char_p_array[i] = ctypes.c_char_p(string)
316
+
317
+ return char_p_array
318
+
319
+ c_exe = ctypes.c_char_p(exe)
320
+ c_argv = get_string_list(argv)
321
+ c_envp = get_string_list(envp)
322
+
323
+ # Call execve
324
+ libc = ctypes.CDLL(None)
325
+ libc.execve(c_exe, c_argv, c_envp)
326
+ """
327
+ script = script .format (executable = (packing ._encode (exe )).hex (),
328
+ formatted_args = [(packing ._encode (arg )).hex () for arg in args ],
329
+ formatted_env = [((packing ._encode (k )) + b'=' + bytes (packing ._encode (v ))).hex () for k , v in env .items ()])
330
+
331
+ # log.debug("Generated execve script:\n%s", script)
332
+
333
+ return script
334
+
252
335
def _gdbserver_args (pid = None , path = None , args = None , which = None , env = None ):
253
336
"""_gdbserver_args(pid=None, path=None, args=None, which=None, env=None) -> list
254
337
@@ -260,21 +343,36 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
260
343
path(str): Process to launch
261
344
args(list): List of arguments to provide on the debugger command line
262
345
which(callaable): Function to find the path of a binary.
346
+ env(dict): Dictionary containing the debugged process environment variables.
263
347
264
348
Returns:
265
349
A list of arguments to invoke gdbserver.
266
350
"""
267
- if [pid , path , args ].count (None ) != 2 :
268
- log .error ("Must specify exactly one of pid, path, or args" )
351
+ if [path , args , pid ].count (None ) == 3 :
352
+ log .error ("Must specify at least one of pid, path, or args" )
353
+
354
+ if pid is not None :
355
+ if [path , args ].count (None ) != 2 :
356
+ log .error ("Cannot specify both pid and path or args" )
357
+
358
+ elif path is None :
359
+ if args :
360
+ # Local which needs str not bytes
361
+ path = which (packing ._decode (args [0 ]))
362
+ else :
363
+ log .error ("Must specify at least one of pid, path, or args" )
364
+
365
+
366
+ path = packing ._need_bytes (path , min_wrong = 0x80 )
367
+
368
+ if args is None :
369
+ args = []
269
370
270
371
if not which :
271
372
log .error ("Must specify which." )
272
373
273
374
gdbserver = ''
274
375
275
- if not args :
276
- args = [str (path or pid )]
277
-
278
376
# Android targets have a distinct gdbserver
279
377
if context .bits == 64 :
280
378
gdbserver = which ('gdbserver64' )
@@ -285,8 +383,6 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
285
383
if not gdbserver :
286
384
log .error ("gdbserver is not installed" )
287
385
288
- orig_args = args
289
-
290
386
gdbserver_args = [gdbserver , '--multi' ]
291
387
if context .aslr :
292
388
gdbserver_args += ['--no-disable-randomization' ]
@@ -296,7 +392,24 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
296
392
if pid :
297
393
gdbserver_args += ['--once' , '--attach' ]
298
394
299
- if env is not None :
395
+ # gdbserver does not support passing argv[0] to the executable
396
+ # To work around this, we use the --wrapper option to start a python
397
+ # script which calls execve() directly
398
+ # https://sourceware.org/pipermail/gdb/2013-May/043021.html
399
+ if context .native :
400
+ script = _generate_execve_script (path , args , env )
401
+ with tempfile .NamedTemporaryFile (mode = 'w' , delete = False , suffix = ".py" ) as tmp :
402
+ tmp .write (script )
403
+
404
+ log .debug ("Wrote execve wrapper script to %s" , tmp .name )
405
+ python = _find_python (which )
406
+ gdbserver_args += ["--wrapper" , python , tmp .name , "--" ]
407
+
408
+ # Gdbserver needs to start with args, therefore we add a dummy arg if none are specified
409
+ if args is None or len (args ) == 0 or len (args [0 ].strip ()) == 0 :
410
+ args = ['dummy' ]
411
+
412
+ elif env is not None :
300
413
env_args = []
301
414
for key in tuple (env ):
302
415
if key .startswith (b'LD_' ): # LD_PRELOAD / LD_LIBRARY_PATH etc.
@@ -363,7 +476,7 @@ def _get_runner(ssh=None):
363
476
else : return tubes .process .process
364
477
365
478
@LocalContext
366
- def debug (args , gdbscript = None , exe = None , ssh = None , env = None , sysroot = None , api = False , force_args = False , ** kwargs ):
479
+ def debug (args , gdbscript = None , exe = None , ssh = None , env = None , sysroot = None , api = False , ** kwargs ):
367
480
r"""
368
481
Launch a GDB server with the specified command line,
369
482
and launches GDB to attach to it.
@@ -435,6 +548,14 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
435
548
>>> io.interactive() # doctest: +SKIP
436
549
>>> io.close()
437
550
551
+ Create a new process with empty argv[0]
552
+
553
+ >>> io = gdb.debug([''], exe="/bin/bash")
554
+ >>> io.sendline(b"echo $0")
555
+ >>> io.recvline()
556
+ b'\n'
557
+ >>> io.close()
558
+
438
559
Create a new process, and stop it at '_start'
439
560
440
561
>>> io = gdb.debug('bash', '''
@@ -533,50 +654,8 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
533
654
log .warn_once ("Skipping debugger since context.noptrace==True" )
534
655
return runner (args , executable = exe , env = env )
535
656
536
- if force_args :
537
- # gdbserver does not support passing argv[0] to the executable
538
- # To work around this, we use the --wrapper option
539
- # https://sourceware.org/pipermail/gdb/2013-May/043021.html
540
-
541
- # Here we create a wrapper that calls execve with the correct argv[0]
542
- src = """
543
- #include <unistd.h>
544
- int main(){
545
- char** argv = {0};
546
- execv("EXE", argv, envp);
547
- }
548
- """
549
- exe = exe .encode ()
550
- src = src .replace ("EXE" , str (exe )[2 :- 1 ])
551
- print (src )
552
- # And a execve binary
553
- outfile = tempfile .NamedTemporaryFile ()
554
- outfile .close ()
555
- with tempfile .NamedTemporaryFile (suffix = ".c" ) as srcfile :
556
- print (srcfile .name , outfile .name )
557
- srcfile .write (src .encode ())
558
- srcfile .flush ()
559
- os .system ("gcc -o {} {}" .format (outfile .name , srcfile .name ))
560
- os .system ("echo WIN" )
561
-
562
-
563
- # # Transform env to list if env:
564
- # env_list = [k + b'=' + v for k, v in env.items()]
565
- # else:
566
- # env_list = 0
567
-
568
- # # Transform args to list
569
- # args = [bytes(a) for a in args]
570
-
571
-
572
- # Create gdbserver args
573
- args = ['gdbserver' , '--no-disable-randomization' , '--wrapper' , outfile .name , '--' ]
574
- args += ["localhost:0" , "dummy" ]
575
-
576
-
577
-
578
- elif ssh or context .native or (context .os == 'android' ):
579
- args = _gdbserver_args (args = args , which = which , env = env )
657
+ if ssh or context .native or (context .os == 'android' ):
658
+ args = _gdbserver_args (args = args , path = exe , which = which , env = env )
580
659
else :
581
660
qemu_port = random .randint (1024 , 65535 )
582
661
qemu_user = qemu .user_path ()
0 commit comments