How To Pack Obfuscated Scripts

The obfuscated scripts generated by PyArmor can replace Python scripts seamlessly, but there is an issue when packing them into one bundle by PyInstaller, py2exe, py2app, cx_Freeze:

All the dependencies of obfuscated scripts CAN NOT be found at all

To solve this problem, the common solution is

  1. Find all the dependenices by original scripts.
  2. Add runtimes files required by obfuscated scripts to the bundle
  3. Replace original scipts with obfuscated in the bundle
  4. Replace entry scrirpt with obfuscated one

PyArmor provides command pack to achieve this. But in some cases maybe it doesn’t work. This document describes what the command pack does, and also could be as a guide to bundle the obfuscated scripts by yourself.

Depend on what tool used, there are different ways.

First obfuscate scripts to dist/obf:

pyarmor obfuscate --output dist/obf hello.py

Work with PyInstaller

Install pyinstaller:

pip install pyinstaller

Generate specfile, add the obfuscated entry script and data files required by obfuscated scripts:

pyinstaller --add-data dist/obf/license.lic
            --add-data dist/obf/pytransform.key
            --add-data dist/obf/_pytransform.*
            hello.py dist/obf/hello.py

Update specfile hello.spec, insert the following lines after the Analysis object. The purpose is to replace all the original scripts with obfuscated ones:

a.scripts[-1] = 'hello', r'dist/obf/hello.py', 'PYSOURCE'
for i in range(len(a.pure)):
    if a.pure[i][1].startswith(a.pathex[0]):
        x = a.pure[i][1].replace(a.pathex[0], os.path.normpath(os.path.abspath('dist/obf')))
        if os.path.exists(x):
            if hasattr(a.pure, '_code_cache'):
                with open(x) as f:
                    a.pure._code_cache[a.pure[i][0]] = compile(f.read(), a.pure[i][1], 'exec')
            a.pure[i] = a.pure[i][0], x, a.pure[i][2]

Run patched specfile to build final distribution:

pyinstaller --clean -y hello.spec

Note

Option –clean is required, otherwise the obfuscated scripts will not be replaced because the cached .pyz will be used.

Check obfuscated scripts work:

# It works
dist/hello/hello.exe

rm dist/hello/license.lic

# It should not work
dist/hello/hello.exe

Work with py2exe

For Python3.3 and later:

pip install py2exe

Build bundle executable to dist with separated library:

build_exe --library library.zip hello.py

Build bundle executable with the obfuscated entry to dist/obf/dist, all the other obfuscated scripts should be include by -i name or -p pkgname:

( cd dist/obf;
  build_exe --library library.zip -i queens hello.py )

Update dist/obf/library.zip, which only includes the obfuscated scripts, merge all the dependenices files from dist/library.zip into it.

Copy all the files to final output:

cp -a dist/obf/dist/* dist/

Copy runtime files required by obfuscated scripts to finial output:

( cd dist/obf;
  cp *.key *.lic _pytransform.dll ../dist/ )

Check obfuscated scripts work:

# It works
dist/hello.exe

rm dist/license.lic

# It should not work
dist/hello.exe

For Python2, write a setup.py and run py2exe as the following way:

python setup.py py2exe hello.py

Work with cx_Freeze 5

Install cx_Freeze:

pip install cx_Freeze

Build bundle executable to dist:

cxfreeze --target-dir=dist hello.py

Build bundle executable with the obfuscated entry to dist/obf/dist, all the other obfuscated scripts should be include by --include-modules NAMES:

cd dist/obf
cxfreeze --target-dir=dist --include-modules=queens hello.py

Update dist/obf/python34.zip, which only includes the obfuscated scripts, merge all the dependenices files from dist/python34.zip into it.

Copy all the files to final output:

cp -a dist/obf/dist/* dist/

Copy runtime files required by obfuscated scripts to finial output:

( cd dist/obf;
  cp *.key *.lic _pytransform.dll ../dist/ )

Check obfuscated scripts work:

# It works
dist/hello.exe

rm dist/license.lic

# It should not work
dist/hello.exe