web-dev-qa-db-ja.com

オペコードを変更するためのプロセッサのマイクロコード操作?

私は最近、セキュリティを曖昧に実装する極端な方法を考え、それが可能かどうかを皆さんに尋ねたかったのです。

特別なプロセッサのドキュメントにアクセスできない人は、マシンの命令セットを難読化するために、CPUのマイクロコードを変更できますか?

マシンがそのようなプロセッサーで起動するために他に何を変更する必要がありますか?BIOS操作で十分でしょうか?

19
d33tah

最新のx86プロセッサはランタイムマイクロコードのアップロードを可能にしますが、形式はモデル固有であり、文書化されておらず、チェックサムおよび場合によっては署名によって制御されます。また、ほとんどの命令はハードワイヤードであるため、マイクロコードの範囲は今日、やや制限されています。詳細については、 この回答 を参照してください。最新のオペレーティングシステムはブート時にマイクロコードブロックをアップロードしますが、これらのブロックはバグ修正の目的でCPUベンダー自身によって提供されます。

(アップロードされたマイクロコードは、フラッシュやEEPROMではない内部の専用RAMブロックに保持されることに注意してください。電源が切断されると、失われます。)


更新:マイクロコードとは何か、何ができるのかについて、誤解や用語の混乱があるようです。そのため、いくつかの長い説明があります。

最初のマイクロプロセッサの時代には、トランジスタは高価でした:チップ製造工場では希少なリソースである多くのシリコン領域を使用していました(チップが大きいほど、不良率が高くなります。チップ全体が機能しない)。したがって、チップ設計者は多くのトリックに頼らなければなりませんでした。そのうちの1つはマイクロコードです。その時代のチップのアーキテクチャは次のようになります。

Z80 architecture

(この画像は このサイト から恥知らずに略奪されました)。 CPUは、データバスを介してリンクされた多くの個別のユニットにセグメント化されています。架空の "add B, C"命令が何を必要とするかを見てみましょう(レジスタBとレジスタCの内容の加算、結果はBに格納されます):

  1. レジスタバンクは、Bレジスタの内容を内部データバスに配置する必要があります。同じサイクルの最後に、「TEMP」ストレージユニットはデータバスから値を読み取って格納する必要があります。
  2. レジスタバンクは、Cレジスタの内容を内部データバスに配置する必要があります。同じサイクルの最後に、「A」ストレージユニットはデータバスから値を読み取って格納する必要があります。
  3. Arithmetic and Logic Unit(ALU)は、2つの入力(TEMPとA)を読み取り、加算を計算する必要があります。結果は、バスの次のサイクルで出力に表示されます。
  4. レジスタバンクは、内部データバス上のバイトを読み取り、それをBレジスタに格納する必要があります。

プロセス全体では、百音4クロックサイクルかかります。 CPUの各ユニットは、特定の順序で順番に受信する必要があります。各CPUユニットに起動信号を送信するコントロールユニットは、すべての命令のすべてのシーケンスを「認識」している必要があります。これは、マイクロコードが介入する場所です。 Microcodeは、このプロセスの基本ステップのビットワードとしての表現です。各CPUユニットには、各マイクロコードにいくつかの予約ビットがあります。たとえば、各ワードのビット0〜3はレジスタバンク用であり、操作するレジスタをエンコードし、操作が読み取りか書き込みかを示します。ビット4〜6はALU用で、どの算術演算または論理演算を実行する必要があるかをALUに伝えます。

マイクロコードを使用すると、制御ロジックはかなり単純な回路になります。これは、マイクロコード内のポインター(ROMブロック)で構成されます。各サイクルで、コントロールユニットは次のマイクロコードワードを読み取り、マイクロコードビットを専用線で各CPUユニットに送信します。命令デコーダは、オペコード(プログラマが認識し、RAMに格納される「マシンコード命令」)からマイクロコードブロックのオフセットへのマップです。デコーダマイクロコードポインターを、オペコードを実装するシーケンスの最初のマイクロコードワードに設定します。

