
Локальный proof-of-concept повышения привилегий для CVE-2023-20938, use-after-free в Android binder, позволяющий получить root и отключить SELinux на уязвимых тестовых сборках.
Доказательство концепции локального повышения привилегий для CVE-2023-20938, use-after-free в драйвере binder Android. Из непривилегированной оболочки получает uid 0 и переводит SELinux в permissive, затем запускает root-оболочку.
Целевая сборка: Android Cuttlefish / Goldfish AVD, x86_64, ядро 5.10.107.
⚠️ Только для авторизованных исследований / образовательных целей. Запускайте это только на своих собственных тестовых устройствах и эмуляторах. Ошибка исправлена в текущей версии Android; эта PoC нацелена на старую, намеренно уязвимую тестовую сборку. Без гарантий.

Записано с помощью asciinema и преобразовано в GIF с помощью
agg:
drivers/android/binder.c, binder_transaction_buffer_release()binder_transaction_buffer_release() доверяет переданному атакующим
offsets_size. Отправка транзакции, у которой offsets_size на один байт короче
полной записи (sizeof(offset) - 1 == 7), заставляет путь освобождения неправильно
разобрать завершающий flat_binder_object и избыточно уменьшить счётчик ссылок
binder_node, освобождая его, пока ещё существует висячая ссылка.
Эксплойт объединяет кросскэш-утечку в произвольное чтение и контролируемую запись,
затем перезаписывает учётные данные. Поэтапно (см. main() в poc_cf.c):
B. Cross-cache leak
free a wave of binder_nodes (kmalloc-128) -> page returns to the buddy
allocator -> reclaim the page with eventpoll epitems (eventpoll_epi cache,
0x80-byte objects). A dangling binder transaction now reads kernel
pointers out of an epitem, leaking a struct file and its epitem address.
C. Arbitrary read
re-trigger the UAF and use a sprayed fake binder_node to turn binder's
hlist_del unlink into a controlled *(where) = what write. Two writes
redirect a victim file's f_inode at a fake inode overlaid on the epitem
and mark it S_PRIVATE. FIGETBSZ on the file then returns
*(epitem.event.data + 24): a steerable 4-byte read.
D/E/F. Privilege escalation
defeat KASLR (leak eventfd_fops via f_op), walk init_task's task list to
the launcher's cred, zero its uid/gid set and seccomp.mode, then zero
selinux_state to drop SELinux to permissive.
G. A pre-forked, eventfd-free launcher execs a root shell.
Двум непривилегированным процессам необходимо разделять узел binder, чтобы активировать UAF;
через ITokenManager (createToken /get-by-token) hwservicemanager.
clang -static -Wall -Wextra -o poc_cf poc_cf.c -lpthread
binder.h должен находиться рядом с poc_cf.c. Сборка намеренно статическая, чтобы
один двоичный файл запускался на целевой системе без наличия цепочки инструментов.
Из каталога пакета хоста Cuttlefish запустите AVD с соответствующим ядром + initramfs:
HOME=$PWD ./bin/launch_cvd \
--kernel_path=bzImage \
--initramfs_path=initramfs.img
adb push poc_cf /data/local/tmp/
adb shell /data/local/tmp/poc_cf
Успешный запуск проходит все этапы и завершается root-оболочкой:
Эксплойт вероятностный. Каждый этап выполняет внутренние повторные попытки.
Поэтому, видите ли вы чистый выход или панику гостя, просто перезапустите (раздел /data
на AVD очищается при перезагрузке, поэтому после паники повторно отправьте двоичный файл).
Если частота захвата низкая на более медленном или загруженном целевом устройстве, увеличьте
задержки или повысьте количество разбрасывания (#define в начале poc_cf.c):
Смещения структур и нерандомизированная база символов (*_NOKASLR) специфичны
именно для этой сборки 5.10.107 и не будут работать в других местах. Чтобы перенести
на другое ядро, обновите блок смещений и блок символов в начале poc_cf.c
значениями из соответствующего vmlinux. Я пробовал на Pixel 7 после извлечения смещений.
Блог Google Android Offensive Security [https://androidoffsec.withgoogle.com/posts/attacking-android-binder-analysis-and-exploitation-of-cve-2023-20938/]
| Макрос | По умолчанию | Значение |
|---|
SETTLE_US | 5000 | пауза после освобождения узла, перед разбрасыпванием |
FAKE_SPRAY_COUNT | 8 | заблокированные sendmsg(), соревнующиеся за захват слота |
FAKE_WAIT_US | 10000 | пауза после освобождения разбрасывания, перед отвязкой |
PRESKIP_US | 1000 | пауза после первого жертвенного sendmsg |
KW_RETRY_COUNT | 8 | попыток на одну запись отвязки |
STAGE_C_TRIES | 8 | попыток включить произвольное чтение |