在调试 Python 程序的时候,一般我们只能通过以下几种方式进行调试:
但是以上的方法也有不方便的地方, 比如对于已经在运行中的程序, 就不可能停止程序后加入 调试代码和增加新的日志.
从 JAVA 的 BTrace(https://kenai.com/projects/btrace) 项目得到灵感,尝试对正在运行的 Python 进程插入代码,在程序运行到指定的函数后,自动连接远程主机进行调试
首先介绍三个开源的项目, 本实验需要用到这三个项目
待注入的代码, 用官方的 tornado hello demo
做例子
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()
注入以下代码(testinject.py
)到 get 中
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, \'192.168.1.1\'), (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)
192.168.1.1
执行 nc -l 4444
bytecode
才会生效在执行上面的步骤后, 在执行第二次 curl http://127.0.0.1:8000 后, 应该能够看到控制台输入 injected 的字样,并且 nc -l 4444 监听的终端会出现 (pdb)>
的字样, 这样就能够对正在运行中的程序进行调试了.
Pyasite
可以注入代码到运行中的 Python 进程,它利用了 Python 的 PyRun_SimpleString
这个API插入代码, 至于进程注入应该是使用了 ptrace
Byteplay
是一个可以维护 Python bytecode的工具, 这部分跟 cglib/asm类似
Pyasite
只能把代码注入到进程中并运行,不能定位到具体的函数并注入 bytecode, 在 testinject.py
中结合 Byteplay 完成了函数定位和替换 get 函数字节码的功能.
函数的定位用到了 sys.settrace 这个API,他提供了 call, line, return, exception事件,在合适的时机调用用户提供的函数, 具体可以参考 https://docs.python.org/2/library/sys.html#sys.settrace 的解释
理论上可以插入任意字节码到程序中的任意位置, 实现对现有进程中代码的任意修改.
上一篇:混淆id的一种方法