
Analizzatore di bytecode Java personalizzabile tramite regole JSON
Analizzatore di bytecode Java personalizzabile tramite regole JSON. È uno strumento da riga di comando che riceve un percorso contenente uno o più file Jar o War, li analizza utilizzando le regole fornite e genera report HTML con i risultati.
usage: java -jar cba-cli.jar [OPTIONS] -a DIRECTORY_TO_ANALYZE
-a,--analyze <pathToAnalyze> Percorso della directory su cui eseguire
l'analisi.
-c,--checks <checks...> Elenco separato da spazi di controlli personalizzati
che verranno eseguiti nell'analisi.
-f,--custom-file <customFile> Specifica un file in formato JSON per eseguire
regole personalizzate. Leggi di più su
https://github.com/fergarrui/custom-bytecode-analyzer.
-h,--help Stampa questo messaggio.
-i,--items-report <maxItems> Numero massimo di elementi per report. Se il
numero di problemi trovati supera questo
valore, il report verrà suddiviso in file
diversi. Utile se ci si aspetta troppi
problemi nel report. Predefinito: 50.
-o,--output <outputDir> Directory in cui salvare il report. Attenzione -
se in questa directory sono già presenti report
salvati, verranno sovrascritti.
Predefinita è "report".
-v,--verbose-debug Aumenta la verbosità in modalità debug.
-vv,--verbose-trace Aumenta la verbosità in modalità trace - rallenta, usala solo quando necessario.
Il file delle regole può essere specificato usando l'argomento -f,--custom-file. Il file è in formato JSON e ha la seguente struttura:
finalPuoi anche controllare net.nandgr.cba.custom.model.Rules.java per vedere la struttura nel codice Java.
Ci sono già diverse regole nella directory examples. Comunque, di seguito sono elencati esempi per ogni regola.
Se dobbiamo trovare classi con deserializzazione personalizzata, possiamo farlo abbastanza facilmente. Una classe definisce deserializzazione personalizzata implementando private void readObject(ObjectInputStream in). Quindi dobbiamo solo trovare tutte le classi in cui quel metodo è definito. Sarebbe sufficiente definire una regola come:
{
"rules": [{
"name": "Deserializzazione personalizzata",
"methods": [{
"name": "readObject",
"visibility": "private",
"parameters" : [{
"type" : "java.io.ObjectInputStream"
}]
}]
}]
}
Segnalerà metodi con visibilità private, nome readObject e un parametro di tipo java.io.ObjectOutputStream. I parametri sono un array; se ne viene specificato più di uno, devono corrispondere tutti per essere segnalati. Poiché abbiamo una sola regola, verrà creato un report denominato: custom-deserialization-0.html.
In questo caso, deve essere definita una regola con due metodi. Lo stesso dell'esempio precedente per la deserializzazione, e uno nuovo per corrispondere a private void writeObject(ObjectOutputStream out). Come mostrato nella struttura JSON sopra, la proprietà rules.rule.methods è un array di metodi, quindi si può scrivere una regola come questa:
{
"rules": [{
"name": "Serializzazione e deserializzazione personalizzate",
"methods": [{
"name": "readObject",
"visibility": "private",
"parameters" : [{
"type" : "java.io.ObjectInputStream"
}]
},{
"name": "writeObject",
"report": "false",
"visibility": "private",
"parameters" : [{
"type" : "java.io.ObjectOutputStream"
}]
}]
}]
}
La proprietà report è stata impostata su false per evitare di segnalare due volte per la stessa regola. Stiamo usando il secondo metodo solo come condizione, ma segnalare solo i metodi readObject dovrebbe essere sufficiente per lo scopo di questa regola.
Se una proprietà non è definita, corrisponderà sempre come true. Ad esempio, questa regola restituirebbe tutte le definizioni di metodo:
{
"rules": [{
"name": "Definizioni di metodo",
"methods": [{
}]
}]
}
È possibile trovare anche le invocazioni di metodo. Il JSON in questo caso sarebbe:
{
"rules": [{
"name": "String equals",
"invocations": [{
"owner": "java.lang.String",
"method": {
"name": "equals"
}
}]
}]
}
La proprietà owner specifica la classe contenente il metodo.
Un altro esempio di invocazione di metodo un po' più utile del precedente:
{
"rules": [{
"name": "Invocazione di metodo per riflessione",
"invocations": [{
"owner": "java.lang.reflect.Method",
"method": {
"name": "invoke"
}
}]
}]
}
È uguale a qualsiasi invocazione di metodo, ma il nome del metodo in questo caso dovrebbe essere <init>.
{
"rules": [{
"name" : "Istanziazione di String",
"invocations" : [{
"owner" : "java.lang.String",
"method" : {
"name" : "<init>"
}
}]
}]
}
Questa regola troverà occorrenze di:
[...]
String s = new String("foo");
[...]
In questo esempio, vogliamo trovare usi di deserializzazione (non classi che definiscono comportamenti di serializzazione come negli esempi precedenti). La deserializzazione avviene quando viene invocato ObjectInputStream.readObject(). Ad esempio in questo frammento di codice:
ObjectInputStream in = new ObjectInputStream(fileInputStream);
Object o = in.readObject();
Quindi dobbiamo trovare invocazioni di metodo da ObjectInputStream chiamato readObject. Ma troverà molti falsi positivi in un contesto di ricerca, perché quando una classe definisce deserializzazione personalizzata, fa un'invocazione a questo metodo all'interno di un metodo private void readObject(ObjectInputStream in), e questo inquinerebbe troppo il report. Se vogliamo escludere questi casi e mantenere solo la deserializzazione genuina, possiamo usare la proprietà notFrom:
{
"rules": [{
"name": "Utilizzo di deserializzazione",
"invocations": [{
"owner": "java.io.ObjectInputStream",
"method": {
"name": "readObject"
},
"notFrom": {
"name": "readObject",
"visibility": "private"
}
}]
}]
}
Questo file troverà invocazioni di java.io.ObjectInputStream.readObject() se l'invocazione non viene effettuata all'interno del metodo private void readObject(ObjectInputStream in).
Una classe compilata con questo codice non verrà segnalata:
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
Object o = in.readObject();
}
Ma questa verrà segnalata:
public Object deserializeObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
Object o = in.readObject();
return o;
}
La proprietà from può essere impostata nelle invocazioni esattamente come notFrom, ma il risultato sarà opposto: corrisponderà solo se l'invocazione viene effettuata dal metodo definito.
La proprietà superClass può essere utilizzata in questo caso. Se vogliamo trovare tutte le classi che estendono javax.servlet.http.HttpServlet, una regola può essere:
{
"rules": [{
"name": "Servlet Java",
"superClass" : "javax.servlet.http.HttpServlet"
}]
}
Una regola può essere scritta per trovare classi che implementano un array di interfacce. Se nella regola viene definita più di un'interfaccia, la classe deve implementarle tutte per essere segnalata. Se vogliamo trovare classi che implementano javax.net.ssl.X509TrustManager, la regola sarebbe:
{
"rules": [{
"name": "Implementazioni di X509TrustManager",
"interfaces" : ["javax.net.ssl.X509TrustManager"]
}]
}
Nota che interfaces è un array, quindi assicurati di aggiungere le stringhe tra parentesi quadre, ad es: ["interfaccia1", "interfaccia2", ...].
Sono supportate anche le annotazioni. È possibile definire multiple proprietà di annotazioni in una regola (trovando annotazioni di classe), nei metodi o nelle variabili (parametri o variabili locali). Se tutte vengono trovate nella classe analizzata, verrà segnalata.
Ad esempio, se vogliamo trovare endpoint Spring, cercheremmo classi o metodi annotati con org.springframework.web.bind.annotation.RequestMapping. Quindi, la regola può essere:
{
"rules": [{
"name": "Endpoint Spring - annotazione di classe",
"annotations" : [{
"type" : "org.springframework.web.bind.annotation.RequestMapping"
}]
},
{
"name": "Endpoint Spring - annotazione di metodo",
"methods" : [{
"annotations" : [{
"type" : "org.springframework.web.bind.annotation.RequestMapping"
}]
}]
}]
}
La proprietà rule.fields può essere utilizzata per trovare campi di classe. Se vogliamo trovare campi String privati con nomi di password, si potrebbe usare una regola come questa:
{
"rules": [{
"name" : "Campi password",
"fields" : [
{
"visibility" : "private",
"type" : "java.lang.String",
"nameRegex" : "(password|pass|psswd|passwd)"
}
]
}]
}
Per trovare variabili, si può usare rule.variables. Questa proprietà segnalerà variabili locali e variabili degli argomenti dei metodi.
Se vogliamo trovare tutte le variabili di tipo javax.servlet.http.Part, una regola potrebbe essere:
{
"rules": [{
"name" : "File di upload Servlet",
"methods" : [{
"variables" : [{
"type" : "javax.servlet.http.Part"
}]
}]
}]
}
È possibile definire più regole nello stesso file JSON. Verranno elaborate e segnalate separatamente e non si influenzeranno a vicenda. Possiamo combinare alcuni degli esempi precedenti:
{
"rules": [{
"name": "Deserializzazione personalizzata",
"methods": [{
"name": "readObject",
"visibility": "private",
"parameters": [{
"type" : "java.io.ObjectInputStream"
}]
}]
},{
"name": "Invocazione di metodo per riflessione",
"invocations": [{
"owner": "java.lang.reflect.Method",
"method": {
"name": "invoke"
}
}]
}]
}
Qui abbiamo due regole ("Deserializzazione personalizzata" e "Invocazione di metodo per riflessione"). Verranno elaborate come se le eseguissi in due esecuzioni separate. E verrà generato un report per regola. Se le regole hanno lo stesso nome, verranno segnalate nello stesso file.
Il progetto può essere scaricato e compilato per aggiungere regole personalizzate più complesse in codice Java che non sono coperte dal formato JSON. Ci sono già tre esempi nel pacchetto net.nandgr.cba.visitor.checks. Sono CustomDeserializationCheck, DeserializationCheck e InvokeMethodCheck. Puoi creare le tue regole estendendo net.nandgr.cba.custom.visitor.base.CustomAbstractClassVisitor.
Come accennato sopra, i report vengono creati per impostazione predefinita nella cartella report. Ogni regola avrà un file separato a meno che non abbiano lo stesso nome.
Se il report è troppo grande, puoi dividerlo usando il parametro -i,--items-report <maxItems>; ognuno conterrà l'argomento specificato o meno (se è l'ultimo).
Ogni elemento segnalato specifica il jar in cui è stato trovato, il nome della classe e il nome del metodo (se rilevante). Mostra anche la versione decompilata della classe per facilitare un rapido controllo visivo.
Esempio di come vengono mostrati gli elementi per una regola che trova istanziazioni di java.io.File:

