Skip to content

Latest commit

 

History

History
299 lines (228 loc) · 23.6 KB

File metadata and controls

299 lines (228 loc) · 23.6 KB

Lingue: English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Français | Deutsch | Español | Italiano | Русский | العربية

Ricostruzione Java per Android

← Indice della documentazione

neverd mobile recupera Java leggibile da APK, DEX e smali usando esclusivamente il motore integrato di NeverD. I lettori, implementati in modo indipendente, condividono un modello Dalvik tipizzato e un generatore Java con lavoro limitato. Questa funzione CLI sperimentale non promette il recupero completo di qualsiasi APK. I contenitori APK e l’output Java non sono disponibili tramite SDK C nativo, SDK dei plugin Python, caricatore GUI o neverd decompile --language.

Il Java prodotto è una ricostruzione del bytecode. Commenti, formattazione, scelte del linguaggio sorgente originale e identificatori rimossi non sono disponibili; anche il bytecode Kotlin produce Java. Un’esecuzione riuscita non dimostra l’equivalenza semantica e non garantisce che ogni metodo possa essere ricompilato. Questo flusso non avvia le applicazioni analizzate.

Avvio rapido

Dopo aver compilato NeverD, scegli una nuova directory di output:

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-java

Apri recovered-app/sources/ per leggere i file Java e recovered-app/report.json per consultare l’inventario degli input e i limiti. Se le classi smali si richiamano tra loro, è preferibile fornire una directory.

Preparazione degli ambienti di esecuzione

Componente Requisito Selezione
NeverD Compilare il target neverd con una toolchain compatibile con C++20. Il flusso mobile è integrato nella CLI nativa e non richiama un interprete Python. Distribuire l’eseguibile con le librerie native richieste dalla propria compilazione. build/bin/neverd / PATH

Il recupero Android usa esclusivamente il motore integrato di NeverD in C++20 e non richiede runtime Python o Java. Accetta dichiarazioni e operazioni comuni rappresentabili di DEX 035, 037–040 e smali. DEX 041, chiamate dinamiche come invoke-custom, alcuni percorsi di inizializzazione, annotazioni semantiche od operazioni sconosciute e identificatori non esprimibili in Java falliscono esplicitamente. Accettare un formato non significa supportarne tutte le istruzioni e dichiarazioni.

La risoluzione dei nomi Java distingue l’intestazione dal corpo della classe e può gestire i casi noti di oscuramento dei nomi nello stesso package; rifiuta esplicitamente i casi in cui mancano dichiarazioni di superclassi o interfacce esterne e non è possibile determinare i tipi o i riferimenti del codice ausiliario Java generato, assumendo soltanto per java.lang.Object, anche senza una dichiarazione disponibile, l’assenza di tipi membro ereditabili. I letterali in virgola mobile di smali vengono arrotondati direttamente alla precisione singola o doppia di destinazione, preservando la rappresentazione in bit risultante.

Il motore C++ integrato conserva e verifica i metadati Signature supportati di classi, campi e metodi, comprese variabili di tipo, array, wildcard, limiti di tipo e oscuramento dei nomi a livello di metodo. La cancellazione dei tipi deve corrispondere all’identità della dichiarazione DEX originale. Throws viene conservato; una gerarchia di eccezioni non dimostrabile viene rifiutata. Ereditarietà generica o sostituzione dei tipi dei membri, rigenerazione dei metodi ponte, chiamate a metodi generici, tipi interni parametrizzati e corrispondenza dei parametri nascosti dei costruttori restano esplicitamente non supportati senza le prove necessarie.

Il marcatore Java 8 @java.lang.Deprecated, privo di elementi e visibile a runtime, viene conservato su classi, campi, metodi e costruttori. Il motore rifiuta le annotazioni dei parametri, gli inizializzatori statici annotati, gli altri livelli di visibilità e i valori di elementi come since o forRemoval. Dopo la ricompilazione, la CI confronta separatamente l’attributo Deprecated del file di classe e le annotazioni a runtime, comprese le dichiarazioni di controllo non annotate e i metodi ausiliari generati; questi ultimi restano esclusi dal conteggio dei metodi originali.

