pyc是一种二进制文件,是由Python源代码(.py文件)经过编译后生成的字节码文件。这种文件加载速度比源码更快,并且是跨平台的,由Python虚拟机执行,其概念类似于Java或.NET的虚拟机。
需要注意的是,pyc文件的内容与Python版本紧密相关。不同版本编译的pyc文件互不兼容,例如,用Python 2.5编译的pyc文件无法被Python 2.4执行。
生成pyc文件的一个主要目的是保护源码。发布商业软件时,直接发布源码存在泄漏风险,编译为pyc文件则能起到一定的保护作用。不过,pyc文件本身也可以被反编译。网上存在针对特定Python版本(如2.3)的反编译工具,但从2.4版本开始可能需要付费版本。因此,深入理解Python字节码和反编译原理,对于CTF逆向和软件安全分析都至关重要。
1. pyc文件反编译
解法:使用 uncompyle6 直接反编译。
示例代码 (test.py):
def check():
flag=1+1
if(flag==2):
return "right"
return "error"
print(check())
生成pyc文件 (Python 3):
在命令行中,使用模块方式编译:
python -m py_compile test.py
执行后会在__pycache__目录下生成对应的.pyc文件。pyc文件可以直接被Python解释器执行:
python test.pyc
运行结果与.py文件一致,但无法直接查看其源码,即使使用IDA等静态分析工具也难以直接阅读。
反编译步骤:
- 安装反编译工具
uncompyle6:
pip install uncompyle6
注:某些情况下最新版可能存在兼容性问题,可指定安装稳定版本,如 pip install uncompyle6==3.7.4。
- 对pyc文件进行反编译:
uncompyle6 test.pyc
执行命令后,即可在终端输出还原的Python源代码。获得源码后,后续的分析流程就与常规的Windows二进制逆向类似了。
2. 文本格式的Python字节码分析
解法:
- 阅读理解Python字节码
- 结合
opcode映射表分析指令含义
有时题目不会直接给出pyc文件,而是提供文本形式的Python字节码(汇编)。我们需要手动或借助脚本分析。
获取字节码:
我们可以通过Python内置的dis和marshal库来获取一个pyc文件的字节码。
import dis, marshal
f = open("test.pyc", "rb").read()
# Python3的pyc文件前16字节为魔数(Magic Number)和元信息,需要跳过
code = marshal.loads(f[16:])
# 反汇编字节码
dis.dis(code)
执行后,会输出类似汇编的文本,这就是Python虚拟机执行的指令序列。
字节码结构:
- Python 2:3个字节为一个指令。
- Python 3:2个字节为一个指令(通常)。例如,
LOAD_CONST指令可能占用偏移0和1两个字节,下一条指令则从偏移2开始。
实战分析:
面对一大段字节码文本,我们需要结合opcode(操作码)的含义逐条分析。核心是理解栈操作(如LOAD_CONST压栈、BINARY_ADD弹出栈顶两个元素相加后结果压栈)、变量存储(STORE_NAME)、控制流跳转(POP_JUMP_IF_FALSE)等。
一个典型的逆向过程是:根据字节码还原出关键的数据流和控制流,推断出原始算法的逻辑,例如加密、校验过程,最终编写脚本求解出flag。
3. 打包为exe的Python程序逆向
解法:
- 使用
pyinstxtractor解包exe,提取出pyc文件结构。
- 关键步骤:修复pyc文件的文件头(魔数和时间戳)。
- 使用
uncompyle6反编译修复后的pyc文件。
步骤详解:
- 解包:使用
pyinstxtractor.py脚本解包由PyInstaller打包的exe文件。
python pyinstxtractor.py attachment.exe
解包后会生成一个_extracted后缀的文件夹,其中包含程序的各种资源。主要关注与主程序同名的文件(如login,可能没有.pyc后缀)。
- 修复文件头:PyInstaller打包过程会剥离pyc文件头部的部分元信息。我们需要从同目录下的
struct文件中复制正确的文件头(通常是前16个字节,具体长度需对比正常pyc文件确定),用十六进制编辑器(如010Editor)将其补回到login文件的开头。
- 重命名与反编译:将修复后的文件重命名为
login.pyc,然后使用uncompyle6进行反编译,即可得到清晰的Python源代码。
- 分析求解:获得源码后,通常是一个包含逻辑判断或加密算法的脚本,根据其逻辑编写解密脚本即可获得flag。
4. 含花指令的pyc文件修复
解法:
- 利用
uncompyle6的错误信息和字节码分析定位“花指令”位置。
- 读取
co_code的长度。
- 从字节码中去除无效的花指令,并同步修正
co_code的长度字段。
- 保存修改后的文件,再用
uncompyle6反编译。
什么是花指令?
花指令是故意插入的、不影响程序最终执行结果但会干扰反编译器正常工作的无用指令。例如,插入一个绝对跳转(JUMP_ABSOLUTE),直接跳过紧接着的一两条无用指令,继续执行正常流程。程序执行无误,但反编译器在解析指令流时可能会因遇到无法理解的指令序列而报错或输出混乱结果。
修复步骤:
- 定位花指令:尝试用
uncompyle6反编译有问题的pyc文件,记下报错信息。同时,使用dis模块反汇编其字节码,在报错位置附近寻找不合理的指令序列,如JUMP_ABSOLUTE跳转到正常代码块之后,而跳转前有一些看似无意义的LOAD_CONST等操作。
- 计算长度:通过
len(code.co_code)获取原始字节码长度。
- 移除花指令:使用十六进制编辑器,找到花指令对应的字节序列并删除。需要参考Python版本的
opcode.h文件确定指令对应的十六进制值。
- 修正长度:删除花指令后,字节码总长度减少。在文件相应位置(通常是
co_code长度字段)将原来的长度值修改为新的、更短的值。
- 反编译验证:保存修改,再次使用
uncompyle6反编译,成功则说明修复完成。
掌握这四种Python程序逆向场景的分析方法,能够有效应对CTF竞赛和实际安全评估中遇到的大多数Python打包或代码混淆情况。