Commit b3cc1890 authored by Luke Macken's avatar Luke Macken

Initial commit!

parents
*.egg*
*.pyc
*.pyo
*.swp
build
dist
Luke Macken <lmacken@redhat.com>
David Malcolm <dmalcolm@redhat.com>
This diff is collapsed.
pyrasite
========
Injects code into a running Python process.
Requirements
~~~~~~~~~~~~
- gdb (https://www.gnu.org/s/gdb)
Example Payloads
~~~~~~~~~~~~~~~~
Hello World
-----------
::
python inject.py <PID> payloads/helloworld.py
This payload is used by the test suite, which can be run by running
::
python setup.py test
Reverse Python Shell
--------------------
::
$ python -v
>>> x = 'foo'
::
$ python inject.py <PID> payloads/reverse_python_shell.py
$ nc -l localhost 9001
Python 2.7.1 (r271:86832, Apr 12 2011, 16:15:16)
[GCC 4.6.0 20110331 (Red Hat 4.6.0-2)]
Type 'quit' to exit.
>>> print x
foo
>>> globals()['x'] = 'bar'
Reverse Shell
--------------
::
$ python inject.py <PID> payloads/reverse_python_shell.py
$ nc -l localhost 9001
Linux tomservo 2.6.40.3-0.fc15.x86_64 #1 SMP Tue Aug 16 04:10:59 UTC 2011 x86_64 x86_64 x86_64 GNU/Linux
Type 'quit' to exit.
% ls
# 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 Red Hat, Inc.
import time, socket, threading
class ReverseConnection(threading.Thread):
host = '127.0.0.1' # The remote host
port = 9001 # The same port as used by the server
def run(self):
running = True
while running:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((self.host, self.port))
self.on_connect(s)
while running:
data = s.recv(1024)
if data == "quit\n" or len(data) == 0:
running = False
else:
try:
running = self.on_command(s, data)
except:
running = False
s.close()
except socket.error, e:
print(str(e))
time.sleep(5)
def on_connect(self, s):
pass
def on_command(self, s, cmd):
raise NotImplementedError("You must prove your own on_command method")
# "meliae" provides a way to dump python memory usage information to a JSON
# disk format, which can then be parsed into useful things like graph
# representations.
#
# https://launchpad.net/meliae
# http://jam-bazaar.blogspot.com/2009/11/memory-debugging-with-meliae.html
from meliae import scanner
scanner.dump_all_objects('objects.json')
import sys
for name in sorted(sys.modules):
print('%s: %s' % (name, sys.modules[name]))
import sys, traceback
for thread, frame in sys._current_frames().iteritems():
print('Thread 0x%x' % thread)
traceback.print_stack(frame)
print("Hello World!")
# 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 Red Hat, Inc.
import sys
from StringIO import StringIO
from _reverseconnection import ReverseConnection
class ReversePythonShell(ReverseConnection):
host = '127.0.0.1'
port = 9001
def on_connect(self, s):
s.send("Python %s\nType 'quit' to exit\n>>> " % sys.version)
def on_command(self, s, cmd):
buffer = StringIO()
sys.stdout = buffer
sys.stderr = buffer
output = ''
try:
exec(cmd)
output = buffer.getvalue()
except Exception, e:
output = str(e)
finally:
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
buffer.close()
s.send(output + '\n>>> ')
return True
ReversePythonShell().start()
# 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 Red Hat, Inc.
import subprocess
from _reverseconnection import ReverseConnection
class ReverseShell(ReverseConnection):
host = '127.0.0.1' # The remote host
port = 9001 # The same port as used by the server
def on_connect(self, s):
uname = self._run('uname -a')[0]
s.send("%sType 'quit' to exit\n%% " % uname)
def on_command(self, s, cmd):
out, err = self._run(cmd)
if err:
out += err
s.send(out + '\n% ')
return True
def _run(self, cmd):
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
out, err = p.communicate()
return out, err
ReverseShell().start()
# 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 Red Hat, Inc.
"""
pyrasite
========
Injects code into a running python process
http://pyrasite.fedorahosted.org
Authors:
Luke Macken <lmacken@redhat.com>
David Malcolm <dmalcolm@redhat.com>
"""
import os, subprocess
class CodeInjector(object):
def __init__(self, pid, filename, verbose=False):
self.pid = pid
self.filename = os.path.abspath(filename)
self.verbose = verbose
def inject(self):
gdb_cmds = [
'PyGILState_Ensure()',
# Allow payloads to import modules along-side them
'PyRun_SimpleString("import sys; sys.path.insert(0, \\"%s\\");")' %
os.path.dirname(self.filename),
'PyRun_SimpleString("execfile(\\"%s\\")")' % self.filename,
'PyGILState_Release($1)',
]
self._run('gdb -p %d -batch %s' % (self.pid,
' '.join(["-eval-command='call %s'" % cmd for cmd in gdb_cmds])))
def _run(self, cmd):
if self.verbose:
print(cmd)
p = subprocess.Popen(cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = p.communicate()
if self.verbose:
print(out)
if err:
print(err)
# 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 Red Hat, Inc.
import os, sys
from inject import CodeInjector
def main():
if len(sys.argv) < 3:
print("Usage: %s <pid> <filename>" % sys.argv[0])
print("\n pid:\tThe ID of the process to inject code into")
print(" filename:\tThe .py file to inject into the process\n")
sys.exit(1)
try:
pid = int(sys.argv[1])
except ValueError:
print "Error: The first argument must be a pid"
sys.exit(2)
filename = sys.argv[2]
if not os.path.exists(filename):
print "Error: The second argument must be a filename"
sys.exit(3)
injector = CodeInjector(pid, filename, verbose='-v' in sys.argv)
injector.inject()
if __name__ == '__main__':
main()
[egg_info]
tag_build = dev
from setuptools import setup, find_packages
version = '1.0'
f = open('README.rst')
long_description = f.read()
f.close()
setup(name='pyrasite',
version=version,
description="Inject code into a running Python process",
long_description=long_description,
keywords='debugging injection runtime',
author='Luke Macken',
author_email='lmacken@redhat.com',
url='http://pyrasite.fedorahosted.org',
license='GPLv3',
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
include_package_data=True,
zip_safe=True,
install_requires=[
],
tests_require=['nose'],
test_suite='nose.collector',
entry_points="""
[console_scripts]
pyrasite = pyrasite.main:main
""",
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Console',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: GNU General Public License (GPL)',
'Topic :: System :: Monitoring',
'Topic :: Software Development :: Debuggers',
],
)
# 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 Red Hat, Inc.
import unittest, subprocess
from pyrasite.inject import CodeInjector
class TestCodeInjection(unittest.TestCase):
def test_injection(self):
cmd = 'python -c "import time; time.sleep(0.5)"'
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
ci = CodeInjector(p.pid, 'payloads/helloworld.py', verbose=True)
ci.inject()
stdout, stderr = p.communicate()
assert 'Hello World!' in stdout, "Code injection failed"
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)
ci = CodeInjector(p.pid, 'payloads/helloworld.py', verbose=True)
ci.inject()
stdout, stderr = p.communicate()
assert 'Hello World!' in stdout, "Multi-threaded code injection failed"
if __name__ == '__main__':
unittest.main()
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