Mastodon

Monday, November 14, 2011

DbgCb: интеграция приложения/драйвера с удалённым отладчиком ядра

Думаю, для многих программистов и исследователей набор вида "виртуальная машина с Windows + WinDbg в качестве удалённого отладчика режима ядра" давно стал привычным и необходимым инструментом. Для того, что бы выжать из такой связки максимум возможностей и упростить рутинную работу, многие применяют вещи вроде скриптовых движков для отладчика (pykd, IDAPython) или отдельных врапперов для отладочных интерфейсов Windows (PyDbg).

Однако, очень часто возникает задача не только частичной автоматизации работы отладчика, но и необходимость взаимодействия с ним из кода отлаживаемого на удалённой системе приложения или драйвера. В стандартном случае, это самое приложение или драйвер о присутствии удалённого отладчика вообще ничего не знает, но при решении некоторых задач для него было бы полезно получать от отладчика разного рода информацию а так же исполнять в его контексте команды без непосредственного участия человека.

Примеры из жизни, где подобные механизмы интеграции приложения/драйвера с отладчиком были бы кстати:
  • Исследование программ и компонентов операционной системы в процессе их работы, в ходе которого может понадобится устанавливать перехваты кода, и делать достаточно сложную обработку полученных данных, для реализации которой попросту не хватит возможностей (или производительности) скрипта на pykd, использующего точки останова.
  • Иногда, для того что бы быстро проверить какую-то идею/технологию, бывает необходимо прямо в "живой" проект добавить код, работающий с какими-либо не экспортируемыми функциями операционной системы (или недокументированными структурами), но вместо жесткого хардкодинга их адресов/смещений было бы удобно "спросить" об этих адресах/смещениях удалённый отладчик, который может извлечь всю необходимую информацию из доступных ему актуальных отладочных символов.
  • В процессе отладки своего кода бывает необходимо с некоторой периодичностью выполнять определённые манипуляции в отладчике (вывести стек вызовов в определённой точке, снять/установить точку останова в момент наступления какого-то события, итд.), без необходимости отвлекаться на, собственно, сам отладчик.
Для решения таких вот небольших повседневных задач я разработал инструмент, под названием DbgCb (Kernel Debugger Communication Engine), который и хочу представить широкому вниманию в данной заметке.

Функционально DbgCb состоит из двух компонентов:
  • Клиентский код, встраиваемый в приложение или драйвер, из которого доступен ряд функций, совершающих полезные действия с удалённым ядерным отладчиком.
  • Расширение для Windows Debugging Tools - выполнено в виде плагина для отладчика WinDbg/KD, предназначено для обработки запросов от клиентского кода.



Взаимодействие этих компонентов реализовано тривиально: плагин для WinDbg использует интерфейс IDebugEventCallbacks, позволяющий ему отслеживать различные события (срабатывание точек останова, изменения состояния отлаживаемой системы, итд.), а клиентский код, при вызове полезной функции, просто генерирует #DB (debug exception), поместив перед этим в регистры процессора специальные "магические" константы, значения которых и сообщают плагину о том, выполнение какой операции от него требуется.

Клиентский код выполнен в виде небольшого фрагмента на языке C, который может быть встроен в произвольное приложение или драйвер, а так же оформлен как библиотека. В текущей версии, в нём реализованы следующие три полезных функции:
 1 /**
 2  * Execute debuuger command (IDebugControl::Execute()).
 3  */
 4 BOOLEAN dbg_exec(PCHAR lpFormat, ...);
 5 
 6 /**
 7  * Evaluate debuuger expression (IDebugControl::Evaluate()).
 8  */
 9 PVOID dbg_eval(PCHAR lpFormat, ...);
10 
11 /**
12  * Get offset of the some structure field
13  */
14 LONG dbg_field_offset(PCHAR lpFormat, ...);

Пример использования API клиентского кода DbgCb в драйвере режима ядра:
 1 NTSTATUS NTAPI DriverEntry(
 2     PDRIVER_OBJECT DriverObject,
 3     PUNICODE_STRING RegistryPath)
 4 {    
 5     DriverObject->DriverUnload = DriverUnload;
 6     
 7     DbgPrint(__FUNCTION__"()\n");    
 8 
 9     // Test debugger command execution.
10     if (dbg_exec(".printf /D \"<b>Hello from " __FUNCTION__ "()</b>\\n\""))
11     {
12         // another DML example
13         dbg_exec(
14             ".printf /D \"<exec cmd=\\\"!drvobj "IFMT"\\\">"
15             "Show _DRIVER_OBJECT information.</exec>\\n\"", 
16             DriverObject
17         );
18 
19         DbgPrint("Breaking into the kernel debugger (check the DML link above)...\n");
20         DbgBreakPoint();
21 
22         // Test symbol querying.
23         PVOID Addr = dbg_eval("nt!KiDispatchException");
24         if (Addr)
25         {
26             DbgPrint("<?dml?><b>nt!KiDispatchException() is at "IFMT"</b>\n", Addr);
27         }        
28         else
29         {
30             DbgPrint(__FUNCTION__"() ERROR: dbg_eval() fails\n");
31         }
32 
33         // Test structure field offset querying.
34         LONG Offset = dbg_field_offset("nt!_EPROCESS::ImageFileName");
35         if (Offset >= 0)
36         {
37             DbgPrint("<?dml?><b>_EPROCESS::ImageFileName offset is 0x%x</b>\n", Offset);
38         }
39         else
40         {
41             DbgPrint(__FUNCTION__"() ERROR: dbg_field_offset() fails\n");
42         }
43     }    
44     else
45     {
46         DbgPrint(__FUNCTION__"() WARNING: dbgcb extension is not loaded\n");
47     }
48     
49     return STATUS_SUCCESS;
50 }

