Analyse de vulnérabilité EvilParcel

Présentation


À la mi-avril, nous avons publié des informations sur le cheval de Troie Android.InfectionAds.1 , qui exploitait plusieurs vulnérabilités critiques dans le système d'exploitation Android. L'un d'eux - CVE-2017-13156 (également connu sous le nom de Janus ) - permet à un programme malveillant d'infecter les fichiers APK sans endommager leur signature numérique.

L'autre est CVE-2017-13315. Il confère au cheval de Troie des privilèges avancés et peut installer et désinstaller indépendamment des applications. Une analyse détaillée d' Android.InfectionAds.1 est disponible dans notre bibliothèque de virus, vous pouvez la trouver ici . Nous allons nous attarder sur la vulnérabilité CVE-2017-13315 plus en détail et voir à quoi elle ressemble.

CVE-2017-13315 appartient au groupe de vulnérabilités qui a reçu le nom général EvilParcel. Ils se trouvent dans différentes classes de système du système d'exploitation Android. En raison d'erreurs dans ce dernier lors de l'échange de données entre les applications et le système, il devient possible de remplacer ces données. Les programmes malveillants qui exploitent les vulnérabilités EvilParcel reçoivent des privilèges plus élevés et peuvent effectuer les opérations suivantes avec leur aide:

  • installer et désinstaller des applications avec toutes les autorisations sans confirmation de l'utilisateur;
  • lorsqu'il est utilisé conjointement avec d'autres vulnérabilités, infecter les programmes installés sur l'appareil et remplacer les originaux «propres» par des copies infectées;
  • Réinitialiser le code de verrouillage d'écran pour l'appareil Android
  • Réinitialisez le code PIN de l'écran de verrouillage de l'appareil Android.

