背景

Python 是一种解释型语言,没有编译过程,发布程序的同时就相当于公开了源码,这也是其作为开源语言的一个特性。但在某些场景下,我们的源码是不想被别人看到的,例如开发商业软件。

加密方式

参考链接:
如何给Python代码进行加密
Python 源码混淆与加密
1、代码混淆
2、发行.pyc 文件
3、使用 py2exe、pyinstaller打包为exe文件
4、使用PyArmor加密脚本
5、使用pyconcrete加密为pye文件
6、使用 Cython将py文件生成为pyd加密文件

编译与反编译过程

本文采用第6种方法,即使用 Cython将py文件生成为pyd加密文件.
正常情况下:
py文件编译为exe文件过程
py—>(pyinstaller包)---->exe文件
反编译过程
exe文件—>(pyinstxtractor包)—>pyc文件—>(uncompyle6包)---->py源码

加密情况下
py文件编译为exe文件过程
py—>(Cypthon)—>c文件和pyd文件–>(pyinstaller包)---->exe文件
中间生成的pyd文件是难以反编译的

本文用到的工具

python 3.8.5
pycharm
visual studio 2022(需要C编译器)
pyinstaller 5.1
Cython 0.29.30
pyinstxtractor.py https://sourceforge.net/projects/pyinstallerextractor/

python3打包为exe文件

这里有个Hello.py文件
在这里插入图片描述
Hello.py文件内容如下:

from tkinter import *
# 这是一段TK测试代码
class ChangeLabelDemo:
    def __init__(self):
        window = Tk()
        window.title = "改变labeldemo"  #窗口标题

        frame1 = Frame(window)
        frame1.pack()
        self.lb1 = Label(frame1, text="Programming is fun")
        self.lb1.pack()

        frame2 = Frame(window)
        frame2.pack()
        label = Label(frame2, text="输入")
        self.msg = StringVar()
        entry = Entry(frame2, textvariable=self.msg)
        btChangeText = Button(frame2, text="改变text", command=self.processButton)
        self.v1 = StringVar()
        rbRed = Radiobutton(frame2, text="Red", bg="red", variable=self.v1, value="R", command=self.processRadiobutton)
        rbYellow = Radiobutton(frame2, text="Yellow", bg="yellow", variable=self.v1, value="Y",
                               command=self.processRadiobutton)

        label.grid(row=1, column=1)
        entry.grid(row=1, column=2)
        btChangeText.grid(row=1, column=3)
        rbRed.grid(row=1, column=4)
        rbYellow.grid(row=1, column=5)

        window.mainloop()

    def processButton(self):
        self.lb1["text"] = self.msg.get()

    def processRadiobutton(self):
        if self.v1.get() == "R":
            self.lb1["fg"] = "red"
        elif self.v1.get() == "Y":
            self.lb1["fg"] = "yellow"

ChangeLabelDemo()

step1:安装pyinstaller包

pip install pyinstaller

step2:在cmd中进入当前文件路径下

cd E:\研究生\pytorch\untitled\encypt

step3:打包生成exe文件,使用如下命令,将其打包为单一exe(去掉-F则不是单一exe,-w是不生成window窗口)

pyinstaller -F -w Hello.py

生成了build,dist 和Hello.spec三个文件
在这里插入图片描述
exe文件在dist文件夹中,双击即可运行
在这里插入图片描述

python3将exe文件进行反编译为源码

exe反编译工具:pyinstxtractor.py下载:https://sourceforge.net/projects/pyinstallerextractor/

将pyinstxtractor.py放到exe文件相同目录
在这里插入图片描述
执行以下cmd命令,首先进入到dist文件夹下

cd dist

在这里插入图片描述
接下来,在cmd中执行下面命令

python pyinstxtractor.py Hello.exe

