前回の続き。

ですが、前回は3年前です。
なのに、皆さん難読化に興味があるのか、Blogの人気記事は難読化です。

今回はリフレクションアゼンブリマージを使った場合の難読化について検証します。

Explanation

前回は、ソースコードの実行結果が、難読化前後で変化しないこと、逆アセンブルした後、人間が容易に理解できるようなソースコードではなくなっていたことを確認しました。
今回は、リフレクションアゼンブリマージがどう変化するのかを実際に見てみます。
サンプルソースは、**GitHub** に置きました。

前回のように、できあがった exe (今回は Obfuscation4.exe) を Eazfuscator.NET の右側の緑の領域に、作成したプログラムをドラッグアンドドロップします。

Reflection

(1) 実行結果の検証

Before

リフレクションの難読化前

After

リフレクションの難読化後

(2) 逆コンパイラによるソースコード解析

リフレクションの逆コンパイラ結果

リフレクションは成功していることがわかります。

Assembly Merge

ではアセンブリをマージしてみましょう。

次はできあがった exe と dll (Obfuscation4Lib.dll) を Eazfuscator.NET の右側の緑の領域に、作成したプログラムをドラッグアンドドロップします。
次にexeを実行すると…

アセンブリマージの実行結果

失敗します。
直感的に、複数のアセンブリを複数ドロップすることでマージできそうですが、それでは失敗します。
結論から言うと、Eazfuscator.NET はアセンブリマージの機能は対応していますが、一工夫必要です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// アセンブリに関する一般情報は以下の属性セットをとおして制御されます。
// アセンブリに関連付けられている情報を変更するには、
// これらの属性値を変更してください。
[assembly: AssemblyTitle("Obfuscation4")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Obfuscation4")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから
// 参照不可能になります。COM からこのアセンブリ内の型にアクセスする場合は、
// その型の ComVisible 属性を true に設定してください。
[assembly: ComVisible(false)]

// 次の GUID は、このプロジェクトが COM に公開される場合の、typelib の ID です
[assembly: Guid("386f10d7-d505-4caf-9d63-98ef058b1088")]

[assembly: Obfuscation(Feature = "merge with Obfuscation4Lib.dll", Exclude = false)]

// アセンブリのバージョン情報は、以下の 4 つの値で構成されています:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// すべての値を指定するか、下のように '*' を使ってビルドおよびリビジョン番号を
// 既定値にすることができます:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

上記コードの中央下の [assembly: Obfuscation(Feature = “merge with Obfuscation4Lib.dll”, Exclude = false)] によってマージするアセンブリを指定できます。

その後、exe だけを再びドラッグアンドドロップして難読化を実行します。
さらに、アセンブリがマージされたことを確認するため、exe だけを別の場所に配置して実行すると上手くいきません

1
2
3
4
5
6
7
ハンドルされていない例外: System.IO.FileNotFoundException: ファイルまたはアセンブリ 'Obfuscation4Lib'、またはその依存関係の 1 つが読み込めませんでした。指定されたファイルが見つかりません。
場所 System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
場所 System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyNameassemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
場所 System.Reflection.RuntimeAssembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean forIntrospection)
場所 System.Reflection.RuntimeAssembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
場所 System.Reflection.Assembly.Load(String assemblyString)
場所 .(String[] )

(゜Д゜) ハア??

という落とし穴があります。
どうも、Mergeには副作用があるようで、難読化等が失敗することがあるようです。
これはヘルプファイルに記載してあります。

Usage of assemblies merging may lead to some side effects which may make obfuscation fail. If such is the case then use the principle of the least common denominator – merge just those assemblies which do not cause obfuscation failure.
Assemblies embedding can be used in conjunction or as an alternative to assemblies merging.

訳) アセンブリマージの使用は難読化の失敗させるかもしれない副作用を引き起こすかもしれません。そのようなケースでは、最小公倍数の原理-難読化失敗を引き起こさないアセンブリだけマージします。
アセンブリの埋め込みは結合に使うことができ、またはアセンブリマージの代用にすることができます。

そのため、

1
2
- [assembly: Obfuscation(Feature = "merge with Obfuscation4Lib.dll", Exclude = false)]
+ [assembly: Obfuscation(Feature = "embed Obfuscation4Lib.dll", Exclude = false)]

のように変更します。
その後に、ビルド、exeのドラッグアンドドロップ実行、exeの隔離、実行をすると、今度こそ成功します。

ですが、先ほどと同じように JetBrains dotPeek で逆コンパイルして確認すると、参照アセンブリにObfuscation4Lib.dllが含まれたままです。

マージ後の逆コンパイル

なのに、exeを隔離して実行しても上手くいくのは、単純にdotPeekが上手く処理できていないだけなのでしょうか?
ただ、思うところはあります。
アセンブリをマージしているのに、 Assembly.Loadでマージ(埋め込み)対象のアセンブリを読み込もうとしているのを認識しているのでしょうか?
何はともあれ、参照済みのアセンブリをわざわざリフレクションでアセンブリをロードするのはやめた方が良いようです。イレギュラーでしょうし。

理由はこれ以上深く追求できませんしませんが、この問題を克服した結果をお見せします。