
Локальный root-эксплойт для CVE-2025-21479 (Adreno KGSL) на iQOO Neo8 (SM8475) — чтение/запись физической памяти, отключает SELinux, запускает root-оболочку.
Экспериментальный код локального повышения привилегий для CVE-2025-21479, нацеленный на Qualcomm Adreno GPU (KGSL) в vivo V2301A (iQOO Neo8 / PD2301, Snapdragon 8+ Gen 1 / SM8475, Adreno A730, Android 14, GKI-ядро 5.10).
По принципу действия эксплойт через команду CP_SMMU_TABLE_UPDATE KGSL управляет SMMU GPU, получая возможность произвольного чтения и записи физической памяти, после чего отключает SELinux, находит символы ядра и получает root-оболочку — без перепрошивки и без разблокированного загрузчика.
Только для исследований безопасности на устройствах, которыми вы владеете или на которые у вас есть разрешение.
and $12, 0x3) содержит дефект: пакет Self-Draw-State (SDS) ошибочно принимается за RingBuffer (RB). Поэтому, отправляя из пользовательского пространства поддельный CP_SMMU_TABLE_UPDATE в составе SDS-блока, можно перезаписать TTBR0 GPU и указать таблицам страниц GPU на произвольную физическую страницу — после этого GPU может читать и писать в любую физическую память.stabilize_rw отображение укрепляется в самоотображаемую область размером 2 МБ, что поддерживает быстрое чтение большими объёмами (fast_pread_bytes и т.п.).selinux_state.enforcing. Затем с фиксированного физического базового адреса 0xA8000000 дампится образ ядра (по умолчанию до 43 МБ) — у SM8475 нет физического KASLR — и структуры kallsyms парсятся прямо из сырого дампа (порядок байт определяется автоматически), поэтому виртуальный KASLR также не влияет.prepare_kernel_cred(0) → commit_creds() + отключение seccomp) патчит __do_sys_capset. Дочерний процесс, созданный через fork, вызывает capset() → получает uid 0 → восстанавливает перезаписанный код ядра → повторно выполняет указанную вами команду (по умолчанию sh) в домене u:r:shell:s0. В итоге код ядра побайтово совпадает с состоянием до запуска.Полная информация об устройстве — в device_info.txt.
├── source/
│ ├── cheese.c # 漏洞利用主程序(include 了下面两个文件)
│ ├── kallsyms_lookup.c # 针对原始内核映像的 kallsyms 解析器
│ └── adrenaline.h # KGSL ioctl / 命令定义(作者 hawkes)
├── exploit # 预编译 arm64 二进制(动态链接,minSdk 21,
│ # NDK r30-beta2)
├── device_info.txt # 用于调参的目标设备信息
└── README.md
Примечание: cheese.c выполняет #include для kallsyms_lookup.c и adrenaline.h; при компиляции все три файла должны находиться в одном каталоге.
Подойдёт любой свежий Android NDK (r25+), например:
# 静态链接(自包含、任意 API 等级可运行;体积较大)
aarch64-linux-android34-clang -o cheese source/cheese.c -static
# 动态链接(体积小,依赖设备自带的 libc;API 等级需匹配设备)
aarch64-linux-android34-clang -o exploit_dyn source/cheese.c
# 若要兼容 API 21+ 设备,请改用 aarch64-linux-android21-clang
adb push exploit /data/local/tmp/
adb shell
chmod +x /data/local/tmp/exploit
/data/local/tmp/exploit
Подробный туториал (внутри есть инструмент в один клик)
После успешного выполнения вы получите shell с uid=0, а SELinux в этой сессии будет отключён. Процесс после повышения привилегий повторно запускает argv[1..] (по умолчанию sh). При каждом запуске есть обратный отсчёт («pte wait 3 second») и прогресс чтения 43 МБ ядра («Read X / 43 MB»).

$(find /data/app -type f -name "libksud.so" | grep resukisu) late-load --kmi android12-5.10
Константы, зависящие от устройства, находятся в начале source/cheese.c:
KERNEL_PHYS_BASE — физический базовый адрес ядра (по умолчанию 0xA8000000, SM8475)IDMAP_PG_DIR_OFFSET — смещение idmap_pg_dir (по умолчанию 0x245a000)selinux_state_offset — смещение для поиска selinux_state (по умолчанию 0x729030; фактический физический адрес = физический адрес idmap_pg_dir + это смещение)gPhyAddrs[] — кандидаты физического базового адреса, перебираемые по очередиDEFAULT_KERNEL_SIZE_MB — запасное значение / предел чтения ядраПредварительные требования: GPU серии Adreno A730/A7xx (затронутая прошивка), ядро, в котором из физического дампа можно извлечь kallsyms, и отсутствие физического KASLR.
can't get GPU r/w — не удалась инициализация GPU; сначала подождите, если не помогает, попробуйте CHEESE_ATTEMPT=1..n или вручную укажите CHEESE_PHYADDR; при отладке одиночной попытки добавьте CHEESE_NO_RETRY=1.CHEESE_READ_FULL_KERNEL=1 (увеличив предел через CHEESE_KALLSYMS_MAX_MB), затем рассмотрите CHEESE_FORCE_MANUAL_PATCHFINDER=1.cheese, на котором основан этот инструментadrenaline.h 2020 года, определяющий интерфейс ioctlМодифицировано на основе zhuowei/cheese (в исходном репозитории нет лицензии). Полное описание принципа — в бюллетене уязвимостей.
Этот репозиторий предназначен только для образовательных целей и исследований в области безопасности; используйте его только на устройствах, которыми вы владеете или на которые у вас есть разрешение. Автор не несёт ответственности за любое злоупотребление. Использование этого инструмента может привести к потере гарантии или нарушению соответствующих условий обслуживания.
| Модель | vivo V2301A (iQOO Neo8, кодовое имя PD2301, плата taro) |
| SoC | SM8475 (Snapdragon 8+ Gen 1) + Adreno 730 |
| Android | 14 (UP1A.231005.007, SDK 34, qssi-user) |
| Ядро | 5.10.198-gki-gfb2f5774872c |
| SELinux | Enforcing (отключается этим инструментом во время выполнения) |
| Переменная | Назначение |
|---|
CHEESE_PHYADDR=<phys> | Принудительно задаёт физический базовый адрес ядра (пропускает встроенную таблицу поиска gPhyAddrs). |
CHEESE_ATTEMPT=<n> | Использует gPhyAddrs[n] вместо gPhyAddrs[0]; после сбоя инициализации GPU maybe_retry автоматически увеличивает номер и повторяет попытку. |
CHEESE_SWAPPER_PG_DIR_OFF=<off> | Пропускает сканирование swapper_pg_dir и использует указанное абсолютное смещение. |
CHEESE_READ_FULL_KERNEL=1 | Читает полный образ ядра (по умолчанию предел чтения — DEFAULT_KERNEL_SIZE_MB = 43 МБ). |
CHEESE_KALLSYMS_MAX_MB=<MB> | Переопределяет предел чтения kallsyms. |
CHEESE_FORCE_MANUAL_PATCHFINDER=1 | Принудительно включает ручной patchfinder в качестве запасного варианта (init_cred, prepare_kernel_cred). |
CHEESE_SKIP_GPU=1 | Пропускает стадию чтения/записи через GPU (только для отладки). |
CHEESE_NO_RETRY=1 | При неудаче сразу завершает работу, без повторов. |