Android 9 Pie et Android 10 émettent des avertissements ou bloquent carrément l'accès aux API cachées. Voici comment les développeurs peuvent contourner les restrictions cachées de l'API.
Retour en arrière il y a plus d'un an et nous sommes tous impatients de voir ce qui va arriver dans les versions bêta d'Android P. Les utilisateurs attendent avec impatience de nouvelles fonctionnalités et les développeurs attendent avec impatience de nouveaux outils pour améliorer leurs applications. Malheureusement pour certains de ces développeurs, la première version bêta d'Android P était accompagnée d'une mauvaise surprise: des restrictions d'API cachées. Avant d’aborder ce que cela signifie exactement, permettez-moi d’expliquer un peu son contexte.
Qu'est-ce que tout cela signifie?
Les développeurs d’applications Android n’ont pas besoin de repartir de zéro lorsqu’ils créent une application. Google fournit des outils pour rendre le développement d'applications plus facile et moins répétitif. L'un de ces outils est le SDK Android. Le SDK est essentiellement une référence à toutes les fonctions que les développeurs peuvent utiliser en toute sécurité dans leurs applications. Ces fonctions sont fournies en standard sur toutes les variantes d'Android approuvées par Google. Le SDK n’est cependant pas exhaustif. Il existe de nombreuses parties « cachées » du framework Android qui ne font pas partie du SDK.
Ces parties "cachées" peuvent être incroyablement utiles pour des tâches plus hacky ou de bas niveau. Par exemple, mon Application Tiroir de widgets utilise une fonction non-SDK pour détecter lorsqu'un utilisateur lance une application à partir d'un widget afin que le tiroir puisse se fermer automatiquement. Vous pensez peut-être: « Pourquoi ne pas simplement intégrer ces fonctions non SDK au SDK? » Eh bien, le problème est que leur fonctionnement n’est pas entièrement prévisible. Google ne peut pas garantir que chaque partie du cadre fonctionne sur chaque appareil qu'il approuve, c'est pourquoi des méthodes plus importantes sont sélectionnées pour être vérifiées. Google ne garantit pas que le reste du framework restera cohérent. Les fabricants peuvent modifier ou supprimer complètement ces fonctions cachées. Même dans différentes versions d’AOSP, vous ne savez jamais si une fonction cachée existera toujours ou fonctionnera comme avant.
Si vous vous demandez pourquoi j'utilise le mot « caché », c'est parce que ces fonctions ne font même pas partie du SDK. Normalement, si vous essayez d’utiliser une méthode ou une classe cachée dans une application, la compilation échouera. Leur utilisation nécessite réflexion ou un SDK modifié.
Avec Android P, Google a décidé qu'il ne suffisait pas de les cacher. Lors de la sortie de la première version bêta, il a été annoncé que la plupart (pas toutes) les fonctions cachées n'étaient plus disponibles pour les développeurs d'applications. La première version bêta vous avertirait lorsque votre application utilisait une fonction sur liste noire. Les prochaines versions bêta ont simplement fait planter votre application. Au moins pour moi, cette liste noire était plutôt ennuyeuse. Non seulement il s'est cassé un peu Gestes de navigation, mais comme les fonctions cachées sont sur liste noire par défaut (Google doit manuellement en ajouter certaines par version), j'ai eu beaucoup de mal à faire fonctionner Widget Drawer.
Il existait désormais plusieurs façons de contourner la liste noire. La première consistait simplement à conserver votre application ciblant l’API 27 (Android 8.1), puisque la liste noire ne s’appliquait qu’aux applications ciblant la dernière API. Malheureusement, avec Google exigences minimales de l'API pour la publication sur le Play Store, il ne serait possible de cibler l'API 27 que pendant une période limitée. Au 1er novembre 2019, toutes les mises à jour d'applications sur le Play Store doivent cibler l'API 28 ou une version ultérieure.
La deuxième solution de contournement est en fait quelque chose que Google a intégré à Android. Il est possible d'exécuter une commande ADB pour désactiver complètement la liste noire. C'est idéal pour un usage personnel et des tests, mais je peux vous dire de première main qu'essayer d'amener les utilisateurs finaux à configurer et à exécuter ADB est un cauchemar.
Alors, où en sommes-nous? Nous ne pouvons plus cibler l'API 27 si nous voulons télécharger sur le Play Store, et la méthode ADB n'est tout simplement pas viable. Cela ne signifie pas pour autant que nous sommes à court d’options.
La liste noire des API masquées s'applique uniquement aux applications utilisateur ne figurant pas sur la liste blanche. Les applications système, les applications signées avec la signature de la plateforme et les applications spécifiées dans un fichier de configuration sont toutes exemptées de la liste noire. Curieusement, la suite des services Google Play est toutes spécifiées dans ce fichier de configuration. Je suppose que Google est meilleur que le reste d'entre nous.
Quoi qu'il en soit, continuons à parler de la liste noire. Ce qui nous intéresse aujourd'hui, c'est que les applications du système sont exemptées. Cela signifie, par exemple, que l'application System UI peut utiliser toutes les fonctions cachées qu'elle souhaite puisqu'elle se trouve sur la partition système. Évidemment, nous ne pouvons pas simplement placer une application sur la partition système. Cela nécessite root, un bon gestionnaire de fichiers, une connaissance des autorisations... Il serait plus facile d'utiliser ADB. Ce n’est pas la seule façon pour nous d’être une application système, du moins en ce qui concerne la liste noire des API cachées.
Revenez à il y a sept paragraphes lorsque j'ai mentionné la réflexion. Si vous ne savez pas ce qu'est la réflexion, je vous recommande de lire cette page, mais voici un bref résumé. En Java, la réflexion est un moyen d'accéder à des classes, méthodes et champs normalement inaccessibles. C'est un outil incroyablement puissant. Comme je l'ai dit dans ce paragraphe, la réflexion était autrefois un moyen d'accéder à des fonctions non SDK. La liste noire des API bloque l'utilisation de la réflexion, mais elle ne bloque pas l'utilisation de double-réflexion.
Maintenant, c'est là que ça devient un peu bizarre. Normalement, si vous vouliez appeler une méthode cachée, vous feriez quelque chose comme ceci (c'est en Kotlin, mais Java est similaire) :
val someHiddenClass = Class.forName("android.some.hidden.Class")
val someHiddenMethod = someHiddenClass.getMethod("someHiddenMethod", String::class.java)
someHiddenMethod.invoke(null, "some important string")
Cependant, grâce à la liste noire des API, vous obtiendrez simplement une exception ClassNotFoundException. Cependant, si vous y réfléchissez à deux fois, cela fonctionne bien :
val forName = Class::class.java.getMethod("forName", String:: class.java)
val getMethod = Class::class.java.getMethod("getMethod", String:: class.java, arrayOf>()::class.java)
val someHiddenClass = forName.invoke(null, "android.some.hidden.Class") asClass
val someHiddenMethod = getMethod.invoke(someHiddenClass, "someHiddenMethod", String::class.java)
someHiddenMethod.invoke(null, "some important string")
Bizarre, non? Eh bien, oui, mais non aussi. La liste noire des API permet de savoir qui appelle une fonction. Si la source n'est pas exemptée, elle plante. Dans le premier exemple, la source est l’application. Cependant, dans le deuxième exemple, la source est le système lui-même. Au lieu d’utiliser la réflexion pour obtenir directement ce que nous voulons, nous l’utilisons pour dire au système d’obtenir ce que nous voulons. Puisque la source de l’appel à la fonction cachée est le système, la liste noire ne nous affecte plus.
Nous avons donc terminé. Nous avons désormais un moyen de contourner la liste noire des API. C'est un peu maladroit, mais nous pourrions écrire une fonction wrapper pour ne pas avoir à y réfléchir à chaque fois. Ce n'est pas génial, mais c'est mieux que rien. Mais en réalité, nous n’avons pas fini. Il existe une meilleure façon de procéder qui nous permettra d'utiliser une réflexion normale ou un SDK modifié, comme au bon vieux temps.
Étant donné que l'application de la liste noire est évaluée par processus (ce qui est le même que par application dans la plupart des cas), le système pourrait avoir un moyen d'enregistrer si l'application en question est exemptée ou non. Heureusement, il existe et nous y avons accès. Grâce à ce nouveau hack à double réflexion, nous disposons d'un bloc de code à usage unique :
val forName = Class::class.java.getDeclaredMethod("forName", String:: class.java)
val getDeclaredMethod = Class::class.java.getDeclaredMethod("getDeclaredMethod", String:: class.java, arrayOf>()::class.java) val vmRuntimeClass = forName.invoke(null, "dalvik.system.VMRuntime") asClass
val getRuntime = getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null) as Method
val setHiddenApiExemptions = getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", arrayOf(arrayOf<String>()::class.java)) asMethodval vmRuntime = getRuntime.invoke(null)
setHiddenApiExemptions.invoke(vmRuntime, arrayOf("L"))
D'accord, techniquement, cela n'indique pas au système que notre application est exemptée de la liste noire des API. Il existe en fait une autre commande ADB que vous pouvez exécuter pour spécifier les fonctions qui ne doivent pas être mises sur liste noire. C'est ce dont nous profitons ci-dessus. Le code remplace essentiellement tout ce que le système considère comme exempté pour notre application. Passer "L" à la fin signifie que toutes les méthodes sont exemptées. Si vous souhaitez exempter des méthodes spécifiques, utilisez la syntaxe Smali: Landroid/some/hidden/Class; Landroid/some/other/hidden/Class;
.
Maintenant, nous avons réellement terminé. Créez une classe Application personnalisée, mettez ce code dans le onCreate()
méthode, et bam, plus de restrictions.
Merci au membre XDA weishu, le développeur de VirtualXposed et Taichi, pour avoir découvert cette méthode à l'origine. Nous tenons également à remercier topjohnwu, développeur reconnu par XDA, de m'avoir signalé cette solution de contournement. Voici un peu plus sur la façon dont cela fonctionne, même si c'est en chinois. moi aussi a écrit à ce sujet sur Stack Overflow, avec un exemple dans JNI également.