Commit f86d40fb authored by Luke Macken's avatar Luke Macken

Merge branch 'release/2.0beta9'

parents 3beb3c96 9cc79978
...@@ -3,8 +3,7 @@ pyrasite ...@@ -3,8 +3,7 @@ pyrasite
.. split here .. split here
Pyrasite lets you to inject arbitrary code into an unaltered running Python Pyrasite lets you to inject arbitrary code into running Python processes.
process.
:documentation: http://pyrasite.com :documentation: http://pyrasite.com
:download: http://pypi.python.org/pypi/pyrasite :download: http://pypi.python.org/pypi/pyrasite
...@@ -14,19 +13,31 @@ process. ...@@ -14,19 +13,31 @@ process.
:jenkins: http://ci.csh.rit.edu/view/Pyrasite :jenkins: http://ci.csh.rit.edu/view/Pyrasite
:irc: #pyrasite on Freenode :irc: #pyrasite on Freenode
Requirements
~~~~~~~~~~~~
* `gdb <https://www.gnu.org/s/gdb>`_ (version 7.3+)
Compatiblity
~~~~~~~~~~~~
Pyrasite works with Python 2.4 and newer. Injection works between versions
as well, so you can run Pyrasite under Python 3 and inject into 2, and
vice versa.
pyrasite-gui pyrasite-gui
~~~~~~~~~~~~ ~~~~~~~~~~~~
The gui has been moved into it's own repository: https://github.com/lmacken/pyrasite-gui The graphical interface can be found here: https://github.com/lmacken/pyrasite-gui
.. image:: http://lewk.org/img/pyrasite/pyrasite-info-thumb.png .. image:: http://lewk.org/img/pyrasite/pyrasite-info-thumb.png
Authors Authors
~~~~~~~ ~~~~~~~
Luke Macken (`@lmacken <http://twitter.com/lmacken>`_) `Luke Macken <http://twitter.com/lmacken>`_
.. image:: http://api.coderwall.com/lmacken/endorsecount.png .. image:: http://api.coderwall.com/lmacken/endorsecount.png
:target: http://coderwall.com/lmacken :target: http://coderwall.com/lmacken
David Malcolm (dmalcolm ~ redhat ~com) David Malcolm <dmalcolm@redhat.com>
pyrasite - A command-line interface for injecting code into running Python processes pyrasite - A command-line interface for injecting code into a running Python process
==================================================================================== ====================================================================================
:: ::
...@@ -19,28 +19,4 @@ pyrasite - A command-line interface for injecting code into running Python proce ...@@ -19,28 +19,4 @@ pyrasite - A command-line interface for injecting code into running Python proce
For updates, visit https://github.com/lmacken/pyrasite For updates, visit https://github.com/lmacken/pyrasite
pyrasite-shell
--------------
You can easily open up a shell and execute commands in a running process using
the `pyrasite-shell`.
.. code-block:: bash
$ pyrasite-shell
Usage: pyrasite-shell <PID>
.. code-block:: bash
$ pyrasite-shell $(pgrep -f "python -v")
Pyrasite Shell 2.0beta7
Python 2.7.2 (default, Oct 27 2011, 01:40:22)
[GCC 4.6.1 20111003 (Red Hat 4.6.1-10)] on linux2
>>> print(x)
foo
>>> globals()['x'] = 'bar'
.. seealso:: :doc:`Payloads` .. seealso:: :doc:`Payloads`
...@@ -15,39 +15,28 @@ GUI ...@@ -15,39 +15,28 @@ GUI
- `Pyrasite <https://github.com/lmacken/pyrasite>`_ - `Pyrasite <https://github.com/lmacken/pyrasite>`_
- Python debuginfo (needed for live object inspection) - Python debuginfo (needed for live object inspection)
- Fedora: python-debuginfo - Fedora: python-debuginfo, Ubuntu: python-dbg
- Ubuntu: python-dbg
- PyGObject3 Introspection bindings - PyGObject3 Introspection bindings
- Fedora: pygobject3 - Fedora: pygobject3, Ubuntu: python-gobject-dev, Arch: python2-gobject
- Ubuntu: python-gobject-dev
- Arch: python2-gobject
- WebKitGTK3 - WebKitGTK3
- Fedora: webkitgtk3 - Fedora: webkitgtk3, Ubuntu: gir1.2-webkit-3.0, Arch: libwebkit3
- Ubuntu: gir1.2-webkit-3.0
- Arch: libwebkit3
- `meliae <https://launchpad.net/meliae>`_ - `meliae <https://launchpad.net/meliae>`_
- easy_install/pip may not work for this install. If not, use the tarball from the distribution website. You may need to install `Cython <http://cython.org>`_ in order to get meliae to build. - easy_install/pip may not work for this install. If not, use the tarball from the distribution website. You may need to install `Cython <http://cython.org>`_ in order to get meliae to build.
- Fedora: python-meliae - Fedora: python-meliae, Ubuntu: python-meliae, Arch: python2-meliae
- Ubuntu: python-meliae
- Arch: python2-meliae
- `pycallgraph <http://pycallgraph.slowchop.com>`_ - `pycallgraph <http://pycallgraph.slowchop.com>`_
- Fedora: python-pycallgraph - Fedora: python-pycallgraph, Ubuntu: python-pycallgraph, Arch: python2-pycallgraph
- Ubuntu: python-pycallgraph
- Arch: python2-pycallgraph
- `psutil <http://code.google.com/p/psutil>`_ - `psutil <http://code.google.com/p/psutil>`_
- Fedora: python-psutil - Fedora: python-psutil, Ubuntu: python-psutil, Arch: python2-psutil
- Ubuntu: python-psutil
- Arch: python2-psutil
Download Download
~~~~~~~~ ~~~~~~~~
......
...@@ -4,6 +4,9 @@ Payloads ...@@ -4,6 +4,9 @@ Payloads
Reverse Python Shell Reverse Python Shell
-------------------- --------------------
.. deprecated:: 2.0
Use the `pyrasite-shell <http://readthedocs.org/docs/pyrasite/en/latest/Shell.html>`_ instead
This lets you easily introspect or alter any objects in your running process. This lets you easily introspect or alter any objects in your running process.
.. literalinclude:: ../pyrasite/payloads/reverse_python_shell.py .. literalinclude:: ../pyrasite/payloads/reverse_python_shell.py
...@@ -62,8 +65,8 @@ installed. ...@@ -62,8 +65,8 @@ installed.
.. image:: http://lewk.org/img/pyrasite-memory-viewer.png .. image:: http://lewk.org/img/pyrasite-memory-viewer.png
Reverse Shell Reverse Subprocess Shell
------------- ------------------------
.. literalinclude:: ../pyrasite/payloads/reverse_shell.py .. literalinclude:: ../pyrasite/payloads/reverse_shell.py
:language: python :language: python
......
pyrasite-shell - Give it a pid, get a shell
===========================================
You can easily drop into a shell and execute commands in a running process
using the ``pyrasite-shell``.
.. code-block:: bash
$ pyrasite-shell
Usage: pyrasite-shell <PID>
.. code-block:: bash
$ pyrasite-shell $(pgrep -f "python -v")
pyrasite shell 2.0beta7
Python 2.7.2 (default, Oct 27 2011, 01:40:22)
[GCC 4.6.1 20111003 (Red Hat 4.6.1-10)] on linux2
>>> print(x)
foo
>>> globals()['x'] = 'bar'
Source
------
.. literalinclude:: ../pyrasite/tools/shell.py
:language: python
:start-after: Copyright
...@@ -14,10 +14,11 @@ Contents ...@@ -14,10 +14,11 @@ Contents
--------- ---------
.. toctree:: .. toctree::
:maxdepth: 3 :maxdepth: 1
Installing Installing
GUI
CLI CLI
Shell
GUI
Payloads Payloads
API API
%global betaver beta8 %global betaver beta9
Name: pyrasite Name: pyrasite
Version: 2.0 Version: 2.0
...@@ -39,6 +39,7 @@ also comes with a variety of example payloads. ...@@ -39,6 +39,7 @@ also comes with a variety of example payloads.
%doc README.rst %doc README.rst
%{_bindir}/pyrasite %{_bindir}/pyrasite
%{_bindir}/pyrasite-memory-viewer %{_bindir}/pyrasite-memory-viewer
%{_bindir}/pyrasite-shell
%{python_sitelib}/* %{python_sitelib}/*
%changelog %changelog
......
#!/bin/bash -x
# gem install fpm
fpm \
-t deb \
-s python \
--python-install-lib /usr/lib/python2.7/dist-packages \
--no-python-fix-name \
--architecture all \
--url http://pyrasite.com \
--license GPLv3 \
--maintainer "Luke Macken <lmacken@redhat.com>" \
--description "Pyrasite lets you inject arbitrary code into running Python processes" \
--depends gdb \
.
__version__ = '2.0beta8' __version__ = '2.0beta9'
__all__ = ('inject', 'inspect', 'PyrasiteIPC', __all__ = ('inject', 'inspect', 'PyrasiteIPC',
'ReverseConnection', 'ReversePythonConnection') 'ReverseConnection', 'ReversePythonConnection')
__license__ = """\ __license__ = """\
......
...@@ -63,6 +63,9 @@ class PyrasiteIPC(object): ...@@ -63,6 +63,9 @@ class PyrasiteIPC(object):
super(PyrasiteIPC, self).__init__() super(PyrasiteIPC, self).__init__()
self.pid = pid self.pid = pid
self.sock = None self.sock = None
self.server_sock = None
self.hostname = None
self.port = None
def connect(self): def connect(self):
""" """
...@@ -80,20 +83,23 @@ class PyrasiteIPC(object): ...@@ -80,20 +83,23 @@ class PyrasiteIPC(object):
af, socktype, proto, canonname, sa = res af, socktype, proto, canonname, sa = res
try: try:
self.server_sock = socket.socket(af, socktype, proto) self.server_sock = socket.socket(af, socktype, proto)
try:
self.server_sock.bind(sa)
self.server_sock.listen(1)
except socket.error:
self.server_sock.close()
self.server_sock = None
continue
except socket.error: except socket.error:
self.server_sock = None self.server_sock = None
continue continue
try:
self.server_sock.bind(sa)
self.server_sock.listen(1)
except socket.error:
self.server_sock.close()
self.server_sock = None
continue
break break
self.hostname, self.port = self.server_sock.getsockname()[0:2] if not self.server_sock:
self.running = True raise Exception('pyrasite was unable to setup a ' +
'local server socket')
else:
self.hostname, self.port = self.server_sock.getsockname()[0:2]
def create_payload(self): def create_payload(self):
"""Write out a reverse python connection payload with a custom port""" """Write out a reverse python connection payload with a custom port"""
...@@ -106,7 +112,8 @@ class PyrasiteIPC(object): ...@@ -106,7 +112,8 @@ class PyrasiteIPC(object):
if line.startswith('#'): if line.startswith('#'):
continue continue
line = line.replace('port = 9001', 'port = %d' % self.port) line = line.replace('port = 9001', 'port = %d' % self.port)
line = line.replace('reliable = False', 'reliable = True') if not self.reliable:
line = line.replace('reliable = True', 'reliable = False')
tmp.write(line) tmp.write(line)
tmp.write('ReversePythonConnection().start()\n') tmp.write('ReversePythonConnection().start()\n')
...@@ -153,7 +160,7 @@ class PyrasiteIPC(object): ...@@ -153,7 +160,7 @@ class PyrasiteIPC(object):
if len(data) == msg_len: if len(data) == msg_len:
return data return data
else: else:
return self.sock.recv(4096) return self.sock.recv(4096).decode('utf-8')
def recv_bytes(self, n): def recv_bytes(self, n):
"""Receive n bytes from a socket""" """Receive n bytes from a socket"""
...@@ -168,9 +175,8 @@ class PyrasiteIPC(object): ...@@ -168,9 +175,8 @@ class PyrasiteIPC(object):
def close(self): def close(self):
if self.sock: if self.sock:
self.sock.close() self.sock.close()
if getattr(self, 'server_sock', None):
self.server_sock.close()
def __repr__(self): def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, self.pid) return "<%s %s>" % (self.__class__.__name__, self.pid)
def __str__(self):
return self.title
...@@ -18,15 +18,41 @@ ...@@ -18,15 +18,41 @@
import os import os
import sys import sys
import argparse import argparse
import subprocess
import pyrasite import pyrasite
def ptrace_check():
ptrace_scope = '/proc/sys/kernel/yama/ptrace_scope'
if os.path.exists(ptrace_scope):
f = open(ptrace_scope)
value = int(f.read().strip())
f.close()
if value == 1:
print("WARNING: ptrace is disabled. Injection will not work.")
print("You can enable it by running the following:")
print("echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope")
print("")
else:
getsebool = '/usr/sbin/getsebool'
if os.path.exists(getsebool):
p = subprocess.Popen([getsebool, 'deny_ptrace'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
if out.decode('utf-8') == u'deny_ptrace --> on\n':
print("WARNING: ptrace is disabled. Injection will not work.")
print("You can enable it by running the following:")
print("sudo setsebool -P deny_ptrace=off")
print("")
def main(): def main():
ptrace_check()
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='pyrasite - inject code into a running python process', description='pyrasite - inject code into a running python process',
epilog="For updates, visit https://github.com/lmacken/pyrasite" epilog="For updates, visit https://github.com/lmacken/pyrasite")
)
parser.add_argument('pid', parser.add_argument('pid',
help="The ID of the process to inject code into") help="The ID of the process to inject code into")
parser.add_argument('filename', parser.add_argument('filename',
......
...@@ -37,6 +37,7 @@ class ReverseConnection(threading.Thread, pyrasite.PyrasiteIPC): ...@@ -37,6 +37,7 @@ class ReverseConnection(threading.Thread, pyrasite.PyrasiteIPC):
host = 'localhost' host = 'localhost'
port = 9001 port = 9001
reliable = True
def __init__(self, host=None, port=None): def __init__(self, host=None, port=None):
super(ReverseConnection, self).__init__() super(ReverseConnection, self).__init__()
...@@ -61,19 +62,20 @@ class ReverseConnection(threading.Thread, pyrasite.PyrasiteIPC): ...@@ -61,19 +62,20 @@ class ReverseConnection(threading.Thread, pyrasite.PyrasiteIPC):
af, socktype, proto, canonname, sa = res af, socktype, proto, canonname, sa = res
try: try:
self.sock = socket.socket(af, socktype, proto) self.sock = socket.socket(af, socktype, proto)
try:
self.sock.connect(sa)
except socket.error:
self.sock.close()
self.sock = None
continue
except socket.error: except socket.error:
self.sock = None self.sock = None
continue continue
try:
self.sock.connect(sa)
except socket.error:
self.sock.close()
self.sock = None
continue
break break
if not self.sock: if not self.sock:
raise Exception('pyrasite cannot establish reverse connection to %s:%d' % (self.host, self.port)) raise Exception('pyrasite cannot establish reverse ' +
'connection to %s:%d' % (self.host, self.port))
self.on_connect() self.on_connect()
......
# This file is part of pyrasite.
#
# pyrasite is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyrasite is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyrasite. If not, see <http://www.gnu.org/licenses/>.
#
# Copyright (C) 2011, 2012 Red Hat, Inc., Luke Macken <lmacken@redhat.com>
import os
import sys
import unittest
import pyrasite
from pyrasite.tests.utils import generate_program, run_program, stop_program, \
interpreters
class TestCodeInjection(unittest.TestCase):
def assert_output_contains(self, stdout, stderr, text):
assert text in str(stdout), \
"Code injection failed: %s\n%s" % (stdout, stderr)
def test_injecting_into_all_interpreters(self):
program = generate_program()
try:
for exe in interpreters():
print("sys.executable = %s" % sys.executable)
print("injecting into %s" % exe)
p = run_program(program, exe=exe)
pyrasite.inject(p.pid,
'pyrasite/payloads/helloworld.py', verbose=True)
stop_program(p)
stdout, stderr = p.communicate()
self.assert_output_contains(stdout, stderr, 'Hello World!')
finally:
os.unlink(program)
def test_many_payloads_into_program_with_many_threads(self):
program = generate_program(threads=50)
num_payloads = 50
try:
for exe in interpreters():
p = run_program(program, exe=exe)
for i in range(num_payloads):
pyrasite.inject(p.pid,
'pyrasite/payloads/helloworld.py', verbose=True)
stop_program(p)
stdout, stderr = p.communicate()
count = 0
for line in stdout.decode('utf-8').split('\n'):
if line.strip() == 'Hello World!':
count += 1
assert count == num_payloads, "Read %d hello worlds" % count
finally:
os.unlink(program)
if __name__ == '__main__':
unittest.main()
...@@ -15,39 +15,57 @@ ...@@ -15,39 +15,57 @@
# #
# Copyright (C) 2011, 2012 Red Hat, Inc. # Copyright (C) 2011, 2012 Red Hat, Inc.
import os
import unittest import unittest
import subprocess
import pyrasite import pyrasite
from pyrasite.tests.utils import run_program, generate_program, stop_program
class TestCodeInjection(unittest.TestCase): class TestIPC(unittest.TestCase):
def test_injection(self): def setUp(self):
cmd = 'python -c "import time; time.sleep(0.5)"' self.prog = generate_program()
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, self.p = run_program(self.prog)
stderr=subprocess.PIPE) self.ipc = pyrasite.PyrasiteIPC(self.p.pid)
pyrasite.inject(p.pid, 'pyrasite/payloads/helloworld.py', verbose=True) def tearDown(self):
stop_program(self.p)
self.ipc.close()
stdout, stderr = p.communicate() def test_listen(self):
assert 'Hello World!' in stdout.decode('utf-8'), \ self.ipc.listen()
"Code injection failed" assert self.ipc.server_sock
assert self.ipc.hostname
assert self.ipc.port
assert self.ipc.server_sock.getsockname()[1] == self.ipc.port
def test_multithreaded_injection(self): def test_create_payload(self):
cmd = [ self.ipc.listen()
'import time, threading', payload = self.ipc.create_payload()
'snooze = lambda: time.sleep(0.5)', assert os.path.exists(payload)
'threading.Thread(target=snooze).start()', code = open(payload)
] compile(code.read(), payload, 'exec')
p = subprocess.Popen('python -c "%s"' % ';'.join(cmd), shell=True, code.close()
stdout=subprocess.PIPE, stderr=subprocess.PIPE) os.unlink(payload)
pyrasite.inject(p.pid, 'pyrasite/payloads/helloworld.py', verbose=True) def test_connect(self):
self.ipc.connect()
assert self.ipc.sock
assert self.ipc.address
stdout, stderr = p.communicate() def test_cmd(self):
assert 'Hello World!' in stdout.decode('utf-8'), \ self.ipc.connect()
"Multi-threaded code injection failed" assert self.ipc.cmd('print("mu")') == 'mu\n'
def test_unreliable(self):
self.ipc.reliable = False
self.ipc.connect()
out = self.ipc.cmd('print("mu")')
assert out == 'mu\n', out
def test_repr(self):
assert repr(self.ipc)
if __name__ == '__main__': if __name__ == '__main__':
......
# This file is part of pyrasite.
#
# pyrasite is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyrasite is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyrasite. If not, see <http://www.gnu.org/licenses/>.
#
# Copyright (C) 2011-2012 Red Hat, Inc., Luke Macken <lmacken@redhat.com>
import os
import glob
import time
import textwrap
import tempfile
import subprocess
def generate_program(threads=1):
(fd, filename) = tempfile.mkstemp()
tmp = os.fdopen(fd, 'w')
script = textwrap.dedent("""
import os, time, threading
running = True
pidfile = '/tmp/pyrasite_%d' % os.getpid()
def cpu_bound():
i = 2
y = 0
def fib(n):
return fib(n - 1) + fib(n - 2)
while running:
y += fib(i)
i += 1
while os.path.exists(pidfile):
time.sleep(0.1)
""")
# CPU-bound threads
for t in range(threads):
script += "threading.Thread(target=cpu_bound).start()\n"
script += "open(pidfile, 'w').close()\n"
tmp.write(script)
tmp.close()
return filename
def run_program(program, exe='/usr/bin/python'):
p = subprocess.Popen([exe, program],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
flag = '/tmp/pyrasite_%d' % p.pid
i = 0
while not os.path.exists(flag):
time.sleep(0.1)
i += 1
if i > 100:
raise Exception("Program never touched pid file!")
return p
def stop_program(p):
os.unlink('/tmp/pyrasite_%d' % p.pid)
def interpreters():
for exe in glob.glob('/usr/bin/python*.*'):
try:
int(exe.split('.')[-1])
except ValueError:
continue # skip python2.7-config, etc
yield exe
...@@ -26,8 +26,7 @@ def shell(): ...@@ -26,8 +26,7 @@ def shell():
print("Usage: pyrasite-shell <PID>") print("Usage: pyrasite-shell <PID>")
sys.exit(1) sys.exit(1)
pid = int(sys.argv[1]) ipc = pyrasite.PyrasiteIPC(int(sys.argv[1]))
ipc = pyrasite.PyrasiteIPC(pid)
ipc.connect() ipc.connect()
print("pyrasite shell %s" % pyrasite.__version__) print("pyrasite shell %s" % pyrasite.__version__)
......
from setuptools import setup, find_packages from setuptools import setup, find_packages
version = '2.0beta8' version = '2.0beta9'
f = open('README.rst') f = open('README.rst')
long_description = f.read().split('split here')[1] long_description = f.read().split('split here')[1]
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment