Репост из блога Esage Lab
Сутью задачи анализа покрытия кода (Code Coverage) является динамический анализ исполняемой программы с целью выяснения того, какие её части (и какое количество раз) были исполнены. Как не сложно догадаться, к основными областям, в которых решение данной задачи является востребованным, относятся тестирование программного обеспечения и фаззинг, как его более частный вариант. Для специалиста, занимающегося автоматизированным выявлением уязвимостей, полученная в ходе анализа покрытия кода информация особенно ценна из-за того, что она позволяет совершенно однозначно оценивать эффективность методов, применяемых для выявления уязвимостей: чем больше ветвей алгоритмов целевого приложения исполнялось в ходе тестирования - тем и эффективнее эти методы тестирования.
Для программ, которые написаны на компилируемых в машинный код языках, существуют следующие классы инструментов, позволяющих анализировать покрытие кода:
PIN представляет собой спонсируемую компанией Intel бесплатную разработку, которая поставляется с частично открытыми исходными текстами в виде SDK для Linux, Windows и Mac OS X. PIN работает на архитектурах IA-32, IA-64 (Itanium) и Intel64 (x86_64). Структурно его можно разделить на основное приложение (pin.exe), внедряемое в контекст анализируемого процесса ядро (pinvm.dll) и разрабатываемый пользователем инструментальный модуль (он так же внедряется в контекст анализируемого процесса). Инструментальный модуль представляет собой DLL-библиотеку, которая использует API, предоставляемый ядром PIN. Суть работы с API сводится к регистрации обработчиков, которые будут вызываться ядром в ходе исполнения кода исследуемого приложения и осуществлять либо логирование связанной с ходом исполнения информации, либо менять логику исполнения кода приложения любым образом, который разработчик инструментального модуля сочтет нужным.
Работать с анализируемым кодом назначаемые обработчики могут на следующих уровнях функциональности и гранулярности:
Множество примеров разработки инструментальных модулей с подробными комментариями доступны в официальном руководстве.
Как уже было сказано, PIN работает по принципу динамической рекомпиляции кода исследуемой программы: в процессе его исполнения на место нужной инструкции записывается переход (JMP) в pinvm.dll, которая вызывает зарегистрированные инструментальным модулем обработчики и модифицирует инструкцию (или базовый блок) следующую за текущей. Таким образом достигается контроль над исполнением всего кода. Высокая скорость работы исследуемого кода при таких, казалось бы, сложных манипуляциях с ним обеспечивается за счёт отсутствия накладных расходов на взаимодействие между процессами и переключение потока между режимом пользователя и ядра, которые "съедают" основную часть процессорного времени при использовании более традиционных способов трассировки. Что интересно, под PIN нормально работают и сложные многопоточные приложения и даже самомодифицирующийся код, вроде следующего:
Для анализа покрытия кода с использованием PIN нами был разработан относительно несложный инструментальный модуль а так же некоторые другие утилиты, которые были объединены в набор под названием Code Coverage Analysis Tools. В него входят:
Для представления информации из перечисленных выше логов в удобном для работы виде служит скрипт coverage_parse.py, который принимает следующие параметры командной строки:
В качестве опциональных параметров указываются следующие ключи:
PIN так же корректно анализирует исполняемый код, который не принадлежит какому-либо загруженному модулю, и находится в произвольной executable-странице памяти. Для того, что бы получить информацию о таком коде из логов, для coverage_parser.py необходимо указать "?" в качестве имени модуля:
Как видно из примера - в выводе скрипта такой код будет представлен в форме ?<address>.
В последнее время наблюдается следующая тенденция: многие исследователи, разрабатывающие различные системы динамического анализа кода, в качестве демонстрации возможностей этих систем ссылаются на тесты, для которых в качестве объекта используются маленькие приложения вроде calc и notepad или всевозможные вариации hackme.exe/vulnerable.exe из десятка строчек. Чаще всего подобные явления говорят или о невозможности решать с помощью демонстрируемого инструмента задачи из реальной жизни, или о том, что разработчик данного инструмента слишком увлечен ним самим, и воспринимает его в отрыве от практических задач.
Так как подобные тенденции мы не разделяем - в качестве демонстрации возможностей PIN и инструментального модуля Coverager.dll будут приведены результаты их тестирования на построении карты покрытия кода процесса браузера Internet Explorer 8, работающего в среде операционной системы Windows 7 SP1.
Для проведения тестирования была написана программа coverage_test.exe, ключевая часть исходных текстов которой выглядит следующим образом:
Программа coverage_test.exe входит в состав Code Coverage Analysis Tools, её запуск следует производить с помощью сценария coverage_test_with_pin.bat, который инициирует запуск процесса браузера Internet Explorer под контролем PIN и проводит с ним то количество тестовых итераций, которое было указанно в качестве аргумента командной строки к coverage_test_with_pin.bat. Каждая тестовая итерация представляет собой открытие адреса google.com с помощью IWebBrowser2::Navigate() и ожидание завершения полной загрузки страницы.
С данной программой было проведено пять серий тестов с количеством итераций 1, 5, 10, 20 и 30. Ниже представлена гистограмма зависимости времени исполнения процесса браузера (в миллисекундах) от количества итераций:
Как видно, исполнение процесса под контролем PIN обуславливает примерно десятикратное снижение производительности в тестах. Однако, спад производительности становится менее значительным при увеличении числа итераций. Причина подобного, вероятно, кроется в самом принципе динамической рекомпиляции: высокие накладные расходы требуются только тогда, когда какой-либо фрагмент кода исполняется в первый раз, а при его повторное исполнение требует гораздо меньше процессорного времени.
PIN является не единственным фреймворком подобного подобного плана, существует так же разработка под названием DynamoRIO, которая похожа на PIN во всех отношениях. В качестве главных преимуществ DynamoRIO можно выделить более высокую производительность а так же полностью открытый под BSD лицензией исходный код. Однако, актуальная версия DynamoRIO имеет ряд проблем со стабильностью - мне так и не удалось добиться нормальной её работы на Windows 7, из-за чего выбор и пал на PIN.
C данными по сравнительной производительности обеих фреймворков можно ознакомится в заметке на GoVirtual: Performance Comparison of DynamoRIO and Pin.
В заключении стоит отметить, что применение PIN непосредственно во время фаззинга оправданно только в том случае, если каждая итерация фаззера не влечет за собой создание нового процесса исследуемого приложения. Во всех остальных случаях применение анализа покрытия кода на основе PIN целесообразно только на этапе выбора тестовых данных, с которыми будет работать мутационный фаззер (выбирается тот набор данных, который дает максимальный размер покрытия при нормальном исполнении).
Code Coverage Analysis Tools доступны для загрузки на странице проекта в GitHub.
Для программ, которые написаны на компилируемых в машинный код языках, существуют следующие классы инструментов, позволяющих анализировать покрытие кода:
- Профилировщики, использующие промежуточное представление кода (пример: LLVM, Vallgrind). Основным их недостатком является то, что такие профилировщики пригодны только для анализа программ с доступными исходными текстами.
- Инструменты, использующие возможности для отладки, предоставляемые операционной системой (например: системный вызов ptrace() в *nix или Debug API в Windows). В качестве примера программ подобного рода можно привести ProcessStalker, фреймворк PaiMei а так же множество сомнительного вида скриптов, как для отладчиков, так и выполненные в виде отдельных инструментов (blocks.py, Tracer.py и другие). Данные инструменты обладают огромным количеством недостатков, что делает их совершенно непригодными для сколь-либо серьёзного использования. Среди основных: низкая производительность, сложности при анализе исполнения всех модулей в контексте процесса а так же, во многих случаях, потребность предварительного анализа интересующих модулей с помощью внешней программы (IDA Pro).
- Инструменты, на прямую использующие возможности для отладки, заложенные в самой аппаратной архитектуре (пример: CFSearch, IKWYD). Подход как таковой не имеет явно выраженных недостатков, и целесообразность применения на практике той или иной программы определяется исключительно недостатками их реализации.
- Полные эмуляторы, представляющие собой модифицированные виртуальные машины исполняющие весь код операционной системы, в среде которой запущенно тестируемое приложение (пример: TEMU). Основным недостатком данного класса программ является крайне низкая производительность, ведь эмуляции подлежит вся операционная система, а не только тестируемое приложение.
- JiT-рекомпиляторы, осуществляющие анализ хода исполнения программы путём модификации её инструкций в процессе исполнения (пример: PIN Toolkit, DynamoRIO). По моему мнению, данные инструменты свободны от большей части перечисленных выше минусов, и поэтому лучше всего подходят для анализа покрытия кода при фаззинге. Из недостатков можно отметить разве что отсутствие готовых инструментов подобного рода, пригодных для анализа покрытия кода компонентов режима ядра.
Основы работы с PIN
PIN представляет собой спонсируемую компанией Intel бесплатную разработку, которая поставляется с частично открытыми исходными текстами в виде SDK для Linux, Windows и Mac OS X. PIN работает на архитектурах IA-32, IA-64 (Itanium) и Intel64 (x86_64). Структурно его можно разделить на основное приложение (pin.exe), внедряемое в контекст анализируемого процесса ядро (pinvm.dll) и разрабатываемый пользователем инструментальный модуль (он так же внедряется в контекст анализируемого процесса). Инструментальный модуль представляет собой DLL-библиотеку, которая использует API, предоставляемый ядром PIN. Суть работы с API сводится к регистрации обработчиков, которые будут вызываться ядром в ходе исполнения кода исследуемого приложения и осуществлять либо логирование связанной с ходом исполнения информации, либо менять логику исполнения кода приложения любым образом, который разработчик инструментального модуля сочтет нужным.
Работать с анализируемым кодом назначаемые обработчики могут на следующих уровнях функциональности и гранулярности:
- Исполняемые модули. Обработчик, который вызывается при загрузке какого-либо исполняемого модуля в контекст исследуемого процесса регистрируется при помощи функции IMG_AddInstrumentFunction(). Для работы с загружаемым модулем из обработчика используются другие IMG-функции, а для работы с отдельными секциями - функции SEC-семейства.
- Функции. Обработчик, который вызывается для каждой функции исследуемой программы регистрируется при помощи функции RTN_AddInstrumentCall(). Для работы с инструкциями исследуемой функции внутри этого обработчика следует использовать RTN_InsHead()/RTN_InsTail() а так же семейство INS-функций для исследования самих инструкций.
- Базовые блоки (basic blocks). Под базовым блоком подразумевается линейный участок кода находящийся между двумя инструкциями, которые являются или точкой входа вектора исполнения или непосредственно меняют значение EIP (CALL, JMP/Jxx, RET, и так далее). Обработчик, который вызывается для каждого базового блока исследуемой программы, регистрируется при помощи функции TRACE_AddInstrumentFunction(). Назначать обработчики для отдельных базовых блоков можно с помощью BBL_InsertCall(). Для работы с отдельными инструкциями, составляющими базовый блок, в теле обработчика можно использовать функции BBL_InsHead()/BBL_InsNext().
- Собственно, сами инструкции. Обработчик, который вызывается для каждой инструкции исследуемой программы регистрируется при помощи функции INS_AddInstrumentFunction(). Назначать обработчики для отдельных инструкций можно с помощью INS_InsertCall(). Для анализа инструкций PIN использует библиотеку под названием XED. Его API так же доступно для использования в инструментальных модулях.
Множество примеров разработки инструментальных модулей с подробными комментариями доступны в официальном руководстве.
Как уже было сказано, PIN работает по принципу динамической рекомпиляции кода исследуемой программы: в процессе его исполнения на место нужной инструкции записывается переход (JMP) в pinvm.dll, которая вызывает зарегистрированные инструментальным модулем обработчики и модифицирует инструкцию (или базовый блок) следующую за текущей. Таким образом достигается контроль над исполнением всего кода. Высокая скорость работы исследуемого кода при таких, казалось бы, сложных манипуляциях с ним обеспечивается за счёт отсутствия накладных расходов на взаимодействие между процессами и переключение потока между режимом пользователя и ядра, которые "съедают" основную часть процессорного времени при использовании более традиционных способов трассировки. Что интересно, под PIN нормально работают и сложные многопоточные приложения и даже самомодифицирующийся код, вроде следующего:
1 #include <Windows.h> 2 3 #pragma comment(linker,"/ENTRY:f_main") 4 #pragma comment(linker,"/SECTION:.text,EWR") 5 6 VOID Func_1(VOID) 7 { 8 MessageBoxA(0, __FUNCTION__"() called", "Message", 0); 9 } 10 11 VOID Func_2(VOID) 12 { 13 MessageBoxA(0, __FUNCTION__"() called", "Message", 0); 14 } 15 16 __declspec(naked) VOID Func(VOID) 17 { 18 __asm 19 { 20 push 0x90909090 21 ret 22 } 23 } 24 25 DWORD WINAPI f_main(VOID) 26 { 27 DWORD Address = 0; 28 char szMessage[0x50]; 29 30 __asm 31 { 32 call _get_addr 33 34 _get_addr: 35 36 pop eax 37 mov Address, eax 38 } 39 40 wsprintf(szMessage, "Address is 0x%.8x", Address); 41 MessageBoxA(0, szMessage, "Message", 0); 42 43 *(PDWORD)((PUCHAR)&Func + 1) = (DWORD)&Func_1; 44 45 Func(); 46 47 *(PDWORD)((PUCHAR)&Func + 1) = (DWORD)&Func_2; 48 49 Func(); 50 51 return 0; 52 }
Анализ покрытия кода с PIN
Для анализа покрытия кода с использованием PIN нами был разработан относительно несложный инструментальный модуль а так же некоторые другие утилиты, которые были объединены в набор под названием Code Coverage Analysis Tools. В него входят:
- Coverager.dll - Собственно, инструментальный модуль для PIN.
- coverage_test.exe - Тестовое приложение, которое позволяет строить карты покрытия кода для Internet Explorer-а используя PIN и Coverager.dll.
- coverage_parse.py - Скрипт для работы с логами, которые генерирует Coverager.dll.
- symlib.pyd - Используемая в coverage_parse.py небольшая Python библиотека для работы с PDB символами.
- Для начала необходимо загрузить актуальную версию PIN и распаковать архив в произвольную директорию.
- Скопировать Coverager.dll в директорию с файлами PIN.
- Отредактировать сценарий execute_pin.bat, что бы переменная среды PINPATH содержала актуальный путь к директории PIN.
> execute_pin.bat calc.exeПосле завершения работы целевого приложения в текущей директории будут созданы следующие текстовые файлы:
- CoverageData.log - Общая информация об исследуемом процессе (размер карты покрытия, количество потоков и исполняемых модулей, и так далее).
- CoverageData.log.modules - Список полных путей к файлам исполняемых модулей, которые были загружены в контекст исследуемого процесса.
- CoverageData.log.routines - Информация о функциях, которые получали управление в ходе исполнения исследуемого процесса.
- CoverageData.log.blocks - Информация об отдельных базовых блоках инструкций, которые получали управление в ходе исполнения исследуемого процесса.
Для представления информации из перечисленных выше логов в удобном для работы виде служит скрипт coverage_parse.py, который принимает следующие параметры командной строки:
> python coverage_parse.py <log_file_path> <--dump-blocks|--dump-routines> [other_options]В качестве <log_file_path> указывается путь к файлу CoverageData.log, а один из обязательных ключей --dump-blocks или --dump-routines указывает на то, с информацией какого уровня гранулярности следует работать (базовые блоки или функции).
В качестве опциональных параметров указываются следующие ключи:
- --outfile <file_path> - Сохранять информацию в указанный файл, вместо вывода в консоль.
- --order-by-names - Сортировать вывод по имени (адресу) символа, который соответствует функции или базовому блоку (данный режим используется по умолчанию).
- --order-by-calls - Сортировать вывод по количеству вызовов функции или базового блока.
- --modules <modules> - Получать информацию только для указанных модулей (по именам). Для передачи списка из нескольких модулей следует разделять из имена запятой (например: --modules "iexplore.exe, ieframe.dll"). Если параметр --modules не указан - в вывод скрипта будет включена информация обо всех исполняемых модулях исследуемого процесса.
- --skip-symbols - По умолчанию скрипт автоматически загружает PDB-символы для нужных исполняемых модулей и использует их для преобразования адресов всех функций и базовых блоков к виду module!symbol+offset. Параметр --no-symbols позволяет отключить загрузку PDB символов, в таком случае, все адреса в выводе скрипта будут представлены в виде module+ofsset.
> python coverage_parse.py CoverageData.log --dump-routines --modules "calc" --order-by-calls SYMLIB: DLL_PROCESS_ATTACH SYMLIB: Symbols path is ".\Symbols;SRV*.\Symbols*http://msdl.microsoft.com/download/symbols" SYMLIB: Module loaded from "C:\Windows\system32\calc.exe" SYMLIB: 2774 symbols loaded for "C:\Windows\system32\calc.exe" Filtering by module name "calc" [+] Ordering list by number of calls [+] Parsing routines list, please wait... # # Calls count -- Function Name # 11560 -- calc.exe!UIntAdd 7863 -- calc.exe!ATL::CAtlArray<ATL::CAtlRegExp::INSTRUCTION,ATL::CElementTraits<ATL::CAtlRegExp::INSTRUCTION> >::operator[] 6559 -- calc.exe!_destroynum 5780 -- calc.exe!ULongLongToUInt 5780 -- calc.exe!_createnum 5102 -- calc.exe!ATL::CAtlRegExp::MatchToken 3788 -- calc.exe!ATL::CAtlArray<ATL::CAtlRegExp::INSTRUCTION,ATL::CElementTraits<ATL::CAtlRegExp::INSTRUCTION> >::SetCount 3416 -- calc.exe!ATL::CAtlArray<ATL::CAtlRegExp::INSTRUCTION,ATL::CElementTraits<ATL::CAtlRegExp::INSTRUCTION> >::CallConstructors 3404 -- calc.exe!ATL::CAtlRegExp::AddInstruction 3196 -- calc.exe!addnum 2902 -- calc.exe!lessnum 2636 -- calc.exe!memcpy 2470 -- calc.exe!_addnum 2453 -- calc.exe!__security_check_cookie 1743 -- calc.exe!CArgument::getPosition 1084 -- calc.exe!CCalculatorMode::HandleBackgroundForChildWindows 1084 -- calc.exe!CProgrammerMode::ProgDlgProc 918 -- calc.exe!operator delete[] 895 -- calc.exe!CEditBoxInput::HandleDlgProcMessage 704 -- calc.exe!ATL::CAtlArray<FORMULA_PART *,ATL::CElementTraits >::GetAt ... пропущено ещё несколько сотен функций ... 1 -- calc.exe!Gdiplus::Graphics::Graphics 1 -- calc.exe!GdipGetImageGraphicsContext 1 -- calc.exe!Gdiplus::Graphics::SetInterpolationMode 1 -- calc.exe!GdipSetInterpolationMode 1 -- calc.exe!Gdiplus::Graphics::SetSmoothingMode 1 -- calc.exe!GdipSetSmoothingMode 1 -- calc.exe!CProgrammerMode::ShowBitStrip 1 -- calc.exe!CProgrammerMode::NormalizeCurrentBit 1 -- calc.exe!CIO_vUpdateDecimalSymbol [+] Processed modules list: # # Routines count -- Module Name # 422 -- gdiplus.dll 58 -- winmm.dll 107 -- apphelp.dll 221 -- gdi32.dll 370 -- kernel32.dll 196 -- msvcrt.dll 57 -- oleaut32.dll 30 -- dwmapi.dll 692 -- ntdll.dll 60 -- shlwapi.dll 5 -- sechost.dll 92 -- imm32.dll 705 -- calc.exe 28 -- cryptbase.dll 37 -- advapi32.dll 577 -- ole32.dll 33 -- lpk.dll 825 -- msctf.dll 10 -- version.dll 309 -- usp10.dll 278 -- windowscodecs.dll 483 -- uxtheme.dll 138 -- oleacc.dll 95 -- clbcatq.dll 251 -- comctl32.dll 258 -- kernelbase.dll 117 -- shell32.dll 25 -- rpcrt4.dll 465 -- user32.dll [+] DONE SYMLIB: DLL_PROCESS_DETACH
PIN так же корректно анализирует исполняемый код, который не принадлежит какому-либо загруженному модулю, и находится в произвольной executable-странице памяти. Для того, что бы получить информацию о таком коде из логов, для coverage_parser.py необходимо указать "?" в качестве имени модуля:
> python coverage_parse.py CoverageData.log --dump-routines --modules "?" SYMLIB: DLL_PROCESS_ATTACH SYMLIB: Symbols path is ".\Symbols;SRV*.\Symbols*http://msdl.microsoft.com/download/symbols" Filtering by module name "?" [+] Parsing routines list, please wait... # # Calls count -- Function Name # 5 -- ?0x541be250 5 -- ?0x55009060 ...
Как видно из примера - в выводе скрипта такой код будет представлен в форме ?<address>.
Тестирование
В последнее время наблюдается следующая тенденция: многие исследователи, разрабатывающие различные системы динамического анализа кода, в качестве демонстрации возможностей этих систем ссылаются на тесты, для которых в качестве объекта используются маленькие приложения вроде calc и notepad или всевозможные вариации hackme.exe/vulnerable.exe из десятка строчек. Чаще всего подобные явления говорят или о невозможности решать с помощью демонстрируемого инструмента задачи из реальной жизни, или о том, что разработчик данного инструмента слишком увлечен ним самим, и воспринимает его в отрыве от практических задач.
Так как подобные тенденции мы не разделяем - в качестве демонстрации возможностей PIN и инструментального модуля Coverager.dll будут приведены результаты их тестирования на построении карты покрытия кода процесса браузера Internet Explorer 8, работающего в среде операционной системы Windows 7 SP1.
Для проведения тестирования была написана программа coverage_test.exe, ключевая часть исходных текстов которой выглядит следующим образом:
1 int _tmain(int argc, _TCHAR* argv[]) 2 { 3 char *lpszCmdLine = GetCommandLine(); 4 char *lpszCmd = strstr(lpszCmdLine, "@ "); 5 if (lpszCmd == NULL) 6 { 7 /* 8 Командная строка для запуска внешнего приложения не указана, 9 coverage_test был запущен из командной строки. 10 */ 11 char szSelfPath[MAX_PATH], szExecPath[MAX_PATH]; 12 GetModuleFileName(GetModuleHandle(NULL), szSelfPath, MAX_PATH); 13 DWORD dwTestIterations = 1; 14 15 SetConsoleCtrlHandler(CtrlHandler, TRUE); 16 17 /* 18 Регистрируем исполняемый файл текущего процесса как отладчик для IEXPLORE.EXE, 19 при попытке его запуска система запустит приложение а путь к исполняемому файлу 20 IEXPLORE.EXE будет передан ему в качестве аргумента командной строки. 21 */ 22 sprintf_s(szExecPath, "\"%s\" @", szSelfPath); 23 SetImageExecutionDebuggerOption("IEXPLORE.EXE", szExecPath); 24 25 for (int i = 1; i < argc; i++) 26 { 27 if (!strcmp(argv[i], "--iterations") && argc > i + 1) 28 { 29 // количество итераций для теста 30 dwTestIterations = atoi(argv[i + 1]); 31 printf("Iterations count: %d\n", dwTestIterations); 32 } 33 else if (!strcmp(argv[i], "--instrumentation-path") && argc > i + 1) 34 { 35 /* 36 Путь к файлу инструментальной программы (в нашем случае это pin.exe), 37 сохраняем его в параметре реестра и использует при запуске IEXPLORE.EXE 38 */ 39 SetOptionalApp(argv[i + 1]); 40 printf("Instrumentation tool path: \"%s\"\n", argv[i + 1]); 41 } 42 } 43 44 // инициализация COM 45 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); 46 if (FAILED(hr)) 47 { 48 printf("CoInitializeEx() ERROR 0x%.8x\n", hr); 49 goto end; 50 } 51 52 DWORD dwTime = GetTickCount(); 53 54 // редактирование настроек IE: активируем single process mode 55 DisableIeMultiprocessMode(); 56 57 /* 58 Вызов функции, которая открывает в IE тестовый адрес указанное число раз. 59 Для взаимодействия с процессом браузера используется интерфейс IWebBrowser2. 60 */ 61 IeOpenUrl(TEST_URL, dwTestIterations); 62 63 // замер времени исполнения 64 dwTime = GetTickCount() - dwTime; 65 printf("Execution time: %d ms\n", dwTime); 66 67 EnableIeMultiprocessMode(); 68 SetOptionalApp(NULL); 69 } 70 else 71 { 72 /* 73 Текущий процесс был запущен системой в результате попытки 74 запуска IEXPLORE.EXE. 75 */ 76 DWORD dwExitCode = 0; 77 char szOptional[MAX_PATH], szCmdLine[MAX_PATH]; 78 strcpy_s(szCmdLine, MAX_PATH, lpszCmd); 79 80 lpszCmd += 2; 81 82 // удаляем опцию "Debugger" для IEXPLORE.EXE 83 SetImageExecutionDebuggerOption("IEXPLORE.EXE", NULL); 84 85 // получаем путь к инструментальной программе 86 if (GetOptionalApp(szOptional, MAX_PATH)) 87 { 88 sprintf_s(szCmdLine, "\"%s\" %s", szOptional, lpszCmd); 89 } 90 91 // запуск IE c использованием PIN 92 printf("CMDLINE: %s\n", szCmdLine); 93 ExecCmd(&dwExitCode, szCmdLine); 94 } 95 96 end: 97 printf("Press any key to quit...\n"); 98 _getch(); 99 100 return 0; 101 }
Программа coverage_test.exe входит в состав Code Coverage Analysis Tools, её запуск следует производить с помощью сценария coverage_test_with_pin.bat, который инициирует запуск процесса браузера Internet Explorer под контролем PIN и проводит с ним то количество тестовых итераций, которое было указанно в качестве аргумента командной строки к coverage_test_with_pin.bat. Каждая тестовая итерация представляет собой открытие адреса google.com с помощью IWebBrowser2::Navigate() и ожидание завершения полной загрузки страницы.
С данной программой было проведено пять серий тестов с количеством итераций 1, 5, 10, 20 и 30. Ниже представлена гистограмма зависимости времени исполнения процесса браузера (в миллисекундах) от количества итераций:
Как видно, исполнение процесса под контролем PIN обуславливает примерно десятикратное снижение производительности в тестах. Однако, спад производительности становится менее значительным при увеличении числа итераций. Причина подобного, вероятно, кроется в самом принципе динамической рекомпиляции: высокие накладные расходы требуются только тогда, когда какой-либо фрагмент кода исполняется в первый раз, а при его повторное исполнение требует гораздо меньше процессорного времени.
Схожие разработки
PIN является не единственным фреймворком подобного подобного плана, существует так же разработка под названием DynamoRIO, которая похожа на PIN во всех отношениях. В качестве главных преимуществ DynamoRIO можно выделить более высокую производительность а так же полностью открытый под BSD лицензией исходный код. Однако, актуальная версия DynamoRIO имеет ряд проблем со стабильностью - мне так и не удалось добиться нормальной её работы на Windows 7, из-за чего выбор и пал на PIN.
C данными по сравнительной производительности обеих фреймворков можно ознакомится в заметке на GoVirtual: Performance Comparison of DynamoRIO and Pin.
Выводы
В заключении стоит отметить, что применение PIN непосредственно во время фаззинга оправданно только в том случае, если каждая итерация фаззера не влечет за собой создание нового процесса исследуемого приложения. Во всех остальных случаях применение анализа покрытия кода на основе PIN целесообразно только на этапе выбора тестовых данных, с которыми будет работать мутационный фаззер (выбирается тот набор данных, который дает максимальный размер покрытия при нормальном исполнении).
Code Coverage Analysis Tools доступны для загрузки на странице проекта в GitHub.