Android - Obfuscation et Désobfuscation
C’est un fait, décompiler une application android (le fichier apk) pour en examiner le code source est une chose plutôt simple. Il existe de nombreux outils permettant de le faire et c’est logique car, grossièrement, l’apk n’est qu’une archive contenant le code source de l’application et les différentes ressources utilisées dans celle-ci (images, fichiers de langue, layouts de vue…). Les motivations pour cela peuvent être nombreuses et variées et ce n’est d’ailleurs pas très important car, à partir du moment où c’est faisable, des personnes le feront.
Le petit problème, c’est qu’en tant que développeur qui a passé de longs mois à bosser sur son appli, on n’a pas forcément envie de tout se faire copier par le premier venu. Donc pour tenter de contrer cela il est possible d’activer l’obfuscation, c’est une phase supplémentaire qui vient s’éxecuter lors de la phase de build.
Et c’est assez simple, il faut juste le déclarer dans le fichier gradle :
buildTypes {
release {
minifyEnabled true
proguardFiles 'proguard-rules.pro'
}
}
Le processus de build va donc avoir une étape supplémentaire. Mais comme rien n’est jamais aussi simple, il faut faire attention à correctement configurer son ficher proguard (qui décrit les règles à appliquer lors de l’obfuscation) afin de ne pas nuire au bon fonctionnement de son application.
La doc officielle de Google en parle, tout comme de nombreux articles de blogs.
Une fois que tout ce travail de configuration est terminé, que notre code source de prod est correctement obfusqué, la partie intéressante commence : les stacktraces…
Les crashs remontés par store sont en fait le résultat d’une stacktrace qui a été générée car une erreur est survenue pendant l’exécution du code de l’application sur un device utilisateur. Or, si le code source présent sur le device d’un utilisateur est obfusqué, la stacktrace qui est générée le sera aussi.
Même si on arrive à identifier certaines lignes ou actions, ça ne sera pas très pratique à débugger car les numéros de ligne ou les noms de classe ne sont pas toujours pertinents.
Ici, impossible de trouver dans quelle classe s’est produite l’erreur car je ne sais pas a quoi correspond ni a
ni b
.
Mais tout n’est pas perdu. Il y a une section dans le PlayStore qui se nomme Deobfuscation files. Elle va nous permettre d’ajouter un fichier de mapping qui va faire le lien entre le code obfusqué et le code réel tel qu’il est dans mon IDE.
Ce fichier est généré automatiquement lors de la phase de build. Il se trouve normalement là :
/app/build/outputs/mapping/release/mapping.txt
et ressemble à ça (en beaucoup plus long) :
android.arch.core.internal.FastSafeIterableMap -> android.arch.a.a.a:
java.util.HashMap mHashMap -> a
void <init>() -> <init>
boolean contains(java.lang.Object) -> a
android.arch.core.internal.SafeIterableMap -> android.arch.a.a.b:
android.arch.core.internal.SafeIterableMap$Entry mStart -> a
android.arch.core.internal.SafeIterableMap$Entry mEnd -> b
java.util.WeakHashMap mIterators -> c
int mSize -> d
void <init>() -> <init>
int size() -> a
java.util.Iterator iterator() -> iterator
java.util.Iterator descendingIterator() -> b
android.arch.core.internal.SafeIterableMap$IteratorWithAdditions iteratorWithAdditions() -> c
java.util.Map$Entry eldest() -> d
java.util.Map$Entry newest() -> e
boolean equals(java.lang.Object) -> equals
java.lang.String toString() -> toString
android.arch.core.internal.SafeIterableMap$Entry access$100(android.arch.core.internal.SafeIterableMap) -> a
android.arch.core.internal.SafeIterableMap$1 -> android.arch.a.a.b$1:
android.arch.core.internal.SafeIterableMap$AscendingIterator -> android.arch.a.a.b$a:
void <init>(android.arch.core.internal.SafeIterableMap$Entry,android.arch.core.internal.SafeIterableMap$Entry) -> <init>
android.arch.core.internal.SafeIterableMap$Entry forward(android.arch.core.internal.SafeIterableMap$Entry) -> a
android.arch.core.internal.SafeIterableMap$DescendingIterator -> android.arch.a.a.b$b:
void <init>(android.arch.core.internal.SafeIterableMap$Entry,android.arch.core.internal.SafeIterableMap$Entry) -> <init>
android.arch.core.internal.SafeIterableMap$Entry forward(android.arch.core.internal.SafeIterableMap$Entry) -> a
Lorsque qu’il est ajouté dans le PlayStore les stacktraces redeviennent intéligibles. Voici donc la même erreur mais avec le fichier de mapping uploadé :
Attention cela ne fonctionne que pour les stacktraces survenues après l’ajout du ficher, ce n’est malheureusement pas rétroactif.
D’où l’importance d’ajouter ce fichier à chaque nouvelle release de l’application pour ne rien rater.
Et pour faire cela on va avoir plusieurs solutions :
-
la première est de se connecter au PlayStore et d’aller dans la bonne section pour ajouter le fichier à la main. Même si ça va fonctionner, il faut espérer ne pas oublier de le faire à chaque fois. Et cela rend le processus de livraison encore plus fastidieux, pas franchement génial.
-
la deuxième solution est donc de le faire automatiquement. Sachant que l’on utilise déjà des outils pour faire l’upload des apk en automatique et pour ne pas avoir à se connecter à l’interface, ça semble être une bonne idée. Ces outils sont Fastlane et le plugin Gradle play-publisher
-
Avec le plugin gradle, c’est plutôt simple, depuis la version 1.2.0, le fichier est automatiquement envoyé sur le store lorsque l’obfusctation est activée.
-
Avec fastlane, c’est à peine un peu plus compliqué, il faut préciser dans le supply le chemin du fichier de mapping
-
supply(
mapping: './app/build/outputs/mapping/release/mapping.txt'
)
Et lors de l’appel à la lane
correspondante au déploiement on peut voir dans les logs que le fichier est bien trouvé et uploadé
+-------------------------+-------------------------------------------------+
| Summary for supply 2.98.0 |
+-------------------------+-------------------------------------------------+
| track | alpha |
| apk | ./app/build/outputs/apk/release/app-release.apk |
| mapping | ./app/build/outputs/mapping/release/mapping.txt |
+-------------------------+-------------------------------------------------+
Si on n’est pas sûr de son coup on retourner sur le PlayStore dans la section Deobfuscation files pour valider que tout s’est bien passé.
Et voilà, maintenant que l’activation de l’obfuscation et la desobfuscation des stacktraces n’est plus un problème, il ne reste plus qu’à les corriger ;)