- PVSM.RU - https://www.pvsm.ru -

На данный момент существует два основных подхода к поиску уязвимостей в приложениях — статический и динамический анализ. У обоих подходов есть свои плюсы и минусы. Рынок приходит к тому, что использовать надо оба подхода — они решают немного разные задачи с разным результатом. Однако в некоторых случаях применение статического анализа ограничено — например, когда нет исходного кода. В этой статье мы расскажем про довольно редкую, но очень полезную технологию, которая позволяет совместить плюсы статического и динамического подходов — статический анализ исполняемого кода.
Уязвимость приложений превратилась в серьезную проблему: по данным Министерства внутренней безопасности США, более 90% успешных кибератак реализуется с использованием различных брешей в приложениях. Самыми известными способами эксплуатации уязвимостей являются:
Анализ программного обеспечения (ПО) на наличие недекларированных возможностей (НДВ) и уязвимостей – основная технология, позволяющая обеспечить безопасность приложения.
Говоря о классических и устоявшихся технологиях анализа ПО на уязвимости и НДВ (на предмет соответствия требованиям информационной безопасности), можно выделить:
Есть еще IAST (интерактивный анализ), однако он по сути является динамическим (в процессе анализа дополнительный агент наблюдает, что происходит во время выполнения приложения). RASP (самозащита приложений Runtime), который также иногда упоминается в ряду средств анализа, является скорее средством защиты.
Динамический анализ (метод «Черного ящика») представляет собой проверку программы во время ее выполнения. У этого подхода можно выделить следующие преимущества.
Но есть и недостатки.
Статический анализ (метод «Белого ящика») представляет собой тип тестирования программы, при котором не происходит выполнение программы.
Перечислим преимущества.
Единственным недостатком метода является наличие ложных срабатываний: необходимость оценки, указывает ли анализатор на реальную ошибку, или есть вероятность, что это срабатывание ложное.
Как мы видим, у обоих методов анализа есть как преимущества, так и недостатки. Однако возможно ли каким-либо образом использовать плюсы этих методов, минимизируя при этом недостатки? Да, если применить бинарный анализ – поиск уязвимостей в исполняемых файлах методом статического анализа.

Бинарный анализ позволяет проводить статический анализ без исходного кода, например, в случае использования кода сторонних подрядчиков. При этом покрытие кода будет полным, в отличие от применения метода динамического анализа. С помощью бинарного анализа можно осуществить проверку использованных в процессе разработки сторонних библиотек, для которых исходного кода нет. Также с помощью бинарного анализа можно проводить контрольную проверку релиза, сравнивая результаты анализа исходного кода из репозитория и исполняемого кода с боевого сервера.
В процессе бинарного анализа происходит преобразование двоичного образа в промежуточное представление (внутреннее представление или модель кода) для дальнейшего анализа. После этого к внутреннему представлению применяются алгоритмы статического анализа. В результате происходит дополнение текущей модели информацией, необходимой для дальнейшего обнаружения уязвимостей и НДВ. На следующем этапе происходит применение правил поиска уязвимостей и НДВ.
Более подробно о схеме статического анализа мы писали в предыдущей статье [2]. В отличие от анализа исходного кода, в котором при построении модели используются элементы теории компиляции (лексический, синтаксический анализ), в бинарном анализе для построения модели используется теория обратной трансляции – дизассемблирование, декомпиляция, деобфускация.
Мы говорим про анализ исполняемых файлов, в которых нет информации отладки (debug info). С debug info задача серьезно упрощается, но если есть debug info, то и исходный код, скорее всего, тоже есть, и задача становится неактуальной.
В этой статье мы называем анализ байткода Java также бинарным анализом, хотя это не совсем корректно. Делаем это для упрощения текста. Безусловно, задача анализа байткода JVM проще анализа бинарного кода C/C++ и Objective-C/Swift. Но общая схема анализа схожа в случае байткода и бинарного кода. Основные сложности, описанные в статье, относятся именно к анализу бинарного кода.
Декомпиляция – это процесс восстановления исходного кода из бинарного кода. Можно говорить про элементы обратной трансляции — дизассемблирование (получение ассемблерного кода из двоичного образа), трансляция ассемблера в трехадресный код или другое представление, восстановление конструкций уровня исходного кода.
Обфускация – преобразования, которые сохраняют функциональность исходного кода, но затрудняют декомпиляцию и понимание полученного бинарного образа. Деобфускация – обратное преобразование. Обфускация может применяться как на уровне исходного кода, так и на уровне бинарного кода.
Начнем немного с конца, но вопрос о просмотре результатов бинарного анализа обычно задается в первую очередь.
Для специалиста, занимающегося анализом бинарного кода, важно отображать уязвимости и НДВ на исходный код. Для этого на завершающем этапе запускается процесс деобфускации (распутывания) в том случае, если были применены запутывающие преобразования, и декомпиляции бинарного кода в исходный. То есть уязвимости можно демонстрировать на декомпилированном коде.
В процессе декомпиляции, даже если мы декомпилируем байткод JVM, часть информации восстанавливается некорректно, поэтому сам анализ происходит на представлении, близком к бинарному коду. Соответственно, встает вопрос: как, находя уязвимости в бинарном коде, локализовать их в исходнике? Решение задачи для байткода JVM мы описывали в статье о поиске уязвимостей в байткоде Java [3]. Решение для бинарного кода аналогичное, то есть вопрос технический.
Повторим важную оговорку — мы говорим про анализ бинарного кода без debug info. В случае присутствия debug info задача существенно упрощается.
Основной вопрос, который нам задают про отображение результатов – достаточно ли декомпилированного кода для того, чтобы понять и локализовать уязвимость?

