8 Commits

Author SHA1 Message Date
extremecoders-re
eaac3869c0 Mention support for encrypted exe's in -ng version 2022-09-23 15:18:26 +05:30
extremecoders-re
0651378023 Add link to pyinstxtractor-web 2022-09-22 03:52:33 +05:30
extremecoders-re
3f9ff3a7b9 Add note to pyinstxtractor-ng 2022-09-18 17:36:41 +05:30
extremecoders-re
dd3a4c4e89 Update README 2022-09-17 16:04:37 +05:30
extremecoders-re
129460a242 Correct major minor version check 2022-09-17 15:56:40 +05:30
extremecoders-re
45cfb9b592 Mention support for 5.4.1, 5.4 2022-09-17 15:38:01 +05:30
extremecoders-re
6c72177e58 Use correct pyc header magic value, Closes #53, #54 2022-09-17 15:21:29 +05:30
extremecoders-re
f0f5741ae4 Mention support for 5.2, 5.3 2022-08-21 14:04:44 +05:30
2 changed files with 56 additions and 20 deletions

View File

@@ -1,12 +1,12 @@
# PyInstaller Extractor
PyInstaller Extractor is a Python script to extract the contents of a PyInstaller generated Windows executable file. The contents of the pyz file (usually pyc files) present inside the executable are also extracted.
PyInstaller Extractor is a Python script to extract the contents of a PyInstaller generated executable file.
The header of the pyc files are automatically fixed so that a Python bytecode decompiler will recognize it. The script can run on both Python 2.x and 3.x. Pyinstaller versions 2.0, 2.1, 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.5.1, 4.6, 4.7, 4.8, 4.9, 4.10, 5.0, 5.0.1, 5.1 are [tested](https://github.com/extremecoders-re/pyinstxtractor-test-binaries) & supported. Probably will work with other versions too.
The header of the pyc files are automatically fixed so that a Python bytecode decompiler will recognize it. The script can run on both Python 2.x and 3.x. PyInstaller versions 2.0, 2.1, 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.5.1, 4.6, 4.7, 4.8, 4.9, 4.10, 5.0, 5.0.1, 5.1, 5.2, 5.3, 5.4, 5.4.1 are [tested](https://github.com/extremecoders-re/pyinstxtractor-test-binaries) & supported. Probably will work with other versions too.
This project was originally hosted on [SourceForge](https://sourceforge.net/projects/pyinstallerextractor/).
## How to use
## Usage
The script can be run by passing the name of the exe as an argument.
@@ -43,10 +43,16 @@ X:\> uncompyle6.exe test.exe_extracted\PYZ-00.pyz_extracted\__future__.pyc
```
## Extracting Linux ELF binaries
Pyinstxtractor can now natively extract Linux ELF binaries without other additional tools.
Pyinstxtractor can natively extract Linux ELF binaries without other requiring additional tools.
For other questions and information, please see the [Wiki](https://github.com/extremecoders-re/pyinstxtractor/wiki/Extracting-Linux-ELF-binaries) and the [FAQ](https://github.com/extremecoders-re/pyinstxtractor/wiki/Frequently-Asked-Questions)
## See also
- [pyinstxtractor-ng](https://github.com/pyinstxtractor/pyinstxtractor-ng):
A standalone binary version of pyinstxtractor. This tool doesn't require Python to run and can extract all supported versions of PyInstaller. It also supports encrypted pyinstaller executables.
- [pyinstxtractor-web](https://pyinstxtractor-web.netlify.app): pyinstxtractor running in the web browser, powered by Go & GopherJS.
## License
GNU General Public License v3.0
GNU General Public License v3.0

View File

@@ -1,5 +1,5 @@
"""
PyInstaller Extractor v2.0 (Supports pyinstaller 5.1, 5.0.1, 5.0, 4.10, 4.9, 4.8, 4.7, 4.6, 4.5.1, 4.5, 4.4, 4.3, 4.2, 4.1, 4.0, 3.6, 3.5, 3.4, 3.3, 3.2, 3.1, 3.0, 2.1, 2.0)
PyInstaller Extractor v2.0 (Supports pyinstaller 5.4.1, 5.4, 5.3, 5.2, 5.1, 5.0.1, 5.0, 4.10, 4.9, 4.8, 4.7, 4.6, 4.5.1, 4.5, 4.4, 4.3, 4.2, 4.1, 4.0, 3.6, 3.5, 3.4, 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
@@ -91,14 +91,6 @@ import zlib
import sys
from uuid import uuid4 as uniquename
# imp is deprecated in Python3 in favour of importlib
if sys.version_info.major == 3:
from importlib.util import MAGIC_NUMBER
pyc_magic = MAGIC_NUMBER
else:
import imp
pyc_magic = imp.get_magic()
class CTOCEntry:
def __init__(self, position, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name):
@@ -117,6 +109,8 @@ class PyInstArchive:
def __init__(self, path):
self.filePath = path
self.pycMagic = b'\0' * 4
self.barePycList = [] # List of pyc's whose headers have to be fixed
def open(self):
@@ -209,7 +203,7 @@ class PyInstArchive:
# Additional data after the cookie
tailBytes = self.fileSize - self.cookiePos - (self.PYINST20_COOKIE_SIZE if self.pyinstVer == 20 else self.PYINST21_COOKIE_SIZE)
# Overlay is the data appended at the end of the PE
self.overlaySize = lengthofPackage + tailBytes
self.overlayPos = self.fileSize - self.overlaySize
@@ -301,13 +295,32 @@ class PyInstArchive:
# s -> ARCHIVE_ITEM_PYSOURCE
# Entry point are expected to be python scripts
print('[+] Possible entry point: {0}.pyc'.format(entry.name))
if self.pycMagic == b'\0' * 4:
# if we don't have the pyc header yet, fix them in a later pass
self.barePycList.append(entry.name + '.pyc')
self._writePyc(entry.name + '.pyc', data)
elif entry.typeCmprsData == b'M' or entry.typeCmprsData == b'm':
# M -> ARCHIVE_ITEM_PYPACKAGE
# m -> ARCHIVE_ITEM_PYMODULE
# packages and modules are pyc files with their header's intact
self._writeRawData(entry.name + '.pyc', data)
# packages and modules are pyc files with their header intact
# From PyInstaller 5.3 and above pyc headers are no longer stored
# https://github.com/pyinstaller/pyinstaller/commit/a97fdf
if data[2:4] == b'\r\n':
# < pyinstaller 5.3
if self.pycMagic == b'\0' * 4:
self.pycMagic = data[0:4]
self._writeRawData(entry.name + '.pyc', data)
else:
# >= pyinstaller 5.3
if self.pycMagic == b'\0' * 4:
# if we don't have the pyc header yet, fix them in a later pass
self.barePycList.append(entry.name + '.pyc')
self._writePyc(entry.name + '.pyc', data)
else:
self._writeRawData(entry.name, data)
@@ -315,10 +328,20 @@ class PyInstArchive:
if entry.typeCmprsData == b'z' or entry.typeCmprsData == b'Z':
self._extractPyz(entry.name)
# Fix bare pyc's if any
self._fixBarePycs()
def _fixBarePycs(self):
for pycFile in self.barePycList:
with open(pycFile, 'r+b') as pycFile:
# Overwrite the first four bytes
pycFile.write(self.pycMagic)
def _writePyc(self, filename, data):
with open(filename, 'wb') as pycFile:
pycFile.write(pyc_magic) # pyc magic
pycFile.write(self.pycMagic) # pyc magic
if self.pymaj >= 3 and self.pymin >= 7: # PEP 552 -- Deterministic pycs
pycFile.write(b'\0' * 4) # Bitfield
@@ -342,10 +365,17 @@ class PyInstArchive:
pyzMagic = f.read(4)
assert pyzMagic == b'PYZ\0' # Sanity Check
pycHeader = f.read(4) # Python magic value
pyzPycMagic = f.read(4) # Python magic value
if self.pycMagic == b'\0' * 4:
self.pycMagic = pyzPycMagic
elif self.pycMagic != pyzPycMagic:
self.pycMagic = pyzPycMagic
print('[!] Warning: pyc magic of files inside PYZ archive are different from those in CArchive')
# Skip PYZ extraction if not running under the same python version
if pyc_magic != pycHeader:
if self.pymaj != sys.version_info.major or self.pymin != sys.version_info.minor:
print('[!] Warning: This script is running in a different Python version than the one used to build the executable.')
print('[!] Please run this script in Python {0}.{1} to prevent extraction errors during unmarshalling'.format(self.pymaj, self.pymin))
print('[!] Skipping pyz extraction')