A certain engineer "COMPLEX"

.NETで難読化を試してみる 第6回

前回の続き。
今回は難読化を有効にした際、Any CPUのアセンブリがx86になる問題の解決策についてです。

Explanation

前回は最高レベルの難読化にしたが故に、x86のアセンブリになってしまいました。
難読化レベルがデフォルトでは起こらない問題でしたので、何かしらのオプションが作用しているのは間違いないでしょう。

そこで Babel Obfuscator のヘルプを覗くと下記のような記述が

Obfuscating x64 Assemblies
Depending on the target platform, the Visual Studio compiler emits the desired CPU architecture into the PE header. Users of Visual Studio can set the target platform in the build project’s property page. The default platform is “Any CPU” which indicates that the compiled assembly can run on any version of Windows. When a managed executable is loaded, Windows examines the PE header to determine whether the application requires 32-bit or 64-bit CPU architecture to ensure that it matches the computer’s CPU type. Windows ensures the compatibility of 32-bit applications on 64-bit CPUs with a technology called WoW64 (Windows on Windows64) that emulates 32-bit instruction sets on a 64-bit CPU, albeit with a performance loss. When the application architecture is known, Windows loads the framework execution engine Just-In-Time compiler (JIT) specific to the executable target platform (x86, x64, Itanium). On x64 operating systems, the MSIL code of assemblies targeting "Any CPU” platform is validated by the .NET Framework runtime before execution.
To protect an assembly from being disassembled by tools like .NET Reflector, Babel can insert into any MSIL method invalid byte codes that do not correspond to any MSIL op-code instruction (--invalidopcodes). The resulting assembly is no longer IL verifiable and Windows can execute it only on a WoW64 subsystem. Suppose that the obfuscated assembly is a DLL referenced by an executable that runs in full 64-bit environment. When the runtime tries to load the DLL obfuscated with invalid op-codes, it checks the CPU JIT requirements, and because they do not match, it throws an InvalidProgramException exception. The only way to generate obfuscated DLLs or executables fully compatible with x64 is to disable the injection of invalid op-codes during obfuscation.

訳)
x64アセンブリの難読化
対象のプラットフォームへの依存、Visual Studio コンパイラはPEヘッダーに必要とするCPUアーキテクチャを発行する。Visual Studioのユーザはビルド設定のプロパティページで対象プラットフォームを設定できます。既定のプラットフォームは "Any CPU" で、コンパイルされたアセンブリがどのバージョンのWindowsでも動作できることを示します。(訳注:正しくは、x86, x64のCPUアーキテクチャの違いであって、Windowsのバージョンではない)。マネージドの実行ファイルがロードされるとき、WindowsはPEヘッダーを検証し、アプリケーションが必要とするのが、32bit CPUまたは64bit CPUかどうかを、コンピュータのCPUの種別と一致するかどうかで確認する。Windowsは64bit CPU上で32bit命令セットをエミュレートするWoW64 (Windows on Windows64) と呼ばれるテクノロジーを持つ64bit CPU上での32bitアプリケーションの互換性を保証するが、パフォーマンスの損失を伴う。アプリケーションのアーキテクチャが既知であるとき、Windowsは、実行可能な対象プラットフォーム (x86, x64, Itanium) を指定するための、フレームワーク実行エンジンである Just-In-Time コンパイラ (JIT) をロードします。x64 のOS上で、"Any CPU"プラットフォームを対象としているMSILコードのアセンブリは実行前に.NET Framework ランタイムによって検証されます。
.NET Reflectorのようなツールによる逆アセンブルからアセンブリを保護するために、BabelはいくつかのMSILメソッドの不正なバイトコード-どのMSILの命令コードにも対応しない (--invalidopcodes) -を挿入することができます。その結果のアセンブリは、もはや IL (中間言語) として検証可能ではなく、WindowsはWoW64のサブシステム上でのみでしか実行させることができません。
難読化されたアセンブリが、完全な64bit環境上で実行される実行可能ファイルから参照されることもあるでしょう。ランタイムが不正な命令コードで難読化されたDLLのロードを試みるとき、CPU JIT 要求仕様をチェックし、それらが合致しないが故に、InvalidProgramException例外をスローします。x64と完全な互換性を持つ、難読化されたDLLまたは実行可能ファイルを生成する唯一の手段は、難読化中の不正な命令コードの挿入を無効にすることです。

