Advanced Topics

Using Super Mode

TheSuper Mode is introduced since v6.2.0, there is only one extensionmodule required to run the obfuscated scripts, and theBootstrap Codewhich may confused some users before is gone now, all the obfuscated scripts aresame. It improves the security remarkably, and makes the usage simple. The onlyproblem is that only the latest Python versions 2.7, 3.7, 3.8 and 3.9 are supported.

Enable super mode by option--advanced2, for example:

pyarmorobfuscate--advanced2foo.py

When distributing the obfuscated scripts to any other machine, so long asextension modulepytransform in any Python path, the obfuscated scripscould work well.

In order to restirct the obfuscated scripts, generate alicense.lic inadvanced. For example:

pyarmorlicenses--bind-macxx:xx:xx:xxregcode-01

Then specify this license with option--with-license, for example:

pyarmorobfuscate--with-licenselicenses/regcode-01/license.lic \--advanced2foo.py

By this way the specified license file will be embedded into the extensionmodulepytransform. If you prefer to use outerlicense.lic, so it canbe replaced with the others easily, just set option--with-license tospecial valueouter, for example:

pyarmorobfuscate--with-licenseouter--advanced2foo.py

More information, refer to next section.

How to use outer license file

Since v6.3.0, the runtime filelicense.lic has been embeded to dynamiclibrary. If you prefer to use outerlicense.lic, so it can be replaced withthe others easily, just set option--with-license to special valueouter, for example:

pyarmorobfuscate--with-licenseouterfoo.py

When the obfuscated scripts start, it will searchlicense.lic in order:

  1. Check environment variablePYARMOR_LICENSE, if set, use this filename
  2. Checksys.PYARMOR_LICENSE, if set use this filename
  3. If it’s not set, searchlicense.lic in the current path
  4. For non super mode, searchlicense.lic in the path of runtime packagepytransform
  5. Raise exception if there is still not found

Here it’s the basic usage ofsys.PYARMOR_LICENSE

For non super mode, edit the functionpyarmor_runtime in theruntime filedist/pytransform/__init__.py, add one line:

sys.PYARMOR_LICENSE='/path/to/license.lic'

For super mode, convert python extensionpytransform.so to samename packagepytransform. For example:

cddistmkdirpytransformmvpytransform.sopytransform/

Then createdist/pytransform/__init__.py

importsyssys.PYARMOR_LICENSE='/path/to/license.lic'name='pytransform'm=__import__(name,globals(),locals(),['*'])sys.modules[__name__].__dict__.update(m.__dict__)

Obfuscating Many Packages

There are 3 packages:pkg1,pkg2,pkg2. All of them will beobfuscated, and use shared runtime files.

First change to work path, create 3 projects:

mkdirbuildcdbuildpyarmorinit--src/path/to/pkg1--entry__init__.pypkg1pyarmorinit--src/path/to/pkg2--entry__init__.pypkg2pyarmorinit--src/path/to/pkg3--entry__init__.pypkg3

Then make theRuntime Package, save it in the pathdist:

pyarmorbuild--outputdist--only-runtimepkg1

Or run commandruntime to generateRuntime Package directly:

pyarmorruntime--outputdist

Next obfuscate 3 packages, save them in thedist:

pyarmorbuild--outputdist--no-runtimepkg1pyarmorbuild--outputdist--no-runtimepkg2pyarmorbuild--outputdist--no-runtimepkg3

Check all the output and test these obfuscated packages:

lsdist/cddistpython-c'import pkg1importpkg2importpkg3'

Note

The runtime packagepytransform in the output pathdist also couldbe move to any other Python path, only if it could be imported.

From v5.7.2, theRuntime Package also could be generate by commandruntime separately:

pyarmorruntime

Solve Conflicts With Other Obfuscated Libraries

Note

New in v5.8.7

Suppose there are 2 packages obfuscated by different developers, could they beimported in the same Python interpreter?

If both of them are obfuscated by trial version of pyarmor, no problem, theanswer is yes. But if anyone is obfuscated by registerred version, the answer isno.

Since v5.8.7, the scripts could be obfuscated with option--enable-suffix togenerate theRuntime Package with an unique suffix, other than fixed namepytransform. For example:

pyarmorobfuscate--enable-suffixfoo.py

The output would be like this:

dist/foo.pypytransform_vax_000001/__init__.py...

The suffix_vax_000001 is based on the registration code of PyArmor.

For project, setenable-suffix by commandconfig:

pyarmorconfig--enable-suffix1pyarmorbuild-B

Or disable it by this way:

pyarmorconfig--enable-suffix0pyarmorbuild-B

Distributing Obfuscated Packages

If there are many packages to distribute, it’s recommend to generate aRuntime Package with enable suffix separately and share it for all ofthese packages.

For example, first generateRuntime Package by commandruntime:

pyarmorruntime--enable-suffix-Odist/shared

The output package may looks likedist/shared/pytransform_vax_000001

For each package, obfuscated it with this shared pytransform:

pyarmorobfuscate--enable-suffix--recursive--bootstrap2 \-Odist/pkg1--runtime@dist/sharedsrc/pkg1/__init__.py

If option--runtime is not available, it’s new in v6.3.7, replace it with--no-runtime:

pyarmorobfuscate--enable-suffix--recursive--bootstrap2 \-Odist/pkg1--no-runtimesrc/pkg1/__init__.py

Then distribute packagepytransform_vax_000001 as a normal package.

Finally, distribute obfuscated packagedist/pkg1, add a dependency in setupscript. For example:

install_requires=['pytransform_vax_000001']

Do the same thing aspkg1 for other packagespkg2,pkg3 etc.

Distributing Obfuscated Scripts To Other Platform

First list all the avaliable platform names by commanddownload:

pyarmordownloadpyarmordownload--help-platform

