## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-SORALIS0912-CVE-2026-43499-PMG110-ROOT
# pmg110-root
Локальное повышение привилегий через CVE-2026-43499 (use-after-free в futex PI `rt_mutex_waiter`), портированное на **OPPO PMG110 / K15 Pro+** — MediaTek MT6991, ColorOS 16.
Загружается один файл, запуск через `LD_PRELOAD`:
root@kitploit:~
adb push out/preload-pmg110-16.0.9.400.so /data/local/tmp/preload.so
adb shell chmod 644 /data/local/tmp/preload.so
adb shell LD_PRELOAD=/data/local/tmp/preload.so /system/bin/true
В случае успеха на устройстве остаётся постоянный `su`:
root@kitploit:~
adb shell /data/local/tmp/su -c id # uid=0(root)
**Проверено на устройстве** (2026-07-27): uid=0 примерно за 35 секунд при «чистом» запуске без переопределений окружения, и `su` затем отвечает из обычного непривилегированного `adb shell`:
root@kitploit:~
$ adb shell "/data/local/tmp/su -c 'echo 0 > /proc/sys/kernel/kptr_restrict'"
$ adb shell "/data/local/tmp/su -c 'grep -w init_task /proc/kallsyms'"
ffffffe89033e780 D init_task
Эксплойт и установка `su` проверены на этом устройстве. То, что затем считала рутированная оболочка, также подтверждает `P0_KERNEL_PHYS_LOAD`, смещения символов и `KS_MTE_TAGGED=0` независимо от эксплойта — см. `targets/pmg110-16.0.9.400/NOTES.md`.
## Что делает и чего не делает
Он выполняет Write 1 (SELinux permissive) и Write 2 (`cred` → `init_cred`), переводит дочерний процесс в uid=0 и оттуда устанавливает встроенный демон `su`.
* **по-прежнему загружается один файл.** `su` — не второй артефакт: `su_daemon.c` собирается как отдельный aarch64 PIE и через `.incbin` встраивается в `.rodata` библиотеки, поэтому он находится внутри `preload.so` и записывается обратно во время выполнения. Путь warhol-root, без изменений.
* нет root-скрипта, нет `ksud`, нет KernelSU
* вызывающий процесс остаётся непривилегированным — он получает root, _обращаясь к демону_ , и то же самое вы будете делать из оболочки потом
* SELinux **остаётся в режиме permissive** , как это делает warhol-root: демон должен обслуживать непривилегированных клиентов через свой сокет. Перезагрузка вернёт enforcing.
`su` устанавливается в трёх местах, потому что одно из них — то, до которого вы действительно доберётесь:
Демон слушает `/data/local/tmp/temp_su.sock` и пишет лог в `/data/local/tmp/su_daemon.log`. Root не сохраняется после перезагрузки — повторно запускайте команду с `LD_PRELOAD` после каждой загрузки.
Для установки KernelSU используйте вместо этого маршрут `/data/local/tmp/a/e` в `ghostlock-oneplus`.
## Связь с warhol-root
Всё, кроме ядра эксплойта, принадлежит warhol-root — взято, а не изобретено заново:
* **структура** — заголовки для каждого устройства в `targets/<device>/`, размещаемые в `source/src/` во время сборки, так что переключение `DEVICE` никогда не оставит позади заголовки предыдущего устройства
* **сборка** — выбор тулчейна в `source/Makefile` (NDK, если он есть, иначе хост-`clang` против sysroot NDK) и двухэтапное правило встраивания, которое создаёт `build/embed/su_daemon_aarch64_pie` перед линковкой `.so`
* **путь su** — `su_daemon.c` и `su_blob.S` побайтно идентичны warhol-root, а `su_install.c` — это его установщик из `preload.c`
**Ядро эксплойта — не warhol-root.** warhol-root — это popsicle, привязанный к GKI 6.12 / android16, и его `generate_target.py` отвергает любой другой баннер. У PMG110 — 6.6 / android15, поэтому ядро здесь — это дерево ghostlock 6.6, само являющееся потомком того же кода (`kernelsnitch/utils.h` и `timeutils.h` побайтно идентичны между двумя репозиториями) и получившее дальнейшее развитие.
Каждая строка собственно эксплойта — Write 1, Write 2, KernelSnitch, маршрут pselect — это один и тот же код в обоих деревьях.
### Откуда вызывается установка su
Это единственное структурное различие, и оно обусловлено тем, что два дерева получают root по-разному.
warhol-root рутит **сам процесс эксплойта** и потому вызывает `install_embedded_su()` напрямую из `run_direct_root()`. Здесь Write 2 подменяет указатель `cred` у **форкнутого дочернего процесса** , а родитель остаётся непривилегированным вызывающим, поэтому дочерний процесс в `child_main()` — единственный контекст, который может выполнить установку, — именно там он и запускается.
Оба дерева несут одну и ту же слабую заглушку `install_embedded_su()` в `util.c`, возвращающую `ENOSYS`; именно сильное определение включает этот маршрут. Это стоит знать, потому что сборка, в которой по какой-то причине теряется `su_install.c`, по-прежнему линкуется и запускается — просто сообщает `su=0/38` и ничего не устанавливает.
## Сборка
root@kitploit:~
make # = make preload -> out/preload-<DEVICE>.so
make DEVICE=<name> # use targets/<name>/
make devices # list available DEVICE values
make info # show the selected target and the resolved toolchain
Тулчейн находится автоматически: сначала `ANDROID_NDK_HOME` / `ANDROID_NDK_ROOT`, затем обычные места установки NDK для Linux и macOS, а если ничего из этого не нашлось — хост-`clang`, нацеленный на sysroot NDK. Устанавливайте `ANDROID_NDK_HOME` только для переопределения поиска. `make info` выводит, что было выбрано.
Сборка двухэтапная — вот что стоит знать:
1. `su_daemon.c` → `build/embed/su_daemon_aarch64_pie` — отдельный aarch64 PIE
2. `su_blob.S` через `.incbin` встраивает этот бинарник в `.rodata`, и всё это линкуется в единый `preload.so`
Поэтому `make clean` и пересборка — единственный способ изменить встроенный `su`: достаточно отредактировать один `su_daemon.c`, зависимость объявлена, но блоб — артефакт сборки и не отслеживается.
`targets/<device>/{target.h,device_offsets.h}` заново размещаются в `source/src/` при каждой сборке, так что устаревший заголовок от другого устройства не может быть молча подхвачен.
`out/*.so` не отслеживается (та же договорённость, что и в warhol-root) — клонируйте и запускайте `make`.
`.so` собирается с флагом `-fvisibility=hidden` и экспортирует **ноль** символов. Библиотека `LD_PRELOAD` выигрывает поиск символов для всего процесса, поэтому всё, что она экспортирует, может затенять одноимённый символ в хост-бинарнике или в libc. Этот флаг управляет только генерацией кода C, поэтому `su_blob.S` помечает свои два символа `.hidden` вручную — без этих строк границы блоба были бы единственным, что библиотека по-прежнему экспортировала.
## Переменные окружения
При проверенном запуске не понадобилась ни одна из них.
## Чтение лога
root@kitploit:~
[*] futex_hashsize 2048 (8 possible CPUs)
[*] ks collisions=3/3 baseline=8 threshold=10x (80) accepted=[1244..1597] slowest_rejected=N
[+] child uid = 0
[+] embedded su wrote 15304 bytes to /apex/com.android.virt/bin/su
[+] embedded su daemon ready pid=NNNN socket=/data/local/tmp/temp_su.sock daemon=/apex/com.android.virt/bin/su
[+] embedded su install ok=1 errno=0 daemon=NNNN
[+] su ready: /data/local/tmp/su and /apex/com.android.virt/bin/su
[+] ghostlock preload verdict: EXPLOIT OK
`child uid = 0` — это успех эксплойта; всё, что после него, — установка. Эти два события намеренно сообщаются раздельно, как и вердикт:
Средний вариант — то различие, которое стоит иметь в виду: он говорит, что смещения в `target.h` верны для этой сборки, а проблема где-то в установке, а это совершенно другая вещь для отладки. `su=0/38` (`ENOSYS`) внутри него конкретно означает, что слинковалась именно слабая заглушка.
**`mm_struct leak failed` с последующим `prepare_kernel_page retry N/24` — не сбой.** Это прогресс цикла, и успешный запуск тоже показывает его. Ничего не является сбоем, пока не исчерпаны 24 попытки и не появится `prepare_kernel_page timeout`. Аналогично, `probing cfi ... expected=9` с `child uid = 2000` — это один недобранный раунд из десяти.
Не судите о запуске по усечённому логу — эта ошибка стоила здесь целого раунда неверной диагностики.
**Полностью неудачный запуск — тоже норма.** Гонка `pselect` не срабатывает в 100 % случаев: запуск может проиграть её пять раз подряд и закончиться `Write 1 failed`, а следующий запуск выигрывает её с первой попытки с `ret=9`. Наблюдалось на этом устройстве. `ret=4 expected=9` — так выглядит проигрыш гонки, а не неправильный `target.h` — один сбой не повод заново выводить смещения. Перезапустите.
## Файлы
## Лицензия
Только для авторизованных исследований в области безопасности и образовательных целей.