Skip to content

Frequently Asked Questions

extremecoders-re edited this page Jul 4, 2023 · 27 revisions

How to extract digitally signed exe's?

Digitally signed exe's are now supported without requiring other tools.

Do I need to fix the header of the extracted .pyc's?

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.

What can go wrong if I use a different version of Python?

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.

How do I install multiple versions of Python safely?

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.

Are encrypted pyz archives supported?

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

Decrypting PyInstaller < 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

Decrypting PyInstaller >= 4.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()

How to decrypt all encrypted pyc files at once?

To decrypt all encrypted pyc files in the PYZ-00.pyz_extracted directory you can use one of the following script.

pyinstaller < 4.0

# 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()

pyinstaller >= 4.0

# 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()

How to determine whether the executable has been made pyinstaller < 4.0 or >= 4.0?

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