Java Bytecode Analyzer personalizable a través de las reglas JSON. Es una herramienta de línea de comandos que recibe una ruta que contiene uno o más archivos JAR o WAR, los analiza utilizando las reglas proporcionadas y genera informes HTML con los resultados.
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.
El archivo de reglas se puede especificar usando -f,--custom-file . El archivo está en formato JSON y tiene la siguiente estructura:
final También puede verificar net.nandgr.cba.custom.model.Rules.java para ver la estructura en el código Java.
Ya hay varias reglas bajo los ejemplos de directorio. De todos modos, a continuación se enumeran ejemplos para cada regla.
Si necesitamos encontrar clases con deserialización personalizada, podemos hacerlo con bastante facilidad. Una clase define la deserialización personalizada mediante la implementación private void readObject(ObjectInputStream in) . Por lo tanto, solo necesitamos encontrar todas las clases donde se define ese método. Sería suficiente para definir una regla como:
{
"rules" : [{
"name" : " Custom deserialization " ,
"methods" : [{
"name" : " readObject " ,
"visibility" : " private " ,
"parameters" : [{
"type" : " java.io.ObjectInputStream "
}]
}]
}]
} Informará métodos con visibilidad private , readObject como nombre y un parámetro de tipo java.io.ObjectOutputStream . Los parámetros son una matriz, si se especifica más de uno, todos tienen que coincidir para ser reportados. Dado que solo tenemos una regla, se creará un informe llamado: Custom-Deserialization-0.html.
En este caso, se debe definir una regla con dos métodos. El mismo que en el ejemplo anterior para la deserialización, y uno nuevo para que coincida con private void writeObject(ObjectOutputStream out) . Como se muestra en la estructura JSON anterior, la propiedad Reglas.Rule.methods es una variedad de métodos, por lo que se puede escribir una regla como esta:
{
"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 "
}]
}]
}]
} El report de la propiedad se estableció en falso para evitar informar dos veces para la misma regla. Estamos utilizando el segundo método solo como condición, pero informar solo los métodos readObject deberían ser suficientes para el propósito de esta regla.
Si no se define una propiedad, siempre coincidirá como verdadera. Por ejemplo, esta regla devolvería todas las definiciones de métodos:
{
"rules" : [{
"name" : " Method definitions " ,
"methods" : [{
}]
}]
}También se pueden encontrar invocaciones de métodos. El JSON en este caso sería:
{
"rules" : [{
"name" : " String equals " ,
"invocations" : [{
"owner" : " java.lang.String " ,
"method" : {
"name" : " equals "
}
}]
}]
} El owner especifica la clase que contiene el método.
Otro ejemplo de invocación de método un poco más útil que el anterior:
{
"rules" : [{
"name" : " Method invocation by reflection " ,
"invocations" : [{
"owner" : " java.lang.reflect.Method " ,
"method" : {
"name" : " invoke "
}
}]
}]
} Es lo mismo que cualquier invocación del método, pero el nombre del método en este caso, debe ser <init> .
{
"rules" : [{
"name" : " String instantiation " ,
"invocations" : [{
"owner" : " java.lang.String " ,
"method" : {
"name" : " <init> "
}
}]
}]
}Esta regla encontrará ocurrencias de:
[...]
String s = new String ( "foo" );
[...] En este ejemplo, queremos encontrar usos de deserialización (no clases que definan comportamientos de serialización como en los ejemplos anteriores). La deserialización ocurre cuando se invoca ObjectInputStream.readObject() . Por ejemplo, en este fragmento de código:
ObjectInputStream in = new ObjectInputStream ( fileInputStream );
Object o = in . readObject (); Por lo tanto, necesitamos encontrar invocaciones de métodos de ObjectInputStream llamado readObject . Pero encontrará muchos falsos positivos en un contexto de investigación, porque cuando una clase define la deserialización personalizada, hace una invocación a este método dentro de un método private void readObject(ObjectInputStream in) , y eso contaminaría demasiado el informe. Si queremos excluir esos casos y mantener solo una deserialización genuina, notFrom puede utilizar la propiedad:
{
"rules" : [{
"name" : " Deserialization usage " ,
"invocations" : [{
"owner" : " java.io.ObjectInputStream " ,
"method" : {
"name" : " readObject "
},
"notFrom" : {
"name" : " readObject " ,
"visibility" : " private "
}
}]
}]
} Este archivo encontrará invocaciones java.io.ObjectInputStream.readObject() si la invocación no se realiza dentro del método private void readObject(ObjectInputStream in) .
No se informará una clase compilada con este código:
private void readObject ( ObjectInputStream in ) throws IOException , ClassNotFoundException {
Object o = in . readObject ();
}Pero este será informado:
public Object deserializeObject ( ObjectInputStream in ) throws IOException , ClassNotFoundException {
Object o = in . readObject ();
return o ;
} La propiedad from se puede establecer en invocaciones exactamente de la misma manera que notFrom , pero el resultado será lo contrario: solo coincidirá si la invocación se realiza con el método definido.
La superClass de propiedad se puede usar en este caso. Si queremos encontrar todas las clases que extienden javax.servlet.http.HttpServlet , una regla puede ser:
{
"rules" : [{
"name" : " Java servlets " ,
"superClass" : " javax.servlet.http.HttpServlet "
}]
}
Se puede escribir una regla para encontrar clases que implementen una serie de interfaces. Si se define más de una interfaz en la regla, la clase debe implementarlos para ser reportados. Si queremos encontrar clases que implementen javax.net.ssl.X509TrustManager , la regla sería:
{
"rules" : [{
"name" : " X509TrustManager implementations " ,
"interfaces" : [ " javax.net.ssl.X509TrustManager " ]
}]
} Tenga en cuenta que interfaces son una matriz , así que asegúrese de agregar las cuerdas entre los soportes cuadrados, por ejemplo: ["interface1", "interface2", ...] .
Las anotaciones también son compatibles. Las propiedades de anotaciones múltiples se pueden definir en una regla (encontrar anotaciones de clase), en los métodos o variables (parámetros o variables locales). Si todos se encuentran en la clase analizada, se informará. Por ejemplo, si queremos encontrar puntos finales de primavera, buscaríamos clases o métodos anotados con org.springframework.web.bind.annotation.RequestMapping . Entonces, la regla puede ser:
{
"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 "
}]
}]
}]
} La rule.fields de propiedad. Los campos se pueden usar para encontrar campos de clase. Si queremos encontrar campos de cadena privados con nombres de contraseña, se podría usar una regla como esta:
{
"rules" : [{
"name" : " Password fields " ,
"fields" : [
{
"visibility" : " private " ,
"type" : " java.lang.String " ,
"nameRegex" : " (password|pass|psswd|passwd) "
}
]
}]
} Para encontrar variables, se pueden usar rule.variables . Esta propiedad informará variables locales y variables de argumentos de métodos. Si queremos encontrar todas las variables de tipo javax.servlet.http.Part , una regla podría ser:
{
"rules" : [{
"name" : " Servlet upload file " ,
"methods" : [{
"variables" : [{
"type" : " javax.servlet.http.Part "
}]
}]
}]
}Se pueden definir múltiples reglas en el mismo archivo JSON. Serán procesados e informados por separado y no se afectarán entre sí. Podemos combinar algunas de las reglas de ejemplos anteriores:
{
"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 "
}
}]
}]
}Aquí, tenemos dos reglas ("Deserialización personalizada" e "Invocación del método por reflexión"). Se procesarán como si lo haga en dos ejecuciones separadas. Y se generará un informe por regla. Si las reglas tienen el mismo nombre, se informarán en el mismo archivo.
El proyecto se puede descargar y construir para agregar reglas personalizadas más complejas en el código Java que no están cubiertos por el formato JSON. Ya hay tres ejemplos en el paquete net.nandgr.cba.visitor.checks . Esos son CustomDeserializationCheck, DeserializationCheck and InvokeMethodCheck . Puede crear sus propias reglas extendiendo net.nandgr.cba.custom.visitor.base.CustomAbstractClassVisitor .
Como se mencionó anteriormente, los informes se crean de forma predeterminada en la carpeta report . Cada regla tendrá un archivo separado a menos que tengan el mismo nombre. Si el informe es demasiado grande, puede dividirlo usando el parámetro -i,--items-report <maxItems> , cada uno de ellos contendrá el argumento especificado o menos (si es el último). Cada elemento informado, especifica el fras donde se encuentra, el nombre de la clase y el nombre del método (si es relevante). También muestra la versión descompilada de la clase para facilitar una verificación visual rápida. Ejemplo de cómo se muestran los elementos para que una regla encuentre instanciaciones java.io.File :

