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:

1
2
from pyarmor_runtime_000000 import __pyarmor__
__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 serveral options -i, --prefix to help generating right code to import it.

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 obfuscaed scripts they could be read.

4.2.1. 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 obfsucated 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):
        print(msg)
    
    # The output will be 'foo.py'
    print(__file__)
    
    # The output will be '<frozen foo>'
    print(hello.__file__)
    

A few options may also change something:

  • pyarmor cfg mix_argname=1 hides annotations.
  • --private, --restrict hide function names in trace back

4.2.2. Supported Third-Party Interpreter

About third-party interperter, for example Jython, and any embeded Python C/C++ code, only they could work with CPython extension module, they could work with Pyarmor. Check third-parth interperter 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