Skip to content

Commit

Permalink
Add PR_SET_PTRACER to process and ssh.process (#828)
Browse files Browse the repository at this point in the history
* 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
zachriggle committed Jan 4, 2017
1 parent 5ce5339 commit cca9782
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 58 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -13,6 +13,7 @@ addons:
- mksh
- zsh
- pandoc
- gdb
cache:
- pip
- directories:
Expand Down
4 changes: 4 additions & 0 deletions docs/source/gdb.rst
@@ -1,3 +1,7 @@
.. testsetup:: *

from pwn import *

:mod:`pwnlib.gdb` --- Working with GDB
======================================

Expand Down
2 changes: 1 addition & 1 deletion pwnlib/elf/elf.py
Expand Up @@ -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
Expand Down
137 changes: 103 additions & 34 deletions pwnlib/gdb.py
Expand Up @@ -5,6 +5,7 @@
import re
import shlex
import tempfile
import time

from pwnlib import adb
from pwnlib import atexit
Expand Down Expand Up @@ -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`.
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 '

Expand Down Expand Up @@ -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)):
Expand Down Expand Up @@ -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('.')))
51 changes: 43 additions & 8 deletions pwnlib/tubes/process.py
Expand Up @@ -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)

Expand Down Expand Up @@ -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.
Expand All @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -543,7 +556,6 @@ def kill(self):
Kills the process.
"""

self.close()

def poll(self, block = False):
Expand Down Expand Up @@ -786,20 +798,43 @@ def libc(self):
e.address = address
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):
Expand Down
7 changes: 7 additions & 0 deletions pwnlib/tubes/ssh.py
Expand Up @@ -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")
Expand Down

0 comments on commit cca9782

Please sign in to comment.