動的コード
コード暗号化を使用すると、アプリケーション内の特定のソースメソッドを暗号化し、ユーザーが利用できる機能に応じてそれらを切り替えられます。たとえば、アプリケーションに基本機能と高度な機能の両方がある場合、高度な機能の暗号化されたソースメソッドをアプリケーションのメインの実行可能ファイルとは別に保管し、それらの機能を利用できるライセンスをユーザーが持っている場合にだけ実行時に読み込む、といったことができます。この動的コードの選択により、開発者は機能を限定したデモ版やトライアル版のアプリケーションを作成し、ユーザーにライセンスキーを提供することで追加機能のロックを解除できます。コード暗号化を使用することで、開発者は知的財産を保護しながら、ユーザーに柔軟なライセンスの選択肢を提供できます。
コード暗号化が提供する動的コードでは、外部の条件に基づいて、メソッドの異なるバージョンを実行時に切り替えられます。たとえば、アプリケーションはデモモードでは限られた機能しか持たなくても、有効なライセンスファイルがあれば、より高度な機能が有効になります。これは、限定版の機能と高度な機能の両方のコードを暗号化し、ライセンスファイルの有無に基づいて、動的コードの選択の仕組みで両者を切り替えることで実現できます。
デモアプリケーション
デモアプリケーションの従来の実装方法では、製品版のコードがデモ版にすでに含まれており、ライセンスファイルによって高度な機能の実行が許可されます。しかし、この方法には大きな欠点があります。ライセンスファイルがあるときに実行されるコードは保護されていないため、攻撃者がライセンスの検証を回避して、ライセンスが必要な機能のロックを解除できてしまいます。
一方、動的コードの選択では、高度な機能を提供するコードは暗号化されており、ライセンスファイルがなければどのような方法でも実行できません。コードを復号して実行するための暗号化キーは、ライセンスファイルに含まれているからです。これにより、リバースエンジニアリングや高度な機能への不正アクセスに対する保護が強化されます。ライセンスファイルは特定のコード部分のロックを解除する固有のキーとして機能するため、攻撃者がライセンスの仕組みを回避して高度な機能を利用することは困難になります。
GitHub で公開されている次のサンプルプロジェクトを見てみましょう。サンプルのソースコードは、コマンドラインから Git で次のコマンドを実行して取得できます。
git clone https://github.com/babelfornet/dynamic-code-example.gitこのサンプルは、動的コードを使って、特定のファイルの有無に基づいてアプリケーションの動作を変える方法を示しています。
[Obfuscation(Feature = "msil encryption:id=feature1;source=demo;internal=true;password=1234", Exclude = false)]
public void Feature1Demo()
{
Console.WriteLine("Feature1 Demo");
Feature2Demo();
}
[Obfuscation(Feature = "msil encryption:id=feature2;source=demo;internal=true;password=1234", Exclude = false)]
public static void Feature2Demo()
{
Console.WriteLine("Feature2 Demo");
}これらは 2 つのデモメソッドで、動的コードの選択によって、実行時に別のバージョンのメソッドに置き換えられるようにタグ付けされています。Feature1Demo() メソッドと Feature2Demo() メソッドは、コード暗号化機能により、それぞれ ID「feature1」と「feature2」で暗号化されます。暗号化されたコードのソースは「demo」と指定され、暗号化されたコードをアセンブリ内に保持するために、internal プロパティが true に設定されています。暗号化のパスワードは「1234」に設定されています。
有効なライセンスファイルなしでアプリケーションを実行すると、デモモードとみなされます。この場合、アセンブリは、動的コードによって実行時に置き換えられるようにタグ付けされた上記の 2 つのメソッドを呼び出します。これらのメソッドはアプリケーションの基本機能を実装しており、完全なライセンス版と比べて機能が制限されていることがあります。
static void Main(string[] args)
{
var app = new App();
app.Feature1Demo();
}次に、アプリケーションにライセンスがある場合にデモメソッドを置き換える、次の 2 つのメソッドを見てみましょう。ソースが「retail」に設定されていることに注意してください。ライセンスファイルが見つかった場合、ID「feature1」のメソッド Feature1Retail() が Feature1Demo() を置き換え、ID「feature2」のメソッド Feature2Retail() が Feature2Demo() を置き換えます。
[Obfuscation(Feature = "msil encryption:id=feature1;source=retail;internal=true;password=4567", Exclude = false)]
public static void Feature1Retail()
{
Console.WriteLine("Feature1 Retail");
Feature2Retail();
}
[Obfuscation(Feature = "msil encryption:id=feature2;source=retail;internal=true;password=4567", Exclude = false)]
public static void Feature2Retail()
{
Console.WriteLine("Feature2 Retail");
}上記の 2 つのメソッドは、デモモードで動作するアセンブリからは実行時に呼び出されず、デモメソッドとは異なるパスワードで暗号化されています。Babel はメソッドを暗号化した後、難読化の過程でこの情報を除去するため、アプリケーションのコードからこのパスワードを取得することはできません。
コード暗号化で「demo」と「retail」を動的に切り替える方法が必要です。上記のメソッドはパスワードで暗号化されているため、実行時にパスワードを取得するための、Babel のコード暗号化のエントリポイントを定義する必要があります。
[Obfuscation(Feature = "msil encryption get password", Exclude = false)]
internal static string GetPassword(string source)
{
Console.WriteLine($"> Get {source} password");
if (source == "demo")
return "1234";
string pwd = File.ReadAllText(source + ".txt");
Console.WriteLine($">> {source} password: {pwd}");
return pwd;
}GetPassword() メソッドは、プログラムの実行中に暗号化されたメソッドが呼び出されたとき、その復号に必要なパスワードを取得するために BVM(Bytecode Virtual Machine)が使用します。
ソース名が「demo」の場合、このメソッドはハードコードされたパスワード「1234」を返します。これは、「demo」のメソッド Feature1Demo() と Feature2Demo() の暗号化に使われたパスワードです。それ以外の場合は、ソースと同じ名前で拡張子が「.txt」のテキストファイルからパスワードを読み取り、呼び出し元に返します。アプリケーションはいつでもデモモードで実行できるため、デモ用のパスワードはコード内に格納されており、このパスワードを隠す必要はありません。
「demo」ソースと「retail」ソースの切り替えは、GetSourceStream() メソッドが行います。このメソッドは、暗号化されたメソッドのいずれかが呼び出されたときに、BVM から呼び出されます。
[Obfuscation(Feature = "msil encryption get stream", Exclude = false)]
internal static object GetSourceStream(string source)
{
Console.WriteLine($"> Get {source} stream");
if (source == "demo")
{
string[] sources = { "retail", "special" };
foreach (var src in sources)
{
if (File.Exists($"{src}.txt"))
{
Console.WriteLine($">> Switch to {src}");
return src;
}
}
return null;
}
// External sources
if (File.Exists(source + ".eil"))
{
Console.WriteLine($">>> Read {source} stream");
return File.OpenRead(source + ".eil");
}
return null;
}このメソッドは、ライセンスファイルの有無といった外部の条件に基づいて、暗号化されたメソッドの適切なソースを動的に選択するうえで重要な役割を果たします。実際、アプリケーションは起動するとデモメソッドを呼び出し、それらは GetPassword() メソッドが返すパスワードで復号できます。
動的コードの選択では、BVM(Binary Virtual Machine)が、暗号化されたコードの実行と、外部の条件に基づくメソッドのバージョンの切り替えを担当します。BVM は、「demo」のメソッドを実行する前に、「demo」をパラメーターとして GetSourceStream() メソッドを呼び出します。「demo」のメソッドはアセンブリの内部にあるため、demo のソースストリームを返す必要はありません。ただし、「retail.txt」ファイルが見つかった場合は「retail」のソースコードに切り替えたいので、GetSourceStream() メソッドから文字列「retail」を返して、「retail」ソースを実行したいことを BVM に伝える必要があります。
BVM は、GetSourceStream() から文字列「retail」を受け取ると、「retail」のコードを実行する必要があると判断します。続いて BVM は、引数「retail」を指定して GetPassword() を呼び出します。GetPassword() は retail.txt ファイルから「retail」版のパスワードを読み取って BVM に返し、BVM はそのパスワードで「retail」のコードを復号します。「retail」のコードが復号されると、「demo」のコードの代わりに実行されます。このようにして、アプリケーションは、コードの再コンパイルや再デプロイをせずに、ライセンスファイルの有無に基づいて、暗号化されたコードの異なるバージョンを実行時に切り替えられます。
動的コードでは、内部ソースと外部ソースを複数定義できます。たとえば、別の外部ソース「special」を宣言し、コードを復号するためのパスワードを含む special.txt ファイルがあるときに、「retail」のコードの代わりに読み込まれるようにできます。
[Obfuscation(Feature = "msil encryption:id=feature1;source=special;internal=false;password=0000", Exclude = false)]
public static void Feature1Special()
{
Console.WriteLine("Feature1 Special");
Feature2Special();
}
[Obfuscation(Feature = "msil encryption:id=feature2;source=special;internal=false;password=0000", Exclude = false)]
public static void Feature2Special()
{
Console.WriteLine("Feature2 Special");
}このコードは、Obfuscation 属性でタグ付けされた 2 つのメソッド、Feature1Special() と Feature2Special() を示しており、internal プロパティは「false」に設定されています。これは、これらのメソッドの難読化されたコードが、アセンブリ自体には含まれず、拡張子「.eil」の外部ファイルに生成されることを意味します。暗号化されたコードを含む外部の「.eil」ファイルは、Obfuscation 属性で指定した適切なパスワードを使って、実行時に読み込んで復号できます。
このサンプルを実行するには、まずソリューションを Debug 構成でコンパイルします。次に、Babel Desktop で「obfuscate.babel」プロジェクトを開いて実行します。このプロジェクトは出力パスを設定していないため、Babel Desktop は難読化されたアセンブリを、入力アセンブリの隣の「BabelOut」フォルダーに書き出します。その後、DynamicCode.exe、DynamicCode.runtimeconfig.json、DynamicCode.deps.json の各ファイルを「BabelOut」フォルダーにコピーします。続いて、PowerShell ウィンドウから「BabelOut」フォルダーの DynamicCode.exe を実行すると、次の出力が表示されます。
> Get demo stream
> Get demo password
Feature1 Demo
Feature2 Demo「retail」ソースをテストするには、retail.txt ファイルを BabelOut フォルダーにコピーして、DynamicCode.exe を実行します。次の出力が表示されます。
> Get demo stream
>> Switch to retail
> Get retail password
>> retail password: 4567
Feature1 Retail
Feature2 Retail「special」ソースをテストするには、special.txt ファイルを BabelOut フォルダーにコピーして、DynamicCode.exe を実行します。次の出力が表示されます。
> Get demo stream
>> Switch to special
> Get special stream
>>> Read special stream
> Get special password
>> special password: 0000
Feature1 Special
Feature2 Special