Python/ target=_blank class=infotextkey>Python是一種解釋型語言,但是與JAVAscript這種純腳本語言不同,python提供了一種編譯成字節碼運行的方法,編譯之后就得到pyc文件,這點和java編譯成class文件再用jvm解釋運行很類似,但是與java不同的是,python編譯字節碼不是一個強制的操作,事實上,編譯是一個自動的過程,一般不會在意它的存在。
編譯成字節碼可以節省加載模塊的時間,提高效率。除了效率之外,字節碼的形式也增加了反向工程的難度,可以保護源代碼。這個只是一定程度上的保護,反編譯還是可以的。
py pyc pyo pyd分別是什么文件
- py: python腳本源文件。
- pyc: python源文件經過編譯得到的字節碼, 二進制文件。
- pyo: python腳本文件開啟優化編譯(-O)得到的字節碼,二進制文件(python -O test.py)。
- pyd: python的動態鏈接庫,windows DLL文件。
編譯py文件生成pyc
我們編寫兩個py腳本
mylib.py:包含一個函數,打印一行文字.
def keyFun():
print("keyFun is running")
main.py:程序運行入口,調用mylib種的keyFun函數
from mylib import keyFun
if __name__ == "__main__":
keyFun()
編譯所有文件,在腳本目錄執行以下命令:
python -m compileall .
可以看到生成了相對應的兩個pyc文件
編譯生成pyc文件
此時執行main.cpython-38.pyc會提示找不到mylib模塊,需要將文件名中的.cpython-38刪掉.
執行pyc文件
反編譯pyc
反編譯pyc的工具很多,我用的是python3.8,這里介紹幾種可以反編譯python3.8的工具.
- 在線反編譯網站: https://tool.lu/pyc/
- python-decompile3工具: https://github.com/rocky/python-decompile3
- Easy Python Decompiler: https://sourceforge.NET/projects/easypythondecompiler/
本文作為演示,使用在線網站反編譯mylib.py,可以看到下圖反編譯代碼與實際代碼一模一樣.
反編譯pyc結果
反編譯pyinstaller打包的exe文件
我們使用pyinstaller將main.py打包成exe文件
pyinstaller -F main.py
pyinstaller打包exe并執行
反編譯pyinstaller打包的exe需要用到pyinstxtractor(
https://github.com/extremecoders-re/pyinstxtractor).
將main.exe復制到pyinstxtractor文件夾,執行python pyinstxtractor.py main.exe
python pyinstxtractor.py main.exe
反編譯exe
可以看到pyinstxtractor已經提示入口文件為main.pyc.我們反編譯main.pyc就可以看到pyc引入可哪些模塊,這個例子可以從反編譯代碼中看到引入了mylib模塊,再接著反編譯mylib.pyc就可以了.
Cython編譯pyd文件
從上面的反編譯pyc文件結果可以看出,pyc很容易就被反編譯,無法保護我們的代碼.這里我們介紹使用Cython將python文件編譯成pyd文件的方法.
首先安裝Cython(Anaconda自帶Cython的話不需要安裝)
pip install Cython
在mylib.py所在目錄新建build_pyd.py文件
from distutils.core import setup
from Cython.Build import cythonize
setup(
ext_modules = cythonize([
"mylib.py"
]),
)
#1.執行 python build_pyd.py build_ext --inplace
#2.再把.cp38-win_amd64刪掉 python renamepyd_file.py
執行python build_pyd.py build_ext --inplace.將會為mylib.py生成對應的.c文件和.pyd文件
Cython生成pyd文件
與上文提到的pyc文件無法直接執行一樣,pyd文件也需要刪除文件名中的.cp38-win_amd64.這樣main.py才能找到對應的mylib.pyd.
import os
lists = os.listdir("./")
for item in lists:
try:
if ".cp38-win_amd64.pyd" in item:
# 重命名文件
fileName = item.replace("cp38-win_amd64.", "")
files = os.rename(item, fileName)
except Exception as e:
print(e)
執行main.py,此時main.py引用的是編譯后的mylib.pyd.如果修改了mylib.py中的代碼,需要刪除pyd文件后調試,不然不會看到改動后的效果.
編譯成pyd后再用pyinstaller打包
使用上文中的方法將python文件編譯為pyd文件后,再用pynstaller打包,這時候我們反編譯就只能看到pyd文件了,要想破解pyd文件就需要使用匯編級別的破解技術,如果你的代碼需要別人這樣去破解的話,那恭喜你了,哈哈.
需要注意的是,編譯為pyd再用pyinstaller打包,可能會出現模塊無法被打包進去的情況,這時候需要編輯spec文件,將mylib模塊添加到hiddenimports中.
a = Analysis(['main.py'],
pathex=['E:\playground\decompiletest'],
binaries=[],
datas=[],
//這里引入mylib模塊
hiddenimports=['mylib'],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)