
Локальный эксплойт повышения привилегий, нацеленный на CVE-2026-43499, для устройства Xiaomi 17T Pro (warhol) под управлением Android 16 с SoC MediaTek MT6993.
CVE-2026-43499 (IonStack) локальное повышение привилегий, портированный на Xiaomi 17T Pro
(warhol) — MediaTek MT6993, Android 16.
Только GKI 6.12 / android16. Оверлей ожидания (waiter overlay), раскладка стека
pselectи все смещения структур привязаны к этой ветке, поэтому ничего из этого не переносится на 6.6, 6.1 или 5.10 — для них нужна база, собранная отдельно.generate_target.pyотказывается от любого другого баннера, а не генерирует заголовок, который вызвал бы отказ на устройстве.
Статус: работает. Проверено на устройстве 2026-07-26 с чистой загрузки, в обоих вариантах прелоадера —
uid=0(root) context=u:r:kernel:s0, SELinux permissive,suустановлен. Root не постоянный: повторно выполняйте строкуLD_PRELOADпосле каждой загрузки. Гонка pselect не даёт 100% результата: неудачный запуск может вызвать панику телефона, а повторная попытка после перезагрузки — это нормально. Полный журнал см. вWARHOL_PORT.md.
Этому устройству требуется исправление kernel-MTE. Стоковый апстрим-попсикл не может получить на нём root — он вечно крутится в
kernel page retry N/12. См. Kernel MTE.
Сборки выпускаются в двух вариантах прелоадера: PRELOADER=retail (по умолчанию) и PRELOADER=eng.
Они выбирают разные целевые каталоги, чтобы ни один не мог перезаписать адреса другого —
см. Вариант прелоадера.
Приведённые ниже факты считаны из розничного полного OTA-пакета — метаданных, манифеста
A/B-нагрузки и образов boot/vendor_boot/dtbo, извлечённых из payload.bin.
Здесь ядро важнее SoC: warhol находится в той же ветке GKI, что и апстрим-попсикл
(android16-5, страницы 4K), на расстоянии в один patchlevel — 6.12.38 против
проверенного 6.12.23 у попсикла. Смещения структур всё равно должны перегенерироваться для
каждой сборки; переносится только форма эксплойта. Для другой сборки OS3.0.x нужен свой target.h.
Цепочка эксплойта жёстко привязана к ветке GKI, а не к SoC. Раскладка ожидания rt_mutex,
оверлей стека pselect и смещения структуры pipe_buffer отслеживают ядро, поэтому база
6.12/android16 лучше, чем база того же вендора на более старой ветке.
x-spy/CVE-2026-43499-popsicle — единственная публичная реализация, проверенная на линии
GKI 6.12/android16 (Xiaomi 17 / Pro / Ultra, ядро 6.12.23-android16-5), поэтому
source/ взят из неё и максимально приближен к апстриму — за исключением двух строк
(см. Kernel MTE, который этому устройству нужен вообще для работы).
Бо́льшая часть отличий MediaTek сосредоточена в генерации целевых данных на этапе сборки, в двух частях:
p0_phys_offset и p0_kernel_phys_load из раздела Qualcomm xbl_config,
которого у warhol нет. Вместо этого generate_target.py --dtb читает базовый адрес DRAM
из узла /memory FDT из vendor_boot, округляя его вниз так же, как это делает
arm64_memblock_init. Физический адрес загрузки ядра считывается из резерва mb_kernel
прелоадера через --preloader; для этого устройства дельта получается 0, и lk
отказывается загружать ядро, размещённое где-либо ещё.boot.img, сжатое в LZ4-legacy, а не в виде голого arm64
Image, поэтому генератор распаковывает его перед анализом.В WARHOL_PORT.md §2 приведён полный вывод.
MediaTek lk не загружает ядро по константе времени компиляции — он ищет резерв DRAM с именем
mb_kernel и проверяет, что ядро приземлилось точно на него. Этот резерв создаёт прелоадер,
именно поэтому сборка прелоадера, установленная на телефоне, здесь вообще является входным
параметром сборки: это единственное, что вне boot.img может сдвинуть
P0_KERNEL_PHYS_LOAD, а неверное значение означает неверный псевдоним линейной карты и
мёртвый телефон, а не неудачный эксплойт.
Отсюда два варианта, хранящиеся в отдельных целевых каталогах:
make preload # PRELOADER=retail -> out/preload-<device>.so
make PRELOADER=eng preload # -> out/preload-<device>-eng.so
make both # оба примера выше, а также вывод их sha256
Оба артефакта хранятся рядом под разными именами. На этой прошивке они получаются побайтно идентичными — это результат приведённого ниже анализа, а не обходной путь, поэтому обе сборки всё равно выполняются и именуются раздельно.
Прочитайте таблицы раскладки памяти обоих прелоадеров самостоятельно:
python3 tools/preloader_memlayout.py <preloader.bin> --diff <preloader_eng.bin>
Для этой прошивки обе таблицы идентичны, mb_kernel.start = 0x80000000 в обеих,
поэтому eng-вариант target.h получается побайтно идентичным retail-варианту, и обе сборки
дают один и тот же preload.so.
До эксплойта доходит разность
P0_KERNEL_PHYS_LOAD − P0_PHYS_OFFSET, и два её слагаемых опираются на неодинаково
надёжные данные:
P0_KERNEL_PHYS_LOAD — измерено по eng-образу, как указанный выше mb_kernel.start.P0_PHYS_OFFSET — обосновано, а не измерено, поскольку это memstart_addr,
который ядро берёт из узла /memory, который lk формирует во время выполнения из данных,
сообщаемых прелоадером после инициализации DRAM, а не из статичного FDT, который читает
генератор. Аргументация была такой: обе сборки разделяют весь путь DRAM — те же исходники
dramc/emi/mblock/memory_layout, и среди eng-only строк нет кода раскладки памяти —
а единственный дополнительный резерв eng-прелоадера, динамический security_fe_rsv объёмом
3 МиБ с mapping=1, не может ни сдвинуть запись с фиксированным адресом, ни вырезать дыру
внизу DRAM. Запуск на устройстве решил вопрос: псевдонимы линейной карты приземлились
под eng, значит, слагаемое верно.Полный вывод в targets/warhol-OS3.0.304.0.WPSJPXM-eng/NOTES.md.
Заметки с запуска на устройстве:
ro.boot.verifiedbootstate=green и неизменным flash.locked=1. Это оставалось открытым
вопросом, пока не было проверено на практике; решает его efuse-хэш корневого ключа, а эта
сборка подписана теми же ключами, что и retail.%s META DIS — строка
только retail, тогда как eng несёт целую реализацию META, которой у retail нет
(Enable fastmeta., FAST META GPIO: %d, META_COM PORT: %d, read_meta_proinfo, …).
Здесь он загрузился сразу в Android, но если телефон с eng-прошивкой когда-нибудь окажется
в другом состоянии, вероятная причина именно в этом, и ни одно значение target.h здесь ни при чём.mb_kernel, две цели разойдутся — хранение их в
отдельных каталогах как раз не даёт этому произойти молча.Это ядро работает с KASAN_HW_TAGS — в /proc/cmdline есть kasan.stack_ring_size=524288
— поэтому указатели slab несут тег выделения в битах 59:56. Апстрим-попсикл предполагает
указатели без тегов и потому вообще не может получить root на этом устройстве: он
бесконечно крутится в [-] kernel page retry N/12 mode=1, и в логе нет ни слова о причине.
Две строки в source/ исправляют это, и обе находятся в warhol-mte-fix.patch:
Снимают тег только проверки. Сами указатели остаются тегированными, потому что тег корректен для памяти, на которую они указывают, — а именно это и нужно при разыменовании их ядром.
Не полагайтесь на arm64.memtag.bootctl: это свойство управляет MTE в userspace и ничего
не говорит о том, тегирует ли ядро собственные выделения.
Этот репозиторий — порт. Авторство цепочки эксплойта принадлежит апстриму.
source/ ядро эксплойта (апстрим-попсикл + исправление kernel-MTE; см. warhol-mte-fix.patch)
src/ main.c slide.c fops.c pipe.c util.c preload.c su_daemon.c + kernelsnitch/
targets/ сгенерированный target.h для каждой сборки, по одному каталогу на fingerprint x вариант прелоадера
warhol-OS3.0.304.0.WPSJPXM/ PRELOADER=retail
warhol-OS3.0.304.0.WPSJPXM-eng/ PRELOADER=eng
tools/
payload_dump.py извлечение разделов из A/B payload.bin (проверяет размер и sha256)
kernel_banner.py чтение баннера Linux из boot.img
bootinfo.py вывод полей заголовков boot / vendor_boot
generate_target.py генератор target.h; апстримный, только для MediaTek, плюс распаковка ядра
preloader_memlayout.py вывод/сравнение таблицы статических резервов DRAM прелоадера MediaTek
lz4legacy.py декомпрессор кадров LZ4 legacy (образы ядра)
out/ артефакты сборки (в gitignore)
Ничего не собирается, пока не существует target.h — Makefile громко падает, а не выдаёт
бинарник под неверное ядро.
# 1. извлеките boot.img прямо из OTA-зипа (payload.bin хранится без сжатия,
# поэтому --base ищет внутри зипа; распаковывать 7.6 ГБ заранее не нужно)
python3 tools/payload_dump.py <ota.zip> --base 5081 -p boot,vendor_boot,dtbo -o <dir>
# 2. подтвердите баннер ядра — на него завязано каждое смещение ниже
python3 tools/kernel_banner.py <dir>/boot.img
python3 tools/bootinfo.py <dir>/boot.img
# 3. сгенерируйте заголовок цели. --preloader читает физический адрес загрузки ядра
# из резерва mb_kernel прелоадера; передавайте прелоадер, который запущен на телефоне.
python3 tools/generate_target.py \
--boot <dir>/boot.img --dtb <dir>/vendor_boot.img --preloader <preloader.bin> \
-o targets/warhol-OS3.0.304.0.WPSJPXM/target.h
# 4. сборка
make preload # DEVICE=warhol-OS3.0.304.0.WPSJPXM, PRELOADER=retail по умолчанию
Если образа прелоадера под рукой нет, --kernel-phys-delta 0 — более старая форма и для этой
прошивки даёт тот же заголовок, — но это утверждение, а не считывание, поэтому предпочтителен
--preloader. Передача обоих заставляет генератор выполнить перекрёстную проверку и упасть при
расхождении.
--base 5081 — это смещение локального заголовка payload.bin для этого конкретного OTA;
для любого другого пакета возьмите его из ota-property-files в META-INF/com/android/metadata.
Требуются Android NDK (NDK_ROOT / ANDROID_NDK_HOME) и llvm-objdump.
# <device> — имя цели: <fingerprint> для PRELOADER=retail, <fingerprint>-eng для eng
adb push out/preload-<device>.so /data/local/tmp/preload.so
adb shell chmod 0644 /data/local/tmp/preload.so
adb shell "LD_PRELOAD=/data/local/tmp/preload.so /system/bin/true"
adb shell "/data/local/tmp/su -c id"
cred и состояние SELinux пропатчиваются в памяти, поэтому это нужно повторять после каждой
перезагрузки. Разблокировка загрузчика не требуется.
Для исследований на оборудовании, которым вы владеете. Запуск может привести к циклу загрузки или «кирпичу» и аннулирует гарантию. Никаких гарантий любого рода.
Апстрим-проекты лицензированы их авторами; этот порт несёт их условия.
| Элемент | Значение | Источник |
|---|
| Кодовое имя | warhol | pre-device=warhol |
| Сборка | Xiaomi/warhol_global/warhol:16/BP2A.250605.031.A3/OS3.0.304.0.WPSJPXM:user/release-keys | post-build |
| Инкрементальная | OS3.0.304.0.WPSJPXM (глобальная JP) | post-build-incremental |
| Android | 16, SDK 36 | post-sdk-level=36 |
| Патч безопасности | 2026-05-01 | post-security-patch-level |
| Тип OTA | A/B (payload.bin, CrAU, 39 разделов) | ota-type=AB |
| SoC | MediaTek MT6993 (Dimensity 9500) | совместимые mediatek,mt6993-* в DTB из vendor_boot; cmdline bootopt=64S3,32N2,64N2 |
| Ядро | 6.12.38-android16-5-g1d46253471dd-ab15048002-4k, clang 19.0.1 | баннер, считанный из извлечённого boot.img |
| boot-образ | заголовок v4, ядро 18 898 125 Б, сжатие LZ4-legacy, без ramdisk | tools/bootinfo.py |
PRELOADER= | Прелоадер на телефоне | Целевой каталог | Статус |
|---|
retail (по умолчанию) | штатный preloader_<device>.bin, побайтно идентичный preloader_raw.img из fastboot-прошивки | targets/warhol-OS3.0.304.0.WPSJPXM/ | ✅ проверено на устройстве 2026-07-26 |
eng | инженерная сборка, preloader_<device>_eng.bin | targets/warhol-OS3.0.304.0.WPSJPXM-eng/ | ✅ проверено на устройстве 2026-07-26 |
| Файл | Изменение | Почему |
|---|
src/util.c | kernelsnitch_setup(..., mte_enabled=1) | при 0 поиск mm_struct пробует только кандидатов без тегов и никогда не достигает тегированного указателя — это структурный промах, а не невезение |
src/util.c | is_kernel_ptr() / is_direct_ptr() снимают тег перед проверкой диапазона | иначе тегированный указатель, считанный из памяти ядра, отклоняется как не находящийся в линейной карте (direct-entry-fatal reason=bad-task-or-cpu) |
src/kernelsnitch/kernelsnitch.h | проход по тегам < 15 → < 16 | тег 0xf и есть указатель без тега / match-all, поэтому проход теперь покрывает и ядро без MTE — mte_enabled=1 безопасен в любом случае |
| Репозиторий | Роль здесь |
|---|
| https://github.com/MobiusM/CVE-2026-43499 | Оригинальный PoC / триггер краха CVE-2026-43499 |
| https://github.com/x-spy/CVE-2026-43499-popsicle | Основа этого порта. Xiaomi 17 Pro Max (popsicle), ядро 6.12.23-android16-5; source/ и tools/generate_target.py взяты отсюда |
| https://github.com/Kananosa/CVE-2026-43499-For-Xiaomi-17T-chagall | Ближайшее родственное устройство (Xiaomi 17T, chagall, тоже MediaTek). Происходит от попсикла, поставляет готовый preload.so — его вкомпилированные константы подтвердили физическое смещение загрузки |
| https://github.com/MiCode/Xiaomi_Kernel_OpenSource | Исходники ядра Xiaomi для перекрёстной проверки раскладки структур |