学习pyc逆向过程的一个总结
python执行过程
- Python先把代码(.py文件)编译成字节码,交给字节码虚拟机,然后虚拟机一条一条执行字节码指令,从而完成程序的执行。
- 字节码在Python虚拟机程序里对应的是PyCodeObject对象。.pyc文件是字节码在磁盘上的表现形式。
- PyCodeObject对象的创建时机是模块加载的时候,即import。
- Python test.py会对test.py进行编译成字节码并解释执行,但是不会生成test.pyc。
- 如果test.py加载了其他模块,如import util,Python会对util.py进行编译成字节码,生成util.pyc,然后对字节码解释执行。
- 如果想生成test.pyc,我们可以使用Python内置模块py_compile来编译。
- 加载模块时,如果同时存在.py和.pyc,Python会尝试使用.pyc,如果.pyc的编译时间早于.py的修改时间,则重新编译.py并更新.pyc。
具体的pyc文件结构和PyCodeObject对应格式就不在这说了,我总结的还没网上总结的好呢。
这只说几种反编译的方法:
1.uncompyle2最好用的方法
新建一个需要反编译的py文件代码如下
|
|
利用python -m py_compile fun.py
这样就产生了pyc文件了。
再新建一个decom.py文件代码如下:
|
|
这里为什么要先读出前8个字节呢,根据pyc的格式决定的:
- 因为pyc文件的最开始4个字节是一个Maigc int, 标识此pyc的版本信息, 不同的版本的 Magic 都在 Python/import.c 内定义
- 接下来四个字节还是个int,是pyc产生的时间(1970.01.01到产生pyc时候的秒数)
- 接下来是个序列化了的 PyCodeObject(此结构在 Include/code.h 内定义),序列化方法在 Python/marshal.c 内定义
所以这里我们只需要最后的PyCodeObject的内容,所以就先将前8个字节读出来不要了。
marshal.loads()的作用是反序列化,因为从py文件生成为pyc文件的时候,python内部会有一个序列化的步骤,所以我们需要在反编译之前先反序列化一下。
在这里marshal有两个函数可以利用,一个就是上面用到的loads函数,他接收的参数是string,也就是pyc除去前8个字节的string;另一个是load函数,他接收的参数是file,所以上面代码需要改一点如下:
|
|
执行完这个decom.py文件后,就会在当前目中中生成一个fun_decom.py的文件,可以拿这个文件和fun.py文件比较一下,是完全一样的。这样就反编译成功了。
2.pycdc
pycdc是github上的一个c++编写的python反编译开源项目,地址为:https://github.com/zrax/pycdc,他的介绍里面有编译过程,当编译完成后会产生两个可执行程序pycdas和pycdc
pycdc是将pyc文件直接反编译为py文件,使用方法为pycdc fun.pyc 这样反编译好的py文件会直接输出到屏幕上,如果想输出到文件中可以用linux中的重定向符”>”,具体为pycdc fun.pyc > decom.py即可
pycdas是将pyc文件反编译到字节码,输出也是默认到屏幕上。
3.Easy Python Decompiler
这个工具是windows下的python反编译工具。到处都可以下载到,他的内部其实也是利用了uncompyle2模块,然后在上面封装了一下而已。
界面如下:
这是个windows版的工具,可以反编译单个pyc,pyo 文件,或者选定反编译一个指定文件夹下面的pyc,pyo 文件。
反编译的结果的名字为原来的名字+”pyc_dic”, 用文本编辑器打开就可以看到源码
以下是我在反编译程序时遇到的问题
我遇到一个文件大小为300k左右,以前也遇到过这么大的pyc文件都是可以反编译的,但是这个pyc利用上面的Easy Python Decompiler会出错什么输出都没有;利用pycdc可以输出,但是只能输出一大部分,然后会报错;利用uncompyle2会没有反应,刚开始我以为是出错了,但是没有任何反应,只能看见内存一直在增长,以为是有内存泄露什么的,所以就给关了。
最终我抱着试一试的态度,让他一直运行,然后看着内存在疯狂的增长,cpu占用100%,这里说一下我的物理内存是16g,交换分区也是16g,等了差不多两个多小时后,物理内存全部用完,交换分区用了8g左右,然后cpu的占用率就下去了,但是内存没有下去,这样又等了半个小时以后,最终的py文件被完全反编译出来了。
然后看这个py文件,里面有一个字典,它的条目数是12000左右,所以后来分析反编译的过程,得到我的结论是pyc文件中,所有的变量、函数、类、字典类的是数据结构,字符串等都作为反编译的条目,然后这里就会有个递归的过程,学过c语言的都应该知道,如果是数量级大的递归的话需要消耗非常多的内存。这里就是这样的。所以说反编译还是最好用python的uncompyle2模块。
下面是一个解析pyc文件到xml的代码,我就是从这里面的stacksize猜测出需要递归的:
|
|
使用方法为python showfile.py fun.pyc > fun.xml
,里面可以详细的看出pyc的结构,这方面的只是就需要去深入了解pyc的文件结构了