Il existe actuellement 7 vulnérabilités connues de ce type:

  • CVE-2017-0806 (erreur dans la classe GateKeeperResponse), publiée en octobre 2017;
  • CVE-2017-13286 (erreur dans la classe OutputConfiguration, publiée en avril 2018;
  • CVE-2017-13287 (erreur dans la classe VerifyCredentialResponse), publiée en avril 2018;
  • CVE-2017-13288 (erreur dans la classe PeriodicAdvertizingReport), publiée en avril 2018;
  • CVE-2017-13289 (bogue dans la classe ParcelableRttResults), publié en avril 2018;
  • CVE-2017-13311 (bogue dans la classe SparseMappingTable), publié en mai 2018;
  • CVE-2017-13315 (erreur dans la classe DcParamObject), publié en mai 2018.

Tous menacent les appareils exécutant Android OS versions 5.0 - 8.1 sur lesquels les mises à jour de sécurité de mai 2018 et ultérieures ne sont pas installées.

Conditions préalables aux vulnérabilités EvilParcel


Voyons comment les vulnérabilités EvilParcel surviennent. Tout d'abord, nous allons examiner certaines des fonctionnalités des applications Android. Dans Android OS, tous les programmes interagissent entre eux, ainsi qu'avec le système d'exploitation lui-même, en envoyant et en recevant des objets de type Intent. Ces objets peuvent contenir un nombre arbitraire de paires clé-valeur à l'intérieur d'un objet de type Bundle.

Lors de la transmission de l'intention, l'objet Bundle est converti (sérialisé) en un tableau d'octets enveloppé dans Parcel, et lors de la lecture des clés et des valeurs d'un bundle sérialisé, il est automatiquement désérialisé.

Dans le Bundle, la chaîne est la clé et la valeur peut être presque n'importe quoi. Par exemple, un type primitif, une chaîne ou un conteneur contenant des types ou des chaînes primitifs. De plus, il peut s'agir d'un objet de type Parcelable.

Ainsi, dans le Bundle, vous pouvez placer un objet de tout type qui implémente l'interface Parcelable. Pour ce faire, vous devrez implémenter les méthodes writeToParcel () et createFromParcel () pour sérialiser et désérialiser l'objet.

Comme bon exemple, créons un bundle sérialisé simple. Écrivons un code qui place trois paires clé-valeur dans le bundle et le sérialise:

Démo de bundle = nouveau bundle ();
demo.putString ("String", "Bonjour tout le monde!");
demo.putInt ("Integer", 42);
demo.putByteArray ("ByteArray", nouvel octet [] {1, 2, 3, 4, 5, 6, 7, 8});
Parcel parcel = Parcel.obtain ();
parcel.writeBundle (démo);

Après avoir exécuté ce code, nous obtenons un ensemble du formulaire suivant:



Figure 1. Structure d'un objet Bundle sérialisé.

Prenons attention aux fonctionnalités suivantes de la sérialisation Bundle:

  • toutes les paires clé-valeur sont écrites l'une après l'autre;
  • avant chaque valeur, son type est indiqué (13 pour un tableau d'octets, 1 pour un entier, 0 pour une chaîne, etc.);
  • avant les données de longueur variable, leur taille est indiquée (longueur pour la chaîne, nombre d'octets pour le tableau);
  • toutes les valeurs sont écrites avec un alignement de 4 octets.

Du fait que toutes les clés et valeurs du Bundle sont écrites séquentiellement, lors de l'accès à une clé ou à la valeur d'un objet Bundle sérialisé, ce dernier est complètement désérialisé, y compris l'initialisation de tous les objets Parcelable qui y sont contenus.

Il semblerait, quel pourrait être le problème? Et c'est que dans certaines classes système qui implémentent Parcelable, les méthodes createFromParcel () et writeToParcel () peuvent rencontrer des erreurs. Dans ces classes, le nombre d'octets lus dans la méthode createFromParcel () sera différent du nombre d'octets écrits dans la méthode writeToParcel (). Si vous placez un objet d'une telle classe à l'intérieur du Bundle, les limites de l'objet à l'intérieur du Bundle changeront après la re-sérialisation. Et c'est là que les conditions d'exploitation de la vulnérabilité EvilParcel sont créées.

Voici un exemple d'une classe avec une erreur similaire:

class Demo implements Parcelable { byte[] data; public Demo() { this.data = new byte[0]; } protected Demo(Parcel in) { int length = in.readInt(); data = new byte[length]; if (length > 0) { in.readByteArray(data); } } public static final Creator<Demo> CREATOR = new Creator<Demo>() { @Override public Demo createFromParcel(Parcel in) { return new Demo(in); } }; @Override public void writeToParcel(Parcel parcel, int i) { parcel.writeInt(data.length); parcel.writeByteArray(data); } } 

Si la taille du tableau de données est 0, lors de la création d'un objet dans createFromParcel (), un int (4 octets) sera lu et deux int (8 octets) seront écrits dans writeToParcel (). Le premier entier sera écrit dans un appel explicite à writeInt. Le deuxième entier sera écrit lorsque writeByteArray () est appelé, car la longueur du tableau est toujours écrite dans Parcel avant lui (voir la figure 1).

Les situations où la taille du tableau de données est 0 sont rares. Mais même lorsque cela se produit, le programme continue de fonctionner si un seul objet est transmis sous forme sérialisée à la fois (dans notre exemple, l'objet Demo). Par conséquent, de telles erreurs, en règle générale, passent inaperçues.

Essayons maintenant de placer un objet Demo avec une longueur de tableau nulle dans le Bundle:


Figure 2. Résultat de l'ajout d'un objet de démonstration avec une longueur de tableau nulle au bundle.

Nous sérialisons l'objet:


Figure 3. Bundle objet après la sérialisation.

Essayons de le désérialiser:


Figure 4. Après désérialisation de l'objet Bundle.

Quel est le résultat? Considérez un extrait de colis:


Figure 5. Structure du colis après désérialisation du faisceau.

D'après les figures 4 et 5, nous voyons que pendant la désérialisation, un entier a été lu dans la méthode createFromParcel au lieu de deux précédemment écrits. Par conséquent, toutes les valeurs suivantes de l'ensemble n'ont pas été lues correctement. La valeur 0x0 à l'adresse 0x60 a été lue comme la longueur de la clé suivante. Et la valeur 0x1 à l'adresse 0x64 a été lue comme une clé. Dans ce cas, la valeur 0x31 à l'adresse 0x68 a été lue comme type de valeur. Il n'y a aucune valeur dans Parcel dont le type est 0x31, donc readFromParcel () a fidèlement signalé une erreur (exception).

Comment cela peut-il être utilisé dans la pratique et devenir une vulnérabilité? Voyons voir! L'erreur décrite ci-dessus dans les classes système Parcelable vous permet de construire Bundle, qui peut différer lors de la première et des désérialisations répétées. Pour illustrer cela, modifiez l'exemple précédent:

 Parcel data = Parcel.obtain(); data.writeInt(3); // 3 entries data.writeString("vuln_class"); data.writeInt(4); // value is Parcelable data.writeString("com.drweb.testbundlemismatch.Demo"); data.writeInt(0); // data.length data.writeInt(1); // key length -> key value data.writeInt(6); // key value -> value is long data.writeInt(0xD); // value is bytearray -> low(long) data.writeInt(-1); // bytearray length dummy -> high(long) int startPos = data.dataPosition(); data.writeString("hidden"); // bytearray data -> hidden key data.writeInt(0); // value is string data.writeString("Hi there"); // hidden value int endPos = data.dataPosition(); int triggerLen = endPos - startPos; data.setDataPosition(startPos - 4); data.writeInt(triggerLen); // overwrite dummy value with the real value data.setDataPosition(endPos); data.writeString("A padding"); data.writeInt(0); // value is string data.writeString("to match pair count"); int length = data.dataSize(); Parcel bndl = Parcel.obtain(); bndl.writeInt(length); bndl.writeInt(0x4C444E42); // bundle magic bndl.appendFrom(data, 0, length); bndl.setDataPosition(0); 

Ce code crée un bundle sérialisé qui contient une classe vulnérable. Regardons le résultat de l'exécution de ce code:


Figure 6. Création d'un bundle avec une classe vulnérable.

Après la première désérialisation, ce bundle contiendra les clés suivantes:


Figure 7. Résultat de la désérialisation d'un bundle avec une classe vulnérable.

Maintenant, sérialisez à nouveau le bundle résultant, puis désérialisez-le à nouveau et consultez la liste des clés:


Figure 8. Résultat de la re-sérialisation et de la désérialisation d'un bundle avec une classe vulnérable.

Que voyons-nous? La clé cachée (avec la valeur de chaîne «Hi there!») Est apparue dans le bundle, qui n'était pas là auparavant. Considérez l'extrait de colis de cet ensemble pour comprendre pourquoi cela s'est produit:


Figure 9. Structure de parcelle de l'objet Bundle avec la classe vulnérable après deux cycles de sérialisation-désérialisation.

Ici, l'essence des vulnérabilités EvilParcel devient plus claire. Il est possible de créer un Bundle spécialement formé qui contiendra une classe vulnérable. Changer les limites de cette classe vous permettra de placer n'importe quel objet dans ce Bundle - par exemple, Intent, qui n'apparaîtra dans le Bundle qu'après la deuxième désérialisation. Cela permettra de masquer Intent aux mécanismes de protection du système d'exploitation.

Opération EvilParcel


Android.InfectionAds.1 utilisant CVE-2017-13315 a installé et désinstallé seul des programmes sans l'intervention du propriétaire de l'appareil infecté. Mais comment ça se passe?

En 2013, l'erreur 7699048 a également été découverte, également connue sous le nom de Launch AnyWhere. Il a permis à une application tierce d'exécuter des activités arbitraires au nom du système utilisateur le plus privilégié. Le schéma ci-dessous montre son mécanisme d'action:


Figure 10. Schéma d'erreur 7699048.

Avec cette vulnérabilité, une application d'exploitation pourrait implémenter le service AccountAuthenticator, qui est conçu pour ajouter de nouveaux comptes au système d'exploitation. Grâce au bogue 7699048, l'exploit est capable d'exécuter une activité pour installer, désinstaller, remplacer des applications, réinitialiser le code PIN ou le verrouillage de modèle et faire d'autres choses désagréables.

Google a corrigé cet écart en interdisant le lancement d'activités arbitraires depuis AccountManager. Désormais, le AccountManager ne permet que le lancement d'activités provenant de la même application. Pour ce faire, il vérifie et compare la signature numérique du programme qui a initié le démarrage de l'activité avec la signature de l'application dans laquelle se situe l'activité lancée. Cela ressemble à ceci:

 if (result != null && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) { /* * The Authenticator API allows third party authenticators to * supply arbitrary intents to other apps that they can run, * this can be very bad when those apps are in the system like * the System Settings. */ int authenticatorUid = Binder.getCallingUid(); long bid = Binder.clearCallingIdentity(); try { PackageManager pm = mContext.getPackageManager(); ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId); int targetUid = resolveInfo.activityInfo.applicationInfo.uid; if (PackageManager.SIGNATURE_MATCH != pm.checkSignatures(authenticatorUid, targetUid)) { throw new SecurityException( "Activity to be started with KEY_INTENT must " + "share Authenticator's signatures"); } } finally { Binder.restoreCallingIdentity(bid); } } 

Il semblerait que le problème ait été résolu, mais tout n'est pas si fluide ici. Il s'est avéré que ce correctif peut être contourné en utilisant la vulnérabilité bien connue EvilParcel CVE-2017-13315! Comme nous le savons déjà, après avoir corrigé Launch AnyWhere, le système vérifie la signature numérique de l'application. Si cette vérification réussit, le bundle est transmis à IAccountManagerResponse.onResult (). Dans le même temps, onResult () est appelé via le mécanisme IPC, de sorte que le bundle est à nouveau sérialisé. Dans l'implémentation onResult (), les événements suivants se produisent:

 /** Handles the responses from the AccountManager */ private class Response extends IAccountManagerResponse.Stub { public void onResult(Bundle bundle) { Intent intent = bundle.getParcelable(KEY_INTENT); if (intent != null && mActivity != null) { // since the user provided an Activity we will silently start intents // that we see mActivity.startActivity(intent); // leave the Future running to wait for the real response to this request } //<.....> } //<.....> } 

Ensuite, le Bundle est extrait de la clé d'intention et l'activité est lancée sans vérifications. Par conséquent, pour démarrer une activité arbitraire avec des droits système, il suffit de construire le Bundle de telle sorte que le champ d'intention soit masqué lors de la première désérialisation et apparaisse lors de la seconde désérialisation. Et, comme nous l'avons déjà vu, c'est précisément cette tâche que remplissent les vulnérabilités EvilParcel.

À l'heure actuelle, toutes les vulnérabilités connues de ce type sont corrigées par des correctifs dans les classes Parcelable vulnérables elles-mêmes. Cependant, la réapparition de classes vulnérables à l'avenir ne peut être exclue. La mise en œuvre du Bundle et le mécanisme d'ajout de nouveaux comptes sont toujours les mêmes qu'auparavant. Ils vous permettent toujours de créer exactement le même exploit lorsque vous découvrez (ou de nouvelles) classes Parcelable vulnérables. De plus, l'implémentation de ces classes se fait toujours manuellement et le programmeur doit garder un œil sur la longueur constante de l'objet Parcelable sérialisé. Et c'est un facteur humain avec toutes les conséquences. Cependant, nous espérons que ces erreurs seront aussi peu nombreuses que possible et que les vulnérabilités d'EvilParcel ne dérangeront pas les utilisateurs d'appareils Android.

Vous pouvez vérifier votre appareil mobile pour les vulnérabilités EvilParcel en utilisant notre antivirus Dr.Web Security Space . Le «vérificateur de sécurité» intégré rendra compte des problèmes identifiés et formulera des recommandations pour les résoudre.

Source: https://habr.com/ru/post/fr457558/


All Articles