チュートリアル
ロウハンマーの再現
ロウハンマーは、メモリの特定の行を繰り返しアクセスすることで隣接行のビットが1から0へフリップする現象です(メモリセルの電荷漏洩によるもので、True Cellモデルでは1→0方向のみ)。フリップは対象ビットの上下の同じ桁のセルが両方とも0(放電状態)の場合にのみ発生します。攻撃者は、自分がアクセス可能なメモリ領域を繰り返し読み書きするだけで、アクセス権のない隣接行のデータを破壊できます。
このチュートリアルでは、単純PC環境上でロウハンマーを再現する具体的な手順を説明します。ロウハンマーの仕様詳細はアーキテクチャを参照してください。
有効化の手順
ロウハンマーシミュレーションはデフォルトで無効です。以下の手順で有効化します。
- CPUが停止していることを確認します(Start/Stopボタンが「▶」の状態)。
- ロウハンマートグルボタンをクリックして有効化します。
ロウハンマートグルボタンはCPU実行中は操作できません。必ずCPUを停止してから切り替えてください。
有効化すると、メモリエディタ上でフリップしやすいビット位置(行ごとに複数)がストライプ表示でハイライトされます。対象ニブル(16進数1桁)の幅を4等分し、フリップ対象のビット位置に対応するストライプを色分けして表示します(左からビット3→ビット0の順、ビット位置ごとに赤・橙・黄緑・緑)。これにより、どのアドレスのどのビットがフリップ対象かを事前に確認できます。
基本: ビットフリップの発生
最もシンプルなロウハンマーの再現方法です。特定のメモリ行を繰り返し読み取る無限ループを実行し、隣接行にビットフリップが発生することを確認します。
ハンマリングプログラム
以下のプログラムは、アドレス0xFD00(行0x7E)を繰り返し読み取る無限ループです。
| アドレス | 値 | 命令 | 説明 |
|---|---|---|---|
| 0x0100 | 0x21 | LXI H, 0xFD00 | HLレジスタにアドレス0xFD00を設定 |
| 0x0101 | 0x00 | ||
| 0x0102 | 0xFD | ||
| 0x0103 | 0x7E | MOV A, M | HLが指すアドレス(0xFD00)から読み取り |
| 0x0104 | 0xC3 | JMP 0x0103 | MOV A, Mに戻る(無限ループ) |
| 0x0105 | 0x03 | ||
| 0x0106 | 0x01 |
このプログラムの核心は、MOV A, MとJMPのたった2命令のループです。LXI Hはループの前にHLレジスタを初期化するだけで、1回だけ実行されます。
メモリとレジスタの設定
- CPUを停止します。
- メモリエディタで、アドレス
0x0100以降に上記の7バイトを入力します。- メモリエディタで
Gキーを押し、0100と入力してEnterでジャンプします。 Enterキーで編集モードに入り、各バイトの値を入力します。
- メモリエディタで
- フリップ対象のメモリに1を含む値を書き込みます(True Cellモデルでは1→0方向のみフリップするため、対象ビットが1でないとフリップが観測できません。また、上下の同じ桁のセルが0である必要があります)。
- アドレス
0xFA00に0xFFを書き込みます(行0x7Dのフリップ対象バイト)。 - アドレス
0xFE00に0xFFを書き込みます(行0x7Fのフリップ対象バイト)。
- アドレス
- レジスタエディタで、PCレジスタに
0x0100を設定します。 - ロウハンマートグルボタンをクリックして有効化します。
実行と確認
- Start/Stopボタンをクリックしてプログラムを実行します。
- しばらく待ちます。
デフォルトでは256回のアクセスでビットフリップが発生します。
- CPUを停止し、メモリエディタで以下のアドレスを確認します。
アドレス 行番号 確認内容 0xFA00 行0x7D 行0x7Eの上方向の隣接行。事前に設定した 0xFFから0xDFに変化します(ビット5が1→0にクリア)。0xFE00 行0x7F 行0x7Eの下方向の隣接行。事前に設定した 0xFFから0xFEに変化します(ビット0が1→0にクリア)。
フリップ対象のバイトオフセットとビット位置は、行ごとに複数が予め固定値として決められています。同じ行なら毎回同じビットが対象になります。対象ビットが1で、かつ上下の同じ桁のセルが両方とも0の場合のみクリアされます。何度起動し直しても同じ結果になります。
動作の仕組み
このプログラムが動作する仕組みを整理します。
ループ1回あたりのメモリアクセスは以下の通りです。
| アクセス | アドレス | 行 | 原因 |
|---|---|---|---|
| 1 | 0x0103 | 行0x00 | MOV A, M 命令のフェッチ |
| 2 | 0xFD00 | 行0x7E | MOV A, M の実行(攻撃対象) |
| 3 | 0x0104 | 行0x00 | JMP 命令のフェッチ |
| 4 | 0x0105 | 行0x00 | JMPオペランド(下位バイト)のフェッチ |
| 5 | 0x0106 | 行0x00 | JMPオペランド(上位バイト)のフェッチ |
ループ1回あたり、行0x7Eへのアクセスにより行0x7Dと行0x7Fのカウンタがそれぞれ1増加し、行0x00へのアクセスにより行0x01のカウンタが4増加します(行0x00は端の行のため上方向の隣接行なし。実際のDRAMでは行のアクティベーションが隣接行に電気的干渉を与えるため、アクセスされた行自体ではなく隣接行のカウンタが増加します)。
- 行0x7D・行0x7F: 行0x7Eへのアクセスによりカウンタが蓄積され、256回のループで閾値(256)に到達 → 行0x7Dと行0x7Fの対象ビットが1から0へクリアされる(事前に0xFFを書き込み、上下の同じ桁のセルが0のためフリップが発生)
- 行0x01: 行0x00への命令フェッチによりカウンタが蓄積され、64回のループで閾値に到達 → 行0x01でもビットフリップが発生しうるが、このチュートリアルでは観測対象外であり、プログラムの動作には影響しない
プログラム自体の命令フェッチにより行0x01のカウンタも増加するため、行0x7D・行0x7Fよりも先に行0x01の閾値に到達します。ただし、行0x01側の変化はこのチュートリアルの観測対象外であり、プログラムの動作にも影響しません。
応用: ページテーブルエントリへの攻撃
攻撃の概要
ロウハンマーのセキュリティ上の脅威は、メモリ保護機構を破壊できる点にあります。ここでは、ロウハンマーによってページテーブルエントリ(PTE)のValidビットを1から0にクリアし、ページフォルトを発生させるシナリオを再現します。
シナリオは以下の通りです。
- ページテーブルを行0x7Fに配置する(アドレス0xFE00〜)
- ページ0のPTEにValid=1を設定する(アドレス0xFE00のビット0が1)
- 隣接する行0x7Eを繰り返し読み取る
- ビットフリップにより、ページ0のPTEのValidビットが1から0にクリアされる
- その後、ページ0にアクセスするとページフォルトが発生する
メモリレイアウト: 行 0x00: 0x0000-0x01FF ← ハンマリングプログラム配置(アドレス 0x0100) ... 行 0x7D: 0xFA00-0xFBFF ← 上方向のフリップ先 行 0x7E: 0xFC00-0xFDFF ← ハンマリング対象(アドレス 0xFD00 を繰り返し読み取り) 行 0x7F: 0xFE00-0xFFFF ← ページテーブル配置(下方向のフリップ先)
フリップ対象のバイトオフセットとビット位置は行ごとに複数が予め固定されています。行0x7Fではオフセット0・ビット0がフリップ対象の一つとして含まれているため、アドレス0xFE00のビット0(PTEのValidビット)が1から0にクリアされます。
メモリとレジスタの設定
1. ページテーブルの配置
メモリエディタで以下の値を設定します。これはページテーブルのうち、このチュートリアルで使用するエントリです。
| アドレス | 値 | 説明 |
|---|---|---|
| 0xFE00 | 0x01 | ページ0のPTEフラグ(Valid=1) |
| 0xFE01 | 0x00 | ページ0の物理ページ番号(0x00) |
| 0xFE02 | 0x01 | ページ1のPTEフラグ(Valid=1) |
| 0xFE03 | 0x01 | ページ1の物理ページ番号(0x01) |
ページ0(0x0000〜0x00FF)とページ1(0x0100〜0x01FF)を物理アドレスと同じアドレスにマッピング(恒等写像)しています。
PTEの構造についてはアーキテクチャ: PTEフラグを参照してください。各PTEは2バイトで、下位バイトがフラグ(ビット0=Valid)、上位バイトが物理ページ番号です。
2. ハンマリングプログラムの配置
基本チュートリアルと同じプログラムを0x0100に配置します。
| アドレス | 値 | 命令 |
|---|---|---|
| 0x0100 | 0x21 | LXI H, 0xFD00 |
| 0x0101 | 0x00 | |
| 0x0102 | 0xFD | |
| 0x0103 | 0x7E | MOV A, M |
| 0x0104 | 0xC3 | JMP 0x0103 |
| 0x0105 | 0x03 | |
| 0x0106 | 0x01 |
3. レジスタの設定
| レジスタ | 値 | 説明 |
|---|---|---|
| PC | 0x0100 | プログラムの先頭アドレス |
| PTBR | 0xFE00 | ページテーブルの先頭アドレス |
この段階ではMMUは無効のままにしておきます。ハンマリング中はMMUを使用しません。
実行と確認
- ロウハンマートグルボタンをクリックして有効化します。
- Start/Stopボタンをクリックしてプログラムを実行します。
- しばらく待ちます。
行0x7Eへのアクセスにより隣接する行0x7Fのカウンタが蓄積され、閾値に達すると行0x7F内の予め決められた位置のビットが1から0へクリアされます。
- CPUを停止します。
- メモリエディタでアドレス
0xFE00を確認します。行0x7Fではオフセット0・ビット0がフリップ対象の一つとして含まれているため、アドレス0xFE00のビット0(Validビット)が1から0にクリアされ、値が
0x01から0x00に変化します。これによりPTEが無効化されます。
ページフォルトの発生
破壊されたPTEを使ってページフォルトが発生することを確認します。
1. 確認用プログラムの配置
メモリエディタで以下のプログラムを0x0110に追加します。このプログラムはアドレス0x0000(ページ0)から読み取りを行います。
| アドレス | 値 | 命令 | 説明 |
|---|---|---|---|
| 0x0110 | 0x3A | LDA 0x0000 | アドレス0x0000(ページ0)から読み取り |
| 0x0111 | 0x00 | ||
| 0x0112 | 0x00 | ||
| 0x0113 | 0x76 | HLT | CPU停止 |
2. MMUの有効化と実行
- レジスタエディタでPCに
0x0110を設定します。 - MMUトグルボタンをクリックしてMMUを有効化します。
- Start/Stopボタンをクリックして実行します。
3. 結果
LDA 0x0000が仮想アドレス0x0000(ページ0)にアクセスしようとしますが、PTEのValidビットが0であるため、ページフォルト例外が発生します。
レジスタエディタでPFAR(ページフォルトアドレスレジスタ)に0x0000が設定されていることを確認できます。
ページフォルトの仕組みについてはアーキテクチャ: ページフォルトを参照してください。
この例では、ベクタテーブルを設定していないため、ページフォルトハンドラへのジャンプは行われません(未定義の動作になります)。ページフォルト処理の完全な再現については、別途ベクタテーブルとハンドラの設定が必要です。