原文链接
答个疑,随便聊聊pyinstaller打包以及安全性吧mp.weixin.qq.com
导语
感觉经常有小伙伴问我关于pyinstaller打包的问题,不回吧,可能会被说咋这么高冷不理人,一个个回吧,又感觉太浪费时间了,干脆写篇文章聊聊被问的比较多的两个问题吧。
相关文件
本期文章中涉及到的所有相关文件可通过关注微信公众号“Charles的皮卡丘”,在公众号内回复“pyinstaller打包”获取。
打包程序素材
关于pyinstaller如何把图片,音乐,字体等素材文件也打包进exe文件中。很久以前其实有篇文章讲过,不过可能是我没讲清楚,以至于到现在也经常有人问我类似的问题。这里就不整那些花里胡哨的东西了,直接讲讲我们该怎么做才能实现这个功能吧,先声明一下,其实这东西官网里就有教程,不明白且想明白为什么这么做的自己看官网的介绍吧:
https://pyinstaller.readthedocs.io/en/v3.3.1/runtime-information.html
还是以之前的表白小软件为例:
相关文件在这可以下载到:
https://github.com/CharlesPikachu/Tools/tree/master/NaughtyConfession
有用的就这三个文件:
其中love.py是主程序,cfg.py是配置文件,resources文件夹里是一些类似字体,音乐等的素材文件。
先直接试试运行如下命令打包:
pyinstaller -F love.py -w
打包结束后根目录变成了这样:
dist文件夹里有打包好的exe文件。打开文件夹,直接双击运行一下,会发现报错:
原因很简单,因为你没把相关的素材文件打包进这个exe文件,而在该目录下根据程序本身的设定是无法读取到这些素材文件的。你需要先把该exe文件移动到love.py这个主程序所在的目录,然后双击运行:
想要把素材文件也打包进exe文件的话,得先修改下程序,把程序中关于素材资源加载路径的相关代码从(在cfg.py文件里):
# 背景音乐路径
BGM_PATH = os.path.join(os.getcwd(), 'resources/music/bgm.mp3')
# 字体路径
FONT_PATH = os.path.join(os.getcwd(), 'resources/font/STXINGKA.TTF')
# 背景图片路径
BG_IMAGE_PATH = os.path.join(os.getcwd(), 'resources/images/bg.png')
# ICON路径
ICON_IMAGE_PATH = os.path.join(os.getcwd(), 'resources/images/icon.png')
改成:
if getattr(sys, 'frozen', False):
cur_path = sys._MEIPASS
else:
cur_path = os.path.dirname(__file__)
# 背景音乐路径
BGM_PATH = os.path.join(cur_path, 'resources/music/bgm.mp3')
# 字体路径
FONT_PATH = os.path.join(cur_path, 'resources/font/STXINGKA.TTF')
# 背景图片路径
BG_IMAGE_PATH = os.path.join(cur_path, 'resources/images/bg.png')
# ICON路径
ICON_IMAGE_PATH = os.path.join(cur_path, 'resources/images/icon.png')
然后新建一个.spec文件,当然,为了方便,你可以直接打开刚刚生成的那个.spec文件(就是运行最前面那个打包命令时,也会根据你的命令来生成一个love.spec文件),类似这样:
打开该文件,可以发现该文件里的内容是这样的(为了方便某些懒癌患者复制粘贴,我就不截图而是直接把内容copy下来了):
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['love.py'],
pathex=['C:\\Users\\ThinkPad\\Desktop\\NaughtyConfession'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='love',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False )
通过修改该文件,可以将指定的素材资源全部打包进exe文件中,具体而言,修改后的文件如下:
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
added_files = [('C:\\Users\\ThinkPad\\Desktop\\NaughtyConfession\\resources', 'resources')]
a = Analysis(['love.py'],
pathex=['C:\\Users\\ThinkPad\\Desktop\\NaughtyConfession'],
binaries=[],
datas=added_files,
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='love',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False )
其实就加了一行代码(第六行):
added_files = [('C:\\Users\\ThinkPad\\Desktop\\NaughtyConfession\\resources', 'resources')]
然后把(第十行):
datas=[],
改成了:
datas=added_files,
就这么简单就完事了,很意外吧?最后在命令行运行:
pyinstaller -F love.spec
同样地,在dist文件夹里会生成打包好的exe文件,双击运行一下,可以发现这个exe文件竟然可以直接运行啦:
至此,我们轻松地实现了将python程序的素材文件一起打包进exe文件的目标。当然,上面只是介绍了一种个人比较习惯且相对简单方便的解决方案,想了解更多相关内容以及原理,各位小伙伴还是自己去查阅官方文档吧:
https://pyinstaller.readthedocs.io/en/v3.3.1/index.html
打包安全性
一样地,我们舍弃那堆花里胡哨的前言,直接开干,毕竟实践出真知,讲一堆有的没的废话,还不如搞一波事,感兴趣的小伙伴自然会自己进一步探究下去。以我们刚刚打包好的exe文件为例,就是它:
假设我们只把这个exe文件发给了心仪的小姐姐/小哥哥(然后人家拉黑了你
)。那么对方能不能通过这个exe文件来获得你的源代码呢?先公布答案,可以。让我们一步步操作下去来实现这个目的。
先到这下载个解包工具:
https://sourceforge.net/projects/pyinstallerextractor/
下载后长这样:
然后运行如下命令:
python pyinstxtractor.py love.exe
运行后发现根目录变成了这样:
多了一个文件夹,打开后发现里面一堆ddl,pyd文件:
在这里面我们可以找到三个比较关键的文件:
其中love就是你之前打包的那个py文件对应的pyc文件。注意,如果exe文件名被改动过,比如一开始打包好的love.exe被改成了pig.exe,那么你找到的文件仍然是:
love.exe.manifest
而不是:
pig.exe.manifest
struct也是一个pyc文件。于是我们现在只需要反编译这些pyc文件就行了,随便搜索下就可以发现一堆相关的网站:
随便选一个就OK了:
http://tools.bugscaner.com/decompyle/
啊,对了,还要下载个十六进制编辑器,比如:
https://wxmedit.github.io/downloads.html
或者直接用notepad++也行。打开love和struct文件(重命名一下加个后缀就变成pyc文件了,这个总不用我教了吧T_T):
把struct.pyc文件里的前12个字节复制到love.pyc文件里,说人话就是我们都知道:
1B = 8bit
表示一个16进制数需要4bit
剩下数数的活就不需要我来教了吧
总之,一顿操作之后,love.pyc文件变成了这样:
保存,然后拿去在线反编译:
可以发现我们已经成功地通过单个exe文件获得了程序的源代码。
不过pyinstaller提供了--key这个选项,可以实现加密打包,但实际上它只对依赖库进行了加密,并没有对主程序做加密处理。所以,不希望自己代码泄露的小伙伴应该知道写程序和打包的时候该怎么做了吧。