Permalink
Browse files

Add PR_SET_PTRACER to process and ssh.process (#828)

* Add PR_SET_PTRACER to initialization, and add doctests for process.leak/corefile

* Add PR_SET_PTRACER to SSH processes

* Do not warn about YAMA if we dont need to

* Fix doctest to use raw strings

* Fix typo in doctest

* Apparently GDB is not installed on Travis by default

* Add warning message to process("foo") vs process("./foo")

* Add gdb.corefile; make run_in_new_terminal return a PID

* Intentionally break doctest because I dont believe that it worked

* Fix codacy issues

* Fix gdb.attach return value

* Add missing import

* Fix typo, use waitpid

* Refactor some code into gdb.version / gdb.binary

* Permit GDB with multipart versions

* Fix repr() on Corefile

* Silence run-in-new-terminal message for gdb.corefile

* Fix version number

* Remove Yama warning message entirely, it should no longer be necessary

* Version detection is hard.  Regular expressions to the rescue!

* Remove process.kill/stop/resume, those will be a different PR

* Fix doctests

* The GDB used by Travis must be so old!

* Apparently Travis is entirely borked.  Use a less robust test.
  • Loading branch information...
1 parent 5ce5339 commit cca97824249109affde3ced9527045fd5af0fe73 @zachriggle zachriggle committed on GitHub Jan 4, 2017
Showing with 187 additions and 58 deletions.
  1. +1 −0 .travis.yml
  2. +4 −0 docs/source/gdb.rst
  3. +1 −1 pwnlib/elf/elf.py
  4. +103 −34 pwnlib/gdb.py
  5. +43 −8 pwnlib/tubes/process.py
  6. +7 −0 pwnlib/tubes/ssh.py
  7. +28 −15 pwnlib/util/misc.py
View
@@ -13,6 +13,7 @@ addons:
- mksh
- zsh
- pandoc
+ - gdb
cache:
- pip
- directories:
View
@@ -1,3 +1,7 @@
+.. testsetup:: *
+
+ from pwn import *
+
:mod:`pwnlib.gdb` --- Working with GDB
======================================
View
@@ -708,7 +708,7 @@ def bss(self, offset=0):
return curr_bss + offset
def __repr__(self):
- return "ELF(%r)" % self.path
+ return "%s(%r)" % (self.__class__.__name__, self.path)
def dynamic_by_tag(self, tag):
dt = None
View
@@ -5,6 +5,7 @@
import re
import shlex
import tempfile
+import time
from pwnlib import adb
from pwnlib import atexit
@@ -247,9 +248,25 @@ def get_gdb_arch():
'thumb': 'arm'
}.get(context.arch, context.arch)
+def binary():
+ gdb = misc.which('gdb')
+
+ if not context.native:
+ multiarch = misc.which('gdb-multiarch')
+
+ if multiarch:
+ return multiarch
+ log.warn_once('Cross-architecture debugging usually requires gdb-multiarch\n' \
+ '$ apt-get install gdb-multiarch')
+
+ if not gdb:
+ log.error('GDB is not installed\n'
+ '$ apt-get install gdb')
+
+ return gdb
@LocalContext
-def attach(target, execute = None, exe = None, need_ptrace_scope = True):
+def attach(target, execute = None, exe = None, need_ptrace_scope = True, gdb_args = None):
"""attach(target, execute = None, exe = None, arch = None) -> None
Start GDB in a new terminal and attach to `target`.
@@ -266,15 +283,16 @@ def attach(target, execute = None, exe = None, need_ptrace_scope = True):
If `gdb-multiarch` is installed we use that or 'gdb' otherwise.
Arguments:
- target: The target to attach to.
- execute (str or file): GDB script to run after attaching.
- exe (str): The path of the target binary.
- arch (str): Architechture of the target binary. If `exe` known GDB will
- detect the architechture automatically (if it is supported).
+ target: The target to attach to.
+ execute (str or file): GDB script to run after attaching.
+ exe(str): The path of the target binary.
+ 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 arguments to pass to GDB.
Returns:
- :const:`None`
-"""
+ PID of the GDB process, or the window which it is running in.
+ """
if context.noptrace:
log.warn_once("Skipping debug attach since context.noptrace==True")
return
@@ -294,28 +312,11 @@ def attach(target, execute = None, exe = None, need_ptrace_scope = True):
# gdb script to run before `execute`
pre = ''
if not context.native:
- if not misc.which('gdb-multiarch'):
- log.warn_once('Cross-architecture debugging usually requires gdb-multiarch\n' \
- '$ apt-get install gdb-multiarch')
pre += 'set endian %s\n' % context.endian
pre += 'set architecture %s\n' % get_gdb_arch()
if context.os == 'android':
pre += 'set gnutarget ' + _bfdname() + '\n'
- else:
- # If ptrace_scope is set and we're not root, we cannot attach to a
- # running process.
- # We assume that we do not need this to be set if we are debugging on
- # a different architecture (e.g. under qemu-user).
- try:
- ptrace_scope = open('/proc/sys/kernel/yama/ptrace_scope').read().strip()
- if need_ptrace_scope and ptrace_scope != '0' and os.geteuid() != 0:
- msg = 'Disable ptrace_scope to attach to running processes.\n'
- msg += 'More info: https://askubuntu.com/q/41629'
- log.warning(msg)
- return
- except IOError:
- pass
# let's see if we can find a pid to attach to
pid = None
@@ -392,13 +393,11 @@ def findexe():
if not pid and not exe:
log.error('could not find target process')
- cmd = None
- for p in ('gdb-multiarch', 'gdb'):
- if misc.which(p):
- cmd = p
- break
- else:
- log.error('no gdb installed')
+ cmd = binary()
+
+ if gdb_args:
+ cmd += ' '
+ cmd += ' '.join(gdb_args)
cmd += ' -q '
@@ -432,10 +431,13 @@ def findexe():
cmd += ' -x "%s"' % (tmp.name)
log.info('running in new terminal: %s' % cmd)
- misc.run_in_new_terminal(cmd)
+
+ gdb_pid = misc.run_in_new_terminal(cmd)
+
if pid and context.native:
proc.wait_for_debugger(pid)
- return pid
+
+ return gdb_pid
def ssh_gdb(ssh, process, execute = None, arch = None, **kwargs):
if isinstance(process, (list, tuple)):
@@ -591,3 +593,70 @@ def find_module_addresses(binary, ssh=None, ulimit=False):
rv.append(lib)
return rv
+
+def corefile(process):
+ r"""Drops a core file for the process.
+
+ Arguments:
+ process: Process to dump
+
+ Returns:
+ A ``pwnlib.elf.corefile.Core`` object.
+ """
+
+ if context.noptrace:
+ log.warn_once("Skipping corefile since context.noptrace==True")
+ return
+
+ temp = tempfile.NamedTemporaryFile(prefix='pwn-corefile-')
+
+ # Due to https://sourceware.org/bugzilla/show_bug.cgi?id=16092
+ # will disregard coredump_filter, and will not dump private mappings.
+ if version() < (7,11):
+ log.warn_once('The installed GDB (%s) does not emit core-dumps which '
+ 'contain all of the data in the process.\n'
+ 'Upgrade to GDB >= 7.11 for better core-dumps.' % binary())
+
+ # This is effectively the same as what the 'gcore' binary does
+ gdb_args = ['-batch',
+ '-q',
+ '--nx',
+ '-ex', '"set pagination off"',
+ '-ex', '"set height 0"',
+ '-ex', '"set width 0"',
+ '-ex', '"set use-coredump-filter on"',
+ '-ex', '"generate-core-file %s"' % temp.name,
+ '-ex', 'detach']
+
+ with context.local(terminal = ['sh', '-c']):
+ with context.quiet:
+ pid = attach(process, gdb_args=gdb_args)
+ os.waitpid(pid, 0)
+
+ return elf.corefile.Core(temp.name)
+
+def version(program='gdb'):
+ """Gets the current GDB version.
+
+ Note:
+ Requires that GDB version meets the following format:
+
+ ``GNU gdb (GDB) 7.12``
+
+ Returns:
+ A tuple
+
+ Example:
+
+ >>> (7,0) <= gdb.version() <= (8,0)
+ True
+ """
+ program = misc.which(program)
+ expr = r'([0-9]+\.?)+'
+
+ with tubes.process.process([program, '--version'], level='error') as gdb:
+ version = gdb.recvline()
+
+ versions = re.search(expr, version).group()
+
+ return tuple(map(int, versions.split('.')))
@@ -278,6 +278,7 @@ def __init__(self, argv = None,
self.preexec_fn = preexec_fn
self.display = display or self.program
+ self.__qemu = False
message = "Starting %s process %r" % (where, self.display)
@@ -350,7 +351,7 @@ def __preexec_fn(self):
ctypes.CDLL('libc.so.6').personality(ADDR_NO_RANDOMIZE)
resource.setrlimit(resource.RLIMIT_STACK, (-1, -1))
- except:
+ except Exception:
self.exception("Could not disable ASLR")
# Assume that the user would prefer to have core dumps.
@@ -367,9 +368,18 @@ def __preexec_fn(self):
try:
PR_SET_NO_NEW_PRIVS = 38
ctypes.CDLL('libc.so.6').prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)
- except:
+ except Exception:
pass
+ # Avoid issues with attaching to processes when yama-ptrace is set
+ try:
+ PR_SET_PTRACER = 0x59616d61
+ PR_SET_PTRACER_ANY = -1
+ ctypes.CDLL('libc.so.6').prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0)
+ except Exception:
+ pass
+
+
if self.alarm is not None:
signal.alarm(self.alarm)
@@ -400,6 +410,7 @@ def __on_enoexec(self, exception):
if self.argv:
args += ['-0', self.argv[0]]
args += ['--']
+ self.__qemu = True
return [args, qemu]
# If we get here, we couldn't run the binary directly, and
@@ -466,7 +477,9 @@ def _validate(self, cwd, executable, argv, env):
# Either there is a path component, or the binary is not in $PATH
# For example, 'foo/bar' or 'bar' with cwd=='foo'
elif os.path.sep not in executable:
+ tmp = executable
executable = os.path.join(cwd, executable)
+ log.warn_once("Could not find executable %r in $PATH, using %r instead" % (tmp, executable))
if not os.path.exists(executable):
self.error("%r does not exist" % executable)
@@ -543,7 +556,6 @@ def kill(self):
Kills the process.
"""
-
self.close()
def poll(self, block = False):
@@ -787,19 +799,42 @@ def libc(self):
return e
@property
+ def elf(self):
+ """elf() -> pwnlib.elf.elf.ELF
+
+ Returns an ELF file for the executable that launched the process.
+ """
+ import pwnlib.elf
+ return pwnlib.elf.elf.ELF(self.executable)
+
+ @property
def corefile(self):
- filename = 'core.%i' % (self.pid)
- process(['gcore', '-o', 'core', str(self.pid)]).wait()
+ """corefile() -> pwnlib.elf.elf.Core
- import pwnlib.elf.corefile
- return pwnlib.elf.corefile.Core(filename)
+ Returns a corefile for the process.
+
+ Example:
+
+ >>> proc = process('bash')
+ >>> isinstance(proc.corefile, pwnlib.elf.corefile.Core)
+ True
+ """
+ import pwnlib.gdb
+ return pwnlib.gdb.corefile(self)
def leak(self, address, count=1):
- """Leaks memory within the process at the specified address.
+ r"""Leaks memory within the process at the specified address.
Arguments:
address(int): Address to leak memory at
count(int): Number of bytes to leak at that address.
+
+ Example:
+
+ >>> e = ELF('/bin/sh')
+ >>> p = process(e.path)
+ >>> p.leak(e.address, 4)
+ '\x7fELF'
"""
# If it's running under qemu-user, don't leak anything.
if 'qemu-' in os.path.realpath('/proc/%i/exe' % self.pid):
View
@@ -852,6 +852,13 @@ def is_exe(path):
sys.stdout.write("Could not disable setuid: prctl(PR_SET_NO_NEW_PRIVS) failed")
sys.exit(-1)
+try:
+ PR_SET_PTRACER = 0x59616d61
+ PR_SET_PTRACER_ANY = -1
+ ctypes.CDLL('libc.so.6').prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0)
+except Exception:
+ pass
+
if sys.argv[-1] == 'check':
sys.stdout.write("1\n")
sys.stdout.write(str(os.getpid()) + "\n")
Oops, something went wrong.

0 comments on commit cca9782

Please sign in to comment.