Les applications Android sont distribuées sous forme de fichiers APK — Android Package Kit — et comprendre leur structure interne est indispensable pour tout praticien de la sécurité mobile. Que vous soyez testeur d'intrusion évaluant une application tierce, développeur cherchant à durcir votre propre produit ou chercheur en sécurité traquant des vulnérabilités, maîtriser la rétro-ingénierie des APK vous confère un avantage décisif. Ce guide parcourt l'ensemble du processus : comprendre le contenu d'un APK, utiliser la chaîne d'outils pour le désassembler et le décompiler, identifier les vulnérabilités typiquement découvertes, et mettre en place les défenses qui y remédient.
1. Anatomie d'un APK
Un APK est simplement une archive ZIP. Il suffit de le renommer en .zip et de le décompresser pour révéler la structure suivante :
- classes.dex — Le bytecode Dalvik compilé qui s'exécute sur le runtime ART d'Android. Les applications volumineuses peuvent comporter
classes2.dex,classes3.dex, etc. (multidex). - AndroidManifest.xml — Un fichier XML encodé en binaire déclarant chaque composant exposé par l'application : Activities, Services, BroadcastReceivers et ContentProviders. Il déclare aussi les permissions requises et la version SDK minimale.
- resources.arsc — Une table de ressources compilée associant des identifiants de ressources (entiers) à leurs valeurs : chaînes, layouts, drawables.
- lib/ — Bibliothèques partagées natives (fichiers ELF
.so) compilées pour des cibles ABI telles quearmeabi-v7a,arm64-v8aetx86_64. - assets/ — Fichiers bruts embarqués avec l'application : HTML, JavaScript pour les WebViews, fichiers de configuration, parfois des bases de données embarquées.
- META-INF/ — Le certificat de signature (
CERT.RSA), le condensat du manifeste (MANIFEST.MF) et le fichier de signature (CERT.SF). C'est ainsi qu'Android vérifie l'authenticité du paquetage.
Comprendre cette structure indique précisément où chercher : les identifiants ont tendance à se dissimuler dans assets/ ou en dur dans classes.dex ; les secrets dans le code natif se trouvent dans les fichiers .so ; l'exposition des composants est déclarée dans AndroidManifest.xml.
2. La Chaîne d'Outils de Rétro-ingénierie
La rétro-ingénierie Android moderne repose sur quelques outils éprouvés qui se complètent mutuellement :
apktool
apktool décode l'APK en smali (assembleur Dalvik) et reconstitue le fichier AndroidManifest.xml lisible par l'homme ainsi que les ressources. C'est la première étape de presque toute analyse.
apktool d app.apk -o output/
Le répertoire output/smali/ résultant contient des représentations au niveau assembleur de chaque classe. Elles sont lisibles mais verbeuses — jadx est plus pratique pour l'analyse de la logique applicative.
jadx
jadx décompile directement le bytecode .dex en code source Java, rendant la logique métier lisible en un coup d'œil.
jadx -d jadx_out/ app.apk
jadx propose également une interface graphique (jadx-gui) offrant recherche, références croisées et navigation — indispensable pour les grandes bases de code.
dex2jar + JD-GUI
Une alternative plus ancienne mais encore utilisée : dex2jar convertit le .dex en fichier .jar, que JD-GUI affiche ensuite en Java. Il gère certaines obfuscations différemment de jadx et peut servir de contre-vérification.
d2j-dex2jar app.apk -o app.jar
jd-gui app.jar
strings sur les bibliothèques natives
Les binaires natifs .so embarquent souvent des secrets codés en dur que l'obfuscation de la couche Java n'atteint pas. Exécuter strings dessus révèle fréquemment des points de terminaison API, des identifiants et des clés cryptographiques.
strings lib/arm64-v8a/libcore.so | grep -iE "key|secret|token|pass|api"
adb logcat
L'analyse des journaux à l'exécution capture ce que l'application fait réellement lors de son exécution. De nombreux développeurs laissent des informations sensibles dans les journaux de débogage qui ne sont jamais supprimés des builds de production.
adb logcat -s "MyApp" | grep -iE "token|auth|password"
3. Ce que Recherchent les Testeurs d'Intrusion
Secrets codés en dur
Après décompilation, on effectue une recherche par motif dans le répertoire de sortie pour détecter des identifiants embarqués. Les découvertes dans le monde réel sont surprenamment fréquentes :
// Exemple de ce que les pentesters trouvent lors de la décompilation
private static final String API_KEY = "sk-prod-1234567890abcdef";
private static final String DB_PASSWORD = "P@ssw0rd123!";
private static final String FIREBASE_SECRET = "AIzaSy...";
Ces chaînes survivent à la décompilation intactes car les constantes de chaîne Java compilent verbatim dans le pool de constantes .dex. L'obfuscation renomme les classes et les méthodes mais chiffre rarement les littéraux de chaîne par défaut.
Composants exportés
Tout composant déclaré avec android:exported="true" dans le manifeste et dépourvu de permission personnalisée peut être invoqué par n'importe quelle application installée ou via adb — sans interaction utilisateur. Les testeurs les recensent immédiatement :
apktool d app.apk -o out/ && grep -n 'exported="true"' out/AndroidManifest.xml
Une Activity exportée qui gère des deeplinks sensibles sans validation constitue un chemin direct vers le contournement de l'authentification ou le vol de données.
Stockage de données non sécurisé
Android propose les SharedPreferences, les bases de données SQLite et le stockage interne/externe. Les erreurs courantes incluent le stockage de mots de passe ou de jetons dans les SharedPreferences (lisibles en monde réel sur des appareils rootés), l'utilisation de bases SQLite sans chiffrement, et l'écriture de données sensibles sur le stockage externe où d'autres applications peuvent les lire.
// Non sécurisé : stocker un jeton de session dans des SharedPreferences non chiffrées
SharedPreferences prefs = getSharedPreferences("auth", MODE_WORLD_READABLE);
prefs.edit().putString("token", sessionToken).apply();
Cryptographie faible
La décompilation révèle souvent des anti-patrons cryptographiques : DES ou 3DES là où AES-256 est approprié ; MD5 ou SHA-1 pour le hachage de mots de passe au lieu de bcrypt ou Argon2 ; AES en mode ECB qui laisse transparaître les motifs du texte clair ; vecteurs d'initialisation statiques ou prévisibles.
Journalisation non sécurisée
La classe Log d'Android est lisible mondialement par toute application détenant READ_LOGS sur les anciennes versions de l'API. Des instructions comme les suivantes constituent une fuite de données :
Log.d("AuthManager", "User token: " + token);
Log.v("Payment", "Card number: " + cardNumber);
4. Contournement du SSL Pinning
L'épinglage de certificat SSL/TLS empêche l'interception en refusant les connexions lorsque le certificat du serveur ne correspond pas à une clé publique ou à un hash de certificat embarqué. Le contourner est un préalable à l'inspection du trafic chiffré avec un proxy tel que Burp Suite.
Contournement basé sur Frida
Frida est un kit d'instrumentation dynamique qui injecte du JavaScript dans un processus en cours d'exécution, accrochant toute fonction Java ou native à l'exécution. L'invocation standard pour une session d'analyse active :
frida -U -f com.target.app --no-pause -l bypass-ssl.js
Un script Frida bien connu pour contourner le pinning OkHttp3 accroche la méthode CertificatePinner.check() et la rend sans effet :
Java.perform(function() {
var CertificatePinner = Java.use('okhttp3.CertificatePinner');
CertificatePinner.check.overload('java.lang.String', 'java.util.List')
.implementation = function(hostname, peerCertificates) {
console.log('[*] SSL pinning contourné pour : ' + hostname);
// retour sans exception — vérification de l'épingle désactivée
};
});
Objection
Objection encapsule Frida dans un explorateur en ligne de commande et automatise les tâches courantes telles que le contournement du SSL pinning, le contournement de la détection root et l'exploration du système de fichiers :
objection -g com.target.app explore
# Puis dans l'invite objection :
android sslpinning disable
5. Analyse Dynamique avec Frida
Au-delà du SSL pinning, Frida permet une manipulation complète à l'exécution de la logique applicative. Les testeurs l'utilisent pour accrocher les vérifications d'authentification, extraire les clés cryptographiques de la mémoire au moment de leur utilisation, et tracer les flux de données sensibles.
Un exemple pratique : accrocher une méthode de connexion pour enregistrer les identifiants et forcer le succès de l'authentification quelle que soit la réponse du serveur :
Java.perform(function() {
var LoginActivity = Java.use('com.example.app.LoginActivity');
LoginActivity.checkPassword.implementation = function(pass) {
console.log('[*] checkPassword appelé avec : ' + pass);
// Contournement de la vérification — retourne toujours true
return true;
};
LoginActivity.validateOtp.implementation = function(otp) {
console.log('[*] OTP intercepté : ' + otp);
return true;
};
});
Ce schéma révèle les identifiants en clair et démontre que la logique d'authentification ne doit jamais être considérée comme fiable côté client.
6. Vulnérabilités Clés dans les APK Réels
Mauvaise configuration Firebase
De nombreuses applications embarquent la configuration Firebase dans google-services.json — un fichier intégré à l'APK et facilement extrait. Si les règles de sécurité Firestore ou Realtime Database du projet Firebase autorisent les lectures non authentifiées, tout attaquant récupérant ce fichier obtient l'accès aux données utilisateurs. Les testeurs trouvent régulièrement des bases de données avec des règles équivalentes à : "read": true, "write": true.
Détournement de deeplinks via des Activities exportées
Si une Activity exportée gère des schémas intent-filter (deeplinks personnalisés comme myapp://reset-password?token=XYZ) sans imposer de vérification de paquetage, une application malveillante peut enregistrer le même schéma et intercepter le deeplink, volant des jetons de réinitialisation de mot de passe ou des codes OAuth.
Tapjacking et attaques par superposition
Sur les anciennes versions d'Android, une superposition malveillante (une fenêtre semi-transparente dessinée par-dessus une application cible) peut inciter les utilisateurs à appuyer sur des éléments d'interface qu'ils ne voient pas — accordant des autorisations, soumettant des formulaires ou confirmant des paiements. La défense est l'attribut XML filterTouchesWhenObscured ou setFilterTouchesWhenObscured(true) dans le code.
7. Défenses
Obfuscation ProGuard / R8
Activez R8 dans votre build de production. Il réduit, optimise et obfusque le bytecode, renommant les classes et méthodes en noms d'un seul caractère et supprimant le code mort. Bien qu'il ne soit pas impénétrable, il augmente considérablement le coût de l'analyse et empêche l'extraction banale d'identifiants.
Épinglage de certificat
Implémentez l'épinglage avec le CertificatePinner d'OkHttp ou la bibliothèque TrustKit-Android. Épinglez sur les hashes de clé publique des CA intermédiaires (pas les certificats feuilles) pour survivre à la rotation des certifications, et incluez toujours un épingle de sauvegarde :
CertificatePinner pinner = new CertificatePinner.Builder()
.add("api.votreapp.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.add("api.votreapp.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=") // sauvegarde
.build();
Android Keystore pour les clés cryptographiques
Ne codez jamais des clés cryptographiques en dur dans le code source. Le système Android Keystore stocke les clés dans un stockage sécurisé matériel et n'expose jamais le matériel de clé brut au processus de l'application. Les clés peuvent être liées à une exigence d'authentification utilisateur (biométrie ou PIN) avant utilisation.
Supprimer les journaux de débogage dans les builds de production
Utilisez des règles ProGuard pour supprimer tous les appels Log.* dans les builds de production, ou adoptez une bibliothèque de journalisation (Timber, par exemple) qui supprime les journaux sous un niveau configuré dans les variantes non-debug.
Restreindre l'exposition des composants
Chaque composant dans AndroidManifest.xml doit explicitement déclarer android:exported="false" sauf s'il y a une raison délibérée de l'exposer. Pour les composants devant être exportés (gestionnaires de deeplinks, cibles de partage), exigez une permission de niveau signature personnalisée afin que seules vos propres applications puissent les invoquer sans interaction utilisateur explicite.
Chiffrer le stockage local
Utilisez la bibliothèque Jetpack Security (EncryptedSharedPreferences, EncryptedFile) pour chiffrer de manière transparente les données sensibles au repos. Pour les bases de données, SQLCipher fournit un chiffrement AES-256 avec des changements d'API minimaux.
Conclusion
La rétro-ingénierie des APK Android est une discipline mature avec une chaîne d'outils bien établie et un ensemble prévisible de classes de vulnérabilités. Les mêmes problèmes — secrets codés en dur, composants exportés, stockage non sécurisé, cryptographie faible, absence d'épinglage — apparaissent de façon répétée dans des applications de toute taille et catégorie. Comprendre comment les attaquants approchent un APK est le chemin le plus direct pour concevoir des applications qui résistent à cette analyse.
Chez Cayvora, notre équipe de sécurité mobile effectue des évaluations complètes d'APK couvrant l'analyse statique, l'instrumentation dynamique, l'inspection du trafic réseau et les tests d'API backend. Si votre application Android gère des données utilisateur sensibles ou des transactions financières, un audit professionnel avant la mise en production n'est pas optionnel — c'est le coût d'une exploitation responsable.