XAML のリネーム
Windows Presentation Foundation(WPF)アプリケーションと、より新しい MAUI アプリケーションは、宣言型マークアップ言語の XAML と BAML(コンパイルされた XAML)を使用しており、ビジュアルデザイナーがユーザーインターフェイスの要素を直接作成できるようにしています。
Babel Obfuscator は、XAML リソースと BAML リソースを解析し、その中で参照されているすべてのメンバーをリネームできます。これにより、難読化されるシンボルの割合が高まり、XAML コードが読みにくくなります。さらに Babel は、XAML リソースまたは BAML リソースを含む複数のアセンブリファイルを 1 つのアセンブリファイルにマージできるため、アプリケーションの構成が整理され、配布が簡単になります。
XAML と BAML のシンボルのリネームは、コマンドラインで --xaml フラグを指定すると有効になります。このスイッチには省略可能な追加のパラメーターがあり、XAML(BAML)の難読化を強化できます。
コマンドライン
babel.exe myapp.exe --xamlMSBuild の Babel タスク
<PropertyGroup>
<ObfuscateXaml>true</ObfuscateXaml>
</PropertyGroup>
<Babel ObfuscateXaml="$(ObfuscateXaml)" />Babel Obfuscator では、--xaml スイッチの後にキーと値のペアを渡すことで、XAML の難読化処理をさらに細かく設定する高度な XAML 設定を使用できます。
使用できるキーと値のペアは次のとおりです。
keys
XAML の静的リソースとは、デザイン時に定義され、コントロールやスタイルなど、アプリケーションのほかの部分から参照できるリソースです。リソースは、XAML コードから参照するときに使うキー名で識別されます。
有効にすると、Babel Obfuscator は XAML/BAML コード内の静的リソースのキー名をリネームします。これにより、コードの難読化の度合いが高まり、攻撃者がリソースの本来の目的を理解することが難しくなります。
res
有効にすると、Babel は XAML/BAML のリソース名を難読化します。つまり、画像や文字列など、XAML/BAML コードで使用されているリソースの名前がリネームされ、本来の目的がわかりにくくなります。これにより、攻撃者が XAML/BAML コードの構造と機能を理解することが難しくなり、アプリケーション全体のセキュリティが向上します。
strip
このオプションは、難読化された XAML/BAML コードから行情報と空白を取り除きます。攻撃者が元のソースコードを復元する手がかりになりうるデバッグ情報がなくなるため、元のコードのリバースエンジニアリングが難しくなります。また、行情報と空白を取り除くことで、難読化された XAML/BAML ファイルのサイズが小さくなり、効率が上がって読み込みも速くなります。
manual
既定では、Babel Obfuscator は XAML コードまたは BAML コード内で参照されているすべてのパブリックメンバーを自動的に難読化します。より細かい調整が必要な場合は、manual オプションを有効にできます。このオプションを使うと、クイックルールまたは XML ルールファイルを使って、難読化するパブリックメンバーを手動で指定できます。難読化の処理をより細かく制御できますが、設定の手間は増えます。
babel.exe myapp.exe --xaml keys=on --xaml res=on --xaml strip=on<PropertyGroup>
<ObfuscateXaml>keys=true;strip=true;manual=false;res=true;true</ObfuscateXaml>
</PropertyGroup>
<Babel ObfuscateXaml="$(ObfuscateXaml)" />リネームされた XAML バインドメンバーに合わせた文字列リテラルの調整
WPF アセンブリをビルドすると、コンパイラーは通常、各 XAML ページを BAML(XAML のバイナリ形式)としてアセンブリのリソースに埋め込みます。読み込みフェーズで Babel はこの BAML を調べ、x:Name の宣言ごとに、その名前を、ルート要素の型にある対応するコードビハインドのバッキングフィールドにバインドしようとします。バインドに成功したフィールドは、以降の難読化の実行中、XAML にバインドされたものとして扱われます。
このように XAML にバインドされたメンバーがリネームされると、Babel は宣言元の型に対して追加の調整処理を行います。型に含まれるすべてのメソッド本体をたどり、値がメンバーの元の名前と等しい文字列リテラルをすべて、メンバーの新しい名前に書き換えます。照合はリテラルのテキストに対する単純な文字列の一致で行われ、書き換えは、その後の使われ方にかかわらず、型の中の該当するリテラルすべてに適用されます。この書き換えの対象外になるのは、System.Resources.ResourceManager に渡されるリテラルと、ユーザーが指定したリネームのルールで明示的に除外された呼び出しサイトだけです。
その目的は、コードビハインドでの WPF の名前に基づく実行時の検索が、リネーム後も動作し続けるようにすることです。this.FindName("tabPreise") のようなパターン、GetControlByName<T>(string name) などの独自のヘルパー、そのほかソース上の文字列の名前でメンバーや名前付き要素を解決するコードは、リテラルがリネーム後のフィールドと同期されているため、引き続き機能します。照合は属性ではなく構造に基づくため、ヘルパーにオプトインの属性を付けなくても、この調整はユーザー定義のヘルパーを自動的に対象にします。
たとえば、次のコードビハインドがあるとします。
private TabItem tabPreise { get; set; }
void Wire()
{
var tab = GetControlByName<TabItem>("tabPreise");
// ...
}Babel がアセンブリを難読化すると、フィールドがリネームされ、その名前を示すリテラルが新しい名前に合わせられます。
private TabItem cm { get; set; }
void Wire()
{
var tab = GetControlByName<TabItem>("cm");
// ...
}この調整が行われるのは、前述の BAML のバインドが成功した場合だけです。BAML の要素の型を解決できない場合、バインドは何も通知せずに失敗し、メンバーには XAML にバインドされたという印が付きません。典型的な原因は、その型を定義するアセンブリが Babel の検索パスになく、リゾルバーがそのアセンブリについて警告 W00013 を出力することです。フィールドのリネーム自体は XAML のフラグに依存しないため、フィールドには新しい名前が付きます。しかし、元のフィールド名を示すリテラルは変更されないまま残るため、難読化されたアセンブリには整合しない IL が含まれることになります。それらのリテラルに依存していた名前に基づく実行時の検索は、実行時に失敗します。
対処方法は 2 つあり、次の順に適用します。
-
埋め込みの BAML を含むプロジェクトでは、W00013 をビルドを止めるべき警告として扱います。XAML の要素の型を定義している参照先アセンブリを含むディレクトリをすべて、
--addsearchオプションで Babel の検索パスに追加し、BAML リゾルバーがすべての要素の型をバインドできるようにします。すべての要素の型が解決されれば、調整処理は設計どおりに実行されます。 -
元の名前を維持したい場合、たとえば単独の
.xamlファイルをアセンブリと一緒に配布し、そのファイルが元の名前でフィールドを参照している場合は、ロックされた XML ルールでフィールドをリネームから除外します。
<Rule name="keep-xname-tabPreise"
feature="renaming" exclude="true" locked="true">
<Target>Fields,Properties,Methods</Target>
<Pattern>MyNamespace.MyForm::tabPreise</Pattern>
<Pattern>MyNamespace.MyForm::get_tabPreise</Pattern>
<Pattern>MyNamespace.MyForm::set_tabPreise</Pattern>
</Rule>フィールド名が変わらなければ、調整処理で書き換えるものはなく、アセンブリは BAML とも、tabPreise を参照する独立した XAML ファイルとも整合したままになります。
W00013 が単なる情報である場合と、難読化された出力の実際の変化につながる場合の全体像については、コマンドラインリファレンスの BAML バインドの経路における W00013 を参照してください。