ネイティブの Win32 イメージ (C/C++ でビルドされたものなど) を逆アセンブルするプロセスが、.NET アプリを逆アセンブルするよりも難しいのはなぜですか?
主な理由は何ですか?何かのせいで?
.netアセンブリは共通中間言語に組み込まれています。CLRが適切なシステムで実行するためにコンパイルするときに、実行される直前までコンパイルされません。CILには多くのメタデータがあるため、さまざまなプロセッサアーキテクチャやさまざまなオペレーティングシステム(LinuxではMonoを使用)にコンパイルできます。クラスとメソッドはほとんどそのままです。
.netではリフレクションも可能であり、メタデータをバイナリに保存する必要があります。
CおよびC++コードは、コンパイル時に、選択したプロセッサアーキテクチャおよびシステムにコンパイルされます。Windows用にコンパイルされた実行可能ファイルはLinuxでは機能しません。その逆も同様です。CまたはC++コンパイラの出力はアセンブリ命令です。ソースコード内の関数は、バイナリ内の関数として存在しない可能性がありますが、何らかの方法で最適化されています。コンパイラーは、論理的に構造化されたコードを取り、それを非常に異なって見えるようにする非常に積極的なオプティマイザーを持つこともできます。コードは(時間または空間で)より効率的になりますが、元に戻すのがより困難になる可能性があります。
.NETの実装により、C#、VB、さらにはCLIやCLRを介したC / C ++などの言語間の相互運用が可能になるため、クラスとオブジェクトのプロパティを正しく送信するには、オブジェクトファイルに追加のメタデータを配置する必要があります。これにより、バイナリオブジェクトにはまだその情報が含まれているため、分解が容易になりますが、C / C ++では必要がないため、その情報を破棄できます(少なくともコードの実行では、もちろんコンパイル時に情報が必要です)。
この情報は通常、クラス関連のフィールドとオブジェクトに限定されています。スタックに割り当てられた変数は、相互運用性のために情報が必要ないため、リリースビルドではおそらくアノテーションがありません。
もう1つの理由-最終的なバイナリを生成するときにほとんどのC++コンパイラが実行する最適化は、マネージコードのILレベルでは実行されません。
結果として、コンテナでの反復のようなものは、ILで意味のある名前を持つ関数呼び出しと比較して、ネイティブコードのカップルinc
/アセンブリ命令のように見えます。jnc
結果として実行されるコードは、JITコンパイラがネイティブコンパイラと同様の呼び出しをインライン化するのと同じ(または少なくとも近い)場合がありますが、見ることができるコードはCLRランドではるかに読みやすくなります。
人々はいくつかの理由について言及しています。逆コンパイルではなく逆アセンブルについて話していると仮定して、別のものについて言及します。
x86 コードの問題点は、コードとデータの区別が非常に難しく、エラーが発生しやすいことです。逆アセンブラーは、正しく理解するために推測に頼る必要があり、ほとんどの場合、何かを見落とします。対照的に、中間言語は「逆アセンブル」されるように設計されているため (JIT コンパイラーが「逆アセンブル」をマシン コードに変換できるようにするため)、マシン コードで見られるようなあいまいさが含まれません。その結果、IL コードの逆アセンブルは非常に簡単になります。
逆コンパイルについて話している場合、それは別の問題です。これは、.NET アプリケーションの最適化が (ほとんど) 行われていないことに関係しています。ほとんどの最適化は、C#/VB.NET/etc ではなく、JIT コンパイラによって行われます。したがって、アセンブリ コードはソース コードとほぼ 1:1 で一致するため、オリジナルを突き止めることは十分に可能です。しかし、ネイティブ コードの場合、一握りのソース行を変換する方法が 100 万通りあります (まったく、no-op でさえ、さまざまなパフォーマンス特性を備えた膨大な数の異なる方法で記述されています!)。 .
一般に、C++ コードと .NET コードの逆アセンブルに大きな違いはありません。C++ はより多くの最適化などを行うため、逆アセンブルがより困難ですが、それは主な問題ではありません。
主な問題は名前です。逆アセンブルされた C++ コードには、すべて A、B、C、D、...A1 などの名前が付いています。このような形式のアルゴリズムを認識できない限り、逆アセンブルされた C++ バイナリから抽出できる情報は多くありません。
反対側の .NET ライブラリには、メソッドの名前、メソッド パラメーター、クラス名、およびクラス フィールド名が含まれています。逆アセンブルされたコードの理解がはるかに容易になります。他のすべてのものは二次的なものです。