关于python类逆向总结32

python类逆向

Python运行原理

Python是解释型语言,没有严格意义上的编译和汇编过程。但是一般可以认为编写好的python源文件,由python解释器翻译成以.pyc为结尾的字节码文件。pyc文件是二进制文件,可以由python虚拟机直接运行。

Python在执行import语句时,将会到已设定的path中寻找对应的模块。并且把对应的模块编译成相应的PyCodeObject中间结果,然后创建pyc文件,并将中间结果写入该文件。然后,Python会import这个pyc文件,实际上也就是将pyc文件中的PyCodeObject重新复制到内存中。而被直接运行的python代码一般不会生成pyc文件。

加载模块时,如果同时存在.py和.pyc,Python会尝试使用.pyc,如果.pyc的编译时间早于.py的修改时间,则重新编译.py并更新.pyc。在这里插入图片描述

1.把原始代码编译成字节码

编译后的字节码是特定于Python的一种表现形式,它不是二进制的机器码,需要进一步编译才能被机器执行。如果Python进程在机器上拥有写入权限,那么它将把程序的字节码保存为一个以.pyc 为扩展名的文件,如果Python无法在机器上写入字节码,那么字节码将会在内存中生成并在程序结束时自动丢弃。

2.把编译好的字节码转发到Python虚拟机(PVM)中进行执行

PVM(Python Virtual Machine)是Python的运行引擎,是Python系统的一部分,它是迭代运行字节码指令的一个大循环、一个接一个地完成操作。

Python分发的文件类型

python打包原理

Python代码的基本运行过程:

Python.exe调用XX.py(源码),解释并运行。
Python.exe调用XX.pyc(字节码),解释并运行。
Python.exe调用XX.pyd(机器码),调用运行。
如果有依赖的库,根据上面三种情况调用运行。

PyInstaller

原理:分析脚本文件,递归找到所有依赖的模块。如果依赖模块有.pyd文件,即将其复制到disk目录。如果没有.pyd文件,则生成.pyc文件拷贝到disk目录,并压缩为.zip保存。制作一个exe,导入PythonXX.dll(解析器库),并添加exe运行需要的相关运行时库。这就构成了一个不用安装Python的运行包。

如果要生成一个exe,则利用PE结构将整个包加载内存中再来运行。

Nuitka

Nuitka是一个Python编写的Python解释器,所以Nuitka可以作为Python的扩展库使用。Nuitka会将所有没有生成pyd的模块的.py代码转换成C代码,然后调用gcc/MSVC编译成.pyd。

.pyc

.pyc:python编译后的二进制文件,是.py文件经过编译产生的字节码,pyc文件是可以由python虚拟机直接执行的程序(PyCodeObject对象在硬盘上的保存形式)。

pyc文件仅在由另一个.py文件或模块导入时从.py文件创建(import)。触发 pyc 文件生成不仅可以通过 import,还可以通过 py_compile 模块手动生成。

pyc文件会加快程序的加载速度,而不会加快程序的实际执行速度。

pyc文件格式
在这里插入图片描述

pyc文件一般由3个部分组成:

Magic num:标识此pyc的版本信息, 不同的版本的 Magic 都在 Python/import.c 内定义
文件创建时间:UNIX时间戳(从1970.1.1开始计数秒数)
序列化了的 PyCodeObject:此结构在 Include/code.h 内定义,序列化方法在 Python/marshal.c 内定义
Python在不同的版本,pyc的头部长度和内容是不同的:

PEP 3147:
在这里插入图片描述

包含两个32位大端数字,后面跟的序列化的PyCodeObject。32位数字表示一个Magic Num和Timestamp。每当Python改变字节码格式时,Magic Num会改变,例如,通过向其虚拟机添加新的字节码。这确保为以前版本的VM构建的pyc文件不会造成问题。Timestamp用于确保pyc文件与用于创建它的py文件匹配。当Magic Num或Timestamp不匹配时,将重新编译py文件并写入新的pyc文件。

PEP 552:
在这里插入图片描述

pyc头文件目前由3个32位的字组成。我们将把它扩大到4个。第一个单词将继续是magic number,对字节码和pyc格式进行版本控制。第二个4byte新增加的字段,将是一个位字段(bit field),对报头其余部分的解释和pyc的失效行为取决于位字段的内容。

如果位字段(bit field)为0,则pyc是传统的基于时间戳的pyc。即第三个和第四个4字节内容分别是时间戳和文件大小,通过比较源文件的元数据和头文件中的元数据来进行无效判断。

如果位字段的最低位被设置,则pyc是基于哈希的pyc。我们将第二个最低位称为check_source标志。位字段之后是源文件的64位散列。我们将使用带有源文件内容硬编码密钥。

对于Magic值,它的逻辑为:后2bytes为0D0A,前面的值满足: [min, max]范围,版本信息定义参考结构内容,示例分析代码如下:

typedef struct {
unsigned short min;
unsigned short max;

wchar_t version[MAX_VERSION_SIZE];

} PYC_MAGIC;

