Accueil La cyber-sécurité Android se lance à fond dans le Fuzzing

Android se lance à fond dans le Fuzzing

0
Android se lance à fond dans le Fuzzing
Android se lance à fond dans le Fuzzing


Le fuzzing est une technique efficace pour détecter les vulnérabilités logicielles. Au cours des dernières années, Android s’est concentré sur l’amélioration de l’efficacité, de la portée et de la commodité du fuzzing au sein de l’organisation. Cet effort a directement abouti à une meilleure couverture des tests, à moins de bogues de sécurité/stabilité et à une meilleure qualité de code. Notre implémentation du fuzzing continu permet aux équipes logicielles de trouver de nouveaux bogues/vulnérabilités et d’empêcher automatiquement les régressions sans avoir à lancer elles-mêmes manuellement des exécutions de fuzzing. Cet article raconte un bref historique du fuzzing sur Android, explique comment Google effectue le fuzzing à grande échelle et documente notre expérience, nos défis et nos succès dans la création d’une infrastructure pour automatiser le fuzzing sur Android. Si vous souhaitez contribuer au fuzzing sur Android, nous avons inclus des instructions sur la façon de démarrer et des informations sur la manière dont le VRP d’Android récompense les contributions au fuzzing qui détectent des vulnérabilités.

Une brève histoire du fuzzing Android

Le fuzzing existe depuis de nombreuses années et Android a été l’un des premiers grands projets logiciels à automatiser le fuzzing et à lui donner la même priorité que les tests unitaires dans le cadre de l’objectif plus large visant à faire d’Android le système d’exploitation le plus sécurisé et le plus stable. En 2019, Android a lancé le projet fuzzing, dans le but d’aider à institutionnaliser le fuzzing en le rendant transparent et intégré à la soumission de code. Le projet de fuzzing Android a abouti à une infrastructure composée de téléphones Pixel et d’appareils virtuels basés sur le cloud de Google qui ont permis des capacités de fuzzing évolutives dans l’ensemble de l’écosystème Android. Ce projet s’est depuis développé pour devenir l’infrastructure de fuzzing interne officielle pour Android et effectue des milliers d’heures de fuzzing par jour sur des centaines de fuzzers.

Sous le capot : comment Android est-il flou

Étape 1 : Définissez et recherchez tous les fuzzers dans le dépôt Android

La première étape consiste à intégrer le fuzzing dans le système de build Android (Bientôt) pour activer la création de binaires fuzzer. Pendant que les développeurs sont occupés à ajouter des fonctionnalités à leur base de code, ils peuvent inclure un fuzzer pour fuzzer leur code et soumettre le fuzzer avec le code qu’ils ont développé. Android Fuzzing utilise une règle de construction appelée cc_fuzz (voir exemple ci-dessous). cc_fuzz (nous prenons également en charge rust_fuzz et java_fuzz) définit un module Soong avec des fichiers sources et des dépendances qui peuvent être intégrés dans un binaire.

cc_fuzz {
  name: "fuzzer_foo",

  srcs: [
    "fuzzer_foo.cpp",
  ],

  static_libs: [
    "libfoo",
  ],

  host_supported: true,
}

Une règle d’empaquetage dans Soong trouve toutes ces définitions cc_fuzz et les construit automatiquement. La structure réelle du fuzzer elle-même est très simple et se compose d’une méthode principale (LLVMTestOneInput) :

#include <stddef.h>
#include <stdint.h>

extern "C" int LLVMFuzzerTestOneInput(
               const uint8_t *data,
               size_t size) {

  // Here you invoke the code to be fuzzed. 
  return 0;
}

Ce fuzzer est automatiquement intégré dans un binaire et, avec ses dépendances statiques/dynamiques (telles que spécifiées dans le fichier de construction Android), sont regroupés dans un fichier zip qui est ajouté au zip principal contenant tous les fuzzers, comme indiqué dans l’exemple ci-dessous.

