Conclusions et conditions de décision
- Enregistrer les dépendances directes et transitives, les points d'entrée de chargement, le moment d'initialisation, les API cibles minimales et les appelants pour chaque SO.
- La découverte statique des noms JNI et l'enregistrement dynamique via RegisterNatives imposent des contraintes différentes ; vérifiez la méthode de liaison effective avant de traiter les noms.
- arm64-v8a, armeabi-v7a et x86_64 constituent des artefacts et des matrices de compatibilité distincts ; un succès sur une architecture ne s'extrapole pas aux autres.
- L'acceptation du niveau de protection et celle de la stabilité à l'exécution doivent être liées au même APK final, à la même signature et au même ensemble de SO.
Cartographier d'abord le graphe de dépendances ELF et la chronologie de chargement réelle
Une application Android peut comprendre des bibliothèques principales, des bibliothèques de logique métier, des bibliothèques algorithmiques, des SDK tiers et des bibliothèques système. Le code Java ou Kotlin peut invoquer directement System.loadLibrary, tandis que les bibliothèques principales peuvent charger d'autres bibliothèques via des dépendances ELF ou un chargement à l'exécution. Le point de plantage ultime peut se situer en dehors de la bibliothèque protégée, car la résolution des symboles et l'ordre d'initialisation sont déterminés par toute la chaîne de dépendances.
Le graphe de dépendances doit enregistrer, par ABI, les fichiers de bibliothèques, les relations DT_NEEDED, les niveaux d'API système minimaux, les points d'entrée de chargement, les fonctions d'initialisation, les interfaces exportées et les appelants. De plus, indiquez si les bibliothèques sont développées en interne, des dépendances open source ou des SDK propriétaires, car cela définit les limites de modification et la responsabilité des tests de régression.
La documentation d'Android NDK stipule que les symboles natifs sont généralement résolus lors du chargement de la bibliothèque. Si le code référence une API absente sur le système cible, dlopen peut échouer immédiatement, même si une branche conditionnelle à l'exécution suggère que ce chemin de code ne serait pas exécuté. Par conséquent, la version minimale d'API NDK et la version du système du dispositif doivent être incluses dans le graphe de dépendances.
| Champ | Éléments à enregistrer | Impact sur le durcissement | Méthode de vérification |
|---|---|---|---|
| Bibliothèque et source | Bibliothèque principale, dépendances transitives, tierces et bibliothèques système | Détermine le périmètre modifiable et la responsabilité des tests de régression | Dépaqueter et vérifier contre chaque ABI dans l'APK final |
| Point d'entrée de chargement | Dépendances statiques, System.loadLibrary ou dlopen à l'exécution | Détermine la première phase d'échec et l'emplacement des journaux | Enregistrer la chronologie de démarrage et les résultats de chargement |
| Version minimale d'API (API Floor) | APP_PLATFORM de compilation et symboles système utilisés | Des symboles manquants peuvent survenir au chargement si la version requise dépasse l'API du dispositif | Démarrage sur dispositifs réels ciblés et vérification des symboles |
| Ordre d'initialisation | JNI_OnLoad, constructeurs et initialisation métier | Les changements de disposition du code ou de synchronisation peuvent amplifier les dépendances implicites | Comparer les chronologies entre les builds non protégés et la candidate de publication (release candidate) |
| Exceptions et threads | Exceptions C++, propriété des threads, utilisation de JNIEnv | Les erreurs inter-bibliothèques et inter-threads provoquent souvent des plantages immédiats | CheckJNI, piles symbolisées et tests de régression métier |
L'enregistrement statique versus dynamique définit les limites de traitement des symboles
JNI peut découvrir les méthodes natives via des conventions de nommage ou établir des correspondances entre les méthodes Java et les adresses de fonctions en utilisant RegisterNatives lors de l'initialisation. L'enregistrement statique repose sur des noms exportés prévisibles ; l'enregistrement dynamique nécessite généralement d'exposer uniquement quelques points d'entrée d'initialisation, mais dépend de la recherche de classes, des signatures de méthodes, du moment de l'enregistrement et du thread de chargement.
Si le durcissement ou le traitement des symboles modifie les noms d'enregistrement statique, le système peut échouer à localiser l'implémentation native. De même, des changements dans les signatures de méthodes, les règles de conservation des noms de classes ou l'ordre d'initialisation dans la table d'enregistrement dynamique peuvent provoquer des échecs avant la première invocation. La simple vérification du nombre de symboles exportés ne prouve pas que les liaisons JNI restent correctes.
Les directives officielles Android JNI avertissent également que JNIEnv est soumis à des contraintes de thread ; les exceptions en attente, les références invalides et l'utilisation inter-thread peuvent entraîner des plantages. Les tests de régression post-durcissement doivent couvrir les threads réels et les chemins d'exception, et non se limiter à invoquer une méthode de démonstration sans paramètre.
| Méthode d'enregistrement | Dépendances | Points de sensibilité au durcissement | Vérification minimale |
|---|---|---|---|
| Découverte de nom statique | Nom de classe Java, nom de méthode, signature et symboles exportés | Renommage, symboles masqués et incompatibilités de signature | Invoquer chaque point d'entrée critique et vérifier les erreurs de liaison |
| RegisterNatives | Recherche de classe, signatures de méthodes, table d'enregistrement et timing d'initialisation | Traitement du nom de classe, ordre d'enregistrement et changements d'adresse de fonction | Confirmer l'achèvement de l'enregistrement et exécuter des scénarios réels d'entrée/sortie |
| Wrappers tiers | Règles internes du SDK et implémentations closed-source | Auto-validation, exports implicites et différences de version | Vérifier contre les matrices de support fournisseur et les scénarios réels |
- Lister les mappings clés Java-vers-Native
- Confirmer la méthode d'enregistrement statique ou dynamique
- Conserver les règles nécessaires de nom de classe et de signature
- Vérifier les exceptions, les threads et les cycles de vie des références
L'ABI doit être accepté comme artefact de release indépendant
L'ABI est plus qu'une étiquette de répertoire. La documentation officielle d'Android NDK spécifie que l'ABI définit le jeu d'instructions, l'endianness, les conventions d'appel, l'utilisation de la pile et des registres, le format de fichier exécutable et la décoration de noms C++. arm64-v8a et armeabi-v7a diffèrent par leur code machine et leurs dépendances ; réussir sur un émulateur x86_64 ne représente pas un succès sur de vrais appareils ARM.
Pour chaque ABI, confirmez que toutes les dépendances directes et transitives existent, que le plancher API de la bibliothèque est compatible avec le système cible et que les outils d'empaquetage n'ont pas supprimé par erreur des fichiers pour une architecture spécifique. Si seule la bibliothèque principale inclut arm64-v8a alors qu'une dépendance tierce manque de l'artefact correspondant, l'application échouera toujours lors du chargement.
Si le produit prévoit de prendre en charge uniquement un sous-ensemble d'ABIs, indiquez-le explicitement dans les notes de version, les configurations des magasins d'applications et les périmètres de test. Les architectures non buildables, non installées ou où les chemins JNI critiques n'ont pas été exécutés doivent être marquées comme non couvertes ; la réussite de la compilation ne peut se substituer à une preuve d'exécution.
| Élément de vérification | arm64-v8a | armeabi-v7a | x86_64 | Règle de décision |
|---|---|---|---|---|
| Toutes les dépendances présentes | Vérifier par bibliothèque | Vérifier par périmètre de release | Vérifier uniquement si requis | Bloquer si une dépendance requise manque |
| Chargeable sur le système minimum | Appareil réel API cible | Appareil réel API cible | Émulateur ou appareil | Ne pas valider uniquement sur le dernier système |
| Chemins JNI critiques | Entrée/sortie réelle | Entrée/sortie réelle | Ne peut pas substituer les résultats ARM | Enregistrer indépendamment pour chaque ABI publié |
| Imputabilité des plantages | Conserver les symboles correspondants | Conserver les symboles correspondants | Conserver les symboles correspondants | Les symboles doivent correspondre au même release candidate |
Quelles conditions d'exécution native la protection affecte-t-elle ?
Le masquage de symboles réduit les indices statiques, le traitement des chaînes diminue l'exposition en clair, et la virtualisation du flux de contrôle ou des fonctions altère la forme d'exécution du code sélectionné. Ces techniques impactent différemment les symboles exportés, les limites de fonctions, le déballage d'exceptions, les appels indirects, l'alignement et les performances. Moins de symboles après traitement ne représente pas une protection native complète, pas plus que la capacité de lire la bibliothèque n'annule tous les effets de protection.
L'initialisation au démarrage, la gestion des signaux, les exceptions C++, les rappels, l'état local aux threads, la recherche réflexive de symboles et l'auto-validation des SDK tiers sont des zones à haute sensibilité. Lors de la définition du périmètre, privilégiez les fonctions métier aux entrées/sorties claires, à la fréquence d'appel mesurable et aux défaillances isolables. Évitez de traiter toute la chaîne d'initialisation lors du premier PoC.
Les configurations de protection doivent consigner la justification, la phase d'appel, l'ABI, les dépendances, le budget de performance et le groupe de rollback pour chaque fonction ou groupe de fonctions. Ce qui suit illustre uniquement le format d'une liste de vérification publique et sûre, excluant les symboles internes ou la syntaxe de configuration produit.
native_group: protocol-core-v1
abis: [arm64-v8a, armeabi-v7a]
load_phase: post-authentication
jni_registration: dynamic
execution:
frequency: bounded
main_thread: false
dependencies:
- business-runtime
- system-crypto
acceptance:
- dependency-resolution
- jni-registration
- exception-path
- output-parity
- latency-budget
rollback: native-group-v0Localiser les plantages après le durcissement SO en identifiant la première divergence
Lors du dépannage, fixez d'abord l'identité du fichier, la signature, l'appareil, le système, la méthode d'installation et les entrées métier pour la base de référence non protégée et la candidate de publication (release candidate). Recherchez ensuite dans la chronologie la première divergence : création du processus, démarrage de l'application, chargement de bibliothèque, JNI_OnLoad, fin de l'enregistrement, première invocation native ou retour métier clé. La dernière entrée du journal de plantage peut n'être qu'une réaction en chaîne.
Une UnsatisfiedLinkError indique généralement des bibliothèques manquantes, des incompatibilités d'ABI, des problèmes de dépendances ou des erreurs de résolution de symboles ; les erreurs d'enregistrement peuvent se manifester par l'absence de méthodes natives ; un JNIEnv incorrect, des références erronées ou des exceptions en attente peuvent provoquer un plantage lors des invocations métier. Conservez les symboles natifs correspondant à la candidate de publication pour l'attribution interne, mais n'exposez ni les vrais symboles ni les adresses dans les rapports publics.
CheckJNI aide à détecter certains mauvais usages de JNI, mais c'est un outil de diagnostic, non une représentation de l'état d'exécution en production, et il ne couvre pas tous les problèmes de mémoire native et de concurrence. Après les corrections, vous devez réexécuter les chemins critiques sous la configuration de publication cible.
| Premier symptôme | Vérifications prioritaires | Preuves requises | Ce qu'il ne faut pas faire en premier |
|---|---|---|---|
| Échec de dlopen | ABI, DT_NEEDED, niveau minimal d'API et symboles | Liste des bibliothèques et erreurs de chargement correspondant à la candidate de publication | Basculer répétitivement des commutateurs de protection sans rapport |
| Méthode native introuvable | Méthode d'enregistrement, nom de classe, signature de méthode et synchronisation | Tableau d'enregistrement, règles de conservation et points d'entrée d'appel | Se fier uniquement au nombre de symboles exportés |
| Plantage à la première invocation | Paramètres, références, threads, exceptions et traitement des fonctions | Pile symbolisée, entrées et comparaison avec la base de référence | Expliquer avec des fichiers de symboles d'une autre version |
| Échec sur certains appareils | API système, différences entre fournisseurs, ABI et bibliothèques tierces | Matrice système des appareils et premières divergences | Substituer les conclusions sur appareil réel par la réussite sur émulateur |
Les conclusions de publication doivent couvrir la robustesse, la compatibilité et la maintenabilité
L'observation de la robustesse statique peut enregistrer les changements de symboles, de chaînes, de structure de code et de la surface d'exposition des points d'entrée clés ; la validation runtime vérifie le chargement, JNI, la logique métier critique, les exceptions et les ABI cibles. Ces deux types de preuves se complètent ; aucune ne peut remplacer l'autre.
La candidate de publication finale doit également compléter les mises à jour d'installation, la vérification de signature, les contrôles de canal, les tests de démarrage, la surveillance des plantages, l'archivage des symboles natifs et les exercices de rollback. Les archives de symboles doivent permettre de trouver la version correspondante via l'identité du fichier ; sinon, les plantages natifs en ligne ne peuvent être correctement symbolisés.
Cet article fournit un cadre d'inspection technique, non des conclusions de test pour un SO ou une configuration de protection spécifique. Sans paquet cible, ABI cible et chemins d'appel réels, on ne peut qu'évaluer si les travaux de préparation sont complets, sans promettre compatibilité ou performance.
- Chaque ABI publié dispose de preuves d'exécution
- Les points d'entrée JNI critiques couvrent les chemins normaux et d'exception
- Les symboles natifs correspondent à l'identité de la candidate finale
- Les résultats de chargement, métier et de rollback sont reproductibles
- Les appareils et systèmes non couverts sont explicitement restreints
Limites des preuves et de l’applicabilité
Cette section sépare les faits documentés sur la plate-forme, le jugement technique et les limites qui ne peuvent pas être généralisées à des allégations de produit non vérifiées.
| Article jugement | Base factuelle ou technique | Limite d'applicabilité |
|---|---|---|
| L'ABI doit être acceptée comme une dimension d'artefact indépendante. | Le NDK Android officiel définit l'ABI couvrant les jeux d'instructions, les conventions d'appel, les registres, la pile, le format ELF et la dénomination des symboles. | Les définitions de la documentation ne prouvent pas que l'application inclut toutes les dépendances ou s'exécute avec succès sur les appareils cibles. |
| Les références aux API natives supérieures au système de l'appareil peuvent échouer lors du chargement. | La documentation sur les problèmes courants du NDK indique que les symboles sont généralement résolus au chargement de la bibliothèque ; référencer des API inexistantes ne peut être contourné par un branchement à l'exécution. | Les échecs spécifiques doivent toujours être confirmés via les paramètres de build, les dépendances et les journaux de l'appareil. |
| Les erreurs JNI nécessitent une validation au niveau des threads, des références et des exceptions. | Les lignes directrices Android JNI listent des problèmes tels qu'un JNIEnv invalide, des références incorrectes, des exceptions en attente et un enregistrement de méthodes défaillant, pouvant provoquer des plantages. | CheckJNI aide uniquement à détecter un sous-ensemble de problèmes et ne peut remplacer les tests complets de logique métier et de sécurité mémoire. |
| Les résultats provenant d'un seul émulateur x86_64 ne peuvent être extrapolés aux appareils réels ARM. | Différentes ABI utilisent des jeux d'instructions et des conventions d'appel distincts ; les combinaisons finales de bibliothèques et de dépendances peuvent également différer. | Si le produit exclut explicitement la publication d'une certaine ABI, elle peut être marquée comme non applicable plutôt que testée de force. |
| La force de protection et la compatibilité doivent être validées sur la même version candidate (release candidate). | Reconstruire ou remplacer des fichiers SO modifie le code, les symboles, les dépendances et le comportement potentiel à l'exécution. | Ceci est un principe de gouvernance des artefacts et n'implique pas qu'un niveau de protection spécifique a été validé. |
Questions d'ingénierie
S'il n'existe qu'un seul SO principal, un graphe de dépendances est-il encore nécessaire ?
Oui. Le SO principal peut toujours dépendre de bibliothèques système, d'exécuteurs C++ ou de bibliothèques tierces et interagir avec la couche Java via JNI. Le graphe de dépendances confirme le point de chargement le plus précoce et les limites de responsabilité.
Si arm64-v8a passe les tests, dois-je encore tester armeabi-v7a ?
Si le paquet de publication inclut et prend en charge armeabi-v7a, une vérification indépendante est requise. Les instructions, les conventions d'appel et les artefacts de dépendance pour ces deux ABI diffèrent et ne peuvent se substituer l'un à l'autre.
Masquer tous les symboles exportés est-il plus sûr ?
Pas nécessairement. Les points d'entrée JNI statiques nécessaires, les appels tiers et les conventions système peuvent reposer sur des symboles exportés. Confirmez d'abord les méthodes d'enregistrement et d'appel, puis minimisez l'exposition.
Puis-je publier si CheckJNI ne signale aucune erreur ?
Non, cela seul est insuffisant pour la publication. Vous devez également vérifier la configuration cible réelle, les entrées métier, les chemins d'exception, l'ABI, la portée du système, les mises à jour d'installation et les capacités de retour arrière.
Dois-je désactiver immédiatement toute protection après un plantage lié au durcissement (hardening) d'un SO ?
Fixez d'abord l'identité de la version candidate et identifiez la première différence. Vous pouvez effectuer une recherche binaire sur les groupes de protection ou revenir en arrière, mais ne modifiez qu'une seule variable enregistrable à la fois pour éviter de générer de nouveaux artefacts non attribuables.
Vous voulez tester cela sur votre propre application ?
Soumettez la version candidate, les systèmes cibles et les chemins commerciaux critiques pour une évaluation Yudun PoC et de compatibilité.
Continuez avec: Liste de contrôle de durcissement et de compatibilité native SO