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:
- Check environment variable
PYARMOR_LICENSE
, if set, use this filename - Check
sys.PYARMOR_LICENSE
, if set use this filename - If it’s not set, search
license.lic
in the current path - For non super mode, search
license.lic
in the path of runtime packagepytransform
- 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:
First create one bootstrap package
pytransform_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
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/
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:
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())
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 run
dist/foo.exe
, it should report license error.
Finally runlicenses to generate new license for the obfuscatedscripts, and copy new
license.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 module
pytransform
tohiddenimports - Add extra path
DISTPATH/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-k
and--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:
Generatefoo.spec by PyUpdater
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
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
Obfuscate the script with
--no-cross-protection
and--restrict0
, forexample:pyarmorobfuscate--no-cross-protection--restrict0foo.py
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.py
as 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:
- Quit directly if “errors” is set to keyword “exit”
- Show same message for any license error
- 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.