Python pyc文件格式解析

admin 2024年2月19日14:01:25评论15 views字数 3123阅读10分24秒阅读模式

Python pyc文件格式解析

这篇文章来讲解对于Python中的可执行文件格式,他常常出现在python的缓存中,作为中间语言来指导机器正确的执行python代码。而有部分场景中有些使用者为了自身的一些特殊需求,如逆向被编译成exe的python程序、版权保护、免杀、CTF出题等原因。而直接将python打包成pyc,至于pyc究竟是如何实现的,它到底是一段python代码还是二进制代码,我们将通过python源码来逐步分析pyc的文件格式和执行流程。

总       述

众所周知python是一个开源项目,他的源码在https://github.com/python/cpython。

同搜索关键字.pyc找到maybe_pyc_file

https://github.com/python/cpython/blob/43a6e4fa4934fcc0cbd83f7f3dc1b23a5f79f24b/Python/pythonrun.c#L318

通过方法名得知,这是一个判断是否为pyc文件的通用函数。

pyc运行环境分析

1、首先通过PyUnicode_Tailmatch后缀判断是否为pyc文件。

Python pyc文件格式解析

如果后缀不一致,随后通过PyImport_GetMagicNumber() &0xFFFF与文件开头的两个字节对比。

Python pyc文件格式解析

2、我们可以通过如下方法得知当前版本的Magic值是多少:

import importlib

importlib._bootstrap_external._RAW_MAGIC_NUMBER

Python pyc文件格式解析

3、找一个当前版本python的缓存pyc文件:(注意大小端)

Python pyc文件格式解析

4、随后查找哪里使用了这个PyUnicode_Tailmatch方法:

找到一个名为pyrun_simple_file的方法 位于pythonrun.c

https://github.com/python/cpython/blob/43a6e4fa4934fcc0cbd83f7f3dc1b23a5f79f24b/Python/pythonrun.c#L396

在他之前是对环境的一些初始化工作:

Python pyc文件格式解析

5、之后通过是否为pyc文件来执行不同的逻辑,我们只关注是pyc时候的情况。

Python pyc文件格式解析

6、进入run_pyc_file中,从这里开始就是真正开始分析并执行pyc的逻辑了。

Python pyc文件格式解析
Python pyc文件格式解析

7、开头通过magic == PyImport_GetMagicNumber()判断文件头格式,PyImport_GetMagicNumber在之前的maybe_pyc_file中已经分析过,它实际上执行的代码对应着的是:importlib._bootstrap_external._RAW_MAGIC_NUMBER。

随后跳过了三个Long,此处PyMarshal_ReadLongFromFile的Long指的是4个字节,这表示实际上执行pyc只会关注开头四个字节的头部,其余部分实际上是无效的。

之前用过pyinstxtractor的朋友可能经常去添加这个头部,网上大多数方法都是通过复制头部过去,实际上可以通过我们之前说的importlib._bootstrap_external._RAW_MAGIC_NUMBER加上没有意义的三个Long就可以运行了。当然在这之前有可能这些是有意义的,但在后面版本被废弃了,所以加上没有意义的三个Long可能不适用于所有版本。

8、在这之后便是两个十分重要的方法:

PyMarshal_ReadLastObjectFromFile和run_eval_code_obj

Python pyc文件格式解析

(1)先深入PyMarshal_ReadLastObjectFromFile:

此函数直接将整个文件读出来,然后传入了PyMarshal_ReadObjectFromString。

Python pyc文件格式解析

此函数初始化PyObject对象,将文件数据的引用一并存入,并将其传递给read_object方法进一步通过文件数据初始化PyObject对象。

Python pyc文件格式解析

(2)read_object方法:

此方法对PyObject的文件配置进行了检测,随后传入r_object。

Python pyc文件格式解析

9、随后我们来到了无比庞大的指令解析部分,通过方法内庞大的switch(type)能确定我们没有来错地方,这之中表示在python中的所有对象的底层元数据类型和解析方式。

