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.

Champs minimaux pour un graphe de dépendances natives
ChampÉléments à enregistrerImpact sur le durcissementMéthode de vérification
Bibliothèque et sourceBibliothèque principale, dépendances transitives, tierces et bibliothèques systèmeDétermine le périmètre modifiable et la responsabilité des tests de régressionDépaqueter et vérifier contre chaque ABI dans l'APK final
Point d'entrée de chargementDépendances statiques, System.loadLibrary ou dlopen à l'exécutionDétermine la première phase d'échec et l'emplacement des journauxEnregistrer 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ésDes symboles manquants peuvent survenir au chargement si la version requise dépasse l'API du dispositifDémarrage sur dispositifs réels ciblés et vérification des symboles
Ordre d'initialisationJNI_OnLoad, constructeurs et initialisation métierLes changements de disposition du code ou de synchronisation peuvent amplifier les dépendances implicitesComparer les chronologies entre les builds non protégés et la candidate de publication (release candidate)
Exceptions et threadsExceptions C++, propriété des threads, utilisation de JNIEnvLes erreurs inter-bibliothèques et inter-threads provoquent souvent des plantages immédiatsCheckJNI, 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.

Points de contrôle clés pour deux méthodes d'enregistrement JNI
Méthode d'enregistrementDépendancesPoints de sensibilité au durcissementVérification minimale
Découverte de nom statiqueNom de classe Java, nom de méthode, signature et symboles exportésRenommage, symboles masqués et incompatibilités de signatureInvoquer chaque point d'entrée critique et vérifier les erreurs de liaison
RegisterNativesRecherche de classe, signatures de méthodes, table d'enregistrement et timing d'initialisationTraitement du nom de classe, ordre d'enregistrement et changements d'adresse de fonctionConfirmer l'achèvement de l'enregistrement et exécuter des scénarios réels d'entrée/sortie
Wrappers tiersRègles internes du SDK et implémentations closed-sourceAuto-validation, exports implicites et différences de versionVé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.

Exemple de matrice d'acceptation ABI
Élément de vérificationarm64-v8aarmeabi-v7ax86_64Règle de décision
Toutes les dépendances présentesVérifier par bibliothèqueVérifier par périmètre de releaseVérifier uniquement si requisBloquer si une dépendance requise manque
Chargeable sur le système minimumAppareil réel API cibleAppareil réel API cibleÉmulateur ou appareilNe pas valider uniquement sur le dernier système
Chemins JNI critiquesEntrée/sortie réelleEntrée/sortie réelleNe peut pas substituer les résultats ARMEnregistrer indépendamment pour chaque ABI publié
Imputabilité des plantagesConserver les symboles correspondantsConserver les symboles correspondantsConserver les symboles correspondantsLes 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.

Format d'enregistrement public sûr pour les groupes de protection native
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-v0

Localiser 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.

Symptômes courants et prochaines étapes de collecte de preuves
Premier symptômeVérifications prioritairesPreuves requisesCe qu'il ne faut pas faire en premier
Échec de dlopenABI, DT_NEEDED, niveau minimal d'API et symbolesListe des bibliothèques et erreurs de chargement correspondant à la candidate de publicationBasculer répétitivement des commutateurs de protection sans rapport
Méthode native introuvableMéthode d'enregistrement, nom de classe, signature de méthode et synchronisationTableau d'enregistrement, règles de conservation et points d'entrée d'appelSe fier uniquement au nombre de symboles exportés
Plantage à la première invocationParamètres, références, threads, exceptions et traitement des fonctionsPile symbolisée, entrées et comparaison avec la base de référenceExpliquer avec des fichiers de symboles d'une autre version
Échec sur certains appareilsAPI système, différences entre fournisseurs, ABI et bibliothèques tiercesMatrice système des appareils et premières divergencesSubstituer 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 jugementBase factuelle ou techniqueLimite 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