在这里插入图片描述
成功解压(反编译),多了个Hello.exe_extracted文件夹
在这里插入图片描述
进入Hello.exe_extracted文件夹,有个Hello文件,此为被解压出的pyc文件,需通过
在这里插入图片描述
接下来尝试对Hello文件反编译
首先安装反编译uncompyle6

pip install uncompyle6

接下来进入exe所在文件夹

cd Hello.exe_extracted

需将Hello文件添加.pyc后缀,不然不能编译。运行下面代码反编译Hello.pyc文件

uncompyle6 Hello.pyc

结果报错

Unknown magic number 227 in Hello.pyc

在这里插入图片描述
报错原因
提示是Unknown magic number 227,这个失败因为pyinstaller工具打包的时候,会将代码文件的magic number(python的版本及编译时间)给清除掉,所以反编译时候需要将magic number添加回去才能识别,magic number我们可以通过解压主目录下的struct文件中提取出来(一般是前16个字节,可以对比打包前的源文件),将struct文件体中的前16个字节提取出来,然后在添加到文件中,然后再执行uncompyle6反编译。
报错解决方法Python3 如何反编译EXE
我们先看看struct文件的内容,注意看第一行,这个就是缺的信息。第二行是以E3开始的。
在这里插入图片描述
接下来看Hello文件的内容(不是pyc文件,所以得把pyc后缀去掉,然后,用vs打开,pycharm和记事本打开会乱码),可以看出是以E3为起始,相对struct文件少一第一行。
在这里插入图片描述
接下来要做的就是把缺失的magic number添加到pyc文件中

在pycharm中运行以下代码

# import py_compile
# import Hello
# Compile.compile('Hello.py')

# import Hello
# Hello()

struct_path = r"E:\研究生\pytorch\untitled\encypt\dist\Hello.exe_extracted/struct"
fn = open(struct_path, 'rb')
print(fn.seek(0))  # 切换指针位置到开头
magic_number = fn.read(16)    # 读取文件的前16个字节,从0开始
print(magic_number)

point_number = fn.tell()     # 获取当前指针位置
print(point_number)    # 获取当前指针位置
print(fn.read(1))  # 再读取第17个字节,输出值是e3

add_magic_num_file_name = r"E:\研究生\pytorch\untitled\encypt\dist\Hello.exe_extracted/Hello.pyc"
MAGIC_NUMBER = b'U\r\r\n\x00\x00\x00\x00\xac\x95\x15_\x01\x01\x00\x00'
f = open(add_magic_num_file_name, 'rb')
new_content = f.read()
new_add_magic_number_file_name = r"E:\研究生\pytorch\untitled\encypt\dist\Hello.exe_extracted/Hello.pyc"
n_f = open(new_add_magic_number_file_name, "wb")
n_f.write(MAGIC_NUMBER + new_content)
f.close()
n_f.close()
output>>>
0
b'U\r\r\n\x00\x00\x00\x00\xac\x95\x15_\x01\x01\x00\x00'
16
b'\xe3'

此时再用vs打开Hello文件。完成,现在可以用uncompyle6反编译了
在这里插入图片描述
在cmd中运行下面代码

 uncompyle6 Hello.pyc

在这里插入图片描述
可见exe源代码被反编译出来,因此如果是公司开发的产品,其安全性有待考虑。

注:
1、如果注释存在中文,则会被解析成Unicode编码,可以用相应的工具转换即可
2、反编译的Hello是主程序文件,其实还需要将PYZ-00.pyz_extracted文件夹下的所有pyc文件都通过uncompyle6进行反编译。
3、PYZ-00.pyz_extracted目录下的依赖库的pyc文件缺少的字节数与主程序不同,注意下PYZ-00.pyz_extracted目录下的依赖库的pyc文件缺少的字节数与主程序不同。
4、依赖库pyc文件批量反编译教程见这里

将py文件编译为pyd文件以防止反编译

将py文件编译为动态链接库,这样破解难度将大大增加。其中,在python里,pyd格式即动态链接库。使用cython即可编译为pyd文件。

