Dotfuscator の名前の変更による難読化により、入力アセンブリ内の型、フィールド、プロパティ、メソッド、およびパラメーターの名前が変更されます。 わかりやすい名前(ComputeGdp
など)を有効ではあるが意味のない名前(a
など)に変更すると、この変換によってリバース エンジニアリングがはるかに困難になります。 攻撃者はアセンブリを逆コンパイルしても、コード要素の意味や相互の関連性に関する重要な情報を見つけられなくなるでしょう。
オーバーロード誘導
Dotfuscator の特許取得済みのオーバーロード誘導(Overload Induction)™ 技術では、コード要素同士に関係がない場合でも、それらのできるだけ多くに同じ新しい名前を付けることで、リバース エンジニアリングをいっそう困難にします。 たとえば、次の C# コードを名前変更する前と後について考えます。
元のソース コード
private void CalcPayroll(SpecialList employeeGroup) {
while (employeeGroup.HasMore()) {
employee = employeeGroup.GetNext(true);
employee.UpdateSalary();
DistributeCheck(employee);
}
}
オーバーロード誘導によって名前変更した後の逆コンパイル済みコード
private void a(a b) {
while (b.a()) {
a = b.a(true);
a.a();
a(a);
}
}
これらは機能的には等価ですが、前者の目的の方が攻撃者から見てはるかにわかりやすくなっています。 後者の場合は、a
が記述されている用途の中で何を参照しているのかを把握することすら困難でしょう。
拡張オーバーロード誘導(Enhanced Overload Inductio)は、Dotfuscator Professional 専用の機能ですが、さらに一歩先を行っており、フィールドの型、プロパティの型、またはメソッドの戻り値の型を、型のメンバーを見分ける特性として考慮しています。 これにより、同じ新しい名前を付けることができるコード要素をさらに増やせるだけでなく、大部分のソース言語(C#、Visual Basic .NET など)ではそれらの特性に対するオーバーロードが禁止されているため自動的な逆コンパイルがより困難になります。
割り当てファイル
名前変更は損失を伴う変換です。つまり、名前変更でアセンブリを処理すると、宣言していたコード要素の意味情報が失われます。 この点で、名前変更は有用な難読化技法ですが、問題を引き起こす可能性もあります。たとえば、アプリケーションのユーザーがエラーに遭遇した際にスタック トレースを提供するときです。 スタック トレースには難読化された名前が残っているため、問題の診断がはるかに困難になります。
ユーザーがそのような状況を解釈できるようにするため、Dotfuscator では名前変更を実行する際に割り当てファイルを生成します。 このファイルは、元のコード要素の名前を新しい名前に割り当てます。 この割り当てファイルとユーザーのアプリケーション リリースを公的に頒布せずに保持することで、実働環境で報告された問題を解決できます。
このファイルは XML ファイルで、形式は単純です。
名前変更の割り当てファイルの抜粋
<dotfuscatorMap version="1.1">
<mapping>
<module>
<name>DotfuscatorCommunitySample.exe</name>
<type>
<name>DotfuscatorCommunitySample.Friendly</name>
<newname>b</newname>
<methodlist>
<method>
<signature>string()</signature>
<name>get_Name</name>
<newname>a</newname>
</method>
</methodlist>
</type>
</module>
</mapping>
</dotfuscatorMap>
この抜粋には名前変更の結果が示されています。つまり、型 DotfuscatorCommunitySample.Friendly
が b
に、そのメソッド get_Name
が a
に名前変更されています。
割り当てファイルの書き込み先は、ユーザー インターフェイスで、[名前の変更]画面の[オプション]タブにある[出力割り当て]セクションで構成できます。
また、登録ユーザーの場合は、難読化されたアプリケーションによって生成されたスタック トレースを自動的にデコードすることもできます。
対象除外
正常なアプリケーション動作のためには、特定のコード要素を名前変更しないことが必要な場合があります。 そのような場合としては、特定のアセンブリを他のアセンブリの依存関係(ライブラリなど)として使用しなければならない場合 や、コード要素を特定するためにリフレクションを使用する場合などがあります。
Dotfuscator には、名前変更処理からコード要素を除外するための方法がいくつか用意されています。
-
ユーザー インターフェイスまたは宣言による難読化によって、名前変更対象からコード要素を明示的に除外する方法
-
カスタム対象除外規則を定義する方法
-
組み込み規則を有効化する方法
-
アセンブリをライブラリ アセンブリとして指定することで、公的にアクセス可能なすべての型とメンバーを除外する方法
決して名前変更されないコード要素は次のとおりです。
- アセンブリ
- モジュール
-
IL(Intermediate Language:中間言語)で予約されている名前(例:コンストラクターを表す
.ctor
)
対象除外規則
対象除外規則を使用すると、一連の論理的な条件を使って複数のコード要素を除外できます。 これにより、コードの変更に適応できる名前付けポリシーを設定できます。
規則には次の 5 種類があります。
- 名前空間の規則
- 型の規則
- メンバーの規則:メソッドの規則、フィールドの規則、プロパティの規則、イベントの規則
- スーパータイプの規則
- カスタム属性の規則
他の規則の中に入れ子にできる規則があります。 詳細については、個々の規則の種類に関するサブセクションを参照してください。
名前空間の規則
型の名前空間が規則の[名前]プロパティと一致する場合、型は名前空間の規則に適合します。
型が名前空間の規則に適合するなら、型の名前、型のメンバーの名前、型のメソッドのパラメーターの名前はすべて名前変更対象から除外されます。
-
子となる対象:なし(最上位レベルの規則)
-
プロパティ
-
正規表現:[名前]プロパティの解釈を制御します。
-
名前:
-
[正規表現]が
true
の場合:型の名前空間と照合する正規表現。 -
[正規表現]が
false
の場合:型の名前空間と照合する正確な名前。
-
-
説明:人間が理解できる、規則の説明。
-
次のような規則を例に考えましょう。
- 名前空間の規則
-
正規表現:
true
-
名前:
Sample(\..*)?
-
正規表現:
この規則は、名前空間が Sample
であるか Sample
で始まるすべての型に一致します。 言い換えると、この規則は名前空間 Sample
とそのサブ名前空間すべてに一致します。 一致した型の名前、型のメンバーの名前、型のメソッドのパラメーターの名前が、名前変更対象から除外されます。
型の規則
型が型の規則に適合するのは、以下のすべてが真である場合です。
-
型の完全な名前が規則の[名前]プロパティと一致する。
-
型の完全な名前に名前空間が含まれている。たとえば、
Sample.MyDictionary
は、名前空間Sample
に含まれる、MyDictionary
という名前の型です。 -
入れ子になっている型の完全な名前に外の型が含まれている。たとえば、
Sample.MyDictionary/KeyValuePair
は、直前の箇条書きに記述された型に含まれる、KeyValuePair
という名前の型です。 -
ジェネリック型の完全な名前にその型指定が含まれている。たとえば、
Sample.IPair`2<K,V>
は、2 つの型パラメーターを取るジェネリック インターフェイスです。
-
-
型の属性に、規則の[属性指定子]プロパティに
+
プレフィックス付きでリストされているすべての属性が含まれている。 -
型の属性に、規則の[属性指定子]プロパティに
-
プレフィックス付きでリストされている属性が含まれていない。 -
型が、規則の子であるスーパータイプの規則の 1 つに適合する 1 つ以上の型のサブタイプである。
-
型がその派生元の型のサブタイプである。
-
型がそれによって実装されるすべてのインターフェイスのサブタイプである。
-
型 A が型 B のサブタイプで、型 B が型 C のサブタイプなら、型 A は型 C のサブタイプでもある。
-
型がそれ自身のサブタイプでない。
-
-
型が、規則の子であるカスタム属性の規則の 1 つに適合している。
また、型が型の規則に適合する別の型のサブタイプであり、かつ型の規則の[派生型に適用する]プロパティが true
の場合は、その型はその型の規則とも適合します。
型が型の規則に適合し、その規則の[型を除外する]プロパティが true
の場合は、その型の名前が名前変更対象から除外されます。
型のメンバーを対象から除外するには、型の規則の子として追加のメンバーの規則を構成しておく必要があります。 型の名前でなくメンバーの名前のみを対象から除外したい場合でも型の規則は必要ながら、[型を除外する]プロパティを false
に設定しておく必要があります。
-
子となる対象:なし(最上位レベルの規則)
-
プロパティ
-
正規表現:[名前]プロパティの解釈を制御します。
-
型を除外する:
true
の場合は、規則により型の名前が対象から除外されます。false
の場合は、規則が(サブ規則のために)型に一致しても、型の名前は対象から除外されません。 -
派生型に適用する:
true
の場合は、この規則に適合する型から派生する型も、その規則に適合させます。 -
名前:
-
[正規表現]が
true
の場合:型の完全な名前と照合する正規表現。 -
[正規表現]が
false
の場合:型の完全な名前と照合する厳密な名前。
-
-
説明:人間が理解できる、規則の説明。
-
属性指定子:規則に適合させる内容を IL のキーワードでさらに絞り込むことができます。
-
+
のプレフィックスは、型が適合するには指定したキーワードを持っている必要があることを示します。 -
-
のプレフィックスは、型が適合するには指定したキーワードを持っていない必要があることを示します。
-
-
次のような規則を例に考えましょう。
- 型の規則
-
正規表現:
true
-
型を除外する:
true
-
派生型に適用する:
false
-
名前:
.*Provider.*
-
属性指定子:
+public
、-generic
-
正規表現:
この規則に適合するのは、名前に Provider
が含まれるすべての非ジェネリックのパブリック型です。 これらすべての型の名前が名前変更対象から除外されます。
メンバーの規則
「メンバーの規則」という用語は、フィールドの規則、メソッドの規則、プロパティの規則、イベントの規則に対する総称です。 これらの規則は、照合するメンバーの種類のみが異なります(フィールドの規則はフィールドと照合し、メソッドの規則はメソッドと照合する、といった具合です)。
メンバーがメンバーの規則に適合するのは、以下のすべてが真である場合です。
-
メンバーを定義している型がその型の規則の親である型の規則と一致する。
-
メンバーと規則が同じ種類である(たとえば、メンバーがフィールドの場合は、規則がフィールドの規則である必要があります)。
-
メンバーの簡易名が規則の[名前]プロパティと一致する。
-
メンバーの署名が規則の[署名]プロパティと一致する。
-
フィールドの規則の場合、メンバーとは IL でのフィールド型の名前になります(ブール値のフィールドの場合は
bool
、日付時刻値のフィールドの場合はSystem.DateTime
、といった具合です)。 -
メソッドの規則の場合、署名にパラメーターの型と戻り値の型が含まれている(たとえば、パラメーターとして 2 つの符合付き 32 ビット整数を取ってブール値を返すメソッドの場合は
bool(int32, int32)
)。 -
プロパティの規則とイベントの規則には適用されません。
-
-
メンバーの属性に、規則の[属性指定子]プロパティに
+
プレフィックス付きでリストされているすべての属性が含まれている。 -
メンバーの属性に、規則の[属性指定子]プロパティに
-
プレフィックス付きでリストされている属性が含まれていない。 -
メンバーが、規則の子であるカスタム属性の規則の 1 つに適合している。
メンバーの規則に適合するメンバーは、名前変更対象から除外されます。 メンバーがメソッドの場合は、メソッドのパラメーターも名前変更対象から除外されます。
-
子となる対象:型の規則
-
プロパティ
-
正規表現:[名前]プロパティと[署名]プロパティの解釈を制御します。
-
名前:
-
[正規表現]が
true
の場合:メンバーの簡易名と照合する正規表現。 -
[正規表現]が
false
の場合:メンバーの簡易名と照合する正確な名前。
-
-
署名:
-
[正規表現]が
true
の場合:メンバーの署名と照合する正規表現。 -
[正規表現]が
false
の場合:メンバーの署名と照合する正確な署名。
-
-
説明:人間が理解できる、規則の説明。
-
属性指定子:規則に適合させる内容を IL のキーワードでさらに絞り込むことができます。
-
+
のプレフィックスは、型が適合するには指定したキーワードを持っている必要があることを示します。 -
-
のプレフィックスは、型が適合するには指定したキーワードを持っていない必要があることを示します。
-
-
次のような型の規則と、その規則の子であるメソッドの規則を例に考えましょう。
- 型の規則
-
正規表現:
true
-
型を除外する:
false
-
派生型に適用する:
true
-
名前:
Sample\.Contracts\..*
-
属性指定子:
+abstract
-
子の規則:
- メソッドの規則
-
正規表現:
true
-
名前:
.*
-
署名:
.*
- 属性指定子:なし
-
正規表現:
- メソッドの規則
-
正規表現:
これらの規則に適合するのは、Sample.Contracts
名前空間の抽象型のあらゆるメソッドと、それらの抽象型から派生した型のあらゆるメソッド([派生型に適用する]が true
のため)です。 適合したメソッドの名前と、それらのメソッドのパラメーターの名前が、名前変更対象から除外されます。
適合したすべての型の名前は規則により、対象から除外されなくなります(型の規則の[型を除外する]が false
のため)。
スーパータイプの規則
型の完全な名前が規則の[名前]プロパティと一致する場合、型はスーパータイプの規則に適合します。
スーパータイプの規則は親の規則を修飾します。スーパータイプの規則自体では何も対象から除外されません。
-
子となる対象:型の規則
-
プロパティ
-
正規表現:[名前]プロパティの解釈を制御します。
-
名前:
-
[正規表現]が
true
の場合:型の完全な名前と照合する正規表現。 -
[正規表現]が
false
の場合:型の完全な名前と照合する厳密な名前。
-
-
説明:人間が理解できる、規則の説明。
-
次のような型の規則とその子の規則を例に考えましょう。
- 型の規則
-
正規表現:
true
-
型を除外する:
true
-
派生型に適用する:
false
-
名前:
.*
- 属性指定子:なし
-
子の規則
- スーパータイプの規則
-
正規表現:
false
-
名前:
Sample.Contracts.SimpleContract
-
正規表現:
- スーパータイプの規則
-
正規表現:
true
-
名前:
Sample\.IProvider.*
-
正規表現:
- メソッドの規則
-
正規表現:
true
-
名前:
.*
-
署名:
string\(\)
- 属性指定子:なし
-
正規表現:
- スーパータイプの規則
-
正規表現:
この型の規則に適合するのは、Sample.Contracts.SimpleContract
から派生したすべての型や、名前が Sample.IProvider
で始まるインターフェイスを実装したあらゆる型です。 [型を除外する]が true
の場合は、これらの型の名前が名前変更対象から除外されます。
また、メソッドの規則では、署名 string()
を使ってその規則に適合した型のあらゆるメソッドも、名前変更対象から除外されます。
ただし、指定したスーパータイプ自体は名前変更対象から除外されません(SimpleContract
が IProvider
を実装している場合は除きます。その場合は、前者が名前変更対象から除外されます)。
カスタム属性の規則
型やメンバーは、カスタム属性の完全な名前が規則の[名前]プロパティと一致する属性でアノテーションが付けられている場合、カスタム属性の規則に適合します。
カスタム属性の規則は親の規則を修飾します。カスタム属性の規則自体では何も対象から除外されません。
-
プロパティ
-
正規表現:[名前]プロパティの解釈を制御します。
-
継承を可能にする:このプロパティを
true
にすると、親の規則のオーバーライドされたメンバーや派生型がこの規則に適合していない場合でも、それらのメンバーや型を適合させることができます。 -
名前:
-
[正規表現]が
true
の場合:カスタム属性の完全な名前と照合する正規表現。 -
[正規表現]が
false
の場合:カスタム属性の完全な名前と照合する正確な名前。
-
-
説明:人間が理解できる、規則の説明。
-
次のような型の規則と、その規則の子であるカスタム属性の規則を例に考えましょう。
- 型の規則
-
正規表現:
false
-
型を除外する:
true
-
派生型に適用する:
true
-
名前:
Sample.Contracts.SimpleContract
- 属性指定子:なし
-
子の規則
- カスタム属性の規則
-
正規表現:
false
- 継承を可能にする:変化します(下記を参照)。
-
名前:
Sample.Attributes.ReferenceImplementationAttribute
-
正規表現:
- カスタム属性の規則
-
正規表現:
Sample.Contracts.SimpleContract
は、Sample.Attributes.ReferenceImplementationAttribute
というアノテーションが付いている場合には、型の規則に適合します。 そのように適合する場合の処理は、さらに次のように条件分岐します。
-
[継承を可能にする]が
true
の場合は、SimpleContract
から派生したすべての型が型の規則に適合します。 -
[継承を可能にする]が
false
の場合は、SimpleContract
から派生した型が、この属性で修飾されているときに、型の規則に適合します。
どちらの場合も、SimpleContract
に ReferenceImplementationAttribute
で修飾されていないときは、派生した型が型の規則に適合しなくなります。
名前空間のオプション
Dotfuscator には、名前変更する型の名前空間に対する処理方法に関する、構成レベルでのオプションが 3 つ用意されています。
-
フラット化および名前の変更:名前空間が削除されます。元は別の名前空間に含まれていた型も含めたあらゆる型が、グローバルな名前空間に含まれるようになります。
-
Sample.IProvider
をa
に名前変更できます。 -
Sample.Contracts.SimpleContract
をb
に名前変更できます。
-
-
名前の変更のみ:名前空間が名前変更されます。名前空間の階層は元の状態のままですが、ただ名前が変更されるということです。
-
Sample.IProvider
をa.b
に名前変更できます。 -
Sample.Contracts.SimpleContract
をa.a.a
に名前変更できます。
-
-
保持:名前空間が名前変更されなくなります。型の簡易名だけが変更されます。
-
Sample.IProvider
をSample.a
に名前変更できます。 -
Sample.Contracts.SimpleContract
をSample.Contracts.a
に名前変更できます。
-
名前変更対象から除外されたどの型も、元の名前空間を保持します。
プロパティとイベントの削除
アセンブリ内の型では、プロパティとイベントを宣言できます。 これらの各コード要素は、コンパイラで通常生成されるバッキング フィールドとバッキング メソッドに関連付けられています。 たとえば、プロパティ MyProperty
は、フィールド <MyProperty>k__BackingField
とメソッド get_MyProperty
および set_MyProperty
に関連付けられています。 他のコード要素は通常、プロパティやイベントを直接参照せずに、これらのフィールドやメソッドを介してプロパティやイベントを参照します。
このため、プロパティやイベントは、名前変更対象から除外されない場合でも、出力アセンブリには含まれません。 これにより、アセンブリのリバース エンジニアリングがはるかに困難になります。その理由は、バッキング フィールドとバッキング メソッドは、それら自体は除外されないとして、相互にメタデータ レベルの結び付きを持たないためです。