Динамический анализ кода с помощью Intel Pin

16/09/20
Технические статьи
Динамический анализ кода с помощью Intel Pin

Динамический анализ кода (также известный как «профилирование») наиболее популярен в областях, где требуется выявить hotspots приложения (пиковые с точки зрения производительности участки программы), гонки потоков (race conditions), найти ошибки при работе с памятью в приложении, оценить фактический расход оперативной памяти, а техники Taint Analysis и In-Memory Fuzzing (техники динамического анализа) позволяют определить какие участки программы наиболее подвержены exploit-ам.

В отличие от более распространенного (и простого в исполнении) статического анализа, динамический анализ обладает рядом преимуществ:

  • Для проведения анализа исходный код программы не обязателен (хотя некоторые инструменты при наличии исходного кода способны собирать больше метрик),
  • Наличие runtime информации (содержимое регистров, содержимое ячеек памяти, известны значения переменных окружения),
  • Диагностика ошибок в многопоточном коде (таких, как борьба потоков за доступ к общим ресурсам, наличие deadlock-ов),
  • Измерение потребляемых программой ресурсов (время исполнения программы или её отдельных частей, число обращений к внешним ресурсам, например, базе данных или файлу),
  • Большая точность в диагностике false-positive ошибок: динамический анализатор не пытается предсказывать поведение программы, как это делает статический анализатор, а при запуске программы просто фиксирует наличие ошибки.

К очевидным недостаткам динамического анализа можно отнести:

  • Невозможность покрыть 100% кода программы (несмотря на существующие вспомогательные техники, например, Consolic Execution, они будут эффективны только для небольших приложений, иначе требуются огромные вычислительные мощности),
  • При наличии исходного кода программы и необходимости внести исправления, найти точное место в коде будет проблематично.

Создание инструментов тестирования при помощи Intel Pin

Для более комплексного анализа приложения, а в некоторых случаях – и в силу прямой необходимости (например, для медицинских приложений для прохождения сертификации FDA, как мы писали ранее) динамический анализ необходимо включать в план тестирования. На рынке существует много готовых решений и инструментов, однако, что делать если необходимого инструмента для анализа приложения на рынке нет или он слишком дорогой или обладает не тем функционалом? В таких случаях на помощь придет Intel Pin.

Intel Pin — это DBI (dynamic binary instrumentation) фреймворк для создания инструментов анализа userspace приложений. Возможности фреймворка позволяют управлять поведением уже скомпилированных программ, встраивая произвольный код, написанный на С/C++, прямо во время исполнения программы, то есть исходный код и перекомпиляция программы не требуются. Pin доступен для ОС Linux, Windows и OS X, также в официальных источниках можно встретить версию Pin для Android.

Инструменты анализа приложений, которые используют Pin API называются Pintools. Для разработки собственного инструмента Intel предоставляет набор разработчика, называемый Pin kits, куда также входят и исходные коды множества инструментов.

Pintool представляет из себя скомпилированный бинарный файл. Для Linux-систем — это разделяемая библиотека с расширением .so, для Windows — динамическая библиотека с расширением .dll. Взаимодействие Intel Pin, Pintool и анализируемой программы можно изобразить в виде схемы:

Динамический анализ кода с помощью Intel Pin

Intel Pin умеет работать в двух режимах — JIT и Probe. JIT (just-in-time) режим более функциональный, но менее производительный (наблюдается существенное замедление относительно исходной программы). Probe режим имеет более ограниченную функциональность, но производительность (связки ‘исходная программа’ + ‘Pintool’) приближается к производительности исходной программы.

Получается, что логически задачу динамического анализа приложения с помощью Intel Pin можно представить в виде схемы ниже:

Динамический анализ кода с помощью Intel Pin

Интересно, что Intel активно использует возможности фреймворка в своих продуктах, например: Intel® VTune ™ Amplifier, Intel® Inspector, Intel® Advisor и Intel® Software Development Emulator (Intel® SDE).

Применение Intel PIN для различных сценариев диагностики уязвимостей ПО