static PYC_MAGIC magic_values[] = {
{ 50823, 50823, L"2.0" },
{ 60202, 60202, L"2.1" },
{ 60717, 60717, L"2.2" },
{ 62011, 62021, L"2.3" },
{ 62041, 62061, L"2.4" },
{ 62071, 62131, L"2.5" },
{ 62151, 62161, L"2.6" },
{ 62171, 62211, L"2.7" },
{ 3000, 3131, L"3.0" },
{ 3141, 3151, L"3.1" },
{ 3160, 3180, L"3.2" },
{ 3190, 3230, L"3.3" },
{ 3250, 3310, L"3.4" },
{ 3320, 3351, L"3.5" },
{ 3360, 3379, L"3.6" },
{ 3390, 3399, L"3.7" },
{ 3400, 3419, L"3.8" },
{ 0 }
};
序列化
PyObject:PyObject 如何序列化,哪些内容被参与了序列化, 参看 Python/marshal.c 内的函数 w_object 函数

PyCodeObject:

结构体 PyCodeObject 在 Include/code.h 中定义如下:

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; /
代码块在文件中的起始行号 */
{
在 Python 代码中,每个作用域(或者叫block或者名字空间)对应一个 PyCodeObject 对象, 所以会出现嵌套: 比如 一个 module 类 定义了 N 个 class, 每个 class 内又定义了 M 个方法. 每个 子作用域 对应的 PyCodeObject 会出现在它的 父作用域 对应的 PyCodeObject 的 co_consts 字段里。

.pyo
pyo文件是源代码文件经过优化编译后生成的文件,是pyc文件的优化版本,由解释器在导入模块时创建。当我们调用Python解释器时,通过添加“-O”标志来启用优化器,生成.pyo文件。在Python3.5之后,不再使用.pyo文件名,而是生成文件名类似“test.opt-n.pyc的文件。

.pyd
.pyd文件类型是特定于Windows操作系统类平台的,是一个动态链接库,它包含一个或一组Python模块,由其他Python代码调用。要创建.pyd文件,需要创建一个名为example.pyd的模块。在这个模块中,需要创建一个名为PyInit_example()的函数。当程序调用这个库时,它们需要调用import foo, PyInit_example()函数将运行。

.pyz
executable python zip archives

具体内容参见下:ZlibArchive

PyInstaller打包

PyInstaller中使用了两种存档。一个是ZlibArchive,它能高效地存储Python模块,并通过一些导入钩子直接导入。另一个是CArchive,类似于.zip文件,这是一种打包(或压缩)任意数据块的通用方法。

ZlibArchive

ZlibArchive包含压缩的.pyc或.pyo文件。spec文件中的PYZ类调用创建了一个ZlibArchive。

ZlibArchive中的目录是一个Python字典,它的Key(import语句中给定的成员名)与ZlibArchive中的查找位置和长度相关联。ZlibArchive的所有部分都以编组格式存储,因此与平台无关。

ZlibArchive在运行时用于导入绑定的python模块,即使使用最大压缩,也比正常导入快。
在这里插入图片描述

CArchive
CArchive很像一个.zip文件,可以包含任何类型的文件。可以python创建,也可以从C代码中解包。CArchive可以附加到另一个文件,比如ELF和COFF可执行文件,存档是在文件的末尾用它的目录创建的,后面只跟一个cookie,它告诉目录从哪里开始以及存档本身从哪里开始。CArchive可以嵌入到另一个CArchive中,内部存档可以在适当的地方打开使用,而不需要提取。

每个目录条目都有可变的长度。条目中的第一个字段给出了条目的长度。最后一个字段是相应打包文件的名称。名称以空内容结尾,每一个成员都可以进行压缩。还有一个与每个成员相关联的类型代码供程序使用。

Carchive结构
在这里插入图片描述

自解压执行文件结构
在这里插入图片描述

pyc反编译

在cmd里输入python pycname.pyc即可让.pyc文件在相应的python版本环境下运行

uncompyle6
Uncompyle6,是一个Python原生的跨版本反编译器,是decompyle, uncompyle, uncompyle2的后继版本。Uncompyle6能够将Python二进制文件pyc, pyo反编译为对应的源代码。支持 Python 1.0-3.8 版本的反编译,拥有较好的逻辑处理和解析能力。

安装:要在python3.8及之前的版本下载。要想不破坏我现有的3.11版本环境,需要搭建虚拟环境后安装。

虚拟环境安装详细过程见最后

使用:

在已经搭建好虚拟环境的前提下

.\py38\Scripts\activate
#进入虚拟环境

uncompyle6 -o test.py test.pyc
#将test.pyc文件反编译,并输出到test.py文件中
生成的文件会存放在C:\Users\admin目录下,剪切出来即可
在这里插入图片描述

如果遇到错误,可能因为pyc文件生成时,头部的magic number被清理,需要另外补上

pycdc
pycdc 是一个用于 pyc 反编译的工具,适用于 Python 3.9 及更高版本。

在GitHub下载pycdc https://github.com/zrax/pycdc

Linux下下载使用

apt-get install cmake
在下载目录下输入 cmake CMakeLists.tx

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值