Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/stable' into beta-staging
Browse files Browse the repository at this point in the history
  • Loading branch information
zachriggle committed Dec 15, 2016
2 parents 620364d + 036397c commit 332c9d2
Show file tree
Hide file tree
Showing 12 changed files with 304 additions and 81 deletions.
120 changes: 108 additions & 12 deletions pwnlib/adb/adb.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,22 @@ def boot_time():
return int(value)

class AdbDevice(Device):
"""Encapsulates information about a connected device."""
"""Encapsulates information about a connected device.
Example:
>>> device = adb.wait_for_device()
>>> device.arch
'arm'
>>> device.bits
32
>>> device.os
'android'
>>> device.product
'sdk_phone_armv7'
>>> device.serial
'emulator-5554'
"""
def __init__(self, serial, type, port=None, product='unknown', model='unknown', device='unknown', features=None, **kw):
self.serial = serial
self.type = type
Expand All @@ -225,13 +240,45 @@ def __init__(self, serial, type, port=None, product='unknown', model='unknown',
if product == 'unknown':
return

with context.local(device=serial):
# Deferred fields
self._initialized = False
self._arch = None
self._bits = None
self._endian = None
self._avd = None

@property
def arch(self):
self.__do_deferred_initialization()
return self._arch

@property
def avd(self):
self.__do_deferred_initialization()
return self._avd

@property
def bits(self):
self.__do_deferred_initialization()
return self._bits

@property
def endian(self):
self.__do_deferred_initialization()
return self._endian


def __do_deferred_initialization(self):
if self._initialized:
return

with context.local(device=self.serial):
abi = str(properties.ro.product.cpu.abi)
context.clear()
context.arch = str(abi)
self.arch = context.arch
self.bits = context.bits
self.endian = context.endian
self._arch = context.arch
self._bits = context.bits
self._endian = context.endian

if self.port == 'emulator':
emulator, port = self.serial.split('-')
Expand All @@ -244,7 +291,8 @@ def __init__(self, serial, type, port=None, product='unknown', model='unknown',
self.avd = r.recvline().strip()
except:
pass
# r = remote('localhost')

self._initialized = True

def __str__(self):
return self.serial
Expand Down Expand Up @@ -299,6 +347,10 @@ def __getattr__(self, name):
>>> adb.getprop(property) == device.getprop(property)
True
"""
if name in self.__deferred_fields:
self._do_deferred_initialization()
return getattr(self, name)

with context.local(device=self):
g = globals()

Expand Down Expand Up @@ -345,7 +397,9 @@ def wait_for_device(kick=False):
for device in devices():
if context.device == device:
return device
break

if not serial:
break
else:
log.error("Could not find any devices")

Expand Down Expand Up @@ -899,7 +953,7 @@ def find_ndk_project_root(source):
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := poc
LOCAL_MODULE := %(local_module)s
LOCAL_SRC_FILES := %(local_src_files)s
include $(BUILD_EXECUTABLE)
Expand Down Expand Up @@ -929,6 +983,8 @@ def _generate_ndk_project(file_list, abi='arm-v7a', platform_version=21):
# Create the directories

# Populate Android.mk
local_module = os.path.basename(file_list[0])
local_module, _ = os.path.splitext(local_module)
local_src_files = ' '.join(list(map(os.path.basename, file_list)))
Android_mk = os.path.join(jni_directory, 'Android.mk')
with open(Android_mk, 'w+') as f:
Expand Down Expand Up @@ -963,7 +1019,7 @@ def compile(source):
abi = 'armeabi-v7a'
sdk = '21'

# If we have an atatched device, use its settings.
# If we have an attached device, use its settings.
if context.device:
abi = str(properties.ro.product.cpu.abi)
sdk = str(properties.ro.build.version.sdk)
Expand Down Expand Up @@ -1000,12 +1056,51 @@ def data(self):
with log.waitfor('Fetching %r partition (%s)' % (self.name, self.path)):
return read(self.path)

@with_device
def walk(top, topdown=True):
join = os.path.join
isdir = lambda x: stat.S_ISDIR(x['mode'])
client = Client()
names = client.list(top)

dirs, nondirs = [], []
for name, metadata in names.items():
if isdir(metadata):
dirs.append(name)
else:
nondirs.append(name)

if topdown:
yield top, dirs, nondirs
for name in dirs:
new_path = join(top, name)
for x in walk(new_path, topdown):
yield x
if not topdown:
yield top, dirs, nondirs

@with_device
def find(top, name):
for root, dirs, files in walk(top):
if name in files or name in dirs:
yield os.path.join(root, name)

@with_device
def readlink(path):
path = process(['readlink', path]).recvall()

# Readlink will emit a single newline
# We can't use the '-n' flag since old versions don't support it
if path.endswith('\n'):
path = path[:-1]

return path

class Partitions(object):
@property
@context.quiet
def by_name_dir(self):
cmd = ['shell','find /dev/block/platform -type d -name by-name']
return adb(cmd).strip()
return next(find('/dev/block/platform','by-name'))

@context.quiet
def __dir__(self):
Expand All @@ -1021,6 +1116,7 @@ def __iter__(self):
yield name

@context.quiet
@with_device
def __getattr__(self, attr):
for name in self:
if name == attr:
Expand All @@ -1031,7 +1127,7 @@ def __getattr__(self, attr):
path = os.path.join(self.by_name_dir, name)

# Find the actual path of the device
devpath = process(['readlink', '-n', path]).recvall()
devpath = readlink(path)
devname = os.path.basename(devpath)

# Get the size of the partition
Expand Down
52 changes: 46 additions & 6 deletions pwnlib/adb/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
https://android.googlesource.com/platform/system/core/+/master/adb/protocol.txt
"""
import functools
import stat
import time

from ..context import context
Expand Down Expand Up @@ -42,11 +43,11 @@ def __flat__(self):
class Connection(remote):
"""Connection to the ADB server"""
def __init__(self, host, port, level=None, *a, **kw):

# Try to make sure ADB is running if it's on the default host and port.
if host == context.defaults['adb_host'] \
and port == context.defaults['adb_port']:
process(context.adb + ['start-server'], level='error').wait_for_close()
with context.quiet:
process(context.adb + ['start-server']).recvall()

with context.quiet:
super(Connection, self).__init__(host, port, level=level, *a, **kw)
Expand Down Expand Up @@ -296,8 +297,6 @@ def wrapper(self, *a, **kw):
return rv
return wrapper

@_with_transport
@_sync
def list(self, path):
"""Execute the ``LIST`` command of the ``SYNC`` API.
Expand Down Expand Up @@ -328,8 +327,23 @@ def list(self, path):
>>> pprint(adb.Client().list('/data/user'))
{'0': {'mode': 41471, 'size': 11, 'time': ...}}
>>> adb.Client().list('/does/not/exist')
{}
Traceback (most recent call last):
...
PwnlibException: Cannot list directory '/does/not/exist': Does not exist
"""
st = self.stat(path)

if not st:
log.error("Cannot list directory %r: Does not exist" % path)

if not stat.S_ISDIR(st['mode']):
log.error("Cannot list directory %r: Path is not a directory" % path)

return self._list(path)

@_with_transport
@_sync
def _list(self, path):
self.c.flat('LIST', len(path), path)
files = {}
while True:
Expand Down Expand Up @@ -389,9 +403,35 @@ def stat(self, path):

return {'mode': mode, 'size': size, 'time': time}

def write(self, path, data, mode=0o755, timestamp=None, callback=None):
"""Execute the ``WRITE`` command of the ``SYNC`` API.
Arguments:
path(str): Path to the file to write
data(str): Data to write to the file
mode(int): File mode to set (e.g. ``0o755``)
timestamp(int): Unix timestamp to set the file date to
callback(callable): Callback function invoked as data
is written. Arguments provided are:
- File path
- All data
- Expected size of all data
- Current chunk
- Expected size of chunk
"""
# We must ensure that 'path' is not a directory
# Writing to a directory is supported, but creates a temporary file
st = self.stat(path)

if st and stat.S_ISDIR(st['mode']):
log.error("Cannot write to %r: Path is a directory" % path)

return self._write(path, data, mode=0o755, timestamp=None, callback=None)

@_with_transport
@_sync
def write(self, path, data, mode=0o755, timestamp=None, callback=None):
def _write(self, path, data, mode=0o755, timestamp=None, callback=None):
path += ',' + str(mode)
self.c.flat('SEND', len(path), path)

Expand Down
49 changes: 47 additions & 2 deletions pwnlib/context/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import os
import platform
import socket
import stat
import string
import subprocess
import sys
Expand Down Expand Up @@ -1126,8 +1127,13 @@ def device(self, device):

@property
def adb(self):
"""Returns an argument array for connecting to adb."""
command = ['adb']
"""Returns an argument array for connecting to adb.
Unless ``$ADB_PATH`` is set, uses the default ``adb`` binary in ``$PATH``.
"""
ADB_PATH = os.environ.get('ADB_PATH', 'adb')

command = [ADB_PATH]

if self.adb_host != self.defaults['adb_host']:
command += ['-H', self.adb_host]
Expand All @@ -1140,6 +1146,45 @@ def adb(self):

return command

@property
def cache_dir(self):
"""Directory used for caching data.
Note:
May be either a path string, or ``None``.
Example:
>>> cache_dir = context.cache_dir
>>> cache_dir is not None
True
>>> os.chmod(cache_dir, 0o000)
>>> context.cache_dir is None
True
>>> os.chmod(cache_dir, 0o755)
>>> cache_dir == context.cache_dir
True
"""
home = os.path.expanduser('~')

if not os.access(home, os.W_OK):
return None

cache = os.path.join(home, '.pwntools-cache')

if not os.path.exists(cache):
try:
os.mkdir(cache)
except OSError:
return None

# Some wargames e.g. pwnable.kr have created dummy directories
# which cannot be modified by the user account (owned by root).
if not os.access(cache, os.W_OK):
return None

return cache


#*************************************************************************
# ALIASES
Expand Down
2 changes: 1 addition & 1 deletion pwnlib/elf/corefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ def vsyscall(self):
def libc(self):
"""Return the first mapping in libc"""
for m in self.mappings:
if m.name.startswith('libc') and m.name.endswith('.so'):
if m.name and m.name.startswith('libc') and m.name.endswith('.so'):
return m

@property
Expand Down
3 changes: 2 additions & 1 deletion pwnlib/elf/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,8 @@ class elf_prstatus_i386(ctypes.Structure):
assert ctypes.sizeof(elf_prstatus_i386) == 0x90

class elf_prstatus_amd64(ctypes.Structure):
_fields_ = generate_prstatus_common(64, user_regs_struct_amd64)
_fields_ = generate_prstatus_common(64, user_regs_struct_amd64) \
+ [('padding', ctypes.c_uint32)]

assert ctypes.sizeof(elf_prstatus_amd64) == 0x150

Expand Down

0 comments on commit 332c9d2

Please sign in to comment.