
# CVE-2023-20938 のローカル権限昇格の概念実証 Android binder の use-after-free 脆弱性(CVE-2023-20938)に対するローカル権限昇格の概念実証であり、脆弱なテストビルド上で root を取得し、SELinux を無効化します。
CVE-2023-20938(Android binder ドライバの Use-After-Free)のローカル権限昇格 Proof-of-Concept です。
非特権シェルから uid 0 を取得し、SELinux を permissive に落とした上で、root シェルを起動します。
このビルドのターゲット:Android Cuttlefish / Goldfish AVD, x86_64, カーネル 5.10.107。
⚠️ 認可された研究・教育目的のみ。 自身のテスト端末やエミュレータに対してのみ実行してください。このバグは現在の Android では修正されています。本 PoC は古く、意図的に脆弱にしたテストビルドを対象としています。無保証です。

asciinema で録画し、agg で GIF に変換しました。
drivers/android/binder.c, binder_transaction_buffer_release()binder_transaction_buffer_release() は攻撃者が指定した offsets_size を信頼します。
offsets_size がエントリ1つ分より1バイト短い(sizeof(offset) - 1 == 7)トランザクションを送信すると、解放処理が末尾の flat_binder_object を誤って解析し、binder_node の参照カウントを過剰にデクリメントして、ダングリング参照が生存したまま解放されます。
このエクスプロイトは、クロスキャッシュリークを連鎖させて任意読み取りと制御された書き込みを実現し、クレデンシャルを書き換えます。段階ごとの流れ(poc_cf.c の main() 参照):
B. クロスキャッシュリーク
binder_nodes の波を解放 (kmalloc-128) → ページがバディアロケータに戻る
→ eventpoll epitems (eventpoll_epi キャッシュ、0x80 バイトオブジェクト) でそのページを回収
ダングリング binder トランザクションが epitem からカーネルポインタを読み取る
これにより struct file とその epitem アドレスが漏洩する
C. 任意読み取り
UAF を再トリガーし、スプレーした偽の binder_node を使って
binder の hlist_del アンリンクを制御された *(where) = what 書き込みに変換する
2回の書き込みで被害ファイルの f_inode を epitem 上に重ねた偽の inode にリダイレクトし、
それを S_PRIVATE にする。そのファイルに対して FIGETBSZ を実行すると
*(epitem.event.data + 24) が返る:操作可能な 4 バイト読み取り
D/E/F. 権限昇格
KASLR を無効化(f_op 経由で eventfd_fops をリーク)、init_task のタスクリストをたどって
launcher の cred に到達し、その uid/gid セットと seccomp.mode をゼロにし、
さらに selinux_state をゼロにして SELinux を permissive に落とす
G. 事前にフォークされ eventfd フリーな launcher が root シェルを実行
UAF を駆動するには、2つの非特権プロセスが binder ノードを共有する必要があります。
hwservicemanager の ITokenManager(createToken / get-by-token)を利用します。
clang -static -Wall -Wextra -o poc_cf poc_cf.c -lpthread
poc_cf.c と同じディレクトリに binder.h を置きます。ビルドは意図的に静的リンクにしており、ターゲットにツールチェインがなくても単一バイナリで動作するようにしています。
Cuttlefish ホストパッケージディレクトリから、対応するカーネル + initramfs を使って AVD を起動します。
HOME=$PWD ./bin/launch_cvd \
--kernel_path=bzImage \
--initramfs_path=initramfs.img
adb push poc_cf /data/local/tmp/
adb shell /data/local/tmp/poc_cf
成功すると各段階を経て root シェルに到達します。
このエクスプロイトは 確率的 です。各段階で内部的にリトライを行います。
クリーンに停止するかゲストがパニックするかに関わらず、再実行 してください(パニック後は AVD の /data がリブートでワイプされるため、バイナリを再度プッシュする必要があります)。
低速またはビジーなターゲットで回収成功率が低い場合は、待機時間を延ばすかスプレー数を増やしてください(poc_cf.c の先頭付近にある #define の値):
| マクロ | デフォルト | 意味 |
|---|---|---|
SETTLE_US | 5000 | ノード解放後、スプレー前の一時停止 |
FAKE_SPRAY_COUNT | 8 | スロット回収に向けて競合する blocked sendmsg() の数 |
FAKE_WAIT_US | 10000 | スプレー解放後、アンリンク前の一時停止 |
PRESKIP_US | 1000 | 犠牲となる最初の sendmsg 後の一時停止 |
KW_RETRY_COUNT | 8 | 1回のアンリンク書き込みあたりの試行回数 |
STAGE_C_TRIES | 8 | 任意読み取りを有効にする試行回数 |
構造体オフセットとランダム化されていないシンボルのベースライン(*_NOKASLR)は この 5.10.107 ビルドに固有 であり、他の環境では動作しません。別のカーネルに移植するには、poc_cf.c の先頭付近にあるオフセットブロックとシンボルブロックを、対応する vmlinux から取得した値に更新してください。私は Pixel 7 でもオフセットを抽出して試しました。