Исходный код DbgCb доступен в репозитории на GitHub, и включает в себя:
  • ./dbgcb/ - Исходные тексты расширения для отладчика.
  • ./dbgcb.dll, ./dbgcb_x64.dll - Исполняемые файлы расширения для отладчика WinDbg/KD.
  • ./common/ - Исходные тексты клиентского кода.
  • ./dbgcb_app/ - Исходные тексты примера использования клиентского кода в приложении.
  • ./dbgcb_app.exe./dbgcb_app_x64.exe - Исполняемые файлы примера приложения.
  • ./dbgcb_drv/ - Исходные тексты примера использования клиентского кода в драйвере.
  • ./dbgcb_drv.sys./dbgcb_drv_x64.sys - Исполняемые файлы примера драйвера.
Перед запуском примера драйвера или приложения, в среде отлаживаемой операционной системы, необходимо выполнить загрузку модуля DbgCb в отладчик, используя команду ".load dbgcb.dll".

При запуске примера драйвера в Command Window отладчика появятся следующие сообщения, свидетельствующие о том, что Kernel Debugger Communication Engine работает:



Помимо пользы для исследовательских задач, данный инструмент существенно облегчает мне жизнь при анализе уязвимостей и отладке приложений, использующих глубокую интеграцию с системой не совсем "легальными" способами. Возможно, кому-то из вас он так же пригодится.

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


Monday, October 10, 2011

Анонс семинаров в хакспейсе Neuron

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

В октябре я буду проводить в хакспейсе цикл из трёх семинаров, которые будут посвящены технологиям поиска уязвимостей и исследования кода. В данные семинары войдет довольно много материала: часть его была представлена в прошлых публикациях и выступлениях, часть была изложена в виде лекций стажерам Esage Lab этим летом, часть будет дана мной широкой публике впервые. Фактически, все семинары являются экстраполяцией моего опыта и исследований в теме поиска уязвимостей, которая будет разбавлена личным видением того, как лучше организовывать те или иные вещи.

1. Фаззинг: автоматизированный поиск уязвимостей в программном обеспечении

Первый семинар цикла будет рассчитан в большей степени на тех людей, для которых тема фаззинга является новой или малоизученной. В рамках данного семинара слушателям будет дано системное изложение основных концепций фаззинга как технологического процесса и его разновидностей как для узкоспециализированных задач, так и для задач широкого класса. Я подробно рассмотрю технические аспекты реализации различных этапов фаззинга, расскажу про существующие инструменты, которые призваны облегчить работу исследователя, и приведу практические примеры автоматизированного нахождения уязвимостей в реальных программных продуктах.

Дата проведения: 12 Октября (Среда), 19:00

2. Уязвимости в ядре, драйверах и других ring0 компонентах операционных систем семейства Windows NT

Так как исследование безопасности Windows NT является одним из моих самых приоритетных направлений развития и работы - этот семинар должен получиться наиболее интересным с практической точки зрения. Для полноценного усваивания материала от слушателя потребуются наличие общих представлений об архитектуре современных операционных систем и природе уязвимостей в программном коде. В ходе семинара будут подробно рассмотрены типовые уязвимости в ядре и драйверах режима ядра Windows, векторы атак на компоненты режима ядра, особенности работы отдельных механизмов, некорректное обращение с которыми наиболее часто является причиной уязвимостей. Так же я подробно расскажу об архитектуре графической подсистемы Windows (win32k.sys, CSRSS) и уязвимостях в ней. В завершении семинара будут рассмотрены практические аспекты реализации эксплойтов для уязвимостей в компонентах режима ядра а так же различные трудности на этом пути и варианты их решения.

 Дата проведения: 19 Октября (Среда), 19:00

3. Инструменты динамического анализа кода

