python⽂件编译与pyc反编译
pyc是编译py之后⽣成的⼆进制⽂件。当我们发布系统的时候不想让别⼈看到源代码,就需要将py⽂件编译⽣成pyc⽂件,对外只提供pyc ⽂件。同样,如果拿到⼀个python程序,只有pyc⽂件,我们就⽆法看到源码,希望有办法反编译pyc⽂件以获得源码。
既然有反编译的需求,为了保护pyc⽂件不被反编译,我们⼜希望到⼀个⽅法来防⽌pyc⽂件被反编译。
本⽂介绍了如何将py⽂件编译成pyc和pyo⽂件,也介绍了如何反编译pyc⽂件, ⼜给出了⼀种字节码混淆⽅法来防⽌pyc⽂件被反编译。
1 编译py⽂件到pyc和pyo
pyc是编译py之后⽣成的⼆进制⽂件,由python虚拟机来执⾏的。当我们发布系统的时候不想让别⼈看到源代码,就需要将py⽂件编译⽣成pyc⽂件,对外只提供pyc⽂件。
同时,在模块被加载时,.pyc⽂件⽐.py⽂件更快
但是pyc的内容跟python的版本相关,不同的版本编译后的pyc⽂件不同,2.5编译的pyc⽂件不能到3.5上执⾏.
发布python软件的过程为:
1. ⽣产pyc⽂件: python -m compileall .
2. 删除py⽂件: find . -name “*.py” |xargs rm -rf
3. 删除pycache⽬录: find . -name “pycache” |xargs rm -rf
1.1 Python2 编译
命令:
python -m py_compile file.py
python -m py_compile {file1,file2}.py
编译完成后,⽣成的 .pyc⽂件在当前⽬录。
可以使⽤-O或者-OO转换python命令来减少编译模块的⼤⼩
-O转换会帮你去掉assert语句
-
OO转换会帮你去掉assert语句和__doc__⽂档字符串
由于⼀些程序可能依赖于assert语句或⽂档字符串,应该在确认需要的情况下使⽤这些选项。
例如 python –O -m py_compile file.py 会在本⽬录⽣成 file.pyo
1.2 python3 编译
Python3的编译和python2⼀样,只是在python3之后编译后的⽂件在pycache⽬录下,⽽不是与源⽂件同⼀⽬录。
那么如何让python3实现⽣成的pyc与源代码在同⼀⽬录呢?可以加上-b参数,例如
Python3 -m py_compile -b file.py
1.3 批量编译
python -m compileall DIR
python3 -m compileall –b DIR
DIR为需要编译的⽬录
例如: python -m compileall .
参数说明:
usage: python compileall.py [-l] [-f] [-q] [-d destdir] [-x regexp] [-i list] [directory|file ...]
arguments: zero or more file and directory names to compile; if no arguments given,
defaults to the equivalent of -l sys.path
options:
-l: don't recurse into subdirectories
-f: force rebuild even if timestamps are up-to-date
-q: output only error messages
-d destdir: directory to prepend to file paths for use in compile-time tracebacks and in
runtime tracebacks in cases where the source file is unavailable
-
x regexp: skip files matching the regular expression regexp; the regexp is searched for
in the full path of each file considered for compilation
-i file: add all the files and directories listed in file to the list considered for
compilation; if "-", names are read from stdin
1.4 代码实现
单⽂件编译:
import py_compile
py_compilepile('path') //path是包括.py⽂件名的路径
批量编译:
import compileall
compileallpile_dir('$dir')
2反编译pyc
如果只有pyc⽂件,我们是⽆法直接查看内容的,这时,就需要反编译⼯具将pyc⽂件反编译成py源程序。 这⾥介绍uncompyle6反编译⼯具。Pyc⽂件能够反编译的前提是⽂件没有被加密或者代码混淆。
2.1 Uncompyle6安装
> git checkout python-2.4
> sudo python setup.py install
如果没有报错,则安装成功了。
2.2反编译
执⾏命令:
>uncompyle6 -o . *.pyc
这时会在当前⽬录⽣成.py源⽂件。
3 防⽌反编译pyc⽂件
如何防⽌pyc⽂件被反编译呢? 这⾥介绍字节码混淆的⽅法,此⽅法能够抵挡低端的反编译⼿段,例如第2章的⽅法,但是,要到⾼⼿,还是抵挡不住。
字节码混淆可以⾮常容易的欺骗通常的反汇编器和反编译器,同时不影响代码的正常执⾏。下⾯这个例⼦展⽰了如何欺骗Uncompyle6反编译器以及dis反汇编器:
#⼀个简单的Python应⽤ sample1.py
print 'Hello World'
对其进⾏编译:
python -m py_compile sample1.py
对编译后的sample1.pyc使⽤Python内置dis模块反汇编:
>>> import marshal,dis
>>> fd = open('sample1.pyc', 'rb')
>>> fd.seek(8)
>>> sample1_code_obj = marshal.load(fd)
>>> fd.close()
>>> dis.dis(sample1_code_obj)
1 0 LOAD_CONST 0 ('Hello World')
3 PRINT_ITEM
4 PRINT_NEWLINE
5 LOAD_CONST 1 (None)
8 RETURN_VALUE
>>>
以上的汇编代码笔者⾁眼反汇编的结果如下:
0 LOAD_CONST 0 ('Hello World') #加载co_consts[0]到栈顶,co_consts[0]存储着常量字符串'Hello World'
3 PRINT_ITEM #打印栈顶到sys.stdout,即print 'Hello World'
4 PRINT_NEWLINE #打印新⾏到sys.stdout,此指令因print语句⽽由编译器⾃动⽣成
5 LOAD_CONST 1 (None) #加载co_consts[1]到栈顶,co_consts[1]存储着None
8 RETURN_VALUE #将栈顶返回给调⽤者,此两条指令为编译器⾃动⽣成
现在我们修改sample1.pyc,在程序⼊⼝增加⼀条绝对跳转指令(可以使⽤UltraEdit 16进制插⼊功能修改pyc⽂
件,”JUMP_ABSOLUTE 3”在Python 2.7中对应的字节码为 0x71 0x03 0x00。修改code string内容的同时应修改code string的长度,此处增加了⼀个3字节指令),使⽤内置dis模块反汇编的结果如下:
python怎么读取py文件1 0 JUMP_ABSOLUTE 3 #⾃⾏添加
>> 3 LOAD_CONST 0 ('Hello World')
6 PRINT_ITEM
7 PRINT_NEWLINE
8 LOAD_CONST 1 (None)
11 RETURN_VALUE
如果读者对汇编代码有⼀定认识,就会明⽩此处的绝对跳转对Python虚拟机执⾏此程序基本没有影响(除了增加⼀个指令执⾏周期),然⽽这个绝对跳转将成功欺骗反编译器。使⽤Uncompyle6反编译的结果如下:
<<< Error: Decompiling stopped due to <class 'uncompyle6.semantics.pysource.ParserError'>
如果⼀个pyc⽂件⽆法被反编译,初级的破解者可能就会⽌步于此了,但对于有经验的⼯程师来说这还远远不够。同样的,我们还要让通常的反汇编器也⽆法⼯作才⾏。按下⾯的汇编代码继续加⼯上⾯的sample1.pyc。
| 1 0 JUMP_ABSOLUTE [71 06 00] 6
| 3 LOAD_CONST [64 FF FF] 65535 (FAKE!)
| >> 6 LOAD_CONST [64 00 00] 0 (Hello World)
| 9 PRINT_ITEM [47 -- --]
| 10 PRINT_NEWLINE [48 -- --]
| 11 LOAD_CONST [64 01 00] 1 (None)
| 14 RETURN_VALUE [53 -- --]
以上第⼆条指令的意思是加载code object常量表的第65535项到栈顶。在上述sample1.pyc中,常量表的长度为2,下标65535已超出常量表的范围,所以这是条⾮法指令。但由于第⼀条绝对跳转的存在,第⼆条指令永远都不会被执⾏。通常的反汇编器如dis会尽全⼒列举有⽤的信息,但并不能理解实际执⾏的控制流,当反汇编器尝试反汇编第⼆条指令时,会试着去读取code object常量表的第65535项并且抛出⼀个’tuple index out of range’的意外。Python内置dis模块的出错信息如下:
>>> fd = open('sample1.pyc', 'rb')
>>> fd.seek(8)
>>> import marshal,dis
>>> sample1_code_obj = marshal.load(fd)
>>> dis.dis(sample1_code_obj)
1 0 JUMP_ABSOLUTE 6
3 LOAD_CONST 65535
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Python27\lib\dis.py", line 43, in dis
disassemble(x)
File "C:\Python27\lib\dis.py", line 96, in disassemble print '(' + _consts[oparg]) + ')',
IndexError: tuple index out of range
>>>
现在Uncompyle6和dis都被欺骗了,代码得到了有效的保护。
4 Reference
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论