3.3. Building Environments

Command pyarmor runs in build machine to generate obfuscated scripts and all the other required files.

Here list everything related to pyarmor.

Above all it only runs in the supported platforms by supported Python versions.

Command line options, configuration options, plugins, hooks and a few environment variables control how to generate obfuscated scripts and runtime files.

All the command line options and environment variables are described in Man Page

3.3.1. Supported Python versions

Table-1. Supported Python Versions
Python Version 2.7 3.0~3.4 3.5~3.6 3.7~3.10 3.11 3.12+ Remark
pyarmor 8 RFT Mode No No No Y Y N/y [1]
pyarmor 8 BCC Mode No No No Y Y N/y  
pyarmor 8 others No No No Y Y N/y  
pyarmor-7 Y Y Y Y No No  

3.3.2. Supported platforms

Table-2. Supported Platforms
OS Windows Apple Linux
Arch x86/x86_64 x86_64 arm64 x86/x86_64 aarch64 armv7 armv6
Themida Protection Y No No No No No No
pyarmor 8 RFT Mode Y Y Y Y Y Y No
pyarmor 8 BCC Mode Y Y Y Y Y N/y No
pyarmor 8 others Y Y Y Y Y Y No
pyarmor-7 [2] Y Y Y Y Y Y Y

notes

[1]N/y means not yet now, but will be supported in future.
[2]pyarmor-7 also supports more linux arches, refer to Pyarmor 7.x platforms.

Important

pyarmor-7 is bug fixed Pyarmor 7.x version, it’s same as Pyarmor 7.x, and only works with old license. Do not use it with new license, it may report HTTP 401 error.

3.3.3. Configuration options

There are 3 kinds of configuration files

  • global: an ini file ~/.pyarmor/config/global
  • local: an ini file ./.pyarmor/config
  • private: each module may has one ini file in Local Path. For example, ./.pyarmor/foo.rules is private configuration of module foo

Use command pyarmor cfg to change options in configuration files.

3.3.4. Plugins

New in version 8.2.

Plugin is a Python script used to do some post-build work when generating obfuscated scripts.

Plugin use cases:

  • Additional processing in the output path
  • Fix import statement in the obfuscated script for special cases
  • Add comment to outer key file
  • Rename binary extension pyarmor_runtime suffix to avoid name conflicts
  • In Darwin use install_name_tool to fix extension module pyarmor_runtime couldn’t be loaded if Python is not installed in the standard path
  • In Darwin codesign pyarmor runtime extensions

Plugin script must define attribute __all__ to export plugin name.

Plugin script could be any name.

Plugin script could define one or more plugin classes:

class PluginName
static post_build(ctx, inputs, outputs, pack=None)

This method is optional.

This method is called when all the obfuscated scripts and runtime files have been generated by pyarmor gen

Parameters:
  • ctx (Context) – building context
  • inputs (list) – all the input paths
  • outputs (list) – all the output paths
  • pack (str) – if not None, it’s an executable file specified by --pack
static post_key(ctx, keyfile, **keyinfo)

This method is optional.

This method is called when outer key has been generated by pyarmor gen key

Parameters:
  • ctx (Context) – building context
  • keyfile (str) – path of generated key file
  • keyinfo (dict) – runtime key information

The possible items in the keyinfo:

Key expired:expired epoch or None
Key devices:a list for binding device hardware information or None
Key data:binding data (bytes) or None
Key period:period in seconds or None
static post_runtime(ctx, source, dest, platform)

This method is optional.

This method is called when the runtime extension module pyarmor_runtime.so in the runtime package has been generated by pyarmor gen.

It may be called many times if many platforms are specified in the command line.

Parameters:
  • ctx (Context) – building context
  • source (str) – source path of pyarmor extension
  • dest (str) – output path of pyarmor extension
  • platform (str) – standard platform name

To make plugin script work, configure it with script name without extension .py by this way:

$ pyarmor cfg plugins + "script name"

Pyarmor search plugin script in these paths in turn:

Here it’s an example plugin script fooplugin.py

__all__ = ['EchoPlugin']