少し長いですが、要約すると、逆アセンブルを防ぐためのオプション (--invalidopcodes) を使うと、x64上での互換性を維持できなくなるよ、って言ってます。
なので、このオプションを無効にすればAny CPUを維持することができるはずです。

再度ヘルプのオプション説明を見ると、

Use this option to emit invalid MSIL op-codes at the beginning of every method. This will stop reflection tools like .NET Reflector to display IL method disassembly. Warning: This will make your assembly not verifiable.
Normally, code that is not verifiable cannot run on x64 Operating Systems. This option should be switched off if the obfuscated assembly targets x64 platforms.

訳)
このオプションは不正なMSIL命令コードを全てのメソッドの先頭に挿入するために使用します。これは.NET Reflectorのような中間言語メソッドの逆アセンブルを表示するツールを阻止します。警告:このツールはアセンブルの検証不可にします。
通常、検証できないコードはx64OS上では実行できません。このオプションは難読化されるアセンブリがx64プラットフォームを対象とする場合はオフにするべきです。

とあります。
画面上、該当するオプションは、Control Flow ObfuscationEmit Invalid Opcodes になります。
最初の説明を見ると、逆アセンブルを防ぐオプションが悪いので、Protection & Code GenerationAnti Reflection や Prevent ILdasm Disassembly が悪いように見えますが、全く無関係です。

Emit Invalid Opcodesだけ無効にして、再度難読化を実行します。

Emit Invalid Opcodes

(1) 実行結果の検証

オプション有効時

Before

オプション無効時

After

dotPeek での結果がAny CPUを表すようになったことがわかります。
最高の難読化レベルでもこのオプションは外しておいた方が無難かもしれません。

Conclusion

一通り、難読化の処理を見てきましたが、ツールのオプションが豊富で、きちんと使い方を知っておかないと、思わぬ落とし穴にはまります。
国産の難読化ツールもありますが、正直海外製が一歩も二歩もリードしているのが印象でした。

自分は、オープンソースの開発を始めたので、難読化ツールの出番は減りますが、ひょっとしたら会社で使うようなことがあった場合に、この知識は有効になるでしょう。

4 thoughts on “.NETで難読化を試してみる 第6回

  1. oooojt

    たいへん参考になりました。ありがとうございます。
    個人でソフトの公開を考えています。エディションが複数ありますがStandardは実用レベルで問題ないでしょうか。Professionalのほうがよいでしょうか。

    1. Takuya Takeuchi Post author

      ありがとうございます。
      Starndardも十分難読化できますが、Assembly Embeddingもないです。
      XAML/BAMLの難読化もないので、WPFとかはロジック部分だけの難読化になりそうです (未検証)。
      Protection Against ReflectionはProfessinalからなので、これがどの程度効くかによりますが、要求によっては必須かと。
      デモ版もあるので、じっくり検討するのをおすすめします。
      難読化後、きちんと動作するかの検討は必須ですので。

      余談ですが、工夫次第でpublicシンボルの難読化はなくてもいけるかもしれません。
      publicメソッドの内容をprivateメソッドに移動し、それを呼ぶようにすれば、publicシンボルの難読化はある程度不要になりそうです。

      1. oooojt

        その後検討の結果エンタープライズ版を導入しました。
        この価格でこれだけのことができるのは素晴らしいですね。
        こちらの紹介記事のおかげで希望に合うのソフトに出会うことができました。
        ありがとうございました。

コメントを残す

メールアドレスが公開されることはありません。

%d人のブロガーが「いいね」をつけました。