获得PyInstaller打包exe的py源码

 参考链接:https://laucyun.com/33359ed9f725529ac9b606d054c8459d.html

way1:pyi-archive_viewer 提取pyc,uncompyle6反编译pyc得到py

way2:python-exe-unpacker   https://github.com/countercept/python-exe-unpacker

way3:PyInstaller Extractor  https://sourceforge.net/projects/pyinstallerextractor/

PyInstaller Extractor (修改添加3.7支持)

  1 """
  2 PyInstaller Extractor v1.9 (Supports pyinstaller 3.3, 3.2, 3.1, 3.0, 2.1, 2.0)
  3 Author : Extreme Coders
  4 E-mail : extremecoders(at)hotmail(dot)com
  5 Web    : https://0xec.blogspot.com
  6 Date   : 29-November-2017
  7 Url    : https://sourceforge.net/projects/pyinstallerextractor/
  8 
  9 For any suggestions, leave a comment on
 10 https://forum.tuts4you.com/topic/34455-pyinstaller-extractor/
 11 
 12 This script extracts a pyinstaller generated executable file.
 13 Pyinstaller installation is not needed. The script has it all.
 14 
 15 For best results, it is recommended to run this script in the
 16 same version of python as was used to create the executable.
 17 This is just to prevent unmarshalling errors(if any) while
 18 extracting the PYZ archive.
 19 
 20 Usage : Just copy this script to the directory where your exe resides
 21         and run the script with the exe file name as a parameter
 22 
 23 C:path	oexe>python pyinstxtractor.py <filename>
 24 $ /path/to/exe/python pyinstxtractor.py <filename>
 25 
 26 Licensed under GNU General Public License (GPL) v3.
 27 You are free to modify this source.
 28 
 29 CHANGELOG
 30 ================================================
 31 
 32 Version 1.1 (Jan 28, 2014)
 33 -------------------------------------------------
 34 - First Release
 35 - Supports only pyinstaller 2.0
 36 
 37 Version 1.2 (Sept 12, 2015)
 38 -------------------------------------------------
 39 - Added support for pyinstaller 2.1 and 3.0 dev
 40 - Cleaned up code
 41 - Script is now more verbose
 42 - Executable extracted within a dedicated sub-directory
 43 
 44 (Support for pyinstaller 3.0 dev is experimental)
 45 
 46 Version 1.3 (Dec 12, 2015)
 47 -------------------------------------------------
 48 - Added support for pyinstaller 3.0 final
 49 - Script is compatible with both python 2.x & 3.x (Thanks to Moritz Kroll @ Avira Operations GmbH & Co. KG)
 50 
 51 Version 1.4 (Jan 19, 2016)
 52 -------------------------------------------------
 53 - Fixed a bug when writing pyc files >= version 3.3 (Thanks to Daniello Alto: https://github.com/Djamana)
 54 
 55 Version 1.5 (March 1, 2016)
 56 -------------------------------------------------
 57 - Added support for pyinstaller 3.1 (Thanks to Berwyn Hoyt for reporting)
 58 
 59 Version 1.6 (Sept 5, 2016)
 60 -------------------------------------------------
 61 - Added support for pyinstaller 3.2
 62 - Extractor will use a random name while extracting unnamed files.
 63 - For encrypted pyz archives it will dump the contents as is. Previously, the tool would fail.
 64 
 65 Version 1.7 (March 13, 2017)
 66 -------------------------------------------------
 67 - Made the script compatible with python 2.6 (Thanks to Ross for reporting)
 68 
 69 Version 1.8 (April 28, 2017)
 70 -------------------------------------------------
 71 - Support for sub-directories in .pyz files (Thanks to Moritz Kroll @ Avira Operations GmbH & Co. KG)
 72 
 73 Version 1.9 (November 29, 2017)
 74 -------------------------------------------------
 75 - Added support for pyinstaller 3.3
 76 - Display the scripts which are run at entry (Thanks to Michael Gillespie @ malwarehunterteam for the feature request)
 77 
 78 """
 79 
 80 from __future__ import print_function
 81 import os
 82 import struct
 83 import marshal
 84 import zlib
 85 import sys
 86 import imp
 87 import types
 88 from uuid import uuid4 as uniquename
 89 
 90 
 91 class CTOCEntry:
 92     def __init__(self, position, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name):
 93         self.position = position
 94         self.cmprsdDataSize = cmprsdDataSize
 95         self.uncmprsdDataSize = uncmprsdDataSize
 96         self.cmprsFlag = cmprsFlag
 97         self.typeCmprsData = typeCmprsData
 98         self.name = name
 99 