Ниже приведем несколько мыслей на этот счет.
В качестве вывода — чаще всего получается показать уязвимость так, чтобы ее можно было понять и верифицировать.
Тут уже не будем говорить про байткод: все интересное про него уже сказали выше. Самое интересное – это анализ настоящего бинарного кода. Здесь мы будем говорить про анализ C/C++, Objective-C и Swift как пример.
Существенные сложности возникают уже при дизассемблировании. Важнейший этап – разделение бинарного образа на подпрограммы. Далее выделить в подпрограммах инструкции ассемблера – дело техники. Подробно мы писали про это в статье для журнала «Вопросы кибербезопасности №1(14) – 2016» [4], здесь опишем кратко.
Как пример, будем говорить про архитектуру x86. В ней инструкции не обладают фиксированной длиной. В бинарных образах отсутствует четкое разделение на секции кода и данных: таблицы импорта, таблицы виртуальных функций могут находиться в секции кода, таблицы переходов могут находиться в промежутках между базовыми блоками функций в секции кода. Соответственно, нужно уметь отделить код от данных и понять, где начинаются и где заканчиваются подпрограммы.
Наиболее распространены два метода решения задачи определения стартовых адресов подпрограмм. В первом методе адреса подпрограмм определяются по стандартному прологу (для архитектуры x86 – это push ebp; mov ebp, esp). Во втором методе происходит рекурсивный обход секции кода от точки входа с распознаванием инструкций вызова подпрограмм. Обход осуществляется за счет распознавания инструкций перехода. Также применяют комбинации описанных методов, когда рекурсивный обход запускается из найденных по прологу стартовых адресов.
На практике оказывается, что такие подходы дают достаточно низкий процент распознанного кода, так как стандартный пролог есть далеко не у всех функций, и существуют косвенные вызовы и переходы.
Базовые алгоритмы можно улучшить следующими эвристиками.
Еще одна важная штука, которую нужно делать при обратной трансляции, чтобы потом нормально искать уязвимость, – это распознавать стандартные функции в бинарном образе. Стандартные функции могут быть статически линкованы в образ, а могут быть даже заинлайнены. Основной алгоритм распознавания – это поиск по сигнатурам с вариациями, для решения можно предложить адаптированный алгоритм Ахо-Корасик. Чтобы собрать сигнатуры, нужно заранее проанализировать образы библиотек, собранные с разными условиями, и выделить их них неизменяющиеся байты.
В предыдущем разделе мы разобрали начальный этап обратной трансляции бинарного образа – дизассемблирование. Этап, действительно, начальный, но определяющий. На этом этапе можно потерять часть кода, что потом окажет драматический эффект на результаты анализа.

Дальше происходит много всего интересного. Кратко скажем об основных задачах. Подробно рассказывать не будем: в деталях кроется либо ноу-хау, про которое мы тут явным образом писать не можем, либо не очень интересные технические и инженерные решения.
Мы рассказали, как можно делать статический анализ, когда исходного кода нет. По опыту общения с заказчиками оказывается, что технология очень востребована. Однако технология редкая: задача бинарного анализа нетривиальна, ее решение требует сложных наукоемких алгоритмов статического анализа и обратной трансляции.
Статья подготовлена в соавторстве с Антоном Прокофьевым, аналитиком Solar appScreener
Автор: Ярослав Александров
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/informatsionnaya-bezopasnost/329556
Ссылки в тексте:
[1] Данные статистики: http://www.tadviser.ru/index.php/%D0%A1%D1%82%D0%B0%D1%82%D1%8C%D1%8F:%D0%9F%D0%BE%D1%82%D0%B5%D1%80%D0%B8_%D0%BE%D1%80%D0%B3%D0%B0%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B9_%D0%BE%D1%82_%D0%BA%D0%B8%D0%B1%D0%B5%D1%80%D0%BF%D1%80%D0%B5%D1%81%D1%82%D1%83%D0%BF%D0%BD%D0%BE%D1%81%D1%82%D0%B8
[2] в предыдущей статье: https://habr.com/ru/company/solarsecurity/blog/439286/
[3] в статье о поиске уязвимостей в байткоде Java: https://habr.com/ru/company/solarsecurity/blog/312056/
[4] в статье для журнала «Вопросы кибербезопасности №1(14) – 2016»: https://cyberrus.com/wp-content/uploads/2016/02/53-60-114-16_8.-%D0%90%D0%BB%D0%B5%D0%BA%D1%81%D0%B0%D0%BD%D0%B4%D1%80%D0%BE%D0%B2.pdf
[5] Источник: https://habr.com/ru/post/460949/?utm_source=habrahabr&utm_medium=rss&utm_campaign=460949
Нажмите здесь для печати.