Étape 2 : Ingérer tous les fuzzers dans les versions Android

Une fois que les fuzzers sont trouvés dans le référentiel Android et intégrés dans les binaires, l’étape suivante consiste à les télécharger sur le stockage cloud en vue de les exécuter sur notre backend. Ce processus est exécuté plusieurs fois par jour. L’infrastructure de fuzzing Android utilise un framework de fuzzing continu open source (Clusterfuzz) pour exécuter des fuzzers en continu sur les appareils et émulateurs Android. Afin d’exécuter les fuzzers sur clusterfuzz, les fichiers zip des fuzzers sont renommés après la build et la dernière build peut être exécutée (voir schéma ci-dessous) :

Le fichier zip fuzzer contient le binaire fuzzer, le dictionnaire correspondant ainsi qu’un sous-dossier contenant ses dépendances et les numéros de révision git (sourcemap) correspondant au build. Les sourcesmaps sont utilisées pour améliorer les traces de pile et produire des rapports de plantage.

Étape 3 : Exécutez les fuzzers en continu et recherchez les bugs

L’exécution continue des fuzzers s’effectue via des tâches planifiées où chaque tâche est associée à un ensemble de périphériques physiques ou d’émulateurs. Un travail est également soutenu par une file d’attente qui représente les tâches de fuzzing qui doivent être exécutées. Ces tâches sont une combinaison de l’exécution d’un fuzzer, de la reproduction d’un crash trouvé lors d’une exécution de fuzzing précédente ou de la minimisation du corpus, entre autres tâches.

Chaque fuzzer fonctionne pendant plusieurs heures ou jusqu’à ce qu’il détecte un crash. Après l’exécution, le fuzzing Android prend toutes les entrées intéressantes découvertes au cours de l’exécution et les ajoute au corpus fuzzer. Ce corpus est ensuite partagé entre les exécutions de fuzzer et s’agrandit au fil du temps. Le fuzzer est ensuite priorisé lors des exécutions suivantes en fonction de la croissance de la nouvelle couverture et des crashs détectés (le cas échéant). Cela garantit que nous donnons aux fuzzers les plus efficaces plus de temps pour fonctionner et trouver des crashs intéressants.

Étape 4 : Générer une couverture de ligne de fuzzers

À quoi sert un fuzzer s’il ne fuzze pas le code qui vous intéresse ? Pour améliorer la qualité du fuzzer et surveiller la progression globale du fuzzing Android, deux types de métriques de couverture sont calculés et disponibles pour les développeurs Android. La première métrique concerne bord couverture qui fait référence aux bords dans le Control Flow Graph (CFG). En instrumentant le fuzzer et le code fuzzé, le moteur de fuzzing peut suivre de petits extraits de code qui sont déclenchés à chaque fois que le flux d’exécution les atteint. De cette façon, les moteurs de fuzzing savent exactement combien (et combien de fois) chacun de ces points d’instrumentation a été touché à chaque exécution afin de pouvoir les regrouper et calculer la couverture.

INFO: Seed: 2859304549
INFO: Loaded 1 modules   (773 inline 8-bit counters): 773 [0x5610921000, 0x5610921305),
INFO: Loaded 1 PC tables (773 PCs): 773 [0x5610921308,0x5610924358),
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2      INITED cov: 2 ft: 2 corp: 1/1b lim: 4 exec/s: 0 rss: 24Mb
#413    NEW    cov: 3 ft: 3 corp: 2/9b lim: 8 exec/s: 0 rss: 24Mb L: 8/8 MS: 1 InsertRepeatedBytes-
#3829   NEW    cov: 4 ft: 4 corp: 3/17b lim: 38 exec/s: 0 rss: 24Mb L: 8/8 MS: 1 ChangeBinInt-
...