Al buscar errores de seguridad, es muy útil tener un gráfico de llamadas. En este momento, se crea un archivo compatible con DOT simple en el directorio report . El gráfico contiene todos los flujos posibles donde se pueden invocar los problemas encontrados. Por ejemplo, si se utiliza una regla para encontrar deserialización, se generará un gráfico que contenga todas las rutas posibles que conduzca al método que llama a la deserialización.
El archivo es call-graph.dot y se vería así (este es un ejemplo extremadamente simple):
graph callGraph {
"demo.callgraph.Class1:method1" -- "demo.callgraph.Class2:method2"
"demo.callgraph.Class3:method3" -- "demo.callgraph.Class2:method2"
}
Para mostrarlo de manera visual, se puede usar DOT (o cualquier software compatible). Por ejemplo, para convertir el archivo a svg :
dot -Tsvg call-graph.dot -o call-graph.svg
Esto se realiza automáticamente de forma predeterminada si se encuentra DOT en la ruta del sistema. Si no, DOT se puede instalar en sistemas basados en Debian utilizando sudo apt-get install graphviz .
Creará un archivo SVG llamado call-graph.svg que se puede convertir en PNG o visualizado utilizando programas como inkscape o simplemente firefox .
Un ejemplo muy simple del archivo anterior llamado-gráfico.