このプロセスの1つの説明は、CPUが実際にマイクロコードを処理することです。マイクロコードは、プログラマーが「マシンコード」と考える実際のオペコードのエミュレーターを実装します。

ROMはコンパクトです:各ROMビットは、1つのトランジスタとほぼ同じサイズか、わずかに小さくなります。これにより、CPU設計者は多くの複雑な異なる動作を小さなシリコンスペースに格納できました。したがって、Atari ST、Amiga、およびSegaメガドライブのコアプロセッサである非常に由緒ある Motorola 68000 CPU は、約40000のトランジスタと同等のスペースに収まり、その3分の1はマイクロコードで構成されます。その小さな領域で、15個の32ビットレジスタをホストし、有名なアドレッシングモードの完全な関連機器を実装できます。オペコードはかなりコンパクトでした(したがってRAMを節約しています)。マイクロコードワードは大きくなりますが、外部からは見えません。


[〜#〜] risc [〜#〜] プロセッサの登場により、このすべてが変更されました。 RISCは、マイクロコードが複雑な動作を伴うオペコードを許可する一方で、命令のデコードに多くのオーバーヘッドを伴うという認識に基づいています。上記で見たように、単純な追加には数クロックサイクルかかります。一方、当時(1980年代後半)のプログラマーは、compilersの使用を好んで、アセンブリをますます避けています。コンパイラは、一部のプログラミング言語を一連のオペコードに変換します。コンパイラが比較的単純なオペコードを使用するのは偶然です。複雑な動作をするオペコードは、コンパイラのロジックに統合するのが困難です。したがって、最終的な結果は、プログラマーが使用しなかった複雑なオペコードのために、マイクロコードがオーバーヘッドを意味し、実行が非効率になることでした。

RISCは、簡単に言えば、CPU内のマイクロコードの抑制です。プログラマ(またはコンパイラ)が見るオペコードareマイクロコード、または十分に近い。つまり、RISCオペコードはより大きくなり(通常、元のARM、Sparc、Mips、Alpha、PowerPCプロセッサーのように、オペコードあたり32ビット)、より規則的なエンコーディングになります。 RISC CPUは、サイクルごとに1つの命令を処理できます。もちろん、命令はCISCの対応するものよりも少ない機能を実行します(「CISC」は68000のように非RISCプロセッサが実行するものです)。

したがって、マイクロコードでプログラミングする場合は、RISCプロセッサを使用します。真のRISCプロセッサには、マイクロコードはありませんstricto sensu;すべてのCPUユニットのアクティブ化ビットに1対1対応で変換されるオペコードがあります。これにより、CPUのスペースを節約しながら、コードを最適化するためのオプションがコンパイラーに提供されます。最初の [〜#〜] arm [〜#〜] は、68000未満の30000トランジスタのみを使用し、同じクロック周波数で大幅に高い計算能力を提供しました。支払う代価はより大きなコードでしたが、そのときRAMはますます安価になりました(そのとき、コンピュータRAMのサイズがmegabytesでカウントされ始めたのです。単なるキロバイトの代わりに)。


その後、混乱し、事態は再び変化しました。 RISCはCISCプロセッサを停止しませんでした。 下位互換性 は、コンピューティング業界において非常に強力な力であることが判明しました。これが、現代のx86プロセッサー(Intel i7以降など)が1970年代後半の8086向けに設計されたコードを実行できる理由です。したがって、x86プロセッサhaveは、複雑な動作を伴うオペコードを実装します。その結果、最新のプロセッサには、命令コードを2つのカテゴリに分離する命令デコーダがあります。

  • コンパイラーが使用する通常の単純なオペコードは、固定された動作に「ハードワイヤード」されたRISCのように実行されます。加算、乗算、メモリアクセス、制御フローオペコードなどはすべてそのように処理されます。
  • 互換性のために保持されている異常で複雑なオペコードは、マイクロコードで解釈されます。マイクロコードは、単純なオペコードの処理に干渉してレイテンシを引き起こさないように、CPUのユニットのサブセットに制限されます。最新のx86でのマイクロコード化された命令の例はfsinであり、浮動小数点オペランドの正弦関数を計算します。

トランジスタは大幅に縮小しているため(2008年のクアッドコア i7 は731millionsのトランジスタを使用しています)、ROM[〜#〜] ram [〜#〜]マイクロコードのブロック。そのRAMブロックはまだCPUの内部にあり、ユーザーコードからアクセスできませんが、updatedにすることができます。結局のところ、マイクロコードは一種のソフトウェアなので、バグがあります。 CPUベンダーは、CPUのマイクロコードのアップデートを公開しています。このような更新は、特定のオペコードを使用してオペレーティングシステムによってアップロードできます(これにはカーネルレベルの特権が必要です)。 RAMについて話しているため、これは永続的なものではなく、起動するたびに再度実行する必要があります。

これらのマイクロコード更新のcontentsはまったく文書化されていません。それらはCPUの正確なモデルに非常に固有であり、標準はありません。さらに、checksumsと考えられ、 [〜#〜] mac [〜#〜] または デジタル署名 であると考えられます-/:ベンダーマイクロコード領域に入るものを厳しく管理したい。悪意を持って作成されたマイクロコードがCPU内で「短絡」をトリガーすることにより、CPUに損傷を与える可能性があると考えられます。


概要:マイクロコードは、しばしば解読されるほどには素晴らしいものではありません。現在、マイクロコードハッキングは閉鎖された領域です。 CPUベンダーは、自分用に予約しています。ただし、could独自のマイクロコードを書き込んだとしても、おそらくがっかりするでしょう。最近のCPUでは、マイクロコードが影響するのはCPUの周辺ユニットだけです。

最初の質問と同様に、マイクロコードで実装された「あいまいなオペコードの動作」は、@ Christianがリンクしているようなカスタム仮想マシンエミュレーターと実際には違いません。それは、最高の状態での「あいまいさによるセキュリティ」、つまり非常に細かいものではありません。このようなものは、リバースエンジニアリングに対して脆弱です。

If伝説のマイクロコードは完全な復号化エンジンを実装できます改ざんに強いキー用のストレージエリア本当に強力なアンチリバースエンジニアリングソリューションを持っています。しかし、マイクロコードはそれを行うことができません。これには、さらにハードウェアが必要です。 セルCPU はそれを行うことができます。これは、Sony PS3で使用されています(Sonyは他の領域でそれを台無しにしました-CPUはシステムで単独ではなく、それ自体で完全なセキュリティを保証することはできません)。

21
Thomas Pornin

このようなハードウェア操作を調べると、「 ここではドラゴン 」の領域に非常に深く入り込んでいます。これに関する実際的な実験を行った研究や実際の攻撃については知りませんので、私の答えは純粋に学術的なものです。

まず、マイクロコードがどのように機能するかについて少し説明した方がいいでしょう。あなたがすでにこのことに固執しているなら、遠慮なくスキップしてください、しかし私はむしろ知らない人のために詳細を含めたいと思います。マイクロプロセッサは、一連の有用な基本機能を提供する方法で相互接続するシリコンダイ上のトランジスタの巨大なアレイで構成されています。これらのトランジスタは、電圧の内部変化、または電圧レベル間の遷移に基づいて状態を変更します。これらの遷移は、実際には方形波であるクロック信号によってトリガーされます。この矩形波は、高周波で高電圧と低電圧を切り替えます。ここで、CPUの「速度」測定を行います。 2GHz。クロックサイクルが低電圧と高電圧の間で切り替わるたびに、単一の内部変化が行われます。これは時計刻みと呼ばれます。最も単純なデバイスでは、単一のクロック刻みがプログラムされた操作全体を構成する可能性がありますが、これらのデバイスは、実行できる機能に関して非常に制限されています。

プロセッサがより複雑になるにつれて、最も基本的な操作(2つの32ビット整数の追加など)を提供するためにハードウェアレベルで実行する必要がある作業量が増加しています。単一のネイティブアセンブリ命令(例:add eax, ebx)は、非常に多くの内部作業を伴う可能性があり、マイクロコードがその作業を定義します。各クロックティックは単一のマイクロコード命令を実行し、単一のネイティブ命令には数百のマイクロコード命令が含まれる場合があります。

命令mov eax, [01234000]、つまりアドレス01234000のメモリから32ビット整数を内部レジスタに移動するための、非常に単純化されたバージョンのメモリ読み取りを見てみましょう。まず、プロセッサは内部の命令キャッシュから命令を読み取る必要があります。これは、それ自体が複雑なタスクです。今のところ、これは無視してみましょう。ただし、命令を解析して他のさまざまな内部ユニットを準備する制御ユニット(CU)内のさまざまな操作が多数含まれています。コントロールユニットが命令を解析すると、操作を実行するためにマイクロ命令のグループを実行する必要があります。まず、システムメモリパイプラインが新しい命令の準備ができていることを確認する必要があります(メモリチップもコマンドを受け取ることを忘れないでください)。次に、パイプラインに読み取りコマンドを送信し、サービスが提供されるのを待つ必要があります。 DDRは非同期なので、操作が完了したことを通知する割り込みを待つ必要があります。割り込みが発生すると、CPUはその命令を続行します。次の操作は、新しい値をメモリから内部レジスタに移動することです。これは思ったほど単純ではありません。通常認識しているレジスタ(eax、ebx、ecx、edx、ebpなど)は、チップ内の特定の物理的なトランジスタセットに固定されていません。実際、CPUには、公開するよりもはるかに多くの物理的な内部レジスタがあり、レジスタの名前変更と呼ばれる手法を使用して、着信、発信、および処理されたデータの変換を最適化します。したがって、メモリバスからの実際のデータを物理レジスタに移動し、そのレジスタを公開されたレジスタ名にマップする必要があります。この場合、それをeaxにマッピングします。

上記はすべて簡略化されています。実際の操作では、さらに多くの作業が必要になる場合や、専用の内部デバイスによって処理される場合があります。そのため、単独ではほとんど機能せず、1つの命令に追加される一連のマイクロ命令を参照している場合があります。場合によっては、パフォーマンスを向上させるために設計された特定の操作を処理する非同期内部ハードウェア操作をトリガーするために、特別なマイクロ命令が使用されます。

ご覧のとおり、マイクロコードは非常に複雑です。 CPUタイプ間で大きく異なるだけでなく、リリースバージョンとリビジョン間でも大きく異なります。これにより、ターゲットを設定することが困難になります。デバイスにプログラムされているマイクロコードを実際に確認することはできません。それだけでなく、マイクロコードがチップにプログラムされる方法も、各プロセッサに固有です。その上、それは文書化されておらず、チェックサムも付けられておらず、署名チェックも必要になる可能性があります。メカニズムとチェックをリバースエンジニアリングするには、いくつかの深刻なハードウェアが必要です。

あなたがマイクロコードを便利な方法で上書きできたとします。どのようにして何か便利なことをしますか?各コードは、実際の操作ではなく、ハードウェアの内部で値をシフトするだけであることに注意してください。マイクロコードをジャグリングしてオペコードを難読化するには、完全なカスタムOSとブートローダーが必要ですが、BIOSは(おそらく)機能します。残念ながら、より最近のシステムでは、CPU上のリアルモードでのコードの実行を含む古いBIOS仕様ではなく、UEFIを使用しています。つまり、まったく新しいBIOSとOSが必要であり、すべてゼロから作成されています。ほとんど有用な難読化方法ではありません。その上、一見任意のバイト値はそれほど恣意的ではないため、命令を再マップすることさえできないかもしれません-個々のビットは、CPU内部の異なる領域を選択するコードにマップします。それらを変更すると、命令データを解析するCPUの機能が損なわれる可能性があります。

より興味深い演習は、ring3からring0に遷移する新しい命令と、元に戻す別の命令を実装することです。すべてのチェックを実行する必要はありません。これにより、OS固有のバックドアを必要とせずに、特権の昇格で楽しいことができるようになります。

21
Polynomial

はい、それは可能です。これらの方針に沿って、シュナイアーのブログでいくつかのアイデアを提案しました。これにはいくつかの方法があります。

  1. 変更されないプロセッサで始まる独自のマイクロコード。これは、たとえば、オープンコアを使用して、内部デザインをフリーズすることができます。次に、あなた(および他のユーザー)がカスタムマイクロコードを実行します。他の人が指摘したように、これは多くの作業です。ただし、高水準言語をマイクロコード/マイクロ命令コンパイラー(これらのキーワードを使用してそれらをグーグル)することができます。これらの組み合わせは、ヘビーウェイトアプローチです。より簡単な概念のバージョンは、既存の命令で構成され、アトミックに実行される新しい命令を作成できるAlphaのPALcodeです。まだ稼働中のプロセッサーに機能が存在するかどうかは不明です。

  2. もう1つの方法は、マイクロコードを作成して、マシンコード命令の識別子を変更することです。コンパイラーとマイクロコード署名者は、保護されたマシン上にあり、ネットワークに接続されていないか、高度に保証されたガードの後ろに座っています。着信シェルコードにはランダムな影響があり、同様の学術研究ではコード実行に至ることはほとんどありません。 (この種のものにはCPUプロトタイプさえあるので、Google命令セットのランダム化。)このスキームは、コンパイラー、デバッガーなどを備えたツールチェーンも生成します。TensilicaのXtensaプロセッサーI.P.特定のアプリケーション用のCPUとツールチェーンをすでに生成しています。これは...それよりずっと単純です。 ;)

  3. 最善の方法は、アーキテクチャを変更して、データに対する適切な操作のみを許可することです。これらは「タグ付き」、「機能」などのアーキテクチャと呼ばれます。タグ付きアーキテクチャは、データ型(例:整数、配列、コード)を表すメモリにタグを追加します。プロセッサタイプは、許可する前に、個々の操作の健全性をチェックします。 Crash-safe.orgの安全な設計はこれを行います。機能システムとは、コードやデータへの安全なポインタを備えたシステムを区分化することです。 CambrigeのCHERIプロジェクトがこれを行います。どちらのスタイルも、優れたセキュリティプロパティや実績を持つ実用的なシステムを開発するために過去に使用されました。以下のそれらの決定的な本。私の現在の設計では、GEMSOS、KeyKOS、またはJXシステムの静脈内で安全なOSを構築するための強固な基盤としてこれらを活用しています。

http://homes.cs.washington.edu/~levy/capabook/

否定的な答えにもかかわらず、あなたが説明するものの精神が行われ、いくつかの一般的な攻撃に対してテストされたため、この答えを与えるだけです。特に手動で新しいマイクロコードを完全に実行するわけではありません。私が述べたようなショートカットを使用するか、この効果をシミュレートするプロセッサを設計します。それが主流になっても、ほとんどのコードインジェクションが停止する場合は、打たれる可能性があります。私はLinuxをPPC(識別情報を削除した状態)でビジネスアプリに使用することをお勧めします。この理由により、少数のユーザーにこの理由があります。これらはまだマルウェアであり、5年以上ハッキングされていません。安価なハードウェアの安定した供給を備えています。したがって、ランダムなISAまたはマイクロコーディングアプローチがさらにうまく機能し、タグ付き/機能がそれよりも優れている(プロのものに対しても)、およびそれらの組み合わせが期待されます)さらに良い。

4
Nick P

X86のマイクロコードを変更することは可能ではないと思いますが、別のマイクロコードでエミュレーターを実行することは可能であり、使用されています。このエミュレータは、CPUブートストラップと同様に、ブート時に起動するように構築できます(そうです、CPUはbootstrapも必要))。

オペコードの難読化がPEプロテクターで使用されており、オペコードの一意のセットとそれらのオペコードを解釈できる仮想マシンが生成されます。この方法は静的分析を困難にし、海賊版対策やマルウェアの記述に使用されます。このテクノロジーの例は Themida です。

2
Cristian Dobre