Commit f86d40fb authored by Luke Macken's avatar Luke Macken

Merge branch 'release/2.0beta9'

parents 3beb3c96 9cc79978
......@@ -3,8 +3,7 @@ pyrasite
.. split here
Pyrasite lets you to inject arbitrary code into an unaltered running Python
process.
Pyrasite lets you to inject arbitrary code into running Python processes.
:documentation: http://pyrasite.com
:download: http://pypi.python.org/pypi/pyrasite
......@@ -14,19 +13,31 @@ process.
:jenkins: http://ci.csh.rit.edu/view/Pyrasite
: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
~~~~~~~~~~~~
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
Authors
~~~~~~~
Luke Macken (`@lmacken <http://twitter.com/lmacken>`_)
`Luke Macken <http://twitter.com/lmacken>`_
.. image:: http://api.coderwall.com/lmacken/endorsecount.png
: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
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`
......@@ -15,39 +15,28 @@ GUI
- `Pyrasite <https://github.com/lmacken/pyrasite>`_
- Python debuginfo (needed for live object inspection)
- Fedora: python-debuginfo
- Ubuntu: python-dbg
- Fedora: python-debuginfo, Ubuntu: python-dbg
- PyGObject3 Introspection bindings
- Fedora: pygobject3
- Ubuntu: python-gobject-dev
- Arch: python2-gobject
- Fedora: pygobject3, Ubuntu: python-gobject-dev, Arch: python2-gobject
- WebKitGTK3
- Fedora: webkitgtk3
- Ubuntu: gir1.2-webkit-3.0
- Arch: libwebkit3
- Fedora: webkitgtk3, Ubuntu: gir1.2-webkit-3.0, Arch: libwebkit3
- `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.
- Fedora: python-meliae
- Ubuntu: python-meliae
- Arch: python2-meliae
- Fedora: python-meliae, Ubuntu: python-meliae, Arch: python2-meliae
- `pycallgraph <http://pycallgraph.slowchop.com>`_
- Fedora: python-pycallgraph
- Ubuntu: python-pycallgraph
- Arch: python2-pycallgraph
- Fedora: python-pycallgraph, Ubuntu: python-pycallgraph, Arch: python2-pycallgraph
- `psutil <http://code.google.com/p/psutil>`_
- Fedora: python-psutil
- Ubuntu: python-psutil
- Arch: python2-psutil
- Fedora: python-psutil, Ubuntu: python-psutil, Arch: python2-psutil
Download
~~~~~~~~
......
......@@ -4,6 +4,9 @@ Payloads
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.
.. literalinclude:: ../pyrasite/payloads/reverse_python_shell.py
......@@ -62,8 +65,8 @@ installed.
.. image:: http://lewk.org/img/pyrasite-memory-viewer.png
Reverse Shell
-------------
Reverse Subprocess Shell
------------------------
.. literalinclude:: ../pyrasite/payloads/reverse_shell.py
: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
---------
.. toctree::
:maxdepth: 3
:maxdepth: 1
Installing
GUI
CLI
Shell
GUI
Payloads
API
%global betaver beta8
%global betaver beta9
Name: pyrasite
Version: 2.0
......@@ -39,6 +39,7 @@ also comes with a variety of example payloads.
%doc README.rst
%{_bindir}/pyrasite
%{_bindir}/pyrasite-memory-viewer
%{_bindir}/pyrasite-shell
%{python_sitelib}/*
%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',
'ReverseConnection', 'ReversePythonConnection')
__license__ = """\
......
......@@ -63,6 +63,9 @@ class PyrasiteIPC(object):
super(PyrasiteIPC, self).__init__()
self.pid = pid
self.sock = None
self.server_sock = None
self.hostname = None
self.port = None
def connect(self):
"""
......@@ -80,20 +83,23 @@ class PyrasiteIPC(object):
af, socktype, proto, canonname, sa = res
try:
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:
self.server_sock = None
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
self.hostname, self.port = self.server_sock.getsockname()[0:2]
self.running = True
if not self.server_sock:
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):
"""Write out a reverse python connection payload with a custom port"""
......@@ -106,7 +112,8 @@ class PyrasiteIPC(object):
if line.startswith('#'):
continue
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('ReversePythonConnection().start()\n')
......@@ -153,7 +160,7 @@ class PyrasiteIPC(object):
if len(data) == msg_len:
return data
else:
return self.sock.recv(4096)
return self.sock.recv(4096).decode('utf-8')
def recv_bytes(self, n):
"""Receive n bytes from a socket"""
......@@ -168,9 +175,8 @@ class PyrasiteIPC(object):
def close(self):
if self.sock:
self.sock.close()
if getattr(self, 'server_sock', None):
self.server_sock.close()
def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, self.pid)
def __str__(self):
return self.title
......@@ -18,15 +18,41 @@
import os
import sys
import argparse
import subprocess
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():
ptrace_check()
parser = argparse.ArgumentParser(
description='pyrasite - inject code into a running python process',
epilog="For updates, visit https://github.com/lmacken/pyrasite"
)
description='pyrasite - inject code into a running python process',
epilog="For updates, visit https://github.com/lmacken/pyrasite")
parser.add_argument('pid',
help="The ID of the process to inject code into")
parser.add_argument('filename',
......
......@@ -37,6 +37,7 @@ class ReverseConnection(threading.Thread, pyrasite.PyrasiteIPC):
host = 'localhost'
port = 9001
reliable = True
def __init__(self, host=None, port=None):
super(ReverseConnection, self).__init__()
......@@ -61,19 +62,20 @@ class ReverseConnection(threading.Thread, pyrasite.PyrasiteIPC):
af, socktype, proto, canonname, sa = res
try:
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:
self.sock = None
continue
try:
self.sock.connect(sa)
except socket.error:
self.sock.close()
self.sock = None
continue
break
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()
......
# 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 @@
#
# Copyright (C) 2011, 2012 Red Hat, Inc.
import os
import unittest
import subprocess
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):
cmd = 'python -c "import time; time.sleep(0.5)"'
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
def setUp(self):
self.prog = generate_program()
self.p = run_program(self.prog)
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()
assert 'Hello World!' in stdout.decode('utf-8'), \
"Code injection failed"
def test_listen(self):
self.ipc.listen()
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):
cmd = [
'import time, threading',
'snooze = lambda: time.sleep(0.5)',
'threading.Thread(target=snooze).start()',
]
p = subprocess.Popen('python -c "%s"' % ';'.join(cmd), shell=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def test_create_payload(self):
self.ipc.listen()
payload = self.ipc.create_payload()
assert os.path.exists(payload)
code = open(payload)
compile(code.read(), payload, 'exec')
code.close()
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()
assert 'Hello World!' in stdout.decode('utf-8'), \
"Multi-threaded code injection failed"
def test_cmd(self):
self.ipc.connect()
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__':
......
# 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():
print("Usage: pyrasite-shell <PID>")
sys.exit(1)
pid = int(sys.argv[1])
ipc = pyrasite.PyrasiteIPC(pid)
ipc = pyrasite.PyrasiteIPC(int(sys.argv[1]))
ipc.connect()
print("pyrasite shell %s" % pyrasite.__version__)
......
from setuptools import setup, find_packages
version = '2.0beta8'
version = '2.0beta9'
f = open('README.rst')
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