Analyseur Java Bytecode personnalisable via les règles JSON. Il s'agit d'un outil de ligne de commande qui reçoit un chemin contenant un ou plusieurs fichiers JAR ou WAR, les analyse en utilisant les règles fournies et génère des rapports HTML avec les résultats.
usage: java -jar cba-cli.jar [OPTIONS] -a DIRECTORY_TO_ANALYZE
-a,--analyze <pathToAnalyze> Path of the directory to run the
analysis.
-c,--checks <checks...> Space separated list of custom checks
that are going to be run in the analysis.
-f,--custom-file <customFile> Specify a file in JSON format to run
custom rules. Read more in
https://github.com/fergarrui/custom-bytecode-analyzer.
-h,--help Print this message.
-i,--items-report <maxItems> Max number of items per report. If the
number of issues found exceeds this
value, the report will be split into
different files. Useful if expecting too
many issues in the report. Default: 50.
-o,--output <outputDir> Directory to save the report. Warning -
if there are already saved reports in
this directory they will be overwritten.
Default is "report".
-v,--verbose-debug Increase verbosity to debug mode.
-vv,--verbose-trace Increase verbosity to trace mode - makes it slower, use it only when you need.
Le fichier de règles peut être spécifié à l'aide de -f,--custom-file . Le fichier est au format JSON et a la structure suivante:
final Vous pouvez également vérifier net.nandgr.cba.custom.model.Rules.java pour voir la structure dans le code java.
Il existe déjà plusieurs règles dans les exemples de répertoires. Quoi qu'il en soit, ci-dessous sont des exemples répertoriés pour chaque règle.
Si nous devons trouver des cours avec désérialisation personnalisée, nous pouvons le faire assez facilement. Une classe définit la désérialisation personnalisée en mettant en œuvre private void readObject(ObjectInputStream in) . Nous avons donc seulement besoin de trouver toutes les classes où cette méthode est définie. Il suffirait de définir une règle comme:
{
"rules" : [{
"name" : " Custom deserialization " ,
"methods" : [{
"name" : " readObject " ,
"visibility" : " private " ,
"parameters" : [{
"type" : " java.io.ObjectInputStream "
}]
}]
}]
} Il rapportera des méthodes avec une visibilité private , readObject comme nom et un paramètre de type java.io.ObjectOutputStream . Les paramètres sont un tableau, si plus d'un est spécifié, ils doivent tous correspondre pour être signalés. Puisque nous n'avons qu'une seule règle, un rapport nommé: Custom-Deserialization-0.html sera créé.
Dans ce cas, une règle avec deux méthodes doit être définie. Le même que dans l'exemple précédent de désérialisation, et un nouveau pour correspondre private void writeObject(ObjectOutputStream out) . Comme indiqué dans la structure JSON ci-dessus, la propriété règles.rule.methods est un tableau de méthodes, donc une règle comme celle-ci peut être écrite:
{
"rules" : [{
"name" : " Custom serialization and deserialization " ,
"methods" : [{
"name" : " readObject " ,
"visibility" : " private " ,
"parameters" : [{
"type" : " java.io.ObjectInputStream "
}]
},{
"name" : " writeObject " ,
"report" : " false " ,
"visibility" : " private " ,
"parameters" : [{
"type" : " java.io.ObjectOutputStream "
}]
}]
}]
} Le report de la propriété a été défini sur False pour éviter de signaler deux fois pour la même règle. Nous utilisons la deuxième méthode comme une condition, mais la déclaration des méthodes readObject devrait être suffisante aux fins de cette règle.
Si une propriété n'est pas définie, elle correspondra toujours comme vraie. Par exemple, cette règle renverrait toutes les définitions de méthodes:
{
"rules" : [{
"name" : " Method definitions " ,
"methods" : [{
}]
}]
}Des invocations de méthode peuvent également être trouvées. Le JSON dans ce cas serait:
{
"rules" : [{
"name" : " String equals " ,
"invocations" : [{
"owner" : " java.lang.String " ,
"method" : {
"name" : " equals "
}
}]
}]
} Le owner spécifie la classe contenant la méthode.
Un autre exemple d'invocation de méthode un peu plus utile que le précédent:
{
"rules" : [{
"name" : " Method invocation by reflection " ,
"invocations" : [{
"owner" : " java.lang.reflect.Method " ,
"method" : {
"name" : " invoke "
}
}]
}]
} C'est la même chose que toute invocation de la méthode, mais le nom de la méthode dans ce cas devrait être <init> .
{
"rules" : [{
"name" : " String instantiation " ,
"invocations" : [{
"owner" : " java.lang.String " ,
"method" : {
"name" : " <init> "
}
}]
}]
}Cette règle trouvera des événements de:
[...]
String s = new String ( "foo" );
[...] Dans cet exemple, nous voulons trouver des usages de désérialisation (pas des classes définissant les comportements de sérialisation comme dans les exemples précédents). La désérialisation se produit lorsque ObjectInputStream.readObject() est invoqué. Par exemple dans cet extrait de code:
ObjectInputStream in = new ObjectInputStream ( fileInputStream );
Object o = in . readObject (); Nous devons donc trouver des invocations de méthode à partir d' ObjectInputStream nommé readObject . Mais il trouvera beaucoup de faux positifs dans un contexte de recherche, car lorsqu'une classe définit la désérialisation personnalisée, ils effectuent une invocation à cette méthode dans une méthode private void readObject(ObjectInputStream in) , et cela polluerait trop le rapport. Si nous voulons exclure ces cas et ne conserver que la désérialisation authentique, les propriétés notFrom peuvent être utilisées:
{
"rules" : [{
"name" : " Deserialization usage " ,
"invocations" : [{
"owner" : " java.io.ObjectInputStream " ,
"method" : {
"name" : " readObject "
},
"notFrom" : {
"name" : " readObject " ,
"visibility" : " private "
}
}]
}]
} Ce fichier trouvera java.io.ObjectInputStream.readObject() Invocations Si l'invocation n'est pas effectuée dans la méthode private void readObject(ObjectInputStream in) .
Une classe compilée avec ce code ne sera pas signalée:
private void readObject ( ObjectInputStream in ) throws IOException , ClassNotFoundException {
Object o = in . readObject ();
}Mais celui-ci sera signalé:
public Object deserializeObject ( ObjectInputStream in ) throws IOException , ClassNotFoundException {
Object o = in . readObject ();
return o ;
} La propriété from peut être définie dans les invocations exactement de la même manière que notFrom , mais le résultat sera le contraire: il ne correspondra que si l'invocation est effectuée à partir de la méthode définie.
La superClass de propriété peut être utilisée dans ce cas. Si nous voulons trouver toutes les classes étendant javax.servlet.http.HttpServlet , une règle peut être:
{
"rules" : [{
"name" : " Java servlets " ,
"superClass" : " javax.servlet.http.HttpServlet "
}]
}
Une règle peut être écrite pour trouver des classes implémentant un tableau d'interfaces. Si plus d'une interface est définie dans la règle, la classe doit les mettre en œuvre pour être signalées. Si nous voulons trouver des classes implémentant javax.net.ssl.X509TrustManager , la règle serait:
{
"rules" : [{
"name" : " X509TrustManager implementations " ,
"interfaces" : [ " javax.net.ssl.X509TrustManager " ]
}]
} Veuillez noter que interfaces sont un tableau , alors assurez-vous d'ajouter les chaînes entre les crochets, par exemple: ["interface1", "interface2", ...] .
Les annotations sont également soutenues. Plusieurs propriétés d'annotations peuvent être définies dans une règle (trouver des annotations de classe), dans les méthodes O variables (paramètres ou variables locales). Si tous se trouvent dans la classe analysée, il sera signalé. Par exemple, si nous voulons trouver des points de terminaison de printemps, nous recherchons des classes ou des méthodes annotées avec org.springframework.web.bind.annotation.RequestMapping . Ainsi, la règle peut être:
{
"rules" : [{
"name" : " Spring endpoint - class annotation " ,
"annotations" : [{
"type" : " org.springframework.web.bind.annotation.RequestMapping "
}]
},
{
"name" : " Spring endpoint - method annotation " ,
"methods" : [{
"annotations" : [{
"type" : " org.springframework.web.bind.annotation.RequestMapping "
}]
}]
}]
} Les rule.fields de la propriété peuvent être utilisés pour trouver des champs de classe. Si nous voulons trouver des champs de chaîne privés avec des noms de mot de passe, une règle comme celle-ci pourrait être utilisée:
{
"rules" : [{
"name" : " Password fields " ,
"fields" : [
{
"visibility" : " private " ,
"type" : " java.lang.String " ,
"nameRegex" : " (password|pass|psswd|passwd) "
}
]
}]
} Pour trouver des variables, rule.variables peuvent être utilisées. Cette propriété rapportera des variables locales et des variables d'arguments de méthode. Si nous voulons trouver toutes les variables de type javax.servlet.http.Part , une règle pourrait être:
{
"rules" : [{
"name" : " Servlet upload file " ,
"methods" : [{
"variables" : [{
"type" : " javax.servlet.http.Part "
}]
}]
}]
}Plusieurs règles peuvent être définies dans le même fichier JSON. Ils seront traités et signalés séparément et ils ne se affecteront pas mutuellement. Nous pouvons combiner certaines des règles d'exemples précédents:
{
"rules" : [{
"name" : " Custom deserialization " ,
"methods" : [{
"name" : " readObject " ,
"visibility" : " private " ,
"parameters" : [{
"type" : " java.io.ObjectInputStream "
}]
}]
},{
"name" : " Method invocation by reflection " ,
"invocations" : [{
"owner" : " java.lang.reflect.Method " ,
"method" : {
"name" : " invoke "
}
}]
}]
}Ici, nous avons deux règles ("désérialisation personnalisée" et "Invocation de méthode par réflexion"). Ils seront traités comme si vous le faisiez dans deux exécutions séparées. Et un rapport par règle sera généré. Si les règles ont le même nom, elles seront signalées dans le même fichier.
Le projet peut être téléchargé et construit pour ajouter des règles personnalisées plus complexes dans le code Java qui ne sont pas couvertes par le format JSON. Il existe déjà trois exemples dans le package net.nandgr.cba.visitor.checks . Ce sont CustomDeserializationCheck, DeserializationCheck and InvokeMethodCheck . Vous pouvez créer vos propres règles en étendant net.nandgr.cba.custom.visitor.base.CustomAbstractClassVisitor .
Comme mentionné ci-dessus, les rapports sont créés par défaut sous le dossier report . Chaque règle aura un fichier séparé à moins qu'il ne soit le même nom. Si le rapport est trop grand, vous pouvez le diviser en utilisant le paramètre -i,--items-report <maxItems> , chacun d'eux conservera l'argument spécifié ou moins (s'il s'agit du dernier). Chaque élément rapporté, spécifie le pot où il est trouvé, le nom de classe et le nom de la méthode (s'il est pertinent). Il montre également la version décompilée de la classe pour faciliter une vérification visuelle rapide. Exemple de la façon dont les éléments sont affichés pour une règle pour trouver des instanciations java.io.File :