DEX e smali conservano anche l’annotazione di piattaforma @android.annotation.SuppressLint su classi, campi, metodi e costruttori. Richiede visibilità build e un unico array di stringhe value; array e stringhe vuoti, valori ripetuti, ordine e caratteri con escape vengono mantenuti. Le annotazioni di parametri e inizializzatori statici restano non supportate. La CI usa l’SDK Android per verificare le annotazioni con conservazione CLASS dopo la ricompilazione e confrontare il comportamento del programma.

Il motore integrato conserva anche @Retention, @Target, @Documented e @Inherited, visibili a runtime, sulle dichiarazioni di annotazione supportate, ed emette un vero @interface. Questo sottoinsieme non ammette campi, metodi, parametri di tipo o dichiarazioni annidate. Sono supportate le annotazioni di primo livello e le annotazioni membro statiche con nomi, accessibilità e relazione con il tipo contenitore verificabili; gli ambiti locali o anonimi vengono rifiutati.

L’applicazione di un marcatore vuoto a una dichiarazione di classe, interfaccia o annotazione richiede una definizione corrispondente e accessibile nello stesso insieme di classi analizzate. Retention e Target devono consentire l’applicazione effettiva. Si può ricostruire una dichiarazione SOURCE, ma la sua applicazione persistente nell’input viene rifiutata; CLASS o l’assenza di Retention richiede la visibilità DEX build (0), mentre RUNTIME richiede la visibilità runtime (1). L’assenza di Retention resta distinta da CLASS esplicito, così come l’assenza di Target resta distinta da un array vuoto; l’ordine dell’array Target viene conservato. I valori Target sono limitati a Java 8; valori successivi come MODULE e RECORD_COMPONENT vengono rifiutati. @Inherited viene conservato senza copiare le applicazioni ereditate come annotazioni dirette delle sottoclassi.

Le dichiarazioni degli elementi delle annotazioni e i relativi valori predefiniti, le applicazioni personalizzate non vuote, i marcatori personalizzati su campi/metodi/parametri, le definizioni di annotazione esterne, i contenitori di annotazioni ripetibili e kotlin.Metadata restano non supportati. La definizione di un marcatore viene emessa come dichiarazione di classe senza metodi degli elementi o metodi ausiliari e non aumenta il numero di metodi recuperati.

La CI elabora esempi Java 8 propri del progetto con D8 e NeverD, ricompila tutto il Java generato e confronta i metadati Signature completi, i risultati della reflection e il comportamento. Queste verifiche non certificano il recupero completo di applicazioni reali.

Linux e macOS

cmake --build build --target neverd

./build/bin/neverd mobile app.apk -o recovered-app

Racchiudere tra virgolette i percorsi con spazi.

Windows PowerShell

& .\build\bin\neverd.exe mobile .\app.apk -o .\recovered-app

Le compilazioni multiconfigurazione possono collocare l’eseguibile in build/bin/Release/. Seguire i normali requisiti di distribuzione delle librerie native di quella compilazione.

Input supportati e confini

La tabella degli input seguente descrive il recupero. Le modalità query descritte più avanti hanno ambiti di convalida più ristretti.

Input Comportamento Limite importante
.apk Convalidare l’intero ZIP, quindi analizzare insieme tutti i classes.dex, classes2.dex e i successivi DEX numerati nella radice dell’archivio Solo codice; nessuna decodifica di risorse o manifest
.dex Validazione e analisi di DEX 035 o 037–040 tramite il lettore integrato DEX 041 e dichiarazioni od operazioni non supportate falliscono; rinominare o troncare un file non produce bytecode valido
.smali Analizzare la classe fornita Le classi adiacenti referenziate non vengono caricate implicitamente
Directory smali Raccogliere ricorsivamente i file .smali e analizzarli insieme Includere le classi annidate e le radici smali dipendenti nella directory di input

Per analizzare un albero di APK decodificato che contiene smali/ e smali_classes2/, passa la loro directory padre comune. Solo i file .smali arrivano al backend, ma prima viene convalidato e copiato l’intero albero fornito; anche gli asset voluminosi non pertinenti contribuiscono quindi ai limiti di input. Una directory compatta che contiene solo le radici smali rilevanti riduce il lavoro.

Gli APK suddivisi sono input separati. Ogni APK che contiene DEX può essere elaborato indipendentemente, ma questo comando non unisce un insieme di APK; le parti contenenti solo risorse falliscono perché non hanno DEX nella radice. .aab, .apks, .xapk, .odex, .oat e .vdex non sono accettati come input mobili.

Le risorse APK, AndroidManifest.xml, gli asset, le librerie JNI/native e il codice scaricato durante l’esecuzione non vengono ricostruiti in Java. Estrai separatamente una libreria nativa .so e usa neverd decompile library.so -o library.c. Per questo flusso statico, i payload cifrati o protetti da un packer devono essere già disponibili come normali DEX/smali; non vengono eseguiti rimozione del packing, collegamento a dispositivi o aggiramento delle protezioni.

Inventario rapido delle classi

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

Questa query legge le identità delle classi senza decodificare i corpi dei metodi né generare Java. Non richiede directory di preparazione o ambienti di esecuzione esterni. L’output testuale contiene un descrittore DEX esatto per riga, nell’ordine della directory ZIP e poi delle definizioni DEX. --class-prefix accetta un prefisso di descrittore o di package con punti; esegue un confronto letterale del prefisso, senza verificare i confini del package. Le definizioni duplicate, anche tra file DEX, falliscono esplicitamente. Falliscono anche le identità delle classi che non possono essere rappresentate senza perdita in UTF-8.

Senza -o, la query scrive su stdout. In modalità query, -o indica un nuovo file, non una directory di recupero. I file esistenti restano intatti. I risultati vengono accumulati finché tutti i DEX selezionati non superano i controlli: un DEX malformato incontrato dopo gli altri impedisce quindi la pubblicazione di un inventario parziale. --json include il numero di classi corrispondenti e totali, il numero di DEX e validation_scope: "dex-envelope-and-class-identities".

Vengono controllati tutti i nomi ZIP, gli header, gli intervalli e i limiti di risorse dichiarati. Solo i payload classes.dex e classesN.dex numerati nella radice vengono decompressi e verificati tramite CRC; l’integrità dei payload delle risorse non pertinenti non viene verificata. Ogni DEX conserva i controlli su header, SHA-1, Adler-32, limiti della mappa e metadati delle classi referenziati. I corpi dei metodi e i metadati non referenziati non vengono convalidati. Questa è una query di inventario, non un controllo di integrità dell’intero archivio né una prova di recupero Java. DEX 041 e le sezioni method-handle e custom-call-site restano non supportati. Il normale percorso di recupero completo continua a convalidare ogni payload dell’archivio.

Si applicano --timeout, --max-files e --max-bytes. Il limite delle classi conta tutte le definizioni tra i DEX prima del filtro; le voci ZIP non selezionate contribuiscono comunque ai limiti dell’archivio. L’operazione di inventario non accetta opzioni iOS o input smali.

Query sui riferimenti nel codice

Trova gli operandi diretti delle istruzioni senza generare 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.jsonl

La corrispondenza cerca sottostringhe letterali e distingue maiuscole e minuscole. --exact confronta l’intera destinazione: contenuto della stringa, descrittore di tipo, identità di metodo come Lpkg/Type;->name(I)V oppure identità di campo come Lpkg/Type;->name:I. --owner limita il proprietario della destinazione dei riferimenti a metodi o campi a un descrittore esatto. Non filtra il metodo che contiene il riferimento. Query vuote, input smali e combinazioni di opzioni di query e recupero non sono supportati; i metacaratteri delle espressioni regolari sono normali caratteri letterali.

L’output predefinito contiene un oggetto JSON compatto per occorrenza (JSON Lines). --json restituisce un report con conteggi e un array references. Ogni riga registra dex_entry, il method contenitore completo, pc_code_units (unità di 16 bit dalla prima istruzione del metodo), opcode, kind, target_index (locale al DEX) e target. Tutte le occorrenze vengono conservate, incluso il codice condiviso attribuito a più definizioni di metodo. Le righe delle stringhe contengono anche le unità target_utf16 esatte; target è null quando un surrogato isolato impedisce la pubblicazione UTF-8 senza perdita. Le altre identità devono essere rappresentabili in UTF-8.

