python逆向基础
一..python的运行机制
(1)、把原始代码编译成字节码
编译后的字节码是特定于Python的一种表现形式,它不是二进制的机器码,需要进一步编译才能被机器执行,这也是Python代码无法运行的像C/C++ 一样快的原因。如果Python进程在机器上拥有写入权限,那么它将把程序的字节码保存为一个以.pyc 为扩展名的文件,如果Python无法在机器上写入字节码,那么字节码将会在内存中生成并在程序结束时自动丢弃。在构建程序的时候最好给Python赋上在计算机上写的权限,这样只要源代码没有改变,生成的.pyc文件可以重复利用,提高执行效率。
(2)、把编译好的字节码转发到Python虚拟机(PVM)中进行执行
PVM是 Python Virtual Machine的简称,它是Python的运行引擎,是Python系统的一部分,它是迭代运行字节码指令的一个大循环、一个接一个地完成操作。
二.python源码的保护及对抗
保护 | 对抗 |
---|---|
生成pyc文件 | Uncompyle6支持通过字节码逆向出全版本python程序源码 |
pyc源码混淆 | 去混淆后Uncompyle6 |
打包成可执行的二进制文件 | 使用脚本解包 |
自定义opcode的python解释器 | 对抗较为困难,可以通过执行过程进行分析 |
python逆向所需的工具
uncompyle6是一个原生python的跨版本反编译器和fragment反编译器
安装库
安装完它会直接设置在环境变量里,可以直接使用
uncompyle6 -o pcat.py pcat.pyc
python的pyc文件,python2与python3头有所不同,python有8字节的头,而python3有16字节的头,主要是魔术字(python版本)和时间属性的二进制信息
Python源码中的PyCodeObject
/* Bytecode object */
typedef struct {
PyObject_HEAD
int co_argcount; /* 位置参数个数 */
int co_kwonlyargcount; /* keyword only arguments */
int co_nlocals; /* 局部变量个数 */
int co_stacksize; /* 栈大小 */
int co_flags; /* CO_..., see below */
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 or comparisons */
unsigned char *co_cell2arg; /* Maps cell vars which are arguments. */
PyObject *co_filename; /* 代码所在文件名 */
PyObject *co_name; /* 模块|函数名|类名 */
int co_firstlineno; /* 代码块在文件中的起始行号 */
PyObject *co_lnotab; /* 字节码指令和行号对应关系 */
void *co_zombieframe; /* for optimization only (see frameobject.c) */
PyObject *co_weakreflist; /* to support weakrefs to code objects */
} PyCodeObject;
获取python的‘汇编’
>>> import dis
>>> def myfun(i):
... i = i+1
... print(i)
... return i
...
>>> dis.dis(myfun)
2 0 LOAD_FAST 0 (i)
2 LOAD_CONST 1 (1)
4 BINARY_ADD
6 STORE_FAST 0 (i)
3 8 LOAD_GLOBAL 0 (print)
10 LOAD_FAST 0 (i)
12 CALL_FUNCTION 1
14 POP_TOP
4 16 LOAD_FAST 0 (i)
18 RETURN_VALUE
>>>
>>> import marshal,dis
>>> f = open(r'D:\__pycache__\1.cpython-37.pyc','rb').read()
>>> f
b'B\r\r\n\x00\x00\x00\x00\x0e\x11\x19`/\x00\x00\x00\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00@\x00\x00\x00s\x0c\x00\x00\x00d\x00d\x01\x84\x00Z\x00d\x02S\x00)\x03c\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00C\x00\x00\x00s\x14\x00\x00\x00|\x00d\x01\x17\x00}\x00t\x00|\x00\x83\x01\x01\x00|\x00S\x00)\x02N\xe9\x01\x00\x00\x00)\x01\xda\x05print)\x01\xda\x01i\xa9\x00r\x04\x00\x00\x00\xfa\x07D:\\1.py\xda\x05myfun\x01\x00\x00\x00s\x06\x00\x00\x00\x00\x01\x08\x01\x08\x01r\x06\x00\x00\x00N)\x01r\x06\x00\x00\x00r\x04\x00\x00\x00r\x04\x00\x00\x00r\x04\x00\x00\x00r\x05\x00\x00\x00\xda\x08<module>\x01\x00\x00\x00s\x00\x00\x00\x00'
>>> code = marshal.loads(f[16:])
>>> dis.dis(code.co_code)
0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (1)
4 MAKE_FUNCTION 0
6 STORE_NAME 0 (0)
8 LOAD_CONST 2 (2)
10 RETURN_VALUE
用uncompyle6反编译源码
uncompyle6 D:\__pycache__\1.cpython-37.pyc
# uncompyle6 version 3.7.4
# Python bytecode 3.7 (3394)
# Decompiled from: Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)]
# Embedded file name: D:\1.py
# Compiled at: 2021-02-02 16:45:02
# Size of source mod 2**32: 47 bytes
def myfun():
i = i + 1
print(i)
return i
# okay decompiling D:\__pycache__\1.cpython-37.pyc
可以通过对这些字节码的理解手动去分析代码,也可以直接用uncompyle6进行分析,当然出题者肯定不会这么容易解出题目,保护的方法有很多,可以添加花指令等