
demo CVE-2019-2215 (Bad Binder) para Android Q
Este repositório é um pequeno projeto de teste para pesquisa da vulnerabilidade
CVE-2019-2215 (Bad Binder) e escrita de um protótipo funcional de exploit para Android com
interface gráfica simples em Kotlin/Jetpack Compose.
No README eu:
O repositório possui um workflow do GitHub Actions que, a cada push/PR, compila o projeto
com o comando ./gradlew assembleDebug e publica o badbinder-debug.apk como artefato.
Pode ser baixado assim:
badbinder-debug-apk
com o APK compilado.Isso foi feito por conveniência, caso queira apenas testar o aplicativo sem montar um ambiente local.
CVE-2019-2215 é um Use-After-Free (UAF) no subsistema de IPC Binder do kernel Android.
De forma simplificada:
struct binder_thread, que descreve uma thread
realizando chamadas Binder;waitqueue);remove_wait_queue,
o que abre um cenário clássico de UAF;Uma análise teórica mais detalhada fiz com base nos materiais:
Conforme recomendado na tarefa, usei um AVD com imagem Android 10.0 (Q) x86_64.
Fiz o seguinte:
/dev/binder.adb shell
ls -l /dev/binder
Nesta etapa me deparei com um fato desagradável:
atualmente, as imagens AVD atuais já vêm com kernel corrigido, no qual
o CVE-2019-2215 está fechado. Ou seja, não será possível obter root em um emulador oficial moderno
— o exploit falha em estágios posteriores ou simplesmente não concede elevação de privilégios.
No final, uso o AVD como simulador para reproduzir a lógica do exploit:
addr_limit,Essa é uma nuance importante: todo o código e relatório abaixo são educacionais, não "de combate".
Criei um pequeno aplicativo Android:
Principais passos:
Criei um projeto normal no Android Studio (Kotlin, suporte mínimo ao Android 10).
Conectei o NDK e o CMake.
Adicionei o arquivo nativo com o exploit (o mesmo cve-2019-2215.c com funções
leak_task_struct, overwrite_addr_limit, etc.).
No CMakeLists.txt adicionei a compilação de libcve-2019-2215.so.
No MainActivity:
init {
System.loadLibrary("cve-2019-2215")
}
external fun runNativeExploit(): String
external fun setNativeLogger(logger: NativeLogger)
No lado Kotlin fiz um ExploitViewModel que implementa a interface
NativeLogger e armazena todas as mensagens em um StateFlow<List<String>>. A UI se inscreve
nesse fluxo e exibe o log em um "terminal".
Ao iniciar a activity, chamo setNativeLogger(viewModel), para que o código nativo receba
um objeto para o qual possa enviar strings.
Compilo e instalo o aplicativo:
./gradlew installDebug
Inicio o AVD e o próprio aplicativo.
Na tela vejo um "terminal" e um botão RUN EXPLOIT.
Ao pressionar:
runNativeExploit() em uma thread em segundo plano.Em um kernel real vulnerável, eu esperaria ver algo como:
[+] Selinux changed: Permissive now.
[+] Root escalation successful!
uid=0(root)...
No emulador atual do Android 10 isso, obviamente, não ocorre, mas todo o resto —
vazamento de task_struct, tentativa de sobrescrever addr_limit, cálculo de cred e kernel_base —
funciona como um "cenário", que era o que se pedia na tarefa.
Abaixo — esquema lógico do exploit com vínculo às funções C específicas.
O plano de alto nível é:
struct binder_thread e usá-lo para
vazar o endereço do task_struct do próprio processo (leak_task_struct).addr_limit no task_struct (overwrite_addr_limit) — isso remove a restrição
entre endereços de user-space e kernel-space para posteriores
copy_to_user / copy_from_user.arb_read / arb_write).cred do processo atual e a base do kernel (verifying),
em seguida:
selinux_enforcing = 0),cred para se tornar root e obter o conjunto completo de capabilities
(runNativeExploit).Paralelamente, integrei um logger JNI para que todas essas etapas fossem visíveis diretamente na UI.
task_struct (leak_task_struct)Função principal:
void leak_task_struct() {
android_log("[*] Starting leak_task_struct...");
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(0, &cpu_set);
ret = sched_setaffinity(0, sizeof(cpu_set), &cpu_set);
assert(ret >= 0);
...
}
O que a função faz:
Fixa a thread na CPU 0 (sched_setaffinity), para tornar o comportamento do alocador do kernel
mais previsível. Isso melhora a estabilidade da exploração do UAF.
Abre /dev/binder, cria um descritor epoll:
fd = open("/dev/binder", O_RDONLY);
epfd = epoll_create(1000);
O descritor Binder é registrado no epoll:
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
Prepara um array struct iovec iov_buffers[IOVEC_N] e aloca memória:
spinner = mmap((void *)0x100000000, page_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
Aqui é importante que os 32 bits inferiores do endereço sejam zeros:
if (((long) spinner & 0xffffffff) != 0) {
android_log("[!] mmap returned wrong address!");
return;
}