PyInstaller:编译exe与反编译
文章目錄
- 1 簡單Python代碼示例
- 2 安裝PyInstaller
- 3 不加密直接編譯exe
- 4 對PyInstaller打包的不加密編譯exe進行反編譯
- 5 加密編譯exe
- 5.1 注意事項
- 5.2 加密編譯
- 6 將外部數據打包到exe中
- 6.1 如果不需要加密編譯
- 6.2 如果需要加密編譯
- 7 為exe添加圖標
1 簡單Python代碼示例
TestAdd.py
#__author__ = 'StubbornHuang' #coding = utf-8import io import os import sysdef addTest(a,b):print ("a+b={}".format(a+b))if __name__ == '__main__':addTest(1,5)2 安裝PyInstaller
輸入以下命令安裝pyinstaller:
pip install pyinstaller驗證是否安裝成功,輸入以下命令:
pyinstaller3 不加密直接編譯exe
在需要打包的py文件目錄下啟動cmd.exe,或者PowerShell.exe,我自己用的是Cmder.exe。
輸入以下命令:
打包的exe在py文件所在目錄的dist子目錄下
如果直接運行TestAdd.exe會一閃而過,最好在當前exe所在目錄下執行命令運行:
我們可以看到直接運行成功。
4 對PyInstaller打包的不加密編譯exe進行反編譯
使用pyinstxtractor.py 對上述不加密的exe進行反編譯,其中pyinstxtractor.py文件內容如下:
""" PyInstaller Extractor v1.9 (Supports pyinstaller 3.3, 3.2, 3.1, 3.0, 2.1, 2.0) Author : Extreme Coders E-mail : extremecoders(at)hotmail(dot)com Web : https://0xec.blogspot.com Date : 29-November-2017 Url : https://sourceforge.net/projects/pyinstallerextractor/For any suggestions, leave a comment on https://forum.tuts4you.com/topic/34455-pyinstaller-extractor/This script extracts a pyinstaller generated executable file. Pyinstaller installation is not needed. The script has it all.For best results, it is recommended to run this script in the same version of python as was used to create the executable. This is just to prevent unmarshalling errors(if any) while extracting the PYZ archive.Usage : Just copy this script to the directory where your exe residesand run the script with the exe file name as a parameterC:\path\to\exe\>python pyinstxtractor.py <filename> $ /path/to/exe/python pyinstxtractor.py <filename>Licensed under GNU General Public License (GPL) v3. You are free to modify this source.CHANGELOG ================================================Version 1.1 (Jan 28, 2014) ------------------------------------------------- - First Release - Supports only pyinstaller 2.0Version 1.2 (Sept 12, 2015) ------------------------------------------------- - Added support for pyinstaller 2.1 and 3.0 dev - Cleaned up code - Script is now more verbose - Executable extracted within a dedicated sub-directory(Support for pyinstaller 3.0 dev is experimental)Version 1.3 (Dec 12, 2015) ------------------------------------------------- - Added support for pyinstaller 3.0 final - Script is compatible with both python 2.x & 3.x (Thanks to Moritz Kroll @ Avira Operations GmbH & Co. KG)Version 1.4 (Jan 19, 2016) ------------------------------------------------- - Fixed a bug when writing pyc files >= version 3.3 (Thanks to Daniello Alto: https://github.com/Djamana)Version 1.5 (March 1, 2016) ------------------------------------------------- - Added support for pyinstaller 3.1 (Thanks to Berwyn Hoyt for reporting)Version 1.6 (Sept 5, 2016) ------------------------------------------------- - Added support for pyinstaller 3.2 - Extractor will use a random name while extracting unnamed files. - For encrypted pyz archives it will dump the contents as is. Previously, the tool would fail.Version 1.7 (March 13, 2017) ------------------------------------------------- - Made the script compatible with python 2.6 (Thanks to Ross for reporting)Version 1.8 (April 28, 2017) ------------------------------------------------- - Support for sub-directories in .pyz files (Thanks to Moritz Kroll @ Avira Operations GmbH & Co. KG)Version 1.9 (November 29, 2017) ------------------------------------------------- - Added support for pyinstaller 3.3 - Display the scripts which are run at entry (Thanks to Michael Gillespie @ malwarehunterteam for the feature request)"""from __future__ import print_function import os import struct import marshal import zlib import sys import imp import types from uuid import uuid4 as uniquenameclass CTOCEntry:def __init__(self, position, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name):self.position = positionself.cmprsdDataSize = cmprsdDataSizeself.uncmprsdDataSize = uncmprsdDataSizeself.cmprsFlag = cmprsFlagself.typeCmprsData = typeCmprsDataself.name = nameclass PyInstArchive:PYINST20_COOKIE_SIZE = 24 # For pyinstaller 2.0PYINST21_COOKIE_SIZE = 24 + 64 # For pyinstaller 2.1+MAGIC = b'MEI\014\013\012\013\016' # Magic number which identifies pyinstallerdef __init__(self, path):self.filePath = pathdef open(self):try:self.fPtr = open(self.filePath, 'rb')self.fileSize = os.stat(self.filePath).st_sizeexcept:print('[*] Error: Could not open {0}'.format(self.filePath))return Falsereturn Truedef close(self):try:self.fPtr.close()except:passdef checkFile(self):print('[*] Processing {0}'.format(self.filePath))# Check if it is a 2.0 archiveself.fPtr.seek(self.fileSize - self.PYINST20_COOKIE_SIZE, os.SEEK_SET)magicFromFile = self.fPtr.read(len(self.MAGIC))if magicFromFile == self.MAGIC:self.pyinstVer = 20 # pyinstaller 2.0print('[*] Pyinstaller version: 2.0')return True# Check for pyinstaller 2.1+ before bailing outself.fPtr.seek(self.fileSize - self.PYINST21_COOKIE_SIZE, os.SEEK_SET)magicFromFile = self.fPtr.read(len(self.MAGIC))if magicFromFile == self.MAGIC:print('[*] Pyinstaller version: 2.1+')self.pyinstVer = 21 # pyinstaller 2.1+return Trueprint('[*] Error : Unsupported pyinstaller version or not a pyinstaller archive')return Falsedef getCArchiveInfo(self):try:if self.pyinstVer == 20:self.fPtr.seek(self.fileSize - self.PYINST20_COOKIE_SIZE, os.SEEK_SET)# Read CArchive cookie(magic, lengthofPackage, toc, tocLen, self.pyver) = \struct.unpack('!8siiii', self.fPtr.read(self.PYINST20_COOKIE_SIZE))elif self.pyinstVer == 21:self.fPtr.seek(self.fileSize - self.PYINST21_COOKIE_SIZE, os.SEEK_SET)# Read CArchive cookie(magic, lengthofPackage, toc, tocLen, self.pyver, pylibname) = \struct.unpack('!8siiii64s', self.fPtr.read(self.PYINST21_COOKIE_SIZE))except:print('[*] Error : The file is not a pyinstaller archive')return Falseprint('[*] Python version: {0}'.format(self.pyver))# Overlay is the data appended at the end of the PEself.overlaySize = lengthofPackageself.overlayPos = self.fileSize - self.overlaySizeself.tableOfContentsPos = self.overlayPos + tocself.tableOfContentsSize = tocLenprint('[*] Length of package: {0} bytes'.format(self.overlaySize))return Truedef parseTOC(self):# Go to the table of contentsself.fPtr.seek(self.tableOfContentsPos, os.SEEK_SET)self.tocList = []parsedLen = 0# Parse table of contentswhile parsedLen < self.tableOfContentsSize:(entrySize, ) = struct.unpack('!i', self.fPtr.read(4))nameLen = struct.calcsize('!iiiiBc')(entryPos, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name) = \struct.unpack( \'!iiiBc{0}s'.format(entrySize - nameLen), \self.fPtr.read(entrySize - 4))name = name.decode('utf-8').rstrip('\0')if len(name) == 0:name = str(uniquename())print('[!] Warning: Found an unamed file in CArchive. Using random name {0}'.format(name))self.tocList.append( \CTOCEntry( \self.overlayPos + entryPos, \cmprsdDataSize, \uncmprsdDataSize, \cmprsFlag, \typeCmprsData, \name \))parsedLen += entrySizeprint('[*] Found {0} files in CArchive'.format(len(self.tocList)))def extractFiles(self):print('[*] Beginning extraction...please standby')extractionDir = os.path.join(os.getcwd(), os.path.basename(self.filePath) + '_extracted')if not os.path.exists(extractionDir):os.mkdir(extractionDir)os.chdir(extractionDir)for entry in self.tocList:basePath = os.path.dirname(entry.name)if basePath != '':# Check if path exists, create if notif not os.path.exists(basePath):os.makedirs(basePath)self.fPtr.seek(entry.position, os.SEEK_SET)data = self.fPtr.read(entry.cmprsdDataSize)if entry.cmprsFlag == 1:data = zlib.decompress(data)# Malware may tamper with the uncompressed size# Comment out the assertion in such a caseassert len(data) == entry.uncmprsdDataSize # Sanity Checkwith open(entry.name, 'wb') as f:f.write(data)if entry.typeCmprsData == b's':print('[+] Possible entry point: {0}'.format(entry.name))elif entry.typeCmprsData == b'z' or entry.typeCmprsData == b'Z':self._extractPyz(entry.name)def _extractPyz(self, name):dirName = name + '_extracted'# Create a directory for the contents of the pyzif not os.path.exists(dirName):os.mkdir(dirName)with open(name, 'rb') as f:pyzMagic = f.read(4)assert pyzMagic == b'PYZ\0' # Sanity CheckpycHeader = f.read(4) # Python magic valueif imp.get_magic() != pycHeader:print('[!] Warning: The script is running in a different python version than the one used to build the executable')print(' Run this script in Python{0} to prevent extraction errors(if any) during unmarshalling'.format(self.pyver))(tocPosition, ) = struct.unpack('!i', f.read(4))f.seek(tocPosition, os.SEEK_SET)try:toc = marshal.load(f)except:print('[!] Unmarshalling FAILED. Cannot extract {0}. Extracting remaining files.'.format(name))returnprint('[*] Found {0} files in PYZ archive'.format(len(toc)))# From pyinstaller 3.1+ toc is a list of tuplesif type(toc) == list:toc = dict(toc)for key in toc.keys():(ispkg, pos, length) = toc[key]f.seek(pos, os.SEEK_SET)fileName = keytry:# for Python > 3.3 some keys are bytes object some are str objectfileName = key.decode('utf-8')except:pass# Make sure destination directory exists, ensuring we keep inside dirNamedestName = os.path.join(dirName, fileName.replace("..", "__"))destDirName = os.path.dirname(destName)if not os.path.exists(destDirName):os.makedirs(destDirName)try:data = f.read(length)data = zlib.decompress(data)except:print('[!] Error: Failed to decompress {0}, probably encrypted. Extracting as is.'.format(fileName))open(destName + '.pyc.encrypted', 'wb').write(data)continuewith open(destName + '.pyc', 'wb') as pycFile:pycFile.write(pycHeader) # Write pyc magicpycFile.write(b'\0' * 4) # Write timestampif self.pyver >= 33:pycFile.write(b'\0' * 4) # Size parameter added in Python 3.3pycFile.write(data)def main():if len(sys.argv) < 2:print('[*] Usage: pyinstxtractor.py <filename>')else:arch = PyInstArchive(sys.argv[1])if arch.open():if arch.checkFile():if arch.getCArchiveInfo():arch.parseTOC()arch.extractFiles()arch.close()print('[*] Successfully extracted pyinstaller archive: {0}'.format(sys.argv[1]))print('')print('You can now use a python decompiler on the pyc files within the extracted directory')returnarch.close()if __name__ == '__main__':main()將pyinstxtractor.py文件復制到TestAdd.exe同目錄下,運行cmd.exe,輸入以下命令進行反編譯:
python pyinstxtractor.py TestAdd.exe
反編譯的文件在exe所在目錄的后綴為exe_extracted文件夾下,示例的提取目錄為TestAdd.exe_extracted/PYZ-00.pyz_extracted文件下
在TestAdd.exe_extracted下有從TextAdd.exe提取出來的pyc文件,我們可以使用反編譯工具進行反編譯。
5 加密編譯exe
如果我們要增加反編譯pyinstaller打包的exe文件的難度該怎么辦?添加key值。
在py目錄啟動cmd.exe,輸入以下命令:
其中運行選項:
-F:強制編譯為單個exe文件,不要多余的文件;
–key 123456789:使用key123456789進行加密編譯;
5.1 注意事項
需要注意的是,在運行上述命令時如果你沒有安裝pycrypto第三方庫,則需要執行下述命令進行安裝:
pip install pycrypto進行安裝,在這其中大概率會出現如下錯誤:
error C2061: 語法錯誤: 標識符“intmax_t”;error C2059: 語法錯誤:“;” ;error C2143: 語法錯誤: 缺少“{”(在“__cdecl”的前面)等等等。
解決方案:
1 進入電腦VS安裝目錄下,搜索stdint.h(示例路徑:D:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include),將該文件復制到以下路徑,示例路徑:C:/Program Files (x86)/Windows Kits/10/Include/10.0.15063.0/ucrt/
2 然后在C:/Program Files (x86)/Windows Kits/10/Include/10.0.15063.0/ucrt/下找到inttypes.h文件,進行文件編譯,
將包含頭文件的代碼:
修改為
#include "stdint.h"3 重新運行
pip install pycrypto進行安裝pycrypto,應該就可以了。
5.2 加密編譯
這是dist目錄下只有單個的TestAdd.exe文件。
這是我們依然采用pyinstxtractor.py對其進行反編譯
可以看到出現了較多的decompress Error 解壓錯誤,TestAdd.exe_extracted/PYZ-00.pyz_extracted文件夾下的文件都是加密的。
這種方式增加了反編譯pyinstaller打包的exe文件的難度,在一定程度上增加了python源代碼的保護性。
6 將外部數據打包到exe中
將TestAdd.py文件內容修改如下:
#__author__ = 'StubbornHuang' #coding = utf-8import io import os import sysdef resource_path(relative_path):""" Get absolute path to resource, works for dev and for PyInstaller """base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))return os.path.join(base_path, relative_path)def printTestFile1():file = 'Test1.txt'print(resource_path(file))with open(resource_path(file), 'r',encoding='UTF-8') as f:while True:line = f.readline() # 逐行讀取if not line:breakprint(line)def printTestFile2():file = 'data/Test2.txt'print(resource_path(file))with open(resource_path(file), 'r',encoding='UTF-8') as f:while True:line = f.readline() # 逐行讀取if not line:breakprint(line)if __name__ == '__main__':printTestFile1()printTestFile2()然后在py所在目錄新建Test1.txt,并輸入以下內容:
pyinstaller外部數據打包測試(單文件)
然后在py所在目錄新建data子文件夾,在data文件夾下新建Test2.txt,并輸入以下內容:
pyinstaller外部數據打包測試(文件夾下的目錄)
好的,將上述準備工作做好之后則開始進行將外部數據增加到exe中
6.1 如果不需要加密編譯
則在py文件所在目錄輸入以下命令
pyi-makespec -F TestAdd.py執行完命令后,可以看到py所在目錄下新增了文件TestAdd.spec
該文件內容為:
將上述文件進行修改,添加外部數據,修改如下:
# -*- mode: python -*-block_cipher = Nonea = Analysis(['TestAdd.py'],pathex=['C:\\Users\\Administrator\\Desktop\\PythonExe\\Test'],binaries=[], datas=[('Test1.txt','.'),('data/Test2.txt','data')],#修改處hiddenimports=[], #填入需要導入的第三方庫,例如flaskhookspath=[],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='TestAdd',debug=False,bootloader_ignore_signals=False,strip=False,upx=True,runtime_tmpdir=None,console=True )上述修改會將Test1.txt以及/data/Test2.txt文件在運行時復制到可執行程序的臨時目錄以便可執行程序可以找到相應的文件。
修改后,使用命令:
進行編譯。
如果出現找不到Test1.txt或者/data/Test2.txt的錯誤,是因為運行可執行文件時,會先將可執行文件進行壓縮,壓縮的位置在 /tmp 下,再執行,所以被打包進去的數據文件在被解壓的路徑下,而,程序是在運行的路徑下搜索,即可執行文件的目錄下,所以找不到數據文件。
所以我們在編寫TestAdd.py文件時,添加了如下函數
用于尋找pyinstaller臨時文件目錄。
6.2 如果需要加密編譯
如果需要加密編譯,參照第5節,運行以下命令:
pyinstaller -F --key 123456789 TestAdd.py生成TestAdd.spec,其文件內容如下:
# -*- mode: python -*-block_cipher = pyi_crypto.PyiBlockCipher(key='123456789')a = Analysis(['TestAdd.py'],pathex=['C:\\Users\\Administrator\\Desktop\\PythonExe\\Test'],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='TestAdd',debug=False,bootloader_ignore_signals=False,strip=False,upx=True,runtime_tmpdir=None,console=True )將其修改為:
# -*- mode: python -*-block_cipher = pyi_crypto.PyiBlockCipher(key='123456789')a = Analysis(['TestAdd.py'],pathex=['C:\\Users\\Administrator\\Desktop\\PythonExe\\Test'],binaries=[],datas=[('Test1.txt','.'),('data/Test2.txt','data')],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='TestAdd',debug=False,bootloader_ignore_signals=False,strip=False,upx=True,runtime_tmpdir=None,console=True )保存之后,執行命令:
pyinstaller TestAdd.spec編譯文件,這樣,編譯出來的exe既是加密后的也是引入外部數據的。
7 為exe添加圖標
找一個在線生成圖標ico的網站,生成自己想要的圖標,我用的是http://www.faviconico.org/favicon,然后將圖標放在py同目錄下。
然后修改TestAdd.spec文件,源文件內容如下:
修改為:
# -*- mode: python -*-block_cipher = pyi_crypto.PyiBlockCipher(key='123456789')a = Analysis(['TestAdd.py'],pathex=['C:\\Users\\Administrator\\Desktop\\PythonExe\\Test'],binaries=[],datas=[('Test1.txt','.'),('data/Test2.txt','data')],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='TestAdd',debug=False,bootloader_ignore_signals=False,strip=False,upx=True,runtime_tmpdir=None,console=True,icon='ico.ico') #增加的圖標然后使用命令
pyinstaller TestAdd.spec
生成exe文件,我們可以看到exe是帶圖標的了
如果您覺得這篇博文有用,請訪問我的個人站:http://www.stubbornhuang.com/,更多博文干貨等著您。
總結
以上是生活随笔為你收集整理的PyInstaller:编译exe与反编译的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: hive执行报错:Both left a
- 下一篇: linux下批量更改一个目下的目录和文件