8B EC 56 8B F4 68 00 70 40 00 FF 15 BC 82 40
上記のようなセンセーションはさまざまな方法でセグメント化でき、各セグメントは対応するアセンブリ命令に変換できますが、各バイナリ実行可能ファイルには唯一のDEFINITEアセンブリがあります。あいまいさを回避する数学的原理は何ですか?
アップデート
ほとんどの票の答えは、実際には私の質問にまったく答えません。
8B EC 56 8B F4 68 00 70 40 00 FF 15 BC 82 40
上記のようなセンセーションはさまざまな方法でセグメント化でき、各セグメントは対応するアセンブリ命令に変換できますが、各バイナリ実行可能ファイルには唯一のDEFINITEアセンブリがあります。あいまいさを回避する数学的原理は何ですか?
アップデート
ほとんどの票の答えは、実際には私の質問にまったく答えません。
あなたの出発点を知る。
つまり、命令の特定の開始バイトが与えられると、その命令がどこで終了するかが明確になり、次の命令の開始バイトが得られ、続行できるようになります。任意のメモリブロックが与えられると、最初の命令がどこから始まるかを知らずに、それを個々の命令に分割することは不可能です。
より数学的な観点からは、バイトが別の有効な命令のプレフィックスである有効な命令はありません。したがって、が有効である場合、それは有効ではないことがab
わかります。したがって、1つの命令である必要があり、次の命令の開始です。ab cd
ab
cd
私があなたの質問を正しく理解しているなら、あなたはその理由を理解しようとしています
8B EC 56 8B F4 68 00 70 40 00 FF 15 BC 82 40
たとえば、分割することができます
8BEC 568BF4 68007040 00FF 15BC 8240
言うのではなく、
8B EC568B F4 68007040 00FF 15BC 8240
これは、アーキテクチャのISAによって完全に指定されます。このドキュメントでは、一連のバイトから命令がどのように一意に構築されるかを正確に説明しています。
ISAが適切に形成されるためには、単一の一連のバイトが最大で単一の一連のデコードされた命令に対応できます(無効な命令がある場合はそれより少なくなる可能性があります)。
もう少し具体的に説明するために、x86の例を見てみましょう。各バイトが何に対応するかを知りたい場合は、こちらをご覧ください。
たとえば、00で始まる命令は加算であることがわかります(追加のパラメータは特定のエンコーディングで次のバイトにあります)。
また、一部の値は、実際には次の命令を変更するプレフィックスであることがわかります(0F-オペコードスペースを拡張するプレフィックス、26、2E、36、3E、64、65、66、67、F0、F2、F3)、およびそれらのいくつかは、正確な次の指示に基づいて異なる意味を持ちます。これらはオペコードではありませんが、オペコードの引数のエンコーディングを変更したり、完全に新しいオペコードスペースを導入したりすることができます(たとえば、SSEは0Fを使用します)。
全体として、逆アセンブラのおかげで、x86エンコーディングは非常に複雑です。
まず、RISCアーキテクチャとCISCアーキテクチャを区別する必要があります。
RISCアーキテクチャでは、通常、同じサイズの命令があるため、あいまいさを表現することはできません。CPUは、たとえば命令ごとに4バイトをフェッチします。これは、どこかから開始する必要があるため(CPUには、提示したようなシーケンスだけではなく、確実に開始点があります)、正しい位置合わせは問題ありません。
CISC命令セットで何が起こるかは基本的に同じです。プログラムのエントリポイントから開始して、オペコードに従って命令をフェッチします。次の命令の長さや最後の命令がどこで終了したかがわからないということは起こらないので、あいまいさを数学的に区別する方法を知る必要はありません。
したがって、すべての命令を分離する方法を尋ねるのは、
thepenisonthetable
数学的な証明はありませんが、どの文字が一緒に正しいか、どの文字が意味をなさないかはわかっています。前の文には「son」が含まれていますが、「ison」から取得されていることがわかります。意味のあるフレーズがなければそう言うことはできませんが、CPUは意味のあるプログラムしか実行しないので、ポイントは何ですか?
したがって、CPUが前の文で機能する場合、最初の意味のある命令「the」、次に「pen」、「is」、「on」が検出され、「son」はとにかく認識できませんでした。
編集:
明確にするために、CISCアーキテクチャでは、あいまいさを持たないようにする必要がある唯一の制約は、別のプレフィックスである命令を持たないようにすることです。16進数ではなく文字azで構成される有限のアルファベットを想定しましょう(実用的な目的のため)。
プログラムカウンターが
abbcbcaabdeffabd
あなたはそれabb
が完全な指示であることができます。その場合ab
、有効な命令にはなりません。そうでない場合、CPUはどこで停止するかを知ることができず、同時にabbc
命令にもなり得ないか、問題が発生する可能性があります。それを維持することは、例えば、それca
が次の命令である可能性があり、c
できなかったし、cbc
どちらでもない。
この引数を文字列全体に拡張できます。CPUが、バイナリの次のバイトが命令の最初のバイトを指している状態にあり、他の命令のプレフィックスである命令がない場合、次の状態でプログラムカウンターが表示されます。次の正しい命令の最初のバイトを指します。
バイナリを16進エディタで開き、データの一部をコピーして逆アセンブラに貼り付けると、おそらく完全な命令をコピーすることはできません。あなたの例を使ってみましょう..WindowsXP32ビットSP3英語で、私が組み立てると、次の8B EC 56 8B F4 68 00 70 40 00 FF 15 BC 82 40
ようになります。
Hex dump Command
8BEC mov ebp,esp
56 push esi
8BF4 mov esi,esp
68 00704000 push 407000
FF15 BC824000 call dword ptr ds:[4082bc]
あなたがそれが完全に異なって組み立てられたのを見ることができるように、以下の他の人の答え...
さて、組み立てる代わりに、最初にオペコード8B EC 56 8B F4 68 00 70 40 00 FF 15 BC 82 40
を追加したとしましょう。C0
C0 8B EC 56 8B F4 68 00 70 40 00 FF 15 BC 82 40
次に、命令で1バイトが何をしたかを以下で確認します。
Hex dump Command
C08B EC568BF4 68 ror byte ptr ds:[ebx+f48b56ec],68
0070 40 add byte ptr ds:[eax+40],dh
00FF add bh,bh
15 BC824000 adc eax,4082bc
ご覧のとおり、完全に変更されています。
プロセッサは、どこから開始し、オペコード命令によって命令に対してどの引数を取るかを知っています。最初の例では、最初のオペコードは8B
、その後に別のバイトが続く可能性があることを認識しているためです。このバイトがEC
そうである場合、命令はここで終了し、それはを意味しmov ebp, esp
ます。
2番目の例では、それはで始まり、C0
その後に別のバイト、つまり別の命令を続けることができます。次にC08B
、は命令であり、EC568BF4 68
は引数です。
プロセッサの内部に巨大な(しかしナノの)回路があり、if(条件)のチェーンのように動作し、16進コード(またはオペコード)の値に応じて「どこに行くか」または「どのように振る舞うか」を知っていると想像してください。 。
あなたの質問に対する答えは、やや派手な「出発点を知っている」のように聞こえますが、もう少し冗長なものが必要な場合もあります。あなたの文字列を考えると:
8B EC 56 8B F4 68 00 70 40 00 FF 15 BC 82 40
そして開始点(8Bが開始点であるとしましょう)バイトの可能な解釈は1つだけです。
したがって、1つの操作が8B EC 56 8B(操作の長さなどによって異なります)であるとすると、次の操作はF4 68です...この場合、マシンが操作を解釈しようとすることはできません56 8BF468。その時点で操作が終了することはありませんでした。
ここで、開始点が56の場合、そのグループを取得することはできますが、前述のグループのいずれも取得することはできません。
メモリのレイアウトは非常に具体的であり、開始点/ジャンプ点は正確で寛容ではありません。これらはコード自体と同じくらい確実に必要です。
リストしたシーケンスは、正確に1つの番号を示しています。バイナリでは、
100010111110110001010110100010111111010001101000000000000111000001000000000000001111111100010101101111001000001001000000
です。10進数では、726522768938664460674442126658667072
です。これらはすべて、まったく同じ値を書き込むためのまったく異なる方法です。特定のアーキテクチャのISAは、ビットをフィールドに分割し、それらに意味を割り当てます。ほとんどのプロセッサには、これらのフィールドの各ビットに割り当てられた意味を説明するマニュアルが簡単に入手できます。
逆アセンブルをコードの実行順序と直線的に混同しないでください。バイナリは、既知の場所から開始して実行順にデコードされます。さまざまな理由による意図的なハッキングを除いて、あいまいさはありません。
可変ワード長の命令セット用の逆アセンブラを作成してみてください。結局のところ、実行順に実行する必要があります。実行時に計算されたアドレスに基づくブランチもあるため、プログラムの一部を逆アセンブルすることしかできません。最新のコンパイラで生成されたコードは、古い手作業で組み立てられたコードよりもはるかに優れています。たとえば、古いスタンドアップアーケードゲームでは、条件付きブランチの前に、条件の1つだけが満たされることを保証する命令があり(なぜそこにあるのか?私たちは決してわかりません)、条件付きブランチに続くデータは、他のオペコードとの衝突に遭遇するような方法。
逆アセンブラを無効にしようとする古いdosプログラムには自己変更コードがあり、1つステップで変更が行われる場合、1つの命令が1つまたは2つの命令の前に別の命令を変更しますが、フルスピードで実行すると、その命令はすでにパイプラインにフェッチされています。メモリ内の変更/破損したものは使用されませんでした。かなりきちんとしたトリック。
とにかく、あなたの質問への答えは、線形順序でバイトを見るのではなく、ベクトルテーブルのリセットおよび他のベクトルによって定義されたアドレスから始まる実行順序でそれらを見るということです。
有効な開始アドレスが何であるかについて、他の場所にもいくつかの手がかりがあるかもしれません。常にリセットベクトルアドレスがあり、通常は割り込みベクトルアドレスがあり、これらはすべてコードブロックの有効な開始点である必要があります。さらに便利なことに、デコードしようとしているブロック内のアドレスを参照するジャンプまたは呼び出し命令を他の場所で見つけた場合、それは別の開始アドレスを提供します。
私はあなたの心配を知っていると思います、そして私が正しいことを知っている限り-プログラムカウンターが1つによって動揺し、それが無効な命令または意図しない命令を実行する原因になる場合、プログラムはおそらくクラッシュします。Trueであり、データブロックに遭遇してそれを実行しようとした場合も同様です。少なくとも後者は、コードとデータが別々のメモリスペースにあり、ビット幅が異なる可能性があるハーバードアーキテクチャを使用することで回避できます。
他の方向について考えるのは面白いかもしれません。他の人のために簡単にセグメント化できるようにコードをどのように設計する必要がありますか?UTF-8のように、シーケンスを開始するバイトの最上位ビットを0にし、シーケンスの途中のビットを1にする必要があります。次に、ランダムな位置から開始すると(バイトがどこにあるかがわかっていると仮定して)、次のシーケンスを簡単に見つけることができます。さらに一歩進んで、シーケンスの開始を簡単に見つけられるように、純粋なビットストリームをどのようにコーディングしますか。そのようなコーディングによって何ビットが無駄になりましたか?
数学についてお伺いしましたが、関連するトピックは「符号理論」「可変長コード」「プレフィックスコード」だと思います。
塩基対のシーケンスからどのように遺伝子を見つけますか?