Pythonコードの難読化を行うためPyArmorを使用するにあたりもろもろの調査を行うため、内部用にドキュメント(PyArmor7.5)の翻訳をしました。
試用版を使って動かしてみる際は、インストールとアンインストール及びPyArmorの使用を参照すると良いかと思います。
PyArmorで難読化した際に問題が発生したら、まずドキュメントの When Things Go Wrong(うまくいかないとき)を参照すると解決に役に立ちます。
PyArmorの内部処理を知りたい場合は、PyArmorの仕組みを参照することをお勧めします。
目的に応じて各章を参照ください。
- まずはインストールして試してみたい
2.インストールとアンインストール 及び 3.PyArmorの使用 - ちょと高度なテクニックを知りたい
4.アドバンストピックス 及び 5.Pyarmored Wheelをビルドする - 簡単なサンプルを見てみたい
6.事例紹介 - プロジェクト化してコード管理をしっかりとしたい
7.Projectの活用 - コマンド及びコマンドのオプションを知りたい
8.Manページ - 難読化についてより詳しく知りたい
9.難読化されたスクリプトを理解する 及び 10.PyArmorの仕組み - 生成されるランタイムモジュールについて詳しく知りたい
11.ランタイムモジュールpytransform - サポートしているプラットフォームを知りたい
12.Support Platforms - Pyarmorの各難読化モードを詳しく知りたい
13.難読化したスクリプトのモード - Pyarmorの性能、セキュリティについて知りたい
14.難読化されたスクリプトのパフォーマンス 及び 15.PyArmorのセキュリティ - 難読化時のトラブルシューティングをしたい
16.うまくいかないとき - ラインセス及び商用版の購入方法を知りたい
17.ライセンス
[追伸]
翻訳に誤りがありましたら随時修正してきますのでご指摘いただけると幸いです。
©️2022 NPO法人AI開発推進協会
Pyarmor ドキュメント
バージョン: PyArmor 7.5
ホームページ: https://pyarmor.dashingsoft.com/
コンタクト: pyarmor@163.com
著者: Jondy Zhao
著作権: This document has been placed in the public domain.
1.はじめに
PyArmorは、Pythonスクリプトの難読化、難読化されたスクリプトの固定マシンへのバインド、難読化されたスクリプトの期限設定に使用するコマンドラインツールです。PyArmorは、以下の方法でPythonスクリプトを保護します。
- コードオブジェクトを難読化し、定数やリテラル文字列を保護します。
- 各関数(コードオブジェクト)のco_codeを実行時に難読化します。
- コードオブジェクトの実行が完了すると同時にフレームのf_localsをクリアします。
- 難読化されたスクリプトのライセンスファイルを確認しながら実行します。
PyArmorは、Python 2.6、2.7、およびPython3をサポートしています。
PyArmorはWindows、Mac OS X、およびLinuxに対してテストされています。
PyArmorはFreeBSDやRaspberry Pi, Banana Pi, Orange Pi, TS-4600 / TS-7600などの組み込みプラットフォームで正常に使用されていますが、完全なテストはされていません。
2.インストールとアンインストール
PyArmorは通常のPythonパッケージです。アーカイブはPyPiからダウンロードできますが、利用可能な場合は pip を使用してインストールする方が簡単です。たとえば、
pip install pyarmor
または新しいバージョンにアップグレードしてください。
pip install --upgrade pyarmor
pyarmor用のWebUIもあります。次のコマンドでインストールしてください。
pip install pyarmor-webui
インストールの確認
すべてのプラットフォームで、コマンドpyarmorが実行パスに存在するはずです。これを確認するには、次のコマンドを入力します。
pyarmor --version
この結果、PyArmor Version X.Y.ZまたはPyArmor Trial Version X.Y.Zが表示されるはずです。
コマンドが見つからない場合は、実行パスに適切なディレクトリが含まれていることを確認してください。
インストールされているコマンド
完全なインストールでは、次のコマンドが実行パスに配置されます。
-
pyarmor
はメインコマンドです。PyArmorの使用を参照してください。 -
pyarmor-webui
は、PyArmorのWeb UIを開くために使用します。
完全なインストール(pipによるインストール)を行わなかった場合、これらのコマンドはコマンドとしてインストールされません。しかし、配布フォルダにあるPythonスクリプトを実行することで、以下に説明するすべての関数を実行することができます。pyarmorコマンドに相当するのは、pyarmor-folder/pyarmor.py
です。
pyarmor-webuiは、pyarmor-folder/webui/server.pyです。
クリーンアンインストール
以下のファイルが、pyarmorがインストールされた後に作成されます。
~/.pyarmor/.pyarmor_capsule.zip (since v6.2.0)
~/.pyarmor/license.lic (since v5.8.0)
~/.pyarmor/platforms/
{pyarmor-folder}/license.lic (before v5.8.0)
~/.pyarmor_capsule.zip (before v6.2.0)
/path/to/project/.pyarmor_config (if using project)
次のコマンドを実行して、クリーンなアンインストールを行います。
pip uninstall pyarmor
rm -rf ~/.pyarmor
rm -rf {pyarmor-folder} (before v5.8.0)
rm -rf ~/.pyarmor_capsule.zip (before v6.2.0)
rm /path/to/project/.pyarmor_config
異なるユーザーでログを取る場合、パス ~
は異なる場合があります。
3.PyArmorの使用
pyarmorコマンドの構文は次のとおりです。
pyarmor[コマンド][オプション]
Pythonスクリプトを難読化する
obfuscateコマンドを使用して、Pythonスクリプトを難読化します。最も単純なケースでは、現在のディレクトリをmyscript.py
プログラムの場所に設定して、以下を実行します。
pyarmor obfuscate myscript.py
PyArmorは、myscript.pyと同じフォルダー内の *.py
すべてを難読化します。
-
pyarmor_capsule.zip
が存在しない場合はHOMEフォルダに作成します。 - スクリプトと同じフォルダに
dist
フォルダがなければ作成します。 - 難読化した myscript.py を
dist
フォルダに書き込みます。 - スクリプトと同じフォルダーにあるすべての難読化された *.py を
dist
フォルダーに書き込みます。 - 難読化されたスクリプトの実行に使用されるランタイムファイルを
dist
フォルダにコピーします。
distフォルダに難読化されたスクリプトと必要なファイルがすべて生成されます。
dist/
myscript.py
pytransform/
__init__.py
_pytransform.so/.dll/.dylib
Runtime Packageと呼ばれるエキストラフォルダ pytransform
は、難読化されたスクリプトを実行するために必要です。
通常、コマンドラインには1つのスクリプトを指定します。それはエントリースクリプトです。 myscript.py
の中身はこんな感じです。
from pytransform import pyarmor_runtime
pyarmor_runtime()
__pyarmor__(__name__, __file__, b'\x06\x0f...')
最初の2行は Bootstrap Code と呼ばれ、エントリースクリプトにのみ含まれています。これらは難読化されたファイルを使用する前に実行する必要があります。他のすべての難読化された *.py
については、最後の1行だけです。
__pyarmor__(__name__, __file__, b'\x0a\x02...')
難読化したスクリプトを実行します。
cd dist
python myscript.py
デフォルトでは、エントリースクリプトと同じパスにある *.py のみが難読化されます。サブフォルダ内のすべての *.py を再帰的に難読化するには、次のコマンドを実行します。
pyarmor obfuscate --recursive myscript.py
難読化したスクリプトを配布する
出力パス dist
にあるファイルをすべてエンドユーザーにコピーするだけです。難読化されたスクリプト以外の ランタイムパッケージ もエンドユーザに配布する必要があることに注意してください。
PyArmorは .py
ファイルのみを扱います。パッケージ内にデータファイルやバイナリ拡張子がある場合、それらを手動で dist
にコピーしてください。
ランタイムパッケージは難読化されたスクリプトを含まないかもしれません。import pytransform
が機能する場合にのみ、任意のPythonパスに移動できます。
難読化されたスクリプトのセキュリティについては、PyArmorのセキュリティ の項を参照してください。
PyArmorはランタイムマシンにインストールする必要はありません。
難読化されたスクリプトのライセンスを生成する
難読化されたスクリプトの新しい license.lic
を生成するには、licenses コマンドを使用します。
難読化されたスクリプトの期限付きのライセンスを生成します。
pyarmor licenses --expired 2019-01-01 r001
PyArmorが新しいライセンスファイルを生成します。
- HOMEフォルダの.pyarmor_capsule.zipからデータを読み込みます。
- licenses/r001フォルダにlicense.licを作成します。
- licenses/r001フォルダにlicense.lic.txtを作成します。
この新しいものを使ってスクリプトを難読化します。
pyarmor obfuscate --with-license licenses/r001/license.lic myscript.py
難読化したスクリプトを実行すると、2019年1月1日以降にエラーが報告されます。
cd dist
python myscript.py
難読化されたスクリプトを固定マシンにバインドするためのライセンスを生成し、まずハードウェア情報を取得します。
pyarmor hdinfo
そして、ハードディスクのシリアル番号とマックアドレスにバインドして新しいライセンスを生成します。
pyarmor licenses --bind-disk "100304PBN2081SF3NJ5T" --bind-mac "20:c1:d2:2f:a0:96" code-002
難読化されたスクリプトを新しいライセンスで実行します。
pyarmor obfuscate --with-license licenses/code-002/license.lic myscript.py
cd dist/
python myscript.py
また、難読化されたスクリプトと一緒に外部ライセンスファイルlicense.licを使用することもできます。外側のライセンスによって、スクリプトを一度難読化し、必要に応じて古いライセンスを上書きする新しいライセンスを生成します。これは従来の方法であり、アウターライセンスファイルの使用方法を参照してください。
ライセンスタイプの拡張
難読化されたスクリプトに対して、他のライセンスタイプを拡張するのは簡単です。エントリースクリプトに認証コードを追加するだけです。難読化されたスクリプトはそれ以上変更できないので、スクリプトの中で好きなことを行ってください。この場合、ランタイムモジュールpytransform が役に立ちます。
望ましい方法は、Using Plugin to Extend License Type(プラグインを使用したライセンスタイプの拡張) です。利点は、スクリプトを全く変更する必要がないことです。認証コードを分離したスクリプトに記述し、難読化したスクリプトに難読化として注入するだけです。詳しくは、プラグインとの付き合い方 を参照してください。
以下はプラグインの例です。
https://github.com/dashingsoft/pyarmor/tree/master/plugins
単一モジュールの難読化
1つのモジュールを正確に難読化するには、--exact
オプションを使用します。
pyarmor obfuscate --exact foo.py
foo.py
だけが難読化されているので、この難読化されたモジュールをインポートしてください。
cd dist
python -c "import foo"
パッケージ全体の難読化
以下のコマンドを実行すると、パッケージが難読化されます。
pyarmor obfuscate --recursive --output dist/mypkg mykpg/__init__.py
難読化されたパッケージをインポートします。
cd dist
python -c "import mypkg"
難読化されたスクリプトをパックする
難読化されたスクリプトをバンドルにパックするには、コマンド pack を使います。
まず PyInstaller をインストールします。
pip install pyinstaller
カレントディレクトリをプログラム myscript.py
の場所に設定し、実行します。
pyarmor pack myscript.py
PyArmorはmyscript.pyをパックします。
-
pyarmor obfuscate
を実行してmyscript.py
を難読化します。 -
pyinstaller myscipt.py
を実行してmyscript.spec
を作成します。 -
myscript.spec
を更新し、元のスクリプトを難読化されたものに置き換えます。 -
pyinstaller myscript.spec
を実行して、難読化されたスクリプトをバンドルします。 -
dist/myscript
フォルダに、ユーザに配布するバンドルアプリが格納されます。
最終的に実行可能なファイルを実行します。
dist/myscript/myscript
バンドルの期限付きライセンスを生成します。
pyarmor licenses --expired 2019-01-01 code-003
pyarmor pack --with-license licenses/code-003/license.lic myscript.py
dist/myscript/myscript
複雑な場合は、packコマンドや難読化スクリプトのパック方法を参照してください。
さらなるセキュリティの向上
これらのPyArmorの機能は、セキュリティをさらに強化する可能性があります。
- 可能であれば Super Plus Mode を使用してスクリプトを難読化し、そうでなければ Super Mode を使用し、プラットフォームがサポートされている場合は Advanced Mode を有効にします。Windowsで、パフォーマンスが要件を満たしている場合は、VMモードを有効にします。
- 難読化されたスクリプトが1つの実行形式バンドルとして配布されている場合は、難読化されたスクリプトをPythonインタプリタにバインドするを試してみてください。
- エントリースクリプトにクロスプロテクションコードのパッチが適用されていることを確認し、クロスプロテクションコードのカスタマイズを試す。
- 対応する Restrict Mode を使って、難読化されたモジュールを誰もインポートできないようにする。
- 高セキュリティコードの難読化
-obf-code=2
を使用する。 - 難読化されたスクリプトにプライベートチェックポイントを挿入することによる プラグインを使用したセキュリティの向上 では、モンキートリックハッキングを防ぐために使用されるpytransformに1つのデコレータ
assert_armored()
と1つの関数check_armored()
があります。難読化されたスクリプトがハッキングされないことを確認するために、他のチェックポイントを追加することもできます。 - データファイルを保護する必要がある場合は、データファイルの保護方法 を参照してください。
難読化されたスクリプトのセキュリティについては、PyArmorのセキュリティを参照してください。
4.アドバンストピックス
スーパーモードの使用
スーパーモード はv6.2.0から導入され、難読化されたスクリプトを実行するために必要な拡張モジュールは1つだけとなり、以前は一部のユーザーを混乱させていた ブートストラップコード はなくなり、すべての難読化スクリプトが同じになりました。これにより、セキュリティは格段に向上し、使い方もシンプルになりました。ただし、最新のPythonバージョン2.7, 3.7, 3.8, 3.9 にしか対応していないのが難点です。
オプション --advanced 2
でsuper modeを有効にすします。 例えば、
pyarmor obfuscate --advanced 2 foo.py
難読化したスクリプトを他のマシンに配布する場合、拡張モジュールpytransform
がPythonのパスにあれば、難読化したスクリプトは正常に動作するはずです。
難読化されたスクリプトを復元するには、事前に license.lic
を生成しておく必要があります。たとえば、
pyarmor licenses --bind-mac xx:xx:xx:xx regcode-01
そして、このライセンスを --with-license
などのオプションで指定します。
pyarmor obfuscate --with-license licenses/regcode-01/license.lic \
--advanced 2 foo.py
この方法で、指定されたライセンスファイルは拡張モジュールpytransformに組み込まれます。もし外側のlicense.lic
を使いたいなら、例えば --with-license
オプションを特別な値 outer
に設定するだけで、簡単に他のライセンスと置き換えることができます。
pyarmor obfuscate --with-license outer --advanced 2 foo.py
詳しくは、次章をご覧ください。
アウターライセンスファイルの使用方法
v6.3.0以降、ランタイムファイルlicense.licはダイナミックライブラリに組み込まれました。もし、外側のlicense.lic
を使用したい場合は、オプション --with-license
に特別な値 outer
を設定します。
pyarmor obfuscate --with-license outer foo.py
難読化されたスクリプトが起動すると、license.lic
を順番に検索します。
- 環境変数
PYARMOR_LICENSE
をチェックし、設定されていれば、このファイル名を使用します。 -
sys.PYARMOR_LICENSE
をチェックし、設定されていれば、このファイル名を使用します。 - 設定されていない場合は、現在のパスで
license.lic
を検索します。 - 非スーパーモードでは、ランタイムパッケージ
pytransform
のパスでlicense.licを
検索します。 - それでも見つからない場合は例外を発生させます
これは sys.PYARMOR_LICENSE
の基本的な使用方法です。
非スーパーモードでは、ランタイムファイル dist/pytransform/__init__.py
の pyarmor_runtime
関数を編集し、一行を追加します。
sys.PYARMOR_LICENSE = '/path/to/license.lic'
スーパーモードでは、pythonの拡張子 pytransform.so
を同名のパッケージ pytransform
に変換してください。例えば、
cd dist
mkdir pytransform
mv pytransform.so pytransform/
そして、dist/pytransform/__init__.py
を作成します。
import sys
sys.PYARMOR_LICENSE = '/path/to/license.lic'
name = 'pytransform'
m = __import__(name, globals(), locals(), ['*'])
sys.modules[__name__].__dict__.update(m.__dict__)
多くのパッケージの難読化
pkg1, pkg2, pkg2 の3つのパッケージがあります。これらはすべて難読化され、共有のランタイムファイルを使用する予定です。
まず、作業パスを変更し、3つのプロジェクトを作成します。
mkdir build
cd build
pyarmor init --src /path/to/pkg1 --entry __init__.py pkg1
pyarmor init --src /path/to/pkg2 --entry __init__.py pkg2
pyarmor init --src /path/to/pkg3 --entry __init__.py pkg3
次に、ランタイムパッケージ を作成し、パス dist
に保存します。
pyarmor build --output dist --only-runtime pkg1
または、ランタイムパッケージを直接生成する場合は、runtimeコマンドを実行します。
pyarmor runtime --output dist
次に3つのパッケージを難読化し、dist
に保存します。
pyarmor build --output dist --no-runtime pkg1
pyarmor build --output dist --no-runtime pkg2
pyarmor build --output dist --no-runtime pkg3
すべての出力を確認し、これらの難読化されたパッケージをテストしてください。
ls dist/
cd dist
python -c 'import pkg1
import pkg2
import pkg3'
出力パスdistにあるランタイムパッケージ pytransform
は、インポートできる場合に限り、他のPythonパスへ移動することができます。
v5.7.2以降では、ランタイムパッケージも別途 runtime コマンドで生成できるようになりました。
pyarmor runtime
他の難読化ライブラリとのコンフリクト解消
v5.8.7での新機能
異なる開発者によって難読化された2つのパッケージがあるとして、それらを同じPythonインタープリタでインポートできますか?
もし両方ともpyarmorの試用版で難読化されているのなら、問題はありません、答えはイエスです。しかし、どちらかが登録版で難読化されている場合、答えは「ノー」です。
v5.8.7以降では、オプション --enable-suffix でスクリプトを難読化し、ランタイムパッケージ を固定名pytransform以外のユニークなサフィックス付きで生成することができます。例えば、
pyarmor obfuscate --enable-suffix foo.py
このような出力になります。
dist/
foo.py
pytransform_vax_000001/
__init__.py
...
接尾辞 _vax_000001
は、PyArmor のレジストレーションコードを元にしたものです。
プロジェクトの場合、config コマンドで enable-suffixを
設定します。
pyarmor config --enable-suffix 1
pyarmor build -B
または、この方法で無効化します。
pyarmor config --enable-suffix 0
pyarmor build -B
難読化されたパッケージの配布
配布するパッケージが多い場合は、enableサフィックスをつけたランタイムパッケージを別途生成し、すべてのパッケージで共有することをお勧めします。
例えば、まずruntimeコマンドでランタイムパッケージを生成します。
pyarmor runtime --enable-suffix -O dist/shared
出力されたパッケージは dist/shared/pytransform_vax_000001
のように見えるかもしれません。
各パッケージについて、この共有 pytransform
で難読化します。
pyarmor obfuscate --enable-suffix --recursive --bootstrap 2 \
-O dist/pkg1 --runtime @dist/shared src/pkg1/__init__.py
v6.3.7からの新機能で、--runtime
オプションが使えない場合は、--no-runtime
に置き換えてください。
pyarmor obfuscate --enable-suffix --recursive --bootstrap 2 \
-O dist/pkg1 --no-runtime src/pkg1/__init__.py
次に、通常のパッケージとして pytransform_vax_000001 パッケージを配布します。
最後に、難読化されたパッケージ dist/pkg1
を配布し、セットアップスクリプトで依存関係を追加します。例えば、
install_requires=['pytransform_vax_000001']
他のパッケージ pkg2, pkg3 などにも pkg1 と同じことを行ってください。
難読化したスクリプトを他のプラットフォームへ配布する
まず、downloadコマンドで利用可能なプラットフォーム名をすべてリストアップします。
pyarmor download
pyarmor download --help-platform
オプション -list
を指定すると、詳細が表示されます。
pyarmor download --list
pyarmor download --list windows
pyarmor download --list windows.x86_64
そして、スクリプトを難読化する際にプラットフォーム名を指定します。
pyarmor obfuscate --platform linux.armv7 foo.py
# For project
pyarmor build --platform linux.armv7
異なる機能を持つスクリプトの難読化
同じプラットフォームで利用可能なダイナミックライブラリは多数存在します。それぞれのライブラリは異なる機能を持っています。例えば、windows.x86_64.0 と windows.x86_64.7 はどちらもwindwos.x86_64というプラットフォームで動作します。最後の数字は、機能を表しています。
- 0: アンチデバッグ、JIT、アドバンスドモード機能、高速性なし
- 7: アンチデバッグ、JIT、アドバンストモード機能、高セキュリティを含む
特殊な機能でスクリプトを難読化することが可能です。例えば
pyarmor obfuscate --platform linux.x86_64.7 foo.py
なお、機能の異なるダイナミックライブラリには互換性がありません。例えば、Windowsで --platform linux.arm.0
でスクリプトを難読化しようとした場合。
pyarmor obfuscate --platform linux.arm.0 foo.py
Windowsのデフォルトのプラットフォームはフル機能の windows.x86_64.7
なので、PyArmorはwindows.x86_64.0
プラットフォームで再起動し、この低機能プラットフォーム linux.arm.0
用に難読化スクリプトを作成しなければなりません。
また、環境変数PYARMOR_PLATFORM
をターゲットマシンと同じ機能プラットフォームに設定することもできます。例えば、
PYARMOR_PLATFORM=windows.x86_64.0 pyarmor obfuscate --platform linux.arm.0 foo.py
# In Windows
set PYARMOR_PLATFORM=windows.x86_64.0
pyarmor obfuscate --platform linux.arm.0 foo.py
set PYARMOR_PLATFORM=
難読化されたスクリプトを複数のプラットフォームで動作させる場合
v5.7.5から、プラットフォーム名が標準化され、利用可能なプラットフォーム名はすべて Standard Platform Names にリストアップされています。これにより、難読化されたスクリプトを複数のプラットフォームで実行できるようになりました。
複数のプラットフォームに対応するためには、これらのプラットフォーム用のダイナミックライブラリを全てランタイムパッケージにコピーする必要があります。例えば、難読化したスクリプトは、Windows/Linux/MacOSで動作させることができます。
pyarmor obfuscate --platform windows.x86_64 \
--platform linux.x86_64 \
--platform darwin.x86_64 \
foo.py
ランタイムパッケージも、一度 runtimeコマンドで生成し、ランタイムファイルなしでスクリプトを難読化することができます。たとえば、
pyarmor runtime --platform windows.x86_64,linux.x86_64,darwin.x86_64
pyarmor obfuscate --no-runtime --recursive \
--platform windows.x86_64,linux.x86_64,darwin.x86_64 \
foo.py
難読化されたスクリプトはダイナミックライブラリをチェックするため、 --no-runtimeオプションを指定した場合でもプラットフォームの指定は必要です。しかし、--no-cross-protectionオプションが指定されている場合、難読化されたスクリプトはダイナミック・ライブラリをチェックしないので、プラットフォームは必要ありません。例えば、
pyarmor obfuscate --no-runtime --recursive --no-cross-protection foo.py
プラットフォームが Windows.x86_64.0
の場合、他のプラットフォームも同じフィーチャーである必要があります。
難読化されたスクリプトが他のプラットフォームで動作しない場合は、ダウンロードしたファイルをすべて更新してみてください。
pyarmor download --update
それでもうまくいかない場合は、$HOME/.pyarmor
パスにあるcahcedプラットフォームファイルを削除してみてください。
他のPythonバージョンによるスクリプトの難読化
マシンに複数のバージョンのPythonがインストールされている場合、pyarmorコマンドはデフォルトのPythonを使用します。他のPythonで難読化する必要がある場合は、そのPythonで明示的にpyarmorを実行します。
例えば、まず pyarmor.py
を探します。
find /usr/local/lib -name pyarmor.py
一般に、ほとんどのlinuxでは、 /usr/local/lib/python2.7/dist-packages/pyarmor
の中にあるはずです。
次に、以下のようにpyarmorを実行します。
/usr/bin/python3.6 /usr/local/lib/python2.7/dist-packages/pyarmor/pyarmor.py
シェルスクリプト /usr/local/bin/pyarmor3
を作成すると便利です。内容は、
/usr/bin/python3.6 /usr/local/lib/python2.7/dist-packages/pyarmor/pyarmor.py "$@"
そして、
chmod +x /usr/local/bin/pyarmor3
前回と同様にpyarmor3を使用します。
Windowsで、pyarmor3.batというbatファイルを作成し、内容は以下のようなものになります。
C:\Python36\python C:\Python27\Lib\site-packages\pyarmor\pyarmor.py %*
プレーンスクリプトでブートストラップコードを実行する
v5.7.0以前では、ブートストラップコード はプレーンスクリプトに直接挿入できましたが、 セキュリティのために、ブートストラップコード は難読化スクリプトの中になければなりません。プレーン・スクリプトの中でブートストラップコードを実行するために、別の方法が必要です。
まず、runtimeコマンドで1つのブートストラップ・パッケージ pytransform_bootstrap を作成します。
pyarmor runtime -i
次に、bootstrap パッケージをプレーンスクリプトのパスに移動します。
mv dist/pytransform_bootstrap /path/to/script
また、例えば、pythonのシステムライブラリにコピーすることもできます。
mv dist/pytransform_bootstrap /usr/lib/python3.5/ (For Linux)
mv dist/pytransform_bootstrap C:/Python35/Lib/ (For Windows)
次に、プレーンスクリプトを編集し、1行挿入します。
import pytransform_bootstrap
これで、この行の後に他の難読化されたモジュールがインポートできるようになりました。
v5.8.1以前は、この方法でブートストラップパッケージを作成してください。
echo "" > __init__.py
pyarmor obfuscate -O dist/pytransform_bootstrap --exact __init__.py
難読化されたスクリプトのunittestの実行
難読化されたスクリプトの多くには、ブートストラップコードが存在しません。そのため、難読化されたスクリプトではunittestスクリプトは動作しないかもしれません。
テストスクリプトが /path/to/tests/test_foo.py だとすると、まずこのテストスクリプトにパッチを当て、プレーンスクリプトでブートストラップコードを実行するよう指示します。
その後、難読化されたモジュールで動作するようになります。
cd /path/to/tests
python test_foo.py
他の方法は、直接システムパッケージunittestにパッチを当てることです。ブートストラップパッケージpytransform_bootstrapがPythonシステムライブラリにコピーされていることを確認してください。プレーンスクリプトでブートストラップコードを実行するを参照してください。
そして、/path/to/unittest/__init__.py
を編集して、一行挿入します。
import pytransform_bootstrap
これで、すべてのユニテストスクリプトが難読化されたスクリプトで動作するようになりました。多くのユニテストスクリプトがある場合に有効です。
Python Interpreter に難読化されたスクリプトを自動的に認識させる
もし Python Interpreter が難読化されたスクリプトを自動的に認識できれば、すべてがシンプルになります。
- 難読化されたスクリプトのほとんどは、メインスクリプトとして実行されます。
- 難読化されたスクリプトの中でマルチプロセシングを呼び出し、新しいプロセスを作成します。
- あるいは Popen や os.exec などを呼び出して、他の難読化されたスクリプトを実行する。
- ...
以下は基本的なステップです。
1. まず、ブートストラップパッケージ pytransform_bootstrap を作成します。
pyarmor runtime -i
v5.8.1以前は、空のパッケージを難読化することで作成する必要があります。
echo "" > __init__.py
pyarmor obfuscate -O dist/pytransform_bootstrap --exact __init__.py
2. 次に、難読化されたスクリプトを実行するための仮想Python環境を作成し、ブートストラップ・パッケージを仮想Pythonライブラリに移動させます。例えば、
# For windows
mv dist/pytransform_bootstrap venv/Lib/
# For linux
mv dist/pytransform_bootstrap venv/lib/python3.5/
3. venv/lib/site.py または venv/lib/pythonX.Y/site.py を編集し、main行の前に pytransform_bootstrap をインポートしてください。
import pytransform_bootstrap
if __name__ == '__main__':
...
また、main関数の末尾に挿入したり、モジュールサイトがインポートされたときに実行できる場所に挿入することもできます。
その後、Pythonの初期化時にモジュールサイトが自動的にインポートされるので、仮想環境上で難読化されたスクリプトを直接実行することができます。
https://docs.python.org/3/library/site.html を参照してください。
この仮想環境ではpyarmorというコマンドは動作せず、難読化されたスクリプトを実行するためにのみ使用されます。
v5.7.0以前は、Runtime Filesでbootstrapパッケージを手動で作成する必要があります。
異なるモードでのPythonスクリプトの難読化
Advanced ModeはPyArmor 5.5.0から導入されましたが、デフォルトでは無効になっています。有効にするにはオプション --advanced
を指定してください。
pyarmor obfuscate --advanced 1 foo.py
# For project
cd /path/to/project
pyarmor config --advanced 1
pyarmor build -B
PyArmor 5.2以降、デフォルトのRestrict Modeは1ですが、オプション --restrict
で変更することができます。
pyarmor obfuscate --restrict=2 foo.py
pyarmor obfuscate --restrict=3 foo.py
# For project
cd /path/to/project
pyarmor config --restrict 4
pyarmor build -B
必要であれば、この方法ですべての制限を無効にすることができます。
pyarmor obfuscate --restrict=0 foo.py
# For project
pyarmor config --restrict=0
pyarmor build -B
スクリプトがlicensesによって生成されたライセンスを使用する場合、すべての制限を無効にするには、licensesコマンドにオプション --disable-restrict-mode
を渡します。例えば、
pyarmor licenses --disable-restrict-mode r001
pyarmor obfuscate --with-license=licenses/r001/license.lic foo.py
# For project
pyarmor config --with-license=licenses/r001/license.lic
pyarmor build -B
Obfuscating Code Mode, Wrap Mode, Obfuscating module Modeは、obfuscateコマンドで変更することができません。これらは、プロジェクトを活用する際に、configコマンドで変更することができます。たとえば、
pyarmor init --src=src --entry=main.py .
pyarmor config --obf-mod=1 --obf-code=1 --wrap-mode=0
pyarmor build -B
プラグインを使用したライセンスタイプの拡張
PyArmorは難読化されたスクリプトのライセンスタイプをプラグインで拡張することができます。例えば、ローカルタイム以外のインターネットタイムをチェックする場合です。
まず、プラグインスクリプトcheck_ntp_time.pyを作成します。このスクリプトの主要な関数はcheck_ntp_time
で、もう一つの重要な関数は _get_license_data
で、難読化スクリプトの license.lic
から追加データを取得するために使用されます。
次に、エントリースクリプト foo.py に2つのコメントを挿入します。
# {PyArmor Plugins}
# PyArmor Plugin: check_ntp_time()
エントリースクリプトを難読化します。
pyarmor obfuscate --plugin check_ntp_time foo.py
プラグインファイルがカレントパスにない場合、代わりに絶対パスを使用します。
pyarmor obfuscate --plugin /usr/share/pyarmor/check_ntp_time foo.py
最後に、この難読化されたスクリプトのために1つのライセンスファイルを生成し、オプション -x
で追加のライセンスデータを渡します。このデータは、プラグインスクリプトの関数 _get_license_data
で得ることができます。
pyarmor licenses -x 20190501 rcode-001
pyarmor obfuscate --with-license licenses/rcode-001/license.lic \
--plugin check_ntp_time foo.py
packコマンド用:
pyarmor licenses -x 20190501 rcode-001
pyarmor pack --with-license licenses/rcode-001/license.lic \
-x " --plugin check_ntp_time" foo.py
その他の例については、https://github.com/dashingsoft/pyarmor/tree/master/plugins を参照してください。
プラグインの仕組みについては、プラグインとの付き合い方を参照してください。
プラグイン内の出力関数名は、プラグイン名と同じでなければなりません。
難読化されたスクリプトを1つの実行ファイルにまとめる
次のコマンドを実行して、スクリプト foo.py
を一つの実行ファイル dist/foo.exe
にパックします。ここで、foo.py
は難読化されていませんが、パックする前に難読化されます。
pyarmor pack -e " --onefile" foo.py
dist/foo.exe
難読化したスクリプトの「license.lic」を実行ファイルにバンドルせず、実行ファイルの外側に配置したい場合は、以下のようにします。
dist/
foo.exe
license.lic
これにより、後で簡単にユーザーごとに異なるライセンスを生成することができます。以下は基本的な手順です。
1. まず、runtime-hook script
を license.py
をコピーして作成します。
import sys
from os.path import join, dirname
with open(join(dirname(sys.executable), 'license.lic'), 'rb') as fs:
with open(join(sys._MEIPASS, 'license.lic'), 'wb') as fd:
fd.write(fs.read())
2. そして、スクリプトに追加オプションを詰めます。
pyarmor pack --clean --without-license -x " --exclude copy_license.py" \
-e " --onefile --icon logo.ico --runtime-hook copy_license.py" foo.py
オプション --without-license
は、難読化されたスクリプトの license.lic
を最終的な実行ファイルにバンドルしないよう指定します。PyInstaller のオプション --runtime-hook
を指定すると、指定したスクリプト copy_license.py
が難読化されたスクリプトがインポートされる前に実行されます。このスクリプトは、外側の license.lic
を正しいパスへコピーします。
dist/foo.exe
を実行すると、ライセンスエラーが報告されるはずです。
3. 最後に licensesを実行して難読化したスクリプトの新しいライセンスを生成し、新しいlicense.licとdist/foo.exeをエンドユーザー向けにコピーしてください。
pyarmor licenses -e 2020-01-01 code-001
cp license/code-001/license.lic dist/
dist/foo.exe
難読化されたスクリプトをカスタマイズされたspecファイルでバンドルする
カスタマイズされた .spec
ファイルがある場合、例えば、以下のように動かします。
pyinstaller myscript.spec
難読化されたスクリプトでpyinstallerバンドルを再パックするを参照してください。
または、-s
オプションで直接難読化し、スクリプトをパックしてください。
pyarmor pack -s myscript.spec myscript.py
このエラーが発生した場合、
Unsupport .spec file, no XXX found
.spec
ファイルを確認し、トップレベルに2行あることを確認します(identationなし)。
a = Analysis(...
pyz = PYZ(...
そして、Analysisオブジェクトを作成する際には、3つのキーパラメータがあります。例えば、
a = Analysis(
...
pathex=...,
hiddenimports=...,
hookspath=...,
...
)
PyArmorはこれらの行に必要なオプションを自動的に追加します。しかし、v5.9.6以前は手動でパッチを当てる必要があります。
- モジュール
pytransform
をhiddenimports
に追加します - DISTPATH/obf/tempのパスをpathexとhookspathに追加します
変更後は以下のようになります。
a = Analysis(['myscript.py'],
pathex=[os.path.join(DISTPATH, 'obf', 'temp'), ...],
binaries=[],
datas=[],
hiddenimports=['pytransform', ...],
hookspath=[os.path.join(DISTPATH, 'obf', 'temp'), ...],
...
この機能は、v5.8.0から導入されました。
v5.8.2以前では、追加パスはDISTPATH/obfであり、DISTPATH/tempではありません。
制限モードによるセキュリティの向上
デフォルトでは、難読化されたスクリプトを変更できないように、制限モード1によって難読化されています。この難読化されたスクリプトを、難読化されたスクリプトの外にインポートできないように、制限モード2で難読化することにより、セキュリティを向上させることができます。例えば、
pyarmor obfuscate --restrict 2 foo.py
また、より安全性を高めるために、制限モード3によってスクリプトを難読化することも可能です。さらに、難読化されたスクリプトの中ですべての関数が呼び出されていることを確認するために、各関数の呼び出しをチェックします。例えば、
pyarmor obfuscate --restrict 3 foo.py
しかし、Pythonパッケージにはモード2と3が適用されません。Pythonパッケージのセキュリティを向上させるために、もう一つの解決策があります。
外部スクリプトで使用される .py
ファイルは、restrict mode 1
で難読化されます。
パッケージ内でのみ使用される他のすべての.pyファイルは、制限モード4で難読化されます。
例えば、mypkg
は2つのファイルを含んでいます。
__init__.py
foo.py
ここでは、mypkg/__init__.py
の内容です。
from .foo import hello
def open_hello(msg):
print('This is public hello: %s' % msg)
def proxy_hello(msg):
print('This is proxy hello from foo: %s' % msg)
hello(msg)
さて、このパッケージをこのように難読化します。
cd /path/to/mypkg
pyarmor obfuscate -O obf/mypkg --exact __init__.py
pyarmor obfuscate -O obf/mypkg --restrict 4 --recursive --exclude __init__.py .
ですから、mypkg
をインポートして、 __init__.py
の中の任意の関数を呼び出してもOKです。
cd /path/to/mypkg/obf
python
>>> import mypkg
>>> mypkg.open_hello("it should work")
>>> mypkg.proxy_hello("also OK")
しかし、mypkg.fooの中のどの関数を呼んでもうまくいきません。例えば、
cd /path/to/mypkg/obf
python
>>> import mypkg
>>> mypkg.foo.hello("it should not work")
Restrict Modeについて詳しくは、Restrict Mode を参照してください。
プラグインを使ったセキュリティの向上
プラグインを使えば、難読化されたスクリプトに任意のプライベートチェックポイントを注入することができ、元のスクリプトには影響を与えません。プラグインとしてコメントされていない場合、難読化されたスクリプトの中で実行される必要があります。
チェック・ロジックは誰にもわからないし、いつでも変更できます。そのため、より安全性が高くなります。例えば、デバッガプロセスがあるかどうか、sys._getframe
で取得できる呼び出し元のバイトコードの合計をチェックする、などです。
インラインプラグインを使った動的ライブラリのチェック
PyArmorはクロスプロテクションを提供しますが、起動時にダイナミックライブラリをチェックし、他者によって変更されていないことを確認することも可能です。この例では、main.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')
そして、この方法でスクリプトを難読化し、インラインプラグインを有効にしてください。
pyarmor obfuscate --plugin on main.py
難読化されたスクリプトが起動すると、最初に以下のプラグインコードが実行されます。
import os
libname = os.path.join( os.path.dirname( __file__ ), '_pytransform.so' )
if not os.stat( libname ).st_mtime_ns == 102839284238:
raise RuntimeError('Invalid Library')
インポートされた関数が難読化されているかどうかを確認する
pytransformには1つのデコレータ assert_armored()
と1つの関数 check_armored()
があり、他のモジュールからのインポート関数が難読化されているかどうかを確認するために使用されます。
例えば、main.py
と foo.py
の2つのスクリプトがあるとします。
#
# This is main.py
#
import foo
def start_server():
foo.connect('root', 'root password')
foo.connect2('user', 'user password')
#
# This is foo.py
#
def connect(username, password):
mysql.dbconnect(username, password)
def connect2(username, password):
db2.dbconnect(username, password)
main.py
の中で、foo.connect
が難読化されていることを確認する必要があります。さもないと、エンドユーザーは難読化された foo.py
をこのプレーンなスクリプトに置き換えて、難読化された main.py
を実行する可能性があります。
def connect(username, password):
print('password is %s', password)
パスワードが盗まれてしまうので、これを避けるために、デコレータ関数を使用して、プラグインによって接続関数が難読化されるようにします。
それでは、main.py
を編集して、インラインプラグインのコードを挿入してみましょう。
import foo
# PyArmor Plugin: from pytransform import assert_armored
# PyArmor Plugin: @assert_armored(foo.connect, foo.connect2)
def start_server():
foo.connect('root', 'root password')
そして、 plugin on
で難読化します。
pyarmor obfuscate --plugin on main.py
難読化されたスクリプトは次のようになります。
import foo
from pytransform import assert_armored
@assert_armored(foo.connect, foo.connect2)
def start_server():
foo.connect('root', 'root password')
start_server
をコールする前に、デコレータ関数 assert_armored
が両方の接続関数が pyarmored
であることをチェックし、そうでない場合は例外を発生させます。
check_armored()
によってもチェックできます。
import foo
from pytransform import check_armored
def start_server():
if not check_armored(foo.connect, foo.connect2):
print('Found hacker')
return
foo.connect('root', 'root password')
Pythonスクリプトからpyarmorを呼び出す
os.execやsubprocess.Popenなどではなく、Pythonスクリプトの内部でPyArmorのメソッドを呼び出すことも可能です。たとえば、以下のようになります。
from pyarmor.pyarmor import main as call_pyarmor
call_pyarmor(['obfuscate', '--recursive', '--output', 'dist', 'foo.py'])
pyarmor の通常の出力をすべて抑制するには、-silent
を付けて呼び出します。
from pyarmor.pyarmor import main as call_pyarmor
call_pyarmor(['--silent', 'obfuscate', '--recursive', '--output', 'dist', 'foo.py'])
v5.7.3以降、この方法でpyarmorを呼び出した場合、何か問題があると、sys.exitを呼び出す以外に例外が発生するようになりました。
Web apiでライセンスキーを生成する
Pythonスクリプト内でファイルに書き出す以外に、文字列としてライセンスキーを生成することも可能です。Web apiで新しいライセンスを生成する必要がある場合に便利です。
from pyarmor.pyarmor import licenses as generate_license_key
lickey = 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)
1つのWeb APIからライセンスを生成する必要がある製品が複数ある場合、登録された製品ごとにキーワード home
を設定します。例えば、
from pyarmor.pyarmor import licenses as generate_license_key
lickey = 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)
難読化スクリプトの実行時に定期的にライセンスをチェックする
通常、難読化されたスクリプトの起動時のみライセンスがチェックされます。v5.9.3以降では、1時間ごとにライセンスをチェックすることもできるようになりました。この場合、--enable-period-modeを指定して新しいライセンスを生成し、デフォルトのライセンスを上書きしてください。例えば、
pyarmor obfuscate foo.py
pyarmor licenses --enable-period-mode code-001
cp licenses/code-001/license.lic ./dist
Nuitkaを使った作業
難読化されたスクリプトは、ランタイムパッケージ pytransform を追加すれば通常のスクリプトとして扱えるので、Nuitka で C プログラムに翻訳することも可能です。難読化する際には、--restrict 0
と --no-cross-protection
オプションを設定する必要があり、そうしないと最終的なC言語プログラムは動作しません。例えば、まずスクリプトを難読化する。
pyarmor obfuscate --restrict 0 --no-cross-protection --package-runtime 0 foo.py
そして、難読化したものをNuitkaで通常のPythonスクリプトとして翻訳します。
cd ./dist
python -m nuitka --include-package=pytransform foo.py
./foo.bin
難読化されたスクリプトのインポートモジュール(パッケージ)がNuitkaで見えないという問題があります。この問題を解決するには、まずオリジナルのスクリプトで対応する.pyiを生成し、それを難読化されたスクリプトの中にコピーしてください。例えば、
# Generating "mymodule.pyi"
python -m nuitka --module mymodule.py
pyarmor obfuscate --restrict 0 --no-bootstrap --package-runtime 0 mymodule.py
cp mymodule.pyi dist/
cd dist/
python -m nuitka --module mymodule.py
しかし、この方法では、バイトコードのほとんどがcコードに変換されないので、Nuitkaの機能を生かせないかもしれません。
Nuitkaで生成したC言語プログラムをlibpythonとリンクして実行する限り、pyarmorはNuitkaと連携して動作することができます。しかし、将来的には、Nuitkaの公式サイトで言われているように、
これは、- 可能な限り - libpythonにアクセスすることなく , C言語でネイティブのデータ型を使って行われます。
この場合、pyarmorはNuitkaと相性が悪いかもしれません。
Cythonを使った作業
ここでは、pyarmorによって難読化されたpythonスクリプト foo.py
をPython37でCython化する例を示します。
print('Hello Cython')
まず、いくつかの追加オプションで難読化します。
pyarmor obfuscate --package-runtime 0 --no-cross-protection --restrict 0 foo.py
難読化されたスクリプトとランタイムファイルは dist
というパスに保存されます。各オプションの意味については、obfuscateコマンドを参照してください。
次に foo.py
と pytransform.py
を -k
と --lenient
オプションを付けて cythonize
して、 foo.c
と pytransform.c
を作成します。
cd dist
cythonize -3 -k --lenient foo.py pytransform.py
k
と --lenient
オプションがない場合、例外が発生します。
undeclared name not builtin: __pyarmor__
そして、foo.c
と pytransform.c
を拡張モジュールにコンパイルしてください。MacOSでは、以下のコマンドを実行するだけですが、Linuxでは、cflag -fPIC
を追加してください。
gcc -shared $(python-config --cflags) $(python-config --ldflags) \
-o foo$(python-config --extension-suffix) foo.c
gcc -shared $(python-config --cflags) $(python-config --ldflags) \
-o pytransform$(python-config --extension-suffix) pytransform.c
最後にテストして、すべての .py
ファイルを削除し、拡張モジュールをインポートしてください。
mv foo.py pytransform.py /tmp
python -c 'import foo'
期待通りにHello Cythonと表示されます。
PyUpdaterとの連携
PyArmorは、この方法で PyUpdater と連携する必要があります。例えば、foo.py
というスクリプトがあるとします。
- PyUpdaterで
foo.spec
を生成します - pyarmorで
foo-patched.spec
を生成します(--debug
オプション付き)
pyarmor pack --debug -s foo.spec foo.py
# If the final executable raises protection error, try to disable restirct mode
# by the following extra options
pyarmor pack --debug -s foo.spec -x " --restrict 0 --no-cross-protection" foo.py
このパッチされた foo-patched.spec
は、PyUpdaterのビルドコマンドで使用されるかもしれません。
もしPythonスクリプトが変更された場合は、再度難読化すればよいです。obfuscateコマンドのすべてのオプションは、packコマンドの出力から取得できます。
もし、上記で問題がある場合は、 PyArmorで普通にコンパイルして、zipで圧縮して、"/pyu-data/new "に入れればうまくいきます。あとは、普通に署名、処理、アップロードをすればよいです。
詳細は、コマンド pack の説明と、高度な使用法難読化されたスクリプトをカスタマイズされたspecファイルでバンドルするを参照してください。
難読化スクリプトのPythonインタプリタへのバインド
難読化されたスクリプトの安全性を高めるために、難読化されたスクリプトを1つの固定されたPythonインタープリタにバインドすることも可能で、Pythonの動的ライブラリが変更されると難読化されたスクリプトは動作しなくなります。
pyarmor licenses --fixed 1 -O dist/license.lic
難読化されたスクリプトをターゲット・マシンで起動すると、Pythonのダイナミック・ライブラリがチェックされます。このライブラリがビルドマシンにあるPythonのダイナミックライブラリと異なる場合、難読化されたスクリプトは終了します。
プロジェクトを使って難読化する場合は、まず固定ライセンスを生成してください。
cd /path/to/project
pyarmor licenses --fixed 1
デフォルトではlicenses/pyarmor/license.licに保存されますので、このライセンスでプロジェクトを構成します。
pyarmor config --license=licenses/pyarmor/license.lic
異なるプラットフォーム用のスクリプトを難読化する場合、まず対象となるプラットフォームでバインドキーを取得します。スクリプトを作成し、バインド先のPythonインタープリターで実行します。
from ctypes import CFUNCTYPE, cdll, pythonapi, string_at, c_void_p, c_char_p
from sys import platform
def get_bind_key():
if platform.startswith('win'):
from ctypes import windll
dlsym = windll.kernel32.GetProcAddressA
else:
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 - refunc1
code = string_at(refunc1, size)
print('Get bind key: %s' % sum(bytearray(code)))
if __name__ == '__main__':
get_bind_key()
バインドキー xxxxxx を出力し、このバインドキーで1つの固定ライセンスを生成します。
pyarmor licenses --fixed xxxxxx -O dist/license.lic
また、 "," で区切られた複数のキーを渡すことで、多くのPythonインタプリタにライセンスをバインドすることも可能です。
pyarmor licenses --fixed 1,key2,key3 -O dist/license.lic
pyarmor licenses --fixed key1,key2,key3 -O dist/license.lic
スペシャル Key1
は、現在のPythonインタプリタを意味します。
32bit Windowsでは、マシンによってバインドキーが異なるため、同じマシンでpythonを再起動してもバインドキーが変わってしまう可能性があるので、この機能は使用しないでください。
クロスプロテクションコードのカスタマイズ
PyArmorのコアダイナミックライブラリを保護するために、デフォルトの保護コードはエントリスクリプトに注入されます。エントリースクリプトの特殊な取り扱いについてを参照してください。しかし、この公開された保護コードは意図的に回避される可能性があるため、より良い方法は、独自の保護コードを書くことです。
v6.2.0以降、runtimeコマンドはデフォルトの保護コードを生成することができるようになり、これは独自の保護コードを書くためのテンプレートとなります。もちろん、あなた自身で書いても構いません。ただし、難読化されたスクリプトを実行する際に、ランタイムファイルが他の人に変更されないことを確認することができれば、です。
まず、保護スクリプト build/pytransform_protection.py
を生成します。
pyarmor runtime --advanced 2 --output build
そのスクリプトを編集し、難読化した後、このカスタマイズしたスクリプトにオプション --cross-protection
を設定してください。例えば、
pyarmor obfuscate --cross-protection build/pytransform_protection.py \
--advanced 2 foo.py
コマンド obfuscate のオプション --advanced
は、コマンド runtime のオプションと同じにする必要があります。
ランタイムファイルlicense.licの任意の場所への保存
ランタイムパッケージにシンボルリンクを作成することで、難読化スクリプトを実行する際に、ランタイムファイルlicense.licを任意の場所に保存することが簡単にできます。
例えば、Linuxの場合、ライセンスファイルを /opt/my_app
に格納します。
ln -s /opt/my_app/license.lic /path/to/obfuscated/pytransform/license.lic
Windowsの場合、ライセンスファイルは C:/Users/Jondy/my_app
に保存されます。
mklink \path\to\obfuscated\pytransform\license.lic C:\Users\Jondy\my_app\license.lic
難読化したパッケージを配布する場合は、インストール後にこの関数を実行すればよいです。
import os
def make_link_to_license_file(package_path, target_license="/opt/mypkg/license.lic"):
license_file = os.path.join(package_path, 'pytransform', 'license.lic')
if os.path.exists(license_file):
os.rename(license_file, target_license)
os.symlink(target_license, license_file)
同じマシンで複数のpyarmorを登録する
v5.9.0から、pyarmorは環境変数 PYARMOR_HOME
からライセンスとカプセルのデータを読み取りますが、デフォルト値は ~/.pyarmor
です。そのため、pyarmorを実行する前に環境変数 PYARMOR_HOME
を別のパスに設定すれば、1つのマシンで複数のpyarmorを登録することが簡単にできます。
また、以下の方法で2つ目のプロジェクト用に新しいコマンドpyarmor2を作成することができます。
Linux では、シェルスクリプト pyarmor2
を作成します。
export PYARMOR_HOME=$HOME/.pyarmor_2
pyarmor "$@"
usr/local/pyarmor2
に保存し、そのモードを変更します。
chmod +x /usr/local/pyarmor2
Windowsの場合、batスクリプト pyarmor2.bat
を作成します。
SET PYARMOR_HOME=%HOME%\another_pyarmor
pyarmor %*
その後、2つ目のプロジェクトでpyarmor2を実行します。
pyarmor2 register pyarmor-regkey-2.zip
pyarmor2 obfuscate foo2.py
難読化された1つのパッケージのライセンス情報を取得する方法
難読化されたパッケージのライセンス情報を取得する方法は? v6.2.5以降では、ランタイムパッケージpytransformのパスで次のスクリプトを実行するだけです。
from pytransform import pyarmor_init, get_license_info
pyarmor_init(is_runtime=1)
licinfo = get_license_info()
print('This obfuscated package is issued by %s' % licinfo['ISSUER'])
print('License information:')
print(licinfo)
スーパーモードによって難読化されたスクリプトには、pytransformパッケージはなく、拡張機能であるpytransformがあります。これは似ていて、よりシンプルです
from pytransform import get_license_info
licinfo = get_license_info()
print('This obfuscated package is issued by %s' % licinfo['ISSUER'])
print('License information:')
print(licinfo)
v6.2.7以降では、この方法でヘルパースクリプトを呼び出すこともできるようになりました。
cd /path/to/obfuscated_package
python -m pyarmor.helper.get_license_info
データファイルの保護方法
これはまだ実験的な機能です。
PyArmorはデータファイルに触れませんが、データファイルをPythonモジュールにラップし、そのデータモジュールを制限モード4で難読化し、難読化したスクリプトからしかインポートできないようにすることは可能です。この方法で、データファイルをPyArmorで保護することができます。
v6.2.7以降では、データファイルからPythonモジュールを作成するヘルパースクリプトが用意されています。
python -m pyarmor.helper.build_data_module data.txt > data.py
次に、このデータモジュールを制限モード4で難読化する。
pyarmor obfuscate --exact --restrict 4 --no-runtime data.py
その後、そのデータファイルを他の難読化されたスクリプトで使用します。例えば、
import data
# Here load the content of data file to memory variable "text"
# And clear it from memory as exiting the context
with data.Safestr() as text:
...
v6.2.7以前は、このヘルパースクリプト build_data_module.py をダウンロードし、直接実行してください。
python build_data_module.py data.txt > data.py
docstringの削除方法
コマンドラインで PYTHONOPTIMIZE=2
を設定することで、難読化されたスクリプトからdocstringを削除することができます。たとえば、
# In linux
PYTHONOPTIMIZE=2 pyarmor obfuscate foo.py
# In Windows
set PYTHONOPTIMIZE=2
pyarmor obfuscate foo.py
スレッドとマルチプロセッシングの制限モードの使用
restrict
モード 3 と 4 でマルチプロセッシングやスレッドを直接使用すると、 保護例外を訴えることがあります。なぜなら、これらのシステムモジュールは難読化されていませんが、 restrict
モジュールの中の関数を呼び出そうとするからです。
解決策の一つは、Thread を拡張して、その run
メソッドをラムダ関数で上書きすることです。たとえば、
from threading import Thread
class PrivateThread(Thread):
def lambda_run(self):
try:
if self._target:
self._target(*self._args, **self._kwargs)
finally:
del self._target, self._args, self._kwargs
run = lambda self : self.lambda_run()
def foo():
print('Hello')
t = PrivateThread(target=foo)
t.start()
システムスレッドを拡張し、自分で run
メソッドを定義している場合は、 run
を lambda_run
にリネームし、 run
ラムダメソッド を追加するだけです。たとえば、
from threading import Thread
class MyThread(Thread):
# def run(self):
def lambda_run(self):
...
# Define a lambda method `run`
run = lambda self : self.lambda_run()
もう一つの解決策は、制限モード 1 のパブリックモジュールを定義し、プレーンなスクリプトにこのパブリックモジュール内の関数を呼び出させることです。
たとえば、次のスクリプトは、パブリックモジュール pub_foo.py
を使用した foo.py
です。
import multiprocessing as mp
import pub_foo
def hello(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()
publicモジュール pub_foo.py
の内容
import foo
def proxy_hello(q):
return foo.hello(q)
foo.py
をモード 3 で、 pub_foo.py
をモード 1 で難読化します。
pyarmor obfuscate --restrict 3 foo.py
# both of options --exact and --no-runtime are required
pyarmor obfuscate --restrict 1 --exact --no-runtime pub_foo.py
3つ目の解決策は、システムモジュールのスレッド化、またはパッケージのマルチプロセシングの一部のモジュールをモード1で難読化することです。呼び出し元が難読化されていることを確認してください。
難読化されたスクリプトでPyInstallerバンドルを再パックする
v6.5.5 以降、PyArmor は難読化されたスクリプトで PyInstaller バンドルを再パックするために使用するヘルパースクリプト repack.py
を提供しています。
まず PyInstaller でスクリプトをパックし、次に PyArmor でスクリプトを難読化し、最後にこのスクリプトを実行して難読化されたスクリプトでバンドルを再パックします。
- PyInstallerでスクリプトをパックし、最終的なバンドルが動作することを確認します。実際のスクリプトでは、他のオプションが必要な場合がありますので、PyInstallerドキュメントを参照してください。このステップで最終的なバンドルが動作しない場合は、PyInstaller issues に問題を報告してください。
# One folder mode
pyinstaller foo.py
# Check it works
dist/foo/foo.exe
# If prefer to one file mode, run this command
pyinstaller --onefile foo.py
# Check it works
dist/foo.exe
スクリプトを obfdist
で難読化し、難読化されたスクリプトが動作することを確認します。実際のスクリプトでは、他のオプションが必要な場合があります。 obfuscate を調べると、より多くの使い方が見つかりますし、buildによってスクリプトが難読化されることもあります。
# Option --package-runtime should be set to 0
pyarmor obfuscate -O obfdist --package-runtime 0 foo.py
# If prefer to super mode, run this command
pyarmor obfuscate -O obfdist --advanced 2 foo.py
# Check it works
python dist/foo.py
最終の実行ファイルを再パックし、PyInstallerと同じPythonインタプリタを使用します。
# If one folder mode
python repack.py -p obfdist dist/foo/foo.exe
# Overwrite the old one
cp foo-obf.exe dist/foo/foo.exe
# If one file mode
python repack.py -p obfdist dist/foo.exe
# Overwrite the old one
cp foo-obf.exe dist/foo.exe
ここで、 foo-obf.exe
はパッチを当てたバンドルです。
obfdist に含まれる難読化されたスクリプトは、PyInstaller バンドル内のスクリプトと同じパスにあることが必要です。オプション -d
は、アーカイブの情報を表示し、アーカイブの構造に従って難読化されたスクリプトを適切な場所にコピーするために使用されます。例えば、このコマンドはアーカイブの情報を表示します。
python repack.py -d -p obfdist dist/foo.exe
難読化されたスクリプトの構造が変更された場合、Pythonで直接メインスクリプトを実行し、まだ動作することを確認ください。
v6.5.5以前は、以下のサイトからrepack.pyをダウンロードしてください。
https://github.com/dashingsoft/pyarmor/raw/master/src/helper/repack.py
v6.5.5以降では、以下の方法で実行してください。
python -m pyarmor.helper.repack -p obfdist dist/foo
難読化されたスクリプトを拡張モジュールにビルドする
難読化されたスクリプトを拡張機能にビルドするためのヘルパースクリプト buildext.py
が pyarmor
のパッケージに含まれています。
- スクリプトを難読化するには、例えば
--no-cross-protection
と--restrict 0
を指定します。
pyarmor obfuscate --no-cross-protection --restrict 0 foo.py
難読化されたスクリプトを拡張機能にビルドします。例えば、
python buildext.py dist/foo.py
オプション -i
を指定すると、難読化されたスクリプトはビルド後に削除されるので、出力パスの dist
はクリーンな状態になります。例えば、
python buildext.py -i dist/
デフォルトではdist内の難読化されたスクリプトのみが扱われますが、サブディレクトリがある場合は、このように全てのスクリプトをリストアップします。
python buildext.py dist/ dist/a/ dist/b/
あるいは、コマンドラインにあるすべてのスクリプトをリストアップします。例えば、
# In Linix
python buildext.py $(find dist/ -name "*.py")
# In Windows
FOR /R dist\ %I IN (*.py) DO python buildext.py %I
__name__ == "__main__"
の場合、拡張機能はこのブロックを無視します。このブロックをメインスクリプトとして実行するには、例えば -e オプションでビルドして実行ファイルを生成してください。
python buildext.py -e dist/foo.py
dist/foo.exe
この実行ファイルは、現在のPython環境で実行する必要があります。それは以下と等しいです。
python dist/foo.py
-h
でより多くの使用法とオプションを表示します。
python buildext.py -h
v6.6.0以前は、buildext.pyを以下からダウンロードしてください。
https://github.com/dashingsoft/pyarmor/raw/master/src/helper/buildext.py
v6.6.0以降では、以下の方法で実行してください。
python -m pyarmor.helper.buildext ...
Windowsの場合、拡張機能のビルドに問題がある場合は、demo.cをdemo.pydにビルドするための簡単なsetup.pyを書いてください。
from distutils.core import setup, Extension
module1 = 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])
Then run it::
python setup.py build_ext
難読化されたパッケージをpipで配布する
ここにシンプルなパッケージがあります。
.
└── mylib
├── mylib
│ ├── __init__.py
│ └── main.py
└── setup.py
まず、 --enable-suffix 1
でユニークなランタイムパッケージを生成します。
cd mylib
pyarmor runtime -O dist/share --enable-suffix 1
そして、このランタイムを用いてパッケージを難読化します。
pyarmor obfuscate --with-runtime @dist/share mylib/__init__.py
次に setup.py
を編集し、必要なランタイムファイルを全てデータファイルとして追加します。例えば、ユニークなパッケージ名が pytransform_vax_xxxxxx
であるとします。
setup(name='mylib',
...
packages=['mylib'],
package_dir={'mylib': 'dist'},
data_files=[('pytransform_vax_xxxxxx', 'dist/share/pytransform_vax_xxxxxx/*')]
...
)
最後にソースパッケージをビルドします。
python setup.py sdist
setup.py
を難読化しないこと。
スーパーモードでは、ランタイムファイルが異なるので、必要に応じて setup.py
を修正してください。
異なるPythonバージョンによる難読化スクリプトの実行
この機能はv6.8.0から導入されました。
一般的に、難読化されたスクリプトは、あるPythonのバージョンでしか実行できません。他のバージョンのPythonで実行するには、まず異なるバージョンのPythonでスクリプトを難読化し、それらを1つのスクリプトにマージする方法があります。
pyarmorのパッケージにはmerge.pyというヘルパースクリプトがあり、難読化された異なるスクリプトを一つにマージするのに使用されます。
ここでは、その基本的な使い方を説明します。
# First obfuscate the scripts by Python 2.7
python2.7 pyarmor.py obfuscate -O py27 foo.py
# Then obfuscate the scripts by Python 3.8
python3.8 pyarmor.py obfuscate -O py38 foo.py
# Finally run this script to merge all of them
python merge.py py38/ py27/
# Look the results
ls merged_dist/
また、スーパーモードにも対応しています。
# First obfuscate the scripts by Python 2.7
python2.7 pyarmor.py obfuscate --advanced 2 -O py27 foo.py
# Then obfuscate the scripts by Python 3.8
python3.8 pyarmor.py obfuscate --advanced 2 -O py38 foo.py
# Finally run this script to merge all of them
python merge.py py38/ py27/
# Look the results
ls merged_dist/
マージされたスクリプトがプロテクションエラーを起こす場合、オプション --no-cross-protection
を使ってスクリプトを難読化するようにしてください。
v6.8.0以前は、merge.pyを以下の場所からダウンロードしてください。
https://github.com/dashingsoft/pyarmor/raw/master/src/helper/merge.py
v6.8.0以降では、以下の方法で実行してください。
python -m pyarmor.helper.merge ...
5.Pyarmored Wheelをビルドする
最近の Python パッケージは pyproject.toml ファイルを含むことができます。これは PEP 518 で初めて導入され、その後 PEP 517、PEP 621、PEP 660 で拡張されました。このファイルにはビルドシステムの要件と情報が含まれており、pip がパッケージをビルドするために使用されます。
v7.2.0以降、pyarmorはPEP 517のバックエンドとして、setuptools.build_meta
に基づいてpyarmored Wheelを構築することができるようになりました。
パッケージ構造の例を示します。
mypkg/
setup.py
pyproject.toml
src/
__init__.py
...
pyproject.toml
はこのような感じです。
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
まず、バックエンドの setuptools.build_meta
が動作することを、以下のコマンドを実行して確認し、wheel をビルドしてください。動作しない場合は、関連するノウハウの pip wheelを学習し、動作するようにしてください。
cd mypkg/
pip wheel .
pyproject.toml
を編集し、ビルドバックエンドを pyarmor.build_meta
に変更します。
[build-system]
requires = ["setuptools", "wheel", "pyarmor>7.2.0"]
build-backend = "pyarmor.build_meta"
同じコマンドでpyarmored wheelを作ります。
cd mypkg/
pip wheel .
また、環境変数 PIP_PYARMOR_OPTIONS
に難読化オプションを追加設定することで、 スーパーモード で Pyarmored Wheel
を構築することも可能です。
cd mypkg/
# In Windows
set PIP_PYARMOR_OPTIONS=--advanced 2
pip wheel .
# In Linux or MacOs
PIP_PYARMOR_OPTIONS="--advanced 2" pip wheel .
v7.2.4以降では、pip configuration pyarmor.advanced
を使用して、 スーパーモード付き pyarmored wheel
を構築することができます。
最初に pip config を実行します。
pip config set pyarmor.advanced 2
次に、ビルドコマンドを実行します。
cd mypkg/
pip wheel .
この方法で、この設定を削除する。
pip config unset pyarmor.advanced
どのように機能するか
pyarmorによって難読化されたPythonスクリプトは、通常のPythonスクリプトと同じで、ダイナミックライブラリや拡張機能が追加されています。つまり、pyarmor.build_meta
は単に、
-
setuptools.build_meta
を呼び出してwheelをビルドします - wheel を解凍します
- 解凍したパスのすべての
.py
ファイルを難読化します - ホイールファイルRECORDにpyarmorランタイムファイルを追加します
- パッチを適用したwheelを再パックする
詳細は pyarmor/build_meta.py の bdist_wheel
という関数を参照してください。
簡単なスクリプトで、基本的な機能を実装しているだけなので、何か問題があれば、手動で行うか、以下の手順でシェルスクリプトを記述してください。
- オリジナルパッケージでwheelをビルド
- このwheelを以下のコマンドで一時的なパスに解凍します。
python3 -m wheel unpack -dest /path/to/temp xxx.whl
-
pyarmor obfuscate
でスクリプトを難読化し、unpackパスのすべての.py
ファイルを難読化されたもので上書きします。 - wheelファイル RECORD にpyarmorランタイムファイルを追加し、アンパックパスで検索します。
- パッチを適用したwheelを再パックする::
python3 -m wheel pack /path/to/temp
この機能に関して、さらなる要求があれば、プルリクエストを歓迎します。
Pyarmored Wheelのビルドはヘルパー機能であり、これ以上のサポートはありません。
バイナリファイルを含むパッケージからwheelをビルドする方法を知らない場合は、難読化されたスクリプトを通常のスクリプトとして使用する方法を参照して、独学で勉強してください。
6.事例紹介
以下にいくつかの例を紹介します。
PyQtアプリケーションの難読化およびパッケージ化
PyQtをベースにしたeasy-hanというツールがあります。主なファイルを以下に示します。
config.json
main.py
ui_main.py
readers/
__init__.py
msexcel.py
tests/
vnev/py36
このツールをPyArmorでパックするために使用されるシェルスクリプトはこちらです。
cd /path/to/src
pyarmor pack --name easy-han \
-e " --hidden-import comtypes --add-data 'config.json;.'" \
-x " --exclude vnev --exclude tests" main.py
cd dist/easy-han
./easy-han
オプション -e
で PyInstaller を実行するための追加オプションを渡し、これらのオプションが PyInstaller で動作することを確認します。
cd /path/to/src
pyinstaller --name easy-han --hidden-import comtypes --add-data 'config.json;.' main.py
cd dist/easy-han
./easy-han
オプション -x
でスクリプトを難読化するための追加オプションを渡すと、tests と vnev のパスに多くの .py
ファイルがありますが、それらすべてを難読化する必要はありません。オプション --exclude
を渡すと、これらのファイルを除外することができ、これらのオプションが obfuscate
コマンドで動作することを確認できます。
cd /path/to/src
pyarmor obfuscate -r --exclude vnev --exclude tests main.py
pack コマンドは自動的にスクリプトを難読化しますので、難読化されたスクリプトをパックすることはしないでください。
オプション --advanced 2
を追加で渡すことで、スーパーモード を有効にし、セキュリティを向上させることができます。たとえば、
pyarmor pack -x " --advanced 2 --exclude tests" foo.py
難読化された Django サイトを Apache と mod_wsgi で実行する
Djangoのシンプルなサイトがあります。
/path/to/mysite/
db.sqlite3
manage.py
mysite/
__init__.py
settings.py
urls.py
wsgi.py
polls/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
urls.py
views.py
最初にすべてのスクリプトを難読化します。
# Create target path
mkdir -p /var/www/obf_site
# Copy all files to target path, because pyarmor don't deal with any data files
cp -a /path/to/mysite/* /var/www/obf_site/
cd /path/to/mysite
# Obfuscating all the scripts in the current path recursively, specify the entry script "wsgi.py"
# The obfuscate scripts will be save to "/var/www/obf_site"
pyarmor obfuscate --src="." -r --output=/var/www/obf_site mysite/wsgi.py
次に、Apacheのサーバー設定ファイルを編集します。
WSGIScriptAlias / /var/www/obf_site/mysite/wsgi.py
WSGIPythonHome /path/to/venv
# The runtime files required by pyarmor are generated in this path
WSGIPythonPath /var/www/obf_site
<Directory /var/www/obf_site/mysite>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
最後にApacheを再起動します。
apachectl restart
7.Projectの活用
Projectとは、難読化されたスクリプトを管理するための、独自の設定ファイルを含むフォルダです。
難読化されたスクリプトをProjectで管理することには、いくつかの利点があります。
- インクリメントビルド、前回のビルドから更新されたスクリプトのみ難読化されます。
- プロジェクト内の難読化されたスクリプトをフィルタリングし、一部のスクリプトを除外することができます。
- 異なるモードでスクリプトを難読化することができます。
- 難読化されたスクリプトをより簡単に管理できます。
Projectによる難読化スクリプトの管理
プロジェクトを作成するには、init コマンドを使用します。
cd examples/pybench
pyarmor init --entry=pybench.py
プロジェクトの設定ファイル .pyarmor_config
が現在のパスに作成されます。または、別のパスにプロジェクトを作成します。
pyarmor init --src=examples/pybench --entry=pybench.py projects/pybench
プロジェクトパス projects/pybench
が作成され、 .pyarmor_config
はそこに保存されます。
プロジェクトの一般的な使い方は、プロジェクトパスの中であらゆることを行うことです。
cd projects/pybench
プロジェクト情報を表示します。
pyarmor info
このプロジェクトに含まれるすべてのスクリプトを build
コマンドで難読化する。
pyarmor build
config コマンドでプロジェクトの設定を変更します。
例えば、 dist
, test
を除外すると、これらのフォルダにある .py
ファイルは難読化されません。
pyarmor config --manifest "include *.py, prune dist, prune test"
データファイルもマニフェストに記載することができ、プロジェクトのビルド時に出力パスにコピーされます。たとえば、以下のようになります。
pyarmor config --manifest "include *.py, include config.json"
pyarmor build
--manifest
を指定すると、プロジェクト・スクリプトを正確に選択することができます。
詳細は、プロジェクト構成ファイル セクションの manifest
属性の説明を参照してください。
強制リビルド:
pyarmor build --force
難読化されたスクリプトの実行:
cd dist
python pybench.py
いくつかのスクリプトを変更した後、再度 ビルド を実行してください。
cd projects/pybench
pyarmor build
異なるモードによるスクリプトの難読化
最初に、 難読化したスクリプトのモード を参照しそれぞれのモードを設定します。
pyarmor config --obf-mod=1 --obf-code=0
その後、新しいモードでスクリプトを難読化する。
pyarmor build -B
子プロジェクトによる特殊なスクリプトの難読化
プロジェクト内のほとんどのスクリプトは制限モード3で難読化されているが、いくつかのスクリプトは制限モード2で難読化する必要があるとします。この場合、子プロジェクトが適切です。
1. 最初に、ソースパスにプロジェクトを作成します。
cd /path/to/src
pyarmor init --entry foo.py
pyarmor config --restrict 3
2. 次に、プロジェクトの設定ファイルをクローンし、 .pyarmor_config-1
という名前の子プロジェクトを作成します。
cp .pyarmor_config .pyarmor_config-1
3. 次に、子プロジェクトに、entryスクリプトなし、制限モード2で、特別なスクリプトを構成します。
pyarmor config --entry "" \
--manifest "include a.py other/path/sa*.py" \
--restrict 2 \
.pyarmor_config-1
4. 最後にプロジェクトと子プロジェクトをビルドします。
pyarmor build -B
pyarmor build --no-runtime -B .pyarmor_config-1
プロジェクト構成ファイル
各プロジェクトには、configure
ファイルがあります。 .pyarmor_config
という名前のjsonファイルで、プロジェクトのパスに格納されています。
- name
プロジェクト名
- title
プロジェクトのタイトル
- src
マニフェスト テンプレート文字列でファイルを照合するためのベースパス。
絶対パス、またはプロジェクト フォルダに基づく相対パス。
- manifest
Python Distutils の MANIFEST.in と同様に、難読化するファイルを文字列で指定します。
global-include *.py
これは、src ツリー内の任意の場所にあるすべての .py ファイルがマッチすることを意味します。
複数のマニフェストテンプレートコマンドはカンマで区切られます。たとえば、global-include *.py, exclude __mainfest__.py, prune test
データファイルもマニフェストで選択することができ、プロジェクトのビルド時に出力パスにコピーされます。
https://docs.python.org/2/distutils/sourcedist.html#commands を参照してください。
- is_package
使用可能な値は、{0, 1, None}です。
1に設定すると、難読化されたスクリプトを保存するための最終的なパスとして、srcのベースネームが出力に追加されますが、ランタイムファイルはパス出力に含まれたままです。
プロジェクトのinit
時に--type
が指定されていない場合、エントリスクリプトが__init__.py
ならば1
、そうでなければNone
が設定されます。
- restrict_mode
使用可能な値は、{0, 1, 2, 3, 4}です。
デフォルトでは、1
が設定されています。
Restrict Mode を参照してください。
- entry
一つまたは複数のエントリースクリプトを含んだ文字列。
プロジェクトをビルドするときに、各エントリに以下のブートストラップコードを挿入します。from pytransform import pyarmor_runtime pyarmor_runtime()
エントリ名はsrcからの相対パス、または絶対パスでファイル名を指定します。
複数のエントリーはカンマで区切られます。main.py, another/main.py, /usr/local/myapp/main.py
manifest がこのエントリーを指定していない場合、エントリーは難読化されていない可能性があることに注意してください。
- output
ビルドの出力を保存するためのパス。プロジェクトのパスからの相対パスです。
- capsule
v5.9.0から削除されました。
プロジェクトカプセルのファイル名。絶対パスでない場合は、プロジェクト・パスからの相対パスとなります。
- obf_code
各コードオブジェクトのバイトコードを難読化する方法です。 Obfuscating Code Mode を参照してください。
- 0
難読化しない- 1 (デフォルト)
デフォルトのアルゴリズムで各コードオブジェクトを難読化する- 2
より複雑なアルゴリズムで各コードオブジェクトを難読化する
- wrap_mode
使用可能な値は、{0, 1, None}です
try...finalブロックでコードオブジェクトをラップするかどうかです。
デフォルトは1
です。 Wrap Mode を参照してください。
- obf_mod
モジュールのコードオブジェクト全体を難読化する方法です。 Obfuscating module Mode を参照してください。
- 0
難読化しない- 1 (デフォルト)
DESアルゴリズムによるバイトコードの難読化を行う
- cross_protection
難読化したスクリプトの動的ライブラリを保護する方法。
- 0
保護しない- 1
デフォルトのテンプレートに保護コードを挿入します。 エントリースクリプトの特殊な取り扱いについて を参照してください。- ファイル名
このファイルからデフォルトテンプレート以外のプロテクトコードのテンプレートを読み込む。
- runtime_path
なし、または任意のパス。
難読化されたスクリプトを実行するとき、ダイナミックライブラリ_pytransform
をどこで見つけるかを指定します。デフォルト値はNone
で、 ランタイムパッケージ 内、またはpytransform.py
と同じパスにあることを意味します。
これは、難読化されたスクリプトをzipファイルにパックするときに便利です。例えば、難読化されたスクリプトをパッケージするためにpy2exe
を使用します。runtime_path
を空の文字列に設定し、 ランタイムファイル をzipファイルの同じパスにコピーすれば、この問題は解決します。
- plugins
なし、または文字列のリスト
難読化されたスクリプトのライセンスタイプを拡張する、複数のプラグインがサポートされています。たとえば、plugins: ["check_ntp_time", "show_license_info"]
プラグインの使用方法については、 プラグインを使用したライセンスタイプの拡張 を参照してください。
- package_runtime
ランタイムファイルの保存方法。
- 0
obufscatedスクリプトと同じパスに保存します。- 1 (デフォルト)
パッケージとしてサブパスpytransformに保存します。
- enable_suffix
v5.8.7での新機能
ランタイムパッケージ(モジュール)とブートストラップコードを生成する方法。
- 0 (デフォルト)
ランタイムパッケージ(モジュール)の名前に接尾辞をつけない。- 1
ランタイムパッケージ(モジュール)の名前にサフィックスがある、例えばpytransform_vax_00001
- platform
v5.9.0での新機能
文字列は、1つまたは複数のプラットフォームを含みます。複数のプラットフォームはカンマで区切られます。
クロスプラットフォーム難読化を行わない場合は、Noneまたは空白にしてください。
- license_file
v5.9.0での新機能
ライセンスファイルを、デフォルトのものとは別に使用する。
デフォルトのものを使用する場合は、None または空白にしてください。
- bootstrap_code
v5.9.0での新機能
難読化されたエントリースクリプトの ブートストラップコード を生成する方法。
- 0
ブートストラップコードをエントリースクリプトに挿入しない- 1 (デフォルト)
ブートストラップ・コードをエントリー・スクリプトに挿入します。スクリプト名が init.py の場合は、先頭のドットで相対インポート、それ以外の場合は絶対インポートにします。- 2
ブートストラップ・コードは、常にエントリー・スクリプトの中で、リーディング・ドットなしの絶対インポートにされます。- 3
ブートストラップ・コードは、常にエントリ・スクリプトの中で先行するドットを持つ相対的なインポートを行う。
8.Manページ
オリジナルページリンクのみです。
https://pyarmor.readthedocs.io/en/latest/man.html
9.難読化されたスクリプトを理解する
グローバルカプセル
HOMEパスの.pyarmor_capsule.zipは、Global Capsuleと呼ばれます。PyArmorは、スクリプトを難読化するとき、または難読化されたスクリプトのライセンスを生成するときに、Global Capsuleからデータを読み取ります。
PyArmorのすべての試用版は、pyarmor obfuscate
コマンドを実行したときに暗黙的に作成される .pyarmor_capsule.zip
を共有しています。これは1024ビットのRSA鍵を使用し、パブリックカプセルと呼ばれます。
購入版では、各ユーザは2048ビットのRSA鍵を使った専用のプライベートカプセルを1つ受け取ります。
通常、このカプセルはビルドマシンにのみ存在し、難読化されたスクリプトでは使用されず、エンドユーザにも配布されることはないはずです。
このカプセルは、難読化されたスクリプトをハックするのに他の人を助けるかもしれません。あなたの個人的なカプセルを他の人に教えないでください。
難読化されたスクリプト
PyArmorによってスクリプトが難読化された後、distフォルダには難読化されたスクリプトを実行するために必要なすべてのファイルが格納されています。
dist/
myscript.py
mymodule.py
pytransform/
__init__.py
_pytransform.so/.dll/.dylib
v6.3以前は、2つのextraファイルがあります。
pytransform.key
license.lic
難読化されたスクリプトは、通常のPythonスクリプトです。モジュールdist/mymodule.pyはこのようなものになります。
__pyarmor__(__name__, __file__, b'\x06\x0f...', 1)
エントリースクリプトのdist/myscript.pyはこのようなものになります。
from pytransform import pyarmor_runtime
pyarmor_runtime()
__pyarmor__(__name__, __file__, b'\x0a\x02...', 1)
超難読スクリプト
もしスクリプトが スーパーモード によって難読化されているなら、それは全く違うものです。ランタイムファイルは1つだけで、それは拡張モジュール pytransform
です。 dist
にはこれらのファイルしかありません。
myscript.py
mymodule.py
pytransform.so or pytransform.dll
難読化されたスクリプトはすべてこのようなものになります。
from pytransform import pyarmor
pyarmor(__name__, __file__, b'\x0a\x02...', 1)
または、拡張子名にサフィックスが含まれます。たとえば、
from pytransform_vax_000001 import pyarmor
pyarmor(__name__, __file__, b'\x0a\x02...', 1)
エントリースクリプト
PyArmorでは、エントリスクリプトは、pythonインタプリタプロセスで最初に実行される、またはインポートされる難読化されたスクリプトのことです。たとえば、1つのpythonパッケージだけが難読化されている場合、init.pyがエントリスクリプトとなります。
ブートストラップコード
Bootstrap Codeというエントリースクリプトの最初の2行です。エントリースクリプトの中だけあります。
from pytransform import pyarmor_runtime
pyarmor_runtime()
難読化されたパッケージの場合、エントリースクリプトは __init__.py
です。ブートストラップコードは、先頭の".
"で関連するインポートをすることができます。
from .pytransform import pyarmor_runtime
pyarmor_runtime()
また、難読化スクリプトとして実行時のパスが指定されている場合は、別の形態があります。
from pytransform import pyarmor_runtime
pyarmor_runtime('/path/to/runtime')
v5.8.7以降では、ランタイムパッケージにサフィックスが付く場合があります。たとえば、
from pytransform_vax_000001 import pyarmor_runtime
pyarmor_runtime(suffix='_vax_000001')
スーパーモード では、エントリースクリプトだけでなく、他の難読化スクリプトにも1行のBootstrap Codeが含まれています。
from pytransform import pyarmor
ランタイムパッケージ
Runtime Packgeという難読化されたスクリプトと同じフォルダにあるpytransformというパッケージです。これは難読化されたスクリプトを実行するのに必要であり、難読化されたスクリプトの唯一の依存関係です。
一般にこのパッケージは難読化されたスクリプトと同じフォルダにありますが、どこに移動してもかまいません。どのPythonパスでも、このパッケージさえあれば、難読化されたスクリプトを通常のスクリプトとして実行することができます。また、同じ Global Capsule によって難読化されたすべてのスクリプトは、このパッケージを共有することができます。
このパッケージには2つのファイルが含まれています。
pytransform/
__init__.py A normal python module
_pytransform.so/.dll/.lib A dynamic library implements core functions
v6.3.0以前は、2つのextraファイルがあります。
pytransform.key Data file
license.lic The license file for obfuscated scripts
v5.7.0以前では、ランタイムパッケージは別の形式のRuntime Filesを持っています。
スーパーモード では、ランタイムパッケージ と ランタイムファイル の両方が拡張モジュールpytransformを参照するようになりました。異なるプラットフォームや異なるPythonのバージョンでは異なる名前を持っています。たとえば、
pytransform.pyd
pytransform.so
pytransform.cpython-38-darwin.so
pytransform.cpython-38-x86_64-linux-gnu.so
ランタイムファイル
1つのパッケージではなく、2つに分かれたファイルになっています。
pytransform.py A normal python module
_pytransform.so/.dll/.lib A dynamic library implements core functions
v6.3.0以前は、2つのextraファイルがあります。
pytransform.key Data file
license.lic The license file for obfuscated scripts
明らかに、ランタイムパッケージの方がランタイムファイルより明確です。
v5.8.7以降、ランタイムパッケージ(モジュール)にはサフィックスが付くことがあります。たとえば、
pytransform_vax_000001/
__init__.py
...
pytransform_vax_000001.py
...
難読化スクリプトのライセンスファイル
特殊なランタイムファイル license.lic
があり、難読化されたスクリプトを実行するために必要です。v6.3.0以降では、ダイナミックライブラリに埋め込むことができます。
Pyarmor obfuscate
を実行すると、デフォルトのライセンスが生成され、どのマシンでも難読化スクリプトを実行できるようになり、有効期限もありません。
難読化されたスクリプトを固定マシンにバインドしたり、難読化されたスクリプトを期限付きにするには、コマンド pyarmor licenses
を使用して新しい license.lic
を生成し、デフォルトのものを上書きしてください。
特殊なランタイムファイル license.lic
があり、難読化されたスクリプトを実行するために必要です。v6.3.0以降では、ダイナミックライブラリに埋め込むことができます。
PyArmorでは、PyArmorのソースパス内にもう1つの license.lic
が存在します。PyArmorを動かすのに必要なもので、me(PyArmor)が発行しています
難読化されたスクリプトを使用する際のポイント
-
難読化されたスクリプトは通常のpythonスクリプトですので、オリジナルのスクリプトを置き換えるためにシームレスに使用することができます。
-
難読化されたスクリプトを実行したりインポートしたりする前に、 ブートストラップコード が実行されなければなリません。
-
ランタイムパッケージ は、 ブートストラップコード が正しく実行できるように、どのPythonパスにも含まれていなければなりません。これはpytransformという名前の通常のPythonパッケージで、Pythonのimportメカニズムによってインポートされるかもしれません。pytransformという名前のパッケージやモジュールがたくさんある場合、難読化されたスクリプトによって正しいパッケージがインポートされることを確認してください。スーパーモード用のランタイムパッケージと非スーパーモード用のランタイムパッケージは全く別物です。
以下の注意点は、スーパーモードでない場合のみ適用されます。
ブートストラップコード は、ctypesによって動的ライブラリ_pytransform.so/.dll/.dylibをロードします。このファイルはプラットフォーム依存で、すべてのビルド済みダイナミック・ライブラリは、 サポート・プラットフォーム のリストにあります。
デフォルトでは、 ブートストラップコード は ランタイムパッケージ の中のダイナミックライブラリ
_pytransform
を検索します。詳細はpytransform._load_library
を確認してください。動的ライブラリ
_pytransform
が ランタイムパッケージ 内にない場合、 ブートストラップコード を変更します。from pytransform import pyarmor_runtime pyarmor_runtime('/path/to/runtime')
multiprocssing.Process
,os.exec
,subprocess.Popen
などで新しい Python インタプリタプロセスを起動する場合、難読化したスクリプトを実行する前に、新しいプロセスで ブートストラップコード が呼び出されることを確認してください。
より詳しい情報は、Pythonスクリプトを難読化する方法 と 難読化されたスクリプトの実行方法 を参照してください。
難読化されたスクリプトの違いについて
Pythonスクリプトが難読化された後に変更されるものがあります。
-
難読化されたスクリプトは、Pythonのメジャー/マイナーバージョンにバインドされます。例えば、Python 3.6で難読化されている場合、Python 3.6で動作する必要があります。Python 3.5では動きません。
-
難読化されたスクリプトはプラットフォームに依存します、サポートプラットフォームはSupport Platformsにリストされています。
-
Pythonインタプリタが
Py_TRACE_REFS
またはPy_DEBUG
でコンパイルされている場合、難読化されたスクリプトを実行する際にクラッシュします。 -
sys.settrace
,sys.setprofile
,threading.settrace
,threading.setprofile
によって設定されたコールバック関数は、難読化されたスクリプトでは無視されます。この機能を利用したモジュールは動作しません。 -
例えばinspectのようなモジュールが、難読化されたスクリプトのバイトコードやコードオブジェクトの属性を見ようとした場合、動作しない可能性があります。
-
難読化されたコードオブジェクトを
cPickle
や3rdパーティのシリアライズツールで渡すと、動作しない可能性があります。 -
難読化されたスクリプトは実行中のフレームを複製するため、
sys._getframe([n])
が異なるフレームを取得する可能性があります。 -
例外が発生した場合、トレースバックの行番号が元のスクリプトと異なる可能性があります(特にこのスクリプトがプラグインスクリプトやクロスプロテクションコードによってパッチされている場合)。
-
難読化されたスクリプトのコードオブジェクトの
__file__
属性は、実際のファイル名ではなく、 になります。そのため、トレースバックでは、ファイル名がと表示されます。
モジュール属性__file__
はfilename
のままであることに注意してください。例えば、foo.pyというスクリプトを難読化し実行すると、
def hello(msg): print(msg) # The output will be 'foo.py' print(__file__) # The output will be '<frozen foo>' print(hello.__file__)
スーパーモードでは、引数がない場合、ビルドイン関数 dirs(), vars() は動作しませんので、この方法で呼び出してください。
dirs() => sorted(locals().keys()) vars() => locals()
なお、dirs(x), vars(x) は x が None でない場合にも動作します。
3rdパーティインタプリターについて
Jythonなどのサードパーティーインターパーターや、Python C/C++の組み込みコードについては、難読化したスクリプトを実行するためには少なくとも以下の条件を満たしている必要があります。
-
Pythonのダイナミックライブラリをロードし、ソース
https://github.com/python/cpython
からビルドし、コアとなるソースコードに手を加えてはいけません。 -
Linuxでは、RTLD_GLOBALが
dlopen
でlibpythonXY.so
をロードするように設定されていなければ、難読化したスクリプトは動作しません。
Boost::python はデフォルトでは libpythonXY.so を RTLD_GLOBAL でロードしないため、難読化されたスクリプトを実行すると "No PyCode_Type found" というエラーが発生します。この問題を解決するには、初期化時に sys.setdlopenflags(os.RTLD_GLOBAL) メソッドを呼び出すようにしてください。
- モジュール
ctypes
が存在し、ctypes.pythonapi._handle
がPythonダイナミックライブラリの実際のハンドルとして設定されている必要があります。
PyArmorはこのハンドルを使っていくつかのPython C APIに問い合わせを行います。
PyPyはCPythonとは全く異なるので、pyarmorとは動作しません。
10.PyArmorの仕組み
foo.py
がPyArmorによって難読化された後、何が起こったか見てみましょう。以下は、出力パスの dist
にあるファイルのリストです。
foo.py
pytransform/
__init__.py
_pytransform.so, or _pytransform.dll in Windows, _pytransform.dylib in MacOS
pytransform.key
license.lic
dist/foo.py
は難読化されたスクリプトで、内容は以下の通りです。
from pytransform import pyarmor_runtime
pyarmor_runtime()
__pyarmor__(__name__, __file__, b'\x06\x0f...')
ランタイムパッケージ と呼ばれる extraフォルダに pytransform
があり、これが難読化されたスクリプトを実行したりインポートするのに必要なものです。このパッケージがPythonのPathにある限り、難読化されたスクリプト dist/foo.py
は通常のPythonスクリプトとして使用することができます。つまり元のPythonスクリプトは難読化されたスクリプトにシームレスに置き換えることができます。
Pythonスクリプトを難読化する方法
PyArmorでPythonスクリプトを難読化するには?
最初に、Pythonスクリプトをコードオブジェクトにコンパイルします。
char *filename = "foo.py";
char *source = read_file( filename );
PyCodeObject *co = Py_CompileString( source, "<frozen foo>", Py_file_input );
次に、コードオブジェクトを次のように変更します。
- バイトコード
co_code
をtry...finally
ブロック内でラップする。
wrap header:
LOAD_GLOBALS N (__armor_enter__) N = length of co_consts
CALL_FUNCTION 0
POP_TOP
SETUP_FINALLY X (jump to wrap footer) X = size of original byte code
changed original byte code:
Increase oparg of each absolute jump instruction by the size of wrap header
Obfuscate original byte code
...
wrap footer:
LOAD_GLOBALS N + 1 (__armor_exit__)
CALL_FUNCTION 0
POP_TOP
END_FINALLY
-
co_consts
に関数名__armor_enter__
,__armor_exit__
を追加する -
co_stacksize
を2倍にする -
co_flags
にCO_OBFUSCAED(0x80000000)
フラグを設定する -
co_const
にあるすべてのコードオブジェクトを再帰的に変更する
次に、再構成されたコードオブジェクトをシリアライズし、定数やリテラル文字列を保護するために難読化します。
char *string_code = marshal.dumps( co );
char *obfuscated_code = obfuscate_algorithm( string_code );
最後に難読化されたスクリプトを生成します。
sprintf( buf, "__pyarmor__(__name__, __file__, b'%s')", obfuscated_code );
save_file( "dist/foo.py", buf );
難読化されたスクリプトは、通常のPythonスクリプトで、次のようなものです。
__pyarmor__(__name__, __file__, b'\x01\x0a...')
プラグインとの付き合い方
PyArmorでは、プラグインを使用して、スクリプトが難読化される前に、Pythonコードを難読化されたスクリプトに挿入します。したがって、難読化されたスクリプトの実行中にプラグインコードを実行できます。たとえば、プラグインを使用してインターネット時間を確認します。
pyarmor obfuscate --plugin check_ntp_time foo.py
なぜプラグインコードを直接スクリプトに挿入しないのでしょうか?
なぜなら、それらのほとんどは難読化されたスクリプトの中で呼び出されなければならないからです。例えば、難読化されたスクリプトのライセンス情報を取得します。
各プラグインは通常のPythonスクリプトであり、PyArmorはこの方法で検索を行います。
- もしプラグインが絶対パスであれば、対応する
.py
ファイルを正確に探します。 - 相対パスの場合は、
.py
ファイルを検索します。
- 現在のパス
- $HOME/.pyarmor/plugins
- {pyarmor_folder}/plugins
- 見つからない場合は例外を発生させる
スクリプトを難読化するプラグインが指定されている場合、各コメント行をスキャンしてプラグインマーカーを探します。
プラグインマーカーには3種類あります。
- Plugin Definition Marker
- Plugin Inline Marker
- Plugin Call Marker
Plugin Definition Markerは、次のようなものです。
# {PyArmor Plugins}
通常、1つのスクリプトの中に1つだけ存在し、すべてのプラグインはここに注入されます。これは1行のコメントでなければならず、インデントしてはいけません。プラグイン定義マーカーがない場合は、どのプラグインも注入されません。
その他は、主にプラグインスクリプトで定義された関数を呼び出すために使用されます。3つの形式があり、この接頭辞を持つコメント行はすべてプラグインマーカーとなります。
# PyArmor Plugin:
# pyarmor_
# @pyarmor_
これらは何度も現れる可能性があり、どのようなインデントでも構いませんが、一般的にはプラグイン定義マーカーの後ろにあるべきです。
Plugin Inline Marker と呼ばれる最初の形式は、PyArmorがこのパターンとそれに続く1つの空白を正確に削除し、残りの部分をそのまま残すだけです。例えば、これらはスクリプトfoo.pyのインラインマーカです。
# PyArmor Plugin: check_ntp_time()
# PyArmor Plugin: print('This is plugin code')
# PyArmor Plugin: if sys.flags.debug:
# PyArmor Plugin: check_something():
dist/foo.py
では、次のように置き換わります。
check_ntp_time()
print('This is plugin code')
if sys.flags.debug:
check_something()
コマンドラインでプラグインが指定されている限り、これらの置き換えが行われます。外部プラグインスクリプトがない場合は、コマンドラインで特別なプラグイン名を使用します。たとえば、
pyarmor obfuscate --plugin on foo.py
2つ目の形式はPlugin call Marker と呼ばれ、プラグインスクリプトで定義された関数を呼び出すためにのみ使用されます。また、この関数名がプラグイン名として指定されていない場合、PyArmorはこのマーカーに触れることはありません。例えば、次のコマンドでスクリプトを難読化します。
pyarmor obfuscate --plugin check_ntp_time foo.py
foo.py
では、関数名 check_multi_mac
としてコマンドラインにプラグイン名が指定されていないため、最初のマーカーだけが処理され、2番目のマーカーはそのまま維持されます。
# pyarmor_check_ntp_time()
# pyarmor_check_multi_mac()
==>
check_ntp_time()
# pyarmor_check_multi_mac()
最後の # @pyarmor_
は、2番目の形式とほぼ同じですが、コメントのプレフィックスが @
に置き換えられ、主にデコレータを注入するために使用されます。たとえば、
# @pyarmor_assert_obfuscated(foo.connect)
def login(user, name):
foo.connect(user, name)
==>
@assert_obfuscated(foo.connect)
def login(user, name):
foo.connect(user, name)
プラグイン名の先頭に @
がある場合、そのプラグインがスクリプト内で使用されているときのみスクリプトに注入され、そうでないときは無視されます。たとえば、
pyarmor obfuscate --plugin @check_ntp_time foo.py
foo.py
スクリプトは、プラグイン関数 check_ntp_time
をPlugin Call Markerで呼び出す必要があります。たとえば、
# pyarmor_check_ntp_time()
Plugin Inline Markerでは動作しません。たとえば、
# PyArmor Plugin: check_ntp_time()
このマーカーさえも check_ntp_time()
に置き換えられますが、プラグインスクリプトは難読化されたスクリプトにインジェクションされることはありません。実行すると、関数 check_ntp_name
が見つからないと文句を言われます。
コマンドラインに --plugin
オプションがない場合、 pyarmor はコメント中のプラグインマーカーを検索しません。外部のプラグインスクリプトがない場合、以下のように特別な名前を使用します。
pyarmor obfuscate --plugin on foo.py
エントリースクリプトの特殊な取り扱いについて
エントリースクリプトには、2つの特別な変更があります。
- 難読化する前に、エントリースクリプトにプロテクションコードを挿入します。
- 難読化した後、難読化したスクリプトにブートストラップコードを挿入します。
エントリスクリプトを難読化する前に、PyArmorはその内容を一行ずつ検索します。もし次のような行があれば、
# {PyArmor Protection Code}
PyArmorはこの行を保護コードに置き換えます。
もしこのような行があれば、
# {No PyArmor Protection Code}
PyArmorはこのスクリプトにパッチを当てません。
もし両方の行が見つからない場合は、その行の前に保護コードを挿入してください。
if __name__ == '__main__'
__main__
行が見つからない場合は、何もしません。
これは、保護コードのデフォルトテンプレートです。
def protect_pytransform():
import pytransform
def check_obfuscated_script():
CO_SIZES = 49, 46, 38, 36
CO_NAMES = set(['pytransform', 'pyarmor_runtime', '__pyarmor__',
'__name__', '__file__'])
co = pytransform.sys._getframe(3).f_code
if not ((set(co.co_names) <= CO_NAMES)
and (len(co.co_code) in CO_SIZES)):
raise RuntimeError('Unexpected obfuscated script')
def check_mod_pytransform():
def _check_co_key(co, v):
return (len(co.co_names), len(co.co_consts), len(co.co_code)) == v
for k, (v1, v2, v3) in {keylist}:
co = getattr(pytransform, k).{code}
if not _check_co_key(co, v1):
raise RuntimeError('unexpected pytransform.py')
if v2:
if not _check_co_key(co.co_consts[1], v2):
raise RuntimeError('unexpected pytransform.py')
if v3:
if not _check_co_key(co.{closure}[0].cell_contents.{code}, v3):
raise RuntimeError('unexpected pytransform.py')
def check_lib_pytransform():
filename = pytransform.os.path.join({rpath}, {filename})
size = {size}
n = size >> 2
with open(filename, 'rb') as f:
buf = f.read(size)
fmt = 'I' * n
checksum = sum(pytransform.struct.unpack(fmt, buf)) & 0xFFFFFFFF
if not checksum == {checksum}:
raise RuntimeError("Unexpected %s" % filename)
try:
check_obfuscated_script()
check_mod_pytransform()
check_lib_pytransform()
except Exception as e:
print("Protection Fault: %s" % e)
pytransform.sys.exit(1)
protect_pytransform()
すべての文字列テンプレート {xxx}
は、PyArmor によって実際の値に置き換えられます。
PyArmor がこの保護コードを挿入するのを防ぐには、スクリプトを難読化する際に --no-cross-protection
を渡してください。
エントリースクリプトが難読化された後、ブートストラップコードが難読化されたスクリプトの先頭に挿入されます。
難読化されたスクリプトの実行方法
難読化されたスクリプト dist/foo.py
を Python Interpreter
で実行するには?
最初の2行は ブートストラップコード と呼ばれています。
from pytransform import pyarmor_runtime
pyarmor_runtime()
これは以下のタスクを実行します。
-
ctpes
によって動的ライブラリ_pytransform
をロードします - license.licが有効かどうかをチェックします
- ビルドインモジュールに3つのcfunctionsを追加します。
pyarmor__
,__armor_enter__
,__armor_exit__
dist/foo.py
の次のコード行です。
__pyarmor__(__name__, __file__, b'\x01\x0a...')
__pyarmor__
が呼ばれると、難読化されたコードから元のモジュールを import
します。
static PyObject *
__pyarmor__(char *name, char *pathname, unsigned char *obfuscated_code)
{
char *string_code = restore_obfuscated_code( obfuscated_code );
PyCodeObject *co = marshal.loads( string_code );
return PyImport_ExecCodeModuleEx( name, co, pathname );
}
その後、このpythonインタプリタのランタイムで処理されます。
- コードオブジェクトが実行されると同時に
__armor_enter__
が呼び出され、このコードオブジェクトのバイトコードが復元されます。
static PyObject *
__armor_enter__(PyObject *self, PyObject *args)
{
// Got code object
PyFrameObject *frame = PyEval_GetFrame();
PyCodeObject *f_code = frame->f_code;
// Increase refcalls of this code object
// Borrow co_names->ob_refcnt as call counter
// Generally it will not increased by Python Interpreter
PyObject *refcalls = f_code->co_names;
refcalls->ob_refcnt ++;
// Restore byte code if it's obfuscated
if (IS_OBFUSCATED(f_code->co_flags)) {
restore_byte_code(f_code->co_code);
clear_obfuscated_flag(f_code);
}
Py_RETURN_NONE;
}
-
__armor_exit__
は、コードオブジェクトの実行が完了する限り呼び出され、再びバイトコードを難読化します。
static PyObject *
__armor_exit__(PyObject *self, PyObject *args)
{
// Got code object
PyFrameObject *frame = PyEval_GetFrame();
PyCodeObject *f_code = frame->f_code;
// Decrease refcalls of this code object
PyObject *refcalls = f_code->co_names;
refcalls->ob_refcnt --;
// Obfuscate byte code only if this code object isn't used by any function
// In multi-threads or recursive call, one code object may be referenced
// by many functions at the same time
if (refcalls->ob_refcnt == 1) {
obfuscate_byte_code(f_code->co_code);
set_obfuscated_flag(f_code);
}
// Clear f_locals in this frame
clear_frame_locals(frame);
Py_RETURN_NONE;
}
難読化されたスクリプトのパック方法
PyArmorによって生成された難読化スクリプトはPythonスクリプトをシームレスに置き換えることができますが、PyInstallerによってそれらを1つのバンドルにパックするときに問題があります。
難読化されたスクリプトのすべての依存関係がまったく検出されない
この問題を解決するために、一般的な解決策は、
- 元のスクリプトの依存関係をすべて見つける
- 難読化されたスクリプトが必要とするランタイムファイルをバンドルに追加する
- バンドル内のオリジナルスクリプトを難読化スクリプトに置き換える
- エントリスクリプトを難読化スクリプトに置き換える
PyArmorはこれを実現するための pack コマンドを提供しています。しかし、場合によってはうまくいかないこともあります。このドキュメントでは、 pack
コマンドが何をするのかを説明し、またあなた自身で難読化スクリプトをバンドルするためのガイドとなります。
まず pyinstaller
をインストールします。
pip install pyinstaller
そして、難読化したスクリプトを dist/obf
におきます。
pyarmor obfuscate --output dist/obf --package-runtime 0 hello.py
次に specfile
を生成し、難読化されたスクリプトが必要とするランタイムファイルを追加します。
pyi-makespec --add-data dist/obf/license.lic:. \
--add-data dist/obf/pytransform.key:. \
--add-data dist/obf/_pytransform.*:. \
-p dist/obf --hidden-import pytransform \
hello.py
スーパーモードによりスクリプトが難読化された場合は、
pyarmor obfuscate --output dist/obf --advanced 2 --package-runtime 0 hello.py
このコマンドで .spec
ファイルを生成します。
pyi-makespec -p dist/obf --hidden-import pytransform hello.py
Windowsの場合、コマンドラインの:は;に置き換えてください。
そして、specfile hello.specにパッチを当てます、Analysisオブジェクトの後に以下の行を挿入してください。この目的は、オリジナルのスクリプトをすべて難読化したものに置き換えることです。
src = os.path.abspath('.')
obf_src = os.path.abspath('dist/obf')
for i in range(len(a.scripts)):
if a.scripts[i][1].startswith(src):
x = a.scripts[i][1].replace(src, obf_src)
if os.path.exists(x):
a.scripts[i] = a.scripts[i][0], x, a.scripts[i][2]
for i in range(len(a.pure)):
if a.pure[i][1].startswith(src):
x = a.pure[i][1].replace(src, obf_src)
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]
パッチを適用した specfile を実行し、最終的なディストリビューションを構築します。
pyinstaller --clean -y hello.spec
オプション --clean
が必要です。そうしないと、キャッシュされた .pyz
が使用されるため、難読化されたスクリプトは置換されません。
難読化されたスクリプトの動作を確認します。
dist/hello/hello.exe
11.ランタイムモジュールpytransform
難読化されたスクリプトがエンドユーザーにとってブラックボックスであることに気づいたなら、あなた自身のPythonスクリプトでもっとできることがあります。このような場合、pytransform
が役に立つでしょう。
pytransform
モジュールは難読化されたスクリプトと一緒に配布され、難読化されたスクリプトを実行する前にインポートする必要があります。また、あなたのPythonスクリプトで使用することもできます。
contents
exception PytransformError |
非推奨です。
これは、pytransform api が失敗したときに発生します。例外の引数は、エラーの原因を示す文字列です。
スーパーモードでは使用できません。
get_expired_days() |
時間制限のあるライセンスがあと何日残っているかを返します。
>0
: この日数で有効
-1
: 有効期限は切れていない
難読化されたスクリプトが期限切れになった場合、例外を発生させて直接終了します。難読化されたスクリプト内のすべてのコードは実行されないので、この関数が 0
を返すことはありません。
get_license_info() |
難読化されたスクリプトのライセンス情報を取得します。
キーを含むdictを返します。
- ISSUER: 発行者ID
- EXPIRED: 期限切れの日付
- IFMAC: このライセンスにバインドされているマックアドレス
- HARDDISK: このライセンスにバインドされているハードディスクのシリアル番号
- IPV4: このライセンスにバインドされているIPV4アドレス
- DATA: このライセンスに保存されている追加データ、ライセンスタイプの拡張に使用されます
- CODE: このライセンスの登録コード
Noneは、ライセンスにこのキーがないことを意味します。
キー ISSUER
は、v6.2.5から導入されました。試用版Pyarmorでlicense.licを生成した場合は trial
になります。購入したpyarmorの場合は、 pyarmor-vax-NNNNN
のように購入したキーになります。v6.0.1以前のpyarmorで生成された license.lic
は、 None
になることに注意してください。
ライセンスが無効な場合(例えば、有効期限が切れているなど)は、 Exception
を発生させます。
get_license_code() |
非スーパーモードでは文字列、スーパーモードではバイトオブジェクトを返します。これは、難読化されたスクリプトのライセンスを生成するための最後の引数です。
ライセンスが無効な場合は、Exception
を発生させます。
get_user_data() |
非スーパーモードの文字列またはスーパーモードのbytesオブジェクトを返します。これは、難読化されたスクリプトのライセンスを生成するものとして -x
で指定されます。
-x
の指定がない場合は None を返します。
ライセンスが無効な場合は、Exception
を発生させます。
get_hd_info(hdtype, name=None) |
hdtype
ごとにハードウェア情報を取得します。 hdtype
は以下のいずれかです。
HT_HARDDISK
は、最初のハードディスクのシリアル番号を返します。
HT_IFMAC
は、最初のネットワークカードの mac アドレスを返します
HT_IPV4h
は、最初のネットワークカードの IPv4 アドレスを返します
HT_DOMAIN
は、ターゲットマシンのドメイン名を返します
何か問題がある場合は Exception を発生させます。
Linuxでは、ネットワークカードやハードディスクの名前を取得するために、name
を使用します。たとえば、
get_hd_info(HT_IFMAC, name="eth2")
get_hd_info(HT_HARDDISK, name="/dev/vda2")
Windowsでは、ネットワークカードやハードディスク(すべて)を取得するために、nameが使用されます。たとえば、
get_hd_info(HT_IFMAC, name="*")
get_hd_info(HT_HARDDISK, name="*")
get_hd_info(HT_HARDDISK, name="/0") # First disk
get_hd_info(HT_HARDDISK, name="/1") # Second disk
v6.5.3での変更点
新しいキーワードパラメータ name
を追加
キーワードパラメータ size
を削除
HT_HARDDISK, HT_IFMAC, HT_IPV4, HT_DOMAIN |
get_hd_info()
を呼び出す際の hdtype
の定数。
assert_armored(*args) |
引数のモジュール/関数/メソッドのリストをチェックするためのデコレータ関数が難読化されています。
モジュールや関数、メソッドをチェックしますが、例えば Class
がサポートしていないような型はチェックできません。関数がビルドインデコレータ、例えば @staticmethod
によってデコレートされている場合、難読化されていないものとして扱われます。
難読化されていないものがあれば、RuntimeErrorを発生させます。
たとえば、
import foo
from pytransform import assert_armored
@assert_armored(foo, foo.connect, foo.connect2)
def start_server():
foo.connect('root', 'root password')
v6.6.2以降、チェックモジュールがサポートされましたが、スーパーモードのみです
check_armored(*args) |
argsに含まれるすべての関数/メソッド/モジュールが難読化されている場合、Trueを返します。
難読化されていないものがあれば、Falseを返します。
モジュール、関数、メソッドをチェックすることができます。 その他の型、例えばClassはサポートしていませんいないものを。関数が、例えば @staticmethod
のようなビルドインデコレーターによってデコレートされている場合、難読化されていないとみなされ、Falseが返されます。
たとえば、
import foo
from pytransform import check_armored
if not check_armored(foo, foo.connect, foo.connect2):
print('My script is hacked')
v6.6.2のスーパーモードのみの新機能です。
Examples
これらのサンプルコードを任意のスクリプト、例えば foo.py
にコピーして難読化し、難読化されたスクリプトを実行してください。
ライセンスの残り日数を表示します。
from pytransform import get_license_info, get_expired_days
try:
code = get_license_info()['CODE']
left_days = get_expired_days()
if left_days == -1:
print('This license for %s is never expired' % code)
else:
print('This license for %s will be expired in %d days' % (code, left_days))
except Exception as e:
print(e)
その他の使用方法については、 プラグインを使用したライセンスタイプの拡張 と プラグインを使ったセキュリティの向上 を参照してください。
難読化されたスクリプトを実行するとき、pytransform.py
は難読化されませんが、PyArmorによって保護されています。もしそれが変更されると、難読化されたスクリプトは保護例外を発生させます。
エントリースクリプトの特殊な取り扱いについて を参照してください。
12.Support Platforms
13.難読化したスクリプトのモード
PyArmorは、セキュリティとパフォーマンスのバランスをとるために、多くのモードでスクリプトを難読化することができます。ほとんどの場合、デフォルトのモードで問題なく動作します。しかし、パフォーマンスがボトルブロックになるような場合、あるいは特殊なケースでは、これらのモードの違いを理解し、異なるモードでスクリプトを難読化し、希望通りに動作させる必要があるかもしれません。
Super Mode
この Super Mode
モードでは、PyCode_Typeの構造が変更され、バイトコードまたはワードコードがマッピングされます。必要なランタイムファイルは pytransform
拡張子のみで、難読化されたスクリプトの形式はユニークで、ユーザーを混乱させる可能性のあるいわゆる ブートストラップコード はありません。難読化されたスクリプトはすべて次のようなものです。
rom pytransform import pyarmor
pyarmor(__name__, __file__, b'\x0a\x02...', 1)
このモードは、適切な場合に有効化することをお勧めします。現在、最新のPythonのバージョンにのみ対応しています。
- Python 2.7
- Python 3.7
- Python 3.8
- Python 3.9
この機能を有効にするには、オプション --advanced 2
をobfuscate に設定します。
pyarmor obfuscate --advanced 2 foo.py
詳しい使い方は、 スーパーモードの使用 をご覧ください。
スーパーモード難読化スクリプトとそうでないものを混在させて場合、動作しません。
Super Plus Mode
これはスーパーモードの拡張で、いくつかの関数をバイナリコードに変換するものです。PyArmor 7.0.1 で導入され、現在では arch X86_64
と Python 3.7, 3.8, 3.9 でのみ動作しています。PyArmor 7.5.0 からは、arch X86_64
の Python 3.10 と、Darwin と Linux の arch AARCH64
の Python 3.7~3.10 が動作するようになりました。
cコンパイラが必要です。LinuxとDarwinでは、gcc
と clang
でOKです。Windowsでは、clang.exe
のみ動作します。以下のいずれかの方法で設定することができます。
- clang.exeがあれば、任意のパスで実行できればOKです
- Windows版LLVMのダウンロードとインストールを行います
-
https://pyarmor.dashingsoft.com/downloads/tools/clang-9.0.zip をダウンロードします。約26Mバイトで、中に入っているファイルは1つだけでです。解凍して
clang.exe
を$HOME/.pyarmor/
に保存します。$HOME
は現在のログオンユーザーのホームパスです。実際のパスは環境変数HOME
で確認してください。
cコンパイラが動作したら、--advanced 5
で Super Plus Mode を有効にします。
pyarmor obfuscate --advanced 5 foo.py
モジュール内の一部の関数だけが spp モードで難読化され、それ以外は super モードで難読化されたままです。sppモードでサポートされていない機能を使用している関数は自動的に無視されます。super plusモードでこのモジュールに何か問題がある場合、モジュールの先頭に1行挿入して手動で無視するようにしてください。
# pyarmor options: no-spp-mode
スーパープラスモードでは、最初の行からスキャンし、空行を無視し、#
で始まる行を解析し、それ以外の行のスキャンを停止します。 pyarmor
のオプションで始まる行があれば、それ以降のオプションを読みます。また、function
や class
などを無視するために、docstringで動作します。たとえば、
def foo(a, b):
'''pyarmor options: no-spp-mode'''
pass
sppモードではいくつかの違いがあります。
- 例外ハンドラにない引数で
raise
を呼び出すと、異なるExceptionが発生します。
>>> raise
RuntimeError: No active exception to reraise
# In spp mode
>>> raise
UnboundlocalError: local variable referenced before assignment
sppモードのサポートされていない機能、
unsupport_nodes = (
ast.Nonlocal,
ast.AsyncFunctionDef, ast.AsyncFor, ast.AsyncWith,
ast.Await, ast.Yield, ast.YieldFrom, ast.GeneratorExp,
ast.NamedExpr,
ast.MatchValue, ast.MatchSingleton, ast.MatchSequence,
ast.MatchMapping, ast.MatchClass, ast.MatchStar,
ast.MatchAs, ast.MatchOr
)
そしてサポートされていない機能、
exec, eval, super, locals, sys._getframe
例えば、以下のような関数は、サポートされていない機能やサポートされていない関数を使用しているため、スーパープラスモードでは難読化されません。
async def nested():
return 42
def foo1():
for n range(10):
yield n
def foo2():
frame = sys._getframe(2)
print('parent frame is', frame)
体験版では、スーパープラスモードはご利用いただけません。
Advanced Mode
このアドバンスドモードという機能は、PyArmor 5.5.0から導入されました。このモードでは、セキュリティを向上させるためにPyCode_Typeの構造が少し変更されます。また、変更されたコードオブジェクトが正常に実行できるように、Pythonインタープリタにフックが注入されます。
また、Python CのコアAPIが予期せず変更された場合、アドバンストモードの難読化スクリプトは動作しなくなります。この機能はマシンの命令セットに大きく依存するため、現在は x86/x64 アーキでのみ利用可能です。 また、Pythoninterpreterが古いgccや他のCコンパイルでコンパイルされている場合、pyarmorは間違いを起こす可能性があります。もし、Python インタープリタが advanced モードで動作しない場合、その問題を報告していただければ幸いです。
このことを考慮し、デフォルトではアドバンスドモードは無効になっています。有効にするには、obfuscate コマンドにオプション --advanced
を渡します。
pyarmor obfuscate --advanced 1 foo.py
アップグレードの注意点
アップグレード前に、製品環境のPythonインタプリタを試用し、アドバンスドモードで動作することを確認してください。以下はそこの手順があります。
https://github.com/dashingsoft/pyarmor-core/tree/v5.3.0/tests/advanced_mode/README.md
次のマイナーバージョンでアップグレードすることをお勧めします。
試用版では、このモジュール内に約30以上の function がある場合、アドバンスドモードでは難読化できません(アドバンスドモード以外では難読化可能です)。
Python3.9では、advanced modeはサポートされていません。super modeが動作するPythonのバージョンでは、super modeを使用することが推奨されます。
VM Mode
VMモードは6.3.3から導入されました。VMモードはコードの仮想化に基づいており、強力なvmツールを使ってダイナミックライブラリのコアアルゴリズムを保護します。このモードはアドバンスドモードとスーパーモードを強化したものです。
アドバンスドモードと一緒にVMモードを有効にするには、このようにします。
pyarmor obfuscate --advanced 3 foo.py
この方法で、スーパーモードでvmモードを有効にします。
pyarmor obfuscate --advanced 4 foo.py
vmモードでは、セキュリティは格段に向上しますが、ダイナミックライブラリのサイズが大きくなり、パフォーマンスが低下します。オリジナルのサイズは600K〜800K程度のもので、vmモードでは4M程度になります。性能については、難読化されたスクリプトのパフォーマンス を参照してテストしてください。
Obfuscating Code Mode
Python モジュールファイルでは、一般に多くの関数があり、各関数はコードオブジェクトを持っています。
-
obf_code == 0
各関数のコードオブジェクトは、そのまま維持されます。 -
obf_code == 1 (デフォルト)
この場合、各関数のコード・オブジェクトはラップ・モードに応じて異なる方法で難読化されます。 -
obf_code == 2
obf_mode 1 とほぼ同じですが,より複雑なアルゴリズムでバイトコードを難読化するため,前者より遅くなります。
Wrap Mode
スーパーモードの場合はラップモードは常に有効になり、スーパーモードでは無効にすることはできません。
- wrap_mode == 0
ラップモードがオフの場合、各関数のコードオブジェクトはこのような形で難読化されます。
0 JUMP_ABSOLUTE n = 3 + len(bytecode)
3 ...
... Here it's obfuscated bytecode of original function
...
n LOAD_GLOBAL ? (__armor__)
n+3 CALL_FUNCTION 0
n+6 POP_TOP
n+7 JUMP_ABSOLUTE 0
このコードオブジェクトが最初に呼ばれたとき
- 最初の命令は JUMP_ABSOLUTE で、これはオフセット n にジャンプします。
- オフセットnで、PyCFunction __armor__を呼び出すように指示されます。この関数は、オフセット3からnまでの難読化されたバイトコードを復元し、元のバイトコードをオフセット0に移動させます。
- 関数呼び出しの後、最後の命令はオフセット0にジャンプすることです。 これで本当のバイトコードが実行されます。
最初の呼び出しの後、この関数は元の関数と同じになります。
- wrap_mode == 1 (デフォルト)
ラップモードがオンの場合,各関数のコードオブジェクトはtry...finally
ブロックによってラップされます.
LOAD_GLOBALS N (__armor_enter__) N = length of co_consts
CALL_FUNCTION 0
POP_TOP
SETUP_FINALLY X (jump to wrap footer) X = size of original byte code
Here it's obfuscated bytecode of original function
LOAD_GLOBALS N + 1 (__armor_exit__)
CALL_FUNCTION 0
POP_TOP
END_FINALLY
このコードオブジェクトが毎回呼び出されるとき、
-
__armor_enter__
が難読化されたバイトコードを復元する - 本当の関数コードを実行する
- 最後のブロックでは、
__armor_exit__
が再びバイトコードを難読化します。
Obfuscating module Mode
- obf_mod == 1
最終的に難読化されたスクリプトは次のようになります。
__pyarmor__(__name__, __file__, b'\x02\x0a...', 1)
第3パラメータは、Pythonスクリプトのコードオブジェクトをシリアライズしたものです。このように生成されます。
PyObject *co = Py_CompileString( source, filename, Py_file_input );
obfuscate_each_function_in_module( co, obf_mode );
char *original_code = marshal.dumps( co );
char *obfuscated_code = obfuscate_whole_module( original_code );
sprintf( buffer, "__pyarmor__(__name__, __file__, b'%s', 1)", obfuscated_code );
-
obf_mod == 2 (デフォルト)
異なる暗号化アルゴリズムを使用し、セキュリティを強化し、より高速にします。v6.3.0以降の新機能です。 -
obf_mod == 0
このモードでは、シリアル化されたモジュールをそのまま維持するための最後のステートメントは次のようになります。
sprintf( buffer, "__pyarmor__(__name__, __file__, b'%s', 0)", original_code );
そして、最終的な難読化されたスクリプトは、次のようになります。
__pyarmor__(__name__, __file__, b'\x02\x0a...', 0)
これらのモードはすべて、現時点ではプロジェクトでのみ変更できます。 異なるモードによるスクリプトの難読化 を参照してください。
Restrict Mode
難読化されたスクリプトは、それぞれ独自の制限モードを持っており、このスクリプトの使用を制限するために使用されます。難読化されたモジュールをインポートして、関数や属性を使用する場合、最初に制限モードがチェックされ、制限モードに違反した場合、保護例外が発生します。
5つの制限モードがあり、モード2と3はスタンドアロンのスクリプトのみ、モード4は主に難読化されたパッケージ、モード5はその両方に適用されます。
- モード1
このモードでは、難読化されたスクリプトを全く変更することができません。例えば、難読化されたスクリプトfoo.py
の末尾にprint文を1つ追加してください。
__pyarmor__(__name__, __file__, b'...', 1)
print('This is obfuscated module')
このスクリプトはインポート時に restrict exception
を発生させます。
- モード2
このモードでは、難読化されたスクリプトはプレーン・スクリプトからインポートすることができず、メイン・スクリプトは エントリースクリプト として難読化されなければなりません。このスクリプトはPythonインタープリタから直接実行されるか、他の難読化されたスクリプトからインポートされる可能性があります。インポートされるとき、呼び出し元とメインスクリプトをチェックし、両方が難読化されていることを確認します。
例えば、foo2.pyはモード2によって難読化されています。このように実行することができます。
python foo2.py
しかし、任意のプレーンスクリプトからそれをインポートしてみてください。たとえば、
python -c'import foo2'
protection exceptionが発生します。
-
モード3
モード2の強化版で、モジュールの属性も保護します。モジュール属性にアクセスするとき、あるいはモジュール関数を呼び出すとき、呼び出し元をチェックし、呼び出し元が難読化されていない場合は保護例外を発生させます。 -
モード4
モード3とほぼ同じですが、唯一の違いは、インポート時にメインスクリプトが難読化されているかどうかをチェックしないことです。
主にPythonのパッケージを難読化するのに使われます。一般的な方法は、 __init__.py
を restrict mode 1 で難読化し、このパッケージの他のすべてのモジュールを restrict mode 4 で難読化する、というものです。
例えば、mypkg
というパッケージがあるとします。
mypkg/
__init__.py
private_a.py
private_b.py
__init__.py
で、プレーンスクリプトが使用するパブリック関数とアトリビュートを定義します。
from . import private_a as ma
from . import private_b as mb
public_data = 'welcome'
def proxy_hello():
print('Call private hello')
ma.hello()
def public_hello():
print('This is public hello')
private_a.py
で、プライベート関数とアトリビュートを定義します。
import sys
password = 'xxxxxx'
def hello():
print('password is: %s' % password)
そして、__init__.py
を モード1 で、その他を モード4 で難読化し、 dist
に格納してください。
dist/
__init__.py
private_a.py
private_b.py
では、Pythonインタプリタからいくつかテストしてみましょう。
import dist as mypkg
# It works
mypkg.public_hello()
mypkg.proxy_hello()
print(mypkg.public_data)
print(mypkg.ma)
# It doesn't work
mypkg.ma.hello()
print(mypkg.ma.password)
-
モード5(v6.4.0での新機能)
モード 5 は、モード 4 の強化版で、フレーム内のグローバルも保護します。モード5で関数を実行すると、外側のプレーン・スクリプトはこの関数のグローバルから何も得ることができません。このモードは、スタンドアロン・スクリプトとパッケージの両方で使用できる最高のセキュリティです。ただし、実行時に各グローバル変数をチェックするため、パフォーマンスが低下する可能性があります。 -
モード100+ (v6.7.4での新機能)
このモードは、モード1〜5を拡張し、モジュール属性__dict__
の制限を有効にしたものです。つまり、モード101はモード1にこの機能を加えたもの、モード102はモード2にこの機能を加えたものに相当します。
この機能を有効にすると、モジュール属性 __dict__
は空の辞書のように見えます。そして、このモジュールのために変更されたことがあります。
-
module.__dict__
にあるすべてのオブジェクトは、このモジュールのクリーンアップ時にクリーンアップされません。 - このモジュールがインポートされた後、
module.__dict__
は、暗黙的にも明示的にも、項目の挿入や削除ができなくなります。 -
dirs
,vars
の関数も空文字を返します。
たとえば、
# This is foo6.py, obfuscated with mode 105
# It's OK
global var_a
var_a = 'This is global variable a'
var_b = 'This is global variable b'
del var_a
def fabico():
global var_b
# Wrong, remove item from module.__dict__ not in modul level
del var_b
# This is foo.py, obfuscated with mode 101
import foo6
# The result is {}
foo6.__dict__
# The output is {}
vars(foo6)
# The output is []
dirs(foo6)
# OK
foo6.var_a = 'Changed by foo'
# Wrong, add new item to __dict__
foo6.__dict__['var_c'] = 1
foo6.var_d = 2
この機能はPython 3.7以降で動作し、それ以前のバージョンのPythonでは効果がありません。
モード3、4のモジュール属性の保護はv6.3.7で導入されました。それ以前は、関数の呼び出しのみが保護されます。
モジュール属性のみが保護されるため、public __init__.py
で private
モジュールから関数やクラスをインポートしないでください。
# Right, import module only
from . import private_a as ma
# Wrong, function `hello` is opened for plain script
from .private_a import hello
モード2と3はPythonパッケージの難読化に使用できません。なぜなら、メインスクリプトも難読化されていなければならず、そうでなければインポートされないからです。
Restrict modeは1つのスクリプトに適用され、異なるスクリプトは異なるRestrict modeによって難読化される可能性があります。
もし、 --obf-code=0
で難読化されているスクリプトがあれば、それはプレーンなスクリプトとして扱われます。
例えば、1つのパッケージに3つのスクリプトがあるとします。
-
__init__.py
: [ restrict_mode : 1, obf-code 2 ] -
foo.py
: [restrict_mode : 4, obf-code 2] -
bar.py
: [restrict_mode : 1, obf-code 0]
ここで、bar.py
は obf-code=0
のため、実行時にプレーンなスクリプトとして表示されます。
したがって、foo.py
は、プレーンスクリプトのように見えるので、 bar.py
の内部でインポートすることができません。しかし、foo.py
は __init__.py
の中で obf-code=2
を持っているので、インポートすることができます。
PyArmor 5.2からは、Restrict Mode 1がデフォルトになりました。
他のrestrict modeによるスクリプトの難読化、
pyarmor obfuscate --restrict=2 foo.py
pyarmor obfuscate --restrict=4 foo.py
# For project
pyarmor config --restrict=2
pyarmor build -B
必要であれば、この方法で上記の制限をすべて無効にすることができます。
pyarmor obfuscate --restrict=0 foo.py
# For project
pyarmor config --restrict=0
pyarmor build -B
スクリプトが licenses によって生成されたライセンスを使用する場合、すべての制限を無効にするには、licenses コマンドにオプション --disable-restrict-mode
を渡します。例えば、以下のようになります。
pyarmor licenses --disable-restrict-mode r001
pyarmor obfuscate --with-license=licenses/r001/license.lic foo.py
# For project
pyarmor config --with-license=licenses/r001/license.lic
pyarmor build -B
より詳細な例については、 制限モードによるセキュリティの向上 を参照してください。
PyArmor 5.7.0から、難読化されたスクリプトに別の暗黙の制限があります。 ブートストラップコード は、難読化されたスクリプトに含まれている必要があり、エントリスクリプトとして指定されている必要があります。。例えば、同じフォルダに foo.py
と test.py
という2つのスクリプトがあり、このコマンドで難読化されるとします。
pyarmor obfuscate foo.py
難読化されたスクリプト dist/test.py
にブートストラップコードを手動で挿入しても、エントリスクリプトとして指定されていないため、うまくいきません。 ブートストラップコード を挿入するためには、このコマンドを実行する必要があります。
pyarmor obfuscate --no-runtime --exact test.py
もし、 ブートストラップコード をプレーン・スクリプトに挿入する必要がある場合は、まず、次のように空のスクリプトを難読化します。
echo "" > pytransform_bootstrap.py
pyarmor obfuscate --no-runtime --exact pytransform_bootstrap.py
そして、プレーンスクリプトでpytransform_bootstrapをインポートしてください。
14.難読化されたスクリプトのパフォーマンス
難読化されたスクリプトのパフォーマンスを確認するために、benchmark コマンドを実行します。
pyarmor benchmark
以下は出力例です。
INFO PyArmor Trial Version 6.3.0
INFO Python version: 3.7
INFO Start benchmark test ...
INFO Obfuscate module mode: 1
INFO Obfuscate code mode: 1
INFO Obfuscate wrap mode: 1
INFO Obfuscate advanced mode: 0
INFO Benchmark bootstrap ...
INFO Benchmark bootstrap OK.
INFO Run benchmark test ...
Test script: bfoo.py
Obfuscated script: obfoo.py
--------------------------------------
import_first_no_obfuscated_module : 6.177000 ms
import_first_obfuscated_module : 15.107000 ms
re_import_no_obfuscated_module : 0.004000 ms
re_import_obfuscated_module : 0.005000 ms
--- Import 10 modules ---
import_many_no_obfuscated_modules : 58.882000 ms
import_many_obfuscated_modules : 50.592000 ms
run_empty_no_obfuscated_code_object : 0.004000 ms
run_empty_obfuscated_code_object : 0.003000 ms
run_no_obfuscated_1k_bytecode : 0.010000 ms
run_obfuscated_1k_bytecode : 0.027000 ms
run_no_obfuscated_10k_bytecode : 0.053000 ms
run_obfuscated_10k_bytecode : 0.119000 ms
call_1000_no_obfuscated_1k_bytecode : 2.411000 ms
call_1000_obfuscated_1k_bytecode : 3.735000 ms
call_1000_no_obfuscated_10k_bytecode : 32.067000 ms
call_1000_obfuscated_10k_bytecode : 42.164000 ms
call_10000_no_obfuscated_1k_bytecode : 22.387000 ms
call_10000_obfuscated_1k_bytecode : 36.666000 ms
call_10000_no_obfuscated_10k_bytecode : 307.478000 ms
call_10000_obfuscated_10k_bytecode : 407.585000 ms
--------------------------------------
INFO Remove test path: ./.benchtest
INFO Finish benchmark test.
2つの関数を含む単純なスクリプトbfoo.pyを使用します。
bfoo.pyというシンプルなスクリプトを使い、以下の2つの関数を含んでいます。
- one_thousand: バイトコードの大きさは約1kです
- ten_thousand: 10k程度のバイトコード
import_first_obfuscated_module
の処理時間には、ダイナミックライブラリの初期化時間やライセンスチェックの時間などが含まれており、通常のスクリプトよりも多くの時間を消費しています。
ただし、スクリプトを約10個の新しいファイルにコピーして新しい名前でインポートするimport_man y_obfuscated_modules
は、通常のスクリプトよりも早く、難読化されたスクリプトがコンパイルされているため、コンパイル時間が節約されます。
残りのテスト、例えば call_1000_no_obfuscated_1k_bytecode
は、関数 one_thousand
を1000回呼び出すことを表しています。call_1000_obfuscated_1k_bytecode
の結果を比較し、難読化スクリプトの性能を知ることができます。結果はテストスクリプト、Pythonのバージョン、難読化モードなどに依存することに注意してください。
利用可能なオプションをすべてリストアップします。
pyarmor benchmark -h
他のオプションを指定して、異なるモードでのパフォーマンスを確認することができます。たとえば、
pyarmor benchmark --wrap-mode 0 --obf-code 2
ベンチマークテストの実行に使用されるスクリプトを見ます。
pyarmor benchmark --debug
使用したファイルはすべて .benchtest
フォルダに保存されます。
異なるモードでの性能
obf-mod
では、2の方がより安全で高速なので、v6.3.0で導入されて以来、デフォルト値となっています。
obf-code
では、2は1より安全性が高く、1より若干遅いです。
wrap-mode
は、0だと各関数が1回ずつ復元されますが、1だと呼び出した数だけ復元されます。このため、特に何度も呼び出される関数の場合は、後者の方が遅くなります。
アドバンストモード と スーパーモード は、ほぼ同じ性能です。しかし、 VMモード はコア機能が仮想化されているため、パフォーマンスが低下します。
難読化したスクリプトでcProfileを実行する
難読化されたスクリプトは、例えばcProfileやprofileで動作させることができます。たとえば、
pyarmor obfuscate foo.py
python -m cProfile dist/foo.py
難読化されたスクリプトの中には,cProfileやprofileの下で例外を発生させるものがあります.
cProfile.py
や profile.py
のソーススクリプトにパッチを当ててください。
古いバージョンの pyarmor では cProfile や profile が動作しないことがあります。この場合は pyarmor をアップグレードして、再度スクリプトを難読化することを試してください。
ビッグスクリプトの性能
MacOS 10.14, 8G RAM, Python 3.7でビッグスクリプトをテストする例です。
テストスクリプト big.py
のサイズは約81Mで、異なる名前で多くの同じ関数が定義されています。
def fib0(n): # return Fibonacci series up to n
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a+b
return result
def fib1(n): # return Fibonacci series up to n
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a+b
return result
def fib2(n): # return Fibonacci series up to n
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a+b
return result
...
メインスクリプト main.py
import sys
import time
def metricmethod(func):
if not hasattr(time, 'process_time'):
time.process_time = time.clock
def wrap(*args, **kwargs):
t1 = time.process_time()
result = func(*args, **kwargs)
t2 = time.process_time()
print('%-50s: %10.6f ms' % (func.__name__, (t2 - t1) * 1000))
return result
return wrap
@metricmethod
def import_big_module(name):
return __import__(name)
@metricmethod
def call_module_function(m):
m.fib2(20)
name = sys.argv[1] if len(sys.argv) > 1 else 'big'
call_module_function(import_big_module(name))
さまざまなケースで python3main.py
を実行します
__pycache__なし、難読化なし、
import_big_module : 52905.399000 ms
call_module_function : 0.020000 ms
__pycache__
があれば、とても速いです。
import_big_module : 2065.303000 ms
call_module_function : 0.011000 ms
次に大きなスクリプトを難読化します。
pyarmor obfuscate big.py
そして、そのテスト結果、
import_big_module : 8690.256000 ms
call_module_function : 0.015000 ms
15.PyArmorのセキュリティ
PyArmorは、Pythonモジュールを2つのレベルで難読化します。まず、モジュール内の各関数を難読化し、次にモジュールファイル全体を難読化します。例えば、foo.pyというファイルがあるとします。
def hello():
print('Hello world!')
def sum(a, b):
return a + b
if __name == '__main__':
hello()
print('1 + 1 = %d' % sum(1, 1))
PyArmorはまず関数 hello
と sum
を難読化し、次に `foo モジュール全体を難読化します。実行時には、現在呼び出されている関数だけが復元され、コード・オブジェクトの実行が完了すると同時に難読化されます。従って、任意のC言語デバッガでコードをトレースしても、一度に得られるのはコード・オブジェクトの一部分だけです。
pytransformのためのクロスプロテクション
PyArmorのコアとなる関数は、動的ライブラリ _pytransform
の中で c
によって書かれています。_pytransform
はJITテクニカルによって自身を保護し、難読化されたスクリプトは _pytransform
によって保護されます。一方、難読化されたスクリプトでは、動的ライブラリ _pytransform
が変更されていないことを確認します。これは Cross Protection と呼ばれます。
動的ライブラリ _pytransform.so
はJITテクニカルを使用して、2つのタスクを実現しています。
- python スクリプトを暗号化するのに使われる
des
キーを、あらゆるc
デバッガがトレースできないようにします。 - コードセグメントはそれ以上変更できないようにします。例えば、
JZ
命令をJNZ
に変更し、ライセンスのチェックに失敗しても_pytransform.so
が実行できるようにします。
JITはどのように動作するのですか?
まず、PyArmorはGNU lightningに基づいた命令セットを定義します。
そして、この命令セットを使って、以下のようなコア関数をcファイルに記述します。
t_instruction protect_set_key_iv = {
// function 1
0x80001,
0x50020,
...
// function 2
0x80001,
0xA0F80,
...
}
t_instruction protect_decrypt_buffer = {
// function 1
0x80021,
0x52029,
...
// function 2
0x80001,
0xC0901,
...
}
_pytransform.so
をビルドし、 _pytransform.so
のコードセグメントのcodesumを計算する。
関連する命令を前に取得した実際のコードサムに置き換え、cファイルの "function 1 "以外のすべての命令を難読化します。更新されたファイルは次のようなものです。
t_instruction protect_set_key_iv = {
// plain function 1
0x80001,
0x50020,
...
// obfuscated function 2
0xXXXXX,
0xXXXXX,
...
}
t_instruction protect_decrypt_buffer = {
// plain function 1
0x80021,
0x52029,
...
// obfuscated function 2
0xXXXXX,
0xXXXXX,
...
}
最後に、この変更されたcファイルを使って _pytransform.so
をビルドしてください。
難読化されたスクリプトを実行すると、_pytransform.so がロードされます。保護された関数が呼び出されると、次のようになります。
- 関数1からコードを生成します
- 関数1を実行します
・コードセグメントのコードサムをチェックし、期待はずれであれば終了する
・tickcountをチェックし、長すぎる場合は、終了します
・デバッガがあるかチェックし、見つかったら終了する
・可能であればハードウェアブレークポイントをクリア
・次の関数である関数2を復元する - 関数2からコードを生成します
- 関数2を実行し、関数1と同じことをします
何回か繰り返した後、本当のコードが呼び出されます。これらはすべて、保護コードにブレークポイントがないことを確認するためです。
Pythonスクリプトで_pytransformを保護するために、いくつかの追加コードがエントリスクリプトに挿入されます。 エントリースクリプトの特殊な取り扱いについて を参照してください。
異なる機能番号のセキュリティ
各プラットフォームには、異なる機能を持つ複数のダイナミックライブラリが存在する場合があります。プラットフォーム名に機能番号のサフィックスを付けて一意な名前にします。例えば、linux.x86_64.21は機能番号21、windows.x86.0は機能番号0となります。
feature 21とfeature 25のライブラリは、強力なvmツールと多くのアンチデバッグ技術によって保護されており、安全です。
Feature 0は何も保護されていないので、第三者のツールで保護するのがよいでしょう。
その他の機能については、単純なvmといくつかのアンチデバッグ技術で保護されていますが、十分な強度ではないので、サードツールで保護することをお勧めします。
コア・アルゴリズムの変更
PyArmorは時々コアアルゴリズムを変更することがあり、新しいバージョンで難読化されたスクリプトは以前のものと全く異なる可能性があります。
16.うまくいかないとき
pyarmorを使用するには、いくつかの必要な知識や技術が必要です。このリストを確認し、あなたがそれらを知っていること、そしてあなたの質問がそれらに関連するものでないことを確認してください。
必要な知識
Shell
pyarmorはコマンドラインツールなので、シェルまたはターミナルで実行する必要があります。シェルコマンドの知識がない場合は、代わりに pyarmor-webui を使用してください。
pyarmorがコマンドを実行すると、引数エラーや不明なオプションがあると文句を言われます。 -h
オプションで利用可能なオプションをすべてリストアップし、これらのヒントでコマンドの構文エラーを修正してください。たとえば、
pyarmor obfuscate -h
Python
Pythonの実行方法 https://docs.python.org/3.8/tutorial/interpreter.html#using-the-python-interpreter
Source Code Encoding
難読化されたスクリプトが予期せぬ出力をする場合、次のことを学ぶ必要があります。
https://docs.python.org/3.8/tutorial/interpreter.html#source-code-encoding
次に、スクリプトに正しいソースコードエンコーディングを設定し、最初にプレーンスクリプトを実行してすべてがうまくいっていることを確認し、それからスクリプトを再度難読化してください。
Python Import System
難読化されたスクリプトを実行するには、追加の ランタイムパッケージ が必要です。これは一般的なPythonパッケージであり、通常のPythonモジュールやパッケージとしてインポートすることができます。このパッケージが正しくインポートされない場合、例えば難読化スクリプトと一緒に配布されていなかったり、間違った場所に格納されていたりすると、難読化スクリプトは以下のような例外を発生させる可能性があります。
ModuleNotFoundError: No module named 'app.pytransform'
これはPyArmorのエラーではなく、Pythonがそれを見つけられないだけです。この場合、Pythonのモジュールやパッケージのインポート方法、絶対インポートと相対インポート、 sys.path
が何であるかを知っておく必要があります。
https://docs.python.org/3.8/library/sys.html#sys.path
難読化されたスクリプトは非常にシンプルなPythonスクリプトで、最初の行はimport文、2行目は関数呼び出しとなっています。importやno module foundのエラーが発生した場合、たとえば、
ImportError: No module named model.NukepediaDB
一般的なPythonスクリプトと同じように考えて、Python Import Systemに従ってモジュール、パッケージ、または拡張ファイルが正しい場所にあるかどうかをチェックします。そうでない場合は、モジュール、パッケージ、拡張機能ファイルを正しいパスに移動してください。
PythonのImport Systemについては、以下の公式ドキュメントや検索エンジンを参照してください。
https://docs.python.org/3.8/reference/simple_stmts.html#the-import-statement
PyInstaller
難読化されたスクリプトを1つの実行ファイルにまとめたい場合で、そしてプロジェクトの構造が複雑な場合は、 PyInstaller を知っている必要がありますが、直接 PyInstaller でプロジェクトをまとめることができます。
https://pyinstaller.readthedocs.io/en/stable/usage.html
一般的な解決策
私は多くの問題を受け取っています。それらのほとんどはpyarmorの欠陥ではなく、pyarmorを間違った方法で使用しています。 したがって、pyarmorで困ったことがある場合は、pyarmorを理解するために数時間を費やすと、問題がすぐに解決する可能性があります。 自助は他人からの助けよりも優れています、それはまた私たちの両方のための時間を節約することができます。
まず、基本ガイドの PyArmorの使用 を読んでいることを確認してください。
難読化されたスクリプトを理解する、特に 難読化されたスクリプトの違いについて のセクションに目を通してください。
特殊なケースでpyarmorをどう使えばいいかわからない場合は、 アドバンストピックス の 目次に目を通してください。
以下は、いくつかの一般的な解決策です。
-
pyarmorを最新の安定版にアップグレードします。アップグレードする前に 変更履歴 を確認してください。pyarmorが以前はうまく動作していて現在は動作しない場合、 クリーンアンインストール を行い、pyarmorを再インストールして、リフレッシュした状態からすべてを起動します。
-
pyarmorによるスクリプトの難読化については、最後のエラーメッセージだけでなく、各ログを注意深くチェックして、
pyarmor
が何をしているかを理解すると、問題を見つけるのに非常に役に立ちます。また、一般的なオプションである-d
を使って、より多くの情報を取得するようにします。たとえば、
pyarmor -d obfuscate --recursive foo.py
難読化されたスクリプトを実行する際、Pythonのデバッグオプション -d
をオンにすると、より詳細な情報が表示されます。トレースバックの中に行番号とスクリプト名があれば、その行の周辺のソーススクリプトを確認してください。難読化されたスクリプトによって変更された機能が使われていないことを確認してください。たとえば、
python -d obf_foo.py
-
難読化したスクリプトを異なるプラットフォームやDockerで配布する場合、関連するクロスプラットフォームオプションが設定されていることを確認してください。難読化されたスクリプトはバイナリライブラリを含むため、プラットフォームに依存し、ターゲットのPythonのバージョンは難読化するスクリプトのバージョンと同じである必要があります。
-
pack コマンドを使用している場合、
PyInstaller
がプレーンスクリプトを直接パックすることができ、最終的なバンドルが動作することを確認します。 -
Restrict Mode 3 以上で難読化されたスクリプトを使用している場合、デフォルトの制限モードを使用してみてください。低い制限モードで動作する場合、スクリプトをチェックして、制限モードに違反していないことを確認してください。
-
複雑なスクリプトやパッケージを使っている場合は、簡単なスクリプトやパッケージで動作確認をしてください。
-
よくわからないことがあれば、数分でテストを行い、pyarmor を理解してください。
pyarmorのデフォルトのオプションは一般的なケースでは有効ですが、複雑なケースでは、各コマンドの異なるオプションを理解する必要があります。まず、 -h
オプションでobfuscateの利用可能な全オプションをリストアップします。
pyarmor obfuscate -h
簡単な説明で目的のオプションを見つけることができます。 よくわからない場合は、 Man Page にアクセスして各オプションの詳細を確認してください。
オプションを理解する一番簡単な方法は、1分間でテストすることです。例えば、 -bootstrap
というオプションは、難読化されたスクリプトのブートストラップ・コードの生成方法を制御するために使われます。
cd /path/to/test
mkdir case-1
cd case-1
echo "print('Hello')" > foo.py
pyarmor obfuscate --bootstrap 2 foo.py
ls dist/
cat dist/foo.py
cd /path/to/test
mkdir case-2
cd case-2
echo "print('Hello')" > foo.py
pyarmor obfuscate --bootstrap 3 foo.py
ls dist/
cat dist/foo.py
異なるオプションを組み合わせて同様のテストを行うことができますので、pyarmorの理解を早めることができるかもしれません。
多くのレポーティングされた issue がありますので、まずはこちらで同じ問題を探してみてください。
問題を報告する
ドキュメントに解決策がない場合、セキュリティ上の問題については、pyarmor@163.com にメールを送ってください。それ以外の場合は、issue to report をクリックして、必要な情報を提供してください。
- pyarmorコマンドと出力ログの全文(必須)
- 難読化されたスクリプトを他のマシンに配布する場合、どのファイルがコピーされるか(任意)
- 難読化されたスクリプトを実行するコマンドと、何か問題があった場合の完全なトレースバック
出力ログはこの方法でファイルにリダイレクトすることができます。
pyarmor obfuscate foo.py >log.txt 2>&1
ここでは一例として、問題のタイトルを紹介します。
cannot import name 'pyarmor' from 'pytransform'
issueの内容(これらを全てgithubにコピーして修正します)。
1. On MacOS 10.14 run pyarmor to obfuscate the script
・・・・・
$ pyarmor obfuscate --exact main.py
INFO Create pyarmor home path: /Users/jondy/.pyarmor
INFO Create trial license file: /Users/jondy/.pyarmor/license.lic
INFO Generating public capsule ...
INFO PyArmor Trial Version 7.0.1
INFO Python 3.7.10
INFO Target platforms: Native
INFO Source path is "/Users/jondy/workspace/pyarmor-webui/test/__runner__/__src__"
INFO Entry scripts are ['main.py']
INFO Use cached capsule /Users/jondy/.pyarmor/.pyarmor_capsule.zip
INFO Search scripts mode: Exact
INFO Save obfuscated scripts to "dist"
INFO Read product key from capsule
INFO Obfuscate module mode is 2
INFO Obfuscate code mode is 1
INFO Wrap mode is 1
INFO Restrict mode is 1
INFO Advanced value is 0
INFO Super mode is False
INFO Super plus mode is not enabled
INFO Generating runtime files to dist/pytransform
INFO Extract pytransform.key
INFO Generate default license file
INFO Update capsule to add default license file
INFO Copying /Users/jondy/workspace/pyarmor-webui/venv/lib/python3.7/site-packages/pyarmor/platforms/darwin/x86_64/_pytransform.dylib
INFO Patch library dist/pytransform/_pytransform.dylib
INFO Patch library file OK
INFO Copying /Users/jondy/workspace/pyarmor-webui/venv/lib/python3.7/site-packages/pyarmor/pytransform.py
INFO Rename it to pytransform/__init__.py
INFO Generate runtime files OK
INFO Start obfuscating the scripts...
INFO /Users/jondy/workspace/pyarmor-webui/test/__runner__/__src__/main.py -> dist/main.py
INFO Insert bootstrap code to entry script dist/foo.py
INFO Obfuscate 1 scripts OK.
・・・・・
2. Copy the whole folder `dist/` to target machine Ubuntu
3. Failed to run the obfuscated script by Python 3.7 in Unbutu
・・・・・
$ cd dist/
$ python3 main.py
Traceback (most recent call last):
File "main.py", line 1, in <module>
from pytransform import pyarmor
ImportError: cannot import name 'pyarmor' from 'pytransform' (/home/jondy/dist/pytransform/__init__.py)
問題は無効としてマークされ、次のいずれかで直接クローズされる可能性があります。
- テンプレートとして報告されていない、または必要な情報がない
- ドキュメントに正確な解決策がある場合
Segment fault
以下のような場合、難読化されたスクリプトがクラッシュすることがあります。
- デバッグ版Pythonで難読化したスクリプトを実行した場合
- Python X.Yで難読化したが、別のPythonバージョンM.Nで難読化したスクリプトを実行した場合
- 異なるプラットフォームでスクリプトを実行するが、
--platform
オプションを指定せずに難読化する
・DockerではAlpine Linux、PyArmorではlinux.x86_64ではなくmusl.x86_64というプラットフォーム名で実行されます
・Windowsでは、32ビットWindowsと64ビットWindowsは別物です
・64ビットWindowsでは、32ビットPythonと64ビットPythonは異なります - 難読化されたコードオブジェクトの
co_code
やその他の属性を何らかの方法で読み取る、一部のサードパッケージはバイトコードを解析して何かを行う可能性があります - 制限モード3以上で難読化されたスクリプトを難読化されていないスクリプトで読み込むとクラッシュすることがあります。また、
obf-code=0
で難読化されたスクリプトもクラッシュする可能性があります - 異なるオプション
--advanced
で難読化されたスクリプトを混在させる - MacOSでは、pyarmorのコアライブラリは標準システムのPythonにリンクされていますが、その他のマシンでは、
install_name_tool
を使ってrpath
を変更し、このマシンに適応させます。
PyArmor 5.5.0 ~ 6.6.0 では、advanced mode が原因で一部のマシンがクラッシュすることがあります。簡単な対処法は、pytransform.py
(pyarmorのインストール先)を編集してアドバンストモードを無効にし、_load_library
関数で202行目をアンコメントすることです。最終的には以下のようなコードになります。
# Disable advanced mode if required
m.set_option(5, c_char_p(1))
Bootstrapの問題
pytransformが見つからない
一般的に、ダイナミックライブラリ _pytransform
は ランタイムパッケージ にあり、v5.7.0以前では難読化されたスクリプトと同じパスにあります。おそらく。
- Linuxでは_pytransform.so
- Windowsでは、_pytransform.dllです。
- MacOSの_pytransform.dylib
まず、そのファイルが存在するかどうかを確認します。存在する場合は、
-
ダイナミックライブラリの実行権限を確認する
Windowsで実行権限がない場合、文句を言われます。[エラー5]アクセスが拒否されました -
ctypes
が_pytransform
を読み込むことができるかどうかを確認します
from pytransform import _load_library
m = _load_library(path='/path/to/dist')
- エントリースクリプトの ブートストラップコード にランタイムパスを設定してみてください
from pytransform import pyarmor_runtime
pyarmor_runtime('/path/to/dist')
それでも動作しない場合は、問題 を報告してください
ERROR: Unsupport platform linux.xxx
サポートプラットフォーム をご覧ください。
/lib64/libc.so.6: version ‘GLIBC_2.14’ not found
GLIBC_2.14が存在しないマシンでは、この例外が発生します。
解決策の一つは、以下の方法で_pytransform.soにパッチを適用することです。
まず、バージョン情報を確認します。
readelf -V /path/to/_pytransform.so
...
Version needs section '.gnu.version_r' contains 2 entries:
Addr: 0x00000000000056e8 Offset: 0x0056e8 Link: 4 (.dynstr)
000000: Version: 1 File: libdl.so.2 Cnt: 1
0x0010: Name: GLIBC_2.2.5 Flags: none Version: 7
0x0020: Version: 1 File: libc.so.6 Cnt: 6
0x0030: Name: GLIBC_2.7 Flags: none Version: 8
0x0040: Name: GLIBC_2.14 Flags: none Version: 6
0x0050: Name: GLIBC_2.4 Flags: none Version: 5
0x0060: Name: GLIBC_2.3.4 Flags: none Version: 4
0x0070: Name: GLIBC_2.2.5 Flags: none Version: 3
0x0080: Name: GLIBC_2.3 Flags: none Version: 2
そして、GLIBC_2.14のエントリーをGLIBC_2.2.5に置き換えます。
- 0x56e8+0x10=0x56f8にある4バイトを0x56e8+0x40=0x5728にコピーします
- 0x56e8+0x18=0x5700の4バイトを0x56e8+0x48=0x5730にコピーします
以下はコマンドの例です。
xxd -s 0x56f8 -l 4 _pytransform.so | sed "s/56f8/5728/" | xxd -r - _pytransform.so
xxd -s 0x5700 -l 4 _pytransform.so | sed "s/5700/5730/" | xxd -r - _pytransform.so
pyarmor obfuscate --platform centos6.x86_64 foo.py
‘pyarmor’ is not recognized issue
pyarmorがpipでインストールされている場合、コンピュータ内で "pyarmor"を検索し、フルパスで pyarmor
を実行するか、環境変数PATH にpyarmorのパスを追加してください。
pipでインストールされていない場合、pyarmorコマンドに相当するのは、配布フォルダーにあるPythonスクリプト "pyarmor.py"を実行することです。
__snprintf_chk: symbol not found
pyarmor を一部の dockers で実行すると、この例外が発生することがあります。これらのドッカーは musl-libc
でビルドされていますが、デフォルトの _pytransform.so
は glibc
でビルドされているため、__snprintf_chk
が musl-libc
で欠落しているのです。
この場合、対応するダイナミックライブラリをダウンロードしてみてください。
# For x86_64
pyarmor download musl.x86_64.7
# For arm64
pyarmor download musl.aarch64.3
# For armv7l
pyarmor download musl.arm.0
そして、トレースバックでファイル名が確認できた古いものを上書きします。
pyarmor v6.7.0以前は、以下の方法で最新版をダウンロードしてください。
x86/64版 http://pyarmor.dashingsoft.com/downloads/latest/alpine/_pytransform.so
ARM用 http://pyarmor.dashingsoft.com/downloads/latest/alpine.arm/_pytransform.so
Apple M1 Hangs Issue
Apple M1 with Features 3でpyarmorを実行すると、ハングアップすることがあります。pyarmorのコアライブラリ _pytransform.dylib
で JIT-compile
を使用しているためです。
Apple M1ではブロックされてしまうので、対応するエンタイトルメントで署名すると解決する場合があります。https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_allow-jit を参照してください。
スクリプトの難読化問題
Warning: code object xxxx isn’t wrapped
これは、いくつかの特別な指示が含まれているため、この関数が難読化されていないことを意味します。
たとえば、2バイトの命令JMP 255があり、コードオブジェクトが難読化された後、オペランドが267に増加し、命令は次のように変更されます。
EXTEND 1
JMP 11
この場合、ラップモードでコードオブジェクトを難読化するのは複雑です。 したがって、コードオブジェクトは非ラップモードで難読化されますが、他のすべてのコードオブジェクトは依然としてラップモードで難読化されます。
現在のバージョンでは、この関数に未使用のコードを追加して、オペランドが重要な値にならないようにして、この警告を回避することができます。
v5.5.0以前では、この場合、コードオブジェクトはそのままになります
Code object could not be obufscated with advanced mode 2
なぜなら、この関数には処理できないジャンプ命令が含まれているからです。この場合、この関数を改良して、最初のステートメントがジャンプ命令を生成しないようにすればよいのです。例えば、代入、関数呼び出しなどの単純な文です。
しかし、複合文、例えばtry、for、if、with、whileなどはジャンプ命令を発生させます。どうしてもリファクタリングができない場合は、この関数の先頭に次のようなステートメントを挿入します。
[None, None]
いくつかの命令が生成されますが、何も変わりません。
Error: Try to run unauthorized function
現在のパスに license.lic
や pytransform.key
があると、Pyarmorはこのエラーを報告するかもしれません。解決策の一つは、これらのファイルをすべて削除することで、もう一つの解決策は、PyArmorをv5.4.5以降にアップグレードすることです。
‘XXX’ codec can’t decode byte 0xXX
スクリプトの最初に正確なソースエンコードを追加してください。たとえば、
# -*- coding: utf-8 -*-
https://docs.python.org/2.7/tutorial/interpreter.html#source-code-encoding を参照してください。
ソースコードがメインスクリプトに追加されている場合でも、この問題が発生します。出力ログをチェックして、正確なスクリプト名を確認してください。
Why plugin doesn’t work
プラグインスクリプトが期待通りに動作しない場合は、まずPythonのデバッグフラグを設定して、プラグインスクリプトがエントリスクリプトに注入されている可能性があることを確認してください。
# In linux
export PYTHONDEBUG=y
# In Windows
set PYTHONDEBUG=y
pyarmor obfuscate --exact --plugin check_ntp_time foo.py
パッチファイル foo.py.pyarmor-patched
が生成され、プラグインスクリプトの内容が正しい場所に挿入されていることが確認され、そしてverify関数が実行されます。
難読化されたスクリプトを実行する問題
The license.lic generated doesn’t work
重要なのは、スクリプトを難読化するために使用するカプセルと、ライセンスを生成するために使用するカプセルは同じでなければならないということです。
PyArmorの試用版ライセンスファイルを通常のものに置き換えたり、たまに削除したりすると、 グローバルカプセル は変更されます(次回pyarmor obfuscateコマンドを実行すると、暗黙のうちに生成されます)。
いずれの場合も、別のカプセルで新しいライセンスファイルを生成すると、以前の難読化スクリプトでは動作しません。古いカプセルがなくなった場合、新しいカプセルでこれらのスクリプトを再度難読化することが1つの解決策です。
NameError: name __pyarmor__
is not defined
難読化されたスクリプトをインポートする前に、 ブートストラップコード が実行されていません。
- mod サブプロセスやマルチプロセッシングで Popen や Process によって新しいプロセスを作るとき、サブプロセスで難読化されたコードをインポートする前に ブートストラップコード が呼ばれることを確認します。さもなければ、この例外が発生します。
-
pytransform.py
またはpytransform/__init__.py
でこの例外が発生した場合、難読化されていないことを確認してください。プレーンスクリプトである必要があります。 - また、システムモジュールの
os
,ctypes
をチェックして、それらが難読化されていないことを確認してください。オプション--exclude
を使って、Pythonシステムライブラリのパス全体を除外してみてください。
ブートストラップコード が実行されたかどうかを確認する方法は? 一つの簡単な方法は、それらの前に一つのprint文を挿入することです。たとえば、
print('Start to run bootstrap code')
from pytransfrom import pyarmor_runtime
pyarmor_runtime()
メッセージが印刷されればOKです。printステートメントを削除して、他の原因を確認してください。
この問題の他の解決策は、スーパーモードを使用して スクリプトを難読化することです。
Marshal loads failed when running xxx.py
- 難読化されたスクリプトを実行するためのPythonのバージョンと難読化するためのPythonのバージョンが同じかどうか確認する。
- 難読化されたスクリプトを
python -d
で実行すると、より多くのエラーメッセージが表示されます。 - ライセンスファイルの生成に使用したカプセルが、スクリプトの難読化に使用したカプセルと同じであることを確認してください。カプセルのファイル名は、コマンド実行時にコンソールに表示されます。
- クロスプラットフォームの難読化では、ダイナミックライブラリ機能が正しく設定されていることを確認してください。
_pytransform can not be loaded twice
関数 pyarmor_runtime
が 2 回呼び出されると、_pytransform can't be loaded twice と表示されます。
例えば、難読化されたモジュールが以下の行を含むとします。
from pytransform import pyarmor_runtime
pyarmor_runtime()
__pyarmor__(....)
エントリースクリプトからこのモジュールをインポートすると、このエラーが報告されます。最初の2行はエントリースクリプトにのみ記述し、他のモジュールには記述してはいけません。
この制限はv5.1から導入されました。このチェックを無効にするには、pytransform.pyを編集して、関数pyarmor_runtimeのこれらの行をコメントするだけです。
if _pytransform is not None:
raise PytransformError('_pytransform can not be loaded twice')
This limitation has been removed from v5.3.5.
Check restrict mode failed
難読化されたスクリプトを間違った方法で使用した場合、デフォルトではすべての難読化されたスクリプトはそれ以上変更できません。
また、難読化されたスクリプトをパックしても、このエラーは報告されません。難読化されたスクリプトをパックせず、プレーンなスクリプトを直接パックしてください。
詳しくは、 Restrict Mode を参照してください。
Protection Fault: unexpected xxx
難読化されたスクリプトを誤って使用すると、デフォルトではすべてのランタイムファイルが変更できなくなります。以下のファイルには触れないでください
pytransform.py
_pytransform.so/.dll/.dylib
エントリースクリプトが新しいバージョンで難読化されていても、ランタイムファイルが古いままだと、この例外が発生することがあります。オプション --no-cross-protection
を使ってこの保護を無効にするか、 オプション --runtime
を使ってスクリプトを難読化するときに同じランタイムファイルを 指定すると、この問題を解決できるかもしれません。
詳細については、エントリースクリプトの特殊な取り扱いについて を参照してください。
Run obfuscated scripts reports: Invalid input packet
試用版と購入版を混在させてスクリプトを難読化し、license.licを生成した場合も、この例外が発生する可能性があります。試用版で生成されたすべてのファイル、例えば難読化スクリプト、ライセンスファイル、ランタイムファイルが削除されていることを確認してください。
難読化スクリプトによってインポートされたランタイムモジュールまたはパッケージ pytransform が難読化スクリプトと一緒に配布されたものであることを確認してください。例えば、難読化されたスクリプトpython dist/foo.pyをpyarmorパッケージのソースパスで実行すると、dist/foo.pyによってpyarmorのpytransform.pyが予期せずインポートされてしまい、この例外が発生する可能性があります。
難読化されたスクリプトを別のプラットフォームで使用する場合は、 難読化したスクリプトを他のプラットフォームへ配布する の注意事項を参照してください。
v5.7.0より前のバージョンでは、現在のパスにlicense.licやpytransform.keyがあるかどうか確認してください。難読化されたスクリプトのためにそれらが生成されていることを確認してください。そうでなければ、名前を変えるか、他のパスに移動してください。
なぜなら、難読化スクリプトはまず現在のパスを検索し、次にランタイムモジュールpytransform.pyのパスを検索してlicense.licとpytransform.keyを見つけるからです。もし、難読化されたスクリプトでそれらが生成されない場合、このエラーが報告されます。
OpenCV fails because of NEON - NOT AVAILABLE
一部のRaspberry Piプラットフォームでは、難読化したスクリプトを実行するとOpenCVのインポートに失敗します。
************************************************** ****************
* FATAL ERROR: *
* This OpenCV build doesn't support current CPU / HW configuration *
* *
* Use OPENCV_DUMP_CONFIG = 1 environment variable for details *
************************************************** ****************
Required baseline features:
NEON - NOT AVAILABLE
terminate called after throwing an instance of 'cv :: Exception'
what (): OpenCV (3.4.6) /home/pi/opencv-python/opencv/modules/core/src/system.cpp:538: error:
(-215: Assertion failed) Missing support for required CPU baseline features. Check OpenCV build
configuration and required CPU / HW setup. in function 'initialize'
解決策としては、 optioin --platform
に linux.armv7.0
を指定することです。
pyarmor obfuscate --platform linux.armv7.0 foo.py
pyarmor build --platform linux.armv7.0
pyarmor runtime --platform linux.armv7.0
もう一つの解決策は、環境変数 PYARMOR_PLATFORM
に linux.armv7.0
を設定することです。たとえば、
PYARMOR_PLATFORM=linux.armv7.0 pyarmor obfuscate foo.py
PYARMOR_PLATFORM=linux.armv7.0 pyarmor build
Or,
export PYARMOR_PLATFORM=linux.armv7.0
pyarmor obfuscate foo.py
pyarmor build
How to customize error message
pyarmorをいじりはじめました。有効期限が切れたライセンスファイルを使用すると、"License is expired "というメッセージが表示されます。このメッセージを変更する方法はありますか?
現時点では、pyarmorパッケージのソーススクリプトpytransform.pyにパッチを当てる必要があります。pyarmor_runtime という関数があります。
def pyarmor_runtime(path=None, suffix='', advanced=0):
...
try:
pyarmor_init(path, is_runtime=1, suffix=suffix, advanced=advanced)
init_runtime()
except Exception as e:
if sys.flags.debug or hasattr(sys, '_catch_pyarmor'):
raise
sys.stderr.write("%s\n" % str(e))
sys.exit(1)
必要に応じて、例外のハンドラーを変更します。
スクリプトが super mode で難読化されている場合、この解決策はうまくいきません。難読化されたスクリプト foo.py
が発生する例外をキャッチするスクリプトを作成することができます。たとえば、
try:
import foo
except Exception as e:
print('something is wrong')
こうすることで、pyarmorの例外だけでなく、通常のスクリプトの例外も捕捉することができます。pyarmor の例外のみを処理するためには、まず runtime でランタイムパッケージを作成し、それを使ってスクリプトを難読化します。
pyarmor runtime --advanced 2 -O dist
pyarmor obfuscate --advanced 2 --runtime @dist foo.py
そして、次のようなブートスクリプト dist/foo_boot.py
を作成します。
try:
import pytransform_bootstrap
except Exception as e:
print('something is wrong')
else:
import foo
スクリプトdist/pytransform_bootstrap.pyは実行時に作成され、空のスクリプトから難読化されるため、pyarmorブートストラップ例外のみが発生します。
undefined symbol: PyUnicodeUCS4_AsUTF8String
Python インタープリタが UCS2 でビルドされている場合、スーパーモード難読化スクリプトを実行すると、この問題が発生することがあります。この場合、UCS2 でビルドされた Centos6.x86_64 プラットフォームで難読化を行ってください。たとえば、
pyarmor obfuscate --advanced 2 --platform centos6.x86_64 foo.py
NameError: name __armor_wrap__
is not defined
Restrictモード が4または5に設定されている場合、この問題が報告されることがあります。この場合、制限モードを 2 に設定してみてください。
オブジェクトメソッド __del__
で発生した場合、pyarmor を v6.7.3+ にアップグレードし、スクリプトを再度難読化してください。
また、Using restrict mode with threading and multiprocessing と次の質問も参照してください。
Object method del raise NameError exception
メソッド __del__
がこの例外を発生させた場合、
NameError: name '__armor_enter__' is not defined
NameError: name '__armor_wrap__' is not defined
pyarmorをv6.7.3+にアップグレードし、スクリプトを再度難読化し、新しいランタイムパッケージが生成されることを確認してください。
もしスクリプトが非スーパーモードで難読化されており、python が 3.7 以降であれば、スクリプトをスーパーモードで難読化してください。あるいは、オブジェクトメソッド __del__
を難読化しないようにスクリプトを改良してください。たとえば、
class MyData:
...
def lambda_del(self):
# Real code for method __del__
...
__del__ = lambda_del
lambda_
で始まる関数名は、pyarmorによって難読化されません。上記の例では、メソッド lambda_del
は難読化されていないため、 __del__
は難読化されます。
他の解決策は、 __del__
を含むスクリプトを難読化せず、難読化されたスクリプトを上書きするために、プレーンなスクリプトをコピーすることです。または、このスクリプトを --obf-code 0
で難読化する。
SystemError: module filename missing
スーパーモードでアウターライセンスのスクリプトを難読化する場合、難読化されたスクリプトが現在のパスにライセンスファイル license.lic
を見つけられなかった場合、このエラーを報告することがあります。
license.lic
が他のパスにある場合は、環境変数 PYARMOR_LICNSE
にフルパスで設定します。たとえば、
export PYARMOR_LICNSE=/path/to/license.lic
Android protection problem
多くのAndroidシステムでは、データパスに動的ライブラリのロードを許可していませんが、難読化したスクリプトのランタイムパッケージには _pytransform.so
が一つ含まれているため、このような例外が発生する可能性があります。
dlopen failed: couldn't map "/storage/emulated/0/dist/_pytransform.so"
segment 1: Operation not permitted
Androidの開発ドキュメントを参照し、Androidが動的ライブラリの読み込みを許可している場所に pytransform
フォルダ全体をコピーし、Pythonがそれを見つけてインポートできる場合のみ PYTHONPATH
やその他の方法で設定してください。
libpython3.9.so.1.0: cannot open shared object file
python39.dll, libpython3.9.so などの python コアライブラリがない場合、この python インタプリタが -enable-shared
でビルドされていることを確認してください。デフォルトでは、ランタイム拡張機能のpytransformはpythonのダイナミックライブラリにリンクされています。
Linux の場合、apt などのパッケージ管理ツールで libpython3.9 をインストールしてください。
難読化されたスクリプトのパッキングの問題
The final bundle does not work
まず最初に、 pack の man ページを完全に読んでください。
次に、スクリプトが PyInstaller によって直接パックでき、最終的なバンドルが動作することを確認してください。たとえば、
pyinstaller foo.py
dist/foo/foo
最終的なバンドルでモジュールが見つからないと文句を言われた場合、追加の PyInstaller オプションが必要です。https://pyinstaller.readthedocs.io を参照してください。
次に、難読化されたスクリプトがパッキングなしで動作することを確認します。たとえば、
pyarmor obfuscate foo.py
python dist/foo.py
両方ともOKなら、出力パスdistとPyInstallerのビルドのキャッシュパスを削除してから、スクリプトを --debug
でパックします。
pyarmor pack --debug foo.py
ビルドファイルは保持されます。パッチが適用された foo-patched.spec
をpyinstallerが使用して、難読化されたスクリプトを直接パックできます。
pyinstaller -y --clean foo-patched.spec
このパッチを適用した .spec
ファイルを確認し、この.specファイルのオプションを変更し、最終的にバンドルが動作することを確認します。
難読化されたスクリプトでPyInstallerバンドルを再パックする を参照してください。この方法で動作することを確認してください。
No module name pytransform
コマンド pyarmor pack を実行しているときにこのエラーが発生した場合、
- コマンドラインで指定されたスクリプトが難読化されていないことを確認します
- キャッシュされた
myscript.spec
を削除するために--clean
オプションを追加してpack
を実行してください
pyarmor pack --clean foo.py
NameError: name ‘pyarmor’ is not defined
トレースバックをチェックして、どのスクリプトがこの例外を発生させたかを見つけると、問題を発見するのに役立ちます。
-
pytransform.py
またはpytransform/__init__.py
がこの例外を発生させた場合、難読化されていないことを確認してください。プレーンなスクリプトである必要があります。 - システムモジュールの
os
やctypes
もチェックして、難読化されていないことを確認してください。この場合、Pythonのシステムライブラリのパスを除外するなどの工夫をしてみてください。
pyarmor pack -x " --exclude venv" foo.py
詳しくは pack を参照してください。
- 自分のスクリプトだけを空のパスにコピーし、そのパスにパックするようにしてください。
- 試用版では動作し、pyarmorを登録後に失敗する場合は、 クリーンアンインストール を試してみてください。
PyArmorの登録に関する問題
Purchased pyarmor is not private
購入版で難読化されていても、試用版からのライセンスは機能します。
- コマンド
pyarmor register
が正しい登録情報を表示していることを確認します - クリーンアンインストール を行い、再度登録します
- 現在のユーザが、pyarmor を登録するユーザと同じであることを確認します
- 環境変数 PYARMOR_HOME が設定されていないことを確認します
- システムを再起動してみてください
Could not query registration information
コマンドとログを使って、pyarmorに登録しようとしました。
~ % pyarmor register pyarmor-regfile-1.zip
INFO PyArmor Version 6.5.2
INFO Start to register keyfile: pyarmor-regfile-1.zip
INFO Save registration data to: /Users/Jondy/.pyarmor
INFO Extracting license.lic
INFO Extracting .pyarmor_capsule.zip
INFO This keyfile has been registered successfully.
登録されているかどうかを見てみると、このように出力されました。
~ % pyarmor register
INFO PyArmor Version 6.5.2
PyArmor Version 6.5.2
Registration Code: pyarmor-vax-000383
Because of internet exception, could not query registration information.
api.dashingsoft.com
のドメインに ping
を打ち、ipアドレスがこのように解決されていることを確認してください。
~ % ping api.dashingsoft.com
PING api.dashingsoft.com (119.23.58.77): 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
そうでない場合は、 /etc/hosts
に1行追加してください。
119.23.58.77 pyarmor.dashingsoft.com
既知の問題点
Obfuscate scripts in cross platform
v5.6.0からv5.7.0まで、クロスプラットフォームに関するバグがあります。linux64/windows64/darwin64で難読化されたスクリプトを、これらのターゲットプラットフォームにコピーすると、動作しなくなります。
armv5, android.aarch64, ppc64le, ios.arm64, freebsd, alpine, alpine.arm, poky-i586
ライセンスに関する質問
Q&Aを参照してください。
その他 質問
難読化されたコードの復元は容易か?
もし誰かが難読化を破ろうとするならば、まずリバースエンジニアの分野の専門家であり、Pythonの専門家であり、Pythonのコードオブジェクトの構造、Pythonインタープリタの各命令を理解しなければなりません。そのような人がリバースエンジニアリングを始めて、何千もの機械命令を順を追って調べ、機械コードでアルゴリズムを調査しなければなりません。ですので、pyarmorを逆引きするのは簡単なことじゃありません。
領収書・請求書の発行方法
Pyarmorの販売はすべてMyCommerceで行っています。
注文、領収書、請求書の発行については、こちらのページをご参照ください。
https://www.mycommerce.com/shopper-support/
または、"ClientSupport@MyCommerce.com "に直接お問い合わせください。
pyarmorの評価版ライセンスはありますか?
申し訳ありませんが、pyarmorのライセンスはオフラインでも動作しますので、評価用ライセンスはありません。
通常、難読化されたスクリプトは、元のスクリプトをシームレスに置き換えることができます。ただし、pyarmorによって変更された機能を除いてです。 難読化されたスクリプトの違いについて にリストしています。
ほとんどのパッケージはpyarmorで動作し、一部のパッケージはパッチを当てるだけでpyarmorも動作します。バイトコードやこのようなものを閲覧するパッケージはpyarmorでは全く動作しません。
17.ライセンス
本ソフトウェアは、 Free To Use But Restricted として配布されます。無料体験版には有効期限はありません。
- 試用版では、大きなスクリプトを難読化することができません。
- 試用版で難読化されたスクリプトは非公開ではありません。つまり、誰でもこれらの難読化されたスクリプトのライセンスファイルを生成することができます。
- 試用版では、最新のダイナミックライブラリをダウンロードすることができず、旧バージョンが利用可能です。
- スーパープラスモードは、試用版では利用できません。
- 試用版では、商用製品のPythonスクリプトを許可なく難読化することはできません。
難読化されたスクリプトのライセンスファイルについては、難読化スクリプトのライセンスファイル を参照ください。
上記の制限を解除するためには、ライセンスコードが必要です。
ソフトウェアに対して発行されるライセンスには、次の2つの基本的なタイプがあります。
-
ホームユーザー向けの個人ライセンス。ユーザーは、自分のコンピューターでソフトウェアを使用するために1つのライセンスを購入します。この種のライセンスを注文するときは、登録名として本名を入力してください。このソフトウェアは、この登録名に対してのみ許可されています。
ホームユーザーは、個人ライセンスを使用して、ライセンス所有者の所有物であるすべてのPythonスクリプトを難読化し、難読化されたスクリプトのプライベートライセンスファイルを生成し、それらと必要なすべてのファイルを他のマシンまたはデバイスに配布できます。
ホームユーザーは、ライセンス所有者の所有物ではないPythonスクリプトを難読化することはできません。 -
ビジネスユーザー向けのエンタープライズライセンス。ユーザーは、組織の1つの製品シリアルにソフトウェアを使用するために1つのライセンスを購入します。この種のライセンスを注文するときは、組織名と製品名を入力してください。このソフトウェアは、この登録名に対してのみ許可されています。
1つの製品シリアルには、同じ製品の現在のバージョンと他の新しいバージョンが含まれます。
ビジネスユーザーは、すべてのコンピューターと組み込みデバイスでエンタープライズライセンスを使用して、この製品シリアルのすべてのpythonスクリプトを難読化し、これらの難読化されたスクリプトのプライベートライセンスファイルを生成し、それらと必要なすべてのファイルを他のマシンとデバイスに配布できます。
ソフトウェア所有者の許可なしに、ある製品シリアル用に購入したライセンスを他の製品シリアル用に使用しないでください。ビジネスユーザーは、さまざまな製品シリアルの新しいライセンスを購入する必要があります。
いずれの場合も、ソフトウェアは、許可された個人または企業が所有するPythonスクリプトを難読化するためにのみ使用されます。
購入方法
ライセンスを購入するには、以下のコマンドを実行してください。
pyarmor register -buy
または、Webブラウザで次のURLを開いてください。
https://order.shareit.com/cart/add?vendorid=200089125&PRODUCT[300871197]=1
個人ライセンスの場合、注文の際に登録名を本名で記入してください。
企業ライセンスの場合は、登録名に企業名を記入し、「License To Product」に本ソフトウェアを使用する製品名を記入してください。
支払いが正常に完了すると、登録ファイル(一般に「pyarmor-regcode-xxxx.txt」と呼ばれる)が電子メールで送信されます。
それをディスクに保存してから、次のコマンドを実行して PyArmor を登録します。
pyarmor register /path/to/pyarmor-regcode-xxxx.txt
登録情報を確認します。
pyarmor register
登録に成功したら、試用版で難読化したスクリプトをすべて削除し、再度難読化します。
PyArmorのバージョンが6.5.2未満の場合、登録ファイル「pyarmor-regcode-xxxx.txt」をテキストエディタで開き、その中のガイドに従ってPyArmorを登録してください。
レジストレーションコードは永久的に有効であり、永久的に使用することができますが、将来のバージョンでは動作しない可能性があり、将来のバージョンを使用する場合は新しいライセンスを購入する必要がある場合があります。
アップグレードの注意事項
2019-10-10以前に購入されたライセンスは、最新版へのアップグレードに対応しておりません。最新版をご利用になるには、新しいライセンスが必要です。
テクニカルサポート
もし何か疑問があれば、まず 質問と解決策 をチェックしてください、あなたが問題を迅速に解決するのに役立つ可能性があります。
解決策がない場合、技術的な問題については、ここ をクリックして、テンプレートに従って問題を報告してください。 ビジネスやセキュリティの問題については、 pyarmor@163.com
までメールを送信してください。
一般的に、メールで送られたその他の問題はすべて無視されます。
問題には3種類あります。
- 難読化されたスクリプトの制限、または既知の問題と呼ばれるもの
- PyArmor の不具合
- 誤った使用方法
最初のカタログについては、修正することができません。例えば、コードオブジェクトのco_codeを訪問するためにinspectを使う、難読化されたスクリプトをトレースするためにpdbを使う、など。これらはすべて動作しません。これらは既知の問題です。 難読化されたスクリプトの違いについて に、すべての既知の問題をリストアップしています。
2番目のカタログについては、私が修正する義務があります。
それ以外は、ドキュメントを読んで修正するのがあなたの役目です。私は、ヒントや例を与えることはあっても、どのコマンドやオプションを使ってスクリプトを難読化すべきかを手取り足取り教えることはしません。
Microsoft Excelを購入し、複雑なグラフを作りたいとします。あなたはエクセルの高度な機能を学び、そして自分でこのチャートを作らなければなりません。マイクロソフトに頼んで複雑なグラフを作ってもらうわけにはいかないですよね。
同様に、pyarmorは多くの機能と優れたドキュメントを提供していますが、あなたは自分でそれらを学ぶ必要があります。例えば、制限モードはPyArmorの高度な機能ですが、私の役目はドキュメントに書かれている通りに実装することです。あなたのスクリプトを読んで、あなたのスクリプトを制限モードに適合させるのは、私の役目ではありません。
サードパーティのパッケージを難読化または使用する予定がある場合は、このパッケージを難読化して、pyarmorと互換性があることを確認することもできません。難読化されたスクリプトの違いについて にリストされています。 パッケージが難読化されたスクリプトによって変更されたこれらの機能を使用している場合、pyarmorでは機能しません。
Pythonの世界には数え切れないほどの大きなパッケージがあり、その中には私が使ったことのない、あるいは全く知らないパッケージもたくさんあります。また、複雑なパッケージを調べて、どの行がpyarmorと衝突するかを見つけるのは簡単ではありませんし、これらの複雑なパッケージをすべてローカルマシンで実行するのは困難です。
一般的にこのような場合、ユーザーは例外の周りの簡単なスナップショットコードやいくつかの実行情報を提供し、私はそれを解析してpyarmorと競合する可能性がある場所を見つけ、解決策を見つけるようにします。
Q&A
-
1つのPyArmorライセンスを購入すると、難読化のために様々なマシンで使用できますか? それとも1台のマシンでのみ有効ですか?1台のマシンにライセンスをインストールし、難読化したコードを配布する必要があるのでしょうか?
-> 様々なマシンで使用できますが、1つのライセンスは1つの製品にのみ有効です。 -
1つのライセンスで、WindowsやLinuxなど様々なプラットフォームで動作するPythonコードの難読化が可能ですか?
-> 現在のバージョンのすべての機能については、イエスです。しかし、将来のバージョンでは。 1つのライセンスで、PyArmorがサポートするすべてのプラットフォームで使用できるかどうかはわかりません。 -
購入したライセンスはどれくらいの期間有効ですか?永久ですか?
-> 永久に有効です。 ただし、PyArmorの将来のすべてのバージョンで機能することを約束することはできません。将来のバージョンを使用する場合は、新しいライセンスを購入する必要があるかもしれません。 -
1つのライセンスで、様々なバージョンのPythonパッケージ/モジュールの難読化を行うことはできますか?
-> はい、1つの製品に属する場合のみ可能です。 -
問題が発生した場合、サポートは受けられますか?
-> githubで問題を報告し、上記のテクニカルサポートのセクションを参照してください。 -
Pyarmorは様々なPythonのバージョンで動作しますか?
-> ほとんどの機能はPython27、Python30〜Python310で動作しますが、いくつかの機能はPython27、Python37以降でのみ動作します。一部の機能はPython27, Python37以降でしか動作しない可能性があります。 -
将来リリースされるPythonバージョンをサポートするためにPyArmorを維持する計画はありますか?
-> はい。 PyArmorの目標は、Pythonを商用ソフトウェアで広く使用できるようにすることです。 -
PyArmorで、モジュールが同じ製品に属しているかどうかを識別する仕組みはどのようなものですか?
-> PyArmorはそれ自体を識別できませんが、難読化されたスクリプトをチェックして、どの登録ユーザーがそれらを配布しているかを見つけることができます。 したがって、2つの製品が1つの同じライセンスで配布されていることがわかります。 -
製品が改訂され、バージョンが変わった場合、同じライセンスを使用できますか、それとも新しいライセンスが必要ですか?
-> 同じライセンスであれば問題ありません。 -
PyArmor EULAの製品連載とはどういう意味ですか?
->製品シリアルとは、販売単位とそのアップグレードバージョンを意味します。 たとえば、個人用のpyarmor、企業用のpyarmor、バージョン1.0から7.0までのpyarmor、およびpyarmorのgrahpicsユーザーインターフェイスであるpyarmor-webuiは、すべて1つの製品シリアルに属しています。