On the compilation and decompilation of Python

Keywords: Python pip Windows xml

On the compilation and decompilation of pyyinstaller

It is very convenient to write scripts in Python, but it needs a specific Python environment to run. Therefore, if you want to run it on other computers, there will be many problems. Even if Python has been installed, the version may vary greatly, and the related dependency libraries are not installed, so it can not run normally. Is there a tool that can package our code with dependency libraries and compiler environments? The answer is yes, pyininstaller is a good tool. You can package your code into exe file with one click. Let's talk about how to use pyinstaller.

1, Package python code with pyyinstaller

1. Install pyyinstaller

The installation process is very simple, running on the command line:

pip install pyinstaller

Installation is complete.

2. Packing code

I wrote a simple code as an example. To demonstrate the packaging process more clearly, I wrote the main() function in a separate file and introduced mylib.py as a library.

#   mylib.py
#
import time


def myfunc():
    now = time.time()
    time_str = time.strftime("%Y-%m-%d %H:%M", time.localtime(now))
    print('Now is' + time_str)
    print("Have a nice day!")

#   main.py
#
import mylib
import os

if __name__ == "__main__":
    mylib.myfunc()
    os.system('pause')

You only need to run it on the command line:

 pyinstaller.exe -F yourcode.py

Yes. You will see the following output:

PS D:\File\tmp\test> pyinstaller.exe -F main.py
580 INFO: PyInstaller: 3.6
582 INFO: Python: 3.7.3
585 INFO: Platform: Windows-10-10.0.18362-SP0
592 INFO: wrote D:\File\tmp\test\main.spec
596 INFO: UPX is not available.
611 INFO: Extending PYTHONPATH with paths
['D:\\File\\tmp\\test', 'D:\\File\\tmp\\test']
612 INFO: checking Analysis
614 INFO: Building Analysis because Analysis-00.toc is non existent
614 INFO: Initializing module dependency graph...
620 INFO: Caching module graph hooks...
657 INFO: Analyzing base_library.zip ...
13893 INFO: Caching module dependency graph...
14161 INFO: running Analysis Analysis-00.toc
14233 INFO: Adding Microsoft.Windows.Common-Controls to dependent assemblies of final executable
  required by d:\programfiles\python\python.exe
15748 INFO: Analyzing D:\File\tmp\test\main.py
15751 INFO: Processing module hooks...
15752 INFO: Loading module hook "hook-encodings.py"...
16003 INFO: Loading module hook "hook-pydoc.py"...
16011 INFO: Loading module hook "hook-xml.py"...
16916 INFO: Looking for ctypes DLLs
16917 INFO: Analyzing run-time hooks ...
16925 INFO: Looking for dynamic libraries
17373 INFO: Looking for eggs
17374 INFO: Using Python library d:\programfiles\python\python37.dll
17374 INFO: Found binding redirects:
[]
17377 INFO: Warnings written to D:\File\tmp\test\build\main\warn-main.txt
17447 INFO: Graph cross-reference written to D:\File\tmp\test\build\main\xref-main.html
17506 INFO: checking PYZ
17507 INFO: Building PYZ because PYZ-00.toc is non existent
17508 INFO: Building PYZ (ZlibArchive) D:\File\tmp\test\build\main\PYZ-00.pyz
18600 INFO: Building PYZ (ZlibArchive) D:\File\tmp\test\build\main\PYZ-00.pyz completed successfully.
18637 INFO: checking PKG
18639 INFO: Building PKG because PKG-00.toc is non existent
18640 INFO: Building PKG (CArchive) PKG-00.pkg
22329 INFO: Building PKG (CArchive) PKG-00.pkg completed successfully.
22332 INFO: Bootloader d:\programfiles\python\lib\site-packages\PyInstaller\bootloader\Windows-64bit\run.exe
22334 INFO: checking EXE
22335 INFO: Building EXE because EXE-00.toc is non existent
22336 INFO: Building EXE from EXE-00.toc
22416 INFO: Appending archive to EXE D:\File\tmp\test\dist\main.exe
22641 INFO: Building EXE from EXE-00.toc completed successfully.

You will see the following files in the current folder:

D:.
│  main.py
│  main.spec
│  mylib.py
├─build
│  └─main
│          Analysis-00.toc
│          base_library.zip
│          EXE-00.toc
│          main.exe.manifest
│          PKG-00.pkg
│          PKG-00.toc
│          PYZ-00.pyz
│          PYZ-00.toc
│          warn-main.txt
│          xref-main.html
└─dist
       main.exe

The dist folder is the generated exe file.

Double click the exe file to run normally.

But the focus of this article is not on how to use pyinstaller. At this time, I was thinking, is such a simple packaging process safe? Will the packaged exe file be easily decompiled?

After consulting relevant materials, it is found that it is indeed possible.

2, Decompilation process of pyinstaller

1. Download and use the pyinstxtrator to unpack

The tool we used in the first step is pyinstxtrator.py, which can unpack the exe file generated by pyinstaller into pyc file.

Project address:

https://sourceforge.net/projects/pyinstallerextractor/

Or click here link A kind of download

Then copy this file to the same level directory of the exe to be unpacked, and run the following command:

python pyinstxtractor.py xx.exe

After running, generate xx.exe_extracted folder, which contains a pile of DLL, PYD and other files. We need to note that there is a xxx.exe.manifest file in it. XXX may be different from your EXE file name, but this is its real name. Then find a file named XXX without a suffix. It is actually the pyc file corresponding to the. py file that you packed before.