100 
101 class PyInstArchive:
102     PYINST20_COOKIE_SIZE = 24           # For pyinstaller 2.0
103     PYINST21_COOKIE_SIZE = 24 + 64      # For pyinstaller 2.1+
104     MAGIC = b'MEI1413121316'  # Magic number which identifies pyinstaller
105 
106     def __init__(self, path):
107         self.filePath = path
108 
109 
110     def open(self):
111         try:
112             self.fPtr = open(self.filePath, 'rb')
113             self.fileSize = os.stat(self.filePath).st_size
114         except:
115             print('[*] Error: Could not open {0}'.format(self.filePath))
116             return False
117         return True
118 
119 
120     def close(self):
121         try:
122             self.fPtr.close()
123         except:
124             pass
125 
126 
127     def checkFile(self):
128         print('[*] Processing {0}'.format(self.filePath))
129         # Check if it is a 2.0 archive
130         self.fPtr.seek(self.fileSize - self.PYINST20_COOKIE_SIZE, os.SEEK_SET)
131         magicFromFile = self.fPtr.read(len(self.MAGIC))
132 
133         if magicFromFile == self.MAGIC:
134             self.pyinstVer = 20     # pyinstaller 2.0
135             print('[*] Pyinstaller version: 2.0')
136             return True
137 
138         # Check for pyinstaller 2.1+ before bailing out
139         self.fPtr.seek(self.fileSize - self.PYINST21_COOKIE_SIZE, os.SEEK_SET)
140         magicFromFile = self.fPtr.read(len(self.MAGIC))
141 
142         if magicFromFile == self.MAGIC:
143             print('[*] Pyinstaller version: 2.1+')
144             self.pyinstVer = 21     # pyinstaller 2.1+
145             return True
146 
147         print('[*] Error : Unsupported pyinstaller version or not a pyinstaller archive')
148         return False
149 
150 
151     def getCArchiveInfo(self):
152         try:
153             if self.pyinstVer == 20:
154                 self.fPtr.seek(self.fileSize - self.PYINST20_COOKIE_SIZE, os.SEEK_SET)
155 
156                 # Read CArchive cookie
157                 (magic, lengthofPackage, toc, tocLen, self.pyver) = 
158                 struct.unpack('!8siiii', self.fPtr.read(self.PYINST20_COOKIE_SIZE))
159 
160             elif self.pyinstVer == 21:
161                 self.fPtr.seek(self.fileSize - self.PYINST21_COOKIE_SIZE, os.SEEK_SET)
162 
163                 # Read CArchive cookie
164                 (magic, lengthofPackage, toc, tocLen, self.pyver, pylibname) = 
165                 struct.unpack('!8siiii64s', self.fPtr.read(self.PYINST21_COOKIE_SIZE))
166 
167         except:
168             print('[*] Error : The file is not a pyinstaller archive')
169             return False
170 
171         print('[*] Python version: {0}'.format(self.pyver))
172 
173         # Overlay is the data appended at the end of the PE
174         self.overlaySize = lengthofPackage
175         self.overlayPos = self.fileSize - self.overlaySize
176         self.tableOfContentsPos = self.overlayPos + toc
177         self.tableOfContentsSize = tocLen
178 
179         print('[*] Length of package: {0} bytes'.format(self.overlaySize))
180         return True
181 
182 
183     def parseTOC(self):
184         # Go to the table of contents
185         self.fPtr.seek(self.tableOfContentsPos, os.SEEK_SET)
186 
187         self.tocList = []
188         parsedLen = 0
189 
190         # Parse table of contents
191         while parsedLen < self.tableOfContentsSize:
192             (entrySize, ) = struct.unpack('!i', self.fPtr.read(4))
193             nameLen = struct.calcsize('!iiiiBc')
194 
195             (entryPos, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name) = 
196             struct.unpack( 
197                 '!iiiBc{0}s'.format(entrySize - nameLen), 
198                 self.fPtr.read(entrySize - 4))
199 
200             name = name.decode('utf-8').rstrip('')
201             if len(name) == 0:
202                 name = str(uniquename())
203                 print('[!] Warning: Found an unamed file in CArchive. Using random name {0}'.format(name))
204 
205             self.tocList.append( 
206                                 CTOCEntry(                      
207                                     self.overlayPos + entryPos, 
208                                     cmprsdDataSize,             
209                                     uncmprsdDataSize,           
210                                     cmprsFlag,                  
211                                     typeCmprsData,              
212                                     name                        
213                                 ))
214 
215             parsedLen += entrySize
216         print('[*] Found {0} files in CArchive'.format(len(self.tocList)))
217 
218 
219 
220     def extractFiles(self):
221         print('[*] Beginning extraction...please standby')
222         extractionDir = os.path.join(os.getcwd(), os.path.basename(self.filePath) + '_extracted')
223 
224         if not os.path.exists(extractionDir):
225             os.mkdir(extractionDir)
226 
227         os.chdir(extractionDir)
228 
229         for entry in self.tocList:
230             basePath = os.path.dirname(entry.name)
231             if basePath != '':
232                 # Check if path exists, create if not
233                 if not os.path.exists(basePath):
234                     os.makedirs(basePath)
235 
236             self.fPtr.seek(entry.position, os.SEEK_SET)
237             data = self.fPtr.read(entry.cmprsdDataSize)
238 
239             if entry.cmprsFlag == 1:
240                 data = zlib.decompress(data)
241                 # Malware may tamper with the uncompressed size
242                 # Comment out the assertion in such a case
243                 assert len(data) == entry.uncmprsdDataSize # Sanity Check
244 
245             with open(entry.name, 'wb') as f:
246                 f.write(data)
247 
248             if entry.typeCmprsData == b's':
249                 print('[+] Possible entry point: {0}'.format(entry.name))
250 
251             elif entry.typeCmprsData == b'z' or entry.typeCmprsData == b'Z':
252                 self._extractPyz(entry.name)
253 
254 
255     def _extractPyz(self, name):
256         dirName =  name + '_extracted'
257         # Create a directory for the contents of the pyz
258         if not os.path.exists(dirName):
259             os.mkdir(dirName)
260 
261         with open(name, 'rb') as f:
262             pyzMagic = f.read(4)
263             assert pyzMagic == b'PYZ' # Sanity Check
264 
265             pycHeader = f.read(4) # Python magic value
266 
267             if imp.get_magic() != pycHeader:
268                 print('[!] Warning: The script is running in a different python version than the one used to build the executable')
269                 print('    Run this script in Python{0} to prevent extraction errors(if any) during unmarshalling'.format(self.pyver))
270 
271             (tocPosition, ) = struct.unpack('!i', f.read(4))
272             f.seek(tocPosition, os.SEEK_SET)
273 
274             try:
275                 toc = marshal.load(f)
276             except:
277                 print('[!] Unmarshalling FAILED. Cannot extract {0}. Extracting remaining files.'.format(name))
278                 return
279 
280             print('[*] Found {0} files in PYZ archive'.format(len(toc)))
281 
282             # From pyinstaller 3.1+ toc is a list of tuples
283             if type(toc) == list:
284                 toc = dict(toc)
285 
286             for key in toc.keys():
287                 (ispkg, pos, length) = toc[key]
288                 f.seek(pos, os.SEEK_SET)
289 
290                 fileName = key
291                 try:
292                     # for Python > 3.3 some keys are bytes object some are str object
293                     fileName = key.decode('utf-8')
294                 except:
295                     pass
296 
297                 # Make sure destination directory exists, ensuring we keep inside dirName
298                 destName = os.path.join(dirName, fileName.replace("..", "__"))
299                 destDirName = os.path.dirname(destName)
300                 if not os.path.exists(destDirName):
301                     os.makedirs(destDirName)
302 
303                 try:
304                     data = f.read(length)
305                     data = zlib.decompress(data)
306                 except:
307                     print('[!] Error: Failed to decompress {0}, probably encrypted. Extracting as is.'.format(fileName))
308                     open(destName + '.pyc.encrypted', 'wb').write(data)
309                     continue
310 
311                 with open(destName + '.pyc', 'wb') as pycFile:
312                     pycFile.write(pycHeader)      # Write pyc magic
313                     pycFile.write(b'' * 4)      # Write timestamp
314                     if self.pyver >= 33:
315                         pycFile.write(b'' * 4)  # Size parameter added in Python 3.3
316                     if self.pyver >= 37:
317                         pycFile.write(b'' * 4)  # Size parameter added in Python 3.7
318                     pycFile.write(data)
319 
320 
321 def main():
322     if len(sys.argv) < 2:
323         print('[*] Usage: pyinstxtractor.py <filename>')
324 
325     else:
326         arch = PyInstArchive(sys.argv[1])
327         if arch.open():
328             if arch.checkFile():
329                 if arch.getCArchiveInfo():
330                     arch.parseTOC()
331                     arch.extractFiles()
332                     arch.close()
333                     print('[*] Successfully extracted pyinstaller archive: {0}'.format(sys.argv[1]))
334                     print('')
335                     print('You can now use a python decompiler on the pyc files within the extracted directory')
336                     return
337 
338             arch.close()
339 
340 
341 if __name__ == '__main__':
342     main()
View Code

推荐使用pyi-archive_viewer。而python-exe-unpacker 有时会报错,(两年前的工具了)


使用pyi-archive_viewer需要先安装PyInstaller(https://github.com/pyinstaller/pyinstaller

  • 安装PyInstaller模块,注意是:pip install PyInstaller

pyi-archive_viewer

 相关命令:

U: go Up one level
O <name>: open embedded archive name
X <name>: extract name
Q: quit

  • pyi-archive_viewer中使用x命令提取文件

pyinstaller打包会去掉pyc的magic和时间戳,需要手动添加数据

(可以先提取第一个文件,后面提取的文件对比添加数据)

 对照添加数据

主模块的依赖包在PYZ-00.pyz,使用o命令打开

 之后再使用x命令提取文件。

  • 反编译pyc

pip install uncompyle6

uncompyle6 (https://github.com/rocky/python-uncompyle6)

Run

$ uncompyle6 *compiled-python-file-pyc-or-pyo*

For usage help:

$ uncompyle6 -h

 当然也有很多pyc反编译在线工具可用


  • PyInstaller Extractor

 python pyinstxtractor.py xxx.exe

 

 生成目录

原文地址:https://www.cnblogs.com/DirWang/p/12018949.html