
adb-управляемый загрузчик KernelSU для стоковых Google Pixel: временный доступ на чтение/запись к ядру через CVE-2026-43499 (GhostLock), затем поздняя загрузка kernelsu.ko, соответствующего сигнатуре, для текущего KMI. Не зависит от менеджера.
Инструмент, управляемый через adb, который превращает стоковый Google Pixel с заблокированным загрузчиком в устройство с root-доступом KernelSU без разблокировки загрузчика или изменения boot-образа. С хоста он запускает непривилегированный userspace-эксплойт ядра на устройстве для получения временного доступа на чтение/запись к ядру, использует этот примитив для патча root-учётных данных, а затем выполняет позднюю загрузку загружаемого модуля ядра KernelSU (kernelsu.ko) в работающее GKI-ядро и передаёт управление уже установленному менеджеру KernelSU (KernelSU, KernelSU-Next, SukiSU или другому варианту). Он нацелен на Pixel с Android 17 на ядрах GKI 6.1 и 6.6 и управляет всем процессом через adb shell для детерминированной последовательности действий и журналирования с временными метками.
Полезная нагрузка на устройстве — это вендорный полный цепочечный LPE для CVE-2026-43499 («GhostLock»), use-after-free в стеке при наследовании приоритета futex/rtmutex в kernel/locking/rtmutex.c. На пути отката requeue-PI функция remove_waiter() очищает pi_blocked_on у requeuer'а (current), а не у фактического ожидающего, оставляя висячий указатель на освобождённый слот в стеке ядра, который содержал rt_mutex_waiter. Ошибка достижима из обычного непривилегированного процесса:
owner, waiter, consumer) строят PI-цепочку; ожидающий паркуется на FUTEX_WAIT_REQUEUE_PI, главный поток запускает FUTEX_CMP_REQUEUE_PI, а sched_setattr от consumer'а запускает откат.pselect()/select() (или маршрутом TCP_ZEROCOPY_RECEIVE на некоторых целях 6.1), чьи слова fd_set ложатся поверх структуры ожидающего, записывая поддельный плоский rt_mutex_waiter, так что висячий указатель проходит по контролируемым атакующим полям rb-tree и lock — единый примитив контролируемой записи указателя.mm_struct//.Затем состояние root и SELinux патчится через канальный примитив: cred корневого дочернего процесса обнуляется до uid/gid 0 с полными наборами capability, его SELinux osid/sid устанавливаются в SECINITSID_KERNEL, seccomp очищается, а selinux_state.enforcing устанавливается в 0.
Цепочка эксплойта, оракул KASLR и побочный канал KernelSnitch происходят из исследования NebuSec IonStack Part II — GhostLock (PoC NebuSec/CyberMeowfia, Apache-2.0), адаптированного здесь для Pixel/aarch64. См. Атрибуция и лицензия.
Ровно одна стадия цепочки может вызвать панику ядра: вывод сдвига KASLR, который соревнуется за страницу, которую, как он надеется, перехватил. Все остальные стадии безопасны для повтора, а база текста ядра фиксирована на время одной загрузки. Хост-процесс разделяется по этому свойству:
KASLR_BASE в своём окружении. Запись поддельного ожидающего перенаправляет ctl_table.data sysctl random_table на известный указатель текста ядра; чтение /proc/sys/kernel/random/boot_id утекает его через proc_do_uuid(), а вычитание смещения образа даёт _stext/базу KASLR. restore_slide_boot_id() восстанавливает повреждённый ctl_table.data. Поскольку проигранная гонка перезагружает устройство, ожидание загрузки предшествует каждой попытке, а проверка живости классифицирует исчезновение как панику. При успехе журнал устройства выдаёт slide-kaslr-ok pid=<pid> base=<hex>, и база привязывается к текущей загрузке.KASLR_BASE=0x<base>. Этот путь никогда не вызывает панику и зацикливается, пока id не сообщит через временный su.Подключённое устройство сопоставляется с data/targets.json во время выполнения; ничего не захардкожено под устройство. Выполняются два независимых сопоставления:
uname -r), либо из совпавшей записи цели, либо выводится из строки релиза (например, android14-6.1).Повторное использование одной полезной нагрузки на многих устройствах следует из структуры GKI/KMI. Каждое устройство на одной сборке GKI запускает побайтово идентичный vmlinux, а смещения полей структур (task_struct->cred, cred->uid, …) заморожены на время жизни ветки KMI контрактом типов KMI и принудительной проверкой CRC MODVERSIONS. Абсолютные адреса символов ядра, напротив, определяются линковщиком для каждой сборки ab<NNN>, поэтому фиксированные смещения эксплойта принадлежат одному конкретному vmlinux; разные образы ядра требуют разных полезных нагрузок, даже если их KMI совпадает. data/targets.json кодирует именно это: многие устройства дедуплицируются на одну полезную нагрузку, ключом которой является образ ядра, а отличающийся образ ядра получает свою собственную.
Поздняя загрузка LKM требует GKI-ядра (5.10+) с поддержкой загружаемых модулей и совпадающего по KMI .ko. Модуль ядра KernelSU аутентифицирует свой менеджер, проверяя в ядре блок подписи v2 APK менеджера и сравнивая SHA-256 сертификата подписи с парой KSU_EXPECTED_SIZE/KSU_EXPECTED_HASH, скомпилированной в .ko. Поэтому kernelsu.ko, поставляемый в релизе менеджера, и APK этого менеджера разделяют одну личность подписи; несоответствующий ksud загружает драйвер, но никогда не устанавливает бит авторизации менеджера, оставляя устройство без рабочего root.
Процесс соблюдает эту связь: он определяет путь к APK установленного менеджера (pm path <package manager>), вытягивает APK, извлекает lib/arm64-v8a/libksud.so как бинарник ksud, а если менеджер не установлен — прерывается. Удерживая временный root, этот ksud размещается как исполняемый файл, принадлежащий root, и вызывается как ksud late-load --kmi <kmi> --package-name <package manager>. Поздняя загрузка определяет текущий KMI, вытягивает "{kmi}_kernelsu.ko" из своих встроенных ресурсов, выполняет ручную релокацию символов (разрешая каждый символ SHN_UNDEF через /proc/kallsyms, переписывая записи в SHN_ABS) и вызывает init_module(2) на пропатченном буфере. Затем она выполняет остальной конвейер инициализации загрузки (установка ksud, restorecon, загрузка sepolicy.rule и root-профилей, запуск скриптов post-fs-data/stage, монтирование оверлея модулей).
Поздняя загрузка демонизируется и повторно включает SELinux в своём форкнутом дочернем процессе, который разрушает демон временного su эксплойта; поэтому проверка не должна идти через su. Вместо этого загруженный драйвер опрашивается напрямую через его поверхность системных вызовов, доступную из обычной оболочки без root: опрашивается ksud debug version и анализируется сообщённая версия ядра. Непустая, ненулевая версия подтверждает, что драйвер резидентен и отвечает. Путь установки драйвера — это механизм reboot(2) magic → install-fd → KSU_IOCTL_GET_INFO (reboot(0xDEADBEEF, 0xCAFEBABE, 0, &fd) устанавливает анонимный fd [ksu_driver]; GET_INFO возвращает {version, flags, features, uapi_version}), с устаревшим каналом prctl(0xDEADBEEF, …) в качестве запасного. Тот же зонд при запуске сокращает весь процесс, когда модуль уже резидентен для текущей загрузки.
Эксплойт размещает свой временный su в /apex/com.android.virt/bin/su на tmpfs, смонтированной поверх этого каталога bin apex внутри пространства имён монтирования adbd, потому что этот каталог предшествует /system/bin в PATH оболочки — так что голый su в adb shell достигает временного, пока идёт процесс. Затем поздняя загрузка разрушает демон временного su (см. (e)) без удаления тени, что оставляет голый adb shell su запускающим осиротевший клиент, который завершается с su: connect daemon: Permission denied, даже несмотря на то, что root работает, и скрывает реальные бинарники apex (crosvm, virtmgr, vm, …). Как только проверка сообщает о живом драйвере, процесс размонтирует tmpfs стейджинга — через собственный /system/bin/su KernelSU, поскольку демон эксплойта уже ушёл — удаляет клиент временного su, сокет и журнал и сообщает, какой su теперь разрешает обычный . Это best-effort: при сбое он предупреждает с ручной командой вместо провала запуска, а перезагрузка очищает монтирование в любом случае.
adb на хосте, с авторизованным устройством (включена отладка по USB).kernelsu.ko.artifacts/exploits/ (см. Сборка полезных нагрузок).# Одно устройство на adb; менеджер установлен; полезные нагрузки собраны.
bin/pixel-ksu-root
Драйвер сопоставляет устройство с data/targets.json, выполняет двухфазный процесс KASLR, выполняет позднюю загрузку модуля через производный от менеджера ksud и проверяет через системный вызов драйвера. Он завершается с ненулевым кодом, если для устройства не находится полезная нагрузка, если менеджер не установлен или если проверка никогда не сообщает о живом драйвере.
KASLR_BASE=0x<hex> — передаётся полезной нагрузке устройства во время Фазы B для повтора против фиксированной, уже выведенной базы для текущей загрузки. Не установлена во время Фазы A, чтобы полезная нагрузка выводила базу сама.ANDROID_NDK_HOME — путь к Android NDK, требуется только при сборке полезных нагрузок.API — уровень API Android для тулчейна NDK при сборке полезных нагрузок (по умолчанию 35).pixel-ksu-root/
├── bin/ Точка входа хост-драйвера (процесс, управляемый adb)
├── data/
│ └── targets.json Таблица сопоставления устройство→полезная нагрузка и устройство→KMI
├── exploit/ Исходный код вендорной полезной нагрузки CVE-2026-43499
│ ├── Makefile Сборка aarch64 NDK по целям
│ ├── src/ Базовый набор исходников android15-6.6
│ │ ├── main.c slide.c fops.c pipe.c root.c preload.c util.c
│ │ ├── su_daemon.c Хелпер временного su, порождаемый цепочкой
│ │ ├── kernelsnitch/ Заголовки побочного канала занятости futex-хэша
│ │ └── targets/ target.h для каждого устройства+сборки (смещения ядра)
│ └── src/61/ Набор исходников android14-6.1 (slide61.c, TCP-маршрут)
├── lib/ Общие функции оболочки/хелперы на стороне хоста
├── scripts/
│ └── build-payloads.sh Собирает и дедуплицирует набор полезных нагрузок
├── artifacts/
│ └── exploits/ Собранные, дедуплицированные .so файлы полезных нагрузок
└── docs/ Заметки по дизайну и анализу
scripts/build-payloads.sh оборачивает exploit/Makefile по целям и выдаёт дедуплицированный набор полезных нагрузок, названный в data/targets.json, в artifacts/exploits/. Он собирает один .so на уникальную группу смещений (из цели build_from этой группы), а не по одному на устройство.
export ANDROID_NDK_HOME=/path/to/android-ndk # должен содержать тулчейн aarch64 NDK
scripts/build-payloads.sh # собирает каждую полезную нагрузку в data/targets.json
Makefile выбирает тулчейн Clang aarch64 NDK из ANDROID_NDK_HOME и компилирует по одной цели за раз; API (по умолчанию 35) выбирает драйвер aarch64-linux-android<API>-clang. Набор исходников выбирается по семейству ядра — цели android15-6.6 компилируют базовый src/, цели android14-6.1 компилируют src/61/ — а абсолютные смещения ядра каждой цели берутся из src/targets/<codename>-<build>/target.h. Для прямой сборки одной цели:
make -C exploit TARGET=husky-CP2A.260705.006
data/targets.json перечисляет 19 записей устройство/сборка, покрывающих 18 моделей Pixel (bluejay встречается на двух сборках прошивки), сгруппированных в 5 полезных нагрузок со смещениями ядра. Выбор по образу ядра, поэтому устройства, разделяющие vmlinux, дедуплицируются на одну полезную нагрузку; отличающийся образ ядра получает свою собственную.
Устройства на общем образе ядра android14-6.1-a (6.1.157-android14-11-gbd23337e42e7-ab14791245) охватывают семейства Pixel 6/6 Pro/6a, 7/7 Pro/7a, 8/8 Pro и 9/9 Pro/9 Pro XL/9 Pro Fold; android14-6.1-b и android14-6.1-akita изолируют модели на том же KMI, чья сборка ядра или смещения отличаются; android15-6.6 покрывает семейство Pixel 10.
NebuSec/CyberMeowfia под Apache-2.0; обнаружение приписано инструментарию VEGA от NebuSec, раскрыто 2026-07-07.exploit/ добавляет смещения целей android14-6.1 и android15-6.6 и демон поздней загрузки KernelSU поверх эксплойта NebuSec; оно не несёт отдельной лицензии и наследует условия вышестоящего Apache-2.0.ksud и модель авторизации менеджера, которые этот инструмент загружает поздней загрузкой.Вендорный исходный код в exploit/ сохраняет свою вышестоящую лицензию (Apache-2.0 для производного от NebuSec эксплойта). Этот проект агностичен к менеджеру: он выполняет позднюю загрузку менеджера любого варианта KernelSU, который установлен, и не нацелен на какой-либо конкретный форк и не включает его.
ksudinit_module и обнаружение драйвераapk_sign.rssk_buffpipe_bufferashmem_miscs[0].fops поддельной структурой file_operations, каждый слот которой указывает на реальную, совместимую по прототипу функцию ядра (configfs_bin_write_iter, configfs_read_iter, copy_splice_read, ashmem_ioctl, noop_llseek, …), так что CFI на прямых рёбрах удовлетворяется, а read/write/splice на ashmem-fd дают ограниченный доступ R/W к ядру.pipe_buffer на утёкшей slab-странице (page указывает на любую цель через преобразование vmemmap↔прямое отображение, ops = anon_pipe_buf_ops, PIPE_BUF_FLAG_CAN_MERGE), так что обычные read()/write() на канале перемещают байты в произвольные адреса ядра и обратно — стабильный произвольный доступ R/W к ядру.uid=0/proc/sys/kernel/random/boot_id с загрузкой, записанной при захвате; любое изменение отбрасывает базу и возвращает к Фазе A. Внешний цикл повторяет вывод→повтор между перезагрузками.adb shellumount| Полезная нагрузка | KMI | Собрано из | Устройства |
|---|
android14-6.1-a | android14-6.1 | bluejay-CP2A.260705.006 | oriole, raven, bluejay (CP2A), panther, cheetah, comet, shiba, husky, lynx, caiman |
android14-6.1-b | android14-6.1 | komodo-CP2A.260705.006 | komodo, tegu, tokay |
android14-6.1-akita | android14-6.1 | akita-CP2A.260805.005 | akita |
android14-6.1-cp1a | android14-6.1 | bluejay-CP1A.260405.005 | bluejay (CP1A) |
android15-6.6 | android15-6.6 | blazer-CP2A.260705.006 | frankel, blazer, mustang, rango |