Protecting Python Source Code

How do I protect Python code from being read by users?

Python, being a byte-code-compiled interpreted language, is very difficult to lock down. Even if you use a exe-packager like py2exe, the layout of the executable is well-known, and the Python byte-codes are well understood.

Usually in cases like this, you have to make a tradeoff. How important is it really to protect the code? Are there real secrets in there (such as a key for symmetric encryption of bank transfers), or are you just being paranoid? Choose the language that lets you develop the best product quickest, and be realistic about how valuable your novel ideas are.

If you decide you really need to enforce the license check securely, write it as a small C extension so that the license check code can be extra-hard (but not impossible!) to reverse engineer, and leave the bulk of your code in Python.

Hiding python source code to prevent people from seeing it.. The whole and proper way

So we will be using a few modules to -->

--> Convert our Python code to C source code and PYD files (PYD is the Python equivalent of DLL files)

--> Pack them into an exe


The modules we will be needing are -->

--> Cython

--> Pyinstaller


We will be using my project files (as an example), to demonstrate how to convert all the files to C source code and PYD files

The files in my project are -->

--> chat_screen.py

--> constants.py

--> main_app.py

--> rooms_list_screen.py



  1. We will make another folder, and name it Distributable executable files (You can name
    it whatever u want)

  2. We will be adding all the Python files in the folder but change the extension of
    all the files from py to pyx

  3. Then make another file, known as setup.py and add the following code to it (Note the extension of that file should be py and not pyx)


The setup.py file:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
from Cython.Build import cythonize

ext_modules = [
Extension("chat_screen", ["chat_screen.pyx"]),
Extension("constants", ["constants.pyx"]),
Extension("rooms_list_screen", ["rooms_list_screen.pyx"]),
Extension("main_app", ["main_app.pyx"])
]
setup(name='My Cython App',
cmdclass={'build_ext': build_ext},
ext_modules=cythonize(ext_modules),
compiler_directives={'language_level': 3},
zip_safe=False
)