Поскольку задачи динамического анализа кода очень часто связанны именно с автоматизированным поиском уязвимостей, и являются очень интересными сами по себе - было решено организовать отдельный семинар, посвящённый полностью этой теме. В ходе семинара будут рассмотрены, применительно к решению конкретных типов задач, принципы работы множества инструментов, использующих в своей основе самые разные подходы к динамическому анализу кода: от банальной трассировки, до динамической рекомпиляции и виртуализации. Многие разработки, о которых будет идти речь, являются передним фронтом не только области безопасности программного обеспечения, но и computer science как таковой.

Дата проведения: ???

Мероприятия носят открытый характер. Для посещения семинаров нужно предварительно «зарегистрироваться», написав на neuron@hackspace.ru о том кто вы и откуда, и что планируете прийти к нам в гости.

Я надеюсь, что в дальнейшем регулярное проведение интересных технических семинаров и докладов в хакспейсе Neuron станет традиционным, и к нему, кроме меня, подключатся и другие исследователи которым есть что рассказать, причём не только по темам, имеющим отношение к безопасности.

Thursday, June 23, 2011

3D графика как инструмент реверс-инженера

Интерфейс взаимодействия между человеком и компьютером за прошедшие десятки лет практически не претерпел фундаментальных изменений, и в настоящее время по прежнему представляет собой либо командную строку, либо плоские "окна". Однако, существуют определённые научные дисциплины, обработка данных для которых с помощью компьютера не мыслима без использования 3D графики. К таким дисциплинам относятся различного рода инженерия, клеточная и молекулярная биология, физика высоких частиц и многие другие. Одним словом, все те области, которые требуют работы с большими объёмами данных со сложной структурой, организацией и множественными связями.

При работе в области программирования а в особенности - реверс-инженеринга, технический специалист, вроде бы как, тоже сталкивается с подобными данными, однако, на сегодняшний день 3D графика (или что-либо с ней связанное), как рабочий инструмент, не имеет широкого распространения в области информационной безопасности. В данной записи речь будет идти про интеграцию библиотеки UbiGraph в программный пакет IDA Pro с целью получения средства для визуализации графа связей между процедурами исследуемого кода.

UbiGraph представляет собой универсальную библиотеку, которая предоставляет API для построения динамических трёхмерных графов. Благодаря использованию клиент-серверной архитектуры со взаимодействием через XML-RPC for C он имеет легковесные биндинги для большинства популярных языков программирования. Для того что бы увидеть возможности этой интереснейшей разработки достаточно просмотреть ролики на YouTube по соответствующим ключевым словам.

Ниже продемонстрирован пример кода на Python, использующий UbiGraph API.
 1 import ubigraph
 2 
 3 U = ubigraph.Ubigraph()
 4 U.clear()
 5 
 6 x = U.newVertex(shape="sphere", color="#ffff00")
 7 
 8 smallRed = U.newVertexStyle(shape="sphere", \
 9   color="#ff0000", size="0.2")
10 
11 previous_r = None
12 for i in range(0, 10):
13 
14   r = U.newVertex(style=smallRed, label=str(i))
15   U.newEdge(x, r, arrow=True)
16 
17   if previous_r != None:
18 
19     U.newEdge(r, previous_r, spline=True, stroke="dashed")
20 
21   previous_r = r
22

Однако, UbiGraph обладает рядом недостатков:
  • Закрытый исходный код сервера.
  • Исполняемый файл сервера доступен только для платформ Linux и OS X
  • Проект, судя по всему, заброшен и не развивается с 2008-го года. Связаться с разработчиком что бы разузнать о деталях текущего состояния проекта (или получить на каких-либо условиях его исходный код) мне так и не удалось.
Но даже не смотря на все эти недостатки - не представляется реальным найти альтернативные разработки, которые бы по гибкости и возможностям приближались бы к UbiGraph. В связи с этим было решено провести ряд экспериментов именно с ним, поставив перед собой цель выяснить на практике перспективы применения 3D визуализаций в реверс-инженеринге.

Рассмотрим "обычный" граф процедур в IDA, который был получен при помощи плагина MyNav:



... и вид этого же (относительно простого и наглядного) графа в 3D, который строится модифицированной для работы с сервером UbiGraph версией MyNav 1.1:



На первый взгляд - существенных преимуществ 3D в данном примере не имеет, однако, благодаря тому, что такой граф можно вращать во всех плоскостях и масштабировать без существенных "тормозов" даже при огромном количестве узлов - его становится банально удобнее использовать для навигации по коду исследуемого файла.

Разумеется, удобство является сугубо индивидуальным понятием, и каждый человек принимает относительно него какие-либо выводы опираясь исключительно на собственные ощущения. Я позиционирую результат проделанной мной работы как возможность взглянуть на привычный всем анализ дизасембилрованного кода под новым, интересным углом, а завершенным продуктом, который "совершит революцию", инструмент, о котором идет речь, ни коим образом не является уж точно.

Настройка рабочей среды


Как было упомянуто выше, UbiGraph запускается только на Linux и OS X. Исследователи, которые работают с IDA в среде данных операционных систем, не будут обременены сложными настройками в принципе, поэтому, далее подробно будет рассказано о настройке рабочей среды для UbiGraph на базе Windows.

