
démonstration CVE-2019-2215 (Bad Binder) pour Android Q
Ce dépôt est un petit projet de test pour étudier la vulnérabilité
CVE-2019-2215 (Bad Binder) et écrire un prototype fonctionnel d'exploit pour Android avec
une interface graphique simple en Kotlin/Jetpack Compose.
Dans ce README, je :
Le dépôt contient un workflow GitHub Actions qui, à chaque push/PR, compile le projet
avec la commande ./gradlew assembleDebug et publie le fichier badbinder-debug.apk comme artefact.
Vous pouvez le télécharger ainsi :
badbinder-debug-apk
contenant l'APK compilé.Cela a été fait pour plus de commodité, si vous souhaitez simplement tester l'application sans configurer un environnement local.
CVE-2019-2215 est une Use-After-Free (UAF) dans le sous-système IPC Binder du noyau Android.
En simplifiant :
struct binder_thread qui décrit un thread
effectuant des appels Binder ;waitqueue) ;remove_wait_queue,
ce qui ouvre un scénario UAF classique ;Une analyse théorique plus détaillée a été réalisée à partir des ressources suivantes :
Selon les instructions, il est recommandé d'utiliser un AVD avec l'image Android 10.0 (Q) x86_64.
J'ai fait ce qui suit :
/dev/binder.adb shell
ls -l /dev/binder
À cette étape, j'ai rencontré un fait désagréable :
actuellement, les images AVD à jour sont déjà livrées avec un noyau patché, dans lequel
CVE-2019-2215 est corrigée. Autrement dit, il ne sera pas possible d'obtenir réellement un root sur un émulateur officiel moderne — l'exploit échoue à des stades ultérieurs ou ne permet tout simplement pas l'élévation de privilèges.
En fin de compte, j'utilise l'AVD comme simulateur pour reproduire la logique de l'exploit :
addr_limit,C'est une nuance importante : tout le code et le rapport ci-dessous sont éducatifs, pas « opérationnels ».
J'ai créé une petite application Android :
Les principales étapes :
Création d'un projet standard dans Android Studio (Kotlin, prise en charge minimale d'Android 10).
Connexion du NDK et de CMake.
Ajout d'un fichier natif contenant l'exploit (le fameux cve-2019-2215.c avec les fonctions
leak_task_struct, overwrite_addr_limit, etc.).
Dans CMakeLists.txt, ajout de la compilation de libcve-2019-2215.so.
Dans MainActivity :
init {
System.loadLibrary("cve-2019-2215")
}
external fun runNativeExploit(): String
external fun setNativeLogger(logger: NativeLogger)
Côté Kotlin, création d'un ExploitViewModel qui implémente l'interface
NativeLogger et stocke tous les messages dans un StateFlow<List<String>>. L'UI est abonnée
à ce flux et affiche le journal dans un « terminal ».
Au lancement de l'activité, j'appelle setNativeLogger(viewModel) pour que le code natif
reçoive un objet auquel envoyer des chaînes.
Compilation et installation de l'application :
./gradlew installDebug
Lancement de l'AVD et de l'application elle-même.
À l'écran, je vois un « terminal » et un bouton RUN EXPLOIT.
Appui sur le bouton :
runNativeExploit() dans un thread d'arrière-plan.Sur un noyau vulnérable réel, je m'attendrais à voir à la fin quelque chose comme :
[+] Selinux changed: Permissive now.
[+] Root escalation successful!
uid=0(root)...
Sur l'émulateur Android 10 actuel, cela ne se produit évidemment pas, mais tout le reste —
fuite de task_struct, tentative de réécriture de addr_limit, calcul de cred et de kernel_base —
s'exécute comme un « scénario », ce qui était requis pour l'exercice.
Ci-dessous, le schéma logique de l'exploit avec les fonctions C correspondantes.
Le plan de haut niveau est le suivant :
struct binder_thread et l'utiliser pour
faire fuir l'adresse de task_struct de son propre processus (leak_task_struct).addr_limit dans task_struct (overwrite_addr_limit) — cela supprime la restriction
entre les adresses espace utilisateur et espace noyau pour les opérations ultérieures
copy_to_user / copy_from_user.arb_read / arb_write).cred du processus courant et la base du noyau (verifying),
puis :
selinux_enforcing = 0),cred pour devenir root et obtenir l'ensemble complet des capacités
(runNativeExploit).En parallèle, j'ai intégré un journaliseur JNI pour que toutes ces étapes soient visibles directement dans l'UI.
task_struct (leak_task_struct)Fonction clé :
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);
...
}
Ce que fait la fonction :