class EchoPlugin:

    @staticmethod
    def post_runtime(ctx, source, dest, platform):
        print('-------- test fooplugin ----------')
        print('ctx is', ctx)
        print('source is', source)
        print('dest is', dest)
        print('platform is', platform)

Store it to local path .pyarmor/fooplugin.py, and enable it:

$ pyarmor cfg plugins + "fooplugin"

Check it, this plugin information should be printed in the console:

$ pyarmor gen foo.py

Disable this plugin:

$ pyarmor cfg plugins - "fooplugin"

3.3.5. Hooks

New in version 8.2.

Hook is a Python script which is embedded into the obfuscated script, and executed when the obfuscated script is running.

When obfuscating the scripts, Pyarmor searches path hooks in the local path and global path in turn. If there is any same name script exists, it’s called module hook script.

For example, .pyarmor/hooks/foo.py is hook script of foo.py, .pyarmor/hooks/joker.card.py is hook script of joker/card.py.

When generating obfuscate script by this command:

$ pyarmor gen foo.py

.pyarmor/hooks/foo.py will be inserted into the beginning of foo.py.

A hook script is a normal Python script, it could do everything Python could do. And it could use 2 special function __pyarmor__() and __assert_armored__() to do some interesting work.

Note that all the source lines in the hook script are inserted into module level of original script, be careful to avoid name conflicts.

3.4. Target Environments

Obfuscated scripts run in target device.

3.4.1. Supported Python versions and platforms

Supported platforms, arches and Python versions are same as Building Environments

3.4.2. Environment variables

A few environment variables are used by obfuscated scripts.

LANG

OS environment variable, used to select runtime error language.

PYARMOR_LANG

It’s used to set language runtime error language.

If it’s set, LANG is ignored.

PYARMOR_RKEY

Set search path for outer key

3.4.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.

3.4.4. Specialized builtin functions

New in version 8.2.

There are 2 specialized builtin functions, both of them could be used without import in the obfuscated scripts.

Generally they’re used with inline marker or in the hook scripts.

__pyarmor__(arg, kwarg, name, flag)
Parameters:
  • name (bytes) – must be b'hdinfo' or b'keyinfo'
  • flag (int) – must be 1

get hdinfo

When name is b'hdinfo', call it to get hardware information.

Parameters:
  • arg (int) – query which kind of device
  • kwarg (str) – None or device name
Returns:

arg == 0 return the serial number of first harddisk

Returns:

arg == 1 return mac address of first network card

Returns:

arg == 2 return ipv4 address of first network card

Returns:

arg == 3 return device name

Return type:

str

Raises:

RuntimeError – when something is wrong

For example,

__pyarmor__(0, None, b'hdinfo', 1)
__pyarmor__(1, None, b'hdinfo', 1)

In Linux, kwarg is used to get named network card or named hard disk. For example:

__pyarmor__(0, "/dev/vda2", b'hdinfo', 1)
__pyarmor__(1, "eth2", b'hdinfo', 1)

In Windows, kwarg is used to get all network cards and hard disks. For example:

__pyarmor__(0, "/0", b'hdinfo', 1)    # First disk
__pyarmor__(0, "/1", b'hdinfo', 1)    # Second disk

__pyarmor__(1, "*", b'hdinfo', 1)
__pyarmor__(1, "*", b'hdinfo', 1)

get keyinfo

When name is b'keyinfo', call it to query user data in the runtime key.

Parameters:
  • arg (int) – what information to get from runtime key
  • kwarg – always None
Returns:

arg == 0 return bind data, no bind data return empty bytes

Return type:

Bytes

Returns:

arg == 1 return expired epoch, -1 if there is no expired date

Return type:

Long

Returns:

None if something is wrong

For example:

print('bind data is', __pyarmor__(0, None, b'keyinfo', 1))
print('expired epoch is' __pyarmor__(1, None, b'keyinfo', 1))
__assert_armored__(arg)
Parameters:arg (object) – arg is a module or callable object
Returns:return arg if arg is obfuscated, otherwise, raise protection error.

For example

m = __import__('abc')
__assert_armored__(m)

def hello(msg):
    print(msg)

__assert_armored__(hello)
hello('abc')