
Адаптер эксплойта CVE-2026-43499 для MT6985 MediaTek Dimensity 9300 (vivo PD2241)
Вывод: потрачено 68M токенов, root не получен.
Причина: тайминговая атака KernelSnitch ненадёжна на MTK Dimensity 9200,CONFIG_PANIC_ON_OOPS=yне оставляет пространства для проб и ошибок.
В этой статье задокументирован весь процесс наступания на грабли, чтобы будущие исследователи могли их избежать.
| Проект | Значение |
|---|
| Устройство | vivo PD2241 (Dimensity 9200 / MT6985), Android 15 |
| Прошивка | PD2241_A_15.2.10.2.W10.V000L1 |
| Ядро | 5.15.178-android13-8-gfb31f5bdd612-dirty |
| Bootloader | Заблокирован (ro.boot.flash.locked=1) |
| SELinux | Enforcing |
| panic_on_oops | Включён → любой OOPS ядра = мгновенная перезагрузка |
| Эксплойт | CyberMeowfia — CVE-2026-43499 (IonStack) |
| Дерево исходников | android_15.0_kernel_MT6985 (5.15.178) — не совпадает с версией устройства (исходники — android15 GKI, устройство работает на android13 GKI) |
Извлечено из arch/arm64/include/asm/memory.h + include/linux/fs.h + android/abi_gki_aarch64.xml:
KIMAGE_TEXT_BASE = 0xffffffc008000000, VA_BITS=39, DIRECT_MAP=256GBlayout-offset-in-bits)iopoll (android13 GKI), IOCTL=0x48, open=0x68atomic_t usage = 4 байта, uid=0x04slab_cache=0x18Ключевое открытие: исходники — android15 GKI, устройство — android13 GKI — смещения task_struct отличаются на 0x40~0x88 байт, нельзя слепо копировать из исходников.
OTA zip (8.3GB)
→ payload.bin (8.2GB)
→ payload_dumper → boot.img (96MB, v4 header)
→ LZ4 распаковка → Image (50MB ARM64)
→ kallsyms-finder → 187810 символов
Символы извлечены из двух версий прошивки (15.2.7.6 / 15.2.10.2) — один и тот же символ отличается между версиями на 10KB~200KB, обязательно использовать правильную версию.
# В rt_mutex_adjust_pi:
LDR x21, [x19, #0x8b0] → pi_blocked_on = 0x8b0 (значение android15, не frankel)
Это подтвердило, что раскладка task_struct относится к ветке android15, а не к android13 от frankel.
NDK r29, make PROJECT=android_15.0_kernel_MT6985 → preload.so (150KB).
[+] preload starting pid=25414
[+] p0 profile ... все символы загружены корректно
[-] KernelSnitch mm_struct leak failed ← иногда этой строки нет (изредка успех)
[+] slide child context route=pselect ← запуск дочернего процесса для утечки KASLR slide
[panic ядра] ← rt_mutex_adjust_prio_chain+0x1b0
Инструкция краша (capstone):
ldar w8, [x27] ; x27 = waiter->lock (загружено из [x28, #0x38])
; значение x27 — мусор → нет отображения в таблице страниц → translation fault
; → die() → panic → перезагрузка
KernelSnitch — точка входа всего эксплойта — он утекает адрес mm_struct через тайминговые различия в futex-хеш-таблицах:
MT6985 имеет CONFIG_KASAN_HW_TAGS=y → ядро помечает slab-аллокации тегами MTE
указатель mm_struct несёт KASAN-тег → futex_hash вычисляется на основе tagged pointer
но bruteforce сканирует direct map (untagged адреса) → вычисленный hash не совпадает
даже с перебором MTE-тегов (0-14, всего 15 вариантов), на системе с VA_BITS=39
биты тега (bit56-59) перекрываются с битами знакового расширения → некоторые комбинации
тегов дают невалидные адреса → пропуск
На устройствах Pixel нет KASAN_HW_TAGS, этот механизм работает. На MTK — нет.
CONFIG_PANIC_ON_OOPS — убийцаPixel: OOPS ядра → dump_stack → продолжение работы → эксплойт может повторять попытки
MT6985: OOPS ядра → die() → panic() → мгновенная перезагрузка → нет пространства для проб
малейшее отклонение в π-цепочке = полный крах; на Pixel отклонение — просто "не вышло, попробуем другой набор адресов".
Плюс bootloader заблокирован (flash.locked=1) → нельзя прошить кастомное ядро, чтобы убрать эту опцию.
Дерево исходников: 5.15.178 android15 GKI
Устройство: 5.15.178-android13 (vivo vendor)
Хотя мажорные номера версий совпадают (5.15.178), ветки GKI разные (android13 vs android15), раскладка ключевых структур (task_struct/cred и др.) не совпадает. Многократное переключение между наборами смещений frankel/android15, в итоге зафиксировано только через дизассемблирование.
| Изменение | Цель | Результат |
|---|---|---|
THRESHOLD_MULT 10→5→3 | Снизить порог обнаружения коллизий | <5 слишком много ложных срабатываний |
APPENDED_FUTEXES 4096→8192 | Увеличить различия в hash-цепочках | Без эффекта |
REPEAT_MEASUREMENT/AVERAGE | Повысить точность выборки | Без эффекта |
MTE=1 | Перебор тегов в bruteforce | Медленнее, крашей меньше |
MM_STRUCT_SZ 0x500→0x400 | Исправить шаг mm_struct | Необходимо, в ABI фактически 992 байта |
IDENTITY_END 64GB→256GB | Расширить область сканирования | Слишком медленно (перебор MTE), всё равно не совпадает |
| TASK offsets: android15↔frankel | Зафиксировать правильные смещения | Дизассемблирование подтвердило android15 |
| FOPS offsets: android15↔frankel | android13 не имеет iopoll | Использован frankel |
В exploit/targets/android_15.0_kernel_MT6985/target.h:
| Категория | Достоверность | Способ проверки |
|---|---|---|
| Раскладка памяти | Верно | Вычисления из memory.h + проверка kallsyms _text |
| Смещения символов (22 шт.) | Верно | Извлечены из boot.img 15.2.10.2 |
| Смещения task_struct | Верно | ABI XML + дизассемблирование capstone (pi_blocked_on=0x8b0) |
| Смещения FOPS | Ошибочно (исправлено 2026-07-31) | Исходные значения скопированы из frankel (android13 без iopoll); в ABI дерева исходников фактически есть iopoll@0x30, ioctl=0x50, open=0x70 — см. VERIFICATION.md |
| Смещения CRED | Верно (проверено) | ABI XML: uid=0x04, securebits=0x24, caps=0x28, security=0x78 |
Ассемблер собирается, код запускается, проигрыш на последнем километре.
CONFIG_PANIC_ON_OOPS — либо прошить кастомное ядро (нужна разблокировка bootloader), либо найти устройство MT6985, где эта опция отключена по умолчанию/proc/self/pagemap — на этом устройстве ограничен (возвращает нули)/proc/mtk_*) — существуют, но требуют дальнейшего анализаsymbols/kallsyms_PD2241_15.2.10.2.txt — полная таблица символов 15.2.10.2, можно использовать напрямуюdevice_config.txt — фактическая конфигурация ядра устройства, видно, что изменил vendorexploit/targets/android_15.0_kernel_MT6985/target.h — смещения структур провереныscripts/server_compile.py — автоматизированная компиляция, после изменения параметров пересборка быстраяtar -xf может не справиться с большими zip, используйте Python zipfile или распаковывайте вручнуюupdate_metadata_pb2.py требует protobuf 5.x, нужно вручную удалить строку импорта runtime_version./preload.so напрямую вызывает segfault: обязательно использовать /system/bin/linker64 /data/local/tmp/preload.solayout-offset-in-bits вычисляется компилятором, в 100 раз точнее ручного подсчёта 5000 байтCONFIG_ANDROID_VENDOR_OEM_DATA=y, CONFIG_SCHED_INFO=y, CONFIG_RSC_* → отклонение от стандартного GKIboot.img → kernel.bin → LZ4 распаковка → Image → kallsyms-finder → таблица символов07/28 Скачивание репозитория CyberMeowfia + исходников MT6985
07/29 Анализ исходников (memory.h, fs.h, ABI XML, различные struct)
Распаковка прошивки (payload.bin → boot.img → Image)
Извлечение символов (kallsyms-finder → 187810 символов)
Многократная компиляция + многократные краши + проверка дизассемблированием
5 автоматических повторов → все провалились
Написание этой статьи
-------------------------------------------
Итого: ~68M токенов, 0 root-шеллов
2026-07-29, выжил, чтобы рассказать эту историю
После получения android_15.0_kernel_MT6985.tar.gz (дерево исходников 5.15.178 + ABI XML) проведена
полная перекрёстная проверка, подробности в VERIFICATION.md. Кратко:
target.h исправлен, плюс используется встроенная в эксплойт
самопроверка leak_kernel_base() на реальном устройстве как страховка.KSNITCH_MTE_ENABLED=1 теперь реально
работает (в исходном util.c mte=0 было захардкожено);rt_mutex_adjust_prio_chain+0x1b0 находится на этапе pselect/pi-цепочки, раньше
самопроверки FOPS; после указанных исправлений стоит повторить проверку на устройстве.Устройство (PD2241, compiler251203103903), 8+ раундов реального тестирования:
MM_STRUCT_SZ=0x400 (исходные 0x500 приводили к смещению
сетки сканирования и только ложным срабатываниям) + перебор MTE-тегов 0..15 (тег указателя mm на
устройстве меняется) + untag поддельных адресов. Теперь реальный mm_struct находится стабильно.rt_mutex_adjust_prio_chain+0x1b0: тайминговая гонка
pselect/pi-цепочки (поддельный waiter не попадает в правильное смещение в стеке ядра). Планировщик
vivo RSC изменил путь futex/pi, что, возможно, в корне ломает эту гонку. Выравнивание slide-стека
вынесено в переменную окружения SLIDE_SHIFT, полное сканирование не завершено.Итоговое решение: откат к платной разблокировке bootloader (больше не полагаемся на этот путь).