Для этого потребуется следующий софт:
  • Виртуальная машина с установленным Linux (дистрибутив не принципиален).
  • X-Сервер для Windows (я использую Xming).
  • IDA Pro версии от 5.7 и старше (можно использовать 6.1 Demo c официального сайта).
  • Python плагин для IDA (для версии Python 2.6.x) и, разумеется, установленный в системе интерпретатор Python 2.6.x.
Суть всех манипуляций по настройке заключается в том, что UbiGraph будет запущен в виртуальной машине с Linux, но при этом его окно с графом будет выводится на Windows-хост благодаря работающему на нем X-серверу:


На Linux загружаем и распаковываем последнюю версию UbiGraph:
# wget http://ubietylab.net/files/alpha-0.2.4/UbiGraph-alpha-0.2.4-Linux32-Debian-4.tgz
# tar -xpf UbiGraph-alpha-0.2.4-Linux32-Debian-4.tgz
# cd UbiGraph-alpha-0.2.4-Linux32-Debian-4/bin/
# ls -la
total 1658
drwxr-xr-x 2 user user     120 May 29  2008 .
drwxr-xr-x 6 user user     328 May 29  2008 ..
-rw-r--r-- 1 user user     927 May 29  2008 REQUIRED_LIBS.txt
-rwxr-xr-x 1 user user 1689496 May 29  2008 ubigraph_server

После этого в Linux систему потребуется установить все библиотеки, с которыми динамически слинкован исполняемый файл ubigraph_server:
# ldd ubigraph_server
 linux-gate.so.1 =>  (0xffffe000)
 libpthread.so.0 => /lib/libpthread.so.0 (0xb789d000)
 libglut.so.3 => /usr/lib/libglut.so.3 (0xb7869000)
 libGL.so.1 => //usr/lib/opengl/xorg-x11/lib/libGL.so.1 (0xb7811000)
 libidn.so.11 => /usr/lib/libidn.so.11 (0xb77de000)
 libstdc++.so.6 => /usr/lib/gcc/i686-pc-linux-gnu/4.4.3/libstdc++.so.6 (0xb76ed000)
 libm.so.6 => /lib/libm.so.6 (0xb76c7000)
 libgcc_s.so.1 => /usr/lib/gcc/i686-pc-linux-gnu/4.4.3/libgcc_s.so.1 (0xb76a9000)
 libc.so.6 => /lib/libc.so.6 (0xb7565000)
 libGLU.so.1 => /usr/lib/libGLU.so.1 (0xb74f6000)
 libdl.so.2 => /lib/libdl.so.2 (0xb74f2000)
 /lib/ld-linux.so.2 (0xb78c8000)
 libX11.so.6 => /usr/lib/libX11.so.6 (0xb73d2000)
 libXext.so.6 => /usr/lib/libXext.so.6 (0xb73c1000)
 libXxf86vm.so.1 => /usr/lib/libXxf86vm.so.1 (0xb73bb000)
 libXdamage.so.1 => /usr/lib/libXdamage.so.1 (0xb73b7000)
 libXfixes.so.3 => /usr/lib/libXfixes.so.3 (0xb73b1000)
 libX11-xcb.so.1 => /usr/lib/libX11-xcb.so.1 (0xb73ae000)
 libxcb-glx.so.0 => /usr/lib/libxcb-glx.so.0 (0xb739a000)
 libxcb.so.1 => /usr/lib/libxcb.so.1 (0xb737f000)
 libdrm.so.2 => /usr/lib/libdrm.so.2 (0xb7374000)
 libXau.so.6 => /usr/lib/libXau.so.6 (0xb7370000)
 libXdmcp.so.6 => /usr/lib/libXdmcp.so.6 (0xb736a000)
 librt.so.1 => /lib/librt.so.1 (0xb7360000)

Обычно, данный шаг не влечет за собой каких-либо проблем, и в моем Gentoo ubigraph_server без проблем запустился после установки всего, что фигурирует в выдаче ldd выше. Далее, на Windows следует запустить X-Сервер (Xming), для этого удобно создать ярлык со следующей командной строкой:
"С:\Program Files\Xming\Xming.exe" -clipboard -ac -multiwindow :0

После этого в Linux запускаем ubigraph_server:
# DISPLAY=192.168.254.1:0 ./ubigraph_server
freeglut (./ubigraph_server): Unable to create direct context rendering for window 'Ubigraph'
This may hurt performance.
freeglut (./ubigraph_server): Unable to create direct context rendering for window 'freeglut menu'
This may hurt performance.
1 processors
Using single-level layout.
Running Ubigraph/XML-RPC server.

... где 192.168.254.1 -- IP адрес Windows хоста с работающим X-Сервером. Если настройки верны и все шаги были выполнены правильно -- в Windows системе появится пустое чёрное окно UbiGraph. После этого переходим к настройкам IDA Pro.

