質問:
なぜ分解は正確な科学ではないのですか?
Einar
2013-08-04 18:44:00 UTC
view on stackexchange narkive permalink

初心者はこちら

ウィキペディアから

逆アセンブルは正確な科学ではありません:可変幅命令を使用するCISCプラットフォーム、または自己変更コードが存在する場合、1つのプログラムで2つ以上の合理的な逆アセンブリが発生する可能性があります。プログラムの実行中に実際に遭遇する命令を決定することは、証明された解決不可能な停止問題に帰着します。

  1. CISCでの逆アセンブルが正確な科学ではないのはなぜですか?逆アセンブルされたコードを再アセンブルすると、異なるが類似したオペコードが生成される可能性があることを理解しています。同じ命令ですが、類似しているため、結果のプログラムに影響を与えることはありません。また、それが正確な科学ではない場合、つまりassemble(disassemble(opcode))!= opcodeの場合、CPUはどのようにしてオペコードのストリームを解釈する方法を決定できますか?

  2. ちなみに、これはRISCプラットフォームでの分解が正確な科学であることを意味しますか?

  3. ol>
より抽象的な理由は、コンパイルが1対1ではないことである可能性があります。すべての高レベルのステートメントが一意のオペコードのセットにコンパイルされるわけではありません。数学のように、あなたはドメインと範囲を持っています。コンパイルにより、ドメインが範囲にマップされます。ドメインが範囲よりもはるかに大きい場合、コンパイルは1対1ではありません。したがって、一般的な(一般的な意味では、解決策は必要からではありませんが、選択を行います)逆関数または再コンパイル/逆アセンブリ関数はありません。
五 答え:
0xea
2013-08-04 18:52:54 UTC
view on stackexchange narkive permalink

最初の質問に答える。最大の問題は、データをコードから実際に分離できないことです。分解には、基本的に2つのアプローチがあります。

  1. 線形スイープ
  2. 再帰的走査
  3. ol>

    線形スイープを使用するディサセンブラーは、あるアドレスで開始し、分解します。ジャンプしたり、分解されたコードについて推論したりせずに、最後まで1つずつ指示します。たとえば、SoftICEとWindbgは線形スイープを使用します。いつ停止するかわからないため、これは問題になる可能性があります。命令がどこで終わり、どこからデータが始まるのかわかりません。そのためには、セクションサイズなどの実行可能形式のメタデータに依存する必要があります。それが問題の原因です。

    一方、再帰的トラバーサルアルゴリズムはジャンプと呼び出しを考慮し、dissasemblerはジャンプに従い、実際に実行されるコードのみを分解します。たとえば、IDAとOllyDBGはこのアプローチを使用します。それは本質的に何がコードで何がデータであるかを知っているという利点があります。しかし、明らかに欠点があります。まず、純粋な再帰的​​トラバーサルでは、すべてのコードが分解されるわけではありません。たとえば、直接参照されていない関数、アドレスを計算することによって実行時に呼び出される関数は検出されません。繰り返しますが、エンジンにはこの問題を回避するためのヒューリスティックがいくつかあります。

    たとえば、いくつかの命令をジャンプバックするプログラムを考えてみましょう。ただし、以前に分解して実行した命令の先頭に到達する代わりに、その途中でジャンプします。解体者はどちらを表示するかをどのように決定する必要がありますか?それがコーダーの意図であった場合、両方が有効である可能性があります。再帰的トラバーサルdissasemblerは、おそらく最初のdissasembledバージョンを表示し、関数の途中へのジャンプをマークするだけです。しかし、バイトを詳細に調べないと、ジャンプ後に実際に何が実行されるかを判断するのは困難です。

    他にも多くの例があります。

    これらのアプローチの両方に関するもう1つの大きな問題は、コードの自己変更です。静的に行うことはできません。

    CPU自体は、コードを実行しているため、これらの問題は発生しません。つまり、動的です。

    2番目の質問に答えると、これは問題ではないと思います。 CISCアーキテクチャのみの問題であり、これらの一部はRISCにも適用できます。

実際、彼らはCISCではなくフォンノイマンアーキテクチャ(自己修正を可能にする)と言うべきです。
@Ruslan:それは自己修正とは何の関係もありません。重要なのは、可変幅命令ではあいまいな解釈が可能になるということです。例えば。命令の途中で「JMP」を実行すると、別個の有効な命令シーケンスが読み取られる可能性があります。これは、RISC *(ほぼ?)*が常に存在する固定幅の命令セットでは不可能です。
はい、私が述べたように、そして0xC0000022Lがさらに説明しているように、自己変更コードだけが問題ではありません
*「自己修正コードだけが問題ではありません」*-まあ、それは** a **の問題でもありません。固定幅アーキテクチャでは、自己変更コードの逆アセンブルに問題はありません。もちろん、逆アセンブラは、ランタイム変更の*後*(これは決定不可能な問題です)*でアセンブリがどのようになるかを知ることはできませんが、それはバイナリを逆アセンブルする範囲外です。
OllyDBGは線形スイープを使用していますか?
私はそれがそうなるとかなり確信していました、しかし私は今その主張のための参照を見つけることができません。 SoftICEと混ぜていたかもしれません。回答で修正されました。ありがとう!
debray
2013-08-04 20:26:15 UTC
view on stackexchange narkive permalink

前の回答で説明した問題に加えて、プログラムが複数の逆アセンブルを持つことができる別の方法があります。次のロジックを持つコードについて考えてみます(構文の肉屋についてお詫びします):

buf = ... 文字列 i> ...
val = 読み取り b>()
if b>(val == 7){
mprotect b>(buf、...、PROT_EXEC); / * bufを実行可能にする* /
goto b> buf; / * bufを実行します* /
}
else b> {
print b>(buf);
}

この場合、 buf がコード(したがって逆アセンブルする必要がある)であるか、データ(したがって逆アセンブルしないでください)であるかは入力によって異なります。したがって、プログラムには複数の可能な分解があります。これは、コードが基本的にデータと区別がつかず、RISCとCISCとは関係がないためです。

このホワイトペーパーでは、コードをもっともらしいデータに偽装する方法について楽しい議論があります。

J。メイソン、S。スモール、F。モンローズ、G。マクマヌス。英語のシェルコード。 コンピュータと通信のセキュリティに関する第16回ACM会議の議事録(CCS)、イリノイ州シカゴ。 2009年11月。 [PDF]

0xC0000022L
2013-08-04 19:02:27 UTC
view on stackexchange narkive permalink

コメントの意味は、CISCでは、x86を例にとると、分解の合理的な表現がいくつか考えられるという事実だと思います。しかし、これは典型的なRISC実装についても部分的に言えると思います。ただし、アセンブラは、基本(RISC)オペコードのさまざまな組み合わせで表されるCISCのようなニーモニックのようなものを提供できます。

たとえば、 rep プレフィックスは、多くのオペコードでは意味がありませんが、リバースエンジニアにとって困難にするために過去に使用されてきました。しかし、最終的に分解を見ている人間にとって、どちらがより良い表現ですか? rep プレフィックスが付いているもの、または付いていないもの(CPUの動作をより反映します)。

逆アセンブラが検出した内容に忠実である場合は、 rep プレフィックス。一方、機械語を人間が読めるようにするのは逆アセンブラの仕事です。したがって、その余分なマイルを移動し、簡潔にするために冗長なプレフィックスが削除されていることを確認することは完全に理にかなっています。同様に、 nop (操作なし)を表すマルチバイトオペコードは、完全に削除するか、残りの部分から目立つように表すか(IDAがアライメントバイトで行うように)、またはそれぞれを nopとして表すことができます。 それぞれ(1バイトバージョンと5バイトバージョンの場合も同様)。

したがって、私の観点からは、マシンコードを分解することは正確な​​科学です。 は正確なニーモニックです(ここでは jne / jnz の二重性は無視しましょう)。そうでなければ、あなたが指摘するように、CPUはどのように動き、何をすべきかを理解しますか?ただし、人間のリバースエンジニアに表現を与えるために、読みやすさと理解のために後処理されることがあります(私はよく言いますか?)。ここで逆アセンブラと逆アセンブラが異なります。


もう1つの問題は、考えられるすべてのコードパスに従うために、ヒューリスティックを使用する必要があることです(これは、優れた逆アセンブラの際立った機能の1つです)。そうしないと、データとコードを区別するのが困難になります。しかし、私の知る限り、「CISC」をその特性で特定することはできないので、これがコメントの主な理由ではないと思います。

Codoka
2015-04-10 13:23:05 UTC
view on stackexchange narkive permalink

逆アセンブルの重要な問題は、バイナリ実行可能ファイルのデータからコードを正確に分離することにあります。このためには、間接ジャンプ(実行時に計算されたアドレスへのジャンプ)のターゲットを正確に決定できる静的分析が必要になります。 スイッチのターゲット計算やC ++のvtablesなど、間接ジャンプの例がいくつかあります。

このタイプの静的分析が存在する場合、決定不可能であることが知られている停止問題を解決することができます。したがって、そのような静的分析は存在できません。これに基づいて、逆アセンブルツールは、間接ジャンプターゲットを決定するためにヒューリスティックおよび/または近似に依存する必要があります。詳細については、 ARM分解の健全性の同様の質問への回答をご覧ください。コード/データの分離のタスクは、バイナリが難読化されているか、自己変更コードがある場合、さらに困難になります。

