在上篇文章中,我们介绍了三种Python脚本保护方案:转pyc文件、脚本级加密和数据文件加密。虽然这些方法具有一定的保护效果,但安全性仍有限。本篇文章将进一步介绍三种更具安全性的Python保护方案,帮助开发者提高代码的防护能力,防止源代码被轻易逆向。
读者群体
- Python脚本开发者
- 安全领域技术研究人员
魔改Python解释器
魔改Python解释器是对上篇文章中转pyc文件的一种加强方案。通过修改Python解释器中的字节码定义后编译出魔改后的Python,可以有效防止标准反编译工具(如uncompyle)对pyc文件进行反编译。本文以Python3.8为例。
步骤
- 在github上下载CPython 3.8的源代码
- 修改
Include/opcode.h
中的字节码定义,例如:#define POP_TOP (1 ^ 0x56)
,对字节码做异或操作 - 编译Python项目
防护效果
通过修改字节码,虽然文件格式不变,但通过标准反编译工具如uncompyle6进行反编译时,会无法还原出源代码。 命令:uncompyle6.exe -o 1.py test.cpython-38.pyc
,已经不能被反编译。
-- Stacks of completed symbols:
START ::= |- stmts .
_come_froms ::= \e__come_froms . COME_FROM
_come_froms ::= \e__come_froms . COME_FROM_LOOP
while1stmt ::= \e__come_froms . l_stmts COME_FROM JUMP_BACK COME_FROM_LOOP
whileTruestmt ::= \e__come_froms . l_stmts JUMP_BACK POP_BLOCK
whileTruestmt38 ::= \e__come_froms . l_stmts JUMP_BACK
whileTruestmt38 ::= \e__come_froms . l_stmts JUMP_BACK COME_FROM_EXCEPT_CLAUSE
whileTruestmt38 ::= \e__come_froms . pass JUMP_BACK
whileTruestmt38 ::= \e__come_froms \e_pass . JUMP_BACK
whilestmt38 ::= \e__come_froms . testexpr \e_l_stmts_opt COME_FROM JUMP_BACK POP_BLOCK
whilestmt38 ::= \e__come_froms . testexpr \e_l_stmts_opt JUMP_BACK POP_BLOCK
whilestmt38 ::= \e__come_froms . testexpr \e_l_stmts_opt JUMP_BACK come_froms
whilestmt38 ::= \e__come_froms . testexpr l_stmts JUMP_BACK
whilestmt38 ::= \e__come_froms . testexpr l_stmts come_froms
whilestmt38 ::= \e__come_froms . testexpr l_stmts_opt COME_FROM JUMP_BACK POP_BLOCK
whilestmt38 ::= \e__come_froms . testexpr l_stmts_opt JUMP_BACK POP_BLOCK
whilestmt38 ::= \e__come_froms . testexpr l_stmts_opt JUMP_BACK come_froms
whilestmt38 ::= \e__come_froms . testexpr returns POP_BLOCK
Instruction context:
->
L. 1 0 GET_AITER
2 GET_AITER
4 <58>
6 UNARY_NOT
# file test.cpython-38.pyc
# Parse error at or near `GET_AITER' instruction at offset 0
test.cpython-38.pyc --
# decompile failed
潜在风险与限制
- 逆向者可以通过对比标准版和魔改版字节码之间的差异,修改反编译工具来进行逆向
- 生成的pyc文件仅能在定制解释器环境中运行,部署不方便
- 每次迭代Python版本需要单独维护魔改解释器,维护成本大
Python到C转换
最具代表性的工具就是cython,Cython是一种将Python源码转换为等价C代码的工具,最终将其编译为Python的C扩展模块(如.pyd、.so文件)。这种方法可以有效防止Python源码泄露,并提升执行效率。Python C拓展模块(pyd/so/dylib),其本质是一个动态库,只不过使用了python sdk编写可以与Python解释器进行交互。
逆向技能要求
- 熟悉Python C API
- 懂native层静态分析和动态调试
步骤
- 安装cython,
pip install cython
- 编写setup.py文件
- 编译,
python setup.py build_ext --inplace
Python源码
def sayhi():
print('Hello from Cython!')
setup文件
from distutils.core import setup
from Cython.Build import cythonize
setup(ext_modules = cythonize("test.py"))
防护效果
下图是汇编代码反编译后的效果

方案局限
- 部分Python特殊语法兼容性不好
- 需要额外学习Cython相关知识
- 需要为不同平台与架构分别编译
- 经验丰富的逆向者仍然能够通过静态和动态调试了解代码逻辑
逆向对抗思路
- 确定编译pyd的python版本和cython版本
- 下载对应版本,自己编译一份带调试信息的pyd
- 结合生成的c文件和IDA工具静态对比分析,了解cython生成pyd的框架结构
- 根据Python提供的C API文档分析代码逻辑
字节码级加密
Python字节码级别的保护技术,这种保护方案是先把Python脚本编译代码对象(Code Object),然后对代码对象中的字节码进行加密,在脚本运行时通过加载解密的Python C扩展库去动态解密和加密字节码到达逆向者无法窃取到字节码的目的,通常还会对解密的Python C扩展库进行代码保护。
核心优势
- 运行时动态解密,内存中无完整字节码暴露
- 与保护前的Python脚本无缝替换
- 兼容主流Python版本Python3.6-3.13
- 结合Native代码加固提供多层防护
逆向技能要求
- 熟悉Python解释器内部结构,如:CodeObject等
- native层静态分析和动态调试
- 有分析混淆后的Native代码的能力
步骤
实现难度比较高,不过,这种保护方案已经有比较成熟稳定实现了,具体操作参考深盾科技官网的《Python程序保护最佳实践》文档
防护效果
from virbox_pyruntime import virbox
virbox((b'X\xa7m\x04h\xbe \x83^\x8a\xcf\xf0\x1e\x0c.........~o\xf6\xd7\x05\x11\xebm\x83\x1c\x8e\x07v\x13Dt\rzA\xf2\x9bN-\xe5\xfb\xde\x1f\xd7`\x1bo\xa4'))
方案局限
- 如果代码调用频率很高会导致性能下降
- 实现较为复杂,但已有成熟的第三方工具支持。
代码保护方案对比
保护方案 | 安全强度 | 性能影响 | 部署复杂度 | 适用场景 |
---|---|---|---|---|
pyc文件 | ★☆☆☆☆ | 无影响 | 中 | 基础保护 |
脚本级混淆 | ★★☆☆☆ | 轻微影响 | 低 | 快速简易保护 |
数据文件加密(DS) | ★★★☆☆ | 轻微影响 | 中 | 敏感数据和脚本保护 |
魔改Python解释器 | ★★★☆☆ | 无影响 | 高 | 封闭可控环境 |
Python到C转换 | ★★★★☆ | 性能提升 | 中 | 性能敏感代码 |
字节码级加密 | ★★★★★ | 可控影响 | 低 | 商业级高安全需求 |
总结
在本篇主题介绍的六种Python保护方案中,字节码级加密方案提供了最强的安全性。尽管每种方案都有其优缺点,但通过合理选择和组合这些保护措施,可以大大提升Python脚本的安全性。