
ブートローダーロックされたASUS Zenfone 9上で、CVE-2025-21479とperfベースの物理アドレスリークを利用して一時的なroot(uid 0)を取得する。GPLv3。
ブートローダーのロック解除ができない ASUS Zenfone 9 (AI2202) で 一時的な root (uid 0) を取得するための研究用コードとメモ — ASUS が unlock ツールを廃止し、開発者オプションから OEM ロック解除トグルを削除し、現行ファームウェアでは fastboot oem unlock / flashing unlock を強硬に拒否するためである。
これはブートローダーのロック解除ではなく、何もフラッシュしない。これは ランタイム root である。デバイス側のプリミティブの連鎖の末に、呼び出し元プロセスが root 資格情報を保持する。再起動で消える。
検証済み環境: ASUS Zenfone 9 (AI2202)、Android 14、ビルド 34.0304.2004.145、SPL 2024-07-05、カーネル 5.10.205-android12-9-00029-g3f12df86bfdb-ab11799032、SM8475 / Adreno 730、ブートローダーロック状態。
認可 / 範囲。 ここにあるものはすべて、運用者が所有するデバイスに対して、認可された ADB 接続を介して実行される。ベンダーのサーバーは攻撃されず、署名鍵や unlock トークンはバイパスされず、どのパーティションにも書き込みは行われない。開発に使用した端末は予備機であり、バックアップ済みで、オフラインに保たれている。自分が所有し、失っても許容できるハードウェアでテストすること。
| 段階 | 内容 | 場所 |
|---|---|---|
| 1 | CVE-2025-21479 (Adreno KGSL): SDS パケットがリングバッファパケットとして誤分類され、ユーザーランドが CP_SMMU_TABLE_UPDATE を発行し、GPU の TTBR0 を攻撃者が選んだ物理アドレスに向けられる | src/cheese.c |
| 2 | 物理アドレスリーク: このビルドでは perf_event_paranoid = -1 であるため、自分が所有するページにハードウェアウォッチポイントを設定すると PERF_SAMPLE_PHYS_ADDR — すなわちそのページの物理アドレス — が返る。上流が依存していた pagemap リーク (ここでは PFN がゼロ化されている) を置き換える | src/pa_leak.{c,h} |
| 3 | 任意の物理読み書き: 偽のページテーブルを (段階 2 で判明した既知の物理アドレスに) 構築するため、スプレーのくじ引きもワイルドウォークも不要 | src/cheese_pa.c |
| 4 | Root: ファームウェアイメージからカーネルシンボルをオフラインで解決し、デバイス上で KASLR スライドを導出し、__do_sys_capset を commit_creds(&init_cred) スタブでパッチし、capset() を呼び出す | src/call_capset.c, scripts/ |
結果は u:r:kernel:s0 SELinux コンテキストにおける uid 0 である (init_cred の SID を継承する)、つまり事実上無制限である。この手順には SELinux が Permissive である必要がある — Enforcing 下では資格情報の差し替えが SELinux フックをバイパスするため、代わりにプロセスが kill される。
まず ZF9_SERIAL に自分のデバイスのシリアルを設定する (すべてのスクリプトがこれを読む):
export ZF9_SERIAL=<your-device-serial>
# 0. one-time: build and push the device binaries (needs an Android NDK)
# see scripts/ for the exact clang invocations used
adb push cheese_pa call_capset /data/local/tmp/
# 1. full cycle: SELinux -> permissive, patch, run a command as root, restore everything
scripts/root-now.sh id
scripts/root-now.sh sh # root shell
このスクリプトは常に元のカーネルテキストと SELinux の状態を復元し (トラップで保護)、読み戻しによって復元を検証する。
uid=0(root) gid=0(root) context=u:r:kernel:s0、capset(NULL,NULL) -> 0。gpu fault threshold exceeded 3 faults in 3000 msecs)、その後さらなるコマンドは EPERM で失敗する。観測されたパッチ成功率は実行ごとに 13/13 から 2/13 dword の間で変動する。screenrecord 負荷をかけている状態では 11-13/13 が検証された。スクリプトはすべての操作 (読み取りもレースする) に対して独自の負荷を開始するが、これらのツールの内部を裸で実行することは決してない。capset の呼び出し元がゴミを実行する可能性がある)。スクリプトはエントリ命令を最後に書き込み、すべての dword を検証し、失敗時には復元する — しかし実行が劣化した場合は、再試行する前に 再起動 すること。守る価値のある安全ルール: すべての書き込みの前に読み取る、パッチしたものは常に復元する、注入したページテーブルエントリを生かしたままにしない、secure/TZ 物理メモリに触れない (致命的である)、劣化した状態から回復するには再起動する。ROADMAP.md には、各項目の根拠となる証拠とともに、落とし穴の完全なリストがある。
ROADMAP.md durable handoff: verified constants, procedure, gotchas, open paths
STATUS.md current state + verification receipts
src/ pa_leak.{c,h} · cheese.c · cheese_pa.c (workhorse: PROBE/POKE/SELFTEST/ROOT modes)
call_capset.c · host_kallsyms.c (offline symbol resolver)
scripts/ root-now.sh · patch-dwords.sh · demo-root.sh · verify-backup.sh
tools/ btf_offsets.py
ファームウェアイメージ、OTA パッケージ、デバイスログは意図的に コミットされていない (.gitignore を参照)。
GPLv3 — LICENSE を参照。