チェックとは、アプリケーションの不正使用を検出して対応する、実行時の検証です。 たとえば、デバッグ チェックは、デバッガーを起動した状態でアプリケーションが実行されているかどうかを検出します。
Dotfuscator では、コードの差し込みによってアプリケーションにチェックを追加できます。 ユーザーがコードを記述する必要はありません。Dotfuscator は適切な検証ロジックを自動的に差し込み、チェックが不正な使用を検出したタイミングに事前に作成したレスポンスを差し込むこともできます。 また、検証結果をアプリケーションに通知するように、チェックを構成することもできます。
難読化が、「静的解析」(つまり、アセンブリ ファイルの解析)からアプリケーションを保護するのに対し、チェックは、実行されている間の「動的解析」からアプリケーションを保護します。 難読化とチェックを一緒に使用することで、アプリケーション コードとビジネス データの両方を攻撃から防御する、アプリケーション保護の階層化アプローチが提供されます。
チェックの構成
Dotfuscator でチェックを差し込むには、まずチェックを有効にします。
Dotfuscator で差し込むチェックを指定するには、Dotfuscator 構成エディターのチェック画面を使用します。 必要であれば、チェック属性を使ってソース コードにアノテーションを付けることにより、チェックを指定することもできます。 これらのどちらの方法でも、チェックの動作を決めるさまざまなプロパティを指定できます。詳細な一覧は、チェック属性のページを参照してください。
Dotfuscator でアセンブリを処理した後、結果として生じるアプリケーションは、指定されたメソッドが実行時に呼び出されると自動的にチェックを実行します。
チェックの種類
どのチェックも特定の種類に属しています。 チェックの種類ごとに異なる検証のセットを実行するので、構成するプロパティは多少異なります。 1 つのアプリケーションで同じ種類のチェックを複数個持つことができますが、それらチェックの対象となる場所は異なっていなければなりません。
以下のチェックの種類があります。
- 改ざんチェック - アプリケーション コードが変更されているかどうかを検出します。
- デバッグ チェック - アプリケーションにデバッガーがアタッチされていないかどうかを検出します。
- Shelf Life チェック - アプリケーションが有効期限後も実行されていないかどうかを検出します。
- ルート チェック - アプリケーションがルート化された Android デバイスで実行されているかどうかを検出します。
各チェックの種類の詳細については、リンクが設定されている上記の対応ページを参照してください。 このページの残り部分では、複数の種類のチェックに該当する要素について説明します。
場所
各チェックは、アプリケーション コード内の 1 つまたは複数のメソッドを対象とします。 これらのメソッドは、チェックの場所と呼ばれます。
実行時に場所が呼び出されるたびに、関連するチェックが実行され、その瞬間のアプリケーションの状態をチェックの種類ごとに検証し、チェックのプロパティに従って対応します。
1 つの場所に、異なる種類の複数のチェックを関連付けることができます。そのような場所が呼び出された場合は、関連付けられているチェックのそれぞれが順番に実行されます。
1 つのチェックで複数の場所を対象とすることができます。そのように構成した場合は、対象の場所のいずれかが呼び出されるたびに、そのチェックが実行されます。
チェックは実行後、別の場所が呼び出されるまで再度実行されることはありません。 たとえば、デバッグ チェックが 1 つの場所、LoadFile(String path)
を対象とするとします。この場所は、ユーザーがアプリケーションでファイルを読み込むたびに呼び出されます。 攻撃者が当該のアプリケーションを実行し、ファイルを読み込んでデバッガーをアタッチした場合、そのデバッガーは、攻撃者が別のファイルを再度読み込むまで検出されません。
プロパティ
各チェックは、不正な状態にどのように対応するかなど、チェックの動作を決定するさまざまなプロパティを使用して構成できます。 使用可能なプロパティはチェックの種類によって異なるため、詳細な一覧については、チェック属性ページを参照してください。
同じ種類でも、チェックが異なれば、異なるプロパティ値を持つことができます。 たとえば、注目する 3 つのメソッド、UserLogin()
、AdminLogin()
、SupportLogin()
を持つアプリケーションについて考えてみましょう。 通常のユーザーおよびローカルの管理者がソフトウェアにデバッガーをアタッチすることは禁止しますが、場合によっては、サポート担当者が当該ソフトウェアをデバッガーで実行してもよいとします。 以下の 2 つのデバッグ チェックを構成できます。
-
1 つ目のデバッグ チェックは
UserLogin()
とAdminLogin()
を対象とします。 チェックの Action プロパティを Exit に設定し、ユーザーまたは管理者がデバッガーを起動した状態でアプリケーションを実行したら、即座にアプリケーションを終了するようにします。 -
2 つ目のデバッグ チェックは
SupportLogin()
を対象とします。 チェックの Action プロパティを None に設定し、サポート メンバーがデバッガーを起動した状態でアプリケーションを実行した場合には、実行を続けられるようにします (アプリケーション通知を構成して遠隔測定を送信することで、サポート担当者でないユーザーがサポート アカウントを使用していないかどうかを監視することもできます)。
アプリケーション通知
チェックでは、その結果をアプリケーション コードに通知できます。 これにより、アプリケーションは不正な状態に対し、アプリケーションの機能を無効にしたり、サード パーティ製の遠隔測定を送信したりするなど、カスタマイズされた方法で対応することができます。
この通知は、指定されたチェック操作の前に行われます。
以下のチェックのプロパティは、アプリケーションに通知する方法を設定するものです。
- ApplicationNotificationSinkElement
- ApplicationNotificationSinkName
- ApplicationNotificationSinkOwner
これらのプロパティは、ブール
値を受け取る、アプリケーション コード内のシンクを指定します。不正な状態が検出された場合には true
、検出されなかった場合には false
を受け取ります。 つまり、シンクを指定した場合は、不正な状態が検出されない結果であっても、シンクは常にチェックによって呼び出されます。 シンクの指定方法の詳細については、チェック属性ページの関連プロパティを参照してください。
例として、次の ApplicationNotificationExample
クラスの改ざんチェックとサンプル コードを検討してみましょう。
internal class ApplicationNotificationExample
{
private bool? myFlag;
public void Run()
{
Console.WriteLine("Application logic...");
TamperCheckLocation();
Console.WriteLine("More Application logic...");
Console.WriteLine($"The CheckResult was '{myFlag}'.");
}
private void TamperCheckLocation()
{
Console.WriteLine("This method is a location of a Tamper Check, which will run before this text.");
}
private void TamperCheckSink(bool tamperingDetected)
{
Console.WriteLine("This method is a sink for the Tamper Check.");
if (tamperingDetected)
{
Console.WriteLine("This application has been tampered!");
}
else
{
Console.WriteLine("This application has not been tampered.");
}
myFlag = tamperingDetected;
}
}
Dotfuscator がチェック コードを差し込んだ後、別のアプリケーション コードによって Run()
が呼び出されると、以下の処理が行われます。
-
Run()
がコンソールに書き込みを行い、TamperCheckLocation()
を呼び出します。 -
TamperCheckLocation()
が実行される前に、改ざんチェックは以下のことを行います。-
Dotfuscator によってアプリケーションが処理された以降に、アプリケーションが変更されたかどうかを判断します。
-
そのアプリケーション通知として
TamperCheckSink(bool)
メソッドを呼び出し、改ざんが検出されていればtrue
を、検出されていなければfalse
を引数として渡します。 -
TamperCheckSink(bool)
は、この改ざんチェックに関する情報をコンソールに書き込み、myFlag
フィールドをチェックの結果に設定して後で使用できるようにし、チェックに制御を返します。 -
チェックの実行を終了し、
TamperCheckLocation()
メソッドに制御を返します。
-
-
TamperCheckLocation()
が実行され、Run()
に制御を返します。 -
Run()
がmyFlag
フィールドの使用を含む追加情報をコンソールに書き込みます。 -
Run()
が呼び出し元に制御を返します。
チェック操作
チェックは、いくつかの組み込み済みの方法(アプリケーションを終了する、ランダムな例外をスローする、など)を用いて、チェック自体で不正なアプリケーションの状態に対応することができます。 このような対応は、チェック操作と呼ばれます。
チェック操作は、チェックのアプリケーション通知動作の後に行われます。
各チェックは、1 つのチェック操作のみを持つことができます。 複雑な動作を定義するには、アプリケーション通知を使用してアプリケーションにその動作を実行させます。
チェックで使用されるチェック操作の種類は、チェックの Action プロパティによって決定されます。
チェック操作には以下の種類があります。
-
None:チェックは、不正なアプリケーションの状態を検出した場合でも、実行後にアプリケーションに制御を返します。
-
Exit:チェックが不正なアプリケーションの状態を検出した場合、アプリケーションは終了コード 0 で直ちに終了します。
-
Exception:チェックが不正なアプリケーションの状態を検出した場合、例外がスローされます。
-
いずれかのチェックにこのチェック操作が指定された場合は、Dotfuscator により、例外の種類がアプリケーションに追加され、このチェック操作の全インスタンスによって例外がスローされます。 この例外は、
System.NullReferenceException
などの一般的な .NET の例外を模倣したものになります。 -
スローされた例外は、そのスタック トレースを非表示にして、それ以上のリバース エンジニアリングを阻止しようとします。 ただし、スタック トレースは、.NET の既定のハンドルされない例外ハンドラーなど、いくつかの場所では表示されます。 このチェック操作によってスローされた例外を、キャッチして処理することができます。ユーザー コードで例外を処理する際、この例外にはスタック トレースがないと認識されます。
-
-
Hang:チェックが不正なアプリケーションの状態を検出した場合、現在のスレッドは無期限にハングします。
操作の確率
チェック操作をランダムに発生させるように構成することができます。 これにより、アプリケーションの動作が攻撃者にとっていら立たしく予測し難いものとなります。ただ、たまにアプリケーションが「誤動作」するだけで、それ以外のときは正常に動作します。
チェックが実行されるときにチェック操作が発生する確率は、チェックの ActionProbability プロパティによって決定されます。
設定値 1.00
は、そのチェック操作を常に実行することを意味し、設定値 0.00
は、そのチェック操作を実行しないことを意味します。 これらの値の間の設定値を選択した場合は、動作がランダムになります。たとえば、設定値 0.25
は、チェックが実行される回数のうち、そのチェック操作が実行されるのは 25%(1/4)のみであることを意味します。
チェック ライブラリ コードの場所の設定
Dotfuscator はアプリケーションに含めるチェックの種類に合わせて、入力アセンブリの 1 つにライブラリ コードを差し込みます。
Dotfuscator は入力アセンブリの依存関係分析を実行することで、最良の入力アセンブリを選択してこのコード差し込みを受け取ります。 入力アセンブリ間の新しい依存関係を最小限にするように、このような方法で選択を行っていますが、新しい依存関係の追加が避けられない場合もあります。
このコードの差し込み先となるアセンブリをオーバーライドするには、"accesspoint" という名前の構成プロパティを追加し、その値として、コードの挿入先となるアセンブリの名前を設定します。 この構成プロパティの追加は、構成エディターで行えます。