ROP検知手法RAPについてまとめてみる

先日、PaX Teamが公表したROP(Return-oriented Programming)検知手法RAPについてまとめてみる。

用語に重複がありまぎらわしいが、論文ではReturn Address ProtectionおよびIndirect Control Transfer Protectionの二つの手法を合わせてRAPと呼んでいるようである。

Return Address Protection

Return Address Protectionはリターンアドレスの書き換えを検知する手法である。

Stack canary(SSP)はリターンアドレスの手前にcanaryを置き検証することでスタックバッファオーバーフローを検知する。 一方、Return Address Protectionはリターンアドレスの値そのものを固定でない値でXORし、これをRAP cookieとして保持し検証することでリターンアドレスの書き換えを検知する。

具体的には、一つのレジスタ(ここではrbx)をRAP cookie用のレジスタとして使用し、コンパイル時に次のようなコードを生成する。

    push rbx           # rbxの値をスタックに退避
    mov rbx, [rsp+8]   # リターンアドレスをrbxに代入
    xor rbx, r12       # 固定でない値でXOR
    ...
    xor rbx, r12       # 再度XORして戻す
    cmp rbx, [rsp+8]   # リターンアドレスと比較
    jnz error          # 異なればエラー処理へ
    pop rbx            # スタックに退避したrbxの値を復元
    ret

error:
    ...                # エラー処理

...の部分においてリターンアドレスが書き換えられた場合、ret命令の前の検証が失敗するためこれを検知できる。 また、検知を回避するためにはr12の値を取得した上でrbxの値をコントロールする必要があり、容易ではない。

なお、rbxの値がメモリに書き出されることがない箇所においてはXORを省くことでパフォーマンスの低下を抑えることができる。

Indirect Control Transfer Protection

Indirect Control Transfer Protectionは、関数の間接呼び出し時および関数からのリターン時のそれぞれにおいて、遷移先が正常なものかどうかを検証する手法である。

まず、対象となる関数の戻り値の型や関数名、関数引数からType Hashと呼ばれる32 bitsの値を生成する。 そして、これを関数の直前や関数からのリターン箇所の後に置いておき適当なタイミングで検証することによって、遷移先が正常なものかどうかを判定する。

具体的には、関数の間接呼び出し前に次のような検証を行うよう、コンパイル時にコードを生成する。

    cmp [rax-8], 0x11223344  # 間接呼び出し先が意図したType Hashを持っているか比較
    jne error                # 異なればエラー処理へ
    call rax                 # 間接呼び出し
    ...

    dq 0x11223344            # Type Hash(正値)
func:
    ...

攻撃によりraxがfunc以外の箇所を指していた場合、Type Hashの検証に失敗するためこれを検知できる。

また、関数からのリターン前にも次のような検証を行うよう、コンパイル時にコードを生成する。

    call func                # 次の命令のアドレスをスタックにpushしてjmp
    jmp after_type_hash      # この命令は2バイトとなる
    dq 0xffffffffaabbccdd    # Type Hash(負値)
after_type_hash:
    ...

func:
    mov rcx, [rsp]           # リターンアドレスをrcxに代入
    cmp [rcx+2], 0xaabbccdd  # リターンアドレスの先に意図したType Hashが置かれているか比較
    jne error                # 異なればエラー処理へ
    ret                      # リターンアドレスにjmp

攻撃によりfuncからのリターンアドレスが異なる箇所を指していた場合、Type Hashの検証に失敗するためこれを検知できる。

なお、現実にはgccglibclinuxchromium等さまざまなプログラムにおいて誤った型キャストが行われているため、手法を適用するにはこれをすべて修正する必要がある。

パフォーマンス

概ね10%未満のパフォーマンス低下におさまっており、パフォーマンスへの影響は小さいとされている。

所感

Return Address ProtectionはShadow Stackの発想をレジスタで実現したもの、Indirect Control Transfer ProtectionはControl Flow Integrityの発想を型情報をもとにしたハッシュ値の検証という形で実現したものといえる。 理論的な概念を適切かつ実用的な形で実装しており、主張通りパフォーマンスへの影響が小さいのであれば、ROP検知手法として非常に優れているように思われる。