バイナリからコードとデータの近似値を抽出しました。次に、セマンティクスをそれらに付加するという問題に対処する必要がありますが、これも困難です。たとえば、 struct {int i; int j;} は、命令でアクセスすると int arr [2] のようになります。したがって、デバッグシンボルなどの外部情報に依存せずに、実行されたコードに一意のセマンティクスを付加することは困難です。

より大きな配列は、その要素がどのようにアクセスされるかによって構造と区別できます。おそらく、 `arr [2]`と同じくらい小さい配列ですらあります。
@Jongwareは同意します。これは、分解において複数の有効な意味を持つという考えを説明するための単純な例です。
perror
2017-06-20 22:03:54 UTC
view on stackexchange narkive permalink

予備的注意

まず、バイナリプログラムの正しい逆アセンブルとは何かについて合意する必要があります。私は次の定義を提案します:

バイナリプログラムの正しい分解は、プログラムが入力したものに関係なく、プログラムによって実行できるすべての可能な命令のセットを提供します。

別の言い方をすれば、プログラムが実行できるすべての入力に対して、プログラムの実行可能なすべての命令を公開するということです。

停止問題

ここで、Turingマシンの停止問題と並行して、次のように定義できます( Wikipedia):

停止問題は、任意のコンピュータプログラムの説明と入力から、プログラムの実行を終了するか、永久に実行し続けるかを決定する問題です。

この(明らかに)非常に単純な問題は、Turingによって決定できないことが示されています。つまり、プログラムで一定量のケースを自動的に処理できたとしても、一部の病的なケースは常に回避されます。マシン/プログラムが指定された入力で停止するかどうかを判断できない私たちのプログラム。

そしてもちろん、そのような病理学的ケースは無限に多いです(したがって、それらを列挙する望みはありません)特別な場合として次々に)。

逆アセンブルの問題に戻りましょう!

プログラムのすべての可能なパスを探索することは、停止問題のために決定不可能です。 >!

実際、停止問題を定式化する2つの方法は、アクセシビリティ問題です。ここでは、特定のポイントに到達できる入力があるかどうかを知りたい場合があります。プログラム。また、プログラムがメモリ内の特定の場所に到達して命令として解釈できるかどうかを知ること(つまり、命令ポインタはある時点でこのアドレスの値を取得します) アクセシビリティの問題。

つまり、分解は決定不能です

しかし、現実の世界では?

わかっています、わかっています、これは数学だけです...現実ではありません...ほとんどの難読化(自発的かどうか)は解決され、バイナリコードから自動的に削除されます...

これは本質的に、これらの難読化を行った人々が決定不能に慣れていなかったためです。問題...

決定不能問題の計算をプログラムに挿入することを想像してみてください。あるいは、それに適用される自動推論を壊すほど難しい複雑なものを挿入することさえ想像してみてください。

例を挙げると、Collat​​zシーケンス( Wikipedia)を見てみましょう。このシーケンスは、しばらくすると常に1で終了すると推測されます。しかし、その背後にある算術問題は非常に複雑であるため、この推測は約1世紀以来成り立っています...これは使用するのに完全な不透明な述語です!もちろん、そのような推測の証拠が存在する可能性もありますが、この問題は、その上に構築を開始し、プログラムの状態空間を探索するコンピューターを混乱させるほど複雑です。

実際、これは現在の問題です。今日の強い難読化における研究の方向性...私たちは以前に使用された小さなトリックでほとんど終わり、人々はより根拠のある問題に基づいて物事を構築し始めます。暗号化と比較するためのソフトウェアの難読化に関して、シャノン(情報理論の父)に相当するものをまだ見逃している場合でも。

最後の言葉

したがって、分解の問題は停止の問題と強く関連していることがわかりました。また、非常に複雑な問題を使用することが、最新のソフトウェア難読化の次のステップになる可能性もあります。

現在の分解ツールは、分解技術の最先端に固執しなければならない場合にできることよりもはるかに遅れているという事実について、最後に一言申し上げます。現在のツールがどれほど先史時代のものであるかを見ると、私はいつも苦痛で泣いています...しかし、すべての現代的な技術を実践するには、開発と保守に多大な労力が必要になるため、誰もそれを行う準備ができていないようです(しかしこれは私のものだけです)謙虚な意見)。



このQ&Aは英語から自動的に翻訳されました。オリジナルのコンテンツはstackexchangeで入手できます。これは、配布されているcc by-sa 3.0ライセンスに感謝します。
Loading...