Настройка IDA Pro


В качестве основы для плагина, который позволял бы выводить граф процедур с помощью UbiGraph, мной был использован написанный на Python плагин MyNav:

MyNav is a plugin for IDA Pro to help reverse engineers in the most typical task like discovering what functions are responsible of some specifical tasks, finding paths between "interesting" functions and data entry points.

Разработанный патч для данного плагина добавляет в его классы построения графов код взаимодействия с сервером UbiGraph. Архив, который включает в себя помимо патча уже модифицированную версию MyNav 1.1 доступен для загрузки в репозитории проекта IDA-UbiGraph на GitHub.

Установка плагина происходит в несколько шагов:
  1. Установите IDAPython согласно инструкциям в его документации.
  2. Из архива IDA-UbiGraph скопируйте в каталог установленной IDA Pro директорию mynav-1.1-ubigraph.
  3. Из архива IDA-UbiGraph скопируйте в каталог установленной IDA Pro файл ubicallback.pyd -- это Python модуль, который используется для обработки on-click событий по узлам графа в окне UbiGraph.
После этого необходимо отредактировать настройки плагина в файле mynav-1.1-ubigraph/ubiconfig.py, указав в нем адреса Linux хоста (на котором запускается ubigraph_server), и Windows хоста (на котором запускается IDA Pro):
 1 
 2 ubi_graph = None
 3 ubi_is_initialized = False
 4 
 5  # ubigraph server XMLRPC URL
 6 ubi_server_url = "http://192.168.254.100:20738/RPC2"
 7 
 8 # ubigraph client IP address 
 9 # i.e., host that runs IDA
10 ubi_local_addr = "192.168.254.1"
11 
12 #
13 # If you're using IDA and UbiGraph server on the
14 # same machine - just write 127.0.0.1 in both of
15 # ubi_server_url and ubi_local_addr
16 #
17

После выполнения всех перечисленных манипуляций можно запускать IDA Pro и начинать работу.

Загрузка плагина MyNav производится с помощью пункта главного меню "File" -> "Script file...", или по горячей клавише Alt+F7. В появившемся диалоге выбора файла требуется открыть mynav-1.1-ubigraph/mynav.py. Если IDAPython корректно установлен, то после этого в главном меню "Edit" -> "Plugins" появится около двух десятков новых пунктов, соответствующих MyNav. Среди них для нашей задачи наиболее интересен "MyNav: Show browser", который так же доступен по горячей клавише Ctrl+Shift+B.

Открыв вкладку с листингом дизассемблированного кода, установив курсор на начало какой-либо процедуры и нажав Ctrl+Shift+B --  мы вызовем MyNav, который задаст вопрос об глубине рекурсивного сканирования дерева функций при построении их графа:



Обычно, оптимальным является число от 1 до 3, так как при указании большей глубины граф получится слишком объёмным. После нажатия на <OK> MyNav построит граф на отдельной вкладке в окне IDA. В это же время, в окне UbiGraph будет построена 3D версия того же графа. При правильных настройках в IDA Output Window так же появится уведомление об успешном подключении к серверу UbiGraph:



Команды, вызываемые в контекстном меню графа MyNav (например: скрыть/показать строки, или скрыть/показать вызовы API), соответствующим образом сказываются и на графе в UbiGraph. Поскольку в самой IDA может быть открыто множество вкладок с графами, в окне UbiGraph будет отображаться самый последний из них.

Работа с UbiGraph в IDA Pro на видео:


3D graphs in IDA Pro with MyNav and UbiGraph from Dmytro on Vimeo.

К сожалению, проект IDA-UbiGraph носит исключительно демонстрационный характер, так как его полноценное развитие не возможно без доработок и глубокой модификации самого UbiGraph. Однако, я буду рад, если после прочтения данной заметки другие программисты и хакеры обратят своё внимание на такую интересную тему, как 3D визуализации в области анализа кода и реверс-инженеринга, и так же начнут свои исследования в данном направлении.

Полезные ссылки:

Monday, June 13, 2011

Выступление на Positive Hack Days 2011

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

Этой весной в Москве прошло весьма необычное для отечественного itsec-сообщества мероприятие под названием Positive Hack Days. Делать его детальный обзор я смысла не вижу, поскольку всё уже было неоднократно высказано другими людьми, положительные отзывы которых я вполне разделаю.

На Positive Hack Days я выступил с мастер-классом "Автоматический поиск уязвимостей в программах без исходных текстов", материалы к которому я выкладываю в данной записи.

Слайды к выступлению:

Использовавшиеся на мастер-классе утилиты (многие из них я не успел показать) доступны для загрузки в виде архива. Ниже идёт описание наиболее интересного содержимого этого архива.

****


./AnalyzeDumps - Программа для анализа аварийных дампов памяти с использованием отладчика KD.EXE и расширения !exploitable.
Использование:
> python ./AnalyzeDumps/analyze_dumps.py <dumps_dir> [--noisy]

... где <dumps_dir> - директория, в которой содержатся файлы аварийных дампов. При указании ключа --noisy в консоль будут выведены все сообщения отладчика.

По завершению работы программы в текущей директории будет создан файл Analyze.log c общей информацией по всем проанализированным дампам, а в <dumps_dir> - отдельный лог для каждого аварийного дампа с полным выводом отладчика.
Примеры сгенерированных файлов для различных тестовых приложений: ./Analyze_Norman.log, ./Analyze_RPCExample.log и ./Analyze_Visio.log

Программа analyze_dumps.py требует для своей работы следующие сторонние инструменты (так же включены в архив):
  • ./MSECExtensions_1_0_6 - Расширения для отладчика MSEC Debugger Extensions, в состав которых входит !exploitable.
  • ./Microsoft KD - Консольный отладчик от Microsoft, который, обычно, входит в состав пакета Microsoft Debugging Tools for Windows.

****


./Code Coverage Tools и ./pin-2.8-37300-msvc9-ia32_intel64-windows - Основанный на PIN Toolkit набор средств для построения и анализа карты покрытия кода исследуемого приложения.

Подробная информация по использованию:

Так же в директории ./Kcachegrind находится Win32-версия одноимённого приложения, которое позволяет визуализировать карты деревьев вызовов, которые были получены с помощью Code Coverage Tools.

****


./FileFuzz - Инструменты для примитивного мутационного фаззинга на примере файлов архивов форматов 7z, ACE, ARJ, CAB, GZ, LZH, RAR, TGZ, и ZIP.

Генерация некорректных файлов производится с помощью программы ./FileFuzz/MutateGen.exe (исходные тексты доступны в ./FileFuzz/MutateGen/) на основе "хороших" файлов архивов указанных выше форматов, которые находятся в директории ./FileFuzz/TEST/.

Пример запуска MutateGen.exe для генерации файлов:
> MutateGen.exe C:\TEST.zip C:\output_dir -BLOCK_SIZE 1 -BLOCK_RANGE_START 0xa0 -BLOCK_RANGE_END 0xff -BLOCK_RANGE_N 1

Назначения параметров командной строки программы:
  • -FILE_RANGE_START - Файловое смещение для прочитанных из указанного файла начальных данных, начиная с которого MutateGen.exe осуществляет их модификацию (по умолчанию FILE_RANGE_START равен нулю).
  • -FILE_RANGE_END - Файловое смещение, определяющее конец зоны начальных данных, которую модифицирует MutateGen.exe (по умолчанию FILE_RANGE_END равен размеру начального файла).
  • -BLOCK_SIZE - Размер блока данных, которые MutateGen.exe модифицирует за один проход (допустимые значения: 1, 2 и 4).
  • -BLOCK_RANGE_START - Стартовое значение счётчика, которое используется как значение для генерируемого блока данных.
  • -BLOCK_RANGE_END - Конечное значение счётчика, которое используется как значение для генерируемого блока данных.
  • -BLOCK_RANGE_N - Величина инкремента счётчика за одну итерацию.

Таким образом, количество циклов генерации данных (количество модифицируемых байт) для одного начального файла рассчитывается по формуле:

N = (FILE_RANGE_END &~ (BLOCK_SIZE - 1)) - FILE_RANGE_START

Количество блоков данных:

BLOCKS = N / BLOCK_SIZE

Количество итераций для одного цикла генерации данных:

I_MAX = ((BLOCK_RANGE_END &~ (BLOCK_RANGE_N - 1)) - BLOCK_RANGE_START) / BLOCK_RANGE_N

Количество cгенерированных файлов с некорректными данными:

FILES = BLOCKS * I_MAX

Для генерации данных используются сценарии generate_bs_1.bat, generate_bs_2.bat, generate_bs_4.bat и generate_bs_4_hi.bat, которые запускают MutateGen.exe с различными настройками.

Фаззинг архивов на примере актуальной версии антивируса Norman Security Suite продемонстрирован на видео.


Norman Security Suite Fuzzing from eSage Lab on Vimeo.

Аварийные дампы для Norman Security Suite а так же файл вызывающий падение антивирусного сканера находятся в директории ./XcptMon/_Norman_dumps_7z, а лог анализа этих аварийных дампов с помощью analyze_dumps.py - в ./Analyze_Norman.log

****


./InMemoryFuzzer и ./RPCExample - Инструменты и тестовые приложения для демонстрации in-memory фаззинга RPC сервера.
В качестве фаззера используется программа на Python под названием InMemoryFuzzer by sinn3r, которая подробно описана в статье "In Memory Fuzzing".

Для удобства использования в архив включена её версия в виде исполняемого файла, созданного с помощью py2exe (см. директорию ./InMemoryFuzzer/dist).

