
Эксплойт LPE ядра для CVE-2023-6931 на Xiaomi Pad 6 (pipa), использующий переполнение read_size в perf для получения root и отключающий SELinux через data-only повреждение sk_destruct в netlink_sock.
Финальная версия эксплойта, использованная на устройстве, выделена в этот каталог.
Устройство Xiaomi Pad 6 / pipa / SM8250
Система HyperOS OS2.0.20.0.UMZCNXM
Ядро 4.19.157-perf-g240e1d0a5f52
Точка входа adb shell (домен shell)
Результат одноразовый uid0 + глобальный SELinux permissive (сбрасывается при перезагрузке)
См. заголовок файла exploit.c, краткое изложение ниже.
1) perf group read_size overflow -> примитив выхода за границы с самоинкрементом u64
2) самоинкремент victim netlink_sock->sk_destruct -> netlink_sock_destruct_work (+0x28e8)
3) close(victim) -> sk_destruct(work) -> container_of(work, netlink_sock, 0x440)
-> fake = victim-0x440 -> sk_free(fake) -> __sk_destruct(&fake->sk_rcu)
-> fake->sk_destruct @ fake+704 = pivot(mov sp, x19)
4) миграция стека в полезную нагрузку skb внутри fake (pt_regs1 @ +960, pt_regs2 @ +1120)
5) restore_tramp -> eret EL1h -> commit_creds(&init_cred)
6) restore_no_kpti -> eret EL0 -> post_root
Отключение SELinux выполняется чистой перезаписью данных: fake+0x248 указывает на selinux_state, а atomic_dec в __sk_destruct уменьшает enforcing с 1 до 0 в младшем битовом сегменте этого слова.
./build.sh # вывод в exploit в этом каталоге
CC=aarch64-linux-gnu-gcc ./build.sh /tmp/exploit
Командная строка, используемая скриптом-драйвером .project/scripts/drive_selinux_off.sh:
adb push exploit_selinux8 /data/local/tmp/exploit_selinux8
adb shell 'chmod 755 /data/local/tmp/exploit_selinux8'
adb shell 'cd /data/local/tmp && setsid nohup /data/local/tmp/exploit_selinux8 \
--selinux-off --layout-interleave --tsec-sid 1 --alarm-us 5000000 \
> /data/local/tmp/run.out 2>&1 < /dev/null &'
Значение параметров:
--selinux-off отключить SELinux (путь чистой перезаписи данных), одновременно с подделкой cred
--layout-interleave чередующая раскладка последовательности выделений, чтобы OOB-самоинкремент попал в sk_destruct целевого объекта
--tsec-sid 1 подделка sid/osid контекста безопасности cred (используется 1, когда реальный sid домена недоступен)
--alarm-us 5000000 самопробуждающийся ITIMER_REAL, зависшая попытка завершается через 5 секунд
Предварительные условия и ограничения запуска:
shell имеет доступ к perf_event и netlink_route, домен приложения — нет, поэтому запускать необходимо из adb shell;perf_event_mlock_kb = 516, не запускайте повторно в рамках одной загрузки: если предыдущий экземпляр не завершился, события не освобождаются, и повторный запуск создаст лишь от 4000 до 6000 событий;kill -9 и начните заново, иначе считайте, что на одну загрузку приходится одна результативная попытка;pause(), root и permissive действуют только для текущей загрузки.--edl, --edl2) для детерминированного входа в EDL из root-окна; в этом каталоге приведена только финально использованная версия.В этом каталоге только первая стадия. Вся цепочка фактически состоит из двух сегментов, точка стыковки — канал root-команд, оставленный первой стадией.
Первая стадия exploit_selinux8 повышение привилегий в ядре + отключение SELinux, затем остановка на канале root-команд
результат: uid0 + permissive, канал = FIFO /data/local/tmp/root_in|root_out
(также абстрактные unix-сокеты rootshell / rootshell2 и режим --root-client PORT)
Вторая стадия stage2-frida/ через указанный канал запускает frida-server от root, внедряется в Settings
результат: сформированный и отправленный из процесса приложения запрос привязки (подпись TEE)
По завершении первой стадии в /data/local/tmp/root_proof.txt записывается ROOTSHELL_OK как подтверждение возможности выполнения команд. После этого любое root-действие с хост-машины — это запись одной строки команды в FIFO, например, строка запуска frida-server на второй стадии:
adb shell "echo 'killall -9 frida-server 2>/dev/null; rm -f /data/local/tmp/frida.log; \
setsid runcon u:r:shell:s0 /data/local/tmp/frida-server16 -D \
</dev/null >/data/local/tmp/frida.log 2>&1 & echo FRIDA_STARTED' > /data/local/tmp/root_in"
runcon u:r:shell:s0 опускать нельзя: после отключения SELinux, если frida-server работает в домене kernel, он не получит Android Context, и PackageManager выбросит NPE.
Внешние зависимости (не входят в этот каталог): frida-server 16.7.19 (54 МБ, находится в .project/tools/ext/frida/fs16, префикс sha256 4eebf1fbc66ff54aba9a9124) и соответствующий python-клиент (.project/tools/ext/fridavenv16, frida 16.7.19). Используется 16.x, а не 17.x, потому что в agent из 17 нет Java bridge.
Полная цепочка вызовов оркестрируется .project/scripts/drive_selinux_off.sh: первое действие после попадания в root-окно — bash .project/scripts/exp_forge_send.sh, при получении code 0 цикл останавливается.
exploit_selinux8 — это поставленный оригинал, контрольные суммы см. в SHA256SUMS. Пересборка из исходников этого каталога даёт другой бинарник:
Пересборка из исходников этого каталога 1,853,744 B f8bb900bf4e197190d56bfc1437bcc9a025e49640a7fa2a78ff0f7fb0a6e7939
Пересборка из оригинальных исходников 1,853,752 B 96c7c3637682a2d5869ab4b6a732688a252f17f5e604b1695bc8fcc022113b3f
Фактически запускавшийся бинарник 1,853,720 B 279837d2ee028fbbb520120180156446ad8dc3286b723e28de300b544be2a21a
Различия локализованы посимвольно, источник только один: в таблице аргументов командной строки появились два режима.
--edl кадр 2 вызывает __qcom_scm_set_dload_mode(dev, 1)
--edl2 кадр 2 напрямую пишет dload cookie
Иными словами, поставленный бинарник собран до добавления EDL-режима, а текущие исходники — после (из них собраны .project/work/exploit_edl1, exploit_edl2). Ветка EDL активируется только при передаче --edl или --edl2, edl_mode по умолчанию равен 0, единственная связанная строка в построении payload — edl_mode ? 1ULL : rt(OFF_INIT_CRED), поэтому при использовании финальной командной строки --selinux-off поведение обоих вариантов идентично. Различие представляет собой функциональное дополнение, а не исправление или изменение поведения; бинарник приложен как оригинал, исходники сохранены в полной версии.
Обработка выполнена с помощью .project/tools/strip_dev_comments.py: удалено 124 блока комментариев процесса разработки (маркеры итераций версий, такие как v96, v127, а также отладочные формулировки на китайском вроде «старое значение/измерено/перезапуск/пропустить»), сохранено 313 структурных комментариев. Сравнение результата предобработки отличается только пустыми строками, токены кода полностью совпадают. В журналах выполнения по-прежнему присутствуют строки вывода с префиксом vNNN:, поскольку они являются выводом программы, а не комментариями.
idk fr what files are really needed, if you want to you might very need a LLM to understand the whole exp and write yours.
| Файл | Описание |
|---|
exploit.c | Исходный код. Удалены комментарии процесса разработки, сохранены структурные комментарии: описание цепочки в заголовке файла, таблица смещений, раскладка структур |
text_syms.h | Таблица символов .text, сгенерированная из дампа kallsyms устройства, необходима для компиляции |
ro_syms.h | То же самое, таблица символов данных только для чтения, необходима для компиляции |
exploit_selinux8 | Бинарник, фактически запускавшийся на устройстве, идентичен тому, что используется в скрипте-драйвере |
build.sh | Скрипт сборки |
SHA256SUMS | Контрольные суммы перечисленных файлов |
| Файл | Описание |
|---|
exp_forge_send.sh | Отправляет и запускает frida-server 16.7.19, проверяет, что Settings работает, получает pid, запускает agent |
frida_run.py | Обёртка клиента frida, поддерживает прямое подключение через FRIDA_PID и самозапуск через FRIDA_SPAWN (в обход am/pm) |
forge_send.js | Внутри процесса Settings копирует getNonce, подписывает applyBind по старому набору полей и отправляет |
| Сравниваемый параметр | Результат |
|---|
| Символы с отличающимся размером функции | Только main (shipped 17,744 B, пересборка 18,004 B, разница 260 B ≈ 65 инструкций) |
| Остальные 1,503 функции | Размеры полностью совпадают, без изменений |
| Символы, присутствующие только в пересборке | Один — edl_mode |
| Таблица аргументов командной строки | shipped 85, пересборка 87, лишние — именно --edl и --edl2 |
Число инструкций всего сегмента .text | 126,533 и 126,597, разница 64 инструкции, что согласуется с различием в main |