Display the detials with option--list:

pyarmordownload--listpyarmordownload--listwindowspyarmordownload--listwindows.x86_64

Then specify platform name when obfuscating the scripts:

pyarmorobfuscate--platformlinux.armv7foo.py# For projectpyarmorbuild--platformlinux.armv7

Obfuscating scripts with different features

There may be many available dynamic libraries for one same platform. Each onehas different features. For example, both ofwindows.x86_64.0 andwindows.x86_64.7 work in the platformwindwos.x86_64. The last numberstands for the features:

  • 0: No anti-debug, JIT, advanced mode features, high speed
  • 7: Include anti-debug, JIT, advanced mode features, high security

It’s possible to obfuscate the scripts with special feature. For example:

pyarmorobfuscate--platformlinux.x86_64.7foo.py

Note that the dynamic library with different features aren’t compatible. Forexample, try to obfuscate the scripts with--platformlinux.arm.0 inWindows:

pyarmorobfuscate--platformlinux.arm.0foo.py

Because the default platform is full featureswindows.x86_64.7 in Windows,so PyArmor have to reboot with platformwindows.x86_64.0, then obfuscate thescript for this low feature platformlinux.arm.0.

It also could be set the enviornment variablePYARMOR_PLATFORM to samefeature platform as target machine. For example:

PYARMOR_PLATFORM=windows.x86_64.0pyarmorobfuscate--platformlinux.arm.0foo.py# In WindowssetPYARMOR_PLATFORM=windows.x86_64.0pyarmorobfuscate--platformlinux.arm.0foo.pysetPYARMOR_PLATFORM=

Running Obfuscated Scripts In Multiple Platforms

From v5.7.5, the platform names are standardized, all the available platformnames list hereStandard Platform Names. And the obfuscated scripts couldbe run in multiple platforms.

In order to support multiple platforms, all the dynamic libraries for theseplatforms need to be copied toRuntime Package. For example, obfuscatinga script could run in Windows/Linux/MacOS:

pyarmorobfuscate--platformwindows.x86_64 \--platformlinux.x86_64 \--platformdarwin.x86_64 \foo.py

TheRuntime Package also could be generated by commandruntimeonce, then obfuscate the scripts without runtime files. For examples:

pyarmorruntime--platformwindows.x86_64,linux.x86_64,darwin.x86_64pyarmorobfuscate--no-runtime--recursive \--platformwindows.x86_64,linux.x86_64,darwin.x86_64 \foo.py

Because the obfuscated scripts will check the dynamic library, the platformsmust be specified even if there is option--no-runtime. But if the option--no-cross-protection is specified, the obfuscated scripts will not checkthe dynamic library, so no platform is required. For example:

pyarmorobfuscate--no-runtime--recursive--no-cross-protectionfoo.py

Note

If the feature number is specified in one of platform, for example, one iswindows.x86_64.0, then all the other platforms must be same feature.

Note

If the obfuscated scripts don’t work in other platforms, try to update allthe downloaded files:

pyarmordownload--update

If it still doesn’t work, try to remove the cahced platform files in the path$HOME/.pyarmor

Obfuscating Scripts By Other Python Version

If there are multiple Python versions installed in the machine, thecommandpyarmor uses default Python. In case the scripts need to beobfuscated by other Python, runpyarmor by this Python explicitly.

For example, first findpyarmor.py:

find/usr/local/lib-namepyarmor.py

Generally it should be in the/usr/local/lib/python2.7/dist-packages/pyarmor in most of linux.

Then run pyarmor as the following way:

/usr/bin/python3.6/usr/local/lib/python2.7/dist-packages/pyarmor/pyarmor.py

It’s convenient to create a shell script/usr/local/bin/pyarmor3, the content is:

/usr/bin/python3.6/usr/local/lib/python2.7/dist-packages/pyarmor/pyarmor.py"$@"

And

chmod+x/usr/local/bin/pyarmor3

then usepyarmor3 as before.

In the Windows, create a bat filepyarmor3.bat, the content would be like this:

C:\Python36\pythonC:\Python27\Lib\site-packages\pyarmor\pyarmor.py%*

Run bootstrap code in plain scripts

Before v5.7.0 theBootstrap Code could be inserted into plain scriptsdirectly, but now, for the sake of security, theBootstrap Code must bein the obfuscated scripts. It need another way to run theBootstrap Codein plain scripts.

First create one bootstrap packagepytransform_bootstrap by commandruntime:

pyarmorruntime-i

Next move bootstrap package to the path of plain script:

mvdist/pytransform_bootstrap/path/to/script

It also could be copied to python system library, for examples:

mvdist/pytransform_bootstrap/usr/lib/python3.5/(ForLinux)mvdist/pytransform_bootstrapC:/Python35/Lib/(ForWindows)

Then edit the plain script, insert one line:

importpytransform_bootstrap

Now any other obfuscated modules could be imported after this line.

Note

Before v5.8.1, create this bootstrap package by this way:

echo"">__init__.pypyarmorobfuscate-Odist/pytransform_bootstrap--exact__init__.py

Run unittest of obfuscated scripts

In most of obfuscated scripts there are noBootstrap Code. So theunittest scripts may not work with the obfuscated scripts.

Suppose the test script is/path/to/tests/test_foo.py, first patch thistest script, refer torun bootstrap code in plain scripts

After that it works with the obfuscated modules:

cd/path/to/testspythontest_foo.py

The other way is patch system packageunittest directly. Make sure thebootstrap packagepytransform_bootstrap is copied in the Python systemlibrary, refer torun bootstrap code in plain scripts

Then edit/path/to/unittest/__init__.py, insert one line:

importpytransform_bootstrap

Now all the unittest scripts could work with the obfuscated scripts. It’s usefulif there are many unittest scripts.

