Advertisement
8thbit

PyInstaller Extractor v1.4

Feb 2nd, 2016
124
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.24 KB | None | 0 0
  1. '''
  2. PyInstaller Extractor v1.4 (Supports pyinstaller 3.0, 2.1, 2.0)
  3. Author : Extreme Coders
  4. E-mail : extremecoders(at)hotmail(dot)com
  5. Date   : 19-Jan-2016
  6. Url    : https://sourceforge.net/projects/pyinstallerextractor/
  7.  
  8. For any suggestions, leave a comment on
  9. https://forum.tuts4you.com/topic/34455-pyinstaller-extractor/
  10.  
  11. This script extracts a pyinstaller generated executable file.
  12. Pyinstaller installation is not needed. The script has it all.
  13.  
  14. For best results, it is recommended to run this script in the
  15. same version of python as was used to create the executable.
  16. This is just to prevent unmarshalling errors(if any) while
  17. extracting the PYZ archive.
  18.  
  19. Usage : Just copy this script to the directory where your exe resides
  20.        and run the script with the exe file name as a parameter
  21.  
  22. C:\path\to\exe\>python pyinstxtractor.py <filename>
  23. $ /path/to/exe/python pyinstxtractor.py <filename>
  24.  
  25. Licensed under GNU General Public License (GPL) v3.
  26. You are free to modify this source.
  27. '''
  28.  
  29. import os
  30. import struct
  31. import marshal
  32. import zlib
  33. import sys
  34. import imp
  35. import types
  36.  
  37.  
  38. class CTOCEntry:
  39.     def __init__(self, position, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name):
  40.         self.position = position
  41.         self.cmprsdDataSize = cmprsdDataSize
  42.         self.uncmprsdDataSize = uncmprsdDataSize
  43.         self.cmprsFlag = cmprsFlag
  44.         self.typeCmprsData = typeCmprsData
  45.         self.name = name
  46.  
  47.  
  48. class PyInstArchive:
  49.     PYINST20_COOKIE_SIZE = 24           # For pyinstaller 2.0
  50.     PYINST21_COOKIE_SIZE = 24 + 64      # For pyinstaller 2.1+
  51.     MAGIC = b'MEI\014\013\012\013\016'  # Magic number which identifies pyinstaller
  52.  
  53.     def __init__(self, path):
  54.         self.filePath = path
  55.  
  56.  
  57.     def open(self):
  58.         try:
  59.             self.fPtr = open(self.filePath, 'rb')
  60.             self.fileSize = os.stat(self.filePath).st_size
  61.         except:
  62.             print('[*] Error: Could not open {}'.format(self.filePath))
  63.             return False
  64.         return True
  65.  
  66.  
  67.     def close(self):
  68.         try:
  69.             self.fPtr.close()
  70.         except:
  71.             pass
  72.  
  73.  
  74.     def checkFile(self):
  75.         print('[*] Processing {}'.format(self.filePath))
  76.         # Check if it is a 2.0 archive
  77.         self.fPtr.seek(self.fileSize - self.PYINST20_COOKIE_SIZE, os.SEEK_SET)
  78.         magicFromFile = self.fPtr.read(len(self.MAGIC))
  79.  
  80.         if magicFromFile == self.MAGIC:
  81.             self.pyinstVer = 20     # pyinstaller 2.0
  82.             print('[*] Pyinstaller version: 2.0')
  83.             return True
  84.  
  85.         # Check for pyinstaller 2.1+ before bailing out
  86.         self.fPtr.seek(self.fileSize - self.PYINST21_COOKIE_SIZE, os.SEEK_SET)
  87.         magicFromFile = self.fPtr.read(len(self.MAGIC))
  88.  
  89.         if magicFromFile == self.MAGIC:
  90.             print('[*] Pyinstaller version: 2.1+')
  91.             self.pyinstVer = 21     # pyinstaller 2.1+
  92.             return True
  93.  
  94.         print('[*] Error : Unsupported pyinstaller version or not a pyinstaller archive')
  95.         return False
  96.  
  97.  
  98.     def getCArchiveInfo(self):
  99.         try:
  100.             if self.pyinstVer == 20:
  101.                 self.fPtr.seek(self.fileSize - self.PYINST20_COOKIE_SIZE, os.SEEK_SET)
  102.  
  103.                 # Read CArchive cookie
  104.                 (magic, lengthofPackage, toc, tocLen, self.pyver) = \
  105.                 struct.unpack('!8siiii', self.fPtr.read(self.PYINST20_COOKIE_SIZE))
  106.  
  107.             elif self.pyinstVer == 21:
  108.                 self.fPtr.seek(self.fileSize - self.PYINST21_COOKIE_SIZE, os.SEEK_SET)
  109.  
  110.                 # Read CArchive cookie
  111.                 (magic, lengthofPackage, toc, tocLen, self.pyver, pylibname) = \
  112.                 struct.unpack('!8siiii64s', self.fPtr.read(self.PYINST21_COOKIE_SIZE))
  113.  
  114.         except:
  115.             print('[*] Error : The file is not a pyinstaller archive')
  116.             return False
  117.  
  118.         print('[*] Python version: {}'.format(self.pyver))
  119.  
  120.         # Overlay is the data appended at the end of the PE
  121.         self.overlaySize = lengthofPackage
  122.         self.overlayPos = self.fileSize - self.overlaySize
  123.         self.tableOfContentsPos = self.overlayPos + toc
  124.         self.tableOfContentsSize = tocLen
  125.  
  126.         print('[*] Length of package: {} bytes'.format(self.overlaySize))
  127.         return True
  128.  
  129.  
  130.     def parseTOC(self):
  131.         # Go to the table of contents
  132.         self.fPtr.seek(self.tableOfContentsPos, os.SEEK_SET)
  133.  
  134.         self.tocList = []
  135.         parsedLen = 0
  136.  
  137.         # Parse table of contents
  138.         while parsedLen < self.tableOfContentsSize:
  139.             (entrySize, ) = struct.unpack('!i', self.fPtr.read(4))
  140.             nameLen = struct.calcsize('!iiiiBc')
  141.  
  142.             (entryPos, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name) = \
  143.             struct.unpack( \
  144.                 '!iiiBc{}s'.format(entrySize - nameLen), \
  145.                 self.fPtr.read(entrySize - 4))
  146.  
  147.             self.tocList.append( \
  148.                                 CTOCEntry(                      \
  149.                                     self.overlayPos + entryPos, \
  150.                                     cmprsdDataSize,             \
  151.                                     uncmprsdDataSize,           \
  152.                                     cmprsFlag,                  \
  153.                                     typeCmprsData,              \
  154.                                     name.decode('utf-8').rstrip('\0')           \
  155.                                 ))
  156.  
  157.             parsedLen += entrySize
  158.         print('[*] Found {} files in CArchive'.format(len(self.tocList)))
  159.  
  160.  
  161.  
  162.     def extractFiles(self):
  163.         print('[*] Begining extraction...please standby')
  164.         extractionDir = os.path.join(os.getcwd(), os.path.basename(self.filePath) + '_extracted')
  165.  
  166.         if not os.path.exists(extractionDir):
  167.             os.mkdir(extractionDir)
  168.  
  169.         os.chdir(extractionDir)
  170.  
  171.         for entry in self.tocList:
  172.             basePath = os.path.dirname(entry.name)
  173.             if basePath != '':
  174.                 # Check if path exists, create if not
  175.                 if not os.path.exists(basePath):
  176.                     os.makedirs(basePath)
  177.  
  178.             self.fPtr.seek(entry.position, os.SEEK_SET)
  179.             data = self.fPtr.read(entry.cmprsdDataSize)
  180.  
  181.             if entry.cmprsFlag == 1:
  182.                 data = zlib.decompress(data)
  183.                 # Malware may tamper with the uncompressed size
  184.                 # Comment out the assertion in such a case
  185.                 assert len(data) == entry.uncmprsdDataSize # Sanity Check
  186.  
  187.             with open(entry.name, 'wb') as f:
  188.                 f.write(data)
  189.  
  190.             if entry.typeCmprsData == b'z':
  191.                 self._extractPyz(entry.name)
  192.  
  193.  
  194.     def _extractPyz(self, name):
  195.         dirName =  name + '_extracted'
  196.         # Create a directory for the contents of the pyz
  197.         if not os.path.exists(dirName):
  198.             os.mkdir(dirName)
  199.  
  200.         with open(name, 'rb') as f:
  201.             pyzMagic = f.read(4)
  202.             assert pyzMagic == b'PYZ\0' # Sanity Check
  203.  
  204.             pycHeader = f.read(4) # Python magic value
  205.  
  206.             if imp.get_magic() != pycHeader:
  207.                 print('[!] WARNING: The script is running in a different python version than the one used to build the executable')
  208.                 print('    Run this script in Python{} to prevent extraction errors(if any) during unmarshalling'.format(self.pyver))
  209.  
  210.             (tocPosition, ) = struct.unpack('!i', f.read(4))
  211.             f.seek(tocPosition, os.SEEK_SET)
  212.  
  213.             try:
  214.                 toc = marshal.load(f)
  215.             except:
  216.                 print('[!] Unmarshalling FAILED. Cannot extract {}. Extracting remaining files.'.format(name))
  217.                 return
  218.  
  219.             print('[*] Found {} files in PYZ archive'.format(len(toc)))
  220.  
  221.             for key in toc.keys():
  222.                 (ispkg, pos, length) = toc[key]
  223.                 f.seek(pos, os.SEEK_SET)
  224.                 data = zlib.decompress(f.read(length))
  225.                 fileName = key
  226.                 try:
  227.                     # for Python > 3.3 some keys are bytes object some are str object
  228.                     fileName = key.decode('utf-8')
  229.                 except:
  230.                     pass
  231.  
  232.                 with open(os.path.join(dirName, fileName + '.pyc'), 'wb') as pycFile:
  233.                     pycFile.write(pycHeader)    # Write pyc magic
  234.                     pycFile.write(b'\0' * 4)     # Write timestamp
  235.                     if self.pyver >= 33:
  236.                         pycFile.write(b'\0' * 4)  # Size parameter added in Python 3.3
  237.                     pycFile.write(data)
  238.  
  239.  
  240. def main():
  241.     if len(sys.argv) < 2:
  242.         print('[*] Usage: pyinstxtractor.py <filename>')
  243.  
  244.     else:
  245.         arch = PyInstArchive(sys.argv[1])
  246.         if arch.open():
  247.             if arch.checkFile():
  248.                 if arch.getCArchiveInfo():
  249.                     arch.parseTOC()
  250.                     arch.extractFiles()
  251.                     arch.close()
  252.                     print('[*] Successfully extracted pyinstaller archive: {}'.format(sys.argv[1]))
  253.                     print('')
  254.                     print('You can now use a python decompiler on the pyc files within the extracted directory')
  255.                     return
  256.  
  257.             arch.close()
  258.  
  259.  
  260. if __name__ == '__main__':
  261.     main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement