def which(name, all=False, *a, **kw): """Retrieves the full path to a binary in ``$PATH`` on the device Arguments: name(str): Binary name all(bool): Whether to return all paths, or just the first *a: Additional arguments for :func:`.adb.process` **kw: Additional arguments for :func:`.adb.process` Returns: Either a path, or list of paths Example: .. doctest:: :skipif: skip_android >>> adb.which('sh') '/system/bin/sh' >>> adb.which('sh', all=True) ['/system/bin/sh'] >>> adb.which('foobar') is None True >>> adb.which('foobar', all=True) [] """ # Unfortunately, there is no native 'which' on many phones. which_cmd = ''' (IFS=: for directory in $PATH; do [ -x "$directory/{name}" ] || continue; echo -n "$directory/{name}\\x00"; done ) [ -x "{name}" ] && echo -n "$PWD/{name}\\x00" '''.format(name=name) which_cmd = which_cmd.strip() data = process(['sh', '-c', which_cmd], *a, **kw).recvall() data = _decode(data) result = [] for path in data.split('\x00'): # Skip empty entries if not path: continue # Return the first entry if all=False if not all: return path # Accumulate all entries if all=True result.append(path) if all: return result return None
def getprop(name=None): """Reads a properties from the system property store. Arguments: name(str): Optional, read a single property. Returns: If ``name`` is not specified, a ``dict`` of all properties is returned. Otherwise, a string is returned with the contents of the named property. Example: .. doctest:: :skipif: skip_android >>> adb.getprop() # doctest: +ELLIPSIS {...} """ with context.quiet: if name: result = process(['getprop', name]).recvall().strip() result = _decode(result) return result result = process(['getprop']).recvall() result = _decode(result) expr = r'\[([^\]]+)\]: \[(.*)\]' props = {} for line in result.splitlines(): if not line.startswith('['): continue name, value = re.search(expr, line).groups() if value.isdigit(): value = int(value) props[name] = value return props
def _s(self, other): # We want strings if isinstance(other, str): return other # We don't want unicode if isinstance(other, six.text_type): return str(other) # We also don't want binary if isinstance(other, six.binary_type): return str(_decode(other))
def recvlinesS(self, numlines=2**20, keepends=False, timeout=default): r"""recvlinesS(numlines, keepends=False, timeout=default) -> str list This function is identical to :meth:`recvlines`, but decodes the received bytes into string using :func:`context.encoding`. You should use :meth:`recvlines` whenever possible for better performance. Examples: >>> t = tube() >>> t.recv_raw = lambda n: b'\n' >>> t.recvlinesS(3) ['', '', ''] >>> t.recv_raw = lambda n: b'Foo\nBar\nBaz\n' >>> t.recvlinesS(3) ['Foo', 'Bar', 'Baz'] """ return [packing._decode(x) for x in self.recvlines(numlines, keepends, timeout)]
def js_unescape(s, **kwargs): r"""js_unescape(s, endian = None, **kwargs) -> bytes Unpack an escaped Unicode string from JavaScript's `escape()` function Arguments: s (str): Escaped string to unpack endian (str): Endianness with which to unpack the string ("little"/"big") Returns: A bytes representation of the unpacked data >>> js_unescape('%uadde%uefbe') b'\xde\xad\xbe\xef' >>> js_unescape('%udead%ubeef', endian='big') b'\xde\xad\xbe\xef' >>> js_unescape('abc%u4141123') b'a\x00b\x00c\x00AA1\x002\x003\x00' >>> data = b'abcdABCD1234!@#$\x00\x01\x02\x03\x80\x81\x82\x83' >>> js_unescape(js_escape(data)) == data True >>> js_unescape('%u4141%u42') Traceback (most recent call last): ValueError: Incomplete Unicode token: %u42 >>> js_unescape('%u4141%uwoot%4141') Traceback (most recent call last): ValueError: Failed to decode token: %uwoot >>> js_unescape('%u4141%E4%F6%FC%u4141') Traceback (most recent call last): NotImplementedError: Non-Unicode % tokens are not supported: %E4 >>> js_unescape('%u4141%zz%u4141') Traceback (most recent call last): ValueError: Bad % token: %zz """ s = packing._decode(s) res = [] p = 0 while p < len(s): if s[p] == '%': if s[p + 1] == "u": # Decode Unicode token e.g. %u4142 n = s[p + 2:p + 6] if len(n) < 4: raise ValueError('Incomplete Unicode token: %s' % s[p:]) try: n = int(n, 16) except ValueError: raise ValueError('Failed to decode token: %s' % s[p:p + 6]) res.append(packing.p16(n)) p += 6 elif s[p + 1] in string.hexdigits and s[p + 2] in string.hexdigits: # Decode Non-Unicode token e.g. %E4 raise NotImplementedError( 'Non-Unicode %% tokens are not supported: %s' % s[p:p + 3]) else: raise ValueError('Bad %% token: %s' % s[p:p + 3]) else: res.append(packing.p16(ord(s[p]))) p += 1 return b''.join(res)
def mkdtemp(self): temp = _decode(context.ssh_session.mkdtemp()) return SSHPath(temp, ssh=context.ssh_session)
def find_module_addresses(binary, ssh=None, ulimit=False): """ Cheat to find modules by using GDB. We can't use ``/proc/$pid/map`` since some servers forbid it. This breaks ``info proc`` in GDB, but ``info sharedlibrary`` still works. Additionally, ``info sharedlibrary`` works on FreeBSD, which may not have procfs enabled or accessible. The output looks like this: :: info proc mapping process 13961 warning: unable to open /proc file '/proc/13961/maps' info sharedlibrary From To Syms Read Shared Object Library 0xf7fdc820 0xf7ff505f Yes (*) /lib/ld-linux.so.2 0xf7fbb650 0xf7fc79f8 Yes /lib32/libpthread.so.0 0xf7e26f10 0xf7f5b51c Yes (*) /lib32/libc.so.6 (*): Shared library is missing debugging information. Note that the raw addresses provided by ``info sharedlibrary`` are actually the address of the ``.text`` segment, not the image base address. This routine automates the entire process of: 1. Downloading the binaries from the remote server 2. Scraping GDB for the information 3. Loading each library into an ELF 4. Fixing up the base address vs. the ``.text`` segment address Arguments: binary(str): Path to the binary on the remote server ssh(pwnlib.tubes.tube): SSH connection through which to load the libraries. If left as :const:`None`, will use a :class:`pwnlib.tubes.process.process`. ulimit(bool): Set to :const:`True` to run "ulimit -s unlimited" before GDB. Returns: A list of pwnlib.elf.ELF objects, with correct base addresses. Example: >>> with context.local(log_level=9999): ... shell = ssh(host='example.pwnme', user='******', password='******') ... bash_libs = gdb.find_module_addresses('/bin/bash', shell) >>> os.path.basename(bash_libs[0].path) 'libc.so.6' >>> hex(bash_libs[0].symbols['system']) # doctest: +SKIP '0x7ffff7634660' """ # # Download all of the remote libraries # if ssh: runner = ssh.run local_bin = ssh.download_file(binary) local_elf = elf.ELF(os.path.basename(binary)) local_libs = ssh.libs(binary) else: runner = tubes.process.process local_elf = elf.ELF(binary) local_libs = local_elf.libs # # Get the addresses from GDB # libs = {} cmd = "gdb -q -nh --args %s | cat" % ( binary) # pipe through cat to disable colored output on GDB 9+ expr = re.compile(r'(0x\S+)[^/]+(.*)') if ulimit: cmd = ['sh', '-c', "(ulimit -s unlimited; %s)" % cmd] else: cmd = ['sh', '-c', cmd] with runner(cmd) as gdb: if context.aslr: gdb.sendline(b'set disable-randomization off') gdb.send(b"""\ set prompt catch load run """) gdb.sendline(b'info sharedlibrary') lines = packing._decode(gdb.recvrepeat(2)) for line in lines.splitlines(): m = expr.match(line) if m: libs[m.group(2)] = int(m.group(1), 16) gdb.sendline(b'kill') gdb.sendline(b'y') gdb.sendline(b'quit') # # Fix up all of the addresses against the .text address # rv = [] for remote_path, text_address in sorted(libs.items()): # Match up the local copy to the remote path try: path = next(p for p in local_libs.keys() if remote_path in p) except StopIteration: print("Skipping %r" % remote_path) continue # Load it lib = elf.ELF(path) # Find its text segment text = lib.get_section_by_name('.text') # Fix the address lib.address = text_address - text.header.sh_addr rv.append(lib) return rv
def wrapperS(self, *a, **kw): return packing._decode(func(self, *a, **kw))