В качестве уязвимого сервера используется написанная на C++ программа, исходные тексты и исполняемые файлы которой находятся в ./RPCExample/ (там же есть и клиент для этого сервера). Проведение фаззинга осуществляется в несколько шагов:
  1. Запускается сервер с помощью сценария ./RPCExample/run_server.bat
  2. Запускается фаззер с помощью сценария ./RPCExample/fuzz_server.bat
  3. Запускается клиент для RPC сервера (./RPCExample/DebugExe/ContextExampleClient.exe)
  4. В момент обработки запроса от клиента в контексте серверного процесса фаззер модифицирует переданную функции _HelloProc() ASCII-строку, в результате чего будет спровоцировано переполнение буфера в этой функции.
  5. После аварийного завершения работы серверного процесса фаззер создаст отчёт о его падении в директории ./RPCExample/crashbin

****


./MSOffice - Инструменты для более продвинутого мутационного фаззинга на примере приложения Visio из состава Microsoft Office.

Генерация данных для фаззинга осуществляется приложением ./MSOffice/fuzzgen/, которое использует принцип мутационных преобразований имеющихся .VSD-документов с частичным парсингом формата файла-контейнера Microsoft Office. Для парсинга структуры документов используются функция StgOpenStorageEx() и интерфейс IStorage.

В ./MSOffice/_faults/ находятся примеры сгенерированных фаззером .VSD-документов, обработка которых вызывает падение Visio. Лог анализа этих аварийных дампов с помощью analyze_dumps.py находится в ./Analyze_Visio.log

Для запуска процесса фаззинга используется приложение ./MSOffice/fuzzrun/, работа с ним продемонстрирована на видео.


Microsoft Office Visio Fuzzing from eSage Lab on Vimeo.

****


./XcptMon - Приложение для мониторинга исключений и создания аварийных дампов. Именно оно используется для генерации дампов в описанных выше тестах.

XcptMon работает по принципу внедрения DLL библиотеки (./XcptMon/XcptMonDll.dll) в контекст анализируемого процесса при его запуске. DLL, в свою очередь, перехватывает функцию KiUserExceptionDispatcher(). Для внедрения DLL используется параметр реестра "Debugger" в ключе HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<image_name>, где <image_name> - имя исполняемого файла анализируемого процесса.

Использование XcptMon:
> XcptMon.exe <image_name> [options]

... где в качестве опций возможно указать следующие ключи:
  • --dumpsdir <path> - Путь к директории для сохранения аварийных дампов.
  • --logpath <path> - Путь к файлу, в который будет записана информация об исключениях.
  • --remove - Отключить XcptMon для указанного имени процесса.
  • --noexit - Не завершать целевой процесс при возникновении первого #AV исключения.
Пример лога с информацией об исключениях:
[+] Target command line: "TestApp.exe"
[+] DLL injected into the target process 7404
[+] Exit on first #AV: "No"
ModuleInit(): From process 'E:\_tmp\PHD\XcptMon\TestApp.exe' (PID: 7404)
[!] EXCEPTION OCCURS:
STATUS_ACCESS_VIOLATION at 0x0119101c
  Access type: Write
      Address: 0x00000000
EAX=0x00000000 EBX=0x00000000 ECX=0x6f6e215c EDX=0x773270b4
ESI=0x6f6e20c1 EDI=0x01193380 EBP=0x0012fe54

[+] 22398742 bytes of minidump has been written to the "0xC0000005_0x0119101C_17.05_19.28.54.DMP"
[!] EXCEPTION OCCURS:
STATUS_ACCESS_VIOLATION at 0x011910af
  Access type: Write
      Address: 0x00000000
EAX=0x00000000 EBX=0x00000000 ECX=0x6f6e215c EDX=0x773270b4
ESI=0x6f6e20c1 EDI=0x00000000 EBP=0x0012fe54

[+] 22402854 bytes of minidump has been written to the "0xC0000005_0x011910AF_17.05_19.28.54.DMP"
[+] Process exit code: 0xc0000005

Так же библиотека XcptMonDll.dll экспортирует глобальные переменные m_szLastFilePath, m_szLastExceptionCode, m_szLastExceptionAddr, m_szMainModuleVersion и m_szFaultModuleVersion, в которые на момент создания аварийного дампа записывается информация о возникшем исключении и целевом приложении. Эту информацию, для включения в свой лог-файл, получает упоминавшийся выше analyze_dumps.py

****


./ioctl_fuzzer-1.2 - Исходные тексты и исполняемые файлы программы IOCTL Fuzzer, которая предназначена для автоматического выявления узявимостей в драйверах режима ядра, связанных с некорректной обработкой IOCTL-запросов.
В директории ./ioctl_fuzzer-1.2/_exploits/ находятся примеры эксплойтов к найденным спомощью IOCTL Fuzzer уязвимостям.

