关于pyc文件的逆向
最近感觉遇到的pyc文件逆向的越来越多了,所以就来总结下。//参考了大佬的blog:http://mozhucy.cn/2018/11/26/python_pyc/
0x1 pyc的文件结构
在命令行输入 python -m filename.py的时候,便会得到一个对应的filename.pyc。
拖进hxd中看二进制。其中,开头的4个字节是magic number,相对比较固定,是标识的此pyc的版本信息,不同的版本的magic在Python/import.c中定义。
而之后4个字节是小端序的时间戳。
之后的字节是个0x63,也就是PyCodeObject的标识符,随后是小端序的co_argument(参数个数),co_nlocals(变量个数),co_stacksize(栈空间)和co_flags(特殊标志)特殊标志的flags:
#define CO_OPTIMIZED 0x0001
#define CO_NEWLOCALS 0x0002
#define CO_VARARGS 0x0004
#define CO_VARKEYWORDS 0x0008
#define CO_NESTED 0x0010
#define CO_GENERATOR 0x0020
#define CO_NOFREE 0x0040
#define CO_FUTURE_DIVISION 0x2000
#define CO_FUTURE_ABSOLUTE_IMPORT 0x4000
#define CO_FUTURE_WITH_STATEMENT 0x8000
#define CO_FUTURE_PRINT_FUNCTION 0x10000
#define CO_FUTURE_UNICODE_LITERALS 0x20000
之后是opcode,先是0x73,之后接4个字节的小端序的数字,表示opcode所占的总的字节//在我的个人理解中,opcode跟我们用c之类的编写的反编译出来的汇编差不多。
之后,一个字节表示存储的类型,之后四个字节表示该类型占的空间(小端序表示)(例如如果是小端序的0x40,那之后的0x40个字节就是他存储的内容)以此类推。继续从大佬blog中拖数据类型标识:
#define TYPE_NULL ‘0’
#define TYPE_NONE ‘N’
#define TYPE_FALSE ‘F’
#define TYPE_TRUE ‘T’
#define TYPE_STOPITER ‘S’
#define TYPE_ELLIPSIS ‘.’
#define TYPE_INT ‘i’
#define TYPE_INT64 ‘I’
#define TYPE_FLOAT ‘f’
#define TYPE_BINARY_FLOAT ‘g’
#define TYPE_COMPLEX ‘x’
#define TYPE_BINARY_COMPLEX ‘y’
#define TYPE_LONG ‘l’
#define TYPE_STRING ‘s’
#define TYPE_INTERNED ‘t’
#define TYPE_STRINGREF ‘R’
#define TYPE_TUPLE ‘(’
#define TYPE_LIST ‘[’
#define TYPE_DICT ‘{’
#define TYPE_CODE ‘c’
#define TYPE_UNICODE ‘u’
#define TYPE_UNKNOWN ‘?’
#define TYPE_SET ‘<’
#define TYPE_FROZENSET ‘>’
0x2 pyc文件反编译的常用工具
最常用的就是uncompyle6,用uncompyle6 -o filename.py filename.pyc这个命令就可以了,当然,在网上也有一些把uncompyle6这种工具开出gui界面的,这个自行搜索。
0x3 出现反编译失败的情况
以hgame的pro的python教室(三&四)为例子
先查看发现magic number之类的没有错,这里介绍一个叫marshal的py库,使用如下:
import dis,marshal
f = open(“third.pyc”, “rb”)
magic = f.read(4)
mtime = f.read(4)
dis.dis(marshal.load(f)),
在偏移13的位置出现了LOAD_CONST 100,然后,py返回错误。
来看下这边的opcode的表达:
0 JUMP_ABSOLUTE 3
3 JUMP_ABSOLUTE 9
6 LOAD_CONST 15 ("You’re Wrong! ")
9 JUMP_ABSOLUTE 14
12 PRINT_ITEM
13 LOAD_CONST 100
好的,有个load_const 100的操作,这个是溢出了范围的,但是在这上面有个jump 14的操作,所以其实12,13根本就不会执行(其实这也是一种常见的防反编译的方法,在程序中加入一段跳转到不存在的地方的操作,但是这个操作正常执行根本不会执行,所以程序运行没有一点毛病,但是反编译工具却无法进行下去了。也就是花指令了)那么就可以直接把它删了,外加改下其他的定义的长度,使得符合上述的规则就ok了。
此外,还有那些用py文件编译出的exe文件,这些文件一般是用py extractor这个工具来进行反编译。