CFI Enforcement

2022-05-28 (v2)

FUJIWARA Katsunori <foozy@lares.dti.ne.jp>

How to navigate in this page

詳細は rst2s5.py での説明参照

はじめに

CFI とは?

Control Flow Attack とは?

※ 説明簡略化のため、スタックベースの ABI を想定

  1. スタック上書きで、引数/呼び出し先アドレスをスタックに積む
    • 想定呼び出し順序と逆順で積む
    • "呼び出したい関数" のアドレスを "戻りアドレス" として積む
  2. return 命令契機で最初の関数が呼び出される
    • "戻りアドレス" として指定された関数が呼び出される
  3. 関数呼び出しが連鎖して任意の処理が実行されてしまう

system(2)exec(3) 等の呼び出しも可能

別名: "Arch Injection", "Return-Into-Libc (or -Libraries)"

既存コードからの Gadget の抽出

Control Flow Attack のバリエーション

gadget 抽出の際にキーとなる間接制御遷移命令に応じて、 以下のような名称が付けられている

複数種類が混在する攻撃をどう呼称するのかは不明 :-)

"CFI 対応" 項目

"CFI 対応" と言った場合は、 主に以下のような検証に主眼が置かれている

検証で不正が検出された場合はプロセスの実行を中断

ハードウェア支援なしでの CFI 対応

CFI 対応に関する時系列:

ハードウェア支援なしでの CFI 対応

以下のような理由から、 ハードウェア支援なしでの CFI 対応の実現が必要

CFI 対応実現例: Microsoft CFG

CFI 対応実現例: Microsoft CFG

Microsoft CFG とは?

コンパイル時処理

コンパイル時に以下の追加処理を行う

ロード時処理

CFG-Aware な Windows は、 実行バイナリ/ライブラリのメモリロードの際に以下の処理を行う

実行時処理

CFG-Aware な Windows では、 プロセス内の間接制御遷移命令 (jump/call) 実行の都度、 以下の処理を行う

後方互換性

CFG 非対応な要素との組み合わせでも、 従来と同様 (= CFI 対応は無効なまま) に動作可能

動的構成変更

Microsoft のドキュメント原文における CFG の挙動に関する言及曰く:

(The runtime support) efficiently maintains state that identifies valid indirect call targets.

(以下は日本語翻版ページでの記述)

有効な間接呼び出しターゲットを識別する状態を効率的に保持

これ単体で読むと、はっきり言ってワケワカラン言及 :-)

動的構成変更

CFI 対応実現例: Microsoft XFG

CFI 対応実現例: Microsoft XFG

Microsoft XFG とは?

コンパイル時処理

Microsoft CFG 対応での処理に加えて、 以下のような処理を行う。

実行時処理

XFG-Aware な Windows では、 遷移先の "正当性" 判定の際に以下を検証する

CFI 対応実現例: Shadow Call Stack

CFI 対応実現例: Shadow Call Stack

Shadow Call Stack とは?

引数/局所変数値/復帰先アドレス等々が混在している "従来のスタック" とは別に、 復帰先アドレスのみ を保持する "Shadow Call Stack" を設ける手法

CFI 対応における "関数からの復帰先の非改竄性" 検証を行う

実現方式

Shadow Call Stack では以下の手順で "関数からの復帰先の非改竄性" 検証を行う

  1. call 命令毎 (or 関数冒頭) に復帰先アドレスを Shadow Call Stack に psuh
  2. Shadow Call Stack から "想定復帰先アドレス" を pop
  3. return 命令毎に以下の一致を確認
    • 復帰先予定アドレス (= 従来のスタック or レジスタで保持)
    • 想定復帰先アドレス
  4. 不一致の場合はプロセス終了

メモリ領域管理

Shadow Call Stack 実現のためには、 以下のようなメモリ領域管理が必要となる

後方互換性

その他の純ソフトウェア CFI 対応

※ LLVM/Clang 固有の CFI 対応

Intel CPU での CFI 支援機能

Intel CPU での CFI 支援機能 (CET: Controlflow Enforcement Technology) は以下の要素から構成される。

Indirect Branch Tracking

コンパイル時処理

Intel IBT を有効にするためのコンパイル時対処:

実行時処理

Intel IBT による遷移先アドレスの正当性検証は、 以下の要領で実施される。

  1. 間接制御遷移命令実施の際には CPU の内部状態を WAIT_FOR_ENDBRANCH に移行 (初期状態は IDLE)
  2. 内部状態が WAIT_FOR_ENDBRANCH の際に実行した命令が:
    • ENDBR32 (or ENDBR64) なら内部状態を IDLE に移行
    • それ以外なら #CP fault を発生

Shadow Stack

コンパイル時処理

実行時処理

Intel SS による復帰先アドレスの正当性検証は、 以下の要領で実施される。

  1. CALL 命令契機で通常 stack と shadow stack の両方に戻り先アドレスを格納
  2. RET 命令契機で両方の stack から戻り先アドレスを取り出し
    • 一致しているなら処理継続 (= 当該アドレスに遷移)
    • 不一致なら #CP fault 発生

ARM CPU での支援機能

Branch Target Instructions

コンパイル時処理

実行時処理

Pointer Authentication Code

コンパイル時処理

実行時処理

Shadow Call Stack との比較

コンパイラ毎の CFI 対応

OS 毎の CFI 対応

カーネルコードの CFI 対応

CFI on SPARC

SPARC での攻撃可否

SPARC での CFI 対応実施可否

