CFI Enforcement
2022-05-28 (v2)
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
How to navigate in this page
- 表示モードの切り替え: t
- 制御表示の切り替え: c
- 次ページ: SPACE, Enter, →, ↓, PageDown, マウスクリック
- 前ページ: ←, ↑, PageUp
- タイトルページ: Home
- 最終ページ: End
- 指定ページ: "ページ数" の入力後に Enter (表紙が 0)
詳細は rst2s5.py での説明参照
はじめに
CFI とは?
Control Flow Integrity
Control Flow Attack に対する対策技術全般を指す用語 (多分)
対策全般を指す場合は "CFI Enforcement" と呼ばれることも多い
セキュリティ向上の文脈で
"Integrity を下げようぜ!" とか
"Integrity に対して何もしないぜ!" とはならない筈なので、
わざわざ "Enforcement" (執行/実施) と言及するのもアレな気も :-)
Control Flow Attack とは?
※ 説明簡略化のため、スタックベースの ABI を想定
- スタック上書きで、引数/呼び出し先アドレスをスタックに積む
- 想定呼び出し順序と逆順で積む
- "呼び出したい関数" のアドレスを "戻りアドレス" として積む
- return 命令契機で最初の関数が呼び出される
- "戻りアドレス" として指定された関数が呼び出される
- 関数呼び出しが連鎖して任意の処理が実行されてしまう
system(2) や exec(3) 等の呼び出しも可能
別名: "Arch Injection", "Return-Into-Libc (or -Libraries)"
既存コードからの Gadget の抽出
攻撃の高度化に伴い、連鎖単位が細分化
- 関数単位 ⇛ コード断片単位 ("gadget")
- "間接制御遷移命令に先立つ数命令" を gadget 化
命令列の途中に割り込んで強引に gadget 化するケースも
e.g. "move eax, 0xc3084189" に相当する "B8 89 41 08 C3" は、
途中への飛び込みで
"mov [ecx+8], eax; ret" 相当の
"41 08 C3" として gadget 化可能
既存バイナリから利用可能な gadget を抽出するツールも存在
e.g.: https://github.com/JonathanSalwan/ROPgadget
Control Flow Attack のバリエーション
gadget 抽出の際にキーとなる間接制御遷移命令に応じて、
以下のような名称が付けられている
- Jump Oriented Programming (JOP)
- Call Oriented Programming (COP)
- Return Oriented Programming (ROP)
複数種類が混在する攻撃をどう呼称するのかは不明 :-)
"CFI 対応" 項目
"CFI 対応" と言った場合は、
主に以下のような検証に主眼が置かれている
検証で不正が検出された場合はプロセスの実行を中断
ハードウェア支援なしでの CFI 対応
CFI 対応に関する時系列:
- 2005-11: Microsoft が CFI に関する論文を発表
- 2014-11: MS-Windows アプリ向け CFI 対応機能 (CFG) をリリース
- 2018: Google が Android OS のビルドで CFI 対応を有効化
- 2020 後半: Intel が最初の CFI 支援機能対応 CPU Tiger Lake をリリース
- 2018 〜 2021: ARM が CFI 支援機能仕様を段階的に発表
ハードウェア支援なしでの CFI 対応
以下のような理由から、
ハードウェア支援なしでの CFI 対応の実現が必要
- CFI 支援機能対応 CPU のリリースはここ数年の出来事
- 攻撃者は CFI 支援機能対応 CPU の普及を待ってくれない
- 今後も CFI 支援機能なし CPU が共存する可能性が高い
CFI 対応実現例: Microsoft CFG
CFI 対応実現例: Microsoft CFG
Microsoft CFG とは?
- Control Flow Guard
- Microsoft による CFI 対応実装
- "CFI 対応" における "制御遷移先の妥当性検証" を実施
- ハードウェア支援のない CPU でも利用可能
- Visual Studio 2015 から対応バイナリをビルド可能
- Windows 10/Windows 8.1 update 3 (KB3000850) 以降で機能有効化可能
コンパイル時処理
コンパイル時に以下の追加処理を行う
- 間接制御遷移命令 (jump/call) 直前への NOP 領域埋め込み
- 上記領域に関する再配置情報の添付
- 命令テキスト中の "正当な間接制御遷移先" の一覧添付
ロード時処理
CFG-Aware な Windows は、
実行バイナリ/ライブラリのメモリロードの際に以下の処理を行う
- 間接制御遷移命令直前の NOP 領域に対しての、
制御遷移先アドレス検証関数の呼び出し処理の埋め込み
- "正当な間接制御遷移先" 情報を元に、
制御遷移先正当性判定用の bitmap メモリ領域を作成
実行時処理
CFG-Aware な Windows では、
プロセス内の間接制御遷移命令 (jump/call) 実行の都度、
以下の処理を行う
- 遷移先の "正当性" を確認 (aka "CFG check")
- 遷移先が不正な場合は即座にプロセスを終了
- 遷移先が正当な場合は処理継続 (= 制御遷移実施)
後方互換性
CFG 非対応な要素との組み合わせでも、
従来と同様 (= CFI 対応は無効なまま) に動作可能
"CFG 有効なバイナリ" + "non CFG-Aware な Windows"
遷移先正当性確認処理の呼び出し埋め込みが実施されないので、
CFG 無効なまま動作
"CFG 無効 なバイナリ" + "CFG-Aware な Windows"
- 無効バイナリからの遷移: CFG 無効なまま
- 無効バイナリ への 遷移 (callback 等): 全領域を "正当" 扱い?
動的構成変更
Microsoft のドキュメント原文における
CFG の挙動に関する言及曰く:
(The runtime support) efficiently maintains state that
identifies valid indirect call targets.
(以下は日本語翻版ページでの記述)
有効な間接呼び出しターゲットを識別する状態を効率的に保持
これ単体で読むと、はっきり言ってワケワカラン言及 :-)
動的構成変更
実行バイナリ内の "正当な間接制御遷移先" の一覧は、
コンパイル&リンク段階で確定
実行時に動的な構成変更が必要なケースも存在
e.g. JIT (Just-In-Time) コンパイラ
動的構成変更向けの API が提供
動的構成変更時でも "efficiently maintain" するぜ!
ということを言いたいのでは?
CFI 対応実現例: Microsoft XFG
CFI 対応実現例: Microsoft XFG
Microsoft XFG とは?
- eXtended Flow Guard
- Microsoft CFG の検証強化版
- ハードウェア支援のない CPU でも利用可能 (の筈)
- Visual Studio 2019 から対応バイナリをビルド可能
- Windows Insider Preview でのみ有効化可能 (?)
コンパイル時処理
Microsoft CFG 対応での処理に加えて、
以下のような処理を行う。
関数のエントリポイント直前に function signature ベースのハッシュ値を配置
以下の function signature 情報を元にハッシュ値を算出
- 関数名
- ベースクラス名
- 引数の数
- 引数型
- 戻り値型
間接制御遷移命令の呼び出し毎に、
想定される function signature ベースのハッシュ値を算出
再配置情報等としてバイナリに付与
実行時処理
XFG-Aware な Windows では、
遷移先の "正当性" 判定の際に以下を検証する
- 遷移先アドレスの正当性 (CFG と同等)
- 想定される function signature ベースのハッシュ値と、
実際の遷移先関数のハッシュ値の一致 (XFG 固有)
- "想定されるハッシュ値" は再配置情報等から取得
- "実際の遷移先関数のハッシュ値" は遷移先アドレスの直前から取得
CFI 対応実現例: Shadow Call Stack
CFI 対応実現例: Shadow Call Stack
Shadow Call Stack とは?
引数/局所変数値/復帰先アドレス等々が混在している
"従来のスタック" とは別に、
復帰先アドレスのみ を保持する "Shadow Call Stack" を設ける手法
CFI 対応における "関数からの復帰先の非改竄性" 検証を行う
実現方式
Shadow Call Stack では以下の手順で
"関数からの復帰先の非改竄性" 検証を行う
- call 命令毎 (or 関数冒頭) に復帰先アドレスを Shadow Call Stack に psuh
- Shadow Call Stack から "想定復帰先アドレス" を pop
- return 命令毎に以下の一致を確認
- 復帰先予定アドレス (= 従来のスタック or レジスタで保持)
- 想定復帰先アドレス
- 不一致の場合はプロセス終了
メモリ領域管理
Shadow Call Stack 実現のためには、
以下のようなメモリ領域管理が必要となる
復帰先アドレス格納操作 以外 に対する当該メモリ領域の保護
実現方式の一例として専用セグメント領域の使用が挙げられる (for x86)
今日のアプリケーションではセグメント操作を行わないので、
Shadow Call Stack 操作以外での改変を (実質的に) 回避可能
Shadow Call Stack 領域の初期化/拡張/解放
新規コンテキスト (e.g. スレッド/割り込み) 毎に、
Shadow Call Stack 用の領域を OS 側で管理する必要アリ
その他の純ソフトウェア CFI 対応
※ LLVM/Clang 固有の CFI 対応
Intel CPU での CFI 支援機能
Intel CPU での CFI 支援機能
(CET: Controlflow Enforcement Technology)
は以下の要素から構成される。
- Indirect Branch Tracking (IBT)
- Shadow Stack (SS)
Indirect Branch Tracking
コンパイル時処理
Intel IBT を有効にするためのコンパイル時対処:
実行時処理
Intel IBT による遷移先アドレスの正当性検証は、
以下の要領で実施される。
- 間接制御遷移命令実施の際には CPU の内部状態を
WAIT_FOR_ENDBRANCH に移行
(初期状態は IDLE)
- 内部状態が WAIT_FOR_ENDBRANCH の際に実行した命令が:
- ENDBR32 (or ENDBR64) なら内部状態を IDLE に移行
- それ以外なら #CP fault を発生
Shadow Stack
コンパイル時処理
- Intel CET の SS は、
命令の内部処理レベルで "Shadow Call Stack" を実現
- コンパイル時の生成命令列には変化なし
- 稼働環境での OS 支援は必要 (e.g. Shadow Call Stack 領域の管理)
実行時処理
Intel SS による復帰先アドレスの正当性検証は、
以下の要領で実施される。
- CALL 命令契機で通常 stack と
shadow stack の両方に戻り先アドレスを格納
- RET 命令契機で両方の stack から戻り先アドレスを取り出し
- 一致しているなら処理継続 (= 当該アドレスに遷移)
- 不一致なら #CP fault 発生
ARM CPU での支援機能
Branch Target Instructions
コンパイル時処理
実行時処理
Pointer Authentication Code
コンパイル時処理
実行時処理
Shadow Call Stack との比較
- PROS:
- 保護されたメモリ領域の管理
(初期化/拡張/開放等々) が必要ない
- 通常スタックのみしか参照しないので、
メモリの局所性が高い (= キャッシュ効率を維持可能)
- CONS
- 妥当性保証の強度は、
アドレスの一部として保存される署名値 (PAC) のビット長に比例するが、
十分な長さが確保できるかは想定実行環境次第。
- PAC 方式におけるハッシュ値計算に必要な CPU サイクルは、
Shadow Call Stack 方式における単純なアドレス値比較
(= 論理比較) よりも多い筈。
⇛ ハードウェア支援により低減可能ではある
コンパイラ毎の CFI 対応
- LLVM/Clang
- GCC
- Intel C/C++
- ARM C/C++
OS 毎の CFI 対応
カーネルコードの CFI 対応
- MS Windows
- Linux
- Android OS (by Google)
CFI on SPARC
SPARC での攻撃可否
SPARC での CFI 対応実施可否
※ 純ソフトウェア的実現の可否
SPARC での遷移先正当性の検証
- landing pad 方式 (Intel IBT/ARM BTIs like)
- マッピング方式 (Micorsoft CFG)
- signature hash 方式 (Microsoft XFG)
SPARC の復帰先正当性検証
- Shadow Call Stack 方式 (Intel SS like)
- アドレス署名方式 (ARM PA like)
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 が間接的に推測する手順は以下の通り:
- (Attacker) キャッシュライン #0 〜 #255 に相当する領域 A から、
メモリの内容を読み出しておく
- (Attacker) この時点での領域 A からの読み出しは、
キャッシュが有効なので高速
- (Defender) 同じくキャッシュライン #0 〜 #255 に相当する別の領域
B に対して、
先頭から N * CacheLineSize バイトの位置の内容を読み出す
(= 読み出させられる)
- (Attacker) 再度領域 A からの読み出しを行い、
キャッシュライン毎の読み出し性能を計測
- (Attacker) 読み出し性能が低いキャッシュラインは (3) における N に相当
脆弱性構成要素 #2: 分岐予測
脆弱性構成要素 #2: 分岐予測
分岐予測手法
- コンパイル時予測
- SPARC V9 BPcc (Branch on integer condition code with prediction)
- 実行時予測
- 分岐命令系
- "Branch Target Buffer" (BTB)
- "Branch History Buffer" (BHB)
- サブルーチン呼び出し系
- "Return Stack Buffer" (RSB) for Intel CPU
- "Return Address Stack" (RSB) for AMD CPU
- "Return Stack" for ARM CPU
分岐予測における予測対象
命令アドレスを元に以下のような予測を行う
分岐予測機能の性質
- 予測情報の保持量は多くない
- 予測情報は CPU コア (or ソケット) 単位で共有される
- 他スレッド/他プロセス/他特権レベルにおける処理によっても、
分岐予測が誘導 される可能性あり
脆弱性構成要素 #3: 投機的実行
脆弱性構成要素 #3: 投機的実行
投機的実行の挙動例
以下のような実装において、
処理 (1) 時点で objp->length がキャッシュに乗っていない場合、
CPU の分岐予測を元に、
実際の index 値の判定を待たずに処理 (2) を実施。
func(obj_t* objp, unsigned int index){
if(index < objp->length){
value_x = objp->data[index];
}
投機的実行の副作用
投機的実行が retire された場合:
- 投機的実行中のレジスタ内容の改変は、キャンセルされる
- 投機的実行中のメモリ内容の改変は、キャンセルされる
- 投機的実行中のキャッシュ内容の改変は、キャンセル されない
Variant #1: Bounds Check Bypass
Variant #1: Bounds Check Bypass
攻撃対象コード
if(index < objp->length){
value_x = objp->data[index];
value_y = somewhere[value_x];
}
事前準備
- 攻撃対象プログラム中に gadget を見つけておく
- 秘匿情報の格納先アドレスを特定しておく
- 広範囲のメモリ領域を読み出すことで、
特定の連続したキャッシュラインをゴミで埋めておく
- 境界判定処理の条件分岐命令が "taken" となる実行を繰り返すことで、
投機的実行において分岐先予測が "taken" 判定となるようにしておく
攻撃手法の概要
- objp->data[index] が秘匿情報の領域を指すような
index 値で関数を呼び出す
- objp->length がキャッシュに乗るまでの間隙で、
投機的実行により (2) 処理が実行される
- 秘匿情報値を保持する value_x を使った間接参照
(somewhere[value_x]) が実行される
- objp->length がキャッシュに乗ることで投機的実行が retire
(= 関数は「失敗」扱いで呼び出し元に復帰)
- キャッシュライン毎の読み出し性能を計測することで、
投機的実行 (3) における間接参照で使用された秘匿情報値を推測
Variant #2: Branch Target Injection
Variant #2: Branch Target Injection
事前準備
- 攻撃対象プログラム中に gadget を見つけておく
- 秘匿情報の格納先アドレスを特定しておく
- 広範囲のメモリ領域を読み出すことで、
特定の連続したキャッシュラインをゴミで埋めておく
- 間接制御遷移命令の分岐先予測を gadget のアドレスで埋めておく
攻撃手法の概要
- 攻撃対象が間接制御遷移命令を実行するように誘導
- 分岐予測+投機的実行により gadget コードが実行される
(= Control Flow Attack と同じ原理)
- gadget 実行の副作用により、
秘匿情報値を offset に使ったメモリアクセスが発生
- Variant #1 と同じ要領で秘匿情報値を推測
Retpoline と Shadow Stack
- Shadow Stack は ROP 対策として復帰先アドレスの非改竄性を検証
- Retpoline は復帰先アドレスの改変により prediction の誘導を回避
- 相性が良いわけが無い!
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
BHI の緩和策
- Spectre-V2 向けのハードウェア緩和策である
eIBRS (Intel) や CSV2 (ARM) は機能せず
- Retpoline のようなソフトウェア緩和策は依然として有効
余談 #2: Microsoft、やらかす?
スラドの記事 曰く:
本ビルドでは一部のデバイスで
Microsoft Store からアプリをインストールしようとするとエラーコード
0xC002001B が表示されたり、
一部の Microsoft Store
アプリが起動できなかったりする問題が修正されている。
この問題は
Control-flow Enforcement Technology (CET) を搭載 する第 11 世代以降の
Intel Core プロセッサーや、
同等のセキュリティ機能を搭載する AMD プロセッサーを搭載するデバイスで、
4 月 25 日にリリースされた累積更新プログラムのプレビュー (KB5011831)
および以降の累積更新プログラムをインストールした場合に発生するとのこと。
邪推して見るに……
ユーザプログラム (and/or ライブラリ) に対する
CET 有効化による問題だと仮定すると、
以下のような原因が考えられる。
Indirect Branch Tracking (IBT) 絡みなら:
正当な制御遷移先に ENDBR32 (or ENDBR64)
命令が埋まっていないコードに対して、
実行時に IBT が有効になってしまった
Shadow Stack (SS) 絡みなら:
想定動作の範疇でスタック上の戻り先アドレスを改竄している
(e.g. retpoline 対応との併用) コードに対して、
実行時に SS が有効になってしまった
Spectre と CFI Enforcement
- 当面有効な Spectre-V2 緩和策の retpoline は:
- 無視できない性能劣化要因
- Shadow Stack 等との相性が悪い
- CFI Enforcement 支援機能を持った CPU の普及には、
いま暫くの時間が必要
- 結果として、様々な技術の利用可否/有効・無効の組み合わせが多岐に渡る
- 複雑化によるヤラカシは当分あちこちで発生しそう……
まとめ
参考ページ
参考書籍