Tuesday 14 February 2017

Extracting encrypted pyinstaller executables

UPDATE: For recent PyInstaller versions, the script below won't work. Please visit the pyinstxtractor wiki for more information.
 
It has been more than a quarter since the last post, and in the meantime, I was very busy and did not have the time to write a proper post. The good news is at the moment, I am comparatively free and can put in a quick post. 

As said earlier, PyInstaller provides an option to encrypt the embedded files within the executable. This feature can be used by supplying an argument --key=key-string while generating the executable. 

Detecting encrypted pyinstaller executables is simple. If  pyinstxtractor is used, it would indicate this as shown in Figure 1.

Trying to extract encrypted pyinstaller archive
Figure 1: Trying to extract encrypted pyinstaller archive


The other tell-tale sign is the presence of the file pyimod00_crypto_key in the extracted directory as shown in Figure 2.
The file pyimod00_crypto_key indicates usage of crypto
Figure 2: The file pyimod00_crypto_key indicates usage of crypto

If encryption is used, pyinstaller AES encrypts all the embedded files present within ZLibArchive i.e. the out00-PYZ.pyz file. When pyinstxtractor encounters an encrypted pyz archive, it would extract the contents as-is without decrypting the individual files as shown in Figure 3.

Contents of an an encrypted pyz archive
Figure 3: Contents of an encrypted pyz archive

To decrypt the files, you would need the key, and the key is present right within the file pyimod00_crypto_key. This is just a pyc file, and can be fed to a decompiler to retrieve the key. 

With the key in hand, it is a matter of another script to decrypt.

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
outf.write('\x03\xf3\x0d\x0a\0\0\0\0')

# Write decrypted data
outf.write(plaintext)

inf.close()
outf.close()
The above snippet can be used for decrypting the encrypted files. Afterward, you can run a decompiler to get back the source.

13 comments:

  1. Thank you very much! :)

    ReplyDelete
  2. What about when there is not a pymod00_crypto_key file at all?

    ReplyDelete
    Replies
    1. This probably means a modified version of pyinstaller has been used. Look, if the application runs without asking for a key it must have the key embedded within it somewhere. You would need to find where.

      Delete
  3. I have the key and the files. But then I am stuck. Am a windows user, but have python 3 installed. But above snip fails with me. I need the python code for a very small program I use, but the HDD drive with the source py files are dead.

    Can we contact eachother, or can you help me here?

    ReplyDelete
    Replies
    1. You should elaborate a bit about the exact error you are facing.
      For contact, you can use my email on the "About Me" page.

      Delete
    2. I would use your "About me", but it is empty. :-)

      I get stuck at your last picture. I dont know have to use that code. I have a folder with *.pyc.encryptet files I need decryptet.

      Delete
  4. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. Hi, you can ignore the manifest. Try decompiling the "xxx" file. If it works, you're good to go. If not, you may need to fix the file header. Check the "Reversing a PyInstaller based ransomware" post.

      Delete
  5. Hi i am facing some issue , using pyinstractor i could extract but when i try to get the py it throws error.




    magic_prepend.prepend(prepend_file)

    for source_path in source_files:
    TypeError: 'NoneType' object is not iterable

    ImportError: Ill-formed bytecode file error when decompiling .pyc file using uncompyle6

    how to get the correct magic number and the butecode.

    all the method fails , is there a way to extract the exe to py which is done in python37.

    ReplyDelete
    Replies
    1. AnonymousMay 26, 2020

      you can list all magics from xdis.magics.versions.

      Delete
  6. I don't know why but my script yeilds

    Traceback (most recent call last):
    File "decrypt.py", line 18, in
    plaintext = zlib.decompress(cipher.decrypt(inf.read()))
    zlib.error: Error -3 while decompressing data: incorrect header check

    ReplyDelete
    Replies
    1. PyInsstaller 4.0 changed the encryption algorithm. AFAIK, think this was specified in the PyInstxtractor wiki.

      Delete
  7. Traceback (most recent call last):
    File "j.py", line 15, in
    cipher = AES.new(key, AES.MODE_CFB, iv)
    File "C:\PYTHON387\lib\site-packages\Crypto\Cipher\AES.py", line 232, in new
    return _create_cipher(sys.modules[__name__], key, mode, *args, **kwargs)
    File "C:\PYTHON387\lib\site-packages\Crypto\Cipher\__init__.py", line 79, in _create_cipher
    return modes[mode](factory, **kwargs)
    File "C:\PYTHON387\lib\site-packages\Crypto\Cipher\_mode_cfb.py", line 270, in _create_cfb_cipher
    cipher_state = factory._create_base_cipher(kwargs)
    File "C:\PYTHON387\lib\site-packages\Crypto\Cipher\AES.py", line 103, in _create_base_cipher
    result = start_operation(c_uint8_ptr(key),
    File "C:\PYTHON387\lib\site-packages\Crypto\Util\_raw_api.py", line 138, in c_uint8_ptr
    raise TypeError("Object type %s cannot be passed to C code" % type(data))
    TypeError: Object type cannot be passed to C code


    getting this error

    ReplyDelete