前言
pyrasite工具通过gdb工具和cpython提供的PyRun_SimpleString方法,能够做到attach到正在运行的python进程中,查看正在运行的python堆栈,以及注入修改代码的功能。
遇到的问题:
- 执行pyrasite $pid dump_stacks.py命令后,被注入的进程控制台无任何输出
- 执行pyrasite-shell $pid命令后,卡住无反应,ctrl+c之后,python栈卡在socket的accept方法
解决方法如下:
参考文档
官方文档:pyrasite.readthedocs.io/en/latest/i…
针对不同的操作系统,需要做一些额外的设置:参考pyrasite.readthedocs.io/en/latest/I…
安装方法:
pip install pyrasite pyrasite-gui
使用方法
使用 pyrasite $pid xxx.py方法,在被attach的进程中执行一段python代码
例如:
dump_stacks是pyrasite提供的通用payload,执行pyrasite --list-payload命令,可以列出支持的payload。
除了通用payload之外,也可以实现自己的payload,方便线上环境debug。
使用 pyrasite-shell $pid方法,会在被attach的进程中新启动一个线程来和控制台交互,实时执行想要的代码,及观测想要观察的数据,更改内容等
例如:
使用pyrasite-memory-viewer $pid查看内存信息
使用前,需要先pip install urwid
遇到的问题及解决方法
- ubuntu下执行时报错
根据提示, 执行echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope命令即可
- 执行
pyrasite $pid dump_stacks.py后, 没有任何输出, 终端并没有打印栈信息
执行pyrasite-shell $pid后,卡住,ctrl+c后发现栈卡在socket和accept方法上:
查看源码:
import os
import subprocess
def inject(pid, filename, verbose=False, gdb_prefix=''):
"""Executes a file in a running Python process."""
filename = os.path.abspath(filename)
gdb_cmds = [
'PyGILState_Ensure()',
'PyRun_SimpleString("'
'import sys; sys.path.insert(0, \"%s\"); '
'sys.path.insert(0, \"%s\"); '
'exec(open(\"%s\").read())")' %
(os.path.dirname(filename),
os.path.abspath(os.path.join(os.path.dirname(__file__), '..')),
filename),
'PyGILState_Release($1)',
]
p = subprocess.Popen('%sgdb -p %d -batch %s' % (gdb_prefix, pid,
' '.join(["-eval-command='call %s'" % cmd for cmd in gdb_cmds])),
shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
if verbose:
print(out)
print(err)
其使用的方法是通过gdb工具执行PyRun_SimpleString方法来注入python代码,同时发现使用--verbose可以输出详细信息:
根据输出的信息,可以看到报错内容 PyGILState_Ensure' has unknown return type; cast the call to its declared return type。
尝试使用gdb -p $pid命令attach到进程中,有下面的报错:
搜索后得知,这个是由于linux机器上安装的python版本编译时没有加-g选项,gdb工具不能通过debug信息找到函数的定义来得知方法的返回类型,因此报错。
解决方法:
- 使用编译时带了-g选项的python版本,方法:源码下载后执行./configure --with-pydebug,添加with-pydebug参数
- yum或者apt下载对应版本的python debug包?(这个并没有找到办法,尝试了下载python38-debug之后仍然不行)
- 查看python源码找到命令中使用到的三个函数的定义,找到函数的返回类型,手动指定其返回类型。
通过第三种方法解决的方式:
PyGILState_Ensure、PyGILState_Release
// PyGILState_Ensure函数返回值为PyGILState_STATE
PyGILState_STATE
PyGILState_Ensure(void)
{
...
}
// PyGILState_Release函数返回值为void
void
PyGILState_Release(PyGILState_STATE oldstate)
{
...
}
继续查找PyGILState_STATE定义:
// 枚举类型直接设定返回值为int即可
typedef
enum {PyGILState_LOCKED, PyGILState_UNLOCKED}
PyGILState_STATE;
PyRun_SimpleString
PyAPI_FUNC(int)
PyRun_SimpleString(const char *s)
{
return PyRun_SimpleStringFlags(s, NULL);
}
// PyRun_SimpleString函数返回值为int
define PyAPI_FUNC(RTYPE) RTYPE
因此, 补充上返回值, inject.py代码可以修改为:
gdb_cmds = [
'(int) PyGILState_Ensure()',
'(int) PyRun_SimpleString("'
'import sys; sys.path.insert(0, \"%s\"); '
'sys.path.insert(0, \"%s\"); '
'exec(open(\"%s\").read())")' %
(os.path.dirname(filename),
os.path.abspath(os.path.join(os.path.dirname(__file__), '..')),
filename),
'(void) PyGILState_Release($1)',
]
通过python -m site命令,找pyrasite安装路径
修改/home/zk/venv/lib/python3.8/site-packages/pyrasite/injector.py的即可,注意不要直接复制代码到py中,会有问题,用vim单独在每一行前面加(int) 和(void):
可以看到执行成功!被监控程序打印了栈。
再次启动进程,使用pyrasite-shell $pid,也能正常连接到进程中,执行python代码