Intel Pin позволяет реализовать различные техники динамического анализа кода с целью диагностики потенциальных уязвимостей ПО. Приведу несколько сценариев из опыта моей команды:

  • Taint Analysis: техника выявления потенциально уязвимых к пользовательскому вводу участков кода с целью обезопасить ПО от использования этих участков в интересах злоумышленников (защита ПО от так называемых exploit-ов). Базируется на идее присвоения каждому объекту в коде (переменной) специальной метки (тега), при условии, что значение этого объекта получено из ненадежного источника, т.е. от пользователя, из сети, из файла. Во время исполнения программы строится так называемое taint tree — метки сливаются, распространяются на другие объекты или удаляются в зависимости от инструкций GET/PUT, LOAD/STORE. Intel Pin позволяет добавить обработчики для каждой такой инструкции. На основе анализа taint tree возможно определить какие части программы потенциально подвержены exploit-ам, что позволяет лучше защитить такие участки от вредоносного воздействия. Если пользовательский ввод — это конфиденциальные сведения, то возможно сделать вывод о жизненном цикле такого типа данных внутри программы.
  • Dynamic Symbolic Execution (или Consolic Execution): техника, применяемая для динамического покрытия кода для подтверждения того, что все ветви кода достижимы и работоспособны. Графически эту задачу можно представить как обход бинарного дерева, где узлы — это условные инструкции (conditional statements, т.е. if операторы), а ребра — последовательность не-условных инструкций (non-conditional statements), т.е. ветви кода. Очевидно, что при первом проходе любой из ветвей кода, значения входных параметров приведут либо к выполнению (true) или к невыполнению условия (false). Для следующего прохода вычисляются значения переменных, при которых условие будет противоположным. Задача Intel Pin добавить соответствующие обработчики для условных инструкций в коде. Фактически, применив данную технику вы покрываете весь код вашей программы с помощью test suite, который сгенерирован автоматически.
  • In-Memory Fuzzing: техника тестирования потенциально уязвимых к пользовательскому вводу участков кода. Основная идея техники базируется на представлении, что почти любая программа будет сломана, если входные данные для неё будут подготовлены случайным образом. В начале выбирается участок кода программы, который необходимо протестировать. Затем определяется множество (диапазон) возможных значений входных параметров. Далее, в случае blackbox тестирования (когда ничего не известно об объекте тестирования) генерируется случайный набор входных параметров из возможного диапазона, для graybox (известна только часть) или whitebox (известно все) тестирования модифицируются известные входные наборы. Теперь участок кода исполняется и, если он приводит к непредсказуемому поведению, т.е. вызывает exception, то входной набор параметров запоминается и результаты анализируются. Далее шаги повторяются для нового набора входных данных. С помощью Intel Pin задача решается довольно просто: добавляются breakpoints до и после тестируемого участка кода; после прохождения первого breakpoint сохраняется контекст и отслеживаются все exceptions в коде. В случае, если произошел exception, набор входных параметров сохраняется и контекст восстанавливается, в случае если exception не произошел — просто восстанавливается контекст. Данная техника позволяет получить устойчивый к пользовательскому вводу код. Один из вариантов применения техники на практике — тестирование parser-а бинарного файла.

Недостатки Intel PIN

Однако, несмотря на возможности широкого применения, Intel Pin не лишен и ряда недостатков.

Во-первых, в нем не предусмотрена возможность работы с IR (Intermediate Representation). IR — это промежуточное представление исходного кода программы с целью последующей обработки (например, оптимизации), где каждая переменная базируется на принципе SSA (Static Single Assignment). Без IR, реализации некоторых вышеприведенных техник значительно усложняются. К примеру, при реализации техники Taint Analysis вместо работы с IR приходится отслеживать все доступы к памяти, чтобы отследить всю цепочку ‘tainted’ объектов.

Во-вторых, при использовании ОС Linux, может возникнуть проблема совместимости с последней версией gcc. Если на своем проекте вы используете компилятор последней версии, то придется подождать обновленной версии Intel Pin.

Еще одна сложность возникнет при условии, что ваш Pintool задуман как многопоточный: для реализации логики работы с потоками придется использовать Pin Thread API. Intel официально заявляет, что на Linux вы не можете использовать pthreads, а если вы пользователь Windows, то придется отказаться от Win32 API.

Наконец, C++11 использовать в Pintools не удастся, по крайней мере, в коробочной версии. Возможность включить C++11 все же есть, если внимательно изучить систему Makefile-ов в Intel Pin. Негативные эффекты этого изменения не обнаружены.

В целом, несмотря на эти ограничения, если перед вами стоит задача разработки своего инструмента для динамического анализа кода, то, безусловно, стоит обратить внимание на Intel Pin, так как его API хорошо документирован, с ним легко работать, а компания Intel прикладывает серьезные усилия в построении инженерного коммьюнити вокруг своих продуктов.

Свяжитесь с нами напрямую

Офисы

Москва

117587, Варшавское ш., д. 125, стр. 16А

Ростов-на-Дону

344002, пр. Буденновский, д. 9, офис 305

Нижний Новгород

603104, ул. Нартова, д. 6, корп. 6, офис 829