We also notice that there is a pyz-00.pyz'extracted folder in this directory, which is full of imported dependency libraries. Of course, our own mylib.py is also in it, which is also the object we decompiled.

2. Decompile pyc file

Find the pyc file, and then decrypt it. pyc is actually a cache file generated during the execution of python program. We will also see it when running python code directly. Decompilation in this format is relatively simple. There are many tools on the Internet, even many online tools. For convenience, I used an online tool here. Enclosed link A kind of

But if we upload the pyc file we found directly, we will find that it cannot be decompiled. Why? We use the hex editor (you can search online, I use wxMEdit here) to open this file, and compare it with the pyc file generated by running the py file directly before.

Let me take a look at the difference between main.pyc. On the left is our unpacking, and on the right is the running generation.

The only difference is that the first 16 bytes (called magic number for python version and compile time) are missing. Can we add it and parse it normally? It's true, but what if there's no original pyc file? Let's look in the xx.exe extracted folder again. We will find a file called struct. Let's add suffix. pyc decompilation to it. It is found that the following content has been decompiled successfully:

This shows that its magic number is correct, so we just need to copy its first 16 bytes, right? Let's try again. It's done! The contents of main.py have been decompiled successfully.

In the same way, the following can also decompile the contents of mylib.py and other dependent libraries, but it is worth noting that many online tutorials do not mention the difference between the number of bytes missing from the pyc file of the dependent library and the main program!!!

: |: |:
Left: in struct file: unpacked mylib.pyc right: correct PyC file

We found that it is not missing 16 bytes, but 4 bytes in the middle!!!

Then, we only need to overwrite the first 12 bytes of mylib.pyc with the 16 bytes of struct header.

After that, decompile.

**Decompiled successfully! **However, Chinese characters are parsed into Unicode code and can be converted using the corresponding tools.

You can see that the exe packaged by pyinstaller can be decompiled easily. Is there a way to encrypt and package? In fact, pyinstaller itself supports encryption. Let's talk about how to package encryption. (this is also a sinkhole...)

3, Using pyinstaller to encrypt package exe

In fact, as long as you add a key parameter to the package, you can encrypt it,

pyinstaller.exe -F --key 123456 xxx.py

However, you need to rely on the pycrypto package, which is usually not included in python. So we need to install it manually.

1. Install pycrypto package

The original installation process should be simple, and it can be installed through pip.

pip install pycrypto

However, the installation process seems to call the VS compiler to compile, which causes inexplicable problems. If you do not report errors during the installation process, Congratulations, you can skip this part.

I found many solutions on the Internet that didn't work. Finally, I found an answer on StackOverflow, which solved the problem perfectly. Original answer address: https://stackoverflow.com/a/46921479/12954728

The solution is as follows, provided that Visual studio is installed on your computer

Take my vs2015 as an example

  1. Find the VS folder in the start menu and run this "compatibility tool command prompt as Administrator“

  2. Find the file stdint.h in your VS installation directory. You'd better search everything

  3. Enter set CL=-FI "your path \ stdint.h" to set the environment variable

  4. Then run pip install pycrypto to install successfully

2. Use pyinstaller to encrypt and package

Now you can encrypt the package by executing the following command. After the key is the key, you can enter it at will.

pyinstaller.exe -F --key 123456 xxx.py

3. Decompile test

Then let's test whether the encrypted package exe can be decompiled.

Execute pyinstxtrator.py again

PS > python pyinstxtractor.py .\main-encrypt.exe
  import imp
[*] Processing .\main-encrypt.exe
[*] Pyinstaller version: 2.1+
[*] Python version: 37
[*] Length of package: 5787283 bytes
[*] Found 63 files in CArchive
[*] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap
[+] Possible entry point: main
[*] Found 136 files in PYZ archive
[!] Error: Failed to decompress Crypto, probably encrypted. Extracting as is.
[!] Error: Failed to decompress Crypto.Cipher, probably encrypted. Extracting as is.
[!] Error: Failed to decompress __future__, probably encrypted. Extracting as is.
[!] Error: Failed to decompress _compat_pickle, probably encrypted. Extracting as is.
[!] Error: Failed to decompress argparse, probably encrypted. Extracting as is.
[!] Error: Failed to decompress ast, probably encrypted. Extracting as is.
[!] Error: Failed to decompress base64, probably encrypted. Extracting as is.
[!] Error: Failed to decompress bdb, probably encrypted. Extracting as is.
[!] Error: Failed to decompress bisect, probably encrypted. Extracting as is.
[!] Error: Failed to decompress bz2, probably encrypted. Extracting as is.
[!] Error: Failed to decompress calendar, probably encrypted. Extracting as is.
[!] Error: Failed to decompress cmd, probably encrypted. Extracting as is.
[!] Error: Failed to decompress code, probably encrypted. Extracting as is.
[!] Error: Failed to decompress codeop, probably encrypted. Extracting as is.

This time, I output a long string of errors. It seems that they are indeed encrypted.

Let's look at the folder again.



There seems to be no change in the main-encrypt.exe'extracted folder, but the pyz-00.pyz'extracted folder is full of encrypted files, which should not be decompiled

However, the main file in the external folder can still be decompiled after the same operation.

It seems that this encryption is only for dependent libraries.

Four, summary

If you don't want others to get your source code, it is recommended to write your program's entry function in a separate file, and package exe with encryption. In this way, even if other people try to decompile, they can only get your entry function.

Original address: https://blog.luzy.tk/archives/155

Published 5 original articles, won praise 4, visited 259
Private letter follow

Posted by ak_mypayday on Wed, 26 Feb 2020 01:26:29 -0800