УДК 004.021
АВТОМАТИЧЕСКАЯ ИДЕНТИФИКАЦИЯ ИНТЕРФЕЙСА БЛОКА ТРАССЫ В ЗАДАЧЕ ВОССТАНОВЛЕНИЯ МОДЕЛИ АЛГОРИТМА
А.Г.Назаров
Институт электронных и информационных систем НовГУ, Anton.Nazarov@novsu.ru
Восстановление модели алгоритма — наиболее часто решаемая задача в процессе анализа бинарного кода программ. Одной из важнейших задач при этом является идентификация интерфейса функционального блока трассы. В статье описываются разработанные алгоритмы поэтапного решения задачи идентификации интерфейса и обосновывается применение результатов работы алгоритмов в рамках системы динамического анализа бинарного кода TrEx. Ключевые слова: динамический анализ бинарного кода, трассы исполнения, восстановление модели алгоритма, идентификация интерфейса
The algorithm model reclaim is the most often solved problem in the binary code analyzing. The main challenge in algorithm model reclaim is interface identification for function blocks of execution trace. This paper describes algorithms that were developed for each step of interface identification process and implementation of these algorithms in dynamic analyzing system of TrEx binary code. Keywords: binary code dynamic analyzing, execution traces, algorithm model reclaim, interface identification
1. Введение
Задачи анализа бинарного кода программ при отсутствии исходного текста на языке высокого уровня являются наиболее трудными задачами обратной инженерии (Reverse engineering) программного обеспечения (ПО). Подобные задачи в большинстве случаев решаются аналитиком вручную или в полуавтоматическом режиме с использованием различных специализированных программных средств (ин-
струментов) для анализа ПО и требуют наличия у аналитика множественных знаний по языкам программирования, технике компиляции и реализации различных компиляторов, методам запутывания кода (обфускации), различным программным платформам и операционным системам, а также по современным процессорным архитектурам и др.
В данной работе рассматривается задача идентификации интерфейса заданной последовательности машинных инструкций в трассе выполнения про-
граммы при построении модели алгоритма в условиях отсутствия исходных текстов. Описывается алгоритм решения данной задачи, основывающийся на анализе потока данных в трассе.
Все алгоритмы, описываемые в данной работе, разрабатываются и реализуются в рамках системы TrEx [1] — программной среды динамического анализа бинарного кода. Возможности среды позволяют решать задачу восстановления алгоритма, преодолевая при этом комплекс средств защиты от статического анализа. Программные инструменты среды базируются на анализе потоков данных в трассе выполнения программы и позволяют выполнять быстрое про-тотипирование специфических для каждого отдельного случая алгоритмов. Трасса снимается с программы посредством выполнения анализируемой программы на симуляторе процессора (например, QEMU). В результате прогона программы записывается вся последовательность машинных команд, выполняемых процессором, а также состояние регистров процессора перед выполнением каждой инструкции. Кроме того, записывается дополнительная информация о возникающих прерываниях, символьная информация об используемых библиотечных функциях.
Реализация описываемых алгоритмов не привязана к конкретному формату трассы (нет привязки к архитектуре процессора, размерности адреса). Возможности системы TrEx позволяют работать с трассами различных архитектур (IA32, Intel 64, ARM, MIPS, PowerPC). В рамках данной статьи под терминами «инструкция» и «машинная команда» понимается одно и то же.
2. Постановка задачи
Восстановление алгоритма — наиболее часто решаемая аналитиком задача, поскольку с ее решения зачастую начинается решение других задач.
С точки зрения анализа программных реализаций реализация алгоритма — это последовательность инструкций, осуществляющих преобразование входных данных в выходные. Под восстановлением алгоритма будем понимать процесс выделения всех инструкций программы, имеющих отношение к реализации алгоритма, построение и верификацию модели алгоритма [2].
Как описано в [2], под моделью алгоритма в системе TrEx понимается формальное описание структуры программы, допустимых функциональных связей между ее элементами и описание контрольных примеров (примеров вычисления конкретного преобразования входных данных в выходные). Под верификацией модели понимается проверка выполнения ограничений, задаваемых формальным описанием. Такая проверка выполняется путем наложения этих ограничений на реальное поведение программы, сохраненное в виде трассы исполнения программы. При этом ищутся отклонения в описываемом трассой поведении от ожидаемого поведения, описанного моделью.
Для получения модели весь код, входящий в реализацию алгоритма, описывается в виде функцио-
нальных блоков и связей между ними. Для каждого функционального блока восстанавливается его интерфейс, посредством которого он связан с другими блоками. Чаще всего функциональный блок имеет реализацию в виде функции исследуемой программы либо в виде блока кода, который в результате анализа так или иначе представляется в виде функции.
Описание интерфейса функционального блока задается логическим блоком и по сути является статическим представлением функции. Оно содержит: адрес входа, список возможных адресов выхода, список формальных параметров и для каждого — список адресов кода, где можно взять фактическое значение параметра. Статическое представление функции в виде логического блока при анализе трассы накладывается на трассу как шаблон для поиска и описания всех динамических представлений, описываемых блоками трассы. Блок трассы описывает каждый конкретный вызов функции в трассе, с фактическими значениями параметров и фактическими связями между ними.
2.1. Задача идентификации интерфейса блока трассы
В процессе построения модели алгоритма, а именно при наложении логического блока на соответствующие блоки трассы, ключевым моментом является идентификация (восстановление) интерфейса блока трассы. В частном случае в качестве логического блока может выступать конкретная функция в трассе, а в качестве блоков трассы — соответствующие вызовы этой функции.
В [1] описывается модель процессора общего назначения и дается определение элемента адресного пространства. В процессе анализа микроинструкций блока трассы для идентификации его интерфейса необходимо:
— идентифицировать входные и выходные элементы адресного пространства;
— идентифицировать используемые глобальные переменные (читаемые или записываемые) — ячейки памяти, доступ к которым осуществляется без использования указателя стека;
— идентифицировать входной и выходной элементы адресного пространства, которые хранят адрес возврата, а также отследить обращения к этому значению внутри анализируемого блока трассы.
3. Идентификация интерфейса блока трассы
Для решения задачи идентификации интерфейса блока трассы анализируются зависимости по данным для каждой ассемблерной инструкции, входящей в данный блок. При этом все вложенные вызовы функций обрабатываются в той же последовательности, как они представлены в трассе. В этом случае в интерфейс блока трассы попадают все элементы адресного пространства, к которым происходило обращение внутри данного блока. При анализе зависимостей ассемблерных инструкций полученный набор элементов классифицируется по виду на входные и выходные, по расположению на регистры, стек и глобальную память. Такое определение интерфейса по-
зволяет описывать полный набор используемых внутри блока параметров и рассматривать блок трассы как независимый относительно других частей трассы.
Низкоуровневый параметр — это элемент адресного пространства, используемый внутри блока трассы и обладающий следующими характеристиками:
— вид параметра определяет, является ли параметр входным, выходным или одновременно входным и выходным;
— расположение параметра определяет тип элемента адресного пространства, соответствующего параметру: регистр, ячейка стека, глобальный параметр.
Под интерфейсом блока трассы будем понимать полный список низкоуровневых параметров данного блока, позволяющий однозначно определить взаимодействие блока с остальными частями трассы.
Разработанный алгоритм идентификации интерфейса выполняет анализ зависимостей по данным для ассемблерных инструкций, входящих в блок трассы и работает в несколько этапов.
1. Восстановление входов и выходов заданного блока и выявление выходов, значение которых используется (читается) до перезаписи после выхода из заданного блока.
2. Анализ обращений к глобальной памяти и стеку внутри заданного блока.
3. Идентификация элементов, используемых только для сохранения значения внутри заданного блока (callee-save регистры).
4. Идентификация элементов адресного пространства, хранящих адрес возврата на входе и выходе из заданного блока.
5. На основании данных, полученных на предыдущих этапах, составляется список параметров заданного блока.
3.1. Восстановление входов и выходов блока трассы
Под входом понимается элемент адресного пространства, значение которого читается внутри блока трассы до перезаписи. Иными словами, вход — элемент, значение которого пришло извне.
Под выходом понимается элемент адресного пространства, значение которого записывалось внутри блока трассы и было прочитано далее по трассе после выхода из данного блока. Условие использования значения выходного элемента после выхода из блока обусловлено необходимостью минимизации количества ложных срабатываний. Это условие может привести к потере корректных данных, если выходное значение по некоторым причинам не используется. Однако на практике при анализе нескольких блоков трассы, соответствующих одному функциональному блоку, удается идентифицировать подобные случаи [3].
Для идентификации входов и выходов используется однопроходный алгоритм анализа зависимостей по данным для инструкций заданного блока В процессе работы алгоритм выделяет элементы адресного пространства, используемые в зависимостях и соответст-
вующие определениям входов и выходов, описанным выше. Далее выполняется анализ трассы после возврата из заданного блока с целью определения выходных элементов, значение которых используется (читается) до перезаписи. Анализ продолжается до тех пор, пока все найденные выходы заданного блока не будут однозначно идентифицированы как используемые или неиспользуемые (значение перезаписано до чтения).
3.2. Анализ обращений к памяти
Для классификации входов и выходов, расположенных в оперативной памяти (не на регистрах), требуется анализировать все обращения к памяти внутри блока и определять, к какой области памяти принадлежит каждая читаемая/записываемая ячейка. Для решения этой задачи разработан алгоритм, который аналогично алгоритму восстановления входов и выходов анализирует зависимости инструкций блока.
Суть алгоритма заключается в определении читаемых и записываемых ячеек памяти внутри анализируемого блока и их классификации по принадлежности к стеку и глобальной памяти. Для определения обращений к стеку отслеживаются обращения к памяти через стековый регистр. Под стековым регистром понимается регистр-указатель стека, а также регистры, хранящие на момент обращения значение указателя стека (не обязательно текущее значение). Формирование набора стековых регистров выполняется в два этапа.
1. Инициализация набора фактическим регистром-указателем стека, соответствующего формату (архитектуре) данной трассы (например, ESP — для Intel 64, 32-бит).
2. Если какой-либо из регистров в текущем наборе является входом в зависимости копирования, то выходной элемент этой зависимости добавляется в набор стековых регистров.
В процессе анализа по набору стековых регистров идентифицируются фактические обращения к стеку, а также фиксируется и пополняется диапазон адресов таких обращений. Впоследствии этот диапазон используется для дополнительной фильтрации обращений к стеку из всех остальных найденных обращений к памяти. Такая дополнительная фильтрация позволяет классифицировать неявное обращение к стековой памяти без использования стекового регистра. Далее все обращения к памяти, не являющиеся обращениями к стеку, считаются обращениями к глобальной памяти.
3.3. Идентификация callee-save элементов
Сохраняемые регистры (callee-save) — это регистры, значения которых необходимо сохранить перед использованием регистра и восстановить после использования. После сохранения значения эти регистры могут быть использованы для промежуточных вычислений внутри блока (вызова функции).
Элемент адресного пространства является сохраняемым, если выполняются следующие условия:
— при первом обращении к элементу внутри заданного блока трассы значение элемента сохраняется в памяти (в стеке) без изменений;
— ячейка памяти не используется внутри заданного блока трассы (не происходит чтения/записи значения) до момента возврата значения данной ячейки в соответствующий элемент, с которого оно пришло;
— после загрузки значения из ячейки памяти к соответствующему элементу не происходило обращений до конца блока трассы;
— значение ячейки памяти, использовавшейся для сохранения, не читалось до перезаписи.
На первом этапе алгоритм идентификации callee-save элементов адресного пространства по микроинструкциям записи неинициализированных значений в память определяет список потенциальных сохраняемых элементов. Далее отслеживает обращения к соответствующим ячейкам памяти и в соответствии с определением фильтрует список потенциальных сохраняемых элементов. В результате на выходе будет получен список элементов, значения которых сохранялись внутри анализируемого блока трассы.
3.4. Идентификация адреса возврата
Задача идентификации адреса возврата (return address) имеет смысл в случае, когда блок трассы является вызовом конкретной функции. Поэтому в общем случае для произвольного блока трассы необходимо выполнять анализ блока трассы, соответствующего внешнему вызову. Таким образом, первым шагом в работе алгоритма идентификации адреса возврата является определение границ внешнего вызова для заданного блока в трассе. Далее выполняется анализ зависимостей для всех инструкций внешнего вызова с целью определить:
— входной элемент адресного пространства, на котором хранится адрес возврата. Зачастую это некоторая ячейка стека, но для некоторых архитектур (например, ARM) значения адреса возврата может находиться в регистре процессора. Для определения данного элемента анализируются обращения к регистру-указателю следующей инструкции (instruction pointer; EIP — для Intel 64, 32-бит);
— выходной элемент адресного пространства, с которого адрес возврата передается на регистр-указатель следующей инструкции;
— факт изменения адреса возврата внутри блока.
3.5. Формирование списка параметров блока трассы
Используя информацию, полученную на предыдущих этапах работы алгоритма идентификации интерфейса, необходимо сформировать полный список параметров заданного блока. Для этого сначала списки входов и выходов блока фильтруются по списку сохраняемых элементов. Исходя из определения понятно, что каждый сохраняемый элемент одновременно попадет и в список входов, и в список выходов заданного блока. В процессе фильтрации кроме сохраняемых элементов из списка выходов удаляются элементы, перезаписываемые после выхода из блока (неиспользуемые выходы).
Далее по отфильтрованным спискам входов и выходов формируются низкоуровневые параметры
блока. При этом параметры разделяются по виду (входные или выходные) и по местоположению (регистр, стек или глобальная переменная). Также в список параметров добавляются входной и выходной элементы, хранящие адрес возврата внутри блока (с признаком, позволяющим отличить от остальных параметров).
4. Применение
Реализация описанного алгоритма идентификации интерфейса блока трассы применяется при построении формальной модели функционального блока. Кроме того, данный алгоритм используется для восстановления параметров функций и вызовов функций в трассе. При восстановлении параметров функции анализируется каждый вызов данной функции в трассе и с помощью описанного алгоритма идентифицируется интерфейс каждого вызова. Далее информация, полученная для каждого отдельного вызова, по определенным правилам суммируется, и в результате получается интерфейс функции в трассе, по которому определяется список параметров данной функции.
5. Близкие работы
Существуют схожие алгоритмы решения различных задач анализа программного обеспечения.
Одной из таких задач является автоматическое извлечение функций из бинарного кода [3]. На основании построенных прототипов функций по результатам работы дизассемблера или отладчика и по построенному графу потоков управления строится исполняемый прототип анализируемой функции, в частности на языке C (с ассемблерными вставками). Основным недостатком данного подхода является привязка алгоритмов к конкретной архитектуре процессора — Intel x86. Некоторые идеи из [3] были обобщены и использованы в данной работе.
Схожие алгоритмы используются при решении задачи восстановления взаимосвязи между входами и выходами различных вычислительных алгоритмов. Для построения зависимостей используется символьное выполнение [4,5] или слайсинг [5,6].
6. Заключение
В данной статье описывается задача автоматического восстановления модели алгоритма по бинарному коду. Модель алгоритма состоит из функциональных блоков, которые описываются формальными моделями в виде логических блоков и реализациями этих моделей в трассе в виде блоков трассы. Приведено описание разработанного алгоритма автоматической идентификации интерфейса блока трассы. В дальнейшем результаты данной работы будут использованы при решении задачи автоматического восстановления прототипа функционального блока и обеспечения возможности привязки к функциям из dll с помощью символьной информации.
Разработанный алгоритм реализован в модуле идентификации интерфейса среды динамического анализа бинарного кода TrEx. Алгоритм находится на стадии экспертного тестирования полученной реали-
зации на трассах, снятых с программ, для которых имеется исходный текст.
Работа поддержана грантом РФФИ (11-0700353).
1. Падарян В.А., Гетьман А.И., Соловьев М.А. Программная среда для динамического анализа бинарного кода // Тр. Ин-та системного программирования РАН. 2009. Т.17. С.51-72.
2. Тихонов А.Ю. Актуальные задачи анализа программного обеспечения в области информационной безопасности и методы их решения // Тр. Ин-та системного программирования РАН. 2012. Т.22. С.35-50.
3. Caballero J., Johnson N.M., McCamant S., and Song D. Binary code extraction and interface identification for security applications // Proc. of the Network and Distributed System Security Symposium. San Diego, CA, USA. Feb. 2010. 18 p.
4. Caballero J. and Song D. Rosetta: Extracting protocol semantics using binary analysis with applications to protocol replay and NAT rewriting // Technical Report CMU-CyLab-07-014. Cylab, Carnegie Mellon University, Oct. 2007. 14 p.
5. Kolbitsch C., Comparetti P.M., Kruegel C., Kirda E., Zhou X., and Wang X. Effective and efficient malware detection at the end host // USENIX Security Symposium. Montr'eal, Canada, Aug. 2009. 16 p.
6. Lanzi A., Sharif M., and Lee W. K-Tracer: A system for extracting kernel malware behavior // Network and Distributed System Security Symposium. San Diego, CA, USA. Feb. 2009. 16 p.
Bibliography (Transliterated)
1. Padarjan V.A., Get'man A.I., Solov'ev M.A. Programmnaja sreda dlja dinamicheskogo analiza binarnogo koda // Tr. In-ta sistemnogo programmirovanija RAN. 2009. T.17. S.51-72.
2. Tihonov A.Ju. Aktual'nye zadachi analiza programmnogo obespechenija v oblasti informacionnoj bezopasnosti i me-tody ih reshenija // Tr. In-ta sistemnogo programmirovanija RAN. 2012. T.22. S.35-50.
3. Caballero J., Johnson N.M., McCamant S., and Song D. Binary code extraction and interface identification for security applications // Proc. of the Network and Distributed System Security Symposium. San Diego, CA, USA. Feb. 2010. 18 p.
4. Caballero J. and Song D. Rosetta: Extracting protocol semantics using binary analysis with applications to protocol replay and NAT rewriting // Technical Report CMU-CyLab-07-014. Cylab, Carnegie Mellon University, Oct. 2007. 14 p.
5. Kolbitsch C., Comparetti P.M., Kruegel C., Kirda E., Zhou X., and Wang X. Effective and efficient malware detection at the end host // USENIX Security Symposium. Montr'eal, Canada, Aug. 2009. 16 p.
6. Lanzi A., Sharif M., and Lee W. K-Tracer: A system for extracting kernel malware behavior // Network and Distributed System Security Symposium. San Diego, CA, USA. Feb. 2009. 16 p.