4.2. Understanding Obfuscated Script

Remain as standard .py files

The obfuscated scripts are normal Python scripts, it’s clear by checking the content of dist/foo.py:

1from pyarmor_runtime_000000 import __pyarmor__
2__pyarmor__(__name__, __file__, b'\xa...')

It’s a simple script, first imports function __pyarmor__ from package pyarmor_runtime_000000, then call this function.

Runtime package

This package pyarmor_runtime_000000 is generated by Pyarmor, it’s also a normal Python package, here it’s package content:

$ ls dist/pyarmor_runtime_000000
...    __init__.py
...    pyarmor_runtime.so

There is binary extension module pyarmor_runtime, this is a big difference from plain Python script. Generally using binary extensions means the obfuscated scripts

  • may not be compatible with different builds of CPython interpreter.

  • often will not work correctly with alternative interpreters such as PyPy, IronPython or Jython

For example, when obfuscating scripts by Python 3.8, they can be run by any Python 3.8.x, but can’t be run by Python 3.7, 3.9 etc.

For example, packaging pure .py script is easy, but packaging binary extension need more work.

For example, in Android pure .py script can be run in any location, but binary extensions must be in special system paths.

The runtime package pyarmor_runtime_000000 could be in any path, it can be taken as a third-party package, save it in any location, and import it following Python import system.

pyarmor provides several options -i, --prefix to help generating right code to import it. Also refer to option --use-runtime and command pyarmor gen runtime for using shared runtime package.

See also

Changing runtime package name in the chapter Customization and Extension

Using shared runtime package in the chapter Advanced Tutorial

Runtime key

The runtime key generally is embedded into extension module pyarmor_runtime, it also could be an outer file. It stores expire date, bind devices, and user private data etc.

Extension module pyarmor_runtime will not load the obfuscated script unless the runtime key exists and is valid.

User also could store any private data in the runtime key, then use hook script to check private data in the obfuscated scripts.

If runtime key is stored in an outer file, any readable text in the header will be ignored. User can add comment at the header of runtime key file, the rest part are bytes data, only in the obfuscated scripts they could be read.

See also

command pyarmor gen key

4.2.1. Restrict modes

By default the obfuscated scripts can’t be changed.

After using --private, the attributes of the obfuscated scripts could not be seen by plain script or Python interpreter.

After using --restrict, the attributes of the obfuscated scripts are protected as --private, and the obfuscated scripts could not be imported by plain script or Python interpreter,

Disable all the restrictions by this command:

$ pyarmor cfg restrict_module 0

Generally only disable all the restrictions for specified module. For example, only no restrictions for module NAME:

$ pyarmor cfg -p NAME restrict_module 0

4.2.2. The differences of obfuscated scripts

Although use obfuscated scripts as they’re normal Python scripts, but the obfuscated scripts are still different from pure Python scripts, they changes a few Python features and results in some third party packages could not work.

Here are major changed features:

  • The obfuscated scripts are bind to Python major/minor version. For example, if it’s obfuscated by Python 3.6, it must run by Python 3.6. It doesn’t work for Python 3.5

  • The obfuscated scripts are platform-dependent, supported platforms and Python versions refer to Building Environments

  • If Python interpreter is compiled with Py_TRACE_REFS or Py_DEBUG, it will crash to run obfuscated scripts.

  • Any module may not work if it try to visit the byte code, or some attributes of code objects in the obfuscated scripts. For example most of inspect function are broken.

  • Pass the obfuscated code object by cPickle or any third serialize tool may not work.

  • sys._getframe([n]) may get the different frame. Note that many third packages uses this feature to get local variable and broken. For example, pandas, cherrypy.

  • The code object attribute __file__ is <frozen name> other than real filename.

    Note that module attribute __file__ is still filename. For example, obfuscate the script foo.py and run it:

    def hello(msg):
    # The output will be 'foo.py'
    # The output will be '<frozen foo>'

A few options may also change something:

  • pyarmor cfg mix_argname=1 hides annotations.

  • Importing obfuscated module by importlib.util.spec_from_file_location need extra handle, refer to issue 846

See also

Work with Third-Party Libraries

Generating cross platform scripts and Obfuscating scripts for multiple Python versions in the chapter Advanced Tutorial

4.2.3. Supported Third-Party Interpreter

About third-party interpreter, for example Jython, and any embedded Python C/C++ code, only they could work with CPython extension module, they could work with Pyarmor. Check third-party interpreter documentation to make sure this.

A few known issues

  • On Linux, RTLD_GLOBAL must be set as loading libpythonXY.so by dlopen, otherwise obfuscated scripts couldn’t work.

  • Boost::python does not load libpythonXY.so with RTLD_GLOBAL by default, so it will raise error “No PyCode_Type found” as running obfuscated scripts. To solve this problem, try to call the method sys.setdlopenflags(os.RTLD_GLOBAL) as initializing.

  • PyPy could not work with pyarmor, it’s total different from CPython

  • WASM is not supported.