
dimostrazione di CVE-2019-2215 (Bad Binder) per Android Q
Questo repository è un piccolo progetto di test per lo studio della vulnerabilità
CVE-2019-2215 (Bad Binder) e per la scrittura di un prototipo funzionante di exploit per Android con
una semplice interfaccia grafica in Kotlin/Jetpack Compose.
In questo README:
Nel repository è configurato un workflow GitHub Actions che, ad ogni push/PR, compila il progetto
con ./gradlew assembleDebug e pubblica il badbinder-debug.apk come artefatto.
È possibile scaricarlo così:
badbinder-debug-apk
con l'APK compilato.Questo è stato fatto per comodità, nel caso si voglia semplicemente testare l'applicazione senza dover configurare un ambiente locale.
CVE-2019-2215 è un Use-After-Free (UAF) nel sottosistema IPC Binder del kernel Android.
In parole povere:
struct binder_thread che descrive un thread
che esegue chiamate Binder;waitqueue);remove_wait_queue,
aprendo uno scenario UAF classico;Un'analisi teorica più dettagliata l'ho fatta basandomi su:
Per il compito è stato consigliato di utilizzare un AVD con immagine Android 10.0 (Q) x86_64.
Io ho fatto quanto segue:
/dev/binder.adb shell
ls -l /dev/binder
In questa fase mi sono imbattuto in un fatto spiacevole:
al momento le immagini AVD attuali sono già fornite con un kernel patchato, in cui
CVE-2019-2215 è corretta. Cioè, non è realmente possibile ottenere root su un emulatore ufficiale moderno
— l'exploit fallisce in fasi successive o semplicemente non fornisce l'elevazione dei privilegi.
Alla fine utilizzo l'AVD come simulatore per riprodurre la logica dell'exploit:
addr_limit,Questa è una sfumatura importante: tutto il codice e il report seguente sono didattici, non «da combattimento».
Ho realizzato una piccola applicazione Android:
I passaggi principali:
Ho creato un normale progetto in Android Studio (Kotlin, supporto minimo per Android 10).
Ho collegato NDK e CMake.
Ho aggiunto un file nativo con l'exploit (lo stesso cve-2019-2215.c con le funzioni
leak_task_struct, overwrite_addr_limit, ecc.).
In CMakeLists.txt ho aggiunto la build di libcve-2019-2215.so.
In MainActivity:
init {
System.loadLibrary("cve-2019-2215")
}
external fun runNativeExploit(): String
external fun setNativeLogger(logger: NativeLogger)
Lato Kotlin ho creato ExploitViewModel, che implementa l'interfaccia
NativeLogger e accumula tutti i messaggi in un StateFlow<List<String>>. L'UI è sottoscritta
a questo flusso e mostra il log in un «terminale».
All'avvio dell'activity chiamo setNativeLogger(viewModel), in modo che il codice nativo riceva
l'oggetto a cui inviare le stringhe.
Compilo e installo l'applicazione:
./gradlew installDebug
Avvio l'AVD e l'applicazione stessa.
Sullo schermo vedo un «terminale» e un pulsante RUN EXPLOIT.
Al clic:
runNativeExploit() in un thread in background.Su un kernel reale vulnerabile, mi aspetterei di vedere alla fine qualcosa come:
[+] Selinux changed: Permissive now.
[+] Root escalation successful!
uid=0(root)...
Sull'emulatore attuale di Android 10 questo, ovviamente, non accade, ma tutto il resto —
leak di task_struct, tentativo di sovrascrivere addr_limit, calcolo di cred e kernel_base —
funziona come «scenario», che era quanto richiesto per il compito.
Di seguito lo schema logico dell'exploit con riferimento a specifiche funzioni C.
Il piano ad alto livello è il seguente:
struct binder_thread e usarlo per
fare leak dell'indirizzo di task_struct del proprio processo (leak_task_struct).addr_limit in task_struct (overwrite_addr_limit) — questo rimuove la limitazione
tra gli indirizzi user-space e kernel-space per le successive
copy_to_user / copy_from_user.arb_read / arb_write).cred del processo corrente e la base del kernel (verifying),
quindi:
selinux_enforcing = 0),cred per diventare root e ottenere il set completo di capability
(runNativeExploit).Parallelamente, ho integrato un logger JNI, in modo che tutte queste fasi siano visibili direttamente nell'UI.
task_struct (leak_task_struct)Funzione chiave:
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);
...
}
Cosa fa la funzione:
Fissa il thread sulla CPU 0 (sched_setaffinity), in modo che il comportamento dell'allocatore del kernel
sia più prevedibile. Questo migliora la stabilità dello sfruttamento UAF.
Apre /dev/binder, crea un descrittore epoll:
fd = open("/dev/binder", O_RDONLY);
epfd = epoll_create(1000);
Il descrittore Binder viene registrato in epoll:
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
Prepara un array struct iovec iov_buffers[IOVEC_N] e alloca memoria: