python編譯&反編譯,你不知道的心機與陷阱


 談到python的文件后綴,說眼花繚亂也不為過.來看看你遇到過哪些類型!

.py      

如果這個不知道,呵呵…那請出門左拐,你還是充錢那個少年,沒有一絲絲改變。接着打游戲去吧…

.pyc      

這個后綴應該算是除了python的py代碼外,遇到最多的一種文件類型了。雖然python被普遍認為是一種解釋性語言,但誰說它就不能被編譯后執行呢?python通過compile生成的pyc文件,然后由python的虛擬機執行。相對於py文件來說,編譯成pyc本質上和py沒有太大區別,只是對於這個模塊的加載速度提高了,並沒有提高代碼的執行速度,通常情況下不用主動去編譯pyc文件。那pyc文件存在的意義在哪里?
除了略微提高的模塊加載速度外,更多的當然是為了一定意義上的保護源碼不被泄露了。
當然為什么說一定意義?因為既然有編譯,就畢竟存在反編譯嘍,這個一會兒說…
如何將py文件編譯成pyc文件呢?方法有三:

1.使用python直接編譯

python -m sample.py

 2. py_compile

import py_compile
py_compile.compile('sample.py')

3. compileall

import compileall
compileall.compile_file('sample.py')
compileall.compile_dir(dirpath)

三種方式一看便知,區別在於compileall可以一次遞歸編譯文件夾下所有的py文件

.pyo      

pyo是源文件優化編譯后的文件,pyo文件在大小上,一般小於等於pyc文件。這個優化沒有多大作用,只是移除了斷言。原文如下:

When the Python interpreter is invoked with the -O flag, optimized code is generated and stored in .pyo files. The optimizer currently doesn’t help much; it only removes assert statements. When -O is used, all bytecode is optimized; .pyc files are ignored and .py files are compiled to optimized bytecode.

使用方式:python -O -m py_compile sample.py

.pyd    

看到了前幾種,相信d這個字母大家猜猜就能想到,它是python的動態鏈接庫;動態鏈接庫(DLL)文件是一種可執行文件,允許程序共享執行特殊任務所必需的代碼和其他資源;你在python安裝目錄的DLL文件夾下,就可以看到它們了。

.pyz(w)

從Python 3.5開始,定義了.pyz和.pyzw分別作為“Python Zip應用”和“Windows下Python Zip應用”的擴展名。
新增了內置zipapp模塊來進行簡單的管理,可以用Zip打包Python程序到一個可執行.pyz文件。
使用也比較簡單,但無法想pyinstaller等打包exe工具一樣脫離python環境單獨運行,而且這類文件往往拿文本編輯器就能看到它的源碼信息,總體來說還是比較雞肋。

.exe      

為什么會再單獨提到exe文件,因為python有很多可以將python源碼打包成exe的工具,從而脫離python環境單獨運行。感興趣的朋友可以去看看我之前總結的文章:Python打包工具--Pyinstaller詳細介紹

python反編譯

所謂道高一尺魔高一丈,不管是python、java還是C語言,只要有編譯,必然存在反編譯的操作。不管再怎么去考慮加密,勢必都有人能破解,只是成本不同而已,所以編譯只可作為一種防君子不防小人的操作。今天主要和大家聊聊pyc文件的反編譯,其中有哪些小心機的做法與陷阱。
首先,反編譯既然是一種非正常手段,python自然不會原生附帶該模塊,我們需要安裝它:
pip install uncompyle6
操作呢,也比較簡單命令行下輸入:
uncompyle6 sample.pyc(pyo) > sample.py
這就完了?說好的心機與陷阱呢?哈哈,接着看…

使用注釋的心機

我們來創建一個名為BreezePython.py的文件,並將下面的代碼進行編譯和反編譯,來看看反編譯后的內容與原始代碼有什么差異

# -*- coding: utf-8 -*-
# @Author : 王翔
# @微信號 : King_Uranus
# @公眾號 : 清風Python
# @GitHub : https://github.com/BreezePython
# @Date : 2019/12/15 11:16
# @Software : PyCharm
# @version :Python 3.7.3
# @File : tmp.py

import platform

def test():
    """
    這是一個測試方法,用來驗證反編譯
    :return: None
    """
    # 打印系統詳情
    print(platform.platform())

test()

編譯:

python -m BreezePython.py

反編譯:
uncompyle6 BreezePython.cpython-37.pyc > output.py
下來看看反編譯后的文件有什么區別吧:

# uncompyle6 version 3.6.4
# Python bytecode 3.7 (3394)
# Decompiled from: Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: C:\Users\Administrator\Desktop\uncompile\BreezePython.py
# Size of source mod 2**32: 453 bytes
import platform

def test():
    """
    這是一個測試方法,用來驗證反編譯
    :return: None
    """
    print(platform.platform())


test()
# okay decompiling BreezePython.cpython-37.pyc

相比於原始的代碼,有什么差別?沒錯,注釋…文檔注釋默認會保留,但使用# 進行的注釋,卻在編譯+反編譯的過程中丟失了。那么如果你是個心機boy,代碼注釋都用#去寫吧,即便反編譯了代碼,沒有注釋的情況下,即便我們自己也會被大段沒有注釋的代碼搞瘋掉了,哈哈。

反編譯的陷阱

反編譯就萬無一失么?NO…舉一個例子吧,這段代碼隨意而為,只是為了讓反編譯的時候拋出錯誤:

class Demo:
    def __init__(self, color):
        self.color = color

    def jduge(self):
        if not self.color:
            return "get None"
        if not self.color == 'red':
            mood = "disappointed"
            return "I'm %s,I like white best" % mood
        return "get None..."


if __name__ == '__main__':
    Main = Demo("red")
    print(Main.jduge())

正常情況這段代碼應該返回:get None…
但這段代碼反編譯后成了什么樣子?

# uncompyle6 version 3.6.4
# Python bytecode 3.7 (3394)
# Decompiled from: Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: C:\Users\Administrator\Desktop\uncompile\BreezePython.py
# Size of source mod 2**32: 391 bytes

class Demo:
    def __init__(self, color):
        self.color = color
    def jduge(self):
        if not self.color:
            return 'get None'
        else:
            mood = self.color == 'red' or 'disappointed'
            return "I'm %s,I like white best" % mood
        return 'get None...'

if __name__ == '__main__':
    Main = Demo('red')
    print(Main.jduge())
# okay decompiling __pycache__\BreezePython.cpython-37.pyc

編譯后的代碼返回:I'm True,I like white best
什么鬼?這編譯成什么了亂七八糟的了東西了。京劇多年采坑經驗,反編譯的代碼,當遇到多個if not 的時候,最后一個if not就會被所謂的簡要寫法弄的變了為了,導致最終反編譯出錯。這個是個坑,同樣你是否也可以利用它呢?

今天的文章就到這里,希望通過這篇文章能讓你對python的文件類型與編譯、反編譯操作有所了解。

作者:華為雲特約供稿開發者 清風Python


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM