
Эксплуатирует CVE-2026-43499 на устройствах Android GKI 6.12: детерминированное произвольное чтение/запись в ядре, обход KASLR и полный root через payload LD_PRELOAD.
Эксплойт для ядра Linux на линии Android GKI 6.12 (устройства Samsung и
Pixel), нацеленный на CVE-2026-43499: ошибочный откат remove_waiter() в
rt_mutex_start_proxy_lock(), использующий current вместо waiter::task,
из-за чего pi_blocked_on ожидающего продолжает указывать на его (позже
снимаемый со стека) rt_mutex_waiter. В сочетании с перезаписью стека fd_set
через pselect() и обходом поддельного rb_tree, управляемым потребителем через
sched_setattr, это даёт детерминированный примитив записи в ядре и полный root.
Эксплойт работает как разделяемая библиотека LD_PRELOAD (preload.so) и после
получения root устанавливает демон su и обои как артефакты.
Статус: активная разработка. Смещения глубины стека для каждой цели (
PSELECT_*сдвиги слов) и поведение slide зависят от устройства и всё ещё проверяются на устройстве. Текущий фокус доводки — цель m1q (ZF1).
Когда FUTEX_CMP_REQUEUE_PI перемещает ожидающего и обход цепочки обнаружения
взаимоблокировки возвращает -EDEADLK, __rt_mutex_start_proxy_lock()
выполняет откат через remove_waiter() (rtmutex.c:1535). Откат корректно
удаляет ожидающего из дерева ожидания, но очищает pi_blocked_on у
вызывающего requeue, а не waiter->task->pi_blocked_on. pi_blocked_on
ОЖИДАЮЩЕГО остаётся висящим на его стековом rt_mutex_waiter, который
снимается со стека после истечения таймаута фьютекса.
После таймаута область стекового кадра ожидающего в ядре переиспользуется:
core_sys_select() копирует три fd_set в этот стековый буфер (стековый путь
nfds < 344 на ZF1). Сформированный массив слов fd_set заново материализует
поддельный rt_mutex_waiter / поддельную задачу / поддельный rt_mutex
(с корнем rb_tree, указатели которого контролируются). Затем поток-потребитель
вызывает sched_setattr_tid(waiter) → rt_mutex_adjust_pi() →
rb_erase_cached, что приводит к записи контролируемого значения по
произвольному адресу.
Весь примитив требует цикла PI-цепочки: владелец удерживает f_pi_target
и также блокируется на f_pi_chain (удерживаемой ожидающим), поэтому обход
цепочки проходит владелец → chain → waiter → target → владелец и завершается
ошибкой -EDEADLK. Удаление цикла заставляет requeue вернуть успех без какого-
либо эффекта в ядре.
sched_blocked_reason (основной для m1q): сохранённый
обратный адрес PC заблокированного kworker (stack_trace_save_tsk)
читается из кольцевого буфера и сравнивается с закомпилированным
смещением worker_thread.SLIDE_LOGGERS_0_1 в данные sysctl boot_id через алиас
линейного отображения; утёкшее значение восстанавливает stext.mm_struct и
использование коллизии хеша futex для поиска mm_struct в куче, что даёт
утечку адреса страницы кучи ядра, используемой как база для распыления
поддельных объектов.configfs_bin_write_iter в слот write_iter (+0x30) статического
file_operations ashmem в .data. Открытие ashmem затем даёт произвольное
чтение/запись в ядре через путь двоичной ФС configfs.Необязательная фаза STAGE3=1, которая создаёт дочерний процесс-мост,
заменяет его mm->pgd на подготовленную поддельную таблицу страниц (3 уровня:
PGD→L1→L2, RX-лист для цикла + RW-лист для стека) и позволяет дочернему
процессу выполнять ассемблерный блоб, использующий только регистры, который
патчит собственный cred через окно физического сканирования 2 МБ —
физическое чтение/запись всей ОЗУ без примитивов configfs/pipe. В настоящее
время подключена только к цели m1q и не запускалась на устройстве.
41 цель в src/targets/<codename>-<build>/, каждая требует как минимум
target.h (смещения символов ядра, KIMAGE_TEXT_BASE, базовый адрес
direct-map, смещения структур). Цели Pixel (comet, tokay, tegu, caiman,
komodo, frankel, mustang, rango, stallion, blazer) переопределяют
общие исходники; цели Samsung (m1q-*) добавляют специфичную для устройств
логику.
make list-projects # full list
Раскладки структур ядра различаются для каждого устройства — смещения должны быть проверены по фактическому бинарнику ядра / kallsyms для каждой цели.
Результат: build/<PROJECT>/bin/preload.so (разделяемая библиотека,
загружаемая через LD_PRELOAD).
# Default project
CC=clang make
# Specific device target (default: blazer-CP2A.260605.012)
CC=clang make PROJECT=m1q-BP4A.251205.006
# Without CC=clang: uses NDK if found ($NDK_ROOT / $ANDROID_NDK_HOME /
# $ANDROID_NDK_ROOT), otherwise host clang + Android sysroot
make PROJECT=m1q-BP4A.251205.006
# Show build configuration
make info
# Clean
make clean
Сборка встраивает PIE-бинарник su_daemon (src/su_daemon.c, собирается в
build/embed/su_daemon_aarch64_pie) и assets/wallpaper.webp в preload.so
через src/su_blob.S / src/wallpaper_blob.S.
Скопируйте результаты сборки в /data/local/tmp, затем запустите эксплойт под
LD_PRELOAD. Запускайте БЕЗ tee на живой цели при доводке — tee буферизует
собственный stdio и теряет хвост лога при панике ядра (stdout эксплойта не
буферизуется, поэтому перенаправление сразу в файл сохраняет каждую строку):
adb push build/m1q-BP4A.251205.006/bin/preload.so /data/local/tmp/preload.so
adb push build/embed/su_daemon_aarch64_pie /data/local/tmp/su_daemon_aarch64_pie
adb shell "chmod 755 /data/local/tmp/preload.so /data/local/tmp/su_daemon_aarch64_pie"
adb shell "LD_PRELOAD=/data/local/tmp/preload.so \
/data/local/tmp/su_daemon_aarch64_pie \
> /data/local/tmp/output.log 2>&1"
При успехе демон прослушивает /data/local/tmp/temp_su.sock, а su
устанавливается в /apex/com.android.virt/bin.
exploit/
Makefile
assets/ # wallpaper.webp (embedded post-root artifact)
src/
main.c # run_exploit() entry point (shared)
preload.c # LD_PRELOAD constructor, su/wallpaper install
util.c # addr translation, page prep, configfs helpers
pipe.c # pipe-based physical read/write
root.c # cred patching / task walk
slide.c # KASLR slide (tracefs + boot_id routes)
fops.c # pselect corruption + fops manipulation
faketables.c # stage-3 fake page-table builders (3-level)
stage3.c # pgd-swap bridge child (no-op unless configured)
stage3_loop.S, stage3_poll.S # bridge blob + enter trampoline
su_daemon.c # root daemon (compiled as separate PIE)
su_blob.S, wallpaper_blob.S # embedded binaries
offset.h # pulls per-target target.h via -DTARGET_CONFIG_H
kernelsnitch/ # mm_struct futex-hash leak library
targets/<codename>-<build>/ # per-device target.h + optional overrides
poc/poc.c # standalone POC (separate from the exploit)
Цели переопределяют общие исходники через механизм pick_src в Makefile:
если src/targets/<PROJECT>/<file>.c существует, используется он вместо
общего src/<file>.c; в противном случае используется общая версия.
-EDEADLK (errno 35). Отсутствие цикла проявляется как
success=1 без эффекта в ядре.PSELECT_WAITER_WORD_SHIFT должен быть 0 (глубины стека ZF1
проверены по ELF). Для остальных целей по-прежнему используется значение по
умолчанию 1 — проверяйте для каждого устройства, прежде чем полагаться на них.p0_data_alias() использует DIRECT_MAP_BASE (линейное отображение),
которое отличается от P0_PAGE_OFFSET на ZF1. Псевдонимы данных для
разыменовываемых объектов ядра должны использовать линейное отображение.pr_error() фатальна (exit(-1)), в отличие от макросов
предупреждений/информации.SLIDE_LOGGERS_0_1, а не
SLIDE_NFULNL_LOGGER; это слово является нерандомизированным алиасом
линейного отображения, поэтому путь boot_id не зависит от slide (используйте
его только при slide=0 — сначала запустите утечку через tracefs).Это эксплойт для Android-устройств, предназначенный для исследований в области безопасности, авторизованного тестирования уязвимостей и защитных работ на оборудовании, которым вы владеете или на тестирование которого имеете явное разрешение. Использование не по назначению на устройствах, которые вам не принадлежат, незаконно. Автор не несёт ответственности за превращённые в «кирпич» устройства, повреждённые ядра или аннулированные гарантии.
preload.c устанавливает встроенный демон su (смонтированный из
tmpfs в /apex/com.android.virt/bin, плюс варианты в пространстве имён adbd
и локальный) и заменяет обои.| Variable | Default | Purpose |
|---|
PSELECT_ROUTE_SHIFT | compile-time | Сдвиг слова fd_set A/B для основного пути (m1q) |
SLIDE_SHIFT | compile-time | Сдвиг слова fd_set A/B для пути slide |
PSELECT_ROUTE_DELAY_USEC | 50000 | Задержка потребителя перед sched_setattr (должна быть > 0) |
SLIDE_CONSUME_USEC, SLIDE_ENTER_DELAY_USEC, SLIDE_CONSUMER_CORE | — | Тайминги/закрепление пути slide |
SKIP_SLIDE | 0 | Использовать запасной вариант direct-map (slide = 0) |
SLIDE_ONLY | 0 | Выполнить только slide KASLR и выйти |
SLIDE_P0_OFFSET, SLIDE_BOOTID_OFF | — | Переопределить смещения p0-alias / boot_id для slide |
KSNITCH_COLLISIONS | 4 | Количество коллизий KernelSnitch |
STAGE3 | 0 | Включить фазу дочернего процесса-моста pgd-swap (m1q) |
STAGE3_DRYRUN | 0 | Подготовить/проверить таблицы и прерваться перед заменой |
PAGE_RECLAIM_SENDS, PSELECT_SIMPLE_LAYOUT | — | Настройки раскладки spray/fd-set |
TMP_PAGE_* / TMP_UNAME_* | — | Экспериментальный путь tmp_page (m1q) |