La couverture de lignes insère des points d’instrumentation spécifiant les lignes dans le code source. La couverture de lignes est très utile pour les développeurs car ils peuvent identifier les zones du code qui ne sont pas couvertes et mettre à jour leurs fuzzers en conséquence pour atteindre ces zones lors de futures exécutions de fuzzing.

L’exploration de l’un des dossiers peut afficher les statistiques par fichier :

Un clic supplémentaire sur l’un des fichiers affiche les lignes qui ont été touchées et les lignes qui n’ont jamais été couvertes. Dans l’exemple ci-dessous, la première ligne a été fuzzée environ 5 millions de fois, mais le fuzzer n’arrive jamais dans les lignes 3 et 4, ce qui indique un écart dans la couverture de ce fuzzer.

Nous disposons de tableaux de bord en interne qui mesurent notre couverture de fuzzing sur l’ensemble de notre base de code. Afin de générer vous-même ces tableaux de bord de couverture, vous suivez ces pas.

Une autre mesure de la qualité des fuzzers est le nombre d’itérations de fuzzing pouvant être effectuées en une seconde. Cela a une relation directe avec la puissance de calcul et la complexité de la cible du fuzz. Cependant, ce paramètre à lui seul ne peut pas mesurer la qualité ou l’efficacité du fuzzing.

Comment nous gérons les bugs de fuzzer

Le fuzzing Android utilise le Clusterfuzz infrastructure floue pour gérer les plantages détectés et déposer un ticket auprès de l’équipe de sécurité Android. La sécurité Android évalue le crash en fonction des Directives de gravité Android puis achemine la vulnérabilité vers l’équipe appropriée pour y remédier. L’ensemble du processus consistant à rechercher le crash reproductible, à le router vers Android Security, puis à attribuer le problème à une équipe responsable peut prendre aussi peu que deux heures et jusqu’à une semaine selon le type de crash et la gravité de la vulnérabilité.

Un exemple de succès récent en matière de fuzzer est (CVE2022-20473), où une équipe interne a écrit un fuzzer de 20 lignes et l’a soumis pour fonctionner sur Android fuzzing infra. En une journée, le fuzzer a été ingéré et poussé vers notre infrastructure de fuzzing pour commencer le fuzzer, et a rapidement découvert une vulnérabilité de gravité critique ! Un correctif pour ce CVE a été appliqué par l’équipe de service.

Pourquoi Android continue d’investir dans le Fuzzing

Protection contre les régressions de code

Le projet Android Open Source (AOSP) est un projet vaste et complexe avec de nombreux contributeurs. En conséquence, des milliers de modifications sont apportées au projet chaque jour. Ces changements peuvent aller de petites corrections de bugs à des ajouts de fonctionnalités importants, et le fuzzing aide à trouver des vulnérabilités qui peuvent être introduites par inadvertance et ne pas être détectées lors de la révision du code.

Le fuzzing continu a permis de détecter ces vulnérabilités avant qu’elles ne soient introduites en production et exploitées par des attaquants. Un exemple concret est (CVE-2023-21041), une vulnérabilité découverte par un fuzzer écrit il y a trois ans. Cette vulnérabilité affectait le micrologiciel Android et aurait pu conduire à une élévation locale des privilèges sans privilèges d’exécution supplémentaires nécessaires. Ce fuzzer a fonctionné pendant de nombreuses années avec des résultats limités jusqu’à ce qu’une régression du code conduise à l’introduction de cette vulnérabilité. Ce CVE a depuis été corrigé.

Protection contre les pièges du langage de mémoire dangereux

Android est un grand partisan de RouillerAndroid 13 étant la première version d’Android avec la majorité du nouveau code dans un langage sécurisé pour la mémoire. La quantité de nouveau code dangereux pour la mémoire entrant dans Android a diminué, mais il reste encore des millions de lignes de code, d’où le besoin de fuzzing persiste.

Non Un Le code est sécurisé : code fuzz dans des langages sécurisés en mémoire