Let Python Interpreter Recognize Obfuscated Scripts Automatically

In a few cases, if Python Interpreter could recognize obfuscatedscripts automatically, it will make everything simple:

  • Almost all the obfuscated scripts will be run as main script
  • In the obfuscated scripts callmultiprocessing to create new process
  • Or callPopen,os.exec etc. to run any other obfuscated scripts

Here are the base steps:

  1. First create one bootstrap packagepytransform_bootstrap:

    pyarmorruntime-i

    Before v5.8.1, it need be created by obfuscating an empty package:

    echo"">__init__.pypyarmorobfuscate-Odist/pytransform_bootstrap--exact__init__.py
  2. Then create virtual python environment to run the obfuscated scripts, movethe bootstrap package to virtual python library. For example:

    # For windowsmvdist/pytransform_bootstrapvenv/Lib/# For linuxmvdist/pytransform_bootstrapvenv/lib/python3.5/
  1. Editvenv/lib/site.py orvenv/lib/pythonX.Y/site.py, importpytransform_bootstrap before the main line:

    importpytransform_bootstrapif__name__=='__main__':...

It also could be inserted into the end of functionmain, or anywhere theycould be executed as modulesite is imported.

After that in the virtual environmentpython could run the obfuscatedscripts directly, because the modulesite is automatically importedduring Python initialization.

Refer tohttps://docs.python.org/3/library/site.html

Note

The commandpyarmor doesn’t work in this virtual environment, it’s onlyused to run the obfuscated scripts.

Note

Before v5.7.0, you need create the bootstrap package by theRuntime Files manually.

Obfuscating Python Scripts In Different Modes

Advanced Mode is introduced from PyArmor 5.5.0, it’s disabled bydefault. Specify option--advanced to enable it:

pyarmorobfuscate--advanced1foo.py# For projectcd/path/to/projectpyarmorconfig--advanced1pyarmorbuild-B

From PyArmor 5.2, the defaultRestrict Mode is 1. It could be changed bythe option--restrict:

pyarmorobfuscate--restrict=2foo.pypyarmorobfuscate--restrict=3foo.py# For projectcd/path/to/projectpyarmorconfig--restrict4pyarmorbuild-B

All the restricts could be disabled by this way if required:

pyarmorobfuscate--restrict=0foo.py# For projectpyarmorconfig--restrict=0pyarmorbuild-B

If the obfuscates scripts uses the license generated bylicenses, inorder to disable all the restricts, pass option--disable-restrict-mode tocommandlicenses. For example:

pyarmorlicenses--disable-restrict-moder001pyarmorobfuscate--with-license=licenses/r001/license.licfoo.py# For projectpyarmorconfig--with-license=licenses/r001/license.licpyarmorbuild-B

The modes ofObfuscating Code Mode,Wrap Mode,Obfuscating module Mode could not be changed in commandobfuscate. They only could bechanged by commandconfig whenUsing Project. For example:

pyarmorinit--src=src--entry=main.py.pyarmorconfig--obf-mod=1--obf-code=1--wrap-mode=0pyarmorbuild-B

Using Plugin to Extend License Type

PyArmor could extend license type for obfuscated scripts by plugin. For example,check internet time other than local time.

First create plugin scriptcheck_ntp_time.py. Thekey function in this script ischeck_ntp_time, the other important function is_get_license_data which used to get extra data from thelicense.lic ofobfuscated scripts.

Then insert 2 comments in the entry scriptfoo.py:

# {PyArmor Plugins}# PyArmor Plugin: check_ntp_time()

Now obfuscate entry script:

pyarmorobfuscate--plugincheck_ntp_timefoo.py

If the plugin file isn’t in the current path, use absolute path instead:

pyarmorobfuscate--plugin/usr/share/pyarmor/check_ntp_timefoo.py

Finally generate one license file for this obfuscated script, pass extra licensedata by option-x, this data could be got by function_get_license_data inthe plugin script:

pyarmorlicenses-x20190501rcode-001pyarmorobfuscate--with-licenselicenses/rcode-001/license.lic \--plugincheck_ntp_timefoo.py

For commandpack:

pyarmorlicenses-x20190501rcode-001pyarmorpack--with-licenselicenses/rcode-001/license.lic \-x" --plugin check_ntp_time"foo.py

More examples, refer tohttps://github.com/dashingsoft/pyarmor/tree/master/plugins

About how plugins work, refer toHow to Deal With Plugins

Important

The output function name in the plugin must be same as plugin name, otherwisethe plugin will not take effects.

Bundle Obfuscated Scripts To One Executable File

Run the following command to pack the scriptfoo.py to oneexecutable filedist/foo.exe. Herefoo.py isn’t obfuscated, itwill be obfuscated before packing:

pyarmorpack-e" --onefile"foo.pydist/foo.exe

If you don’t want to bundle thelicense.lic of the obfuscatedscripts into the executable file, but put it outside of the executablefile. For example:

dist/foo.exelicense.lic

So that we could generate different licenses for different userslater easily. Here are basic steps:

  1. First create runtime-hook scriptcopy_license.py:

    importsysfromos.pathimportjoin,dirnamewithopen(join(dirname(sys.executable),'license.lic'),'rb')asfs:withopen(join(sys._MEIPASS,'license.lic'),'wb')asfd:fd.write(fs.read())
  2. Then pack the scirpt with extra options:

    pyarmorpack--clean--without-license-x" --exclude copy_license.py" \-e" --onefile --icon logo.ico --runtime-hook copy_license.py"foo.py

Option--without-license tellspack not to bundle thelicense.licof obfuscated scripts to the final executable file. By option--runtime-hook of PyInstaller, the specified scriptcopy_license.py will be executed before any obfuscated scripts areimported. It will copy outerlicense.lic to right path.

