Mastodon

Tuesday, April 19, 2011

От покрытия кода к дереву вызовов

Репост из блога Esage Lab

В прошлом посте нами было рассказано о практических аспектах применения dynamic binary instrumentation engines (на примере PIN) для анализа покрытия кода при фаззинге. Но очевидно, что столь мощные технологии годятся для решения и более сложных задач: в этот раз речь будет идти про использование PIN для построения карты исполнения кода в виде дерева вызовов различных процедур.

Для UNIX-like операционных систем существует весьма продвинутый инструмент под названием Callgrind, представляющий собой модуль для известного профилировщика Vallgrind. Задачей Callgrind является запись информации о вызове всех процедур исследуемого приложения в процессе его исполнения, при этом, в качестве результата работы, он генерирует текстовый лог, в котором присутствует информация о том, какие процедуры, из каких мест кода и сколько раз были вызваны. Это и называется деревом вызовов. На самом деле, Callgrind способен записывать и много дополнительной информации, включая время исполнения отдельных ветвей алгоритма, однако, именно связи между процедурами исследуемого кода являются наиболее интересной информацией для реверс-инженера. Формат выходного файла Callgrind называется Calltree Profile Format, документация по нему доступна на официальном сайте.

К сожалению, версии Callgrind для Windows на данный момент не существует, поэтому нами было принято решение частично продублировать его функции в ранее разработанном пакете инструментов Code Coverage Analysis Tools.

Основную задачу по сбору информации, как несложно догадаться, выполняет разработанный нами инструментальный модуль для PIN, который, для активации режима записи всех вызовов функций, следует запускать с опцией "-c". Запуск целевого приложения удобно осуществлять с помощью сценария execute_pin_calls.bat, пример:
> execute_pin_calls.bat "C:\Program Files\Internet Explorer\iexplore.exe"
Примечание: перед этим следует не забыть поместить библиотеку Coverager.dll в корневую директорию PIN, и записать полный путь к ней в переменную среды PINPATH, путём редактирования execute_pin_calls.bat.

После завершения работы исследуемого приложения в текущей директории будет создано некоторое количество текстовых файлов с именами вида CoverageData.log.<N>, где <N> - порядковый номер потока, который исполнялся в контексте исследуемого приложения. Эти файлы содержат информацию о дереве вызовов каждого из потоков. Для работы с деревом вызовов следует преобразовать их в Calltree Profile Format с помощью программы coverage_to_callgraph.py, которая принимает следующие параметры командной строки:
> python coverage_to_callgraph.py <log_file_path> <thread_number> [options]
... где <log_file_path> - путь к файлу CoverageData.log, а <thread_number> - порядковый номер потока. Если в результирующий лог требуется включить информацию обо всех потоках, то в качестве  <thread_number> следует указать "*". В качестве опциональных параметров указываются следующие ключи:
  • --modules - Получать информацию только для указанных модулей (по именам). Для передачи списка из нескольких модулей следует разделять их имена запятой (например: --modules "iexplore.exe, ieframe.dll"). Если параметр --modules не указан - в результирующий файл будет включена информация обо всех исполняемых модулях исследуемого процесса.
  • --skip-symbols - По умолчанию coverage_to_callgraph.py автоматически загружает PDB-символы для нужных исполняемых модулей. Параметр --no-symbols позволяет отключить загрузку PDB символов, в таком случае, имена всех процедур в результирующем файле будут представлены в виде module+ofsset.
Пример:
C:\> python coverage_to_callgraph.py CoverageData.log *
SYMLIB: DLL_PROCESS_ATTACH
SYMLIB: Symbols path is "C:\Symbols;SRV*C:\Symbols*http://msdl.microsoft.com/download/symbols"

Code Coverage Analysis Tool for PIN
by Oleksiuk Dmitry, eSage Lab (dmitry@esagelab.com)

[!] Psyco is not available
[+] Input file(s): CoverageData.log.0, CoverageData.log.1, CoverageData.log.10, CoverageData.log.11, CoverageData.log.12, CoverageData.log.13, CoverageData.log.14, CoverageData.log.15, CoverageData.log.16, CoverageData.log.17, CoverageData.log.18, CoverageData.log.19, CoverageData.log.2, CoverageData.log.20, CoverageData.log.21, CoverageData.log.22, CoverageData.log.3, CoverageData.log.4, CoverageData.log.5, CoverageData.log.6, CoverageData.log.7, CoverageData.log.8, CoverageData.log.9
[+] Output file: Callgrind.out
[+] 80 modules readed
[+] Parsing routines list, please wait...

[+] 27806 routines readed
[+] Parsing call tree, please wait...

SYMLIB: Module loaded from "C:\Windows\SYSTEM32\ntdll.dll"
SYMLIB: 4239 symbols loaded for "C:\Windows\SYSTEM32\ntdll.dll"
SYMLIB: Module loaded from "C:\Windows\system32\IEFRAME.dll"
SYMLIB: 33516 symbols loaded for "C:\Windows\system32\IEFRAME.dll"
SYMLIB: Module loaded from "C:\Windows\System32\mshtml.dll"
SYMLIB: 35150 symbols loaded for "C:\Windows\System32\mshtml.dll"
SYMLIB: Module loaded from "C:\Windows\system32\OLEAUT32.dll"
SYMLIB: 3940 symbols loaded for "C:\Windows\system32\OLEAUT32.dll"

... skipped ...

[+] DONE (15 mins., 33 secs.)

SYMLIB: DLL_PROCESS_DETACH

По завершению работы coverage_to_callgraph.py в текущей директории будет создан файл Callgrind.out, работать с которым можно с помощью программы Kcachegrind, которая является наиболее удобным и популярным просмотрщиком логов Callgrind. Windows-версия Kcachegrind так же доступна на SourceForge.

Вид главного окна программы:


Kcachegrind обладает весьма развитыми возможностями для навигации по дереву вызовов, он позволяет:
  • Строить графы вызовов для интересующих процедур.
  • Отображать список функций, которые вызывали текущую (либо непосредственно, либо по цепочке).
  • Отображать список функций, которые были вызваны текущей.
  • Фильтровать отображаемую информацию по имени класса или исполняемого модуля и многое другое.
Выбор типа группировки для списка функций:


Для отрисовки графов в графов в Kcachegrind используется инструмент dot, из состава пакета Graphviz.

Пример графа вызовов относительно текущей процедуры:


Ещё несколько примеров:





Разработанный инструмент хорошо справляется с анализом весьма тяжелых приложений (таких как веб-браузеры), что и было продемонстрировано на примерах выше. Так же его можно использовать для детектирования факта успешной эксплуатации какой-либо уязвимости: Kcachegrind покажет исполненный в результате атаки шеллкод как неизвестную страницу памяти, которая не принадлежит какому-либо исполняемому модулю.
Стоит отметить, что запуск инструментального модуля в режиме записи всех вызовов не приводит к сколь-либо заметному снижению производительности исследуемого приложения по сравнению с режимом построения простой карты покрытия кода (см. результаты тестов, которые были озвучены в предыдущей записи).
Обновленный набор Code Coverage Analysis Tools доступен для загрузки на странице проекта в GitHub.