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

Enhance adb.process(), Fix Android Debugging under GDB #834

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 61 additions & 11 deletions pwnlib/adb/adb.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
from pwnlib.device import Device
from pwnlib.log import getLogger
from pwnlib.protocols.adb import AdbClient
from pwnlib.util.sh_string import sh_string
from pwnlib.util import misc

log = getLogger(__name__)
Expand Down Expand Up @@ -337,7 +338,7 @@ def __wrapped(self, function):
"""Wrapps a callable in a scope which selects the current device."""
@functools.wraps(function)
def wrapper(*a, **kw):
with context.local(device=self):
with context.local(device=self.serial):
return function(*a,**kw)
return wrapper

Expand All @@ -350,7 +351,7 @@ def __getattr__(self, name):
>>> adb.getprop(property) == device.getprop(property)
True
"""
with context.local(device=self):
with context.local(device=self.serial):
g = globals()

if name not in g:
Expand Down Expand Up @@ -402,7 +403,7 @@ def wait_for_device(kick=False):
else:
log.error("Could not find any devices")

with context.local(device=device):
with context.local(device=device.serial):
# There may be multiple devices, so context.device is
# insufficient. Pick the first device reported.
w.success('%s (%s %s %s)' % (device,
Expand Down Expand Up @@ -748,13 +749,13 @@ def unlink(path, recursive=False):

flags = '-rf' if recursive else '-r'

output = c.execute(['rm', flags, path]).recvall()
output = process(['rm', flags, path]).recvall()

if output:
log.error(output)

@with_device
def process(argv, *a, **kw):
def process(argv, shell=False, executable=None, env=None, *a, **kw):
"""Execute a process on the device.

See :class:`pwnlib.tubes.process.process` documentation for more info.
Expand All @@ -768,16 +769,66 @@ def process(argv, *a, **kw):
>>> print adb.process(['cat','/proc/version']).recvall() # doctest: +ELLIPSIS
Linux version ...
"""

if isinstance(argv, (str, unicode)):
argv = [argv]

# Save off a copy of the original argv array, which is stashed
# on the object.
saved_argv = list(argv)

# Determine the full path to the executable
if not executable:
executable = argv[0] # which(argv[0]) or './' + argv[0]

# If we're doing shell expansion, we need to feed it back into 'sh'
# Yes, this is a little convoluted.
if shell:
argv = ['sh', '-c'] + argv

# If we are *setting* the environment, prepent the "env -i" ...
if env:
# Build the command-line for env iteself
env_cmd = ['env', '-i']
for k,v in env.items():
env_cmd += ['%s=%s' % (k,v)]

argv = env_cmd + argv

# Echo the PID of our shell, so we know our own PID.
#
# This must be performed in this routine, since the '$' are escaped
# otherwise.
cmd = 'echo $$;'

# Actually "exec" the command, rather than fork-and-exec
#
# This avoids weird side-effects where execute(['sh'])
# would actually create two "sh" processes.
#
# The Toolbox version does not support '-a'
argv0 = sh_string(argv[0])
argv_str = ' '.join(map(sh_string, argv))
cmd += 'exec '

if argv0 != os.path.basename(executable):
cmd += '-a %s '

cmd += argv_str

message = "Starting %s process %r" % ('Android', argv[0])

if log.isEnabledFor(logging.DEBUG):
if argv != [argv[0]]: message += ' argv=%r ' % argv

with log.progress(message) as p:
return AdbClient().execute(argv)
result = AdbClient().execute(cmd)

result.argv = saved_argv
result.pid = int(result.recvline())
result.executable = executable or saved_argv[0]

return result

@with_device
def interactive(**kw):
Expand Down Expand Up @@ -843,7 +894,6 @@ def which(name, all = False, *a, **kw):

return None


@with_device
def whoami():
return process(['sh','-ic','echo $USER']).recvall().strip()
Expand Down Expand Up @@ -905,10 +955,10 @@ def getprop(name=None):
"""
with context.quiet:
if name:
return process(['getprop', name]).recvall().strip()

io = process(['getprop', name], executable='/system/bin/getprop')
return io.recvall().strip()

result = process(['getprop']).recvall()
result = process(['getprop'], executable='/system/bin/getprop').recvall()

expr = r'\[([^\]]+)\]: \[(.*)\]'

Expand Down Expand Up @@ -1066,7 +1116,7 @@ def enable_uart(self):
return

# Need to be root
with context.local(device=context.device):
with context.local(device=context.device.serial):
# Save off the command line before rebooting to the bootloader
cmdline = kernel.cmdline

Expand Down
16 changes: 13 additions & 3 deletions pwnlib/commandline/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,25 @@ def main(args):
target = int(args.pid)
elif args.process:
if context.os == 'android':
target = adb.pidof(process)
targets = adb.pidof(args.process)
else:
target = pidof(process)
targets = pidof(args.process)

if not targets:
log.error("Could not find PID for %r", args.process)

target = targets[0]
else:
parser.print_usage()
return 1

if args.pid or args.process:
gdb.attach(target, gdbscript=gdbscript)
pid = gdb.attach(target, gdbscript=gdbscript)

# Android needs to keep the fowarding open
if context.os == 'android':
with log.waitfor("Press 'enter' to terminate ADB forwarding session"):
pause()
else:
gdb.debug(target, gdbscript=gdbscript).interactive()

Expand Down
22 changes: 22 additions & 0 deletions pwnlib/gdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,23 @@
This causes some issues with the normal Pwntools workflow, since the process
heirarchy looks like this:

Android Debugging
~~~~~~~~~~~~~~~~~

All of the functionality described here also works on Android devices, given:

1. Your ``gdb`` can debug the device's binaries
2. The device has a ``gdbserver`` binary
3. You have properly set ``context.os`` and ``context.arch``

.. code-block:: python

# set context.os, context.arch, context.bits, and context.device
context.device = adb.wait_for_device()

# launch a process
p = adb.process('sh')

::

python ---> target
Expand Down Expand Up @@ -603,6 +620,11 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
misc.run_in_new_terminal(' '.join(cmd))
return

elif isinstance(target, tubes.sock.sock) and context.os == 'android':
# Android processes are really just sockets connected to
# the ADB server.
pass

elif isinstance(target, tubes.sock.sock):
pids = proc.pidof(target)
if not pids:
Expand Down
15 changes: 9 additions & 6 deletions pwnlib/protocols/adb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,12 @@ def transport(self, serial=None):

@_autoclose
@_with_transport
def execute(self, argv):
def execute(self, string):
r"""Executes a program on the device.

Arguments:
string(str): Raw string fed directly to the system shell.

Returns:
A :class:`pwnlib.tubes.tube.tube` which is connected to the process.

Expand All @@ -251,11 +254,11 @@ def execute(self, argv):
'hello\n'
"""
self.transport(context.device)
if isinstance(argv, str):
argv = [argv]
argv = list(map(sh_string, argv))
cmd = 'exec:%s' % (' '.join(argv))
if OKAY == self.send(cmd):

if not isinstance(string, str):
log.error("Not a string: %r", string)

if OKAY == self.send('exec:' + string):
rv = self._c
self._c = None
return rv
Expand Down
2 changes: 1 addition & 1 deletion pwnlib/tubes/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ def __init__(self, argv = None,
self.executable = which(self.argv[0])

#: Environment passed on envp
self.env = os.environ if env is None else env
self.env = env if env is not None else dict(os.environ)

self._cwd = os.path.realpath(cwd or os.path.curdir)

Expand Down