首先安装cpython

pip install Cython

Hello.py文件创建新的.py文件,并命名为setup.py,其内容为:

# -*- coding: utf-8 -*-
"""
Created on Friday July 1 16:40:20 2022
@author: Li Zhengping
link:
https://www.jb51.net/article/178209.html
https://blog.csdn.net/ZhaDeNianQu/article/details/87717293
"""
from distutils.core import setup
from Cython.Build import cythonize
 
setup(name = 'any words.....', ext_modules = cythonize(["Hello.py",]))

然后在cmd中执行以下命令

cd E:\研究生\pytorch\untitled\encypt
python setup.py build_ext --inplace

注意:编译需要相关的VC环境,我是安装 VS22版本的,不安装是无法编译的。

运行过程及生成结果如下图,其中红框的pyd文件即编译好了。因为我是64位的系统和python,所以会生成amd64后缀,我们把这个删掉重命名为Hello.pyd即可。
注:当同时存在mylib.pyd和mylib.py时,引入优先级是pyd>py,所以不用移除py文件,默认引入时就是pyd。在这里插入图片描述
此时,我们删除build文件夹
再次编译为exe即可。
cmd中运行下面代码

pyinstaller -F -w Hello.py

编译之后的文件
在这里插入图片描述
同样的在dist文件夹下存放exe文件
在这里插入图片描述

可以验证一下:再次反编译main.exe后
首先进入dist路径下

cd dist

注意要在exe同路径文件中放入pyinstxtractor.py文件
接下来运行

python pyinstxtractor.py Hello.exe

其结果是依然为Hello.pyc文件,而不是Hello.pyd文件,也就是打包的是Hello.py文件而不是Hello.pyd文件,这样操作依然能被反编译。
在这里插入图片描述
接下来搜了一下如何打包pyd文件,找到了如下博客:Python .py生成.pyd文件并打包.exe注意事项

首先需要创建一个main.py文件,其作为一个入口程序来导入pyd文件,pyd文件默认优先级高于py文件,因此Hello.py和Hello.pyd可以同时存在,也就是说Hello.py不需要删除。
在这里插入图片描述
main.py内容如下

from tkinter import *
import Hello
if __name__ == '__main__':
    Hello.ChangeLabelDemo()

注意:
1、程序的__main__入口只能有一个,如果源py文件中有定义main入口,需要注释掉并调整代码缩进,否则通过main.py调用pyd文件遇到if name == ‘main’:之后的代码都不会运行。
2、源文件Hello.py文件头部import到的第三方库需全部复制到main.py文件头部,不然运行会闪退

接下来在cmd输入

pyinstaller -F -w main.py

在这里插入图片描述
接下来我们用pyinstxtractor.py解包main.exe文件验证一下,命令行依次输入(记得先把pyinstxtractor.py复制到当前文件夹下):

cd dist
python pyinstxtractor.py main.exe

会得到一个main.exe_extracted文件夹,在文件夹下发现文件Hello.pyd,而不是Hello.py文件,说明通过引用pyd文件打包成功。pyd文件是难以反编译的,因此源码可以得到很好的保护。
在这里插入图片描述
大工告成

参考链接

通过pyinstaller打包为exe:
1、pyinstxtractor.py 的改进 - 反编译pyinstaller生成exe的工具
2、pyinstaller打包pyqt5编写的项目为exe(脱离环境可运行)
3、对python生成的EXE文件 进行反编译
将py文件生成为pyd文件进行反编译:
4、PyInstaller将Python文件打包为exe后如何反编译(破解源码)以及防止反编译
5、PyInstaller封装程序反编译 PyInstxtractor + Uncompale
将pyd文件打包成exe文件:
6、Python .py生成.pyd文件并打包.exe注意事项

欢迎关注我的公众号《故障诊断与python学习》

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