
Analizador de bytecode de Java personalizable mediante reglas JSON.
Analizador de bytecode de Java personalizable mediante 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> Ruta del directorio para ejecutar el
análisis.
-c,--checks <checks...> Lista separada por espacios de comprobaciones personalizadas
que se ejecutarán en el análisis.
-f,--custom-file <customFile> Especifica un archivo en formato JSON para ejecutar
reglas personalizadas. Lea más en
https://github.com/fergarrui/custom-bytecode-analyzer.
-h,--help Imprime este mensaje.
-i,--items-report <maxItems> Número máximo de elementos por informe. Si el
número de problemas encontrados supera este
valor, el informe se dividirá en diferentes
archivos. Útil si se esperan demasiados
problemas en el informe. Valor por defecto: 50.
-o,--output <outputDir> Directorio para guardar el informe. Advertencia:
si ya hay informes guardados en este directorio, serán sobrescritos.
El valor por defecto es "report".
-v,--verbose-debug Aumenta la verbosidad al modo depuración.
-vv,--verbose-trace Aumenta la verbosidad al modo traza - lo hace más lento, úselo solo cuando sea necesario.
El archivo de reglas se puede especificar usando el argumento -f,--custom-file. El archivo está en formato JSON y tiene la siguiente estructura:
finalTambién puedes consultar net.nandgr.cba.custom.model.Rules.java para ver la estructura en código Java.
Ya existen varias reglas en el directorio examples. De todas formas, 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 deserialización personalizada implementando private void readObject(ObjectInputStream in). Por lo tanto, solo necesitamos encontrar todas las clases donde esté definido ese método. Bastaría con definir una regla como:
{
"rules": [{
"name": "Custom deserialization",
"methods": [{
"name": "readObject",
"visibility": "private",
"parameters" : [{
"type" : "java.io.ObjectInputStream"
}]
}]
}]
}
Reportará métodos con visibilidad private, nombre readObject y un parámetro de tipo java.io.ObjectOutputStream. Los parámetros son un array; si se especifica más de uno, todos deben coincidir para ser reportados. Como 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 deserialización, y uno nuevo para coincidir con private void writeObject(ObjectOutputStream out). Como se muestra en la estructura JSON anterior, la propiedad rules.rule.methods es un array 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"
}]
}]
}]
}
La propiedad report se estableció en false para evitar reportar dos veces la misma regla. Estamos usando el segundo método solo como condición, pero reportar solo los métodos readObject debería ser suficiente para el propósito de esta regla.
Si una propiedad no está definida, siempre coincidirá como verdadero. 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"
}
}]
}]
}
La propiedad 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 de 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 definen 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 llamadas readObject. Pero encontrará muchos falsos positivos en un contexto de investigación, porque cuando una clase define deserialización personalizada, hacen 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 quedarnos solo con la deserialización genuina, se puede usar la propiedad notFrom:
{
"rules": [{
"name": "Deserialization usage",
"invocations": [{
"owner": "java.io.ObjectInputStream",
"method": {
"name": "readObject"
},
"notFrom": {
"name": "readObject",
"visibility": "private"
}
}]
}]
}
Este archivo encontrará invocaciones de java.io.ObjectInputStream.readObject() si la invocación no se realiza dentro del método private void readObject(ObjectInputStream in).
Una clase compilada con este código no será reportada:
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
Object o = in.readObject();
}
Pero esta sí será reportada:
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á el opuesto: solo coincidirá si la invocación se realiza desde el método definido.
La propiedad superClass 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 implementan un array de interfaces. Si se define más de una interfaz en la regla, la clase debe implementarlas todas para ser reportada. Si queremos encontrar clases que implementan javax.net.ssl.X509TrustManager, la regla sería:
{
"rules": [{
"name": "X509TrustManager implementations",
"interfaces" : ["javax.net.ssl.X509TrustManager"]
}]
}
Tenga en cuenta que interfaces es un array, así que asegúrese de agregar las cadenas entre corchetes, por ejemplo: ["interface1", "interface2", ...].
También se admiten anotaciones. Se pueden definir múltiples propiedades de anotaciones en una regla (encontrando anotaciones de clase), en métodos o variables (parámetros o variables locales). Si todas se encuentran en la clase analizada, será reportada.
Por ejemplo, si queremos encontrar endpoints de Spring, 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 propiedad rule.fields se puede usar para encontrar campos de clase. Si queremos encontrar campos String 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 puede usar rule.variables. Esta propiedad reportará 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. Se procesarán y reportarán por separado y no se afectarán entre sí. Podemos combinar algunos de los ejemplos de reglas 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 ("Custom deserialization" y "Method invocation by reflection"). Se procesarán como si lo hiciera en dos ejecuciones separadas. Y se generará un informe por regla. Si las reglas tienen el mismo nombre, se reportarán en el mismo archivo.
El proyecto se puede descargar y compilar para agregar reglas personalizadas más complejas en código Java que no estén cubiertas por el formato JSON. Ya hay tres ejemplos en el paquete net.nandgr.cba.visitor.checks. Son CustomDeserializationCheck, DeserializationCheck y InvokeMethodCheck. Puede crear sus propias reglas extendiendo net.nandgr.cba.custom.visitor.base.CustomAbstractClassVisitor.
Como se mencionó anteriormente, los informes se crean por defecto 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 contendrá el argumento especificado o menos (si es el último).
Cada elemento reportado especifica el jar donde se encuentra, el nombre de la clase y el nombre del método (si corresponde). 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 una regla que busca instanciaciones de java.io.File:

Al buscar errores de seguridad, es muy útil tener un grafo de llamadas. Por el momento, se crea un archivo simple compatible con DOT en el directorio report.
El grafo contiene todos los posibles flujos desde los cuales se pueden invocar los problemas encontrados. Por ejemplo, si se usa una regla para encontrar deserialización, se generará un grafo que contiene todas las rutas posibles que conducen 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 forma 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 hace automáticamente por defecto si DOT se encuentra en la ruta del sistema. Si no, se puede instalar DOT en sistemas basados en Debian usando sudo apt-get install graphviz.
Creará un archivo SVG llamado call-graph.svg que se puede convertir a PNG o visualizar usando programas como inkscape o simplemente firefox.
Un ejemplo muy simple del archivo call-graph.dot anterior sería:

Hay algunas limitaciones, como por ejemplo, si el elemento buscado está en un método java.lang.Runnable.run() o similar, no encontrará desde dónde se ejecuta el hilo.
Además, el grafo limpia ciclos para evitar StackOverflowErrors, está hecho de una manera un poco conservadora para que no se agote la memoria del sistema durante el análisis de un directorio grande.
Se agregarán más opciones en versiones futuras.
java -jar cba-cli-<version>.jar -a /ruta/con/jars -f /ruta/con/archivo/json/reglas.json
Para usar reglas Java personalizadas, los nombres de las clases deben especificarse como argumentos de -c.
java -jar cba-cli-<version>.jar -a /ruta/con/jars -c DeserializationCheck
Acepta una lista separada por espacios, 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 /ruta/con/jars -c DeserializationCheck InvokeMethodCheck CustomDeserializationCheck YourCustomRule
java -jar cba-cli-<version>.jar -a /ruta/con/jars -f /ruta/con/archivo/json/reglas.json -c YourCustomRule1 YourCustomRule2
Para encontrar errores, se puede aumentar la verbosidad. Nivel de depuración:
java -jar cba-cli-<version>.jar -a /ruta/con/jars -c YourCustomRule1 -v
Nivel de traza:
java -jar cba-cli-<version>.jar -a /ruta/con/jars -c YourCustomRule1 -vv
Por el momento, el APK debe convertirse primero a JAR para ser analizado.
d2j-dex2jar.sh -f -o app_a_analizar.jar app_a_analizar.apk-a el directorio que contiene el archivo jar convertido.Ya hay un archivo jar ejecutable en el directorio bin en: https://github.com/fergarrui/custom-bytecode-analyzer/blob/master/bin/cba-cli-0.1-SNAPSHOT.jar . Si desea realizar modificaciones o agregar reglas personalizadas, el proyecto se puede compilar haciendo:
git clone https://github.com/fergarrui/custom-bytecode-analyzer.git
cd custom-bytecode-analyzer
mvn clean package
Se generarán dos jars 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