从一道NISACTF赛题看Python逆向:pyc文件修复与RSA加密破解全记录
最近在NISACTF 2022的赛场上遇到了一道名为“ezpython”的逆向题目,这道题看似简单,却巧妙地将Python打包程序的反编译、pyc文件头修复、以及RSA加密算法的分析融合在一起,成为了一个检验逆向工程师综合能力的典型案例。很多朋友在初次接触这类题目时,往往会在pyc文件修复这一步卡住,或者面对复杂的加密逻辑感到无从下手。今天,我就结合这道赛题的完整解题过程,为大家详细拆解Python逆向中的这两个核心难点,并分享一些实用的工具和技巧。
对于使用PyInstaller打包的Python程序,逆向的第一步通常是从EXE文件中提取出原始的Python字节码文件(.pyc)。这个过程看似简单,但实际操作中会遇到各种意想不到的问题,比如文件头损坏、版本不匹配等。而一旦成功提取并修复了pyc文件,我们就能看到程序的源代码,这时候真正的挑战才刚刚开始——如何理解并逆向分析其中的加密算法逻辑。在“ezpython”这道题中,出题人精心设计了一个包含RSA加密和自定义异或加密的双重保护机制,需要我们对Python的加密库和基本的密码学原理有深入的理解。
1. PyInstaller打包程序的逆向基础与工具链
在Python逆向工程中,PyInstaller是一个常见的打包工具,它能够将Python脚本及其依赖项打包成独立的可执行文件。这种打包方式虽然方便了程序的发布和运行,但也给逆向分析带来了一定的挑战。不过,只要掌握了正确的方法和工具链,我们仍然能够从打包后的EXE文件中还原出原始的Python源代码。
1.1 PyInstaller的工作原理与文件结构
要理解如何逆向PyInstaller打包的程序,首先需要了解它的工作原理。PyInstaller在打包时会将Python解释器、脚本的字节码文件(.pyc)、以及所有依赖的库文件一起打包到一个可执行文件中。当用户运行这个EXE文件时,PyInstaller会在临时目录中解压这些文件,然后启动Python解释器执行主脚本。
PyInstaller打包的EXE文件结构:
├── 可执行文件头部(包含解压逻辑)
├── Python解释器
├── 主脚本的.pyc文件
├── 依赖库的.pyc文件
└── 其他资源文件
了解这个结构后,我们就可以有针对性地进行逆向操作。核心思路是模拟PyInstaller的解压过程,从EXE文件中提取出.pyc文件,然后对这些字节码文件进行反编译。
1.2 核心工具:pyinstxtractor的使用详解
目前最常用的PyInstaller解包工具是pyinstxtractor,这是一个开源的Python脚本,专门用于从PyInstaller打包的EXE文件中提取原始文件。它的使用非常简单,但有几个关键点需要注意。
首先,你需要从GitHub或SourceForge下载pyinstxtractor.py脚本。将下载的脚本与目标EXE文件放在同一目录下,然后在命令行中执行:
python pyinstxtractor.py ez_python.exe
执行成功后,会在当前目录生成一个以“_extracted”结尾的文件夹,里面包含了从EXE文件中提取出的所有文件。对于“ezpython”这道题,提取后的文件夹内容大致如下:
ez_python_extracted/
├── PYZ-00.pyz_extracted/ # 依赖库文件
├── struct # 关键的系统模块文件
├── src # 主程序文件(无后缀)
└── 其他元数据文件
注意:不同版本的PyInstaller打包的程序,其内部结构可能略有差异。如果遇到解包失败的情况,可以尝试更新pyinstxtractor到最新版本,或者检查EXE文件是否被其他工具加壳保护。
在实际操作中,我遇到过一些特殊情况。比如某些题目可能会对PyInstaller进行修改,或者使用了自定义的打包参数,这时候标准的pyinstxtractor可能无法正常工作。针对这种情况,我们可以尝试分析EXE文件的二进制结构,手动定位.pyc文件的位置。不过对于大多数CTF题目和实际应用场景,pyinstxtractor已经足够应对。
1.3 常见问题与解决方案
在使用pyinstxtractor的过程中,可能会遇到以下几个常见问题:
-
Python版本不匹配:pyinstxtractor需要与目标EXE文件使用的Python版本兼容。如果遇到错误,可以尝试使用不同版本的Python运行pyinstxtractor。
-
文件提取不完整:有时候pyinstxtractor可能无法提取出所有文件,特别是当EXE文件被压缩或加密时。这时可以尝试使用
--debug参数获取更多调试信息。 -
提取出的文件无法识别:PyInstaller可能会对提取出的文件进行重命名或修改,需要根据文件内容和结构进行判断。
针对“ezpython”这道题,解包过程相对顺利。但接下来我们会遇到一个更棘手的问题——提取出的.pyc文件无法直接反编译。
2. pyc文件头修复:从损坏字节码到可反编译文件
从PyInstaller打包的EXE中提取出的.pyc文件通常是不完整的,它们缺少了标准的文件头信息,这就是为什么我们无法直接使用uncompyle6等工具进行反编译。要解决这个问题,我们需要理解.pyc文件的结构,并学会如何修复损坏的文件头。
2.1 pyc文件结构深度解析
一个完整的.pyc文件由三部分组成:Magic Number、时间戳和代码对象。其中Magic Number是最关键的部分,它标识了该.pyc文件是由哪个特定版本的Python编译器生成的。不同Python版本的Magic Number是不同的,如果Magic Number错误或缺失,反编译工具就无法正确解析文件。
| 组成部分 | 大小(字节) | 说明 |
|---|---|---|
| Magic Number | 4 | Python版本的标识符 |
| 时间戳 | 4 | 源文件最后修改时间 |
| 代码对象 | 可变 | 编译后的字节码数据 |
在“ezpython”这道题中,我们从EXE文件中提取出的src文件(实际上是主程序的.pyc文件)缺少了前8个字节的Magic Number和时间戳。而同时提取出的struct文件(Python标准库模块的.pyc文件)则包含了完整的文件头信息。这就给了我们修复的机会——我们可以从struct文件中复制Magic Number和时间戳到src文件中。
2.2 使用十六进制编辑器手动修复
修复.pyc文件头最直接的方法是使用十六进制编辑器,如WinHex、010 Editor或HxD。以下是详细的操作步骤:
-
同时打开struct文件和src文件:在十六进制编辑器中同时打开这两个文件,确保能够看到它们的二进制内容。
-
定位Magic Number的位置:在struct文件中,Magic Number通常位于文件的开头。对于Python 3.4(本题使用的版本),Magic Number的十六进制表示为
EE 0C 0D 0A。但需要注意的是,有些情况下Magic Number可能会有所不同。 -
复制正确的文件头:从struct文件的开头复制前8个字节(Magic Number + 时间戳),然后粘贴到src文件的开头,覆盖原有的内容。
-
保存并重命名文件:将修复后的src文件保存,并为其添加
.pyc后缀,得到src.pyc。
为了更直观地展示修复过程,下面是一个修复前后的对比示例:
# 修复前的src文件(开头部分):
E3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
...
# 修复后的src.pyc文件(开头部分):
EE 0C 0D 0A 5F 2B 1F 5F E3 00 00 00 00 00 00 00
...


5825

被折叠的 条评论
为什么被折叠?