Hay algunas limitaciones, como por ejemplo, si el elemento buscado está en un java.lang.Runnable.run() o un método similar, no encontrará de dónde se ejecuta el hilo. Además, el gráfico son los ciclos de limpieza para evitar StackOverflowError , se realiza de manera conservadora, por lo que la memoria del sistema no se drena durante un análisis de un directorio grande.
Se agregarán más opciones en futuras versiones.
java -jar cba-cli-<version>.jar -a /path/with/jars -f /path/with/json/file/rules.json
Para usar reglas Java personalizadas, los nombres de clases deben especificarse como argumentos de -c .
java -jar cba-cli-<version>.jar -a /path/with/jars -c DeserializationCheck
Acepta una lista separada por el espacio, por lo que se pueden definir múltiples reglas personalizadas (cada una de las reglas creará un informe separado):
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
Para encontrar errores, se puede aumentar la verbosidad. Nivel de depuración:
java -jar cba-cli-<version>.jar -a /path/with/jars -c YourCustomRule1 -v
Nivel de rastreo:
java -jar cba-cli-<version>.jar -a /path/with/jars -c YourCustomRule1 -vv
Por el momento, el APK debe convertirse en JAR primero para ser analizado.
d2j-dex2jar.sh -f -o app_to_analyze.jar app_to_analyze.apk-a parámetro El directorio que contiene el archivo jar convertido. Ya hay un archivo JAR ejecutable en el directorio bin en: https://github.com/fergararrui/custom-bytecode-analyzer/blob/master/bin/cba-cli-0.1-snapshot.jar. Si desea realizar modificaciones o agregar reglas personalizadas, el proyecto se puede construir haciendo:
git clone https://github.com/fergarrui/custom-bytecode-analyzer.git
cd custom-bytecode-analyzer
mvn clean package
Se generarán dos frascos en la carpeta target . cba-cli-<version>.jar contiene todas las dependencias y es ejecutable. Se puede ejecutar usando java -jar cba-cli-<version>.jar