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
Python Version |
2.7 |
3.0~3.6 |
3.7~3.10 |
3.11 |
3.12 |
3.13+ |
Remark |
---|---|---|---|---|---|---|---|
pyarmor 8 RFT Mode |
No |
No |
Y |
Y |
Y |
N/y |
|
pyarmor 8 BCC Mode |
No |
No |
Y |
Y |
Y |
N/y |
|
pyarmor 8 others |
No |
No |
Y |
Y |
Y |
N/y |
|
pyarmor-7 |
Y |
Y |
Y |
No |
No |
No |
3.3.2. Supported platforms
OS |
Windows |
Apple 2 |
Linux 3 |
||||
---|---|---|---|---|---|---|---|
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 4 |
Y |
Y |
Y |
Y |
Y |
Y |
Y |
OS |
FreeBSD |
Alpine Linux |
Android |
||||
---|---|---|---|---|---|---|---|
Arch |
x86_64 |
x86_64 |
aarch64 |
x86/x86_64 |
aarch64 |
armv7 |
armv6 |
pyarmor 8 RFT Mode |
Y |
Y |
Y |
Y |
Y |
Y |
No |
pyarmor 8 BCC Mode |
Y |
Y |
Y |
Y |
Y |
Y |
No |
pyarmor 8 others |
Y |
Y |
Y |
Y |
Y |
Y |
No |
pyarmor-7 |
Y |
Y |
Y |
Y |
Y |
Y |
Y |
OS |
Linux |
Linux, Alpine Linux |
---|---|---|
Arch |
loongarch64 |
ppc64le, mips32el/64el, riscv64 7 |
pyarmor 8 RFT Mode |
Y |
Y |
pyarmor 8 BCC Mode |
N |
N |
JIT Feature |
N |
Y |
pyarmor 8 others |
Y |
Y |
notes
- 1
N/y
means not yet now, but will be supported in future.- 2
Apple Silcon only supports Python 3.9+
- 3
This Linux is built with glibc
- 4
pyarmor-7 also supports more linux arches, refer to Pyarmor 7.x platforms.
- 5
These platforms are introduced in Pyarmor 8.3
- 6
The prebuilt extensions for these arches are published in the package pyarmor.cli.core.linux and pyarmor.cli.core.alpine respectively since Pyarmor 8.5.9 (not tested)
- 7
For riscv64, only support Python 3.10+
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 modulefoo
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 conflictsIn Darwin use install_name_tool to fix extension module
pyarmor_runtime
couldn’t be loaded if Python is not installed in the standard pathIn 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_script(ctx, res, source)
This method is optional.
This method is called after each script has been obfuscated
Generally it’s used to patch the obfuscated source, and return patched source
- Parameters
ctx (Context) – building context
res (FileResource) – instance of pyarmor.cli.resource.FileResource
source (str) – the source of obfuscated script
- 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:
Current path
local path, generally
./.pyarmor/
global path, generally
~/.pyarmor/
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.
See also
3.3.5.1. Special hook script
New in version 8.3.
If want to do something before obfuscated scripts are executed, it need use a special hook script .pyarmor/hooks/pyarmor_runtime.py
, it will be called when initializing Python extension pyarmor_runtime.
First create script .pyarmor/hooks/pyarmor_runtime.py
and define all in the hook function bootstrap()
, only this function will be called.
- bootstrap(user_data)
- Parameters
user_data (bytes) – user data in runtime key
- Returns
False, quit and raise protection exception Any others, continue to execute the obfuscated scripts.
- Raises
SystemExit – quit without traceback
ohter Exception – quit with traceback
An example script:
def bootstrap(user_data):
# Import everything in the function, not in the module level
import sys
import time
from struct import calcsize
print('user data is', user_data)
# Check platform
if sys.platform == 'win32' and calcsize('P'.encode()) * 8 == 32:
raise SystemExit('no support for 32-bit windows')
# Check debugger in Windows
if sys.platform == 'win32':
from ctypes import windll
if windll.kernel32.IsDebuggerPresent():
print('found debugger')
return False
# In this example, user_data is timestamp
if time.time() > int(user_data.decode()):
return False
Check it, first copy this script to .pyarmor/hooks/pyarmor_runtime.py
:
$ pyarmor gen --bind-data 12345 foo.py
$ python dist/foo.py
user data is b'12345'
Traceback (most recent call last):
File "dist/foo.py", line 2, in <module>
...
RuntimeError: unauthorized use of script (1:10325)
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.
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'
orb'keyinfo'
flag (int) – must be
1
get hdinfo
When
name
isb'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 unused
- Returns
arg == 4 return domain 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
isb'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, function or method object
- Returns
return
arg
ifarg
is obfuscated, otherwise, raise protection error.
For example
m = __import__('abc') __assert_armored__(m) def hello(msg): print(msg) __assert_armored__(hello) hello('abc')