-
Notifications
You must be signed in to change notification settings - Fork 628
Frequently Asked Questions
Digitally signed exe's are now supported without requiring other tools.
You do not need to fix the header of the extracted pyc's. They are automatically taken care of. In case you are using an old version of pyinstxtractor from SourceForge please update to the latest version from here.
Is it necessary to run the script in the same version of Python which was used to build the executable?
It's important to use the same version of Python. If you don't know which version of Python has been used, run the script once and it would tell you the version of Python.
Alternatively use pyinstxtractor-ng which doesn't depend on Python to run.
Firstly, if you use a different version of Python, pyinstxtractor will not extract the PYZ archive. Secondly, the magic signature of the extracted pyc's (first 4 bytes) in the top-level directory will be wrong. In such cases, de-compiling the pyc's will fail. If you are familiar with Python internals and pyc file format you can of-course correct the signature manually using a hex editor.
pyinstxtractor-ng doesn't have these issues as mentioned in the previous question.
It's recommended to use miniconda to install and manage multiple versions of Python, each in their own isolated environment. For example, to create a Python 3.6 environment you can simply do
$ conda create -n my-py3.6-environment python=3.6
$ conda activate my-py3.6-environment
If you don't like conda, you can use pyenv.
Encrypted pyz archives are supported out of the box in pyinstxtractor-ng.
NOTE: For PyInstaller versions >= 4.0, see down below
Pyinstxtractor doesn't support encrypted pyz archives. This will be introduced later in the form of a separate script (See pyinstxtractor-ng).
Currently you can use the following snippet to decrypt encrypted pyc's within the pyz extracted directory. Note that the script below is written to run on Python 2.7 and will work on PyInstaller versions earlier than 4.0
#!/usr/bin/env python2
# For pyinstaller < 4.0
from Crypto.Cipher import AES
import zlib
CRYPT_BLOCK_SIZE = 16
# key obtained from pyimod00_crypto_key
key = 'MySup3rS3cr3tK3y'
inf = open('_abcoll.pyc.encrypted', 'rb') # encrypted file input
outf = open('_abcoll.pyc', 'wb') # output file
# Initialization vector
iv = inf.read(CRYPT_BLOCK_SIZE)
cipher = AES.new(key, AES.MODE_CFB, iv)
# Decrypt and decompress
plaintext = zlib.decompress(cipher.decrypt(inf.read()))
# Write pyc header
# The header below is for Python 2.7
outf.write('\x03\xf3\x0d\x0a\0\0\0\0')
# Write decrypted data
outf.write(plaintext)
inf.close()
outf.close()
The script can decompile encrypted pyc's from any Python version. However you need to change the pyc header appropriately from the list below. This was generated from xdis
Python 2.7: \x03\xf3\x0d\x0a\0\0\0\0
Python 3.0: \x3b\x0c\x0d\x0a\0\0\0\0
Python 3.1: \x4f\x0c\x0d\x0a\0\0\0\0
Python 3.2: \x6c\x0c\x0d\x0a\0\0\0\0
Python 3.3: \x9e\x0c\x0d\x0a\0\0\0\0\0\0\0\0
Python 3.4: \xee\x0c\x0d\x0a\0\0\0\0\0\0\0\0
Python 3.5: \x17\x0d\x0d\x0a\0\0\0\0\0\0\0\0
Python 3.6: \x33\x0d\x0d\x0a\0\0\0\0\0\0\0\0
Python 3.7: \x42\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0
Python 3.8: \x55\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0
Python 3.9: \x61\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0
Python 3.10: \x6f\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0
Python 3.11: \xa7\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0
For PyInstaller versions >= 4.0, use ONE of the following scripts. This was tested on Python 3.8. Requires the tinyaes module.
#!/usr/bin/env python3
# For pyinstaller >= 4.0
import tinyaes
import zlib
CRYPT_BLOCK_SIZE = 16
# key obtained from pyimod00_crypto_key
key = bytes('MySup3rS3cr3tK3y', 'utf-8')
inf = open('base64.pyc.encrypted', 'rb') # encrypted file input
outf = open('base64.pyc', 'wb') # output file
# Initialization vector
iv = inf.read(CRYPT_BLOCK_SIZE)
cipher = tinyaes.AES(key, iv)
# Decrypt and decompress
plaintext = zlib.decompress(cipher.CTR_xcrypt_buffer(inf.read()))
# Write pyc header
# The header below is for Python 3.8
outf.write(b'\x55\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0')
# Write decrypted data
outf.write(plaintext)
inf.close()
outf.close()
Alternatively, you may use the following script which uses PyCryptoDome instead of tinyaes. (Note: PyInstaller>=4.0 itself uses tinyaes)
#!/usr/bin/env python3
# For pyinstaller >=4.0
from Crypto.Cipher import AES
from Crypto.Util import Counter
import zlib
CRYPT_BLOCK_SIZE = 16
# key obtained from pyimod00_crypto_key
key = bytes('MySup3rS3cr3tK3y', 'utf-8')
inf = open('base64.pyc.encrypted', 'rb') # encrypted file input
outf = open('base64.pyc', 'wb') # output file
# Initialization vector
iv = inf.read(CRYPT_BLOCK_SIZE)
ctr = Counter.new(128, initial_value=int.from_bytes(iv, byteorder='big'))
cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
# Decrypt and decompress
plaintext = zlib.decompress(cipher.decrypt(inf.read()))
# Write pyc header
# The header below is for Python 3.8
outf.write(b'\x55\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0')
# Write decrypted data
outf.write(plaintext)
inf.close()
outf.close()
To decrypt all encrypted pyc files in the PYZ-00.pyz_extracted
directory you can use one of the following script.
# For pyinstaller < 4.0
import glob
import zlib
from Crypto.Cipher import AES
from pathlib import Path
CRYPT_BLOCK_SIZE = 16
# key obtained from pyimod00_crypto_key
key = bytes('MySup3rS3cr3tK3y', 'utf-8')
for p in Path("PYZ-00.pyz_extracted").glob("**/*.pyc.encrypted"):
inf = open(p, 'rb') # encrypted file input
outf = open(p.with_name(p.stem), 'wb') # output file
# Initialization vector
iv = inf.read(CRYPT_BLOCK_SIZE)
cipher = AES.new(key, AES.MODE_CFB, iv)
# Decrypt and decompress
plaintext = zlib.decompress(cipher.decrypt(inf.read()))
# Write pyc header
# The header below is for Python 3.8
outf.write(b'\x55\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0')
# Write decrypted data
outf.write(plaintext)
inf.close()
outf.close()
# Delete .pyc.encrypted file
p.unlink()
# For pyinstaller >=4.0
import glob
import zlib
import tinyaes
from pathlib import Path
CRYPT_BLOCK_SIZE = 16
# key obtained from pyimod00_crypto_key
key = bytes('MySup3rS3cr3tK3y', 'utf-8')
for p in Path("PYZ-00.pyz_extracted").glob("**/*.pyc.encrypted"):
inf = open(p, 'rb') # encrypted file input
outf = open(p.with_name(p.stem), 'wb') # output file
# Initialization vector
iv = inf.read(CRYPT_BLOCK_SIZE)
cipher = tinyaes.AES(key, iv)
# Decrypt and decompress
plaintext = zlib.decompress(cipher.CTR_xcrypt_buffer(inf.read()))
# Write pyc header
# The header below is for Python 3.8
outf.write(b'\x55\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0')
# Write decrypted data
outf.write(plaintext)
inf.close()
outf.close()
# Delete .pyc.encrypted file
p.unlink()
You can try decompiling the pyimod02_archive.pyc file. If there are references to tinyaes in the decompiled code its using pyinstaller >= 4.0. Else it's pyinstaller < 4.0 and there will be references to pycrypto instead.
You can also check the AES encryption modes to be more sure. Pyinstaller < 4.0 uses AES in CFB mode, there should be the word "CFB" somewhere in the decompiled code. Pyinstaller >= 4.0 uses AES in CTR mode and you should be able to find the word "CTR" in that case.
Summarizing,
Pyinstaller < 4.0 => PyCrypto and CFB
Pyinstaller >= 4.0 => tinyaes and CTR