@@ -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,12 +343,30 @@ 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 = [path or str (pid ).encode ("utf-8" )]
269
370
270
371
if not which :
271
372
log .error ("Must specify which." )
@@ -285,8 +386,6 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
285
386
if not gdbserver :
286
387
log .error ("gdbserver is not installed" )
287
388
288
- orig_args = args
289
-
290
389
gdbserver_args = [gdbserver , '--multi' ]
291
390
if context .aslr :
292
391
gdbserver_args += ['--no-disable-randomization' ]
@@ -296,7 +395,24 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
296
395
if pid :
297
396
gdbserver_args += ['--once' , '--attach' ]
298
397
299
- if env is not None :
398
+ # gdbserver does not support passing argv[0] to the executable
399
+ # To work around this, we use the --wrapper option to start a python
400
+ # script which calls execve() directly
401
+ # https://sourceware.org/pipermail/gdb/2013-May/043021.html
402
+ if context .native :
403
+ script = _generate_execve_script (path , args , env )
404
+ with tempfile .NamedTemporaryFile (mode = 'w' , delete = False , suffix = ".py" ) as tmp :
405
+ tmp .write (script )
406
+
407
+ log .debug ("Wrote execve wrapper script to %s" , tmp .name )
408
+ python = _find_python (which )
409
+ gdbserver_args += ["--wrapper" , python , tmp .name , "--" ]
410
+
411
+ # Gdbserver needs to start with args, therefore we add a dummy arg if none are specified
412
+ if args is None or len (args [0 ].strip ()) == 0 :
413
+ args = ['dummy' ]
414
+
415
+ elif env is not None :
300
416
env_args = []
301
417
for key in tuple (env ):
302
418
if key .startswith (b'LD_' ): # LD_PRELOAD / LD_LIBRARY_PATH etc.
@@ -363,7 +479,7 @@ def _get_runner(ssh=None):
363
479
else : return tubes .process .process
364
480
365
481
@LocalContext
366
- def debug (args , gdbscript = None , exe = None , ssh = None , env = None , sysroot = None , api = False , force_args = False , ** kwargs ):
482
+ def debug (args , gdbscript = None , exe = None , ssh = None , env = None , sysroot = None , api = False , ** kwargs ):
367
483
r"""
368
484
Launch a GDB server with the specified command line,
369
485
and launches GDB to attach to it.
@@ -435,6 +551,14 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
435
551
>>> io.interactive() # doctest: +SKIP
436
552
>>> io.close()
437
553
554
+ Create a new process with empty argv[0]
555
+
556
+ >>> io = gdb.debug([''], exe="/bin/bash")
557
+ >>> io.sendline(b"echo $0")
558
+ >>> io.recvline()
559
+ b'\n'
560
+ >>> io.close()
561
+
438
562
Create a new process, and stop it at '_start'
439
563
440
564
>>> io = gdb.debug('bash', '''
@@ -533,50 +657,8 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
533
657
log .warn_once ("Skipping debugger since context.noptrace==True" )
534
658
return runner (args , executable = exe , env = env )
535
659
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
660
elif ssh or context .native or (context .os == 'android' ):
579
- args = _gdbserver_args (args = args , which = which , env = env )
661
+ args = _gdbserver_args (args = args , path = exe , which = which , env = env )
580
662
else :
581
663
qemu_port = random .randint (1024 , 65535 )
582
664
qemu_user = qemu .user_path ()
0 commit comments