Lors d'un audit de sécurité ou d'un test d'intrusion visant une application mobile Android, intercepter les flux réseau chiffrés HTTPS entre le client et l'API backend représente l'étape préliminaire indispensable. Qu'il s'agisse d'analyser des applications bancaires, des mécanismes d'authentification ou des échanges de données propriétaires, les chercheurs en sécurité doivent pouvoir observer et modifier les requêtes en transit. Cependant, les développeurs déploient de plus en plus fréquemment des mécanismes d'épinglage de certificats SSL/TLS (Certificate Pinning) pour contrer l'interception de type Man-in-the-Middle (MITM).
Bien que l'épinglage de certificats constitue une protection précieuse contre la compromission d'autorités de certification tierces ou les réseaux Wi-Fi publics hostiles, il ne garantit en aucun cas l'intégrité de l'application face à un auditeur ou un attaquant disposant d'un accès physique complet au terminal mobile. Android étant un système ouvert, toute vérification exécutée côté client peut être observée, instrumentée et neutralisée.
Dans ce guide technique approfondi, l'équipe de sécurité mobile de Cayvora détaille le fonctionnement de l'épinglage SSL sous Android, puis présente quatre méthodes éprouvées de contournement : le patch statique d'APK, l'instrumentation dynamique automatisée avec Objection, l'injection de scripts Frida ciblés pour OkHttp3, et le hooking natif en assembleur ARM64 pour les frameworks modernes tels que Flutter intégrant BoringSSL. Enfin, nous aborderons les mesures de durcissement recommandées pour les équipes de développement.
1. Qu'est-ce que l'Épinglage de Certificat SSL/TLS (Pinning) ?
Dans une négociation HTTPS standard sous Android, le système d'exploitation vérifie l'identité du serveur distant via la hiérarchie classique de l'infrastructure à clés publiques (PKI). L'application initie une session TLS, le serveur présente sa chaîne de certificats X.509, et le système Android s'assure que la chaîne remonte jusqu'à une autorité de certification racine de confiance préinstallée dans le magasin système (/system/etc/security/cacerts/).
Si un auditeur installe un certificat d'autorité racine personnalisée (comme celui de PortSwigger Burp Suite ou OWASP ZAP) dans le magasin utilisateur de l'appareil, les flux HTTPS peuvent être interceptés et déchiffrés sans avertissement. Pour bloquer cette interception, les concepteurs d'applications mettent en place le Certificate Pinning (ou Public Key Pinning).
Plutôt que d'accorder sa confiance aveugle à n'importe quelle autorité présente dans le magasin de l'OS, l'application vérifie formellement que le certificat ou la clé publique du serveur distant correspond à une empreinte cryptographique (hash SHA-256) codée en dur dans l'application. Si le certificat présenté lors du handshake TLS ne concorde pas avec l'empreinte épinglée, la connexion est immédiatement rompue avec une exception telle que javax.net.ssl.SSLHandshakeException: CertPathValidatorException.
2. Implémentations Courantes du Pinning sous Android
L'épinglage de certificats se déploie à différents niveaux architecturaux :
A. Configuration de Sécurité Réseau Android (Android 7.0+)
Introduite avec l'API level 24 (Android 7.0), la ressource network_security_config.xml permet de définir des règles réseau déclaratives sans modifier le code source Java/Kotlin. Par défaut, Android ignore les certificats utilisateur pour les applications ciblées, et permet d'épingler explicitement des clés publiques :
<network-security-config>
<domain-config>
<domain includeSubdomains="true">api.cayvora.com</domain>
<pin-set expiration="2027-01-01">
<pin digest="SHA-256">k2v657xBsOVe1PQR/JU7tVu5prsS400NZP1IwTUqlMY=</pin>
</pin-set>
</domain-config>
</network-security-config>
B. OkHttp3 CertificatePinner
La bibliothèque OkHttp développée par Square gère la couche réseau de la grande majorité des applications Android. OkHttp intègre une classe dédiée nommée CertificatePinner :
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("api.cayvora.com", "sha256/k2v657xBsOVe1PQR/JU7tVu5prsS400NZP1IwTUqlMY=")
.build();
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build();
Lors de l'établissement du flux TLS, la méthode interne CertificatePinner.check(hostname, peerCertificates) parcourt la chaîne de certificats et lève une exception SSLPeerUnverifiedException si aucune correspondance n'est constatée.
C. TrustKit-Android et X509TrustManager Personnalisé
Des bibliothèques telles que TrustKit automatisent la validation et les rapports d'échec d'épinglage. D'autres applications implémentent directement l'interface Java X509TrustManager, en redéfinissant la méthode checkServerTrusted() pour comparer manuellement les clés ou empreintes du serveur avec des constantes internes.
3. Méthode 1 — Contournement Statique par Patch de l'APK
Lorsque l'application ciblée ne dispose pas de contrôles d'intégrité stricts ou de détection d'anti-altération, le patch statique constitue la méthode la plus pérenne. Elle consiste à décompiler le paquet APK, modifier sa configuration réseau, puis le recompiler et le signer.
Étape 1 : Décompilation avec apktool
Désassemblez l'APK pour extraire les ressources et le bytecode smali :
apktool d target_app.apk -o app_src/
Étape 2 : Modification du fichier network_security_config.xml
Consultez le répertoire app_src/res/xml/. Si le fichier network_security_config.xml existe, modifiez-le pour accorder confiance aux certificats ajoutés par l'utilisateur et supprimer les blocs <pin-set> :
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>
Si aucun fichier de configuration réseau n'existe, créez le fichier ci-dessus à l'emplacement app_src/res/xml/network_security_config.xml et liez-le dans app_src/AndroidManifest.xml au sein de la balise <application> :
<application
android:name="com.example.app.MyApplication"
android:networkSecurityConfig="@xml/network_security_config" ... >
Étape 3 : Recompilation et Signature de l'APK
Recompilez l'archive APK à l'aide d'apktool :
apktool b app_src/ -o patched_unsigned.apk
Les versions récentes d'Android refusent catégoriquement l'installation de paquets non signés. On utilise l'outil uber-apk-signer pour aligner le fichier ZIP et lui appliquer automatiquement des signatures v1, v2 et v3 valides :
java -jar uber-apk-signer.jar --apks patched_unsigned.apk
Le fichier résultant patched_unsigned-aligned-debugSigned.apk peut alors être déployé via ADB (adb install -r ...). Une fois lancée sur un appareil doté du certificat mandataire (proxy CA), l'application achemine son trafic vers Burp Suite en clair.
4. Méthode 2 — Contournement Dynamique avec Objection
Si l'application dispose de protections d'intégrité, de ressources obfusquées ou de vérifications cryptographiques sur sa signature, l'application patchée refusera de s'exécuter. L'instrumentation dynamique permet d'intercepter la mémoire vive du processus sans modifier les fichiers sur le disque, sur un terminal rooté ou un émulateur.
Objection est une boîte à outils d'exploration d'applications mobiles basée sur le framework d'instrumentation binaire Frida. Elle offre des commandes intuitives pour neutraliser les contrôles de sécurité à chaud.
Lancement de l'application sous Objection
Assurez-vous que le serveur frida-server s'exécute sur le terminal Android avec les privilèges root :
adb root
adb push frida-server /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server && /data/local/tmp/frida-server &"
Lancez l'application via Objection en utilisant le mode d'amorçage au démarrage (spawn) pour intercepter le trafic dès les premières requêtes :
objection --gadget com.example.app explore
Désactivation de l'épinglage SSL en une commande
Dans la console interactive Objection, saisissez simplement :
android sslpinning disable
Objection injecte alors dynamiquement des hooks JavaScript ciblant les principales classes de validation Java :
- Interception de
TrustManagerImpl.verifyChain()pour valider silencieusement toute chaîne de certificats. - Interception de
OkHttpClient.Builder.certificatePinner()pour substituer un pinner vide. - Neutralisation des vérificateurs de noms d'hôtes dans
HttpsURLConnection. - Désactivation des implémentations de confiance pour Apache HttpClient, WebViews et divers SDKs tiers.
5. Méthode 3 — Scripts Frida Personnalisés pour OkHttp3
Dans les applications critiques, les développeurs implémentent parfois des variantes personnalisées d'OkHttp3 où les classes standard ont été renommées ou modifiées. Lorsque les outils automatisés comme Objection échouent, le recours à un script Frida sur mesure offre une précision chirurgicale sur le flux d'exécution.
Hooking direct de OkHttp3 CertificatePinner.check()
Le point névralgique d'OkHttp3 se trouve dans la méthode check() de la classe okhttp3.CertificatePinner. Cette méthode prend deux paramètres : le nom d'hôte (String) et la liste des certificats du serveur (List). Si l'épinglage échoue, elle lève une SSLPeerUnverifiedException ; si la vérification réussit, elle retourne simplement void.
En remplaçant l'implémentation de cette méthode pour la forcer à retourner immédiatement sans vérifier ni lever d'exception, nous désactivons totalement l'épinglage :
Java.perform(function() {
try {
var CertificatePinner = Java.use("okhttp3.CertificatePinner");
CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function(hostname, peerCertificates) {
console.log('[+] Bypassed OkHttp3 CertificatePinner for: ' + hostname);
return; // Retourne void sans lever d'exception
};
console.log('[*] Hook OkHttp3 CertificatePinner installe avec succes.');
} catch (err) {
console.log('[-] OkHttp3 CertificatePinner non trouve ou obfusque : ' + err);
}
});
Gestion de l'obfuscation de code (ProGuard / R8)
Lorsque le code est obfusqué avec ProGuard ou R8, la classe okhttp3.CertificatePinner est renommée en un nom arbitraire (ex. a.b.c.d). Nous exploitons alors l'API de réflexion de Frida pour inspecter dynamiquement les classes en mémoire et identifier celles possédant une méthode correspondant à la signature (Ljava/lang/String;Ljava/util/List;)V :
Java.perform(function() {
Java.enumerateLoadedClasses({
onMatch: function(className) {
if (className.indexOf("android.") === -1 && className.indexOf("java.") === -1) {
try {
var clazz = Java.use(className);
var methods = clazz.class.getDeclaredMethods();
methods.forEach(function(m) {
if (m.getName() === "check" && m.getParameterTypes().length === 2) {
clazz.check.overload('java.lang.String', 'java.util.List').implementation = function(h, p) {
console.log('[+] Pinner obfusque detecte et neutralise dans : ' + className + ' pour ' + h);
return;
};
}
});
} catch(e) {}
}
},
onComplete: function() { console.log('[*] Enumeration des classes terminee.'); }
});
});
Injectez le script au lancement de l'application via la ligne de commande Frida :
frida -U -f com.example.app -l bypass_okhttp.js
6. Méthode 4 — Épinglage au Niveau Natif (BoringSSL / Flutter)
L'essor des technologies cross-platform natives, en particulier Google Flutter, a profondément modifié le paysage de la sécurité mobile. Les applications Flutter sont compilées sous forme de bibliothèques partagées natives ARM (libapp.so et libflutter.so). Comme Flutter n'achemine pas ses communications HTTP à travers l'environnement d'exécution Android Java (ART), tous les hooks basés sur le runtime Java (comme network_security_config.xml ou TrustManagerImpl) sont totalement inopérants.
Flutter embarque sa propre version compilée de la bibliothèque cryptographique BoringSSL de Google au sein de libflutter.so. La validation des certificats s'effectue intégralement en C/C++ compilé.
Identification de la fonction de vérification dans BoringSSL
Dans BoringSSL, la routine chargée de valider la chaîne de certificats se nomme généralement ssl_crypto_x509_session_verify_cert_chain (ou session_verify_cert_chain). En code source C, cette fonction retourne un entier : 1 en cas de succès de la vérification, et 0 (ou une valeur négative) en cas d'échec.
En désassemblant libflutter.so dans Ghidra ou IDA Pro, les analystes repèrent cette fonction en recherchant des chaînes de caractères caractéristiques comme "ssl_client" ou "x509_ctx", ou par signature d'instructions assembleur ARM64.
Écriture d'un Hook Natif ARM64 sous Frida
Dès que l'adresse de la fonction ou son motif d'octets est localisé dans libflutter.so, nous utilisons l'API native Interceptor de Frida pour intercepter son exécution au niveau des instructions processeur :
// Hook Natif ARM64 pour Flutter / BoringSSL
function hookFlutterPinning() {
var m = Process.findModuleByName("libflutter.so");
if (!m) {
console.log("[-] libflutter.so pas encore charge, nouvel essai...");
setTimeout(hookFlutterPinning, 500);
return;
}
// Exemple de symbole ou offset pour ARM64 libflutter.so
var targetSymbol = "ssl_crypto_x509_session_verify_cert_chain";
var verifyAddr = Module.findExportByName("libflutter.so", targetSymbol);
if (verifyAddr) {
Interceptor.attach(verifyAddr, {
onEnter: function(args) {
console.log("[+] Appel a la fonction de verification BoringSSL intercepte");
},
onLeave: function(retval) {
console.log("[+] Valeur de retour originale : " + retval + ", remplacement par 1 (SUCCES)");
retval.replace(ptr(1)); // Force la valeur de retour a 1
}
});
console.log("[*] Hook natif actif sur " + targetSymbol);
} else {
console.log("[-] Symbole non exporte, recours au balayage par motif de signature...");
}
}
hookFlutterPinning();
En modifiant la valeur de retour dans le registre processeur X0 pour lui attribuer la valeur 1 à la sortie de la fonction, la négociation TLS native se poursuit comme si le certificat avait été validé, rendant possible l'interception complète des flux Flutter via Burp Suite.
7. Durcissement Défensif : Conception d'Architectures Mobiles Résilientes
Comme le démontrent ces quatre approches, l'épinglage de certificats ne constitue pas une barrière infranchissable. Dès lors qu'un terminal est rooté ou soumis à un émulateur contrôlé par l'auditeur, le client mobile ne peut être considéré comme un environnement de confiance absolu. Néanmoins, l'application de principes de défense en profondeur élève considérablement la difficulté technique requise pour contourner les protections.
1. Stratégie d'épinglage multicouche
Ne dépendez jamais d'une unique couche logicielle :
- Configurez les règles déclaratives dans
network_security_config.xmlcomme base minimale. - Implémentez l'épinglage
CertificatePinnerdans OkHttp au niveau du code, en associant toujours des clés de secours pour permettre le renouvellement des certificats sans bloquer le parc applicatif en production. - Ajoutez des routines de vérification croisée en code natif C/C++ via l'interface JNI pour valider de manière asynchrone les paramètres de la connexion TLS.
2. Détection de Root, d'Altération et d'Environnements d'Instrumentation
Les techniques de contournement dynamique nécessitent la présence de serveurs d'instrumentation ou de binaires spécifiques. L'application doit valider son environnement avant d'émettre des requêtes sensibles :
- Détection de Root : Recherche des binaires de privilèges (
/system/bin/su,/data/local/tmp/su), des builds de test et des montages Magisk ou KernelSU. - Détection de Frida : Surveillance de
/proc/self/mapspour déceler la présence defrida-agent.so, vérification des ports d'écoute locaux (ex.27042) et du champTracerPiddans/proc/self/status. - Attestation Matérielle : Intégration de l'API Google Play Integrity pour attester que l'application s'exécute sur un appareil physique officiel non altéré.
3. Obfuscation avancée du code (R8 et Déstructuration de Contrôle)
Configurez R8 ou ProGuard pour supprimer systématiquement les noms de classes, les signatures de méthodes et les informations de débogage. L'emploi de solutions de durcissement professionnelles intégrant le chiffrement des chaînes et l'aplatissement du graphe de contrôle (Control Flow Flattening) complique fortement l'identification des points d'ancrage en rétro-ingénierie.
Conclusion
L'épinglage SSL constitue une mesure défensive essentielle pour protéger les données des utilisateurs contre les attaques de type Man-in-the-Middle sur les réseaux non fiables. Toutefois, les professionnels de la cybersécurité et les développeurs doivent garder à l'esprit que la sécurité d'une application ne doit jamais reposer uniquement sur des vérifications côté client. En combinant le patch statique, les outils automatisés comme Objection, les scripts Frida sur mesure et le hooking natif BoringSSL, les équipes de sécurité évaluent efficacement les APIs backend sous-jacentes pour y déceler les vulnérabilités les plus critiques.