Try to rundist/foo.exe, it should report license error.

  1. Finally runlicenses to generate new license for the obfuscatedscripts, and copy newlicense.lic anddist/foo.exe to endusers:

    pyarmorlicenses-e2020-01-01code-001cplicense/code-001/license.licdist/dist/foo.exe

Bundle obfuscated scripts with customized spec file

If there is a customized .spec file works, for example:

pyinstallermyscript.spec

Refer torepack pyinstaller bundle with obfuscated scripts

Or obfuacate and pack scripts with option-s directly:

pyarmorpack-smyscript.specmyscript.py

If it raises this error:

Unsupport.specfile,noXXXfound

Check .spec file, make sure there are 2 lines in top level (no identation):

a=Analysis(...pyz=PYZ(...

And there are 3 key parameters when creating anAnalysis object, for example:

a=Analysis(...pathex=...,hiddenimports=...,hookspath=...,...)

PyArmor will append required options to these lines automatically. But beforev5.9.6, it need to be patched by manual:

  • Add modulepytransform tohiddenimports
  • Add extra pathDISTPATH/obf/temp topathex andhookspath

After changed, it may be like this:

a=Analysis(['myscript.py'],pathex=[os.path.join(DISTPATH,'obf','temp'),...],binaries=[],datas=[],hiddenimports=['pytransform',...],hookspath=[os.path.join(DISTPATH,'obf','temp'),...],...

Note

This featuer is introduced since v5.8.0

Before v5.8.2, the extra path isDISTPATH/obf, notDISTPATH/obf/temp

Improving The Security By Restrict Mode

By default the scripts are obfuscated by restrict mode 1, that is, theobfuscated scripts can’t be changed. In order to improve the security,obfuscating the scripts by restrict mode 2 so that the obfuscated scripts can’tbe imported out of the obfuscated scripts. For example:

pyarmorobfuscate--restrict2foo.py

Or obfuscating the scripts by restrict mode 3 for more security. It will evencheck each function call to be sure all the functions are called in theobfuscated scripts. For example:

pyarmorobfuscate--restrict3foo.py

However restrict mode 2 and 3 aren’t applied to Python package. There is anothersolution for Python package to improve the security:

  • The.py files which are used by outer scripts are obfuscated by restrice mode 1
  • All the other.py files which are used only in the package are obfuscated by restrict mode 4

For example,mypkg includes 2 files:

  • __init__.py
  • foo.py

Here it’s the content ofmypkg/__init__.py

from.fooimporthellodefopen_hello(msg):print('This is public hello:%s'%msg)defproxy_hello(msg):print('This is proxy hello from foo:%s'%msg)hello(msg)

Now obfuscate this package by this way:

cd/path/to/mypkgpyarmorobfuscate-Oobf/mypkg--exact__init__.pypyarmorobfuscate-Oobf/mypkg--restrict4--recursive--exclude__init__.py.

So it’s OK to importmypkg and call any function in the__init__.py:

cd/path/to/mypkg/obfpython>>>importmypkg>>>mypkg.open_hello("it should work")>>>mypkg.proxy_hello("also OK")

But it doesn’t work to call any function in themypkg.foo. For example:

cd/path/to/mypkg/obfpython>>>importmypkg>>>mypkg.foo.hello("it should not work")

More information about restrict mode, refer toRestrict Mode

Using Plugin To Improve Security

By plugin any private checkpoint could be injected into the obfuscated scripts,and it doesn’t impact the original scripts. Most of them must be run in theobfuscated scripts, if they’re not commented as plugin, it will break the plainscripts.

No one knows your check logic, and you can change it in anytime. So it’s moresecurity. For example, check there is debugger process, check the sum of bytecode of caller, which could be got bysys._getframe etc.

Using Inline Plugin To Check Dynamic Library

AlthouthPyArmor provides cross protection, it also could check the dynamiclibrary in the startup to make sure it’s not changed by others. This exampleuses inline plugin to check the modified time protecting the dynamic library byinserting the following comment tomain.py

# PyArmor Plugin: import os# PyArmor Plugin: libname = os.path.join( os.path.dirname( __file__ ), '_pytransform.so' )# PyArmor Plugin: if not os.stat( libname ).st_mtime_ns == 102839284238:# PyArmor Plugin:     raise RuntimeError('Invalid Library')

Then obfuscate the script and enable inline plugin by this way:

pyarmorobfuscate--pluginonmain.py

Once the obfuscated script starts, the following plugin code will be run atfirst

importoslibname=os.path.join(os.path.dirname(__file__),'_pytransform.so')ifnotos.stat(libname).st_mtime_ns==102839284238:raiseRuntimeError('Invalid Library')

Checking Imported Function Is Obfuscated

In thepytransform there is one decoratorassert_armored() andone functioncheck_armored() used to make sure the imported functions fromother module are obfuscated.

For example, there are 2 scriptsmain.py andfoo.py

## This is main.py#importfoodefstart_server():foo.connect('root','root password')foo.connect2('user','user password')## This is foo.py#defconnect(username,password):mysql.dbconnect(username,password)defconnect2(username,password):db2.dbconnect(username,password)

In themain.py, it need to be surefoo.connect is obfuscated. Otherwise theend users may replace the obfuscatedfoo.py with this plain script, and runthe obfuscatedmain.py

defconnect(username,password):print('password is%s',password)

The password is stolen, in order to avoid this, use decorator functionto make sure the functionconnect is obfuscated by plugin.

Now let’s editmain.py, insert inline plugin code

importfoo# PyArmor Plugin: from pytransform import assert_armored# PyArmor Plugin: @assert_armored(foo.connect, foo.connect2)defstart_server():foo.connect('root','root password')

Then obfuscate it with plugin on:

pyarmorobfuscate--pluginonmain.py

The obfuscated script would be like this

importfoofrompytransformimportassert_armored@assert_armored(foo.connect,foo.connect2)defstart_server():foo.connect('root','root password')

Before callstart_server, the decorator functionassert_armored willcheck bothconnect functions are pyarmored, otherwise it will raiseexception.

You can also check it bycheck_armored()

importfoofrompytransformimportcheck_armoreddefstart_server():ifnotcheck_armored(foo.connect,foo.connect2):print('Found hacker')returnfoo.connect('root','root password')

Callpyarmor From Python Script

It’s also possible to call PyArmor methods inside Python script not byos.execorsubprocess.Popen etc. For example

frompyarmor.pyarmorimportmainascall_pyarmorcall_pyarmor(['obfuscate','--recursive','--output','dist','foo.py'])

In order to suppress all normal output of pyarmor, call it with--silent

frompyarmor.pyarmorimportmainascall_pyarmorcall_pyarmor(['--silent','obfuscate','--recursive','--output','dist','foo.py'])

From v5.7.3, whenpyarmor called by this way and something is wrong, it willraise exception other than callsys.exit.

Generating license key by web api

It’s also possible to generate license key as string other than writing to afile inside Python script. It may be useful in case the new license need to begenerated by web api.

frompyarmor.pyarmorimportlicensesasgenerate_license_keylickey=generate_license_key(name='reg-001',expired='2020-05-30',bind_disk='013040BP2N80S13FJNT5',bind_mac='70:f1:a1:23:f0:94',bind_ipv4='192.168.121.110',bind_data='any string')print('Generate key:%s'%lickey)

If there are more than one product need generate licenses from one Web API, setkeywordhome to each registerred product. For example

frompyarmor.pyarmorimportlicensesasgenerate_license_keylickey=generate_license_key(name='product-001',expired='2020-06-15',home='~/.pyarmor-1')print('Generate key for product 1:%s'%lickey)lickey=generate_license_key(name='product-002',expired='2020-05-30',home='~/.pyarmor-2')print('Generate key for product 2:%s'%lickey)

Check license periodly when the obfuscated script is running

Generally only at the startup of the obfuscated scripts the license ischecked. Since v5.9.3, it also could check the license per hour. Just generate anew license with--enable-period-mode and overwrite the default one. Forexample:

pyarmorobfuscatefoo.pypyarmorlicenses--enable-period-modecode-001cplicenses/code-001/license.lic./dist

Work with Nuitka

Because the obfuscated scripts could be taken as normal scripts with an extraruntime packagepytransform, they also could be translated to C program byNuitka. When obfuscating the scripts, the option--restrict0 and--no-cross-protection should be set, otherwise the final C program could notwork. For example, first obfustate the scripts:

pyarmorobfuscate--restrict0--no-cross-protection--package-runtime0foo.py

Then translate the obfuscated one as normal python scripts by Nuitka:

cd./distpython-mnuitka--include-package=pytransformfoo.py./foo.bin

There is one problem is that the imported modules (packages) in the obfuscatedscripts could not be seen by Nuitka. To fix this problem, first generate thecorresponding.pyi with original script, then copy it within the obfuscatedone. For example:

# Generating "mymodule.pyi"python-mnuitka--modulemymodule.pypyarmorobfuscate--restrict0--no-bootstrap--package-runtime0mymodule.pycpmymodule.pyidist/cddist/python-mnuitka--modulemymodule.py

But it may not take advantage of Nuitka features by this way, because most ofbyte codes aren’t translated to c code indeed.

Note

So long as the C program generated by Nuitka is linked against libpython toexecute, pyarmor could work with Nuitka. But in the future, just as said inthe Nuitka official website:

Itwilldothis-wherepossible-withoutaccessinglibpythonbutinCwithitsnativedatatypes.

In this case, pyarmor maybe not work with Nuitka.

Work with Cython

Here it’s an example show how tocythonize a python scriptfoo.py obfuscatedby pyarmor with Python37:

print('Hello Cython')

First obfuscate it with some extra options:

pyarmorobfuscate--package-runtime0--no-cross-protection--restrict0foo.py

The obfuscated script and runtime files will be saved in the pathdist, aboutthe meaning of each options, refer to commandobfuscate.

Nextcythonize bothfoo.py andpytransform.py with extra options-kand--lenient to generatefoo.c andpytransform.c:

cddistcythonize-3-k--lenientfoo.pypytransform.py

Without options-k and--lenient, it will raise exception:

undeclarednamenotbuiltin:__pyarmor__

Then compilefoo.c andpytransform.c to the extension modules. In MacOS,just run the following commands, but in Linux, with extra cflag-fPIC:

gcc -shared $(python-config --cflags) $(python-config --ldflags) \     -o foo$(python-config --extension-suffix) foo.cgcc -shared $(python-config --cflags) $(python-config --ldflags) \    -o pytransform$(python-config --extension-suffix) pytransform.c

Finally test it, remove all the.py files and import the extension modules:

mvfoo.pypytransform.py/tmppython-c'import foo'

It will printHello Cython as expected.

Work with PyUpdater

PyArmor should work withPyUpdater by this way, for example, there is ascriptfoo.py:

  1. Generatefoo.spec by PyUpdater

  2. Generatefoo-patched.spec by pyarmor with option--debug:

    pyarmorpack--debug-sfoo.specfoo.py# If the final executable raises protection error, try to disable restirct mode# by the following extra optionspyarmorpack--debug-sfoo.spec-x" --restrict 0 --no-cross-protection"foo.py

This patchedfoo-patched.spec could be used by PyUpdater in build command

If your Python scripts are modified, just obfuscate them again, all the optionsfor commandobfuscate could be got from the output of commandpack

If anybody is having issues with the above. Just normally compiling it inPyArmor then zipping and putting it into “/pyu-data/new” works. From there onyou can just normally sign, process and upload your update.

More information refer to the description of commandpack and advancedusageBundle obfuscated scripts with customized spec file

Binding obfuscated scripts to Python interpreter

In order to improve the security of obfuscated scripts, it also could bind theobfuscated scripts to one fixed Python interperter, the obfuscated scripts willnot work if the Python dynamic library are changed.

If you use commandobfuscate, after the scripts are obfuscated, just generatea newlicense.lic which is bind to the current Python and overwrite thedefault license. For example:

pyarmorlicenses--fixed1-Odist/license.lic

When start the obfuscated scripts in target machine, it will check the Pythondynamic library, it may be pythonXY.dll, libpythonXY.so or libpythonXY.dylib indifferent platforms. If this library is different from the python dynamiclibrary in build machine, the obfuscated script will quit.

If you use project to obfuscate scripts, first generate a fixed license:

cd/path/to/projectpyarmorlicenses--fixed1

By default it will be saved tolicenses/pyarmor/license.lic, then configurethe project with this license:

pyarmorconfig--license=licenses/pyarmor/license.lic

If obfuscate the scripts for different platform, first get the bind key intarget platform. Create a script then run it with Python interpreter which wouldbe bind to:

fromctypesimportCFUNCTYPE,cdll,pythonapi,string_at,c_void_p,c_char_pfromsysimportplatformdefget_bind_key():ifplatform.startswith('win'):fromctypesimportwindlldlsym=windll.kernel32.GetProcAddressAelse:prototype=CFUNCTYPE(c_void_p,c_void_p,c_char_p)dlsym=prototype(('dlsym',cdll.LoadLibrary(None)))refunc1=dlsym(pythonapi._handle,b'PyEval_EvalCode')refunc2=dlsym(pythonapi._handle,b'PyEval_GetFrame')size=refunc2-refunc1code=string_at(refunc1,size)print('Get bind key:%s'%sum(bytearray(code)))if__name__=='__main__':get_bind_key()

It will print the bind keyxxxxxx, then generate one fixed license with thisbind key:

pyarmorlicenses--fixedxxxxxx-Odist/license.lic

It also could bind the license to many Python interpreters by passing multiplekeys separated by,:

pyarmorlicenses--fixed1,key2,key3-Odist/license.licpyarmorlicenses--fixedkey1,key2,key3-Odist/license.lic

The special key1 means current Python interpreter.

Note

Do not use this feature in 32-bit Windows, because the bind key isdifferent in different machine, it may be changed even if python isrestarted in the same machine.

Customizing cross protection code

In order to protect core dynamic library of PyArmor, the default protection codewill be injected into the entry scripts, refer toSpecial Handling of Entry Script. However this public protection code may be bypassed deliberately,the better way is to write your private protection code, it could improve thesecurity largely.

Since v6.2.0, commandruntime could generate the default protection code,it could be as template to write your own protection code. Of course, you maywrite it by yourself. Only if it could make sure the runtime files aren’tchanged by someone else as running the obfuscated scripts.

First generate protection scriptbuild/pytransform_protection.py:

pyarmorruntime--advanced2--outputbuild

Then edit it with your private code, after that, obfuscate the scripts and setoption--cross-protection to this customized script, for example:

pyarmorobfuscate--cross-protectionbuild/pytransform_protection.py \--advanced2foo.py

Note

The option--advanced in commandobfuscate must be same as incommandruntime, because the runtime files may be different totaly.

Storing runtime file license.lic to any location

By creating a symbol link in the runtime package, it’s easy to store runtimefilelicense.lic to any location when running the obfuscated scripts.

In linux, for example, store license file in/opt/my_app:

ln-s/opt/my_app/license.lic/path/to/obfuscated/pytransform/license.lic

In windows, store license file inC:/Users/Jondy/my_app:

mklink \path\to\obfuscated\pytransform\license.licC:\Users\Jondy\my_app\license.lic

When distributing the obfuscated package, just run this function on post-install:

importosdefmake_link_to_license_file(package_path,target_license="/opt/mypkg/license.lic"):license_file=os.path.join(package_path,'pytransform','license.lic')ifos.path.exists(license_file):os.rename(license_file,target_license)os.symlink(target_license,license_file)

Register multiple pyarmor in same machine

From v5.9.0, pyarmor reads license and capsule data from environment variablePYARMOR_HOME, the default value is~/.pyarmor. So it’s easy to registermultiple pyarmor in one machine by setting environment variablePYARMOR_HOMEto another path before run pyarmor.

It also could create a new commandpyarmor2 for the second project by thefollowing way.

In Linux, create a shell scriptpyarmor2

export PYARMOR_HOME=$HOME/.pyarmor_2pyarmor "$@"

Save it to/usr/local/pyarmor2, and change its mode:

chmod+x/usr/local/pyarmor2

In Windows, create a bat scriptpyarmor2.bat

SETPYARMOR_HOME=%HOME%\another_pyarmorpyarmor%*

After that, runpyarmor2 for the second project:

pyarmor2registerpyarmor-regkey-2.zippyarmor2obfuscatefoo2.py

How to get license information of one obfuscated package

How to get the license information of one obfuscated package? Since v6.2.5, justrun this script in the path of runtime packagepytransform

frompytransformimportpyarmor_init,get_license_infopyarmor_init(is_runtime=1)licinfo=get_license_info()print('This obfuscated package is issued by%s'%licinfo['ISSUER'])print('License information:')print(licinfo)

For the scripts obfuscated by super mode, there is no packagepytransform, butan extensionpytransform. It’s simiar and more simple

frompytransformimportget_license_infolicinfo=get_license_info()print('This obfuscated package is issued by%s'%licinfo['ISSUER'])print('License information:')print(licinfo)

Since v6.2.7, it also could call the helper script by this way:

cd/path/to/obfuscated_packagepython-mpyarmor.helper.get_license_info

How to protect data files

This is still an experiment feature.

PyArmor does not touch data files, but it could wrap data file to python module,and then obfuscate this data module by restrict mode 4, so that it only could beimported from the obfuscated scripts. By this way, the data file could beprotected by PyArmor.

Since v6.2.7, there is a helper script which could create a python module fromdata file, for example:

python-mpyarmor.helper.build_data_moduledata.txt>data.py

Next obfuscate this data module with restrict mode 4:

pyarmorobfuscate--exact--restrict4--no-runtimedata.py

After that, use the data file in other obfuscated scripts. For example:

importdata# Here load the content of data file to memory variable "text"# And clear it from memory as exiting the contextwithdata.Safestr()astext:...

Before v6.2.7, download this helper scriptbuild_data_module.py and run it directly:

pythonbuild_data_module.pydata.txt>data.py

How to remove docstrings

By setting PYTHONOPTIMIZE=2 in the command line the docstrings could be removedfrom the obfuscated scripts. For examples:

# In linuxPYTHONOPTIMIZE=2pyarmorobfuscatefoo.py# In WindowssetPYTHONOPTIMIZE=2pyarmorobfuscatefoo.py

Using restrict mode with threading and multiprocessing

It may complain of protection exception if usingmultiprocessing orthreading with restrict mode 3 and 4 directly. Because both of thesesystem modules aren’t obfuscated, but they try to call the function in therestrict modules.

One solution is to extend systemThread to overwrite its methodrun withlambda function. For example,

fromthreadingimportThreadclassPrivateThread(Thread):deflambda_run(self):try:ifself._target:self._target(*self._args,**self._kwargs)finally:delself._target,self._args,self._kwargsrun=lambdaself:self.lambda_run()deffoo():print('Hello')t=PrivateThread(target=foo)t.start()

If you have extended systemThread and defined methodrun by yourself, justrenamerun tolambda_run, and add lambda methodrun. For example

fromthreadingimportThreadclassMyThread(Thread):# def run(self):deflambda_run(self):...# Define a lambda method `run`run=lambdaself:self.lambda_run()

Another solution is to define a public module with restrict mode 1, let plainscripts call functions in this public module.

For example, here is a scriptfoo.py using public modulepub_foo.py

importmultiprocessingasmpimportpub_foodefhello(q):print('module name:%s'%__name__)q.put('hello')if__name__=='__main__':ctx=mp.get_context('spawn')q=ctx.Queue()# call "proxy_hello" instead private "hello"p=ctx.Process(target=pub_foo.proxy_hello,args=(q,))p.start()print(q.get())p.join()

The content of public modulepub_foo.py

importfoodefproxy_hello(q):returnfoo.hello(q)

Now obfuscatefoo.py with mode 3 andpub_foo.py with mode 1:

pyarmorobfuscate--restrict3foo.py# both of options --exact and --no-runtime are requiredpyarmorobfuscate--restrict1--exact--no-runtimepub_foo.py

The third solution is to obfuscate system modulethreading or somemodules in packagemultiprocessing with mode 1. Make sure the caller isobfuscated.

Repack PyInstaller bundle with obfuscated scripts

Since v6.5.5, PyArmor provides a helper scriptrepack.py which is used torepack PyInstaller bundle with obfuscated scripts.

First pack the script by PyInstaller, next obfuscate the scripts by PyArmor,finally run this script to repack the bundle with obfuscated scripts.

  • Pack the script with PyInstaller, make sure the final bundle works. For realscripts, other options may be required, please checkPyInstallerdocumentation. If the final bundlecould not work in this step, please report issues toPyInstaller issues:

    # One folder modepyinstallerfoo.py# Check it worksdist/foo/foo.exe# If prefer to one file mode, run this commandpyinstaller--onefilefoo.py# Check it worksdist/foo.exe
  • Obfuscate the scripts to “obfdist”, make sure the obfuscated scripts work. Forreal scripts, other options may be required, please checkobfuscate tofind more usages, and the scripts also could be obfuscated bybuild:

    # Option --package-runtime should be set to 0pyarmorobfuscate-Oobfdist--package-runtime0foo.py# If prefer to super mode, run this commandpyarmorobfuscate-Oobfdist--advanced2foo.py# Check it workspythondist/foo.py
  • Repack the final executable, use the same Python interpreter as PyInstallerusing:

    # If one folder modepythonrepack.py-pobfdistdist/foo/foo.exe# Overwrite the old onecpfoo-obf.exedist/foo/foo.exe# If one file modepythonrepack.py-pobfdistdist/foo.exe# Overwrite the old onecpfoo-obf.exedist/foo.exe

Herefoo-obf.exe is the patched bundle.

The obfuscated scripts in theobfdist must be in the same path as it in thePyInstaller bundle. The option-d is used to print the archive information,copy the obfuscated scripts to the right place according to the structure of thearchive. For example, this command could print the archive information:

pythonrepack.py-d-pobfdistdist/foo.exe

Note that if the structure of obfuscated scripts are changed, run the mainscript by Python directly, make sure it still works.

Note

Before v6.5.5, please downloadrepack.py from

https://github.com/dashingsoft/pyarmor/raw/master/src/helper/repack.py

Since v6.5.5, run it by this way:

python-mpyarmor.helper.repack-pobfdistdist/foo

Build obfuscated scripts to extensions

There is a helper scriptbuildext.py in the package of pyarmor used to buildobfuscated scripts to extensions

  1. Obfuscate the script with--no-cross-protection and--restrict0, forexample:

    pyarmorobfuscate--no-cross-protection--restrict0foo.py
  2. Build obfuscated script to extension, for example:

    pythonbuildext.pydist/foo.py

If option-i is specified, the obfuscated scripts will be deleted afterbuilding, so the output pathdist is clean. For example:

pythonbuildext.py-idist/

By default only the obfuscated scripts in thedist are handled, if there aresub-directories, list all of them like this:

pythonbuildext.pydist/dist/a/dist/b/

Or list all the scripts in the command line, for example:

# In Linixpython buildext.py $(find dist/ -name "*.py")# In WindowsFOR /R dist\ %I IN (*.py) DO python buildext.py %I

The extension will ignore the blockif__name__=="__main__", in order torun this block as main script, build it with option-e to generate anexecutable, for example:

pythonbuildext.py-edist/foo.pydist/foo.exe

This executable must be run in the current Python environment, it equals:

pythondist/foo.py

Show more usage and options by-h:

pythonbuildext.py-h

Note

Before v6.6.0, please downloadbuildext.py from

https://github.com/dashingsoft/pyarmor/raw/master/src/helper/buildext.py

Since v6.6.0, run it by this way:

python-mpyarmor.helper.buildext...

Note

For Windows, if something is wrong with building extension, just write asimplesetup.py to builddemo.c todemo.pyd:

fromdistutils.coreimportsetup,Extensionmodule1=Extension('demo',sources=['demo.c'])setup(name='pyarmor.helper.buildext',version='1.0',description='This is a helper package to build extension',ext_modules=[module1])Thenrunit::pythonsetup.pybuild_ext

Distributing Obfuscated Package With pip

Here it’s a simple package:

.└── mylib    ├── mylib    │   ├── __init__.py    │   └── main.py    └── setup.py

First generate uniqueRuntime Package with--enable-suffix1:

cdmylibpyarmorruntime-Odist/share--enable-suffix1

Then obfuscate the package with this runtime:

pyarmorobfuscate--with-runtime@dist/sharemylib/__init__.py

Next editsetup.py, add all the required runtime files as data files. Forexample, suppose the unique package name ispytransform_vax_xxxxxx

setup(name='mylib',...packages=['mylib'],package_dir={'mylib':'dist'},data_files=[('pytransform_vax_xxxxxx','dist/share/pytransform_vax_xxxxxx/*')]...)

Finally build the source package:

pythonsetup.pysdist

Note

Do not obfuscatesetup.py

For super mode, the runtime files are different, please modifysetup.pyas required.

Run Obfuscated Scripts By Different Python Versions

This feature is introduced in v6.8.0

Generally the obfuscated scripts can be run only by one Python version. In orderto run it by other Python version, one solution is, first obfuscate the scriptsby different Python version, then merge them to one script.

There is a helper scriptmerge.py in the package of pyarmor used to mergedifferent obfuscated scripts to one.

Here it’s the basic usage:

# First obfuscate the scripts by Python 2.7python2.7pyarmor.pyobfuscate-Opy27foo.py# Then obfuscate the scripts by Python 3.8python3.8pyarmor.pyobfuscate-Opy38foo.py# Finally run this script to merge all of thempythonmerge.pypy38/py27/# Look the resultslsmerged_dist/

It also works for super mode:

# First obfuscate the scripts by Python 2.7python2.7pyarmor.pyobfuscate--advanced2-Opy27foo.py# Then obfuscate the scripts by Python 3.8python3.8pyarmor.pyobfuscate--advanced2-Opy38foo.py# Finally run this script to merge all of thempythonmerge.pypy38/py27/# Look the resultslsmerged_dist/

Note

Try to use option--no-cross-protection to obfuscate the scripts if themerged scripts raise protection error.

Note

Before v6.8.0, please downloadmerge.py from

https://github.com/dashingsoft/pyarmor/raw/master/src/helper/merge.py

Since v6.8.0, run it by this way:

python-mpyarmor.helper.merge...

How to customize error message

I have started to play around with pyarmor. When using a license file thatexpires you get the message “License is expired”. Is there a way to change thismessage?

From pyarmor v7.8.0 (it’s not released now), there are 2 license error messagescould be customized by runtime configure file~/.pyarmor/runtime.cfg with jsonformat:

  • License is expired
  • License is not for this machine

In order to customize the error message, first create the file~/.pyarmor/runtime.cfg, edit it, then obfuscate the scripts.

There are 3 kind of ways to customize error handlers by editing the content ofthis file:

  1. Quit directly if “errors” is set to keyword “exit”
  1. Show same message for any license error
  1. Show customized message for different errors

For old version, you need patch the source scriptpytransform.py in the pyarmorpackage. There is a functionpyarmor_runtime

defpyarmor_runtime(path=None,suffix='',advanced=0):...try:pyarmor_init(path,is_runtime=1,suffix=suffix,advanced=advanced)init_runtime()exceptExceptionase:ifsys.flags.debugorhasattr(sys,'_catch_pyarmor'):raisesys.stderr.write("%s\n"%str(e))sys.exit(1)

Change the hanler of the exception as you desired.

If the scripts are obfuscated by super mode, this solution doesn’t work. You maycreate a script to catch exceptions raised by obfuscated scriptfoo.py. Forexample

try:importfooexceptExceptionase:print('something is wrong')

By this way not only the exceptions of pyarmor but also of normal scripts arecatched. In order to handle the exceptions of pyarmor only, first create runtimepackage byruntime, and obfuscate the scripts with it:

pyarmorruntime--advanced2-Odistpyarmorobfuscate--advanced2--runtime@distfoo.py

Then create a boot scriptdist/foo_boot.py like this

try:importpytransform_bootstrapexceptExceptionase:print('something is wrong')else:importfoo

The scriptdist/pytransform_bootstrap.py is created byruntime, it’sobfuscated from an empty script, so only pyarmor bootstrap exceptions are raisedby it.