
Disparando e Analisando a Vulnerabilidade do Kernel Android CVE-2019-2215
Em novembro de 2017, um bug de use-after-free no kernel do Linux foi detectado pelo sistema syzkaller. Em fevereiro de 2018, isso foi corrigido em alguns kernels Linux e versões do Android.
Essa correção nunca foi incluída nos boletins de segurança mensais do Android, portanto não foi corrigida em muitos dispositivos recém-lançados, como Pixel e Pixel 2.
Em setembro de 2019, o Android foi informado das implicações de segurança desse bug pelo Project Zero. Em seguida, o Android atribuiu o CVE-2019-2215 a essa vulnerabilidade para torná-la mais formal e conhecida.
CVE-2019-2215 é um use-after-free em binder.c que permite a elevação de privilégios (obter acesso root) a partir de um aplicativo Android. Não é necessária a interação do usuário para explorar essa vulnerabilidade. Ela requer apenas a instalação de um aplicativo local malicioso.
Aqui vamos apresentar essa vulnerabilidade do kernel Android em mais detalhes e usar essa vulnerabilidade para obter acesso root (escalonamento de privilégios) em todo o dispositivo Android.
Usaremos esta Prova de Conceito (PoC):
https://github.com/cloudfuzz/android-kernel-exploitation
Primeiro, mostraremos uma maneira de acionar essa vulnerabilidade em um emulador Android e causar uma falha (crash) no kernel. Depois, para ver como isso seria perigoso, continuamos usando a PoC para obter acesso root no dispositivo Android simulado. Em seguida, analisaremos o código do kernel para ver qual é o motivo (análise estática e análise dinâmica).
Após a análise, veremos como conseguimos o acesso root. Por fim, veremos como essa vulnerabilidade é mitigada usando patches.
Para causar uma falha no kernel acionando essa vulnerabilidade, usamos os seguintes passos:
Assista ao seguinte vídeo:
[video]
Nesta (e na próxima) seção, vamos entender por que a falha acontece usando análises estática e dinâmica.
Aqui analisaremos o código do kernel (análise estática) para entender o problema. Em crash_report.txt está o relatório do KASan que diz que se trata de um bug de use-after-free. Isso significa que um objeto é alocado no heap (e temos uma referência a ele), depois liberamos o objeto do heap e então o chamamos erroneamente por uma referência. Nesse relatório, o stack trace dessas três etapas é impresso.
Se você se lembra do que foi dito acima, usamos o 'trigger.cpp' da PoC.
Aqui está o código principal do 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); }
Vamos ver o que 'trigger.cpp' está fazendo.
No Android (como em outros sistemas operacionais baseados em Unix) temos alguns processos. Cada programa que executamos cria um (ou mais) processos e esses processos são gerenciados pelo SO. O SO pode alternar entre eles (multitarefa) ou encerrar um processo, etc. Por razões de segurança, os processos são isolados uns dos outros por padrão.
Em alguns casos, um processo pode precisar trocar dados com outro processo. Isso é chamado de Comunicação entre Processos (IPC). Existem várias maneiras de os processos se comunicarem no linux. O Android introduziu um mecanismo de IPC específico chamado **'Binder'**. O Binder é um driver de kernel para facilitar a comunicação entre processos.
No android, o IPC pode ser feito chamando diretamente alguns métodos do kernel (a maioria deles está em drivers/binder.c) ou usando implementações de alto nível (por exemplo, em java).

Para usar o **binder**, devemos abrir o módulo binder do kernel. Isso é feito usando a linha 3 do trigger.cpp. Então temos um ponteiro de descritor de arquivo. Usando este fd, o iniciador do kernel e os destinatários do IPC podem ser identificados.
Todas as interações com o driver acontecerão por meio de um pequeno conjunto de comandos **'ioctl**' (BINDER_THREAD_EXIT, BINDER_WRITE_READ, ...).
Mais sobre binder: [link1](https://www.nds.ruhr-uni-bochum.de/media/attachments/files/2012/03/binder.pdf), [link2](http://rts.lab.asu.edu/web_438/project_final/CSE_598_Android_Architecture_Binder.pdf)
No linux, temos um conceito chamado '**event polling**'. A API '**epoll**' é usada quando queremos monitorar múltiplos descritores de arquivo (descritores de arquivo são o que temos ao abrir um driver ou trabalhar com E/S, etc).
**epoll** é uma struct do kernel que tem dois campos importantes.
* interest list = lista de descritores de arquivo que queremos monitorar.
* ready list = lista de descritores de arquivo que estão prontos para E/S.
Para usar o event polling, primeiro criamos um epoll (linha 4), depois adicionamos ou removemos (EPOLL_CTL_ADD) um evento (&event) associado a um descritor de arquivo (**fd**) ao nosso epoll criado (**epfd**) chamando o método **epoll_ctl** do kernel.
=> `epoll_event event` é um evento, disparado quando o arquivo associado (fd) está disponível para operação de leitura.
Agora entendemos o que **trigger.cpp** está fazendo (sem necessidade de aprofundar!). Ele abre o módulo **binder**, cria um **epoll** para ouvi-lo quando estiver pronto. Então, na linha 6, saímos do binder que iniciamos na linha 3.
#### Alocar:
Ao chamar open(), na verdade chamamos **open_binder()** (implementação de open() em binder.c); em open_binder(), uma nova struct **'binder_proc'** será criada e:
` fd->pricate_data = binder_proc`
Ao chamar **epoll_create(),** uma nova struct epoll será criada e adicionada a uma estrutura de fila.

Ao chamar **epoll_ctrl(epdf, ADD, fd, event)**, um novo **ep_item** é criado, associa **fd **(descritor de arquivo a ser ouvido) a este **ep_item**, e ele é inserido na **árvore rubro-negra** do event_poll (uma estrutura de dados no ep para salvar ep_items). Ele também chama **ep_item_poll(),** este método trata a associação da função de retorno (call back) ao ep_item.
Ele cria uma nova struct **binder_thread** (**a alocação acontece aqui**), vincula-a ao **binder_proc **(criado acima), então uma struct **epoll_entry** é criada; ela tem duas listas,** epoll_entry->wait** e **epoll_entry->whead**, ambas as listas têm um ponteiro para o **binder_thread** criado antes.