From 61bc060dcf035e01b2cb30e219524f179b58d59b Mon Sep 17 00:00:00 2001 From: Zach Riggle Date: Thu, 5 Jan 2017 00:44:16 -0500 Subject: [PATCH 1/5] WIP --- pwnlib/adb/adb.py | 63 +++++++++++++++++++++++++++++++++++++++-------- pwnlib/gdb.py | 22 +++++++++++++++++ 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/pwnlib/adb/adb.py b/pwnlib/adb/adb.py index 5b3884097..9f9537695 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, @@ -754,7 +755,7 @@ def unlink(path, recursive=False): 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,59 @@ 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 = 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. + argv0 = sh_string(argv[0]) + argv_str = ' '.join(map(sh_string, argv)) + cmd += 'exec -a %s %s' % (argv0, 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(argv) + + 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 +887,6 @@ def which(name, all = False, *a, **kw): return None - @with_device def whoami(): return process(['sh','-ic','echo $USER']).recvall().strip() @@ -905,10 +948,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 ui.recvall().strip() - result = process(['getprop']).recvall() + result = process(['getprop'], executable='/system/bin/getprop').recvall() expr = r'\[([^\]]+)\]: \[(.*)\]' @@ -1066,7 +1109,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/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: From 798951ac7966395302a230800f5bd7b3908d69fd Mon Sep 17 00:00:00 2001 From: Zach Riggle Date: Thu, 5 Jan 2017 00:47:41 -0500 Subject: [PATCH 2/5] process.env should be a stable copy --- pwnlib/tubes/process.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From 359fffe99abcaa527b844fa74961b5b4707ecd0c Mon Sep 17 00:00:00 2001 From: Zach Riggle Date: Fri, 13 Jan 2017 14:48:07 -0500 Subject: [PATCH 3/5] Fix typo --- pwnlib/adb/adb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnlib/adb/adb.py b/pwnlib/adb/adb.py index 9f9537695..ab1c6a40d 100644 --- a/pwnlib/adb/adb.py +++ b/pwnlib/adb/adb.py @@ -949,7 +949,7 @@ def getprop(name=None): with context.quiet: if name: io = process(['getprop', name], executable='/system/bin/getprop') - return ui.recvall().strip() + return io.recvall().strip() result = process(['getprop'], executable='/system/bin/getprop').recvall() From c9edc41645d59bf3977b8089ff3586e4743e7ec4 Mon Sep 17 00:00:00 2001 From: Zach Riggle Date: Sat, 14 Jan 2017 15:00:50 -0500 Subject: [PATCH 4/5] WIP --- pwnlib/adb/adb.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pwnlib/adb/adb.py b/pwnlib/adb/adb.py index ab1c6a40d..b78822f9a 100644 --- a/pwnlib/adb/adb.py +++ b/pwnlib/adb/adb.py @@ -779,7 +779,7 @@ def process(argv, shell=False, executable=None, env=None, *a, **kw): # Determine the full path to the executable if not executable: - executable = which(argv[0]) or './' + argv[0] + 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. @@ -805,9 +805,16 @@ def process(argv, shell=False, executable=None, env=None, *a, **kw): # # 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 -a %s %s' % (argv0, argv_str) + cmd += 'exec ' + + if argv0 != os.path.basename(executable): + cmd += '-a %s ' + + cmd += argv_str message = "Starting %s process %r" % ('Android', argv[0]) From e4f25403654d15f981af29f7663a0c9a9be85569 Mon Sep 17 00:00:00 2001 From: Zach Riggle Date: Fri, 24 Feb 2017 16:54:45 -0500 Subject: [PATCH 5/5] wip --- pwnlib/adb/adb.py | 4 ++-- pwnlib/commandline/debug.py | 16 +++++++++++++--- pwnlib/protocols/adb/__init__.py | 15 +++++++++------ 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/pwnlib/adb/adb.py b/pwnlib/adb/adb.py index b78822f9a..9c34d35de 100644 --- a/pwnlib/adb/adb.py +++ b/pwnlib/adb/adb.py @@ -749,7 +749,7 @@ 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) @@ -822,7 +822,7 @@ def process(argv, shell=False, executable=None, env=None, *a, **kw): if argv != [argv[0]]: message += ' argv=%r ' % argv with log.progress(message) as p: - result = AdbClient().execute(argv) + result = AdbClient().execute(cmd) result.argv = saved_argv result.pid = int(result.recvline()) 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/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