11 KiB
Libc Protections
チャンクアライメントの強制
Mallocはメモリを8バイト(32ビット)または16バイト(64ビット)のグループで割り当てます。これは、32ビットシステムではチャンクの末尾が0x8に、64ビットシステムでは0x0に整列する必要があることを意味します。セキュリティ機能は、各チャンクがこれらの特定の場所で正しく整列しているかどうかを確認し、ビンからのポインタを使用する前にチェックします。
セキュリティ上の利点
64ビットシステムでのチャンクアライメントの強制は、フェイクチャンクの配置をアドレスの16分の1に制限することで、Mallocのセキュリティを大幅に向上させます。これにより、攻撃がより複雑になり、入力値を制御できる場合に攻撃がより複雑で成功しにくくなります。
- __malloc_hookに対するFastbin攻撃
Mallocの新しい整列ルールは、__malloc_hook
を巧妙に操作する古典的な攻撃を阻止します。以前は、攻撃者がチャンクサイズを操作してこの関数ポインタを上書きし、コード実行を取得できました。今、厳格な整列要件により、そのような操作がもはや実行不可能になり、一般的な攻撃経路を閉じ、全体的なセキュリティを向上させます。
fastbinsおよびtcacheのポインタマングリング
ポインタマングリングは、メモリ管理操作でのfastbinおよびtcache Fdポインタを保護するために使用されるセキュリティ強化技術です。この技術は、メモリ情報の漏洩が必要ないあるいは既知の位置に対して直接メモリ位置を操作する攻撃手法、特に相対的な上書きを必要としないメモリ攻撃戦術を防ぐのに役立ちます。
この技術の中心は、次のような難読化式です:
New_Ptr = (L >> 12) XOR P
- Lはポインタの格納場所です。
- Pは実際のfastbin/tcache Fdポインタです。
12ビット右にビットシフトされた格納場所(L)をXOR演算する理由は重要です。この操作は、通常システムアーキテクチャの制約により予測可能な最も下位の12ビットの決定論的性質に対処します。ビットをシフトすることで、予測可能な部分が方程式から取り除かれ、新しい難読化されたポインタのランダム性が向上し、これらのビットの予測可能性に依存する攻撃に対して保護されます。
この難読化されたポインタは、プログラムが使用するアドレスをランダム化する**アドレス空間配置ランダム化(ASLR)**によって提供される既存のランダム性を利用します。これにより、攻撃者がプロセスのメモリレイアウトを予測するのを困難にします。
セキュリティ上の利点
ポインタマングリングは、ヒープ管理での部分的および完全なポインタの上書きを防ぐことを目的としており、セキュリティを大幅に向上させます。この機能は、いくつかの方法で攻撃手法に影響を与えます:
- バイトバイト相対上書きの防止:以前は、攻撃者が正確なアドレスを知らなくても、ヒープチャンクを異なる場所にリダイレクトするためにポインタの一部を変更できました。これは、リークレスのHouse of Roman攻撃で明らかな手法です。ポインタマングリングにより、そのような相対的な上書きはヒープリークなしではブルートフォースが必要となり、成功の可能性が著しく低下します。
- Tcache Bin/Fastbin攻撃の難化:fastbinまたはtcacheエントリを操作して関数ポインタ(例:
__malloc_hook
)を上書きする一般的な攻撃が妨げられます。たとえば、攻撃はLibCアドレスをリークし、tcacheビンにチャンクを解放し、その後Fdポインタを__malloc_hook
にリダイレクトして任意のコードを実行する可能性があります。ポインタマングリングにより、これらのポインタは正しく難読化される必要があり、正確な操作のためにヒープリークが必要となり、攻撃の障壁が高まります。 - ヒープ以外の場所でのヒープリークの要件:ヒープ以外の場所(スタック、.bssセクション、またはPLT/GOTなど)に偽のチャンクを作成することも、ポインタマングリングの必要性によりヒープリークが必要となります。これにより、これらの領域の攻撃の複雑さが拡大し、LibCアドレスを操作する必要があるのと同様になります。
- ヒープアドレスのリークがより困難に:ポインタマングリングにより、fastbinおよびtcacheビンのFdポインタをヒープアドレスのリーク元として使用することが制限されます。ただし、未整列の、小さな、大きなビンのポインタは未難読化のままであり、アドレスのリークに使用できます。この変化により、攻撃者はこれらのビンを探索して攻撃可能な情報を探る必要がありますが、一部の手法ではリーク前にポインタを復号化することができる場合もありますが、制約があります。
ヒープリークを使用したポインタの復号化
{% hint style="danger" %} プロセスの詳細な説明については、こちらの元の投稿を確認してください。 {% endhint %}
アルゴリズムの概要
ポインタの難読化および復号化に使用される式は次のとおりです:
New_Ptr = (L >> 12) XOR P
ここで、Lは格納場所であり、PはFdポインタです。Lを右に12ビットシフトすることで、XORの性質により、Lのシフトされた部分がゼロになり、Pの対応するビットが変更されないため、Pの上位12ビットが得られます。
アルゴリズムの主要なステップ:
- 最上位ビットの初期リーク: シフトされたLをPとXORすることで、Lのシフトされた部分がゼロになり、Pの対応するビットが変更されないため、Pの上位12ビットが得られます。
- ポインタビットの回復: XORが逆向きになるため、結果と1つのオペランドを知っていると、他のオペランドを計算できます。この特性を使用して、既知のビットセットと難読化されたポインタの部分とを繰り返しXORすることで、Pの全ビットセットを推測できます。
- 反復的な難読化解除: このプロセスは繰り返され、前のステップで発見されたPのビットを使用して、難読化されたポインタの次のセグメントをデコードします。すべてのビットが回復されるまで、このプロセスが繰り返されます。
- 決定論的ビットの処理: シフトによりLの最後の12ビットが失われますが、これらは決定論的であり、後処理で再構築できます。
このアルゴリズムの実装はこちらで見つけることができます:https://github.com/mdulin2/mangle
ポインターガード
ポインターガードは、glibcで使用されるエクスプロイト緩和技術であり、特にatexit()
などのライブラリ呼び出しによって登録された関数ポインターを保護するために使用されます。この保護は、ポインターをスクランブルし、スレッドデータ(fs:0x30
)に格納された秘密とXOR演算を行い、ビット単位の回転を適用することを含みます。このメカニズムは、関数ポインターを上書きして制御フローを乗っ取る攻撃者を防ぐことを目的としています。
リークを使用してポインターガードをバイパスする
- ポインターガード操作の理解: ポインターのスクランブル(マングリング)は、ポインターを64ビットの秘密とXOR演算し、0x11ビットの左回転を行う
PTR_MANGLE
マクロを使用して行われます。元のポインターを回復するための逆操作はPTR_DEMANGLE
によって処理されます。 - 攻撃戦略: この攻撃は既知の平文アプローチに基づいており、攻撃者はマングリングに使用される秘密を推測するためにポインターの元のバージョンとマングリングされたバージョンの両方を知る必要があります。
- 既知の平文の悪用:
- 固定関数ポインターの特定: glibcのソースコードを調査するか、
__libc_pthread_functions
のような初期化された関数ポインターテーブルを調べることで、攻撃者は予測可能な関数ポインターを見つけることができます。 - 秘密の計算:
__pthread_attr_destroy
などの既知の関数ポインターとその関数ポインターテーブルからのマングリングされたバージョンを使用して、秘密を計算することができます。これは、マングリングされたポインターを逆回転(右回転)し、その後、関数のアドレスとXOR演算することで行われます。
- 代替平文: 攻撃者は、0や-1などの既知の値でポインターをマングリングして、これらがメモリ内で識別可能なパターンを生成するかどうかを実験することができます。これらのパターンがメモリダンプで見つかると、秘密が明らかになる可能性があります。
- 実践的な応用: 秘密を計算した後、攻撃者は制御された方法でポインターを操作することができ、libcベースアドレスの知識と任意のメモリ位置を読み取る能力を持つ多スレッドアプリケーションでポインターガード保護をバイパスすることができます。