Langues: English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Français | Deutsch | Español | Italiano | Русский | العربية
neverd mobile restaure du Java lisible depuis APK, DEX et smali uniquement avec le moteur intégré de NeverD. Les lecteurs, développés indépendamment, partagent un modèle Dalvik typé et un générateur Java au travail borné. Cette fonction CLI expérimentale ne promet la restauration complète de tout APK. Les conteneurs APK et la sortie Java ne sont pas accessibles via le SDK C natif, le SDK des plugins Python, le chargeur graphique ou neverd decompile --language.
Le Java produit est une reconstruction du bytecode. Les commentaires, la mise en forme, les choix propres au langage source d’origine et les identifiants supprimés ne sont pas disponibles ; le bytecode Kotlin produit lui aussi du Java. Une exécution réussie ne prouve pas l’équivalence sémantique et ne garantit pas que chaque méthode se recompilera. Ce traitement ne lance aucune application analysée.
Après avoir compilé NeverD, choisissez un nouveau répertoire de sortie :
neverd mobile app.apk -o recovered-app
neverd mobile classes.dex -o recovered-dex
neverd mobile MainActivity.smali -o recovered-class
neverd mobile decoded/smali -o recovered-javaOuvrez recovered-app/sources/ pour lire les fichiers Java et recovered-app/report.json pour consulter l’inventaire des entrées et les limites. Une entrée de type répertoire est préférable lorsque les classes smali se référencent entre elles.
| Composant | Exigence | Sélection |
|---|---|---|
| NeverD | Compilez la cible neverd avec une chaîne compatible C++20. Le traitement mobile est intégré à la CLI native et n’appelle aucun interpréteur Python. Distribuez l’exécutable avec les bibliothèques natives nécessaires à votre compilation. |
build/bin/neverd / PATH |
La restauration Android utilise uniquement le moteur intégré de NeverD en C++20 et ne nécessite aucun environnement Python ou Java à l’exécution. Il accepte les déclarations et opérations ordinaires représentables de DEX 035, 037–040 et smali. DEX 041, les appels dynamiques tels que invoke-custom, certains chemins d’initialisation, les annotations sémantiques ou opérations inconnues et les identifiants impossibles à exprimer en Java échouent explicitement. Accepter un format ne signifie pas en prendre en charge toutes les instructions ou déclarations.
La résolution des noms Java distingue l’en-tête du corps d’une classe et peut traiter les masquages connus au sein d’un même package ; elle refuse explicitement les cas où l’absence de déclarations d’une superclasse ou d’une interface externe empêche de déterminer les types ou les références du code auxiliaire Java généré, et seul java.lang.Object est supposé ne fournir aucun type membre héritable sans déclaration disponible. Les littéraux à virgule flottante smali sont arrondis directement à la précision simple ou double cible, en préservant la représentation binaire résultante.
Le moteur C++ intégré conserve et valide les métadonnées Signature prises en charge pour les classes, champs et méthodes : variables de type, tableaux, jokers, bornes et masquage au niveau des méthodes. L’effacement doit correspondre à l’identité de la déclaration DEX d’origine. Throws est conservé ; une hiérarchie d’exceptions non prouvée est refusée. L’héritage générique ou la substitution des types membres, la régénération des méthodes ponts, les appels de méthodes génériques, les types internes paramétrés et la correspondance des paramètres cachés des constructeurs restent explicitement non pris en charge sans les preuves nécessaires.
Le marqueur Java 8 @java.lang.Deprecated, sans éléments et visible à l’exécution, est conservé sur les classes, champs, méthodes et constructeurs. Les annotations de paramètres, les initialiseurs statiques annotés, les autres niveaux de visibilité et les valeurs d’éléments tels que since ou forRemoval sont refusés. Après recompilation, la CI compare séparément l’attribut Deprecated du fichier de classe et les annotations visibles à l’exécution, y compris les déclarations témoins sans annotation et les méthodes auxiliaires générées ; ces dernières ne sont pas comptées parmi les méthodes d’origine.
DEX et smali conservent aussi l’annotation de plateforme @android.annotation.SuppressLint sur les classes, champs, méthodes et constructeurs. Elle exige la visibilité build et un unique tableau de chaînes value ; les tableaux et chaînes vides, répétitions, ordre et caractères échappés sont conservés. Les annotations de paramètres et d’initialiseurs statiques restent non prises en charge. La CI utilise le SDK Android pour vérifier les annotations conservées avec CLASS après recompilation et comparer le comportement du programme.
Le moteur intégré conserve aussi @Retention, @Target, @Documented et @Inherited, visibles à l’exécution, sur les déclarations d’annotation prises en charge, et produit un véritable @interface. Ce sous-ensemble ne permet aucun champ, méthode, paramètre de type ou déclaration imbriquée. Les annotations de premier niveau et les annotations membres statiques sont admises si leurs noms, leur accessibilité et leur relation avec le type englobant sont établis ; les portées locales ou anonymes sont refusées.
L’application d’un marqueur vide à une déclaration de classe, d’interface ou d’annotation exige une définition correspondante et accessible dans le même ensemble de classes analysées. Retention et Target doivent autoriser cette application. Une déclaration SOURCE peut être reconstruite, mais son application persistante dans l’entrée est refusée ; CLASS ou l’absence de Retention exige la visibilité DEX build (0), et RUNTIME exige la visibilité runtime (1). L’absence de Retention reste distincte d’un CLASS explicite, comme l’absence de Target reste distincte d’un tableau vide ; l’ordre du tableau Target est conservé. Les valeurs Target sont limitées à Java 8 ; les valeurs plus récentes, telles que MODULE et RECORD_COMPONENT, sont refusées. @Inherited est conservé sans copier les applications héritées comme annotations directes des sous-classes.
Les déclarations d’éléments d’annotation et leurs valeurs par défaut, les applications personnalisées non vides, les marqueurs personnalisés sur les champs/méthodes/paramètres, les définitions d’annotation externes, les conteneurs d’annotations répétables et kotlin.Metadata restent non pris en charge. La définition d’un marqueur est émise comme une déclaration de classe sans méthodes d’éléments ni méthodes auxiliaires, et n’augmente pas le nombre de méthodes récupérées.
La CI traite des exemples Java 8 propres au projet avec D8 et NeverD, recompile tout le Java généré et compare les métadonnées Signature complètes, les résultats de réflexion et le comportement. Ces vérifications ne certifient pas la récupération complète d’applications réelles.
cmake --build build --target neverd
./build/bin/neverd mobile app.apk -o recovered-appEntourez de guillemets les chemins contenant des espaces.
& .\build\bin\neverd.exe mobile .\app.apk -o .\recovered-appUne compilation multiconfiguration peut placer l’exécutable dans build/bin/Release/. Respectez les règles habituelles de déploiement des bibliothèques natives de cette compilation.
La table des entrées ci-dessous décrit la reconstruction. Les modes de requête présentés plus loin ont des périmètres de validation plus restreints.
| Entrée | Comportement | Limite importante |
|---|---|---|
.apk |
Valider le ZIP entier, puis analyser ensemble tous les classes.dex, classes2.dex et autres DEX numérotés à la racine de l’archive |
Code uniquement ; aucun décodage des ressources ou du manifeste |
.dex |
Validation et analyse de DEX 035 ou 037–040 par le lecteur intégré | DEX 041 et les déclarations ou opérations non prises en charge échouent ; renommer ou tronquer un fichier ne produit pas un bytecode valide |
.smali |
Analyser la classe fournie | Les classes voisines référencées ne sont pas chargées implicitement |
| Répertoire smali | Collecter récursivement les fichiers .smali et les analyser ensemble |
Inclure les classes imbriquées et les racines smali dépendantes dans le répertoire d’entrée |
Pour analyser une arborescence d’APK décodé contenant smali/ et smali_classes2/, passez leur répertoire parent commun. Seuls les fichiers .smali parviennent au backend, mais l’arborescence entière est d’abord validée et copiée ; les ressources volumineuses sans rapport avec le code comptent donc aussi dans les limites d’entrée. Un répertoire compact contenant uniquement les racines smali pertinentes réduit le travail.
Les APK fractionnés constituent des entrées séparées. Chaque APK contenant du DEX peut être traité indépendamment, mais cette commande ne fusionne pas un ensemble d’APK ; les fractions constituées uniquement de ressources échouent, car elles ne contiennent aucun DEX à la racine. .aab, .apks, .xapk, .odex, .oat et .vdex ne sont pas acceptés comme entrées mobiles.
Les ressources APK, AndroidManifest.xml, les assets, les bibliothèques JNI/natives et le code téléchargé à l’exécution ne sont pas reconstruits en Java. Extrayez séparément une bibliothèque native .so et utilisez neverd decompile library.so -o library.c. Pour ce traitement statique, les charges utiles chiffrées ou empaquetées doivent déjà être disponibles sous forme de DEX/smali ordinaires ; aucun dépaquetage de protection, aucune connexion à un appareil et aucun contournement de protection ne sont effectués.
neverd mobile app.apk --list-classes
neverd mobile classes.dex --list-classes --class-prefix com.example
neverd mobile app.apk --list-classes --class-prefix Lcom/example/ --json
neverd mobile app.apk --list-classes -o classes.txtCette requête lit les identités des classes sans décoder les corps de méthodes
ni générer de Java. Elle ne nécessite ni répertoire de préparation ni environnement
d’exécution externe. La sortie texte contient un descripteur DEX exact par ligne,
dans l’ordre du répertoire ZIP puis des définitions DEX. --class-prefix accepte
un préfixe de descripteur ou de package avec des points ; la correspondance est
littérale, sans imposer une frontière de package. Les définitions de classe en
double, y compris entre fichiers DEX, échouent explicitement. Les identités de
classe impossibles à représenter sans perte en UTF-8 échouent également.
Sans -o, la requête écrit sur stdout. En mode requête, -o désigne un
nouveau fichier, et non un répertoire de reconstruction. Les fichiers
existants restent intacts. Les résultats sont conservés en mémoire jusqu’à la
réussite de tous les DEX sélectionnés : un DEX malformé rencontré plus tard
empêche toute publication partielle. --json inclut les nombres de classes
correspondantes et totales, le nombre de DEX et
validation_scope: "dex-envelope-and-class-identities".
Tous les noms ZIP, en-têtes, plages et limites de ressources déclarées sont
vérifiés. Seules les charges utiles classes.dex et classesN.dex numérotées
à la racine sont décompressées et vérifiées par CRC ; l’intégrité des autres
ressources reste non vérifiée. Chaque DEX conserve les contrôles d’en-tête,
SHA-1, Adler-32, limites de la map et métadonnées de classe référencées. Les
corps de méthodes et les métadonnées non référencées ne sont pas validés. Il
s’agit d’un inventaire, pas d’un contrôle d’intégrité de l’archive entière ni
d’une preuve de reconstruction Java. DEX 041, les sections de handles de
méthode et de sites d’appel personnalisés restent non pris en charge. Le
parcours habituel de reconstruction complète valide toujours toutes les
charges utiles de l’archive.
--timeout, --max-files et --max-bytes s’appliquent. La limite de classes
compte toutes les définitions de tous les DEX avant filtrage ; les entrées ZIP
non sélectionnées comptent toujours dans les limites de l’archive.
les options iOS et les entrées smali ne sont pas acceptés pour l’inventaire.
Recherchez les opérandes directs des instructions sans générer de Java :
neverd mobile app.apk --find-refs string --query 'login failed' --json
neverd mobile classes.dex --find-refs type --query 'Lcom/example/Service;' --exact
neverd mobile app.apk --find-refs method --query '->connect(' --owner 'Lcom/example/Client;'
neverd mobile app.apk --find-refs field --query 'Lcom/example/State;->ready:Z' --exact -o refs.jsonlLa recherche utilise des sous-chaînes littérales et respecte la casse.
--exact compare la cible entière : contenu d’une chaîne, descripteur de type,
identité de méthode telle que Lpkg/Type;->name(I)V ou identité de champ telle
que Lpkg/Type;->name:I. --owner limite le propriétaire cible des références
de méthode ou de champ à un descripteur exact. Il ne filtre pas la méthode
contenant la référence. Les requêtes vides, les entrées smali et le mélange
d’options de requête et de reconstruction ne sont pas pris en charge ; les
métacaractères d’expression régulière sont des caractères littéraux ordinaires.
Par défaut, la sortie contient un objet JSON compact par occurrence (JSON Lines).
--json produit un rapport avec des compteurs et un tableau references.
Chaque ligne contient dex_entry, l’identité complète de la méthode contenant
la référence dans method, pc_code_units (unités de 16 bits depuis la première
instruction de la méthode), opcode, kind, target_index (local au DEX) et
target. Toutes les occurrences sont conservées, y compris le code partagé
attribué à plusieurs définitions de méthode. Les lignes de chaînes contiennent
aussi les unités exactes target_utf16 ; target vaut null lorsqu’un surrogate
isolé empêche une publication UTF-8 sans perte. Les autres identités doivent
être représentables en UTF-8.
Le scanner utilise les frontières d’instructions, les contrôles d’opérandes,
le traitement des charges utiles et les contrôles de flot de contrôle du
lecteur de reconstruction. Les valeurs immédiates et les données des charges
utiles switch/array ne peuvent pas devenir des références. Il vérifie chaque
corps de code défini, même si aucune cible ne correspond, avant de fixer
code_scan_complete: true. defined_method_count inclut les déclarations
natives et abstraites ; scanned_method_count compte les définitions avec un
corps, et scanned_code_item_count les corps physiques distincts par DEX.
matching_pool_entries compte les cibles correspondantes, même non référencées.
validation_scope: "dex-code-references" couvre l’enveloppe DEX, les tables
d’identifiants, l’appartenance des classes/membres et les données de code,
d’exceptions et de débogage consommées. Les annotations, les valeurs statiques
encodées, les usages limités aux déclarations et les métadonnées non référencées
sont hors de ce périmètre. Ce n’est ni une reconstruction Java ni un vérificateur
ART. Le code non pris en charge et les données consommées malformées échouent
explicitement. La validation APK suit la même frontière des charges utiles
sélectionnées que l’inventaire des classes ; l’intégrité des autres ressources
reste non vérifiée.
Les résultats de tous les DEX sélectionnés sont conservés avant publication,
y compris les vérifications de classes en double entre DEX. L’option -o
désigne un nouveau fichier. --max-files borne séparément les totaux de
définitions de classes, de définitions de méthodes et d’occurrences retournées,
ainsi que les entrées ZIP. --max-bytes borne l’entrée, les données de requête
conservées, la mémoire de travail par corps et la sortie (avec des marges
conservatrices pour l’expansion JSON). Ce sont des budgets d’opération ; le RSS
du processus inclut aussi les tampons d’entrée, le surcoût de l’allocateur et
l’environnement d’exécution. Les budgets habituels de temps et de travail
s’appliquent également.
neverd mobile app.apk -o recovered-app --platform=android \
--timeout=600 --max-files=30000 --max-bytes=4294967296 --json| Option | Valeur par défaut | Signification |
|---|---|---|
-o PATH |
Obligatoire pour la reconstruction | Nouveau répertoire de reconstruction ; dans les deux modes de requête, nouveau fichier de sortie facultatif |
--list-classes |
Désactivé | Rechercher les identités des classes APK/DEX sans reconstruction Java |
--class-prefix PREFIX |
Toutes les classes | Préfixe littéral de descripteur ou avec points ; exige --list-classes |
--find-refs KIND |
Désactivé | Rechercher les références directes d’instructions à une chaîne, un type, une méthode ou un champ |
--query TEXT |
Obligatoire pour les références | Sous-chaîne littérale de l’identité cible |
--exact |
Désactivé | Faire correspondre l’identité cible entière de la référence |
--owner DESCRIPTOR |
Tout propriétaire | Propriétaire cible exact pour les requêtes de méthodes/champs |
--platform=auto|android |
auto |
Choisir Android explicitement ou déduire la plateforme de l’entrée |
--timeout N |
300 |
Budget de temps d’analyse positif en secondes |
--max-files N |
20000 |
Limite positive du nombre d’entrées, répertoires créés compris |
--max-bytes N |
2147483648 |
Limite positive en octets pour l’entrée, les données extraites et la sortie finale |
--json |
Désactivé | Afficher un rapport JSON ; les requêtes de références produisent sinon du JSON Lines |
Une sélection --arch autre que la valeur par défaut, --artifact, --metadata-only et un --max-func non nul sont réservés à iOS et refusés pour Android ; --arch=auto explicite est accepté.
Les budgets de fichiers et d’octets restent applicables aux entrées, aux données extraites et à la sortie finale. Les lecteurs et le générateur intégrés vérifient aussi une quantité de travail bornée et le temps écoulé. Augmenter une limite ne désactive pas les autres.
recovered-app/
sources/ packages et classes Java restaurés
metadata/android-methods.json couverture des méthodes du moteur intégré
report.json inventaire versionné et limites
Les entrées temporaires sont supprimées. Des classes imbriquées peuvent partager le fichier de leur classe englobante : le nombre de fichiers Java n’est donc pas celui des classes DEX. Les méthodes générées peuvent employer une boucle de répartition Java ; elles n’exécutent pas le DEX original et ne l’appellent pas via une passerelle d’exécution.
Le rapport intégré contient android_method_recovery, également écrit dans metadata/android-methods.json, et conserve chaque méthode d’origine. L’invariant est method_count = recovered_method_count + projected_method_count + declaration_only_method_count + unrecovered_method_count ; un projected_method_count absent vaut zéro, et unrecovered_method_count reste nul avant publication. Les méthodes native et abstract d’origine portent le statut declaration-only et ne comptent pas comme corps restaurés.
Un sous-ensemble de classes locales nommées sans capture peut être émis dans la méthode statique exacte qui les contient. Il exige une méthode ordinaire à types scalaires, une classe sans champs héritant directement d’Object, un véritable constructeur sans argument, des méthodes d’instance scalaires et des usages d’objets dont l’absence d’échappement hors du périmètre pris en charge est vérifiée. Les classes anonymes, captures, modificateurs non pris en charge et usages non prouvés échouent explicitement.
Ces méthodes locales et leur méthode englobante reçoivent source-projected, avec projection_kind: "named-method-local". La couverture reste partial même si le rapport global indique success. Les noms binaires et les indicateurs d’accès après recompilation restent non vérifiés. Le compilateur Java pouvant choisir un autre nom binaire local, class_source_bindings conserve la classe d’origine, la méthode englobante exacte, le chemin source et le nom local avec binary_name_status: "unverified".
generated_source_helpers inventorie les méthodes supplémentaires avec les types exacts throw-helper, constant-helper, default-constructor et field-initializer. Ce dernier désigne un <clinit> supplémentaire généré qui ne figurait pas dans l’inventaire des méthodes d’origine. Ces ajouts ne comptent pas dans le total des méthodes d’origine. Une compilation réussie ou une concordance ponctuelle des noms ne constitue pas une restauration complète. L’exemple abrégé suivant ne contient aucune méthode projetée :
{
"schema_version": 1,
"status": "success",
"platform": "android",
"source": "app.apk",
"input_kind": "apk",
"backend": {
"name": "neverd",
"version": "1",
"execution": "builtin"
},
"input_code_files": [
"classes.dex",
"classes2.dex"
],
"dex_count": 2,
"smali_count": 0,
"java_source_count": 2,
"java_sources": [
"sources/example/Main.java",
"sources/example/Peer.java"
],
"logs": [],
"android_method_recovery": {
"schema_version": 1,
"status": "recovered",
"class_count": 2,
"method_count": 6,
"recovered_method_count": 5,
"declaration_only_method_count": 1,
"unrecovered_method_count": 0
}
}input_kind vaut apk, dex, smali ou smali-directory. input_code_files répertorie les noms de bytecode ou les chemins smali en entrée, tandis que java_sources et logs sont relatifs à la racine de sortie. source est le nom de base de l’entrée. Les rapports réels contiennent d’autres limites de reconstruction ; conservez-les lorsque vous transmettez les résultats à d’autres outils.
Pour l’automatisation, vérifiez le code de sortie du processus avant d’exploiter status et, si vous redirigez stdout, placez le rapport hors du nouveau répertoire de sortie :
neverd mobile app.apk -o recovered-app --json > recovery-result.jsonLa CLI native renvoie zéro en cas de succès et une valeur non nulle en cas d’échec. Avec --json, les erreurs traitées comprennent schema_version, status: "error" et error. L’analyse des arguments, le démarrage de l’exécutable ou des bibliothèques natives et les interruptions peuvent être signalés uniquement sur stderr. Vérifiez d’abord le code de sortie.
La publication est transactionnelle : la sortie existante est préservée et les résultats temporaires d’un échec sont supprimés. Les opérations non prises en charge, flux de registres non résolus, déclarations impossibles à représenter, traitements d’exceptions mal formés et budgets épuisés font échouer le moteur intégré au lieu de publier des corps manquants. La réussite ne prouve pas l’équivalence sémantique.
| Symptôme | Action |
|---|---|
| DEX, instruction, déclaration ou initialisation non pris en charge | Lire le diagnostic et vérifier le sous-ensemble accepté |
| Entrée invalide ou classe dupliquée | Corriger le bytecode ou l’ensemble de classes ; aucun corps non pris en charge n’est omis silencieusement |
| Délai ou budget dépassé | Réduire l’entrée ou ajuster --timeout, --max-files et --max-bytes selon les ressources disponibles |
| Sortie déjà présente | Choisir un nouveau répertoire de sortie |
Python sert uniquement aux scripts de test de développement ci-dessous ; la récupération mobile intégrée s’exécute dans la CLI native C++20.
cmake --build build --target check-neverd-mobile
ctest --test-dir build -L NeverDMobileTests --output-on-failure
python3 scripts/test_mobile_android_internal.py --d8 PATH --neverd build/bin/neverdLes tests des composants et de la CLI vérifient l’analyse, les contrats de sortie et le nettoyage après échec. Le test d’exécution interne utilise un JDK (java et javac) et D8 pour construire des exemples DEX/APK indépendants, puis compiler et exécuter le Java restauré. Ce sont des dépendances de test, pas des prérequis de restauration intégrée. Exécutez-le sur la compilation actuelle et examinez les résultats avant de déclarer un cas vérifié. La réussite d’exemples ne démontre pas la restauration complète de toute application.
Consultez la présentation mobile pour le parcours iOS associé.