将任意Bytecode注入运行中的Python进程
admin
2023-07-31 01:51:18
0

在调试 Python 程序的时候,一般我们只能通过以下几种方式进行调试:

  1. 程序中已经有的日志
  2. 在代码中插入 import pdb; pdb.set_trace()

但是以上的方法也有不方便的地方, 比如对于已经在运行中的程序, 就不可能停止程序后加入 调试代码和增加新的日志.

从 JAVA 的 BTrace(https://kenai.com/projects/btrace) 项目得到灵感,尝试对正在运行的 Python 进程插入代码,在程序运行到指定的函数后,自动连接远程主机进行调试

首先介绍三个开源的项目, 本实验需要用到这三个项目

  1. Pyasite https://github.com/lmacken/pyrasite Tools for injecting code into running Python processes
  2. Byteplay https://github.com/serprex/byteplay 一个字节码维护项目,类似 java的asm/cglib
  3. Rpdb-Shell https://github.com/alex8224/Rpdb-Shell

待注入的代码, 用官方的 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)

环境

  1. ubuntu 14.04 64bit LTS
  2. Python 2.7.6

步骤

  1. 在机器上安装上面需要用到的三个项目
  2. python server.py
  3. 192.168.1.1 执行 nc -l 4444
  4. pyrasite $(ps aux |grep server.py |grep -v grep|awk \'{print $2}\’) testinject.py
  5. 执行 curl http://localhost:8000 两次, 在第二次请求时替换的 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 的解释

理论上可以插入任意字节码到程序中的任意位置, 实现对现有进程中代码的任意修改.

相关内容

热门资讯

Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...
Apache Doris 2.... 亲爱的社区小伙伴们,我们很高兴地向大家宣布,Apache Doris 2.0.0 版本已于...
python清除字符串里非数字... 本文实例讲述了python清除字符串里非数字字符的方法。分享给大家供大家参考。具体如下: impor...