Quando si cercano bug di sicurezza, è molto utile avere un grafo delle chiamate. Al momento, viene creato un semplice file compatibile con DOT nella directory report.
Il grafo contiene tutti i possibili flussi da cui i problemi trovati possono essere invocati. Ad esempio, se viene utilizzata una regola per trovare deserializzazione,
verrà generato un grafo contenente tutti i possibili percorsi che portano al metodo che chiama la deserializzazione.
Il file è call-graph.dot e sarebbe simile a questo (questo è un esempio estremamente semplice):
graph callGraph {
"demo.callgraph.Class1:method1" -- "demo.callgraph.Class2:method2"
"demo.callgraph.Class3:method3" -- "demo.callgraph.Class2:method2"
}
Per visualizzarlo in modo grafico, è possibile utilizzare DOT (o qualsiasi software compatibile). Ad esempio, per convertire il file in svg:
dot -Tsvg call-graph.dot -o call-graph.svg
Questo viene fatto automaticamente per impostazione predefinita se DOT si trova nel PATH di sistema. Altrimenti, DOT può essere installato in sistemi basati su Debian usando sudo apt-get install graphviz.
Verrà creato un file SVG chiamato call-graph.svg che può essere convertito in PNG o visualizzato utilizzando programmi come inkscape o semplicemente firefox.
Un esempio molto semplice del file call-graph.dot sopra, sarebbe:

Ci sono alcune limitazioni, ad esempio, se l'elemento cercato si trova in un metodo java.lang.Runnable.run() o simile, non troverà da dove viene eseguito il thread.
Inoltre, il grafo pulisce i cicli per evitare StackOverflowError; è realizzato in modo un po' conservativo in modo che la memoria del sistema non venga prosciugata durante l'analisi di una directory grande.
Verranno aggiunte più opzioni nelle versioni future.
java -jar cba-cli-<version>.jar -a /percorso/con/jar -f /percorso/con/file/json/regole.json
Per utilizzare regole Java personalizzate, i nomi delle classi devono essere specificati come argomenti di -c.
java -jar cba-cli-<version>.jar -a /percorso/con/jar -c DeserializationCheck
Accetta un elenco separato da spazi, quindi è possibile definire più regole personalizzate (ciascuna creerà un report separato):
java -jar cba-cli-<version>.jar -a /percorso/con/jar -c DeserializationCheck InvokeMethodCheck CustomDeserializationCheck YourCustomRule
java -jar cba-cli-<version>.jar -a /percorso/con/jar -f /percorso/con/file/json/regole.json -c YourCustomRule1 YourCustomRule2
Per trovare errori, la verbosità può essere aumentata. Livello debug:
java -jar cba-cli-<version>.jar -a /percorso/con/jar -c YourCustomRule1 -v
Livello trace:
java -jar cba-cli-<version>.jar -a /percorso/con/jar -c YourCustomRule1 -vv
Al momento, l'APK deve essere convertito in JAR prima di essere analizzato.
d2j-dex2jar.sh -f -o app_da_analizzare.jar app_da_analizzare.apk-a la directory contenente il file jar convertito.C'è già un jar eseguibile nella directory bin all'indirizzo: https://github.com/fergarrui/custom-bytecode-analyzer/blob/master/bin/cba-cli-0.1-SNAPSHOT.jar. Se vuoi fare modifiche o aggiungere regole personalizzate, il progetto può essere compilato eseguendo:
git clone https://github.com/fergarrui/custom-bytecode-analyzer.git
cd custom-bytecode-analyzer
mvn clean package
Verranno generati due jar nella cartella target. cba-cli-<version>.jar contiene tutte le dipendenze ed è eseguibile. Può essere eseguito usando java -jar cba-cli-<version>.jar