So what we are doing here is, using the Extension class to make all the files, an extension of the app. So the format for the same would be Extension("The name by which u r importing the file (mostly the file name)", ["The full file name with its pyx extension"]

Then we are making a setup function and specifying the list of the extensions we made earlier in the ext_modules kwarg.. But, we have to wrap the list inside of the cythonize function to compile all the files into C source code and pyd files.




  1. Now, open a command prompt window in the same folder and run this command python setup.py build_ext --inplace and wait. This will output some C and PYD files.. These are your Python files, now compiled.

  2. Now, make another file named main.py (Note the extension of that file should be py and not pyx) and add this code to it __import__("main_app") (Replace the file name with your main file), and then run it simply. If your script works without any errors, that means you are ready to compile your app into an exe!!

  3. Now, install Pyinstaller with pip install pyinstaller and in the same folder, open a command prompt and run pyinstaller main.py and wait.

  4. You will see a spec file in the same directory and a build and dist folder. We can ignore both of those folders for now.

  5. Then, open the spec file and you will see something like this in it



block_cipher = None


a = Analysis(['main.py'],
pathex=['Omitted due to privacy concerns'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='main',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='main')

In the datas kwarg of the Analysis function, add the following code

[
("chat_screen.c", "."),
("chat_screen.cp39-win_amd64.pyd", "."),
("constants.c", "."),
("constants.cp39-win_amd64.pyd", "."),
("main_app.c", "."),
("main_app.cp39-win_amd64.pyd", "."),
("rooms_list_screen.c", "."),
("rooms_list_screen.cp39-win_amd64.pyd", ".")
]

So what we are doing here is, adding every C and pyd file that was generated in the spec file, to make sure they make into the compiled exe. The . represents that the files should be kept in the root of the exe directory.

Also, add the list of imports you are doing in your script in the hiddenimports kwarg of the Analysis function, which in my case is ['requests', 'kivy'].. So my spec file looks something like this

# -*- mode: python ; coding: utf-8 -*-
block_cipher = None


a = Analysis(['main.py'],
pathex=['Omitted due to privacy concerns'],
binaries=[],
datas=[
("chat_screen.c", "."),
("chat_screen.cp39-win_amd64.pyd", "."),
("constants.c", "."),
("constants.cp39-win_amd64.pyd", "."),
("main_app.c", "."),
("main_app.cp39-win_amd64.pyd", "."),
("rooms_list_screen.c", "."),
("rooms_list_screen.cp39-win_amd64.pyd", ".")
],
hiddenimports=['requests', 'kivy'],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='main',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='main')




  1. Now, in a command prompt window in the same folder, type in pyinstaller main.spec and press Enter.. Now your files are being converted to an exe!!! Just wait for some time now, and when the process is finished, just go to the following directory in the same folder dist\<name>\ and you will find all the files of your app here. If you want to launch the app, look for a main.exe file and run it.. There, your app is now ready!!!

If you want to make it into a single file, then instead of just doing pyinstaller main.spec, do pyinstaller main.spec --onefile and it would make a single executable file in the same directory!!!

Don't use UPX with Pyinstaller in this case, as it resulted in some random DLL errors in my case, which got fixed after I repacked without using UPX

How to protect Python source code?

I did it by creating .py library and simple .py program that uses that library. Then I compiled library to .pyc and distributed: program as .py source and library as compiled .pyc.

How to protect Python source code, while making the file available for running?

The criteria you've posted are inconsistent.
Python is an interpreted language. The entity running the language (i.e. Python interpreter) is reading your code and executing it, line by line. If you wrap it up to send to someone, their Python interpreter must have read permissions on the file, whether it's source code or "compiled" Python (which is easily decompiled into equivalent source code).

If we take a wider interpretation of "send to someone", there may be a business solution that serves your needs. You would provide your functionality, rather than the code: deploy it as a service from some available server: your own, or rented space. To do this, you instead provide an interface to your functionality.

If this fulfills your needs, you now have your next research topic.

Best way to protect source code of .exe program running on Python?

Since people in this thread did not provide any satisfiable answers, I will explain what I did to secure my program. As I mentioned previously, my goal is not to create an "uncrackable" program, just one that is secure enough to deter away amateurs.

I got help on one of the reverse-engineering forums, so props to those people!

Firstly, I used Nuitka to convert .py file into a C-based standalone executable. Then, I passed a resulting .exe file through VMProtect to obfuscate the binaries.

I've tested it on a few CS graduates, and they weren't able to crack or deobfuscate the program, so this is good enough for me.

P.S. Those who said that "it is impossible" or "your business model is wrong", please do not share your opinions unless you have a reverse-engineering experience, thank you :)

Are executables produced with Cython really free of the source code?

The code is found in the original pyx-file next to your exe. Delete/don't distribute this pyx-file with your exe.


When you look at the generated C-code, you will see why the error message is shown by your executable:

For a raised error, Cython will emit a code similar to the following:

__PYX_ERR(0, 11, __pyx_L3_error) 

where __PYX_ERR is a macro defined as:

#define __PYX_ERR(f_index, lineno, Ln_error) \
{ \
__pyx_filename = __pyx_f[f_index]; __pyx_lineno = lineno; __pyx_clineno = __LINE__; goto Ln_error; \
}

and the variable __pyx_f is defined as

static const char *__pyx_f[] = {
"test.pyx",
"stringsource",
};

Basically __pyx_f[0] tells where the original code could be found. Now, when an exception is raised, the (embedded) Python interpreter looks for your original pyx-file and finds the corresponding code (this can be looked up in __Pyx_AddTraceback which is called when an error is raised).

Once this pyx-file is not around, the original source code will no longer be known to the Python interpreter/anybody else. However, the error trace will still show the names of the functions and line-numbers but no longer any code snippets.

The resulting executable (or extension if one creates one) doesn't content any bytecode (as in pyc-files) and cannot be decompiled with tools like uncompyle: bytecode is produced when py-file is translated into Python-opcodes which are then evaluated in a huge loop in ceval.c. Yet for builtin/cython modules no bytecode is needed because the resulting code uses directly Python's C-API, cutting out the need to have/evaluate the opcodes - these modules skip interpretation, which a reason for them being faster. Thus no bytecode will be in the executable.

One important note though: One should check that the linker doesn't include debug information (and thus the C-code where the pyx-file content can be found as comments). MSVC with /Z7 options is such an example.


However, the resulting executable can be disassembled to assembler and then the generated C-code can be reverse engineered - so while cythonizing is Ok to make it hard to understand the code, it is not the right tool to conceal keys or security algorithms.



Related Topics



Leave a reply



Submit