
Запуск и анализ уязвимости ядра Android CVE-2019-2215
В ноябре 2017 года системой syzkaller была обнаружена use-after-free ошибка в ядре linux. В феврале 2018 года она была исправлена в некоторых ядрах linux и версиях Android.
Это исправление никогда не включалось в ежемесячные бюллетени безопасности Android, поэтому оно не было исправлено на многих новых устройствах, таких как Pixel и Pixel2.
В сентябре 2019 года компания Project Zero уведомила Android о последствиях этой ошибки для безопасности. Затем Android присвоил этой уязвимости идентификатор CVE-2019-2215, чтобы сделать её более формальной и известной.
CVE-2019-2215 — это use-after-free в файле binder.c, который позволяет повышение привилегий (получение root-доступа) из приложения Android. Для эксплуатации этой уязвимости не требуется взаимодействия с пользователем. Требуется лишь установка вредоносного локального приложения.
Здесь мы более подробно рассмотрим эту уязвимость ядра Android и используем её для получения root-доступа (повышения привилегий) ко всему устройству Android.
Мы будем использовать это Proof of Concept (PoC):
https://github.com/cloudfuzz/android-kernel-exploitation
Сначала мы покажем, как запустить эту уязвимость на эмуляторе Android и вызвать крах ядра. Затем, чтобы увидеть, насколько это может быть опасно, мы продолжим использовать PoC для получения root-доступа на эмулированном устройстве Android. После этого мы проанализируем код ядра, чтобы понять причину (статический и динамический анализ).
После анализа мы увидим, как мы получили root-доступ. В конце мы рассмотрим, как эта уязвимость смягчается с помощью патчей.
Чтобы вызвать крах ядра путём запуска этой уязвимости, выполните следующие шаги:
Посмотрите следующее видео:
[video]
В этом (и следующем) разделе мы собираемся понять, почему происходит крах, используя статический и динамический анализ.
Здесь мы проанализируем код ядра (статический анализ), чтобы понять проблему. В crash_report.txt есть отчёт от KASan, который говорит, что это ошибка use-after-free. Это означает, что объект был выделен в куче (и у нас есть ссылка на него), затем мы освободили объект из кучи, и после этого ошибочно обратились к нему по ссылке. В этом отчёте напечатан стек вызовов для этих трёх этапов.
Если вы помните из предыдущего, мы использовали 'trigger.cpp' из PoC.
Вот основной код trigger.cpp:``` int main() { //1 int fd, epfd; //2 struct epoll_event event = {.events = EPOLLIN}; //3 fd = open("/dev/binder", O_RDONLY); //4 epfd = epoll_create(1000); //5 epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event); //6 ioctl(fd, BINDER_THREAD_EXIT, NULL); }
Давайте посмотрим, что делает `trigger.cpp`.
В Android (как и в других Unix-подобных операционных системах) существуют процессы. Каждая запущенная программа создаёт один (или более) процесс, и эти процессы управляются ОС. ОС может переключаться между ними (многозадачность) или завершать процесс и т.д. По соображениям безопасности процессы по умолчанию изолированы друг от друга.
В некоторых случаях одному процессу может потребоваться обменяться данными с другим процессом. Это называется межпроцессным взаимодействием (IPC). В Linux существует несколько способов взаимодействия процессов. Android представил собственный механизм IPC, называемый **'Binder'**. Binder — это драйвер ядра, обеспечивающий межпроцессное взаимодействие.
В Android IPC может осуществляться прямым вызовом некоторых методов ядра (большинство из них находится в drivers/binder.c) или с помощью высокоуровневых реализаций (например, на Java).

Для использования **binder** необходимо открыть модуль ядра binder. Это делается в строке 3 файла trigger.cpp. После этого у нас есть указатель на файловый дескриптор. С помощью этого fd можно идентифицировать инициатора и получателя IPC.
Все взаимодействия с драйвером осуществляются через небольшой набор команд **'ioctl'** (BINDER_THREAD_EXIT, BINDER_WRITE_READ, ...).
Подробнее о binder: [ссылка1](https://www.nds.ruhr-uni-bochum.de/media/attachments/files/2012/03/binder.pdf), [ссылка2](http://rts.lab.asu.edu/web_438/project_final/CSE_598_Android_Architecture_Binder.pdf)
В Linux существует понятие **'event polling'**. API **'epoll'** используется, когда необходимо отслеживать несколько файловых дескрипторов (файловые дескрипторы — это то, что мы получаем при открытии драйвера или работе с вводом/выводом и т.д.).
**epoll** — это структура ядра, имеющая два важных поля:
* interest list = список файловых дескрипторов, которые мы хотим отслеживать.
* ready list = список файловых дескрипторов, готовых к вводу/выводу.
Чтобы использовать event polling, мы сначала создаём epoll (строка 4), затем добавляем или удаляем (EPOLL_CTL_ADD) событие (&event), связанное с файловым дескриптором (**fd**), в наш созданный epoll (**epfd**) с помощью вызова метода ядра **epoll_ctl**.
=> `epoll_event event` — это событие, которое срабатывает, когда связанный файл (fd) становится доступен для операции чтения.
Теперь мы понимаем, что делает **trigger.cpp** (нет необходимости углубляться!). Он открывает модуль **binder**, создаёт **epoll** для прослушивания, когда binder будет готов. Затем, в строке 6, мы выходим из binder, который запустили в строке 3.
#### Выделение:
При вызове open() фактически вызывается **open_binder()** (реализация open() в binder.c). В open_binder() создаётся новая структура **'binder_proc'** и:
` fd->pricate_data = binder_proc`
При вызове **epoll_create()** создаётся новая структура epoll и добавляется в очередь.

При вызове **epoll_ctrl(epdf, ADD, fd, event)** создаётся новый **ep_item**, который связывается с **fd** (прослушиваемым файловым дескриптором), и вставляется в **красное чёрное дерево** event_poll (структура данных в ep для хранения ep_items). Также вызывается **ep_item_poll()**, который обрабатывает привязку функции обратного вызова к ep_item.
Создаётся новая структура **binder_thread** (**здесь происходит выделение памяти**), которая связывается с **binder_proc** (созданным выше). Затем создаётся структура **epoll_entry**, содержащая два списка: **epoll_entry->wait** и **epoll_entry->whead**. Оба списка содержат указатель на **binder_thread**, созданный ранее.
Затем **epoll_entry** связывается с ep_item (**ep_item->pwqlist** — это список, содержащий данный epoll_entry).