Python pyc文件格式解析

10、但这还不够,我们需要知道不同类型之间的组合方式:

首先看看switch之前有哪些代码,开头的代码片段在多分支代码中是非常重要的一个部分。

Python pyc文件格式解析

11、首先通过r_byte从缓冲区中取出一个字节作为code。

开头一个小检查,确认当前没有EOF。随后给depth加一,然后和MAX_MARSHAL_STACK_DEPTH对比。由此得出depth是一个栈大小的计数。

12、随后通过取出的一个字节将它分为两个部分,分别为flag与type。

其中FLAG_REF被定义为 0x80。

13、然后通过type进行switch分支跳转,不同的分支会产生不同的返回值,并作为统一的对象PyObject*返回给调用者。

为了更好的展示它的结构,随机选择一个3.9的pyc文件用010Editor打开。

按照3.9的pyc文件格式我修正了原先010Editor库中的pyc解析模板。此模板将在文件末尾提供。

Python pyc文件格式解析

14、magic表示之前的pyc文件头。skip表示跳过的那三个头部数据,Data的type则是r_object第一个r_byte出来的那个code,被分为type和flag,此处值为E3抛弃掉flag位带入到switch:

Python pyc文件格式解析
Python pyc文件格式解析

15、找到case TYPE_CODE,代码中有各种数据的读取:

Python pyc文件格式解析

16、通过010Editor统计,内部有一些运行环境参数、代码段,静态变量段,名称段等等。表示这段代码使用到的任何东西。

可以看到代码段等类型都是PyObject并且再次使用r_object来调用,构成了一个递归。

Python pyc文件格式解析

17、除了TYPE_CODE以外还有很多基本数据结构,其中包含了python里面所有的数据结构。由于代码量太多了此处仅介绍部分常见结构。

(1)0TYPE_INT是直接读取一个long:

Python pyc文件格式解析

(2)TYPE_STRING是先读取一个long n,随后再通过r_string读取n个字节:

Python pyc文件格式解析

(3)TYPE_TUPLE 与TYPE_STRING的区别是读取n个PyObject:

Python pyc文件格式解析

18、我们回到TYPE_CODE:

由图可知,实际上他的成员code就是一个TYPE_STRING,它被当作字节序列来使用,一共是626个字节的长度。

Python pyc文件格式解析

这里面存放着很多的指令。

Python pyc文件格式解析

19、随后是TYPE_CODE中的consts段,在这个段里有许多代码环境中用到的常量。包括字符串、整数、浮点数、元组、列表等常量,同时也包含了子函数在内。

Python pyc文件格式解析

子函数也可以有code和consts,可以这样递归下去:

Python pyc文件格式解析

20、其次是names,其中包含了代码中会用到的库名,方法名,类名等。它们的类型通常是TYPE_ASCII系列或者TYPE_REF。(TYPE_REF用于引用其他ref_flag为1的变量,人工找的话不是很方便)

Python pyc文件格式解析

下面三个理论上是变量名的集合,但是全通过TYPE_REF引用了别处的值。

Python pyc文件格式解析

21、随后是文件名,与名称。文件名同样引用了别处的字符串。

Python pyc文件格式解析

就是这样一层一层的递归造就了整个pyc的运行环境。

结  束  语

至此pyc文件格式就结束了,整个pyc文件都围绕着一个递归构建的PyObject。它能表示python的基本数据结构,表示函数结构,表示代码等python的一切。而下一篇文章讲解python是如何使用PyObject来加载,来构建执行环境,来执行其中的代码的。

Python pyc文件格式解析

END

原文始发于微信公众号(锋刃科技):Python pyc文件格式解析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年2月19日14:01:25
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Python pyc文件格式解析http://cn-sec.com/archives/2124583.html

发表评论

匿名网友 填写信息