Solar inCode умеет обнаруживать уязвимости в байткоде Java. Но показать инструкцию байткода, которая содержит уязвимость, мало. Как показать уязвимость в исходном коде, которого нет?

На практике при использовании инструмента поиска уязвимостей к каждой найденной уязвимости нужно применить одно из трех действий:

  • устранить;
  • принять риски;
  • доказать, что это не уязвимость (false positive).

Все три действия требуют некоторого анализа уязвимости (например, нужно понять, как ее устранить и какие риски она несет).

Как проводить такой анализ, если мы находим уязвимости в байткоде Java, а исходного кода у нас нет?

Основным методом поиска уязвимостей при создании inCode был выбран статический анализ — поиск уязвимостей без выполнения кода. При статическом анализе нужно:

  • построить модель кода (промежуточное представление);
  • дополнить модель информацией о данных с применением алгоритмов статического анализа (анализа потока данных, потока управления — dataflow analysis, taint analysis);
  • применить правила поиска уязвимостей (правила говорят, где в модели кода находятся уязвимости, в терминах этой модели и информации, которой она дополнена).

Находя уязвимости в промежуточном представлении, необходимо их отображать в терминах исходного кода для дальнейшего анализа.

Первыми приложениями, в которых Solar inCode искал уязвимости, были Android- и Java-приложения. Поиск уязвимостей в исполняемых файлах достаточно востребован:

  1. заказчику по условиям контракта могут не передавать исходный код;
  2. даже если исходный код передали, на боевой стенд (или в Google Play) может уйти исполняемый код, не соответствующий переданному исходному;
  3. разработчики используют сторонние компоненты без исходного кода, такой код также нужно контролировать.

Поэтому для Android- и Java-приложений в качестве промежуточного представления для статического анализа мы выбрали байткод Java. После компиляции исходного кода мобильного приложения в байткод Java компилятор Dalvik объединяет class-файлы и перекомпилирует код в байткод для Dalvik, получая исполняемый dex-файл. Исполняемый файл вместе с ресурсами и файлом конфигурации упаковывается в пакет apk, который распространяется через Google Play. Существуют средства, реализующие обработку и преобразования пакетов apk: распаковку, расшифровку ресурсов и файлов конфигурации, трансляцию кода Dalvik в байткод Java (apktool, dex2jar).

Байткод также можно получить из исходного кода путем компиляции (так мы делаем при анализе исходного кода Java и Scala). Таким образом, байткод Java хорошо подходит в качестве единого внутреннего представления при анализе исходного и исполняемого кода Java и Android-приложений (на самом деле, также можно проводить анализ всех языков, компилирующихся в байткод Java).

Байткод Java можно декомпилировать, при этом получить код достаточно высокого качества. Есть множество декомпиляторов для Java (JD, fernflower, Procyon). Мы не стали использовать восстановленный Java-код в качестве промежуточного представления, поскольку любые средства декомпиляции допускают ошибки, что может сказаться на качестве поиска уязвимостей.

Итак, мы нашли уязвимости в байткоде Java (о том, как это делается, мы будем писать в следующих статьях). Что делать с результатами?

Мы должны показать их в терминах «исходного» кода, точнее, восстановленного кода высокого уровня. Под уязвимостью здесь мы понимаем набор позиций инструкций в байткоде, которые определяют уязвимость (небезопасный вызов метода, набор инструкций, через который проходит поток небезопасных данных). Таким образом, каждой инструкции в байткоде мы должны сопоставить номер строки в восстановленном коде. В class-файле (файл байткода, соответствующий классу в исходном коде) существует атрибут LineNumberTable, в котором хранится отображение позиций в байткоде на номера строк в исходном коде. Таким образом, для отображения уязвимостей в терминах языка Java нужно, чтобы в байткоде был атрибут LineNumberTable.

При анализе байткода (в том числе полученного из apk-файла) атрибута LineNumberTable может не оказаться. Он может удаляться при компиляции или при обратной трансляции из apk. Хотя это и не так важно — удаленный из байткода LineNumberTable соответствовал исходному коду, который написал разработчик, а не восстановленному «исходному» коду. Это означает, что необходимо восстановить атрибут LineNumberTable в анализируемом байткоде, который будет указывать на восстановленный код.

Базовый алгоритм основывается на построении абстрактного синтаксического дерева декомпилируемого кода (AST) по байткоду Java и выводе исходного кода в процессе обхода AST. В процессе обхода AST (обход в глубину) также происходит сохранение информации о соответствии номеров строк восстанавливаемого кода и позиций инструкций в методах байткода.

