2009 г.
Программная среда для динамического анализа бинарного кода
В.А. Падарян, А.И. Гетьман, М.А. Соловьев
Труды Института системного программирования РАН
Аннотация. В данной работе рассматривается среда TrEx, позволяющая выполнять динамический анализ защищенного бинарного кода. Преследуемой целью является получение описания интересующего алгоритма. Среда реализует оригинальную методику анализа и предоставляет пользователю развитый набор программных средств, объединенных в рамках единого графического интерфейса. Подробно рассматриваются некоторые особенности среды, такие как аритектурнонезависимое API для работы средств анализа, возможности свертки вызовов функций, расширение пользовательского интерфейса скриптовым языком.
Содержание
- 1. Введение
- 2. Методика анализа бинарного кода
- 3. Среда TrEx
- 3.1. Модель процессора общего назначения
- 3.2. Свертка функций
- 3.3. Связывание интерфейсов среды TrEx со скриптовым языком
- 4. Дальнейшие работы
- 5. Заключение
- Литература
1. Введение
В выполнении исследований программного обеспечения, оформленного в виде готового к работе бинарного кода, часто без исходных текстов, заинтересованы многие организации, решающие задачи сертификации ПО, а также проблемы отладки своих разработок или их совместимости с другими программами и системами. Всем им требуется проводить анализ бинарного кода различных программ, как по отдельности, так и в комплексе со всей средой исполнения компьютера, иногда вплоть до самого низкоуровневого кода операционной системы. Целью таких исследований является получение информации об особенностях реализации алгоритмов, восстановление реализации этих алгоритмов и их представление в понятном аналитику виде, восстановление протоколов обмена информацией и форматов данных, а также поиск «недокументированных» возможностей, ошибок и уязвимостей.
В качестве примера рассмотрим ситуацию, когда требуется исследовать работу ПО, передающего данные по сети. Поскольку в работу вовлекается код не только самой программы, но и операционной системы и драйверов сетевых интерфейсов, недостаточно исследовать содержимое исполняемого файла программы. Требуется провести анализ всего стека сетевых протоколов, поскольку ошибки реализации и недокументированные возможности одних компонент могут влиять на работу и использоваться другими компонентами. Общий объём бинарного кода, который представляет для аналитика интерес, может составлять десятки мегабайт. Решение этой задачи логично разбивается на решение ряда подзадач:
- Поиск «точек зацепления», т.е. мест в системе, с которых целесообразно начинать исследование.
- Раскрутка от точки зацепления назад (исследование кода, который привёл в точку) или вперёд (исследование кода, который работал после точки) с целью нахождения реализации алгоритма.
- Поиск и анализ данных, влияющих на алгоритм по входу, и данных, образующихся по выходу алгоритма.
- Восстановление алгоритмов, протоколов и форматов данных.
- Выявление недекларированных возможностей, уязвимостей реализации.
Важно отметить, что перечисленные подзадачи возникают при решении практически любых других задач, связанных с исследованием бинарного кода. Главным (и, возможно, единственным до сегодняшнего дня) эффективным методом решения таких задач является комбинация методов статического анализа (используется дизассемблер и декомпилятор) и «ручного» динамического анализа (используется отладчик и некоторые вспомогательные средства – дамперы, мониторы и т.д.).
Статический анализ позволяет выполнять локальные исследования бинарного кода, к которому не применялись способы затруднения анализа. Из способов затруднения анализа отметим следующие:
- использование свойств процессорной архитектуры фон Неймана, в которой исполняемый код и данные без наличия специальной информации не различимы (и, соответственно, задача их различения в таком случае является алгоритмически неразрешимой);
- использование необходимости знания состояния и предыстории возникновения этого состояния вычислительной среды;
- использование «размазывания» кода алгоритма по всему исполняемому модулю или даже нескольким модулям.
- обычные методы затруднения статического анализа (например, переходы по вычисляемым адресам, смешанное кодирование инструкций, когда в одной инструкции может быть закодирована другая, которая может выполниться вместо основной при получении управления);
- упаковка исполняемого кода;
- обфускация (запутывающие преобразования);
- виртуальные машины.
Все перечисленные методы и особенно их комбинации при качественной реализации делают статический анализ совершенно неэффективным вследствие значительной трудоемкости, в результате чего аналитик вынужден применять динамические средства анализа (т.е. отладчик). Процесс отладки более сложен и требует большей квалификации от аналитика, однако позволяет преодолевать некоторые проблемы, не решаемые в статике. Например, аналитик может посмотреть в отладчике, где в интересующий его момент функционирования системы находится код, а где данные, куда и в какой-то момент осуществляется переход по вычисляемому адресу, как выглядит распакованный код. Помимо того, аналитик может в некоторых случаях исследовать алгоритм распаковки, если этот алгоритм будет достаточно компактен и не защищен. Главной проблемой динамического анализа является то, что на данный момент нет доступных средств автоматизации труда, и практически все действия, выполняемые аналитиком, являются ручными операциями. Ситуацию усугубляет применение активных защит от динамического анализа, когда код содержит средства обнаружения работы под отладчиком и реагирования в виде неправильного функционирования, обфускация кода, использование виртуальных машин и многое другое. При правильной реализации такие методы защиты сводят к минимуму вероятность успеха исследования.
Вычислительная мощь компьютеров растёт с каждым днём, и это позволяет реализовывать всё более сложные, комплексные и ресурсоёмкие алгоритмы усложнения и запутывания кода. Человеческих сил и возможностей уже недостаточно для анализа гигантских объёмов информации, содержащихся в исследуемых системах. Развитие средств запутывания алгоритмов, в том числе, и на аппаратном уровне, влечет невозможность решения задач восстановления алгоритмов существующими методами. Средства запутывания все более востребованы на рынке, и сейчас наблюдается активное их развитие и распространение.
В данной работе описывается TrEx – программная среда динамического анализа бинарного кода. Возможности среды позволяют решать задачу восстановления алгоритма, преодолевая при этом комплекс средств защиты от анализа. Программные инструменты среды базируются анализе потоков данных в трассе выполнения программы и позволяют выполнять быстрое прототипирование специфических для каждого отдельного случая алгоритмов.
Статья состоит из пяти разделов. Во втором разделе кратко описывается методика, на основе которой предлагается решать поставленную задачу. В третьем разделе описывается программная система, реализующая эту методику. В четвертом разделе рассматриваются дальнейшие пути развития программной системы. В последнем, пятом, разделе делаются итоговые заключения.
2. Методика анализа бинарного кода
Среда TrEx реализует методику анализа, подробное рассмотрение которой можно найти в работе [1]. Здесь приводится краткое ее описание.
Исследуемая программа выполняется на симуляторе, обеспечивающем потактовую симуляцию инструкций, например, AMD SimNow [2], Virtutech Simics [3] и т.п. Аналитик добивается выполнения исследуемой функциональности на соответствующих начальных данных, и в это время осуществляется сохранение трассы. Трасса представляет собой непрерывную последовательность выполняемых на процессоре инструкций и снимки внутреннего состояния процессора при выполнении каждой инструкции. Таким образом, в трассе содержится значительный массив информации, описывающий все аспекты функционирования исследуемой программы (как заданных до начала трассировки, так и возникших в момент трассировки, например, пришедшие в систему сетевые пакеты). Трассировке практически не мешают никакие из существующих методов защиты исполняемого кода от анализа, потому что эти методы защищают только форму представления защищаемого кода, тогда как функциональность не может быть искажена. В частности, применение обфускации «диспетчер» [4] становится бесполезным, так как базовые блоки программы все равно должны выполняться в определенном порядке, и этот порядок непосредственно отражается в трассе.
Поскольку трасса содержит все состояния процессора, некоторая часть записей в ней относится к выполнению других процессов и коду самой операционной системы. После того как из трассы выделены инструкции, относящиеся к исследуемой программе, ищется место ввода начальных данных соответствующего алгоритма или место вывода результата его работы. Фиксируются те ячейки памяти или регистры, в которых эти данные расположены.
Далее из трассы выделяются только те инструкции, входных операндов которых достигли начальные данные (в случае фиксирования в трассе ввода) или работа которых повлияла на результирующие значения в выходе алгоритма (в случае, когда определялся выход алгоритма).
Данный анализ не является статическим, он представляет собой post-mortem обработку отладочных данных, но преследуемые им цели (лучшее понимание программы) аналогичны целям программного слайсинга. В дальнейшем этот способ фильтрации шагов трассы будем называть слайсингом трассы.
Полученный слайс программы содержит значительно меньшее количество инструкций и уже, как правило, является обозримым. Сокращение размеров трассы составляет, как правило, 3-4 порядка.
Следующим этапом идет построение работоспособного ассемблерного листинга. Из элементов трассы извлекаются инструкции, упорядочиваются по их расположению в памяти, для константных переходов и адресов памяти генерируются метки, строятся пролог и эпилог, обеспечивающие размещение и выдачу начальных и результирующих данных. Следует отметить, что нерешенной на данный момент остается проблема построения листинга для самомодифицирующегося кода.
Ассемблерная программа рассматривается как самодостаточный контрольный пример, выполнение которого способно подтвердить корректность всех проведенных аналитиком операций, поскольку его выполнение должно выдавать те же результаты, что были получены при работе исходной программы. Перед передачей прикладному аналитику контрольный пример может быть подвергнут декомпиляции в язык высокого уровня.
Содержание Вперёд