diff --git a/pwnlib/adb/adb.py b/pwnlib/adb/adb.py index 5b3884097..9c34d35de 100644 --- a/pwnlib/adb/adb.py +++ b/pwnlib/adb/adb.py @@ -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__) @@ -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 @@ -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: @@ -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, @@ -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. @@ -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): @@ -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() @@ -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'\[([^\]]+)\]: \[(.*)\]' @@ -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 diff --git a/pwnlib/commandline/debug.py b/pwnlib/commandline/debug.py index eb0ff4203..a4b9a450a 100644 --- a/pwnlib/commandline/debug.py +++ b/pwnlib/commandline/debug.py @@ -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() diff --git a/pwnlib/gdb.py b/pwnlib/gdb.py index 7e3658bb3..3de5e34c0 100644 --- a/pwnlib/gdb.py +++ b/pwnlib/gdb.py @@ -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 @@ -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: diff --git a/pwnlib/protocols/adb/__init__.py b/pwnlib/protocols/adb/__init__.py index da56297d9..ba3ba1c8a 100644 --- a/pwnlib/protocols/adb/__init__.py +++ b/pwnlib/protocols/adb/__init__.py @@ -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. @@ -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 diff --git a/pwnlib/tubes/process.py b/pwnlib/tubes/process.py index 1fc649e5f..cd02c562e 100644 --- a/pwnlib/tubes/process.py +++ b/pwnlib/tubes/process.py @@ -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)