Java ByteCode Analyzer personalizável via JSON Regras. É uma ferramenta de linha de comando que recebe um caminho contendo um ou mais arquivos de jar ou guerra, analisa-os usando as regras fornecidas e gera relatórios HTML com os 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.
O arquivo de regras pode ser especificado usando -f,--custom-file . O arquivo está no formato JSON e possui a seguinte estrutura:
final Você também pode verificar net.nandgr.cba.custom.model.Rules.java para ver a estrutura no código Java.
Já existem várias regras nos exemplos de diretório. De qualquer forma, abaixo estão os exemplos listados para todas as regras.
Se precisarmos encontrar aulas com desserialização personalizada, podemos fazê -lo com bastante facilidade. Uma classe define a desserialização personalizada implementando private void readObject(ObjectInputStream in) . Portanto, precisamos encontrar apenas todas as classes onde esse método é definido. Seria suficiente apenas para definir uma regra como:
{
"rules" : [{
"name" : " Custom deserialization " ,
"methods" : [{
"name" : " readObject " ,
"visibility" : " private " ,
"parameters" : [{
"type" : " java.io.ObjectInputStream "
}]
}]
}]
} Ele relatará métodos com visibilidade private , readObject como nome e um parâmetro do tipo java.io.ObjectOutputStream . Os parâmetros são uma matriz, se mais de um for especificado, todos precisam corresponder para serem relatados. Como temos apenas uma regra, será criado um relatório chamado: personalização-desserialização-0.html será criada.
Nesse caso, uma regra com dois métodos deve ser definida. O mesmo do que no exemplo anterior de deserivação e um novo para combinar private void writeObject(ObjectOutputStream out) . Como mostrado na estrutura JSON acima, as regras da propriedade.Rule.Methods são uma variedade de métodos; portanto, uma regra como essa pode ser escrita:
{
"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 "
}]
}]
}]
} O report da propriedade foi definido como falso para evitar relatar duas vezes para a mesma regra. Estamos usando o segundo método apenas como uma condição, mas apenas os métodos readObject devem ser suficientes para os fins desta regra.
Se uma propriedade não estiver definida, ela sempre corresponderá como verdadeira. Por exemplo, esta regra retornaria todas as definições de métodos:
{
"rules" : [{
"name" : " Method definitions " ,
"methods" : [{
}]
}]
}As invocações de método também podem ser encontradas. O JSON neste caso seria:
{
"rules" : [{
"name" : " String equals " ,
"invocations" : [{
"owner" : " java.lang.String " ,
"method" : {
"name" : " equals "
}
}]
}]
} O owner especifica a classe que contém o método.
Outro exemplo de invocação de método um pouco mais útil que o anterior:
{
"rules" : [{
"name" : " Method invocation by reflection " ,
"invocations" : [{
"owner" : " java.lang.reflect.Method " ,
"method" : {
"name" : " invoke "
}
}]
}]
} É o mesmo do que qualquer invocação de método, mas o nome do método neste caso deve ser <init> .
{
"rules" : [{
"name" : " String instantiation " ,
"invocations" : [{
"owner" : " java.lang.String " ,
"method" : {
"name" : " <init> "
}
}]
}]
}Esta regra encontrará ocorrências de:
[...]
String s = new String ( "foo" );
[...] Neste exemplo, queremos encontrar usos de desserialização (não classes que definem comportamentos de serialização como nos exemplos anteriores). A desserialização acontece quando ObjectInputStream.readObject() é invocado. Por exemplo, neste snippet de código:
ObjectInputStream in = new ObjectInputStream ( fileInputStream );
Object o = in . readObject (); Portanto, precisamos encontrar invocações de método a partir ObjectInputStream chamado readObject . Mas encontrará muitos falsos positivos em um contexto pesquisador, porque quando uma classe define a desserialização personalizada, eles fazem uma invocação para esse método dentro de um método private void readObject(ObjectInputStream in) e isso poluiria demais o relatório. Se queremos excluir esses casos e manter apenas a deserialização genuína, notFrom da propriedade pode ser usada:
{
"rules" : [{
"name" : " Deserialization usage " ,
"invocations" : [{
"owner" : " java.io.ObjectInputStream " ,
"method" : {
"name" : " readObject "
},
"notFrom" : {
"name" : " readObject " ,
"visibility" : " private "
}
}]
}]
} Este arquivo encontrará java.io.ObjectInputStream.readObject() Invocações se a invocação não for feita dentro do método private void readObject(ObjectInputStream in) .
Uma classe compilada com este código não será relatada:
private void readObject ( ObjectInputStream in ) throws IOException , ClassNotFoundException {
Object o = in . readObject ();
}Mas este será relatado:
public Object deserializeObject ( ObjectInputStream in ) throws IOException , ClassNotFoundException {
Object o = in . readObject ();
return o ;
} A from pode ser definida em invocações exatamente da mesma maneira do que notFrom , mas o resultado será o oposto: ele será correspondido apenas se a invocação for feita com o método definido.
A superClass da propriedade pode ser usada neste caso. Se quisermos encontrar todas as classes que estendem javax.servlet.http.HttpServlet , uma regra pode ser:
{
"rules" : [{
"name" : " Java servlets " ,
"superClass" : " javax.servlet.http.HttpServlet "
}]
}
Uma regra pode ser gravada para encontrar classes implementando uma variedade de interfaces. Se mais de uma interface for definida na regra, a classe deve implementar todos eles a serem relatados. Se quisermos encontrar classes que implementem javax.net.ssl.X509TrustManager , a regra seria:
{
"rules" : [{
"name" : " X509TrustManager implementations " ,
"interfaces" : [ " javax.net.ssl.X509TrustManager " ]
}]
} Observe que interfaces são uma matriz ; portanto, adicione as cordas entre os colchetes, por exemplo: ["interface1", "interface2", ...] .
As anotações também são suportadas. As propriedades de várias anotações podem ser definidas em uma regra (encontrando anotações de classe), em métodos o variáveis (parâmetros ou variáveis locais). Se todos eles forem encontrados na classe analisada, será relatada. Por exemplo, se quisermos encontrar pontos de extremidade da primavera, procuraríamos aulas ou métodos anotados com org.springframework.web.bind.annotation.RequestMapping . Então, a regra pode 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 "
}]
}]
}]
} A rule.fields da propriedade.Fields pode ser usada para encontrar campos de classe. Se quisermos encontrar campos privados de string com nomes de senha, uma regra como esta poderia ser usada:
{
"rules" : [{
"name" : " Password fields " ,
"fields" : [
{
"visibility" : " private " ,
"type" : " java.lang.String " ,
"nameRegex" : " (password|pass|psswd|passwd) "
}
]
}]
} Para encontrar variáveis, rule.variables podem ser usadas. Esta propriedade relatará variáveis locais e o método argumenta variáveis. Se quisermos encontrar todas as variáveis do tipo javax.servlet.http.Part , uma regra pode ser:
{
"rules" : [{
"name" : " Servlet upload file " ,
"methods" : [{
"variables" : [{
"type" : " javax.servlet.http.Part "
}]
}]
}]
}Várias regras podem ser definidas no mesmo arquivo JSON. Eles serão processados e relatados separadamente e não se afetarão. Podemos combinar algumas das regras de exemplos 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 "
}
}]
}]
}Aqui, temos duas regras ("deserialização personalizada" e "invocação de métodos por reflexão"). Eles serão processados como se você fizesse isso em duas execuções separadas. E um relatório por regra será gerado. Se as regras tiverem o mesmo nome, elas serão relatadas no mesmo arquivo.
O projeto pode ser baixado e criado para adicionar regras personalizadas mais complexas no código Java que não são cobertas pelo formato JSON. Já existem três exemplos no pacote net.nandgr.cba.visitor.checks . Esses são CustomDeserializationCheck, DeserializationCheck and InvokeMethodCheck . Você pode criar suas próprias regras, estendendo net.nandgr.cba.custom.visitor.base.CustomAbstractClassVisitor .
Como mencionado acima, os relatórios são criados por padrão na pasta report . Toda regra terá um arquivo separado, a menos que tenha o mesmo nome. Se o relatório for muito grande, você poderá dividi-lo usando o parâmetro -i,--items-report <maxItems> , cada um deles manterá o argumento especificado ou menos (se for o último). Todo item relatado, especifica o frasco onde é encontrado, o nome da classe e o nome do método (se for relevante). Ele também mostra a versão descompilada da classe para facilitar uma verificação visual rápida. Exemplo de como os itens são mostrados para uma regra para encontrar java.io.File instanciações:

Ao procurar por bugs de segurança, é muito útil ter um gráfico de chamadas. No momento, um arquivo compatível com pontos simples é criado no diretório report . O gráfico contém todos os fluxos possíveis de onde os problemas encontrados podem ser invocados. Por exemplo, se uma regra para encontrar a desserialização for usada, um gráfico que contém todos os caminhos possíveis que leva ao método que chama a desertalização será gerado.
O arquivo é call-graph.dot e seria assim (este é um exemplo extremamente simples):
graph callGraph {
"demo.callgraph.Class1:method1" -- "demo.callgraph.Class2:method2"
"demo.callgraph.Class3:method3" -- "demo.callgraph.Class2:method2"
}
Para exibi -lo de maneira visual, DOT pode ser usado (ou qualquer software compatível). Por exemplo, para converter o arquivo em svg :
dot -Tsvg call-graph.dot -o call-graph.svg
Isso é feito automaticamente por padrão se o ponto for encontrado no caminho do sistema. Caso contrário, DOT pode ser instalado em sistemas baseados em Debian usando sudo apt-get install graphviz .
Ele criará um arquivo SVG chamado call-graph.svg que pode ser convertido em PNG ou visualizado usando programas como inkscape ou apenas firefox .
Um exemplo muito simples do arquivo de chamadas de arquivo acima, seria: seria:

Existem algumas limitações, como, por exemplo, se o item pesquisado estiver em um método java.lang.Runnable.run() ou similar, ele não encontrará de onde o thread é executado. Além disso, o gráfico está limpando os ciclos para evitar StackOverflowError S, é feito de maneira um pouco conservadora, para que a memória do sistema não seja drenada durante uma análise de um grande diretório.
Mais opções serão adicionadas em versões futuras.
java -jar cba-cli-<version>.jar -a /path/with/jars -f /path/with/json/file/rules.json
Para usar as regras Java personalizadas, os nomes das classes devem ser especificados como argumentos de -c .
java -jar cba-cli-<version>.jar -a /path/with/jars -c DeserializationCheck
Aceita uma lista separada por espaço, para que várias regras personalizadas possam ser definidas (cada uma das regras criará um relatório 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 erros, a verbosidade pode ser aumentada. Nível de depuração:
java -jar cba-cli-<version>.jar -a /path/with/jars -c YourCustomRule1 -v
Nível de rastreamento:
java -jar cba-cli-<version>.jar -a /path/with/jars -c YourCustomRule1 -vv
No momento, o APK deve ser convertido em JAR primeiro para ser analisado.
d2j-dex2jar.sh -f -o app_to_analyze.jar app_to_analyze.apk-a parâmetro, o diretório que contém o arquivo JAR convertido. Já existe um arquivo JAR executável no diretório bin em: https://github.com/fergarrui/custom-bytecode-analyzer/blob/master/bin/cba-cli-0.1-snapshot.jar. Se você deseja fazer modificações ou adicionar regras personalizadas, o projeto pode ser construído fazendo:
git clone https://github.com/fergarrui/custom-bytecode-analyzer.git
cd custom-bytecode-analyzer
mvn clean package
Dois frascos serão gerados na pasta target . cba-cli-<version>.jar contém todas as dependências e é executável. Pode ser executado usando java -jar cba-cli-<version>.jar