
Анализатор Java-байткода, настраиваемый через JSON-правила
Анализатор байт-кода Java, настраиваемый с помощью JSON-правил. Это инструмент командной строки, который принимает путь, содержащий один или несколько файлов Jar или War, анализирует их по заданным правилам и генерирует HTML-отчёты с результатами.
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.
Файл правил может быть указан с помощью аргумента -f,--custom-file. Файл имеет формат JSON и следующую структуру:
finalВы также можете проверить net.nandgr.cba.custom.model.Rules.java, чтобы увидеть структуру в Java-коде.
Уже есть несколько правил в каталоге examples. Ниже приведены примеры для каждого правила.
Если нам нужно найти классы с пользовательской десериализацией, мы можем сделать это довольно легко. Класс определяет пользовательскую десериализацию, реализуя private void readObject(ObjectInputStream in). Таким образом, нам нужно только найти все классы, где определён этот метод. Достаточно просто определить правило:
{
"rules": [{
"name": "Custom deserialization",
"methods": [{
"name": "readObject",
"visibility": "private",
"parameters" : [{
"type" : "java.io.ObjectInputStream"
}]
}]
}]
}
Будут отражены методы с видимостью private, именем readObject и параметром типа java.io.ObjectInputStream. Параметры — это массив; если указано более одного, все они должны совпадать для отчёта. Поскольку у нас только одно правило, будет создан отчёт с именем custom-deserialization-0.html.
В этом случае нужно определить одно правило с двумя методами. То же, что и в предыдущем примере для десериализации, и новый для соответствия private void writeObject(ObjectOutputStream out). Как показано в структуре JSON выше, свойство rules.rule.methods — это массив методов, поэтому можно написать такое правило:
{
"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"
}]
}]
}]
}
Свойство report было установлено в false, чтобы избежать двойного отчёта по одному правилу. Мы используем второй метод только как условие, но отчёт только по методам readObject должен быть достаточен для целей этого правила.
Если свойство не определено, оно всегда будет считаться истинным. Например, это правило вернёт все определения методов:
{
"rules": [{
"name": "Method definitions",
"methods": [{
}]
}]
}
Также можно находить вызовы методов. JSON в этом случае будет таким:
{
"rules": [{
"name": "String equals",
"invocations": [{
"owner": "java.lang.String",
"method": {
"name": "equals"
}
}]
}]
}
Свойство owner указывает класс, содержащий метод.
Ещё один пример вызова метода, немного более полезный:
{
"rules": [{
"name": "Method invocation by reflection",
"invocations": [{
"owner": "java.lang.reflect.Method",
"method": {
"name": "invoke"
}
}]
}]
}
То же самое, что и любой вызов метода, но имя метода в этом случае должно быть <init>.
{
"rules": [{
"name" : "String instantiation",
"invocations" : [{
"owner" : "java.lang.String",
"method" : {
"name" : "<init>"
}
}]
}]
}
Это правило найдёт вхождения:
[...]
String s = new String("foo");
[...]
В этом примере мы хотим найти случаи использования десериализации (а не классы, определяющие поведение сериализации, как в предыдущих примерах). Десериализация происходит при вызове ObjectInputStream.readObject(). Например, в этом фрагменте кода:
ObjectInputStream in = new ObjectInputStream(fileInputStream);
Object o = in.readObject();
Таким образом, нам нужно найти вызовы методов из ObjectInputStream с именем readObject. Но это даст много ложных срабатываний в контексте исследований, потому что когда класс определяет пользовательскую десериализацию, он вызывает этот метод внутри метода private void readObject(ObjectInputStream in), что сильно засоряет отчёт. Если мы хотим исключить эти случаи и оставить только настоящую десериализацию, можно использовать свойство notFrom:
{
"rules": [{
"name": "Deserialization usage",
"invocations": [{
"owner": "java.io.ObjectInputStream",
"method": {
"name": "readObject"
},
"notFrom": {
"name": "readObject",
"visibility": "private"
}
}]
}]
}
Этот файл будет находить вызовы java.io.ObjectInputStream.readObject(), если вызов выполняется не внутри метода private void readObject(ObjectInputStream in).
Класс, скомпилированный с таким кодом, не будет отражён в отчёте:
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
Object o = in.readObject();
}
А этот будет:
public Object deserializeObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
Object o = in.readObject();
return o;
}
Свойство from может быть установлено в вызовах точно так же, как notFrom, но результат будет противоположным: совпадение будет только если вызов сделан из определённого метода.
В этом случае можно использовать свойство superClass. Если мы хотим найти все классы, расширяющие javax.servlet.http.HttpServlet, правило может быть таким:
{
"rules": [{
"name": "Java servlets",
"superClass" : "javax.servlet.http.HttpServlet"
}]
}
Можно написать правило для поиска классов, реализующих массив интерфейсов. Если в правиле определено более одного интерфейса, класс должен реализовать их все, чтобы быть отражённым. Если мы хотим найти классы, реализующие javax.net.ssl.X509TrustManager, правило будет таким:
{
"rules": [{
"name": "X509TrustManager implementations",
"interfaces" : ["javax.net.ssl.X509TrustManager"]
}]
}
Обратите внимание, что interfaces — это массив, поэтому убедитесь, что строки добавлены в квадратных скобках, например: ["interface1", "interface2", ...].
Аннотации также поддерживаются. В правиле можно определить несколько свойств аннотаций (поиск аннотаций классов), в методах или переменных (параметры или локальные переменные). Если все они найдены в анализируемом классе, он будет отражён.
Например, если мы хотим найти конечные точки Spring, мы бы искали классы или методы, аннотированные org.springframework.web.bind.annotation.RequestMapping. Таким образом, правило может быть:
{
"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"
}]
}]
}]
}
Свойство rule.fields можно использовать для поиска полей класса. Если мы хотим найти приватные поля String с именами, связанными с паролями, можно использовать такое правило:
{
"rules": [{
"name" : "Password fields",
"fields" : [
{
"visibility" : "private",
"type" : "java.lang.String",
"nameRegex" : "(password|pass|psswd|passwd)"
}
]
}]
}
Для поиска переменных можно использовать rule.variables. Это свойство будет отражать локальные переменные и переменные-аргументы методов.
Если мы хотим найти все переменные типа javax.servlet.http.Part, правило может быть таким:
{
"rules": [{
"name" : "Servlet upload file",
"methods" : [{
"variables" : [{
"type" : "javax.servlet.http.Part"
}]
}]
}]
}
В одном JSON-файле можно определить несколько правил. Они будут обрабатываться и отражаться отдельно и не будут влиять друг на друга. Мы можем объединить некоторые из предыдущих примеров правил:
{
"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"
}
}]
}]
}
Здесь у нас два правила ("Custom deserialization" и "Method invocation by reflection"). Они будут обрабатываться так, как если бы вы выполнили два отдельных запуска. Для каждого правила будет сгенерирован отчёт. Если правила имеют одинаковое имя, они будут отражены в одном файле.
Проект можно загрузить и собрать, чтобы добавить более сложные пользовательские правила на Java, которые не охватываются JSON-форматом. Уже есть три примера в пакете net.nandgr.cba.visitor.checks: CustomDeserializationCheck, DeserializationCheck и InvokeMethodCheck. Вы можете создать свои собственные правила, расширив net.nandgr.cba.custom.visitor.base.CustomAbstractClassVisitor.
Как упоминалось выше, отчёты по умолчанию создаются в папке report. Для каждого правила будет отдельный файл, если только они не имеют одинакового имени.
Если отчёт слишком большой, его можно разделить с помощью параметра -i,--items-report <maxItems>; каждый файл будет содержать указанное количество элементов или меньше (если это последний).
Каждый отражённый элемент указывает, в каком jar он найден, имя класса и имя метода (если применимо). Также отображается декомпилированная версия класса для быстрой визуальной проверки.
Пример того, как элементы отображаются для правила поиска создания экземпляров java.io.File:

При поиске ошибок безопасности очень полезно иметь граф вызовов. В настоящее время в каталоге report создаётся простой файл, совместимый с DOT.
Граф содержит все возможные пути, из которых могут вызываться найденные проблемы. Например, если используется правило для поиска десериализации, будет сгенерирован граф, содержащий все возможные пути, ведущие к методу, который вызывает десериализацию.
Файл называется call-graph.dot и выглядит следующим образом (это крайне простой пример):
graph callGraph {
"demo.callgraph.Class1:method1" -- "demo.callgraph.Class2:method2"
"demo.callgraph.Class3:method3" -- "demo.callgraph.Class2:method2"
}
Для визуального отображения можно использовать DOT (или любое совместимое программное обеспечение). Например, для преобразования файла в svg:
dot -Tsvg call-graph.dot -o call-graph.svg
Это делается автоматически по умолчанию, если DOT найден в системном PATH. Если нет, DOT можно установить в системах на основе Debian с помощью sudo apt-get install graphviz.
Будет создан SVG-файл с именем call-graph.svg, который можно преобразовать в PNG или просмотреть с помощью таких программ, как inkscape или просто firefox.
Очень простой пример приведённого выше файла call-graph.dot:

Есть некоторые ограничения, например, если искомый элемент находится в методе java.lang.Runnable.run() или подобном, он не найдёт, откуда выполняется поток.
Кроме того, граф очищает циклы, чтобы избежать StackOverflowError; это сделано достаточно консервативно, чтобы не истощать память системы при анализе большой директории.
В будущих версиях будет добавлено больше возможностей.
java -jar cba-cli-<version>.jar -a /path/with/jars -f /path/with/json/file/rules.json
Для использования пользовательских Java-правил необходимо указать имена классов в качестве аргументов -c.
java -jar cba-cli-<version>.jar -a /path/with/jars -c DeserializationCheck
Принимает разделённый пробелами список, поэтому можно определить несколько пользовательских правил (каждое правило создаст отдельный отчёт):
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
Для поиска ошибок можно увеличить детализацию. Уровень отладки:
java -jar cba-cli-<version>.jar -a /path/with/jars -c YourCustomRule1 -v
Уровень трассировки:
java -jar cba-cli-<version>.jar -a /path/with/jars -c YourCustomRule1 -vv
В настоящее время APK сначала необходимо преобразовать в JAR для анализа.
d2j-dex2jar.sh -f -o app_to_analyze.jar app_to_analyze.apk-a каталог, содержащий преобразованный jar-файл.Исполняемый jar-файл уже находится в каталоге bin по адресу: https://github.com/fergarrui/custom-bytecode-analyzer/blob/master/bin/cba-cli-0.1-SNAPSHOT.jar. Если вы хотите внести изменения или добавить пользовательские правила, проект можно собрать следующим образом:
git clone https://github.com/fergarrui/custom-bytecode-analyzer.git
cd custom-bytecode-analyzer
mvn clean package
В папке target будут созданы два jar-файла. cba-cli-<version>.jar содержит все зависимости и является исполняемым. Его можно запустить с помощью java -jar cba-cli-<version>.jar.