Skip to content

Commit

Permalink
QEMU-related enhancements (#1052)
Browse files Browse the repository at this point in the history
* Add support for finding QEMU's LD_PREFIX

* Refactor pwnlib.qemu and add official documentation

* Add more documentation, support for sysroot

* More docs, binfmt info

* Fix variable name overload

* Fix missed refactor

* Check for empty/null env

* Add qemu.rst to index
  • Loading branch information
zachriggle authored Oct 13, 2017
1 parent b088f46 commit 412d345
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 22 deletions.
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Each of the ``pwntools`` modules is documented here.
log
memleak
protocols
qemu
replacements
rop
rop/*
Expand Down
10 changes: 10 additions & 0 deletions docs/source/qemu.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.. testsetup:: *

from pwn import *


:mod:`pwnlib.qemu` --- QEMU Utilities
==========================================

.. automodule:: pwnlib.qemu
:members:
16 changes: 11 additions & 5 deletions pwnlib/elf/elf.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import intervaltree

from pwnlib import adb
from pwnlib import qemu
from pwnlib.asm import *
from pwnlib.context import LocalContext
from pwnlib.context import context
Expand All @@ -68,7 +69,6 @@
from pwnlib.elf.datatypes import constants
from pwnlib.elf.plt import emulate_plt_instructions
from pwnlib.log import getLogger
from pwnlib.qemu import get_qemu_arch
from pwnlib.term import text
from pwnlib.tubes.process import process
from pwnlib.util import misc
Expand Down Expand Up @@ -261,6 +261,11 @@ def __init__(self, path, checksec=True):
self.arch = 'mips64'
self.bits = 64

# Is this a native binary? Should we be checking QEMU?
with context.local(arch=self.arch):
#: Whether this ELF should be able to run natively
self.native = context.native

self._address = 0
if self.elftype != 'DYN':
for seg in self.iter_segments_by_type('PT_LOAD'):
Expand Down Expand Up @@ -630,10 +635,11 @@ def _populate_libraries(self):
if os.path.exists(lib):
continue

qemu_lib = '/etc/qemu-binfmt/%s/%s' % (get_qemu_arch(arch=self.arch), lib)

if os.path.exists(qemu_lib):
libs[os.path.realpath(qemu_lib)] = libs.pop(lib)
if not self.native:
ld_prefix = qemu.ld_prefix()
qemu_lib = os.path.exists(os.path.join(ld_prefix, lib))
if qemu_lib:
libs[os.path.realpath(qemu_lib)] = libs.pop(lib)

self.libs = libs

Expand Down
13 changes: 9 additions & 4 deletions pwnlib/gdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,14 @@
from pwnlib import adb
from pwnlib import atexit
from pwnlib import elf
from pwnlib import qemu
from pwnlib import tubes
from pwnlib.asm import _bfdname
from pwnlib.asm import make_elf
from pwnlib.asm import make_elf_from_assembly
from pwnlib.context import LocalContext
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.qemu import get_qemu_user
from pwnlib.util import misc
from pwnlib.util import proc

Expand Down Expand Up @@ -365,6 +365,7 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):

runner = _get_runner(ssh)
which = _get_which(ssh)
sysroot = None

if context.noptrace:
log.warn_once("Skipping debugger since context.noptrace==True")
Expand All @@ -374,7 +375,8 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):
args = _gdbserver_args(args=args, which=which)
else:
qemu_port = random.randint(1024, 65535)
qemu_user = get_qemu_user()
qemu_user = qemu.user_path()
sysroot = qemu.ld_prefix(env)
if not qemu_user:
log.error("Cannot debug %s binaries without appropriate QEMU binaries" % context.arch)
args = [qemu_user, '-g', str(qemu_port)] + args
Expand Down Expand Up @@ -404,7 +406,7 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):
if not ssh and context.os == 'android':
host = context.adb_host

attach((host, port), exe=exe, gdbscript=gdbscript, need_ptrace_scope = False, ssh=ssh)
attach((host, port), exe=exe, gdbscript=gdbscript, need_ptrace_scope = False, ssh=ssh, sysroot=sysroot)

# gdbserver outputs a message when a client connects
garbage = gdbserver.recvline(timeout=1)
Expand Down Expand Up @@ -446,7 +448,7 @@ def binary():
return gdb

@LocalContext
def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_args = None, ssh = None):
def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_args = None, ssh = None, sysroot = None):
"""attach(target, gdbscript = None, exe = None, arch = None, ssh = None) -> None
Start GDB in a new terminal and attach to `target`.
Expand All @@ -458,6 +460,7 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
arch(str): Architechture of the target binary. If `exe` known GDB will
detect the architechture automatically (if it is supported).
gdb_args(list): List of additional arguments to pass to GDB.
sysroot(str): Foreign-architecture sysroot, used for QEMU-emulated binaries
Returns:
PID of the GDB process (or the window which it is running in).
Expand Down Expand Up @@ -560,6 +563,8 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
if not context.native:
pre += 'set endian %s\n' % context.endian
pre += 'set architecture %s\n' % get_gdb_arch()
if sysroot:
pre += 'set sysroot %s\n' % sysroot

if context.os == 'android':
pre += 'set gnutarget ' + _bfdname() + '\n'
Expand Down
119 changes: 112 additions & 7 deletions pwnlib/qemu.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,81 @@
"""Run foreign-architecture binaries
Overview
--------
So you want to exploit ARM binaries on your Intel PC?
Pwntools has a good level of integration with QEMU user-mode emulation,
in order to run, debug, and pwn foreign architecture binaries.
In general, everything magic happens "behind the scenes", and pwntools
attempts to make your life easier.
When using :class:`.process.process`, pwntools will attempt to blindly
execute the binary, in case your system is configured to use ``binfmt-misc``.
If this fails, pwntools will attempt to manually launch the binary under
qemu user-mode emulation. Preference is given to statically-linked variants,
i.e. ``qemu-arm-static`` will be selected before ``qemu-arm``.
Debugging
~~~~~~~~~
When debugging binaries with :func:`.gdb.debug`, pwntools automatically adds
the appropriate command-line flags to QEMU to start its GDB stub, and
automatically informs GDB of the correct architecture and sysroot.
Sysroot
~~~~~~~
You can override the default sysroot by setting the ``QEMU_LD_PREFIX``
environment variable. This affects where ``qemu`` will look for files when
``open()`` is called, e.g. when the linker is attempting to resolve ``libc.so``.
Required Setup
--------------
For Ubuntu 16.04 and newer, the setup is relatively straightforward for most
architectures.
First, install the QEMU emulator itself. If your binary is statically-linked,
thsi is sufficient.
$ sudo apt-get install qemu-user
If your binary is dynamically linked, you need to install libraries like libc.
Generally, this package is named ``libc6-$ARCH-cross``, e.g. ``libc-mips-cross``.
ARM comes in both soft-float and hard-float variants, e.g. ``armhf``.
$ sudo apt-get install libc6-arm64-cross
If your binary relies on additional libraries, you can generally find them
easily with ``apt-cache search``. For example, if it's a C++ binary it
may require ``libstdc++``.
$ apt-cache search 'libstdc++' | grep arm64
Any other libraries that you require you'll have to find some other way.
Telling QEMU Where Libraries Are
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The libraries are now installed on your system at e.g. ``/usr/aarch64-linux-gnu``.
QEMU does not know where they are, and expects them to be at e.g. ``/etc/qemu-binfmt/aarch64``.
If you try to run your library now, you'll probably see an error about ``libc.so.6`` missing.
Create the ``/etc/qemu-binfmt`` directory if it does not exist, and create a symlink to
the appropriate path.
$ sudo mkdir /etc/qemu-binfmt
$ sudo ln -s /usr/aarch64-linux-gnu /etc/qemu-binfmt/aarch64
Now QEMU should be able to run the libraries.
"""
from __future__ import absolute_import

import os

from pwnlib.context import LocalContext
from pwnlib.context import context
from pwnlib.log import getLogger
Expand All @@ -8,14 +84,14 @@
log = getLogger(__name__)

@LocalContext
def get_qemu_arch():
def archname():
"""
Returns the name which QEMU uses for the currently selected
architecture.
>>> get_qemu_arch()
>>> pwnlib.qemu.archname()
'i386'
>>> get_qemu_arch(arch='powerpc')
>>> pwnlib.qemu.archname(arch='powerpc')
'ppc'
"""
return {
Expand All @@ -31,17 +107,17 @@ def get_qemu_arch():
}.get((context.arch, context.endian), context.arch)

@LocalContext
def get_qemu_user():
def user_path():
"""
Returns the path to the QEMU-user binary for the currently
selected architecture.
>>> get_qemu_user()
>>> pwnlib.qemu.user_path()
'qemu-i386-static'
>>> get_qemu_user(arch='thumb')
>>> pwnlib.qemu.user_path(arch='thumb')
'qemu-arm-static'
"""
arch = get_qemu_arch()
arch = archname()
normal = 'qemu-' + arch
static = normal + '-static'

Expand All @@ -52,3 +128,32 @@ def get_qemu_user():
return normal

log.warn_once("Neither %r nor %r are available" % (normal, static))

@LocalContext
def ld_prefix(path=None, env=None):
"""Returns the linker prefix for the selected qemu-user binary
>>> pwnlib.qemu.ld_prefix(arch='arm')
'/etc/qemu-binfmt/arm'
"""
if path is None:
path = user_path()

# Did we explicitly specify the path in an environment variable?
if env and 'QEMU_LD_PREFIX' in env:
return env['QEMU_LD_PREFIX']

if 'QEMU_LD_PREFIX' in os.environ:
return os.environ['QEMU_LD_PREFIX']

# Cyclic imports!
from pwnlib.tubes.process import process

with context.quiet:
with process([path, '--help'], env=env) as io:
line = io.recvline_regex('QEMU_LD_PREFIX *=')

name, libpath = line.split('=', 1)

return libpath.strip()

12 changes: 6 additions & 6 deletions pwnlib/tubes/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
import time
import tty

from pwnlib import qemu
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.qemu import get_qemu_user
from pwnlib.timeout import Timeout
from pwnlib.tubes.tube import tube
from pwnlib.util.hashes import sha256file
Expand Down Expand Up @@ -432,21 +432,21 @@ def __on_enoexec(self, exception):

# Determine what architecture the binary is, and find the
# appropriate qemu binary to run it.
qemu = get_qemu_user(arch=binary.arch)
qemu_path = qemu.user_path(arch=binary.arch)

if not qemu:
raise exception

qemu = which(qemu)
qemu_path = which(qemu_path)
if qemu:
self._qemu = qemu
self._qemu = qemu_path

args = [qemu]
args = [qemu_path]
if self.argv:
args += ['-0', self.argv[0]]
args += ['--']

return [args, qemu]
return [args, qemu_path]

# If we get here, we couldn't run the binary directly, and
# we don't have a qemu which can run it.
Expand Down

0 comments on commit 412d345

Please sign in to comment.