Notre travail ne s’arrête pas aux langages non sécurisés pour la mémoire, et nous encourageons également le développement de fuzzers dans des langages comme Rust. Bien que le fuzzing ne trouve pas de vulnérabilités courantes que l’on s’attendrait à voir dans des langages dangereux en mémoire comme C/C++, de nombreux problèmes non liés à la sécurité ont été découverts et corrigés qui contribuent à la stabilité globale d’Android.

Des défis flous

En plus des problèmes génériques liés aux binaires C/C++ tels que les dépendances manquantes, les fuzzers peuvent avoir leurs propres classes de problèmes :

Faibles exécutions par seconde: pour fuzzer efficacement, le nombre de mutations doit être de l’ordre de centaines par seconde sinon le fuzzing mettra très longtemps à couvrir le code. Nous avons résolu ce problème en ajoutant un ensemble d’alertes qui surveillent en permanence l’état des fuzzers ainsi que toute baisse soudaine de la couverture. Une fois qu’un fuzzer est identifié comme sous-performant, un e-mail automatisé est envoyé à l’auteur du fuzzer avec des détails pour l’aider à améliorer le fuzzer.

Fuzzer le mauvais code : Comme toutes les ressources, les ressources de fuzzing sont limitées. Nous voulons nous assurer que ces ressources nous donnent le meilleur rendement, et cela signifie généralement les consacrer au code fuzzing qui traite des entrées non fiables (c’est-à-dire potentiellement contrôlées par un attaquant). Cela peut couvrir tous les moyens par lesquels le téléphone peut recevoir des entrées, notamment Bluetooth, NFC, USB, Web, etc. L’analyse des entrées structurées est particulièrement intéressante car il existe une marge pour des erreurs de programmation en raison de la complexité des spécifications. Le code qui génère une sortie n’est pas particulièrement intéressant à fuzzer. De même, le code interne qui n’est pas exposé publiquement pose également moins de problèmes de sécurité. Nous avons résolu ce problème en identifiant le code le plus vulnérable (voir la section suivante).

Que faut-il fuzzer

Afin de fuzzer les composants les plus importants du code source Android, nous nous concentrons sur les bibliothèques qui possèdent :

  1. Une histoire de vulnérabilités : l’histoire ne doit pas être l’histoire lointaine depuis le changement de contexte mais plutôt se concentrer sur les 12 derniers mois.
  2. Modifications récentes du code : les recherches indiquent que davantage de vulnérabilités se trouvent dans le code récemment modifié que dans le code plus stable.
  3. Accès à distance : les vulnérabilités du code accessible à distance peuvent être critiques.
  4. Privilégié : comme pour le point 3, les vulnérabilités du code qui s’exécute dans des processus privilégiés peuvent être critiques.

Comment soumettre un fuzzer à l’AOSP

Nous écrivons et améliorons constamment des fuzzers en interne pour couvrir certains des domaines les plus sensibles d’Android, mais il y a toujours place à l’amélioration. Si vous souhaitez commencer à écrire votre propre fuzzer pour un domaine de l’AOSP, vous pouvez le faire pour rendre Android plus sécurisé (exemple CL) :

  1. Obtenir Code source Android
  2. Vous avez un téléphone de test ?
  3. Écrire un cible de peluche (suivre les directives de la section « Quoi fuzz »)
  4. Télécharger votre fuzzer à AOSP.

Commencez par lire notre documentation sur Fuzzer avec libFuzzer et vérifiez votre fuzzer dans le Projet Android Open Source. Si votre fuzzer trouve un bug, vous pouvez le soumettre au Programme de prime aux bogues Android et pourrait être éligible à une récompense !

Vous avez des questions sur cet article ou souhaitez entrer en contact avec notre équipe ? Nous voulons de vos nouvelles! Veuillez nous contacter en nous envoyant directement un e-mail à [email protected].

LAISSER UN COMMENTAIRE

S'il vous plaît entrez votre commentaire!
S'il vous plaît entrez votre nom ici