Lo scanner usa i confini delle istruzioni, i controlli degli operandi, la gestione dei payload e i controlli del flusso del lettore di recupero. Valori immediati e dati dei payload switch/array non possono diventare riferimenti. Controlla ogni corpo di codice definito, anche se nessuna destinazione corrisponde, prima di impostare code_scan_complete: true. defined_method_count include le dichiarazioni native e astratte; scanned_method_count conta le definizioni con corpo e scanned_code_item_count conta i corpi fisici distinti per DEX. matching_pool_entries conta le destinazioni corrispondenti, incluse quelle non referenziate.

validation_scope: "dex-code-references" comprende l’involucro DEX, le tabelle degli identificatori, l’appartenenza di classi e membri e i dati di codice, eccezioni e debug consumati. Annotazioni, valori statici codificati, usi nelle sole dichiarazioni e metadati non referenziati restano fuori dalla query. Non è recupero Java né un verificatore ART. Codice non supportato e dati consumati malformati falliscono esplicitamente. La convalida APK ha lo stesso confine dei payload selezionati dell’inventario delle classi; l’integrità dei payload delle risorse non selezionate resta non verificata.

I risultati vengono accumulati per tutti i DEX selezionati prima della pubblicazione, inclusi i controlli delle classi duplicate tra DEX. L’opzione facoltativa -o indica un nuovo file. --max-files limita separatamente il totale delle definizioni di classe, delle definizioni di metodo e delle occorrenze nei risultati, oltre alle voci ZIP. --max-bytes limita input, dati di query conservati, memoria di lavoro per corpo e output (con margini conservativi per l’espansione JSON). Sono budget dell’operazione; l’RSS del processo comprende anche buffer di input, overhead dell’allocatore e runtime. Si applicano anche i consueti budget di tempo e lavoro.

Opzioni e precedenza

neverd mobile app.apk -o recovered-app --platform=android \
  --timeout=600 --max-files=30000 --max-bytes=4294967296 --json
Opzione Valore predefinito Significato
-o PATH Obbligatoria per il recupero Nuova directory di recupero; con entrambe le modalità query, nuovo file di output facoltativo
--list-classes Disattivato Interroga le identità delle classi APK/DEX senza recupero Java
--class-prefix PREFIX Tutte le classi Prefisso letterale di descrittore o con punti; richiede --list-classes
--find-refs KIND Disattivato Interroga i riferimenti diretti delle istruzioni a stringhe, tipi, metodi o campi
--query TEXT Obbligatoria per i riferimenti Sottostringa letterale dell’identità della destinazione
--exact Disattivato Confronta l’intera identità della destinazione del riferimento
--owner DESCRIPTOR Qualsiasi proprietario Proprietario esatto della destinazione per query su metodi/campi
--platform=auto|android auto Selezionare Android esplicitamente o dedurre la piattaforma dall’input
--timeout N 300 Budget positivo del tempo di analisi in secondi
--max-files N 20000 Limite positivo del numero di voci, comprese le directory create
--max-bytes N 2147483648 Limite positivo in byte per input, dati estratti e output finale
--json Disattivato Stampare un report in JSON; altrimenti le query sui riferimenti emettono JSON Lines

Una selezione --arch diversa da quella predefinita, --artifact, --metadata-only e un --max-func diverso da zero appartengono a iOS e vengono rifiutati per Android; --arch=auto esplicito è accettato.

Input, dati estratti e output finale conservano i budget di file e byte. I lettori e il generatore integrati controllano anche lavoro limitato e tempo trascorso. Aumentare un limite non disattiva gli altri.

Struttura dell’output e report JSON

recovered-app/
  sources/                       package e classi Java recuperati
  metadata/android-methods.json  copertura dei metodi del motore integrato
  report.json                    inventario con versione e limiti

Gli input temporanei vengono rimossi. Le classi annidate possono condividere il file della classe esterna, quindi il numero di file Java non equivale al numero di classi DEX. I metodi generati possono usare un ciclo di dispatch Java; non eseguono il DEX originale né lo richiamano tramite un ponte a runtime.