Lors de la recherche de bogues de sécurité, il est très utile d'avoir un graphique d'appel. Pour le moment, un fichier simple compatible DOT est créé dans le répertoire report . Le graphique contient tous les flux possibles d'où les problèmes trouvés peuvent être invoqués. Par exemple, si une règle pour trouver la désérialisation est utilisée, un graphique contenant tous les chemins possibles menant à la méthode qui appelle la désérialisation sera générée.
Le fichier est call-graph.dot et il ressemblerait à ceci (c'est un exemple extrêmement simple):
graph callGraph {
"demo.callgraph.Class1:method1" -- "demo.callgraph.Class2:method2"
"demo.callgraph.Class3:method3" -- "demo.callgraph.Class2:method2"
}
Pour l'afficher de manière visuelle, DOT peut être utilisé (ou tout logiciel compatible). Par exemple, pour convertir le fichier en svg :
dot -Tsvg call-graph.dot -o call-graph.svg
Cela se fait automatiquement par défaut si le point se trouve dans le chemin du système. Sinon, DOT peut être installé dans des systèmes basés sur Debian à l'aide sudo apt-get install graphviz .
Il créera un fichier SVG nommé call-graph.svg qui peut être converti en PNG ou visualisé à l'aide de programmes comme inkscape ou simplement firefox .
Un exemple très simple du fichier ci-dessus, graph.dot, serait:

Il y a certaines limites, comme par exemple, si l'élément recherché se trouve dans une java.lang.Runnable.run() ou une méthode similaire, il ne trouvera pas d'où le thread est exécuté. En outre, le graphique consiste à nettoyer les cycles pour éviter StackOverflowError , il est fabriqué de manière un peu conservatrice afin que la mémoire du système ne soit pas drainée lors d'une analyse d'un grand répertoire.
Plus d'options seront ajoutées dans les futures versions.
java -jar cba-cli-<version>.jar -a /path/with/jars -f /path/with/json/file/rules.json
Pour utiliser les règles Java personnalisées, les noms de classe doivent être spécifiés comme arguments de -c .
java -jar cba-cli-<version>.jar -a /path/with/jars -c DeserializationCheck
Accepte une liste séparée d'espace, de sorte que plusieurs règles personnalisées peuvent être définies (chacune des règles créera un rapport distinct):
java -jar cba-cli-<version>.jar -a /path/with/jars -c DeserializationCheck InvokeMethodCheck CustomDeserializationCheck YourCustomRule
java -jar cba-cli-<version>.jar -a /path/with/jars -f /path/with/json/file/rules.json -c YourCustomRule1 YourCustomRule2
Pour trouver des erreurs, la verbosité peut être augmentée. Niveau de débogage:
java -jar cba-cli-<version>.jar -a /path/with/jars -c YourCustomRule1 -v
Niveau de trace:
java -jar cba-cli-<version>.jar -a /path/with/jars -c YourCustomRule1 -vv
Pour le moment, l'APK doit d'abord être converti en JAR pour être analysé.
d2j-dex2jar.sh -f -o app_to_analyze.jar app_to_analyze.apk-a paramètre du répertoire contenant le fichier JAR converti. Il existe déjà un fichier JAR exécutable sous le répertoire bin à: https://github.com/fergarrui/custom-bytecode-analyzer/blob/master/bin/cba-cli-0.1-snapshot.jar. Si vous souhaitez effectuer des modifications ou ajouter des règles personnalisées, le projet peut être construit en faisant:
git clone https://github.com/fergarrui/custom-bytecode-analyzer.git
cd custom-bytecode-analyzer
mvn clean package
Deux pots seront générés dans le dossier target . cba-cli-<version>.jar contient toutes les dépendances et est exécutable. Peut être exécuté à l'aide de java -jar cba-cli-<version>.jar