Python: 将任意Bytecode注入运行中的Python进程
May 26, 2015
在调试 Python 程序的时候,一般我们只能通过以下几种方式进行调试:
- 程序中已经有的日志
- 在代码中插入 import pdb; pdb.set_trace()
但是以上的方法也有不方便的地方, 比如对于已经在运行中的程序, 就不可能停止程序后加入 调试代码和增加新的日志.
从 JAVA 的
BTrace( https://kenai.com/projects/btrace
) 项目得到灵感,尝试对正在运行的 Python进程插入代码,在程序运行到指定的函数后,自动连接远程主机进行调试
首先介绍三个开源的项目, 本实验需要用到这三个项目
-
Pyasitehttps://github.com/lmacken/pyrasite
Tools for injecting code into running Python processes -
Byteplayhttps://github.com/serprex/byteplay
一个字节码维护项目,类似 java的asm/cglib - Rpdb-Shellhttps://github.com/alex8224/Rpdb-Shell
待注入的代码, 用官方的
import tornado.ioloop import tornado.web import os class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, world>") application = tornado.web.Application([ (r"/>", MainHandler), ]) if __name__ == "__main__>": application.listen(8888) print(os.getpid()) tornado.ioloop.IOLoop.instance().start()
注入以下代码(
import sys import dis import inspect from byteplay import * def wearedcode(fcode): c = Code.from_code(fcode) if c.code[1] == (LOAD_CONST, 'injected'): return fcode c.code[1:1] = [ (LOAD_CONST, injected'), (STORE_FAST, 'name'), (LOAD_FAST, 'name'), (PRINT_ITEM, None), (PRINT_NEWLINE, None), (LOAD_CONST, -1), (LOAD_CONST, None), (IMPORT_NAME, 'rpdb'), (STORE_FAST, 'rpdb'), (LOAD_FAST, 'rpdb'), (LOAD_ATTR, 'trace_to_remote'), (LOAD_CONST, '10.86.11.116'), (CALL_FUNCTION, 1), (POP_TOP, None) ] return c.to_code() def trace(frame, event, arg): if event != 'call': return co = frame.f_code func_name = co.co_name if func_name == "write>": return if func_name == "get>": import tornado.web args = inspect.getargvalues(frame) if 'self' in args.locals: if isinstance(args.locals['self'], tornado.web.RequestHandler): getmethod = args.locals['self'].get code = getmethod.__func__.__code__ getmethod.__func__.__code__ = wearedcode(code) return sys.settrace(trace)
Table of Contents
环境
- ubuntu 14.04 64bit LTS
- Python 2.7.6
步骤
- 在机器上安装上面需要用到的三个项目
- python server.py
-
在
192.168.1.1 执行nc -l 4444 - pyrasite $(ps aux |grep server.py |grep -v grep|awk ‘{print $2}’) testinject.py
-
执行 curl http://localhost:8000
两次, 在第二次请求时替换的bytecode 才会生效
结果
在执行上面的步骤后, 在执行第二次 curl http://127.0.0.1:8000
后, 应该能够看到控制台输入 injected 的字样,并且 nc -l 4444 监听的终端会出现
原理
函数的定位用到了 sys.settrace 这个API,他提供了以下事件,在合适的时机调用用户提供的函数, 具体可以参考 https://docs.python.org/2/library/sys.html#sys.settrace
的解释
理论上可以插入任意字节码到程序中的任意位置, 实现对现有进程中代码的任意修改.
0 Comments