Skip to content

Commit 00663aa

Browse files
gfelberpeace-maker
andauthored
Add port, gdb_args, and gdbserver_args to gdb.debug() (#2382)
* added two arguments to gdb.debug: + port: specifies the port that should be used by the gdbserver (this is useful for alpine) + gdb_args: allows forwarding arguments to the gdb binary spawned by gdb.attach() (just a passthrough) aslo downgraded "GDB Python API is supported only for local processes" to a warning, because it seems to work on some systems. * added changelog entry and fixed python2.7 compatibility * also added optional gdbserver_args parameter to gdb.debug() * implemented requested fixes for #2382: + gdb.debug port is now also used for qemu + GDB Python API is now tested for tubes.process.process a warning for ssh.process and an error for everything else + updated docs to use mention that gdbserver ports are randomized by default + now using gdbserver_port to check if the correct port was set + fixed CHANGELOG.md structure * implemented requested fixes for #2382: + gdb.debug port is now also used for qemu + GDB Python API is now tested for tubes.process.process a warning for ssh.process and an error for everything else + updated docs to use mention that gdbserver ports are randomized by default + now using gdbserver_port to check if the correct port was set + fixed CHANGELOG.md structure + added timeouts for recvline() gdb tests + also run gdb api tests for ssh runner --------- Co-authored-by: gfelber <@users.noreply.github.com> Co-authored-by: peace-maker <[email protected]>
1 parent 18c8bfb commit 00663aa

File tree

2 files changed

+86
-55
lines changed

2 files changed

+86
-55
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ The table below shows which release corresponds to each branch, and what date th
8787
- [#2415][2415] Add shellcraft template for IPv6 socket
8888
- [#2405][2405] Add "none" ssh authentication method
8989
- [#2427][2427] Document behaviour of remote()'s sni argument as string.
90+
- [#2382][2382] added optional port, gdb_args and gdbserver_args parameters to gdb.debug()
9091

9192
[2360]: https://github.com/Gallopsled/pwntools/pull/2360
9293
[2356]: https://github.com/Gallopsled/pwntools/pull/2356
@@ -103,6 +104,7 @@ The table below shows which release corresponds to each branch, and what date th
103104
[2415]: https://github.com/Gallopsled/pwntools/pull/2415
104105
[2405]: https://github.com/Gallopsled/pwntools/pull/2405
105106
[2427]: https://github.com/Gallopsled/pwntools/pull/2405
107+
[2382]: https://github.com/Gallopsled/pwntools/pull/2382
106108

107109
## 4.13.0 (`beta`)
108110

pwnlib/gdb.py

+84-55
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ def debug_assembly(asm, gdbscript=None, vma=None, api=False):
195195
196196
>>> assembly = shellcraft.echo("Hello world!\n")
197197
>>> io = gdb.debug_assembly(assembly)
198-
>>> io.recvline()
198+
>>> io.recvline(timeout=1)
199199
b'Hello world!\n'
200200
"""
201201
tmp_elf = make_elf_from_assembly(asm, vma=vma, extract=False)
@@ -230,7 +230,7 @@ def debug_shellcode(data, gdbscript=None, vma=None, api=False):
230230
>>> assembly = shellcraft.echo("Hello world!\n")
231231
>>> shellcode = asm(assembly)
232232
>>> io = gdb.debug_shellcode(shellcode)
233-
>>> io.recvline()
233+
>>> io.recvline(timeout=1)
234234
b'Hello world!\n'
235235
"""
236236
if isinstance(data, six.text_type):
@@ -283,7 +283,7 @@ def _execve_script(argv, executable, env, ssh):
283283
return tmp.name
284284

285285

286-
def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None, python_wrapper_script=None):
286+
def _gdbserver_args(pid=None, path=None, port=0, gdbserver_args=None, args=None, which=None, env=None, python_wrapper_script=None):
287287
"""_gdbserver_args(pid=None, path=None, args=None, which=None, env=None) -> list
288288
289289
Sets up a listening gdbserver, to either connect to the specified
@@ -292,6 +292,8 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None, python
292292
Arguments:
293293
pid(int): Process ID to attach to
294294
path(str): Process to launch
295+
port(int): Port to use for gdbserver, default: random
296+
gdbserver_args(list): List of additional arguments to pass to gdbserver
295297
args(list): List of arguments to provide on the debugger command line
296298
which(callaable): Function to find the path of a binary.
297299
env(dict): Environment variables to pass to the program
@@ -300,6 +302,11 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None, python
300302
Returns:
301303
A list of arguments to invoke gdbserver.
302304
"""
305+
if gdbserver_args is None:
306+
gdbserver_args = list()
307+
elif not isinstance(gdbserver_args, (list, tuple)):
308+
gdbserver_args = [gdbserver_args]
309+
303310
if [pid, path, args].count(None) != 2:
304311
log.error("Must specify exactly one of pid, path, or args")
305312

@@ -323,7 +330,7 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None, python
323330

324331
orig_args = args
325332

326-
gdbserver_args = [gdbserver, '--multi']
333+
gdbserver_args = [gdbserver, '--multi'] + gdbserver_args
327334
if context.aslr:
328335
gdbserver_args += ['--no-disable-randomization']
329336
else:
@@ -351,7 +358,7 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None, python
351358
else:
352359
gdbserver_args += ['--no-startup-with-shell']
353360

354-
gdbserver_args += ['localhost:0']
361+
gdbserver_args += ['localhost:%d' % port]
355362
gdbserver_args += args
356363

357364
return gdbserver_args
@@ -416,17 +423,20 @@ def _get_runner(ssh=None):
416423
else: return tubes.process.process
417424

418425
@LocalContext
419-
def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=False, **kwargs):
426+
def debug(args, gdbscript=None, gdb_args=None, exe=None, ssh=None, env=None, port=0, gdbserver_args=None, sysroot=None, api=False, **kwargs):
420427
r"""
421428
Launch a GDB server with the specified command line,
422429
and launches GDB to attach to it.
423430
424431
Arguments:
425432
args(list): Arguments to the process, similar to :class:`.process`.
426433
gdbscript(str): GDB script to run.
434+
gdb_args(list): List of additional arguments to pass to GDB.
427435
exe(str): Path to the executable on disk
428436
env(dict): Environment to start the binary in
429437
ssh(:class:`.ssh`): Remote ssh session to use to launch the process.
438+
port(int): Gdb port to use, default: random
439+
gdbserver_args(list): List of additional arguments to pass to gdbserver
430440
sysroot(str): Set an alternate system root. The system root is used to
431441
load absolute shared library symbol files. This is useful to instruct
432442
gdb to load a local version of binaries/libraries instead of downloading
@@ -480,12 +490,12 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
480490
Send a command to Bash
481491
482492
>>> io.sendline(b"echo hello")
483-
>>> io.recvline()
493+
>>> io.recvline(timeout=30)
484494
b'hello\n'
485495
486496
Interact with the process
487497
488-
>>> io.interactive() # doctest: +SKIP
498+
>>> io.interactive(timeout=1) # doctest: +SKIP
489499
>>> io.close()
490500
491501
Create a new process, and stop it at '_start'
@@ -504,7 +514,7 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
504514
Send a command to Bash
505515
506516
>>> io.sendline(b"echo hello")
507-
>>> io.recvline()
517+
>>> io.recvline(timeout=10)
508518
b'hello\n'
509519
510520
Interact with the process
@@ -516,53 +526,24 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
516526
517527
>>> io = gdb.debug(args=[b'\xde\xad\xbe\xef'], gdbscript='continue', exe="/bin/sh")
518528
>>> io.sendline(b"echo $0")
519-
>>> io.recvline()
529+
>>> io.recvline(timeout=10)
520530
b'\xde\xad\xbe\xef\n'
521531
>>> io.close()
522532
523533
Demonstrate that LD_PRELOAD is respected
524534
525535
>>> io = process(["grep", "libc.so.6", "/proc/self/maps"])
526-
>>> real_libc_path = io.recvline().split()[-1]
536+
>>> real_libc_path = io.recvline(timeout=1).split()[-1]
527537
>>> io.close()
528538
>>> import shutil
529539
>>> local_path = shutil.copy(real_libc_path, "./local-libc.so") # make a copy of libc to demonstrate that it is loaded
530540
>>> io = gdb.debug(["grep", "local-libc.so", "/proc/self/maps"], gdbscript="continue", env={"LD_PRELOAD": "./local-libc.so"})
531-
>>> io.recvline().split()[-1] # doctest: +ELLIPSIS
541+
>>> io.recvline(timeout=1).split()[-1] # doctest: +ELLIPSIS
532542
b'.../local-libc.so'
533543
>>> io.close()
534544
>>> os.remove("./local-libc.so") # cleanup
535545
536546
537-
Using GDB Python API:
538-
539-
.. doctest::
540-
:skipif: is_python2
541-
542-
Debug a new process
543-
544-
>>> io = gdb.debug(['echo', 'foo'], api=True)
545-
546-
Stop at 'write'
547-
548-
>>> bp = io.gdb.Breakpoint('write', temporary=True)
549-
>>> io.gdb.continue_and_wait()
550-
551-
Dump 'count'
552-
553-
>>> count = io.gdb.parse_and_eval('$rdx')
554-
>>> long = io.gdb.lookup_type('long')
555-
>>> int(count.cast(long))
556-
4
557-
558-
Resume the program
559-
560-
>>> io.gdb.continue_nowait()
561-
>>> io.recvline()
562-
b'foo\n'
563-
>>> io.close()
564-
565-
566547
Using SSH:
567548
568549
You can use :func:`debug` to spawn new processes on remote machines as well,
@@ -591,17 +572,63 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
591572
592573
>>> io = gdb.debug(args=[b'\xde\xad\xbe\xef'], gdbscript='continue', exe="/bin/sh", ssh=shell)
593574
>>> io.sendline(b"echo $0")
594-
>>> io.recvline()
575+
>>> io.recvline(timeout=10)
595576
b'$ \xde\xad\xbe\xef\n'
596577
>>> io.close()
597578
598579
Using an empty args[0] on a remote process
599580
600581
>>> io = gdb.debug(args=[], gdbscript='continue', exe="/bin/sh", ssh=shell)
601582
>>> io.sendline(b"echo $0")
602-
>>> io.recvline()
583+
>>> io.recvline(timeout=10)
603584
b'$ \n'
604585
>>> io.close()
586+
587+
588+
Using GDB Python API:
589+
590+
.. doctest::
591+
:skipif: is_python2
592+
593+
Debug a new process
594+
595+
>>> io = gdb.debug(['echo', 'foo'], api=True)
596+
597+
or using ssh
598+
599+
>>> shell = ssh('travis', 'example.pwnme', password='demopass')
600+
>>> ssh_io = gdb.debug(['/bin/echo', 'foo'], ssh=shell, api=True)
601+
602+
Stop at 'write'
603+
604+
>>> bp = io.gdb.Breakpoint('write', temporary=True)
605+
>>> io.gdb.continue_and_wait()
606+
>>> ssh_bp = ssh_io.gdb.Breakpoint('write', temporary=True)
607+
>>> ssh_io.gdb.continue_and_wait()
608+
609+
Dump 'count'
610+
611+
>>> count = io.gdb.parse_and_eval('$rdx')
612+
>>> long = io.gdb.lookup_type('long')
613+
>>> int(count.cast(long))
614+
4
615+
>>> count = ssh_io.gdb.parse_and_eval('$rdx')
616+
>>> long = ssh_io.gdb.lookup_type('long')
617+
>>> int(count.cast(long))
618+
4
619+
620+
Resume the program
621+
622+
>>> io.gdb.continue_nowait()
623+
>>> io.recvline(timeout=1)
624+
b'foo\n'
625+
>>> io.close()
626+
627+
>>> ssh_io.gdb.continue_nowait()
628+
>>> ssh_io.recvline(timeout=1)
629+
b'foo\n'
630+
>>> ssh_io.close()
631+
>>> shell.close()
605632
"""
606633
if isinstance(args, six.integer_types + (tubes.process.process, tubes.ssh.ssh_channel)):
607634
log.error("Use gdb.attach() to debug a running process")
@@ -615,8 +642,8 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
615642
which = _get_which(ssh)
616643
gdbscript = gdbscript or ''
617644

618-
if api and runner is not tubes.process.process:
619-
raise ValueError('GDB Python API is supported only for local processes')
645+
if api and runner is not tubes.process.process and not ssh:
646+
raise ValueError('GDB Python API is supported only for local and ssh processes')
620647

621648
args, env = misc.normalize_argv_env(args, env, log)
622649
if env:
@@ -632,17 +659,17 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
632659

633660
if ssh or context.native or (context.os == 'android'):
634661
if len(args) > 0 and which(packing._decode(args[0])) == packing._decode(exe):
635-
args = _gdbserver_args(args=args, which=which, env=env)
662+
args = _gdbserver_args(gdbserver_args=gdbserver_args, args=args, port=port, which=which, env=env)
636663

637664
else:
638665
# GDBServer is limited in it's ability to manipulate argv[0]
639666
# but can use the ``--wrapper`` option to execute commands and catches
640667
# ``execve`` calls.
641668
# Therefore, we use a wrapper script to execute the target binary
642669
script = _execve_script(args, executable=exe, env=env, ssh=ssh)
643-
args = _gdbserver_args(args=args, which=which, env=env, python_wrapper_script=script)
670+
args = _gdbserver_args(gdbserver_args=gdbserver_args, args=args, port=port, which=which, env=env, python_wrapper_script=script)
644671
else:
645-
qemu_port = random.randint(1024, 65535)
672+
qemu_port = port if port != 0 else random.randint(1024, 65535)
646673
qemu_user = qemu.user_path()
647674
sysroot = sysroot or qemu.ld_prefix(env=env)
648675
if not qemu_user:
@@ -671,17 +698,19 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
671698
# Set the .executable on the process object.
672699
gdbserver.executable = exe
673700

674-
# Find what port we need to connect to
675701
if ssh or context.native or (context.os == 'android'):
676-
port = _gdbserver_port(gdbserver, ssh)
702+
gdb_port = _gdbserver_port(gdbserver, ssh)
703+
if port != 0 and port != gdb_port:
704+
log.error("gdbserver port (%d) doesn't equals set port (%d)" % (gdb_port, port))
705+
port = gdb_port
677706
else:
678707
port = qemu_port
679708

680709
host = '127.0.0.1'
681710
if not ssh and context.os == 'android':
682711
host = context.adb_host
683712

684-
tmp = attach((host, port), exe=exe, gdbscript=gdbscript, ssh=ssh, sysroot=sysroot, api=api)
713+
tmp = attach((host, port), exe=exe, gdbscript=gdbscript, gdb_args=gdb_args, ssh=ssh, sysroot=sysroot, api=api)
685714
if api:
686715
_, gdb = tmp
687716
gdbserver.gdb = gdb
@@ -948,7 +977,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
948977
... detach
949978
... quit
950979
... ''')
951-
>>> io.recvline()
980+
>>> io.recvline(timeout=10)
952981
b'Hello from process debugger!\n'
953982
>>> io.sendline(b'echo Hello from bash && exit')
954983
>>> io.recvall()
@@ -975,7 +1004,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
9751004
9761005
Observe the forced line
9771006
978-
>>> io.recvline()
1007+
>>> io.recvline(timeout=1)
9791008
b'Hello from process debugger!\n'
9801009
9811010
Interact with the program in a regular way
@@ -999,7 +1028,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
9991028
... detach
10001029
... quit
10011030
... ''')
1002-
>>> io.recvline()
1031+
>>> io.recvline(timeout=10)
10031032
b'Hello from remote debugger!\n'
10041033
>>> io.sendline(b'echo Hello from bash && exit')
10051034
>>> io.recvall()
@@ -1018,7 +1047,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
10181047
>>> io.recvline(timeout=5) # doctest: +SKIP
10191048
b'Hello from ssh debugger!\n'
10201049
>>> io.sendline(b'This will be echoed back')
1021-
>>> io.recvline()
1050+
>>> io.recvline(timeout=1)
10221051
b'This will be echoed back\n'
10231052
>>> io.close()
10241053
"""

0 commit comments

Comments
 (0)