Il report integrato include android_method_recovery, scritto anche in metadata/android-methods.json, e conserva ogni metodo originale. Vale method_count = recovered_method_count + projected_method_count + declaration_only_method_count + unrecovered_method_count; un projected_method_count assente vale zero e unrecovered_method_count resta zero prima della pubblicazione. I metodi originali native e abstract hanno stato declaration-only e non contano come corpi recuperati.

Un sottoinsieme di classi locali con nome e senza catture può essere emesso nel metodo statico contenitore esatto. Sono richiesti un metodo ordinario con tipi scalari, una classe priva di campi che estenda direttamente Object, un vero costruttore senza argomenti, metodi di istanza scalari e usi degli oggetti verificati che non escano dall’ambito supportato. Classi anonime, catture, modificatori non supportati e usi non dimostrati continuano a fallire esplicitamente.

I metodi locali e il metodo contenitore ricevono source-projected, con projection_kind: "named-method-local"; la copertura rimane partial anche quando il report generale indica success. I nomi binari e i flag di accesso dopo la ricompilazione restano non verificati. Il compilatore Java può scegliere un nome binario diverso, quindi class_source_bindings conserva classe originale, metodo contenitore esatto, percorso sorgente e nome locale con binary_name_status: "unverified".

generated_source_helpers elenca i metodi aggiuntivi con i tipi esatti throw-helper, constant-helper, default-constructor e field-initializer. L’ultimo indica un <clinit> generato aggiuntivo, assente dall’inventario dei metodi originali. Queste aggiunte non rientrano nel totale dei metodi originali. Una compilazione riuscita o una singola corrispondenza dei nomi non dimostra un recupero completo. L’esempio abbreviato seguente non contiene metodi proiettati:

{
  "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 è apk, dex, smali o smali-directory. input_code_files elenca i nomi del bytecode o i percorsi smali di input, mentre java_sources e logs sono relativi alla radice dell’output. source è il nome base dell’input. I report effettivi includono altri limiti di ricostruzione; conservali quando presenti i risultati ad altri strumenti.

Per l’automazione, controlla il codice di uscita del processo prima di leggere status e, quando reindirizzi stdout, salva il report fuori dalla nuova directory di output:

neverd mobile app.apk -o recovered-app --json > recovery-result.json

La CLI nativa restituisce zero in caso di successo e un valore non nullo in caso di errore. Con --json, gli errori gestiti includono schema_version, status: "error" ed error. Parsing degli argomenti, errori di avvio dell’eseguibile o delle librerie native e interruzioni possono essere segnalati soltanto su stderr. Controllare prima lo stato di uscita.

Gestione degli errori e risoluzione dei problemi

La pubblicazione è transazionale: l’output esistente viene conservato e i risultati temporanei falliti vengono rimossi. Operazioni non supportate, flussi di registri irrisolti, dichiarazioni non rappresentabili, gestione delle eccezioni malformata e budget esauriti fanno fallire il motore integrato anziché pubblicare corpi mancanti. Un recupero riuscito non prova l’equivalenza semantica.

Sintomo Azione
DEX, istruzione, dichiarazione o inizializzazione non supportati Leggere il messaggio diagnostico e verificare il sottoinsieme supportato
Input non valido o classe duplicata Correggere bytecode o insieme di classi; i corpi non supportati non vengono omessi silenziosamente
Tempo o budget superato Ridurre l’input o adattare --timeout, --max-files e --max-bytes alle risorse disponibili
Output già esistente Scegliere una nuova directory di output

Verifica e profondità del supporto

Python serve soltanto agli script di test di sviluppo riportati sotto; il recupero mobile integrato viene eseguito nella CLI nativa 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/neverd

I test dei componenti e della CLI controllano parsing, contratti di output e pulizia dopo gli errori. Il runner interno di confronto usa un JDK (java e javac) e D8 per costruire esempi DEX/APK indipendenti, poi compilare ed eseguire il Java recuperato. Sono dipendenze di test, non requisiti per il recupero integrato. Eseguirlo sulla build attuale ed esaminarne i risultati prima di dichiarare verificato un caso. Il successo degli esempi non dimostra il recupero completo di qualsiasi applicazione.

Vedere la panoramica mobile per il flusso iOS correlato.