※ 純ソフトウェア的実現の可否

SPARC での遷移先正当性の検証

SPARC の復帰先正当性検証

SPARC でのハードウェア支援

INFORMATION WANTED !!!!

CFI on Solaris

INFORMATION WANTED !!!!

余談 #1: Spectre 再び

余談 #1: Spectre 再び

Spectre とは?

"Meltdown and Spectre" 情報ページ曰く:

Both attacks use side channels to obtain the information from the accessed memory location.

Spectre による秘匿情報の入手では、 データ向けキャッシュメモリにおける "キャッシュ有効性の有無による性能差" を side channel とした手法が用いられる。

脆弱性構成要素 #1: キャッシュメモリ

脆弱性構成要素 #1: キャッシュメモリ

キャッシュメモリの副作用

間接参照による値の推測

Defender しか知らない "秘密の値 N" を、 Attacker が間接的に推測する手順は以下の通り:

  1. (Attacker) キャッシュライン #0 〜 #255 に相当する領域 A から、 メモリの内容を読み出しておく
  2. (Attacker) この時点での領域 A からの読み出しは、 キャッシュが有効なので高速
  3. (Defender) 同じくキャッシュライン #0 〜 #255 に相当する別の領域 B に対して、 先頭から N * CacheLineSize バイトの位置の内容を読み出す (= 読み出させられる)
  4. (Attacker) 再度領域 A からの読み出しを行い、 キャッシュライン毎の読み出し性能を計測
  5. (Attacker) 読み出し性能が低いキャッシュラインは (3) における N に相当

脆弱性構成要素 #2: 分岐予測

脆弱性構成要素 #2: 分岐予測

分岐予測手法

分岐予測における予測対象

命令アドレスを元に以下のような予測を行う

分岐予測機能の性質

脆弱性構成要素 #3: 投機的実行

脆弱性構成要素 #3: 投機的実行

投機的実行の挙動例

以下のような実装において、 処理 (1) 時点で objp->length がキャッシュに乗っていない場合、 CPU の分岐予測を元に、 実際の index 値の判定を待たずに処理 (2) を実施。

func(obj_t* objp, unsigned int index){
    if(index < objp->length){  /* (1) */
        value_x = objp->data[index];  /* (2) */
    }

投機的実行の副作用

投機的実行が retire された場合:

Variant #1: Bounds Check Bypass

Variant #1: Bounds Check Bypass

攻撃対象コード

if(index < objp->length){  /* (1) */
    value_x = objp->data[index];  /* (2) */
    value_y = somewhere[value_x]; /* (3) */
}

事前準備

攻撃手法の概要

  1. objp->data[index] が秘匿情報の領域を指すような index 値で関数を呼び出す
  2. objp->length がキャッシュに乗るまでの間隙で、 投機的実行により (2) 処理が実行される
  3. 秘匿情報値を保持する value_x を使った間接参照 (somewhere[value_x]) が実行される
  4. objp->length がキャッシュに乗ることで投機的実行が retire (= 関数は「失敗」扱いで呼び出し元に復帰)
  5. キャッシュライン毎の読み出し性能を計測することで、 投機的実行 (3) における間接参照で使用された秘匿情報値を推測

Variant #2: Branch Target Injection

Variant #2: Branch Target Injection

事前準備

攻撃手法の概要

  1. 攻撃対象が間接制御遷移命令を実行するように誘導
  2. 分岐予測+投機的実行により gadget コードが実行される (= Control Flow Attack と同じ原理)
  3. gadget 実行の副作用により、 秘匿情報値を offset に使ったメモリアクセスが発生
  4. Variant #1 と同じ要領で秘匿情報値を推測

Retpoline

Retpoline

Retpoline の仕組み

※ 詳細は "Google: Retpoline: a software construct for preventing branch-target-injection" 参照。

Retpoline と Shadow Stack

Intel も明確に併用禁止

PC Watch の記事 曰く:

Retpoline の手法は CET (Control-flow Enforcement Technology) と呼ばれる、 Intel が将来の CPU で実装する脆弱性対処の仕組みと干渉する可能性がある、 としている。 この CET は enhanced IBRS と協調する仕組みになっているので、 将来は Retpoline を利用しないことが望ましい 、としている。

New Variant: Branch History Injection

New Variant: Branch History Injection

攻撃手法

攻撃手法詳細は未確認だが、 名称からして、 間接分岐命令における投機的実行の実行先を BHB 汚染経由で誘導するもの思われる。

詳細は "VUSec: Branch History Injection" 参照。

BHI の緩和策

余談 #2: Microsoft、やらかす?

スラドの記事 曰く:

本ビルドでは一部のデバイスで Microsoft Store からアプリをインストールしようとするとエラーコード 0xC002001B が表示されたり、 一部の Microsoft Store アプリが起動できなかったりする問題が修正されている。

この問題は Control-flow Enforcement Technology (CET) を搭載 する第 11 世代以降の Intel Core プロセッサーや、 同等のセキュリティ機能を搭載する AMD プロセッサーを搭載するデバイスで、 4 月 25 日にリリースされた累積更新プログラムのプレビュー (KB5011831) および以降の累積更新プログラムをインストールした場合に発生するとのこと。

邪推して見るに……

ユーザプログラム (and/or ライブラリ) に対する CET 有効化による問題だと仮定すると、 以下のような原因が考えられる。

Spectre と CFI Enforcement

まとめ

参考ページ

参考書籍

おわり

ご清聴ありがとうございました