В каждом узле дерева мы знаем позицию инструкции в байткоде относительно начала текущего метода и номер строки в файле восстанавливаемого кода. Поэтому при обходе также сохраняются границы методов в восстанавливаемом коде для последующей фильтрации пар «позиция в байткоде метода»-«номер строки в файле».

Отдельно обрабатываются анонимные классы, поскольку они порождают вложенные друг в друга методы. Для этого по окончании обхода происходит анализ вложенности интервалов позиций методов в исходном коде.

Формализация восстанавливаемого отображения и алгоритм восстановления



На практике мы часто получаем проекты, содержащие и исходный код (тогда мы можем получить байткод с таблицей номеров строк путем компиляции), и байткод (различные сторонние компоненты, библиотеки и так далее). Для анализа таких проектов в inCode реализована комбинированная предобработка проекта на Java. Она состоит из следующих шагов:

  • в проекте обнаруживаются все class-файлы, файлы с исходным кодом на java и scala, jar/war файлы с байткодом;
  • в зависимости от настройки сканирования проекта, которую задает пользователь, в список class-файлов включаются class-файлы из jar/war-файлов (чаще всего это означает, что проект анализируется вместе с библиотеками) ;
  • из class-файлов и файлов исходного кода мы получаем полные имена классов, с помощью имен классов происходит сопоставление файлов исходного кода и файлов с байткодом;
  • файлы байткода, для которых не нашлись файлы исходного кода, подвергаются декомпиляции с описанной выше процедурой восстановления информации о номерах строк.

При такой предобработке учитываются анонимные и вложенные классы — один файл исходного кода может соответствовать нескольким файлам байткода.

В результате у нас для каждого файла байткода есть файл с Java-кодом (или восстановленным, или исходным) и таблица номеров строк, связывающая его с этим файлом.

С помощью описанных в статье процедур и алгоритмов любую уязвимость, найденную в Java- или Android-приложении, inCode отображает на исходный код, вне зависимости от того, был ли он передан на анализ.

Похожий подход применяется при анализе бинарных файлов iOS-приложений, однако там все гораздо сложнее: задача декомпиляции бинарного кода архитектуры ARM является гораздо менее исследованной. Этой теме будут посвящены дальнейшие публикации.
Поделиться с друзьями
-->

Комментарии (7)


  1. Regis
    01.11.2016 11:57
    +3

    Не совсем понял о поиске какого класса уязвимостей в статье идет речь.


  1. yaleksar
    01.11.2016 12:36

    Речь идет о программных конструкциях, которые могут нарушить информационную безопасность приложений. Класс уязвимостей ограничен только движком анализа и правилами поиска.

    У нас обнаруживаются уязвимости типа «внедрение» (SQL Injection, XSS, Path traversal и так далее), вызовы небезопасных функций (например, хеширования и шифрования), другие шаблонные небезопасные конструкции (например, отключение проверки сертификатов с помощью X509TrustManager), участки, подозрительные на НДВ (или закладки) — всего более 150 различных уязвимостей для языка Java.


    1. vilgeforce
      01.11.2016 12:40

      В чем небезопасность функций хэширования или шифрования?


      1. yaleksar
        01.11.2016 12:44

        Я не совсем точно выразился в предыдущем комментарии. Уязвимостью является использование небезопасных функций хеширования и шифрования (типа MD5 или DES) для работы с критичными данными (конфиденциальными данными, для генерации идентифицирующих значений).


        1. vilgeforce
          01.11.2016 12:46
          +3

          При этом, я уверен, у вас не отслеживается использование самописных кривых алгоритмов генерации ключей, хэширования или шифрования. И сразу виден сценарий: меняем относительно стойкий DES на XOR строкой из 2 байт и предупреждение уходит, значит код нормальный :-)


          1. yaleksar
            01.11.2016 13:02
            +1

            Реализация самописных алгоритмов может отслеживаться по побочным эффектам — например, использованию результатов работы таких алгоритмов. На практике скорее используют небезопасные стандартные алгоритмы, чем самописные, поэтому большая часть таких уязвимостей обнаруживается.

            Безусловно, инструменты автоматического анализа кода — как статического, так и динамического — не могут выявить абсолютно всех проблем, особенно связанных с некоторыми логическими уязвимостями.
            Для такого глубоко анализа лучше применять ручной аудит кода :)


            1. vilgeforce
              01.11.2016 13:03
              +2

              Я, можно сказать, периодически наблюдаю как вместо стандартных небезопасных начинают городить самописное… Такие лапочки прямо! :-)