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
-
Find the VS folder in the start menu and run this "compatibility tool command prompt as Administrator“
-
Find the file stdint.h in your VS installation directory. You'd better search everything
-
Enter set CL=-FI "your path \ stdint.h" to set the environment variable
-
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