scsiprot_smart - "Теоретически эксплуатируемая" Local Admin to Ring0 уязвимость в стандартном драйвере Windows atapi.sys при обработке IOCTL запроса IOCTL_SCSI_MINIPORT. Приведенный демонстрационный эксплойт, к сожалению, DoS only.

TM_TmComm_9000402b_exploit - Полнофункциональный Local Admin to Ring0 эксплойт к уязвимости в драйвере актуальной версии (3.0.0.1303 на момент написания текста) антивирусного продукта под названием Trend Micro Titanium Maximum Security.

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.

Wednesday, March 23, 2011

Анализ покрытия кода при поиске уязвимостей

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

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

Для программ, которые написаны на компилируемых в машинный код языках, существуют следующие классы инструментов, позволяющих анализировать покрытие кода:
  1. Профилировщики, использующие промежуточное представление кода (пример: LLVM, Vallgrind). Основным их недостатком является то, что такие профилировщики пригодны только для анализа программ с доступными исходными текстами.
  2. Инструменты, использующие возможности для отладки, предоставляемые операционной системой (например: системный вызов ptrace() в *nix или Debug API в Windows). В качестве примера программ подобного рода можно привести ProcessStalker, фреймворк PaiMei а так же множество сомнительного вида скриптов, как для отладчиков, так и выполненные в виде отдельных инструментов (blocks.py, Tracer.py и другие). Данные инструменты обладают огромным количеством недостатков, что делает их совершенно непригодными для сколь-либо серьёзного использования. Среди основных: низкая производительность, сложности при анализе исполнения всех модулей в контексте процесса а так же, во многих случаях, потребность предварительного анализа интересующих модулей с помощью внешней программы (IDA Pro).
  3. Инструменты,  на прямую использующие возможности для отладки, заложенные в самой аппаратной архитектуре (пример: CFSearch, IKWYD). Подход как таковой не имеет явно выраженных недостатков, и целесообразность применения на практике той или иной программы определяется исключительно недостатками их реализации.
  4. Полные эмуляторы, представляющие собой модифицированные виртуальные машины исполняющие весь код операционной системы, в среде которой запущенно тестируемое приложение (пример: TEMU). Основным недостатком данного класса программ является крайне низкая производительность, ведь эмуляции подлежит вся операционная система, а не только тестируемое приложение.
  5. JiT-рекомпиляторы, осуществляющие анализ хода исполнения программы путём модификации её инструкций в процессе исполнения (пример: PIN Toolkit, DynamoRIO). По моему мнению, данные инструменты свободны от большей части перечисленных выше минусов, и поэтому лучше всего подходят для анализа покрытия кода при фаззинге. Из недостатков можно отметить разве что отсутствие готовых инструментов подобного рода, пригодных для анализа покрытия кода компонентов режима ядра.
В данной заметке я расскажу об относительно малоизвестном но весьма мощном инструментальном средстве под названием PIN Toolkit. PIN является наиболее функциональным и стабильным представителем программ, использующих динамическую рекомпиляцию кода для анализа его исполнения.

Основы работы с 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 перехватывает исполнение целевого процесса начиная с точки входа системного загрузчика - в инструментальных модулях гарантированно безопасное использование только функций самого PIN-а и стандартной С/С++ библиотеки. Попытка использования, к примеру, Win32 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 символами.
Используются эти утилиты следующим образом:
  1. Для начала необходимо загрузить актуальную версию PIN и распаковать архив в произвольную директорию.
  2. Скопировать Coverager.dll в директорию с файлами PIN.
  3. Отредактировать сценарий execute_pin.bat, что бы переменная среды PINPATH содержала актуальный путь к директории PIN.
Для построения карты покрытия кода какого-нибудь приложения следует запустить execute_pib.bat из консоли, передав ему командную строку для запуска целевого приложения в качестве аргумента. Пример:
> execute_pin.bat calc.exe
После завершения работы целевого приложения в текущей директории будут созданы следующие текстовые файлы:
  • CoverageData.log - Общая информация об исследуемом процессе (размер карты покрытия, количество потоков и исполняемых модулей, и так далее).
  • CoverageData.log.modules - Список полных путей к файлам исполняемых модулей, которые были загружены в контекст исследуемого процесса.
  • CoverageData.log.routines - Информация о функциях, которые получали управление в ходе исполнения исследуемого процесса.
  • CoverageData.log.blocks - Информация об отдельных базовых блоках инструкций, которые получали управление в ходе исполнения исследуемого процесса.
Файлы CoverageData.log.routines и CoverageData.log.blocks содержат информацию о функциях/блоках, принадлежащих всем исполняемым модулям, включая файл самого процесса а так же стандартные системные (ntdll, kernel32, user32, и так далее) и любые другие динамически загружаемые библиотеки.

Для представления информации из перечисленных выше логов в удобном для работы виде служит скрипт 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.
Пример использования скрипта для получения информации о наиболее часто вызываемых функциях модуля calc.exe:
> 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.