python 逆向笔记

PYC 文件

pyc 文件是 python 在编译过程中出现的主要中间过程文件。pyc 文件是二进制的,类似 java 的字节码,可以由 python 虚拟机直接执行的。

PyCodeObject

实际上,pyc 文件就是 PyCodeObject 对象在硬盘上的保存形式。

而 PyCodeObject 的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef struct {
PyObject_HEAD
int co_argcount; /* 位置参数个数 */
int co_nlocals; /* 局部变量个数 */
int co_stacksize; /* 栈大小 */
int co_flags;
PyObject *co_code; /* 字节码指令序列 */
PyObject *co_consts; /* 所有常量集合 */
PyObject *co_names; /* 所有符号名称集合 */
PyObject *co_varnames; /* 局部变量名称集合 */
PyObject *co_freevars; /* 闭包用的的变量名集合 */
PyObject *co_cellvars; /* 内部嵌套函数引用的变量名集合 */
/* The rest doesn’t count for hash/cmp */
PyObject *co_filename; /* 代码所在文件名 */
PyObject *co_name; /* 模块名|函数名|类名 */
int co_firstlineno; /* 代码块在文件中的起始行号 */
PyObject *co_lnotab; /* 字节码指令和行号的对应关系 */
void *co_zombieframe; /* for optimization only (see frameobject.c) */
} PyCodeObject;

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 动态调试跟静态分析。