Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issues related with debugging #1323

Merged
merged 10 commits into from
Dec 29, 2019
2 changes: 1 addition & 1 deletion examples/attach.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from pwn import *

bash = process('/bin/bash')
gdb.attach(bash, execute = '''
gdb.attach(bash, gdbscript = '''
p "hello from pwnlib"
c
''')
Expand Down
2 changes: 1 addition & 1 deletion examples/remote_gdb_debugging.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from pwn import *

s = ssh(getpass.getuser(), '127.0.0.1', port = 22, keyfile = "~/.ssh/id_rsa")
c = gdb.ssh_gdb(s, '/bin/sh', execute = '''
c = gdb.ssh_gdb(s, '/bin/sh', gdbscript = '''
p/x $pc
c''')

Expand Down
23 changes: 10 additions & 13 deletions pwnlib/gdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,9 +404,6 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, **kw
if isinstance(args, (int, tubes.process.process, tubes.ssh.ssh_channel)):
log.error("Use gdb.attach() to debug a running process")

if env is None:
env = os.environ

if isinstance(args, (bytes, six.text_type)):
args = [args]

Expand Down Expand Up @@ -438,11 +435,11 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, **kw
if not which(args[0]):
log.error("%s is not installed" % args[0])

exe = exe or which(orig_args[0])
if not exe:
log.error("%s does not exist" % orig_args[0])
else:
gdbscript = 'file "%s"\n%s' % (exe, gdbscript)
if not ssh:
exe = exe or which(orig_args[0])
if not (exe and os.path.exists(exe)):
log.error("%s does not exist" % exe)


# Start gdbserver/qemu
# (Note: We override ASLR here for the gdbserver process itself.)
Expand Down Expand Up @@ -660,9 +657,9 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a

shell = target.parent

tmpfile = shell.mktemp()
tmpfile = shell.mktemp().decode()
gdbscript = 'shell rm %s\n%s' % (tmpfile, gdbscript)
shell.upload_data(context._encode(gdbscript), tmpfile)
shell.upload_data(gdbscript or '', tmpfile)

cmd = ['ssh', '-C', '-t', '-p', str(shell.port), '-l', shell.user, shell.host]
if shell.password:
Expand All @@ -671,7 +668,7 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
cmd = ['sshpass', '-p', shell.password] + cmd
if shell.keyfile:
cmd += ['-i', shell.keyfile]
cmd += ['gdb -q %r %s -x "%s"' % (target.executable,
cmd += ['gdb -q %s %s -x "%s"' % (target.executable.decode(),
target.pid,
tmpfile)]

Expand Down Expand Up @@ -721,7 +718,7 @@ def findexe():
exe_fn = adb.proc_exe
exe = exe_fn(pid)

if not pid and not exe:
if not pid and not exe and not ssh:
log.error('could not find target process')

if exe:
Expand Down Expand Up @@ -901,7 +898,7 @@ def find_module_addresses(binary, ssh=None, ulimit=False):
""" % entry)
gdb.clean(2)
gdb.sendline('info sharedlibrary')
lines = gdb.recvrepeat(2)
lines = context._decode(gdb.recvrepeat(2))

for line in lines.splitlines():
m = expr.match(line)
Expand Down
21 changes: 11 additions & 10 deletions pwnlib/tubes/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,6 @@ def __init__(self, argv = None,
if not shell:
executable, argv, env = self._validate(cwd, executable, argv, env)

# Permit invocation as process('sh') and process(['sh'])
if isinstance(argv, (bytes, six.text_type)):
argv = [argv]

# Avoid the need to have to deal with the STDOUT magic value.
if stderr is STDOUT:
stderr = stdout
Expand Down Expand Up @@ -308,13 +304,15 @@ def __init__(self, argv = None,
# and binfmt is not installed (e.g. when running on
# Travis CI), re-try with qemu-XXX if we get an
# 'Exec format error'.
prefixes = [([], executable)]
executables = [executable]
prefixes = [([], self.executable)]
exception = None

for prefix, executable in prefixes:
try:
self.proc = subprocess.Popen(args = prefix + argv,
args = argv
if (prefix):
args = prefix + args
self.proc = subprocess.Popen(args = args,
shell = shell,
executable = executable,
cwd = cwd,
Expand Down Expand Up @@ -509,11 +507,14 @@ def _validate(self, cwd, executable, argv, env):
# - Must be a list/tuple of strings
# - Each string must not contain '\x00'
#
if isinstance(argv, (bytes, six.text_type)):
if isinstance(argv, (six.text_type, six.binary_type)):
argv = [argv]

if not all(isinstance(arg, (bytes, six.text_type)) for arg in argv):
self.error("argv must be strings: %r" % argv)
if not isinstance(argv, (list, tuple)):
self.error('argv must be a list or tuple: %r' % argv)

if not all(isinstance(arg, (six.text_type, six.binary_type)) for arg in argv):
self.error("argv must be strings or bytes: %r" % argv)

# Create a duplicate so we can modify it
argv = list(argv or [])
Expand Down
18 changes: 10 additions & 8 deletions pwnlib/tubes/ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from pwnlib.log import getLogger
from pwnlib.term import text
from pwnlib.timeout import Timeout
from pwnlib.tubes.process import process
from pwnlib.tubes.sock import sock
from pwnlib.util import hashes
from pwnlib.util import misc
Expand Down Expand Up @@ -774,7 +773,7 @@ def process(self, argv=None, executable=None, tty=True, cwd=None, env=None, time
>>> s.process(['LOLOLOL\x00', '/proc/self/cmdline'], executable='cat').recvall()
b'LOLOLOL\x00/proc/self/cmdline\x00'
>>> sh = s.process(executable='/bin/sh')
>>> sh.pid in pidof('sh') # doctest: +SKIP
>>> str(sh.pid).encode() in s.pidof('sh') # doctest: +SKIP
True
>>> s.process(['pwd'], cwd='/tmp').recvall()
b'/tmp\n'
Expand Down Expand Up @@ -823,11 +822,17 @@ def process(self, argv=None, executable=None, tty=True, cwd=None, env=None, time
if not isinstance(argv, (list, tuple)):
self.error('argv must be a list or tuple')

if not all(isinstance(arg, (six.text_type, six.binary_type)) for arg in argv):
self.error("argv must be strings or bytes: %r" % argv)

if shell:
if len(argv) != 1:
self.error('Cannot provide more than 1 argument if shell=True')
argv = ['/bin/sh', '-c'] + argv

# Create a duplicate so we can modify it
argv = list(argv or [])

# Python doesn't like when an arg in argv contains '\x00'
# -> execve() arg 2 must contain only strings
for i, oarg in enumerate(argv):
Expand Down Expand Up @@ -860,12 +865,8 @@ def process(self, argv=None, executable=None, tty=True, cwd=None, env=None, time
# Validate, since failures on the remote side will suck.
if not isinstance(executable, (six.text_type, six.binary_type)):
self.error("executable / argv[0] must be a string: %r" % executable)
if not isinstance(argv, (list, tuple)):
self.error("argv must be a list or tuple: %r" % argv)
if env is not None and not isinstance(env, dict) and env != os.environ:
self.error("env must be a dict: %r" % env)
if not all(isinstance(s, (six.text_type, six.binary_type)) for s in argv):
self.error("argv must only contain strings: %r" % argv)

# Allow passing in sys.stdin/stdout/stderr objects
handles = {sys.stdin: 0, sys.stdout:1, sys.stderr:2}
Expand Down Expand Up @@ -1328,7 +1329,7 @@ def _libs_remote(self, remote):
self.error('Unable to find libraries for %r' % remote)
return {}

return misc.parse_ldd_output(data)
return misc.parse_ldd_output(context._decode(data))

def _get_fingerprint(self, remote):
cmd = '(sha256 || sha256sum || openssl sha256) 2>/dev/null < '
Expand Down Expand Up @@ -1553,6 +1554,7 @@ def upload_data(self, data, remote):
>>> print(open('/tmp/upload_bar').read())
Hello, world
"""
data = context._encode(data)
# If a relative path was provided, prepend the cwd
if os.path.normpath(remote) == os.path.basename(remote):
remote = os.path.join(self.cwd, remote)
Expand Down Expand Up @@ -1700,7 +1702,7 @@ def libs(self, remote, directory = None):

libs = self._libs_remote(remote)

remote = self.readlink('-f',remote).strip()
remote = context._decode(self.readlink('-f',remote).strip())
libs[remote] = 0

if directory == None:
Expand Down
1 change: 1 addition & 0 deletions pwnlib/util/cyclic.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ def cyclic_find(subseq, alphabet = None, n = None):

if isinstance(subseq, six.integer_types):
subseq = packing.pack(subseq, bytes=n)
subseq = context._encode(subseq)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks python 3 build. What is your usecase?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope cyclic_find can also accept a str likes cyclic_find('baaacaaa') besides of cyclic_find(b'baaacaaa') in Python 3.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must be done somehow differently. _encode enforces bytes, while we need the type that is used in the alphabet. The internal library usage does not use str with binary alphabets, nor does it use bytes with text alphabets.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can unify subseq and alphabet to bytes. Add a commit to fix the build.


if len(subseq) != n:
log.warn_once("cyclic_find() expects %i-byte subsequences by default, you gave %r\n"\
Expand Down