欢迎回到我们的《零基础:100个小案例玩转Python软件开发!》系列!在第六节课,我将教大家如何开发一个反编译工具。
一、PyInstaller打包EXE
首先我们要知道,如果要将一个Python文件或PyMe项目打包成EXE可执行文件,需要使用专门的打包工具,比如pyinstaller,nuitka等,其中最常用的是pyinstaller,因为它的命令行比较简单。比如我们有下面一个login.py:
import tkinter as tk from tkinter import messagebox # 创建窗口 window = tk.Tk() window.title("登录") window.geometry("250x200") # 测试账户 users = {"admin": "123456", "test": "test123"} # 标题 tk.Label(window, text="用户登录", font=("Arial", 14)).pack(pady=10) # 用户名 tk.Label(window, text="用户名:").pack() user_entry = tk.Entry(window) user_entry.pack() # 密码 tk.Label(window, text="密码:").pack() pwd_entry = tk.Entry(window, show="*") pwd_entry.pack() # 登录按钮 def check_login(): user = user_entry.get() pwd = pwd_entry.get() if user in users and users[user] == pwd: messagebox.showinfo("成功", "登录成功!") # 打开新窗口 new_window = tk.Toplevel(window) new_window.title("主界面") new_window.geometry("300x200") tk.Label(new_window, text=f"欢迎 {user}!").pack(pady=50) tk.Button(new_window, text="退出", command=new_window.destroy).pack() else: messagebox.showerror("错误", "账号或密码错误!") tk.Button(window, text="登录", command=check_login, width=10).pack(pady=20) # 运行 window.mainloop()它运行时的样子是这样的:
我们在代码文件所在的文件夹里进入cmd命令行,然后输入命令:
pyinstaller -c -w -F login.py回车运行后,pyinstaller会开始工作,结束后会将当前文件打包成一个独立的EXE。
打包过程中会生成两个文件夹,一个是build,主要用于存放打包的临时生成文件,另一个是dist,是用于发布的结果。打包结束后,会在dist中生成login.exe文件,双击运行,可以看到一切正常。
虽然PyInstaller可以将Python脚本、依赖库和解释器打包成单个可执行文件,但实际上这些资源只是被"包裹"在一起,并没有真正的加密保护。这一节我们就开发一个pyinstaller反编译工具,看能不能将上面的login.exe还原为源码文件。
二、开发自己的反编译工具
我们启动PyMe,新建一个空白工具“反编译工具”,在设计器从左边的控件工具体中拖动Label、Entry、LabelButton到界面上,修改文字和样式,做成如下所示的界面。
我们希望在点击Python文件对应的“浏览”按钮时,将文件路径填入到Entry_1中,在点击导出文件夹对应的“浏览”按钮时,将文件夹路径填入到Entry_2中,然后点击"导出"时能够将EXE反编译出python文件并导出到指定文件夹。
在第一个“浏览”按钮上用鼠标右键单击,然后在弹出菜单中选择”事件响应“菜单项,进入事件响应编辑框后在左边选择Command事件,在右边选择”调用其它界面“,然后再选择”调用打开文件框“。
进入代码编辑器后,修改生成的Fun.OpenFile函数参数,将Python改为EXE,代表打开EXE文件,判断一下返回的openPath是否有效,如果有效设置到Entry_1中。
#LabelButton 'LabelButton_1' 's Command Event : def LabelButton_1_onCommand(uiName,widgetName,threadings=0): openPath = Fun.OpenFile(对话框标题="打开EXE文件",文件类型列表=[('EXE File','*exe'),('All files','*')],初始文件夹路径= os.path.abspath('.'),选择多文件=False) if openPath: Fun.SetText(界面名称=uiName,控件名称='Entry_1',字符串文本=openPath)接下来可以返回设计器为第二个浏览按钮如法炮制来”打开目录查找“并设置到Entry_2中。也可以在代码编辑器右边的微缩界面中点选第二个浏览按钮,然后在下面的控件事件类型列表中为“按钮点击”事件绑定函数“,这样就可以直接生成LabelButton_2_onCommand函数了。
在函数里用鼠标右键单击,就可以在弹出菜单中选择”系统函数“下的”打开目录查找“来生成弹出对话框界面。
最终代码完善如下:
#LabelButton 'LabelButton_2' 's Command Event : def LabelButton_2_onCommand(uiName,widgetName,threadings=0): openPath = Fun.SelectDirectory(title='打开目录查找',initDir=os.path.abspath('.')) if openPath: Fun.SetText(界面名称=uiName,控件名称='Entry_2',字符串文本=openPath)最后是为”导出“按钮绑定点击事件函数并进行反编译逻辑处理,这段代码比较长,我们需要先用pyinstxtractor来从EXE提取文件。
pyinstxtractor下载地址如下:
https://github.com/extremecoders-re/pyinstxtractor
下载完后将文件放置到当前工程工程文件夹下,然后调用以下命令就可以将exe中的文件提取出来。
python pyinstxtractor.py your_app.exe如果你是VIP会员,你可以鼠标右键,通过"AI生成代码“来生成这段处理逻辑。
在编程小助手输入框中输入:”调用pyinstxtractor对exePath进行反编译”,它就可以生成对应的函数。
具体逻辑如下:
#LabelButton 'LabelButton_3' 's Command Event : def LabelButton_3_onCommand(uiName,widgetName,threadings=0): exePath = Fun.GetText(uiName,'Entry_1') srcPath = Fun.GetText(uiName,'Entry_2') import io import os import sys import subprocess import tempfile import shutil # 强制标准输出使用UTF-8编码 sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') def extract_exe_with_pyinstxtractor(exePath, srcPath=None): """ 使用pyinstxtractor提取PyInstaller打包的exe文件 Args: exePath (str): 要提取的exe文件路径 srcPath (str, optional): 输出目录路径。如果为None,则在exe同目录创建输出文件夹 Returns: str: 提取后的目录路径 """ #检查exe文件是否存在 if not os.path.exists(exePath): raise FileNotFoundError(f"找不到exe文件: {exePath}") #如果未指定输出路径,则在exe同目录创建输出文件夹 if srcPath is None: exe_dir = os.path.dirname(exePath) exe_name = os.path.splitext(os.path.basename(exePath))[0] srcPath = os.path.join(exe_dir, f"{exe_name}_extracted") #获取pyinstxtractor.py的路径 #方法1: 如果pyinstxtractor已安装 try: import pyinstxtractor pyinstxtractor_path = os.path.join(os.path.dirname(pyinstxtractor.__file__), 'pyinstxtractor.py') except ImportError: print("pyinstxtractor未安装") return #执行pyinstxtractor print(f"正在提取 {exePath} ...") print(f"输出目录: {srcPath}") try: #使用Python运行pyinstxtractor cmd = [sys.executable, pyinstxtractor_path, exePath] #运行命令 result = subprocess.run( cmd, capture_output=True, text=True, cwd=srcPath, #在输出目录中运行 encoding='utf-8' ) #检查执行结果 if result.returncode != 0: print(f"pyinstxtractor执行错误:\n{result.stderr}") raise RuntimeError(f"pyinstxtractor执行失败,返回码: {result.returncode}") print("提取完成!") print(f"输出: {result.stdout}") #查找提取的文件夹(pyinstxtractor会在当前目录创建以exe名开头的文件夹) extracted_dirs = [d for d in os.listdir(srcPath) if d.startswith(os.path.basename(exePath)) and os.path.isdir(os.path.join(srcPath, d))] if extracted_dirs: extracted_dir = os.path.join(srcPath, extracted_dirs[0]) print(f"提取的文件位于: {extracted_dir}") return extracted_dir else: print("警告: 未找到提取的文件夹") return srcPath except Exception as e: print(f"提取过程中出错: {e}") raise extract_exe_with_pyinstxtractor(exePath, srcPath)不过这时只是完成了EXE文件提取出pyc文件,运行测试一下,点击导出时,将会开始处理提取。
完成后可以在目标文件夹看到生成的login.pyc。
如果我们想要将pyc进一步还原为python代码,还需要另一个工具uncompyle6或直接到https://www.decompiler.com/上传上传pyc文件即可查看源码。
通过 pip install uncompyle6 安装一下。
然后在提取代码后面稍做修改,调用uncompyle6来进行pyc到py文件的转换。
extracted_dir = extract_exe_with_pyinstxtractor(exePath, srcPath) import uncompyle6 pycfilelist = Fun.WalkAllResFiles(文件夹路径=extracted_dir,是否遍历子文件夹=False,是否指定扩展名="pyc") for pyc in pycfilelist: if os.path.exists(pyc): print(f"\n=== 正在反编译 {pyc} ===") os.system(f"uncompyle6 {pyc} > {pyc.replace('.pyc', '_decompiled.py')}") print(f"已保存:{pyc.replace('.pyc', '_decompiled.py')}")好啦,现在整个工程逻辑就完成啦,我们运行测试一下,可以将EXE文件反编译到指定文件夹下并生成对应的python文件。
打开login_decompiled.py,就可以看到代码源文件啦!
三、PyMe的防护措施
经过上面的开发,相信大家对于pyinstaller打包EXE又有了新的认识,虽然说pyinstaller打包很快很方便,但是并不是很安全,开发一些小工具倒是可以,但是如果想要有更好的源代码保护,就建议使用加密打包或Nuitka打包,这两项在PyMe中都支持,在发布时弹出的打包对话框中,你可以通过切换打包工具使用nuitka,或者直接在pyinstaller中使用加密保护源码选项。这两项都会将源码先转换成c语言代码再编译,大大强化EXE的反编译保护。
最后,在PyMe的”案例商店“的"文件操作"分类中有一个专门测试PyMe工程EXE反编译的工具案例”EXE2PY“,感兴趣小伙伴也可以下载体验。
它的原理和本节类似,只是更加完善一些。
在本节的课程中,我们通过一个反编译工具的开发案例讲述了Python打包工具的和反编译保护的知识,关注PyMe公众号教程,跟着我继续学习,让我们一起加油!
官网:www.py-me.com
下载:https://pyme.lanzoum.com/igiUy3cviulg