
Временный root (uid 0) на ASUS Zenfone 9 с заблокированным загрузчиком через CVE-2025-21479 + утечку физических адресов на основе perf. GPLv3.
Исследовательский код и заметки для получения временного root (uid 0) на ASUS Zenfone 9 (AI2202), загрузчик которого невозможно разблокировать — потому что ASUS прекратила поддержку своего инструмента разблокировки, удалила переключатель OEM-unlocking из меню «Для разработчиков» и жёстко отклоняет fastboot oem unlock / flashing unlock на текущей прошивке.
Это не разблокировка загрузчика, и ничего не прошивается. Это runtime-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-соединение. Никакой сервер производителя не атакуется, никакой ключ подписи или токен разблокировки не обходится, и ничего не записывается ни в один раздел. Телефон, использованный для разработки, — запасной, с резервной копией и без подключения к сети. Тестируйте на оборудовании, которым владеете и которое можете позволить себе потерять.
Результат — uid 0 в SELinux-контексте u:r:kernel:s0 (он наследует SID от init_cred), то есть
фактически без ограничений. Для этого шага SELinux должен быть в режиме Permissive — в режиме Enforcing
процесс вместо этого убивается, поскольку подмена учётных данных обходит SELinux-хук.
Сначала задайте 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 (защищено trap-обработчиком) и проверяет восстановление обратным чтением.
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. Скрипты запускают собственную нагрузку для каждой операции (чтения тоже участвуют в
гонке), но никогда не запускают внутренности этих инструментов «голыми».capset может выполнить мусор). Скрипты
записывают инструкцию входа последней, проверяют каждый dword и восстанавливают при сбое — но если
запуск деградировал, перезагрузитесь перед повторной попыткой.Правила безопасности, которые стоит соблюдать: читать перед каждой записью, всегда восстанавливать то, что
патчите, никогда не оставлять внедрённую запись таблицы страниц активной, не трогать защищённую/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.
| Этап | Что делает | Где |
|---|
| 1 | CVE-2025-21479 (Adreno KGSL): SDS-пакет ошибочно классифицируется как пакет ringbuffer, что позволяет userland выполнить CP_SMMU_TABLE_UPDATE и направить TTBR0 GPU на выбранный атакующим физический адрес | src/cheese.c |
| 2 | Утечка физического адреса: perf_event_paranoid = -1 в этой сборке, поэтому аппаратная точка наблюдения на принадлежащей нам странице возвращает PERF_SAMPLE_PHYS_ADDR — физический адрес этой страницы. Заменяет утечку через pagemap, на которую опирался upstream (здесь 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/ |