From c6aa6bccb8cf3ca56849368ec5747ab442e04277 Mon Sep 17 00:00:00 2001 From: Zach Riggle Date: Fri, 26 Aug 2016 17:31:16 -0700 Subject: [PATCH 01/13] Add Android emulator to Travis --- .travis.yml | 3 ++ pwnlib/adb.py | 3 ++ travis/install.sh | 73 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 64764396f..b645dfc85 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,12 +5,15 @@ addons: packages: - gcc-multilib - gcc-4.6-arm-linux-gnueabihf + - lib32stdc++6 cache: - pip - directories: - usr - /home/travis/virtualenv/python2.7.9/lib/python2.7/site-packages/ - /home/travis/virtualenv/python2.7.9/bin/ + - android-ndk + - android-sdk python: - "2.7" before_install: diff --git a/pwnlib/adb.py b/pwnlib/adb.py index 81ecbbdd2..5e254e659 100644 --- a/pwnlib/adb.py +++ b/pwnlib/adb.py @@ -168,6 +168,9 @@ def disable_verity(): return elif 'Now reboot your device' in reply: reboot(wait=True) + elif 'error: closed' in reply: + # Device does not support dm-verity? + return else: log.error("Could not disable verity:\n%s" % reply) diff --git a/travis/install.sh b/travis/install.sh index 120ebb1d0..66f95fbb0 100644 --- a/travis/install.sh +++ b/travis/install.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash -e +set -x local_deb_extract() { wget $1 @@ -59,12 +60,80 @@ setup_travis() setup_linux() { - sudo apt-get install -y software-properties-common openssh-server libncurses5-dev libncursesw5-dev + sudo apt-get install -y software-properties-common openssh-server libncurses5-dev libncursesw5-dev openjdk-8-jre-headless sudo apt-add-repository --yes ppa:pwntools/binutils sudo apt-get update sudo apt-get install binutils-arm-linux-gnu binutils-mips-linux-gnu binutils-powerpc-linux-gnu } +setup_android_emulator() + + if ! which java; then + echo "OpenJDK-8-JRE is required for Android stuff" + exit 1 + fi + + if (uname | grep -i Darwin &>/dev/null); then + brew install android-sdk android-ndk + else + if [ ! -f android-sdk/android ]; then + # Install the SDK, which gives us the 'android' and 'emulator' commands + wget https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz + tar xf android-sdk_r24.4.1-linux.tgz + rm -f android-sdk_r24.4.1-linux.tgz + + # Travis caching causes this to exist already + rm -rf android-sdk + + mv android-sdk-linux android-sdk + file android-sdk/tools/android + fi + + export PATH="$PWD/android-sdk/tools:$PATH" + which android + + # Install the NDK, which is required for adb.compile() + NDK_VERSION=android-ndk-r12b + if [ ! -f android-ndk/ndk-build ]; then + wget https://dl.google.com/android/repository/$NDK_VERSION-linux-x86_64.zip + unzip android-ndk-*.zip + rm -f android-ndk-*.zip + + # Travis caching causes this to exist already + rm -rf android-ndk + + mv $NDK_VERSION android-ndk + fi + + export NDK=$PWD/android-ndk + export PATH=$NDK:$PATH + fi + + # Grab prerequisites + echo y | android update sdk --no-ui --all --filter platform-tools,extra-android-support + echo y | android update sdk --no-ui --all --filter android-21 + + # Valid ABIs: + # - armeabi-v7a + # - arm64-v8a + # - x86 + # - x86_64 + ABI='armeabi-v7a' + + # Grab the emulator image + echo y | android update sdk --no-ui --all --filter sys-img-$ABI-android-21 + + # Create our emulator Android Virtual Device (AVD) + echo no | android --silent create avd --name android-$ABI --target android-21 --force --snapshot --abi $ABI + + # In the future, it would be nice to be able to use snapshots. + # However, I haven't gotten them to work nicely. + emulator -avd android-$ABI -no-window -no-boot-anim -no-skin -no-audio -no-window -no-snapshot & + adb wait-for-device + adb shell id + adb shell getprop +} + setup_osx() { brew update @@ -85,4 +154,6 @@ elif [[ "$(uname)" == "Linux" ]]; then setup_linux fi +setup_android_emulator + dpkg -l From 84c71d6b078c1f6b16816a6a78bee3126915529b Mon Sep 17 00:00:00 2001 From: Zach Riggle Date: Fri, 26 Aug 2016 18:56:50 -0700 Subject: [PATCH 02/13] Squelch travis 4MB log error --- travis/install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/travis/install.sh b/travis/install.sh index 66f95fbb0..4c8886b10 100644 --- a/travis/install.sh +++ b/travis/install.sh @@ -96,8 +96,8 @@ setup_android_emulator() NDK_VERSION=android-ndk-r12b if [ ! -f android-ndk/ndk-build ]; then wget https://dl.google.com/android/repository/$NDK_VERSION-linux-x86_64.zip - unzip android-ndk-*.zip - rm -f android-ndk-*.zip + unzip -q android-ndk-*.zip + rm -f android-ndk-*.zip # Travis caching causes this to exist already rm -rf android-ndk From 23c09b4b1b6715a49ae9fcd2f6df0a563e32be03 Mon Sep 17 00:00:00 2001 From: Zach Riggle Date: Fri, 26 Aug 2016 19:01:50 -0700 Subject: [PATCH 03/13] Add platform-tools to PATH for adb, fastboot, etc. --- travis/install.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/travis/install.sh b/travis/install.sh index 4c8886b10..9bcc25c20 100644 --- a/travis/install.sh +++ b/travis/install.sh @@ -90,6 +90,7 @@ setup_android_emulator() fi export PATH="$PWD/android-sdk/tools:$PATH" + export PATH="$PWD/android-sdk/platform-tools:$PATH" which android # Install the NDK, which is required for adb.compile() From 2acdeef32018c0a3ed3db76b7773b98de7914c0e Mon Sep 17 00:00:00 2001 From: Zach Riggle Date: Fri, 26 Aug 2016 19:06:03 -0700 Subject: [PATCH 04/13] Add missing bracket --- travis/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/travis/install.sh b/travis/install.sh index 9bcc25c20..f4bb626de 100644 --- a/travis/install.sh +++ b/travis/install.sh @@ -67,7 +67,7 @@ setup_linux() } setup_android_emulator() - +{ if ! which java; then echo "OpenJDK-8-JRE is required for Android stuff" exit 1 From 18a8cfb88d5bdcce32121bb80ff67e12401a3420 Mon Sep 17 00:00:00 2001 From: Zach Riggle Date: Fri, 26 Aug 2016 19:12:51 -0700 Subject: [PATCH 05/13] Disable verbose logging --- travis/install.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/travis/install.sh b/travis/install.sh index f4bb626de..3a9104e8f 100644 --- a/travis/install.sh +++ b/travis/install.sh @@ -1,5 +1,4 @@ #!/usr/bin/env bash -e -set -x local_deb_extract() { wget $1 From 8f98c01b773a0a0b3aacb1db6a86565bae6f7b96 Mon Sep 17 00:00:00 2001 From: Zach Riggle Date: Fri, 26 Aug 2016 17:39:20 -0700 Subject: [PATCH 06/13] More robust handling of ADB device status, and lack of devices --- pwnlib/adb.py | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/pwnlib/adb.py b/pwnlib/adb.py index 5e254e659..69dda8936 100644 --- a/pwnlib/adb.py +++ b/pwnlib/adb.py @@ -66,7 +66,7 @@ def reboot_bootloader(): class AdbDevice(Device): """Encapsulates information about a connected device.""" - def __init__(self, serial, type, port, product='unknown', model='unknown', device='unknown'): + def __init__(self, serial, type, port=None, product='unknown', model='unknown', device='unknown'): self.serial = serial self.type = type self.port = port @@ -102,12 +102,16 @@ def from_adb_output(line): ZX1G22LM7G device usb:336789504X product:shamu model:Nexus_6 device:shamu features:cmd,shell_v2 84B5T15A29020449 device usb:336855040X product:angler model:Nexus_6P device:angler 0062741b0e54b353 unauthorized usb:337641472X + emulator-5554 offline """ # The last few fields need to be split at colons. split = lambda x: x.split(':')[-1] fields[3:] = list(map(split, fields[3:])) + if fields[1] in ('unauthorized', 'offline'): + return + return AdbDevice(*fields[:6]) @context.quiet @@ -360,6 +364,8 @@ def getprop(name=None): If ``name`` is not specified, a ``dict`` of all properties is returned. Otherwise, a string is returned with the contents of the named property. """ + wait_for_device() + with context.quiet: if name: return process(['getprop', name]).recvall().strip() @@ -634,19 +640,29 @@ def _generate_ndk_project(file_list, abi='arm-v7a', platform_version=21): def compile(source): """Compile a source file or project with the Android NDK.""" - # Ensure that we can find the NDK. - ndk = os.environ.get('NDK', None) - if ndk is None: - log.error('$NDK must be set to the Android NDK directory') - ndk_build = os.path.join(ndk, 'ndk-build') + ndk_build = misc.which('ndk-build') + if not ndk_build: + # Ensure that we can find the NDK. + ndk = os.environ.get('NDK', None) + if ndk is None: + log.error('$NDK must be set to the Android NDK directory') + ndk_build = os.path.join(ndk, 'ndk-build') # Determine whether the source is an NDK project or a single source file. project = find_ndk_project_root(source) if not project: - project = _generate_ndk_project(source, - str(properties.ro.product.cpu.abi), - str(properties.ro.build.version.sdk)) + # Realistically this should inherit from context.arch, but + # this works for now. + abi = 'armeabi-v7a' + sdk = '21' + + # If we have an atatched device, use its settings. + if context.device: + abi = str(properties.ro.product.cpu.abi) + sdk = str(properties.ro.build.version.sdk) + + project = _generate_ndk_project(source, abi, sdk) # Remove any output files lib = os.path.join(project, 'libs') From 40bf991153e859a12300818d98b4f299ebada5ce Mon Sep 17 00:00:00 2001 From: Zach Riggle Date: Sun, 28 Aug 2016 11:47:36 -0700 Subject: [PATCH 07/13] Add basic tests for some ADB code --- docs/source/adb.rst | 1 + pwnlib/adb.py | 114 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 100 insertions(+), 15 deletions(-) diff --git a/docs/source/adb.rst b/docs/source/adb.rst index efaecbfb5..73d788148 100644 --- a/docs/source/adb.rst +++ b/docs/source/adb.rst @@ -1,6 +1,7 @@ .. testsetup:: * from pwn import * + adb = pwnlib.adb :mod:`pwnlib.adb` --- Android Debug Bridge ===================================================== diff --git a/pwnlib/adb.py b/pwnlib/adb.py index 69dda8936..a09966898 100644 --- a/pwnlib/adb.py +++ b/pwnlib/adb.py @@ -1,5 +1,6 @@ """Provides utilities for interacting with Android devices via the Android Debug Bridge. """ +import functools import glob import os import platform @@ -21,7 +22,11 @@ log = getLogger(__name__) def adb(argv, *a, **kw): - """Returns the output of an ADB subcommand.""" + """Returns the output of an ADB subcommand. + + >>> adb.adb(['get-serialno']) + 'emulator-5554\n' + """ if isinstance(argv, (str, unicode)): argv = [argv] @@ -30,7 +35,10 @@ def adb(argv, *a, **kw): return tubes.process.process(context.adb + argv, *a, **kw).recvall() def root(): - """Restarts adbd as root.""" + """Restarts adbd as root. + + >>> adb.root() + """ log.info("Enabling root on %s" % context.device) with context.quiet: @@ -46,9 +54,19 @@ def root(): else: log.error("Could not run as root:\n%s" % reply) +def no_emulator(f): + @functools.wraps(f) + def wrapper(*a,**kw): + c = current_device() + if c and c.port == 'emulator': + log.error("Cannot invoke %s.%s on an emulator." % (f.__module__, f.__name__)) + return f(*a,**kw) + return wrapper +@no_emulator def reboot(wait=True): - """Reboots the device.""" + """Reboots the device. + """ log.info('Rebooting device %s' % context.device) with context.quiet: @@ -57,8 +75,10 @@ def reboot(wait=True): if wait: wait_for_device() +@no_emulator def reboot_bootloader(): - """Reboots the device to the bootloader.""" + """Reboots the device to the bootloader. + """ log.info('Rebooting %s to bootloader' % context.device) with context.quiet: @@ -66,7 +86,7 @@ def reboot_bootloader(): class AdbDevice(Device): """Encapsulates information about a connected device.""" - def __init__(self, serial, type, port=None, product='unknown', model='unknown', device='unknown'): + def __init__(self, serial, type, port=None, product='unknown', model='unknown', device='unknown', features=None): self.serial = serial self.type = type self.port = port @@ -85,6 +105,19 @@ def __init__(self, serial, type, port=None, product='unknown', model='unknown', self.bits = context.bits self.endian = context.endian + if self.port == 'emulator': + emulator, port = self.serial.split('-') + port = int(port) + try: + with remote('localhost', port, level='error') as r: + r.recvuntil('OK') + r.recvline() # Rest of the line + r.sendline('avd name') + self.avd = r.recvline().strip() + except: + pass + # r = remote('localhost') + def __str__(self): return self.serial @@ -103,16 +136,23 @@ def from_adb_output(line): 84B5T15A29020449 device usb:336855040X product:angler model:Nexus_6P device:angler 0062741b0e54b353 unauthorized usb:337641472X emulator-5554 offline + emulator-5554 device product:sdk_phone_armv7 model:sdk_phone_armv7 device:generic """ - # The last few fields need to be split at colons. - split = lambda x: x.split(':')[-1] - fields[3:] = list(map(split, fields[3:])) + fields = line.split() - if fields[1] in ('unauthorized', 'offline'): - return + serial = fields[0] + type = fields[1] + kwargs = {} + + if serial.startswith('emulator-'): + kwargs['port'] = 'emulator' - return AdbDevice(*fields[:6]) + for field in fields[2:]: + k,v = field.split(':') + kwargs[k] = v + + return AdbDevice(serial, type, **kwargs) @context.quiet def devices(serial=None): @@ -131,9 +171,29 @@ def devices(serial=None): return tuple(result) +def current_device(): + """Returns an ``AdbDevice`` instance for the currently-selected device + (via ``context.device``). + + Example: + + >>> adb.current_device() + AdbDevice(serial='emulator-5554', type='device', port='emulator', product='sdk_phone_armv7', model='sdk phone armv7', device='generic') + >>> adb.current_device().port + 'emulator' + """ + for device in devices(): + if device == context.device: + return device + @LocalContext def wait_for_device(kick=False): - """Waits for a device to be connected.""" + """Waits for a device to be connected. + + Examples: + + >>> adb.wait_for_device() + """ with log.waitfor("Waiting for device to come online") as w: with context.quiet: if kick: @@ -198,7 +258,7 @@ def unroot(): reply = adb('unroot') if 'restarting adbd as non root' not in reply: - log.error("Could not run as root:\n%s" % reply) + log.error("Could not unroot:\n%s" % reply) def pull(remote_path, local_path=None): """Download a file from the device. @@ -207,6 +267,12 @@ def pull(remote_path, local_path=None): remote_path(str): Path or directory of the file on the device. local_path(str): Path to save the file to. Uses the file's name by default. + + Example: + + >>> _=adb.pull('/proc/version', './proc-version') + >>> read('./proc-version') #doctest: +ELLIPSIS + "Linux version ...\n" """ if local_path is None: local_path = os.path.basename(remote_path) @@ -234,6 +300,13 @@ def push(local_path, remote_path): Arguments: local_path(str): Path to the local file to push. remote_path(str): Path or directory to store the file on the device. + + Example: + + >>> write('./filename', 'contents') + >>> _=adb.push('./filename', '/data/local/tmp') + >>> adb.read('/data/local/tmp/filename') + 'contents' """ msg = "Pushing %r to %r" % (local_path, remote_path) @@ -259,6 +332,11 @@ def read(path, target=None): path(str): Path to the file on the device. target(str): Optional, location to store the file. Uses a temporary file by default. + + Examples: + + >>> read('/proc/version') #doctest: +ELLIPSIS + "Linux version ...\n" """ with tempfile.NamedTemporaryFile() as temp: target = target or temp.name @@ -273,6 +351,10 @@ def write(path, data=''): Arguments: path(str): Path to the file on the device data(str): Contents to store in the file + + ExamplesS: + + >>> write('') """ with tempfile.NamedTemporaryFile() as temp: misc.write(temp.name, data) @@ -290,6 +372,10 @@ def process(argv, *a, **kw): if isinstance(argv, (str, unicode)): argv = [argv] + for i, arg in enumerate(argv): + if ' ' in arg and '"' not in arg: + argv[i] = '"%s"' % arg + display = argv argv = context.adb + ['shell'] + argv @@ -364,8 +450,6 @@ def getprop(name=None): If ``name`` is not specified, a ``dict`` of all properties is returned. Otherwise, a string is returned with the contents of the named property. """ - wait_for_device() - with context.quiet: if name: return process(['getprop', name]).recvall().strip() From a43c3b4967b11c1734aad9bff8a3f3c9d7f520f0 Mon Sep 17 00:00:00 2001 From: Zach Riggle Date: Mon, 29 Aug 2016 12:50:26 -0700 Subject: [PATCH 08/13] Add with_device annotation to auto-detect a device --- pwnlib/adb.py | 121 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 85 insertions(+), 36 deletions(-) diff --git a/pwnlib/adb.py b/pwnlib/adb.py index a09966898..84d1c8708 100644 --- a/pwnlib/adb.py +++ b/pwnlib/adb.py @@ -34,6 +34,54 @@ def adb(argv, *a, **kw): return tubes.process.process(context.adb + argv, *a, **kw).recvall() +@context.quiet +def devices(serial=None): + """Returns a list of ``Device`` objects corresponding to the connected devices.""" + lines = adb(['devices', '-l']) + result = [] + + for line in lines.splitlines(): + # Skip the first 'List of devices attached' line, and the final empty line. + if 'List of devices' in line or not line.strip(): + continue + device = AdbDevice.from_adb_output(line) + if device.serial == serial: + return device + result.append(device) + + return tuple(result) + +def current_device(any=False): + """Returns an ``AdbDevice`` instance for the currently-selected device + (via ``context.device``). + + Example: + + >>> adb.current_device() + AdbDevice(serial='emulator-5554', type='device', port='emulator', product='sdk_phone_armv7', model='sdk phone armv7', device='generic') + >>> adb.current_device().port + 'emulator' + """ + all_devices = devices() + for device in all_devices: + if any or device == context.device: + return device + +def with_device(f): + @functools.wraps(f) + def wrapper(*a,**kw): + if not context.device: + device = current_device(any=True) + if device: + log.warn_once('Automatically selecting device %s' % device) + context.device = device + if not context.device: + log.error('No devices connected, cannot invoke %s.%s' % (f.__module__, f.__name__)) + return f(*a,**kw) + return wrapper + + +@with_device def root(): """Restarts adbd as root. @@ -64,6 +112,7 @@ def wrapper(*a,**kw): return wrapper @no_emulator +@with_device def reboot(wait=True): """Reboots the device. """ @@ -76,6 +125,7 @@ def reboot(wait=True): wait_for_device() @no_emulator +@with_device def reboot_bootloader(): """Reboots the device to the bootloader. """ @@ -154,38 +204,6 @@ def from_adb_output(line): return AdbDevice(serial, type, **kwargs) -@context.quiet -def devices(serial=None): - """Returns a list of ``Device`` objects corresponding to the connected devices.""" - lines = adb(['devices', '-l']) - result = [] - - for line in lines.splitlines(): - # Skip the first 'List of devices attached' line, and the final empty line. - if 'List of devices' in line or not line.strip(): - continue - device = AdbDevice.from_adb_output(line) - if device.serial == serial: - return device - result.append(device) - - return tuple(result) - -def current_device(): - """Returns an ``AdbDevice`` instance for the currently-selected device - (via ``context.device``). - - Example: - - >>> adb.current_device() - AdbDevice(serial='emulator-5554', type='device', port='emulator', product='sdk_phone_armv7', model='sdk phone armv7', device='generic') - >>> adb.current_device().port - 'emulator' - """ - for device in devices(): - if device == context.device: - return device - @LocalContext def wait_for_device(kick=False): """Waits for a device to be connected. @@ -220,6 +238,7 @@ def wait_for_device(kick=False): return context.device +@with_device def disable_verity(): """Disables dm-verity on the device.""" with log.waitfor("Disabling dm-verity on %s" % context.device) as w: @@ -238,7 +257,7 @@ def disable_verity(): else: log.error("Could not disable verity:\n%s" % reply) - +@with_device def remount(): """Remounts the filesystem as writable.""" with log.waitfor("Remounting filesystem on %s" % context.device) as w: @@ -251,6 +270,7 @@ def remount(): if 'remount succeeded' not in reply: log.error("Could not remount filesystem:\n%s" % reply) +@with_device def unroot(): """Restarts adbd as AID_SHELL.""" log.info("Unrooting %s" % context.device) @@ -260,6 +280,7 @@ def unroot(): if 'restarting adbd as non root' not in reply: log.error("Could not unroot:\n%s" % reply) +@with_device def pull(remote_path, local_path=None): """Download a file from the device. @@ -294,6 +315,7 @@ def pull(remote_path, local_path=None): return result +@with_device def push(local_path, remote_path): """Upload a file to the device. @@ -325,6 +347,7 @@ def push(local_path, remote_path): return result @context.quiet +@with_device def read(path, target=None): """Download a file from the device, and extract its contents. @@ -345,6 +368,7 @@ def read(path, target=None): return result @context.quiet +@with_device def write(path, data=''): """Create a file on the device with the provided contents. @@ -352,7 +376,7 @@ def write(path, data=''): path(str): Path to the file on the device data(str): Contents to store in the file - ExamplesS: + Examples: >>> write('') """ @@ -360,6 +384,7 @@ def write(path, data=''): misc.write(temp.name, data) push(temp.name, path) +@with_device def process(argv, *a, **kw): """Execute a process on the device. @@ -367,6 +392,12 @@ def process(argv, *a, **kw): Returns: A ``process`` tube. + + Examples: + + >>> adb.root() + >>> adb.process(['whoami']).recvall().strip() + 'root' """ argv = argv or [] if isinstance(argv, (str, unicode)): @@ -384,22 +415,27 @@ def process(argv, *a, **kw): return tubes.process.process(argv, *a, **kw) +@with_device def interactive(**kw): """Spawns an interactive shell.""" return shell(**kw).interactive() +@with_device def shell(**kw): """Returns an interactive shell.""" return process([], **kw) @context.quiet +@with_device def which(name): """Retrieves the full path to a binary in ``PATH`` on the device""" return process(['which', name]).recvall().strip() +@with_device def whoami(): return process(['whoami']).recvall().strip() +@with_device def forward(port): """Sets up a port to forward to the device.""" tcp_port = 'tcp:%s' % port @@ -407,6 +443,7 @@ def forward(port): atexit.register(lambda: adb(['forward', '--remove', tcp_port])) @context.quiet +@with_device def logcat(stream=False): """Reads the system log file. @@ -426,6 +463,7 @@ def logcat(stream=False): else: return adb(['logcat', '-d']) +@with_device def pidof(name): """Returns a list of PIDs for the named process.""" with context.quiet: @@ -433,6 +471,7 @@ def pidof(name): data = io.recvall().split() return list(map(int, data)) +@with_device def proc_exe(pid): """Returns the full path of the executable for the provided PID.""" with context.quiet: @@ -440,6 +479,7 @@ def proc_exe(pid): data = io.recvall().strip() return data +@with_device def getprop(name=None): """Reads a properties from the system property store. @@ -474,10 +514,12 @@ def getprop(name=None): return props +@with_device def setprop(name, value): """Writes a property to the system property store.""" return process(['setprop', name, value]).recvall().strip() +@with_device def listdir(directory='/'): """Returns a list containing the entries in the provided directory. @@ -485,10 +527,11 @@ def listdir(directory='/'): Because ``adb shell`` is used to retrieve the listing, shell environment variable expansion and globbing are in effect. """ - io = process(['find', directory, '-maxdepth', '1', '-print0']) + # io = process(['find', directory, '-maxdepth', '1', '-print0']) + io = process(['sh','-c',"""'cd %s; for file in ./* ./.*; do [ -e "$file" ] || continue; echo -n "$file"; echo -ne "\\x00"; done'""" % directory]) data = io.recvall() paths = filter(len, data.split('\x00')) - relpaths = [os.path.relpath(path, directory) for path in paths] + relpaths = [os.path.relpath(path, '.') for path in paths] if '.' in relpaths: relpaths.remove('.') return relpaths @@ -504,18 +547,23 @@ def fastboot(args, *a, **kw): log.error("Unknown device") return tubes.process.process(['fastboot', '-s', serial] + list(args), **kw).recvall() +@with_device def fingerprint(): """Returns the device build fingerprint.""" return str(properties.ro.build.fingerprint) +@with_device def product(): """Returns the device product identifier.""" return str(properties.ro.build.product) +@with_device def build(): """Returns the Build ID of the device.""" return str(properties.ro.build.id) +@with_device +@no_emulator def unlock_bootloader(): """Unlocks the bootloader of the device. @@ -790,6 +838,7 @@ def __dir__(self): return list(self) @context.quiet + @with_device def __iter__(self): root() From 348bcac0ff70d8e38d9cf111627323c8e22b1212 Mon Sep 17 00:00:00 2001 From: Zach Riggle Date: Tue, 30 Aug 2016 14:40:32 -0700 Subject: [PATCH 09/13] Fix unit test failures in ADB module --- pwnlib/adb.py | 54 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/pwnlib/adb.py b/pwnlib/adb.py index 84d1c8708..bad4adef0 100644 --- a/pwnlib/adb.py +++ b/pwnlib/adb.py @@ -22,7 +22,7 @@ log = getLogger(__name__) def adb(argv, *a, **kw): - """Returns the output of an ADB subcommand. + r"""Returns the output of an ADB subcommand. >>> adb.adb(['get-serialno']) 'emulator-5554\n' @@ -57,9 +57,10 @@ def current_device(any=False): Example: - >>> adb.current_device() + >>> device = adb.current_device(any=True) + >>> device AdbDevice(serial='emulator-5554', type='device', port='emulator', product='sdk_phone_armv7', model='sdk phone armv7', device='generic') - >>> adb.current_device().port + >>> device.port 'emulator' """ all_devices = devices() @@ -208,9 +209,16 @@ def from_adb_output(line): def wait_for_device(kick=False): """Waits for a device to be connected. + By default, waits for the currently-selected device (via ``context.device``). + To wait for a specific device, set ``context.device``. + To wait for *any* device, clear ``context.device``. + + Return: + An ``AdbDevice`` instance for the device. + Examples: - >>> adb.wait_for_device() + >>> device = adb.wait_for_device() """ with log.waitfor("Waiting for device to come online") as w: with context.quiet: @@ -293,7 +301,7 @@ def pull(remote_path, local_path=None): >>> _=adb.pull('/proc/version', './proc-version') >>> read('./proc-version') #doctest: +ELLIPSIS - "Linux version ...\n" + "Linux version ..." """ if local_path is None: local_path = os.path.basename(remote_path) @@ -359,7 +367,7 @@ def read(path, target=None): Examples: >>> read('/proc/version') #doctest: +ELLIPSIS - "Linux version ...\n" + "Linux version ..." """ with tempfile.NamedTemporaryFile() as temp: target = target or temp.name @@ -378,7 +386,8 @@ def write(path, data=''): Examples: - >>> write('') + >>> adb.write('/dev/null', 'data') + >>> adb.write('/data/local/tmp/') """ with tempfile.NamedTemporaryFile() as temp: misc.write(temp.name, data) @@ -396,8 +405,8 @@ def process(argv, *a, **kw): Examples: >>> adb.root() - >>> adb.process(['whoami']).recvall().strip() - 'root' + >>> adb.process(['cat','/proc/version']).recvall() #doctest:+ELLIPSIS + "Linux version ..." """ argv = argv or [] if isinstance(argv, (str, unicode)): @@ -428,12 +437,31 @@ def shell(**kw): @context.quiet @with_device def which(name): - """Retrieves the full path to a binary in ``PATH`` on the device""" - return process(['which', name]).recvall().strip() + """Retrieves the full path to a binary in ``PATH`` on the device + + >>> adb.which('sh') + '/system/bin/sh' + """ + + # Unfortunately, there is no native 'which' on many phones. + which_cmd = ''' +IFS=: +BINARY=%s +P=($PATH) +for path in "${P[@]}"; do + if [ -e "$path/$BINARY" ]; then + echo "$path/$BINARY"; + break + fi +done +''' % name + + which_cmd = which_cmd.strip() + return process([which_cmd]).recvall().strip() @with_device def whoami(): - return process(['whoami']).recvall().strip() + return process(['sh','-ic','echo $USER']).recvall().strip() @with_device def forward(port): @@ -475,7 +503,7 @@ def pidof(name): def proc_exe(pid): """Returns the full path of the executable for the provided PID.""" with context.quiet: - io = process(['readlink','-e','/proc/%d/exe' % pid]) + io = process(['realpath','/proc/%d/exe' % pid]) data = io.recvall().strip() return data From 3dcfafc0de9c523ce196bc2176005eb3a789ffd4 Mon Sep 17 00:00:00 2001 From: Zach Riggle Date: Tue, 30 Aug 2016 16:32:05 -0700 Subject: [PATCH 10/13] Fix unit test quote style and ELLIPSE formatting --- pwnlib/adb.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pwnlib/adb.py b/pwnlib/adb.py index bad4adef0..9aca77d73 100644 --- a/pwnlib/adb.py +++ b/pwnlib/adb.py @@ -300,8 +300,8 @@ def pull(remote_path, local_path=None): Example: >>> _=adb.pull('/proc/version', './proc-version') - >>> read('./proc-version') #doctest: +ELLIPSIS - "Linux version ..." + >>> print read('./proc-version') # doctest: +ELLIPSIS + Linux version ... """ if local_path is None: local_path = os.path.basename(remote_path) @@ -366,8 +366,8 @@ def read(path, target=None): Examples: - >>> read('/proc/version') #doctest: +ELLIPSIS - "Linux version ..." + >>> print read('/proc/version') # doctest: +ELLIPSIS + Linux version ... """ with tempfile.NamedTemporaryFile() as temp: target = target or temp.name @@ -405,8 +405,8 @@ def process(argv, *a, **kw): Examples: >>> adb.root() - >>> adb.process(['cat','/proc/version']).recvall() #doctest:+ELLIPSIS - "Linux version ..." + >>> print adb.process(['cat','/proc/version']).recvall() # doctest: +ELLIPSIS + Linux version ... """ argv = argv or [] if isinstance(argv, (str, unicode)): From b20e17dd1386a87da4cb65951383e3f9df89fd2b Mon Sep 17 00:00:00 2001 From: Zach Riggle Date: Tue, 30 Aug 2016 17:27:33 -0700 Subject: [PATCH 11/13] Skip Android emulator installation if we dont need it --- travis/install.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/travis/install.sh b/travis/install.sh index 3a9104e8f..b648ef1cd 100644 --- a/travis/install.sh +++ b/travis/install.sh @@ -67,6 +67,24 @@ setup_linux() setup_android_emulator() { + # If we are running on Travis CI, and there were no changes to Android + # or ADB code, then we do not need the emulator + if [ -n "$TRAVIS" ]; then + if ! (git log --stat "$TRAVIS_COMMIT_RANGE" | grep -E "android|adb"); then + # In order to avoid running the doctests that require the Android + # emulator, while still leaving the code intact, we remove the + # RST file that Sphinx searches. + rm -f 'docs/source/adb.rst' + + # However, the file needs to be present or else things break. + touch 'docs/source/adb.rst' + + echo "Skipping Android emulator install, Android tests disabled." + return + fi + fi + + if ! which java; then echo "OpenJDK-8-JRE is required for Android stuff" exit 1 From 9b179567da617ed2a80fae03835d2366694b9fd3 Mon Sep 17 00:00:00 2001 From: Zach Riggle Date: Tue, 30 Aug 2016 17:28:30 -0700 Subject: [PATCH 12/13] Remove un-cacheable Android SDK and NDK directories from Travis cache --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b645dfc85..142edd953 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,8 +12,6 @@ cache: - usr - /home/travis/virtualenv/python2.7.9/lib/python2.7/site-packages/ - /home/travis/virtualenv/python2.7.9/bin/ - - android-ndk - - android-sdk python: - "2.7" before_install: From 6e4e46386fc81a7c47bd9b6fa365942bfd3e1f33 Mon Sep 17 00:00:00 2001 From: Zach Riggle Date: Tue, 30 Aug 2016 17:30:29 -0700 Subject: [PATCH 13/13] Do not install emulator in all cases (e.g. OSX) --- travis/install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/travis/install.sh b/travis/install.sh index b648ef1cd..a84f5c46c 100644 --- a/travis/install.sh +++ b/travis/install.sh @@ -161,6 +161,7 @@ setup_osx() if [[ "$USER" == "travis" ]]; then setup_travis + setup_android_emulator elif [[ "$USER" == "shippable" ]]; then sudo apt-get update sudo apt-get install openssh-server gcc-multilib @@ -170,8 +171,8 @@ elif [[ "$(uname)" == "Darwin" ]]; then setup_osx elif [[ "$(uname)" == "Linux" ]]; then setup_linux + setup_android_emulator fi -setup_android_emulator dpkg -l