python 逆向笔记
PYC 文件
pyc 文件是 python 在编译过程中出现的主要中间过程文件。pyc 文件是二进制的,类似 java 的字节码,可以由 python 虚拟机直接执行的。
PyCodeObject
实际上,pyc 文件就是 PyCodeObject 对象在硬盘上的保存形式。
而 PyCodeObject 的结构如下:
1 | typedef struct { |
PyObject_HEAD
不同的 Python 版本会有不同的 PyObject_HEAD,以下是各版本的文件头:
Python 版本 | 十六进制文件头 |
---|---|
Python 2.7 | 03f30d0a00000000 |
Python 3.0 | 3b0c0d0a00000000 |
Python 3.1 | 4f0c0d0a00000000 |
Python 3.2 | 6c0c0d0a00000000 |
Python 3.3 | 9e0c0d0a0000000000000000 |
Python 3.4 | ee0c0d0a0000000000000000 |
Python 3.5 | 170d0d0a0000000000000000 |
Python 3.6 | 330d0d0a0000000000000000 |
Python 3.7 | 420d0d0a000000000000000000000000 |
Python 3.8 | 550d0d0a000000000000000000000000 |
Python 3.9 | 610d0d0a000000000000000000000000 |
Python 3.10 | 6f0d0d0a000000000000000000000000 |
Python 3.11 | a70d0d0a000000000000000000000000 |
可以注意到,实际上有改变的只有开头的两个字节。
反编译
使用 uncompyle 可以将 pyc 文件完美反编译。uncompyle6 Github主页
可以直接使用 pip 包管理工具安装:
1 | pip3 install uncompyle |
而 uncompyle 用法如下:
1 | uncompyle6 *.pyc |
uncomyle6 会直接将反编译后的源码输出在标准输出中,推荐用法:
1 | uncompyle6 *.pyc > filename |
将源码输出到文件里面,比如说:
1 | uncompyle6 test.pyc > test.py |
有时候 uncompyle6 可能出于某些原因,反编译失败,这时可以使用在线网站进行反编译。
有的时候对于某些源码可能无法完全反编译成功,这个时候需要借助工具 pycdas 反字节码。
pycdas 需要在 GitHub 上克隆仓库 pycdc 的源码后编译获得。
反反编译
更改魔术头
当想要保护我们的 pyc 文件不被反编译,最简单的做法就是更改魔术头,即 PyObject_HEAD。
可能是完全删除魔术头,也可能是修改为不是原生版本的魔术头,我们只需要根据情况添加或修改即可。
有关魔术头可以直接参考 PyObject_HEAD
EXE 文件
使用 PyInstaller 进行封装的 exe 可执行文件,可以使用 pyinstxtractor 进行反编译。pyinstxtractor Github主页
基本用法:
1 | python3 pyinstxtractor.py *.exe |
pyinstxtractor 会将 exe 文件拆分回单独的 pyc 文件、pyd 文件,并且会在 pyc 文件中加入当前 python 版本的魔术头 (如果原代码版本不是所使用的版本,会有 warning 提示,需要注意)。
特别地,如果 python 版本与 exe 版本不对,会导致 pyz 无法解压,这时候需要改用基于 xdis 版本的 pyinstxtractor。
PYD 文件
pyd 文件相当于 python 的运行时 dll,在 python 代码中可以直接使用
import
将 pyd 文件当作模块导入。
对于 pyd 的逆向,我们需要借助 ida 的 attach
动态调试跟静态分析。