Каждый год в Канаде на конференции CanSecWest проходит конкурс PWN2OWN по
поиску багов с современных ОС и браузерах. За каждый успешный сплойт
предлагается серьезное вознаграждение. И каждый год от участников, имеющих
готовые решения, нет отбоя. Для поиска критических уязвимостей в столь
популярном софте есть много техник и ноу-хау, но большинство из них строятся на
такой простой технике как фаззинг.
Термин Fuzzing появился еще в 1988 году в работе "The Fuzz Generator",
опубликованной Бартом Миллером. Именно в эту светлую голову впервые пришла идея
подсовывать программе заведомо некорректные и зачастую вообще случайные данные,
отлавливая ситуации, когда та не сможет их обработать и вылетит. Эффективность
такого подхода до сих пор велика. Точки ввода данных в программу могут быть
самые разные: текстовая строка, введенная через графический интерфейс, бинарные
данные из файла или, например, значение поля в каком-нибудь сетевом запросе.
Вместо программы может быть драйвер, ActiveX-компонент или, например,
SWF-приложение. В той или иной мере фаззинг сейчас является одним из наиболее
эффективных средств выявления проблем безопасности кода.
Что такое фаззинг?
В зависимости от того, где осуществляется манипуляции с данными, фаззинг
разделяется на множество категорий. Один из самых простых видов — файловый
фаззинг, подразумевающий, что некой программе предлагается открыть некорректно
составленный файл. Возьмем, к примеру, прогу для просмотра картинок. Если взять
JPEG-файл и интересным образом поменять несколько байтов, то эта программа
вполне возможно выругается: "Что это ты мне подсунул?". А, возможно, вообще не
сможет его переварить и вылетит, к примеру, с проблемой переполнения
буфера. Значит, ее теоретически можно расковырять, доведя дело до рабочего
эксплойта.
Если говорить о способе манипуляции с данными, то фаззинг распределяется на
генерацию и мутацию. Генерация — это случайным образом придуманный набор байтов,
который подсовывается той же проге для просмотра картинок со словами: "Это на
самом деле JPEG-файл, читай его". Мутация — прием намного более изящный,
подразумевающий внесение изменений в "хороший", то есть вполне корректный файл.
Если в случае с файловым фаззингом еще можно использовать "генерацию", то в
таких вещах, как сетевые протоколы, имеет смысл применять исключительно подход
мутации. Более того, крайне желательно иметь представление, за что отвечает то
или иное поле пакета и намеренно манипулировать с теми данными, которые могут
быть некорректно обработаны. В зависимости от интеллекта, фаззеры бывают глупые
и умные:
- Глупый (dump) фаззер ничего не знает о структуре файлов. Если говорить о
сетевых протоколах, то единственное, что он может сделать — это изменить
несколько байтов в исходном пакете и отправить его в надежде, что это может
вызвать сбой.
- Умный (smart) фаззер имеет некоторое представленные о структуре данных.
Вместо того, чтобы полностью надеяться на удачу, он может играться только с
теми данными, которые отвечают, например, за размер буфера. Или подставлять
в поля такие значения, которые заведомо, с учетом известного формата, будут
некорректными.
Фаззим файлы
Одна из простейших утилит для реализации глупого фаззинга —
MiniFuzz. Проект разработан внутри Microsoft для тестирования своих
собственных проектов. Дело в том, что использование фаззеров является
обязательным этапом методологии SDL (Security
Development Lifecycle), принятой в Microsoft для разработчиков безопасного
кода, включающей помимо прочего обильное fuzz-тестирование. Minifuzz можно
натравить на любое приложение; главное, чтобы в качестве параметра для запуска
оно воспринимало указание на файл, который ему необходимо открыть (скажем,
winword.exe test_sample.doc). Для начала работы необходимо набрать несколько
образцов "правильных" файлов и положить их каталог, обозначенный как Template
files, а также выбрать приложение для проверки, указав формат параметров для его
запуска. Когда ты нажмешь на кнопку Start Fuzzing, программа возьмет один из
образцов, изменит некоторые байты внутри него (количество изменяемых данных
зависит от параметра Aggressiveness) и скормит его исследуемому приложению. Если
тестируемая программа не вылетит через некоторый таймаут (по умолчанию 2
секунды), значит, тест пройден успешно.
Приложение будет закрыто, и начнется следующая итерация
проверки. Если же во время тестирования программа вылетит (бинго!), то для
анализа у тебя будет, во-первых, файл-образец, который вызвал сбой при открытии,
а, во-вторых, crash-файл с дампом программы. Для большего удобства Minifuzz
легко прикручивается в Visual Studio, позволяя запускать fuzz-тестирование прямо
из среды разработки через меню "Tools -> MiniFuzz". Впрочем, если по каким-то
причинам MiniFuzz тебе не подойдет, то можно попробовать другой инструмент для
dumb-фаззинга —
FileFuzz,
который разработан не Microsoft, а известной security-командой iDefense Labs.
Фаззим протоколы
Если MiniFuzz — это очень простой (хотя и эффективный)
dump-фаззер, то проект Peach
(в переводе — персик), разработанный Майком Эддингтоном — это уже мощное решение
для smart-фаззинга, поддерживающее как режим мутации, так и генерации. Для
проведения умного фаззинга программе необходимо знать структуру данных, с
которыми она будет экспериментировать. Поэтому на вход фаззера подаются так
называемые PeachPit’ы ("косточки от персика") — специальные XML-конфиги, в
которых задается структура данных, описание взаимоотношений между различными ее
элементами, а также подходы для реализации мутаций. В отличие от Minifuzz, Peach
может фаззить не только файлы, но и сетевые сервисы, RPC, COM/DCOM, SQL-хранимые
процедуры и многое другое. Правда, такая универсальность приводит и к некоторым
трудностям в использовании.
Сразу предупреждаю: это не та программа, которую запускаешь и
сразу понимаешь, что к чему. Чтобы внести ясность, предлагаю разобраться с Peach
на конкретном примере, но вместо фаззинга файлов обратиться к другой области, а
именно — поиску уязвимостей в сетевых сервисах. Для успеха придется
дополнительно установить WinDBG в качестве отладчика, а также снифер Wireshark и
драйвер Winpcap, чтобы иметь возможность перехватывать сетевые пакеты во время
фаззинга сетевых протоколов.
Любой фаззинг в Peach'е начинается с создания PeachPit. Как я
уже сказал, в этом XML-файле определяется цель фаззинга, описывается структура
данных, с которой будет работать фаззер, а также определяются правила
манипуляции с ними. Для удобства автор фреймворка предлагает библиотеку для
Visual Studio, серьезно упрощающую работу с PeachPit'ами, в том числе с помощью
автодополнения кода. Любая "косточка" состоит из нескольких функциональных
блоков. Чтобы не составлять весь файл с нуля, в корневом каталоге фаззера есть
файл-шаблон template.xml, который мы и возьмем за основу.
Важная часть любого PeachPit'а — описание модели данных. Именно
в этом месте мы делаем фаззер "умным", рассказываем ему о структуре файле или
протокола (размерах полей, смещениях и т.д.), с которым предстоит работать.
Возьмем для примера простейший протокол TFTP и попробуем профаззить запрос на
чтение файла (Read). Если верить RFC, то выглядит он следующим образом:
TFTP PACKET
-------------------------------------- | \x00\x01 | Filename | 0 | Mode | 0 | --------------------------------------
Получается, что запрос начинается с HEX-символов "\x00\x01",
после которых следует название файла и флаги режима передачи файла. При этом
после полей Filename и Mode идут нулевые байты. Итак, задача — написать фаззер,
который будет играться со значением Filename. Начнем с создания модели запроса в
нашем PeachPit'е в соответствии с RFC:
<DataModel name="tftprrx"> <Blob name="opcode" valueType="hex" value="00 01" token="true"/> <String name="filename" value="filename.txt" nullTerminated="true"/> <String name="mode" value="NETASCII" token="true" nullTerminated="true"/> </DataModel>
В первой строке модели мы задаем двухбайтный код, обозначающий
запрос на чтение. Указанный здесь параметр token="true" мы будем использовать
каждый раз, чтобы дать понять Peach, что это поле остается как есть, и его не
нужно фаззить. Обрати внимание, что в следующей строке, которая описывает поле
filename, этого флага как раз нет, и именно поэтому фаззер будет манипулировать
со значением в этом поле (или, другими словами, фаззить). В последней строке
описывается поле, обозначающее режим работы. Для полей "filename" и "mode" мы
подставляем флаг nullTerminated, указывая на то, что после них идут нулевые
байты-разделители. Обрати внимание, что для каждого из трех полей указывается
его тип (blob или string). Таким образом мы рассказываем фаззеру, с каким типом
данных он будет иметь дело. Понятно, что это очень простая модель, в большинстве
случаев с ее составлением придется поработать намного плотнее.
После того, как модель данных готова, необходимо описать логику
работы фаззера, которая описывается в следующем блоке PeachPit'а. Поскольку
единственное место, где мы будем осуществлять фаззинг — это поле filename, то и
логика нас будет очень простая. Укажем Peach'у, что необходимо отправлять данные
(Action type="output"), используя ранее описанную модель данных "tftprrx":
<StateModel name="state1" initialState="Initial"> <State name="Initial"> <Action type="output"> <DataModel ref="tftprrx"/> </Action> </State> </StateModel>
Следующий блок конфигурации фаззера — описание агентов. Агенты
присоединяют к нужному процессу отладчик и постоянно следят за его состоянием. В
случае вылета приложения из-за ошибки агенты записывают различные детали падения
в логфайл, в том числе и вызвавший сбой запрос (в случае, если речь идет о
снифинге сетевого протокола). Для того, чтобы классифицировать падение (Exploitable,
Probably Exploitable, Probably Not Exploitable, Unknown), разработчик
рекомендует дополнительно к отладчику WinDBG установить плагин
!exploitable.
Обозначим в этом блоке, что будем отслеживать состояние приложения TFTPD32 и
весь UDP-трафик, поступающий на 69 порт (TFTP):
<Agent name="RemoteAgent" location="http://192.168.1.10:9000"> <Monitor class="debugger.WindowsDebugEngine"> <Param name="Service" value="TFTPD32" /> </Monitor> <Monitor class="network.PcapMonitor"> <Param name="filter" value="udp port 69" /> </Monitor> </Agent>
Вот теперь почти все готово. Осталось связать между собой модель
данных, логику и агентов, объединив их в единое целое — secuiryt-тест . У нас
будет только один тест (для фаззинга поля filename), но в реальной ситуации
можно написать столько тестов, сколько необходимо. По сути, нужен один тест для
каждого описанного блока с моделью данных.
<Test name="tftprrx"> <Agent ref="RemoteAgent"/> <StateModel ref="state1"/> <Publisher class="udp.Udp"> <Param name="host" value="192.168.1.10" /> <Param name="port" value="69" /> </Publisher> </Test>
После "publisher" указывается, каким образом будут передаваться
данные. Поскольку TFTP работает по UDP-протоколу, то его мы и используем при
составлении теста. Последний блок, который необходимо изменить в файле-шаблоне —
это блок для запуска фаззера ("Run"). Здесь определяется, куда ты хочешь
сохранить логи с результатами и какие тесты хочешь провести:
<Run name="DefaultRun"> <Logger class="logger.Filesystem"> <Param name="path" value="logs"/> </Logger> <Test ref="tftprrx"/> </Run> </Peach>
Составление "косточек" для Peach'а может показаться непростой
задачей, и это действительно так. А как иначе объяснить фаззеру особенности
формата данных и то, каким образом ему эффективнее играться с теми или иными
параметрами? В этом и есть смысл умного фаззинга. С другой стороны, если прямо
сейчас попробовать реализовать другой метод того же протокола TFTP (скажем,
метод write), то сразу осознаешь, что кода потребуется намного меньше — большая
часть XML-конфига уже готова. Специально для проверки корректности PeachPits'ов
в состав фаззера входит специальный скрипт peachvalidator.pyw. Если валидатор
отдаст отмашку на старт фаззера, можно запускать Peach:
peach.py -a peach.py tftpfuzzer.xml
Первая команда активирует агентов, а вторая позволяет запустить
фаззер с использованием только что составленного XML-конфига.
Фаззим драйвера
Итак, мы уже разобрались с фаззингом файлов, протоколов — теперь
попробуем использовать фаззинг для поиска ошибок в драйверах. Тут надо понимать,
что драйверы используются не только для управления устройствами, вовсе нет.
Многие программы устанавливают в систему драйвер в качестве посредника для
доступа в более привилегированный режим — Ring0. Прежде всего, это антивирусы и
утилиты, обеспечивающие (по крайней мере, обещающие обеспечить) безопасность
системы. Драйверы, в общем, ничем не отличаются от программы в плане
безопасности: как и везде, большое количество уязвимостей связано с неправильной
обработкой данных, в особенности тех, что поступают в IRP-запросах. I/O request
packets (IRP) — это специальные структуры, использующиеся моделью драйверов
Windows для взаимодействия и обмена данными драйверов друг с другом и самой
системой. Получается, и здесь есть все условия для того, чтобы автоматизировать
поиск уязвимостей. Конечно, инструмент тут нужен совершенно особенный, потому
как обычным фаззерам доступ в недра системы закрыт.
Одна из немногих разработок в этой области —
IOCTL Fuzzer,
которая изначально нацелена на проведение fuzzing-тестов, манипулируя с данными
в IRP-запросах. Программа устанавливает в систему вспомогательный драйвер (не
удивляйся, что подобную активность антивирус посчитает подозрительной), который
перехватывает вызовы NtDeviceIoControlFile, тем самым получая возможность
контролировать все IRP-запросы от любого приложения к драйверам режима ядра. Это
нужно потому, что изначально формат IRP-запроса для конкретного драйвера или
программы неизвестен. А имея на руках перехваченный IRP-запрос, его можно легко
изменить — получается классический фаззинг с помощью мутации. Проспуфенный
IRP-запрос ничем не отличается от оригинального за исключением поля с данными,
которые заполняется фаззером псевдослучайным образом. Поведение фаззера,
лог-файл, названия драйверов для спуфинга и другие параметры задаются с помощью
простейшего XML-конфига, который находится в корне программы. Но прежде чем
рваться в бой, необходима некоторая подготовка.
Из эксперимента с драйверами на рабочей машине ничего хорошего
не выйдет. Если IOCTL Fuzzer удастся нащупать слабое место в каком-нибудь из
драйверов, то система легко улетит в BSOD, а это едва ли прибавит удобства для
идентификации уязвимости :). По этой причине для использования фаззера нам
понадобится отдельная виртуальная машина, к которой мы подключим удаленный
дебаггер ядра. Тут честь и хвала Microsoft, которые не только смогли сделать
толковый отладчик WinDbg, поддерживающий удаленный дебаггинг, но и
распространяют его бесплатно. Взаимодействие между гостевой системой в VMware и
удаленным отладчиком WinDbg осуществляется с помощью именованного канала (named
pipe), который мы сейчас и создадим.
1. Сначала создаем именованный канал в VMware. Для этого
переходим в меню "Settings „p Configuration Editor", нажимаем на кнопку добавить
оборудование ("Add"), выбираем "Serial Port", жмем "Next", далее из списка
выбираем тип порта — "Use named pipe" и оставляем дефолтное название для
именованного канала (\\.\pipe\com_1). После этого задаем режим работы "This end
is server. The other end is application" в двух выпадающих полях и напоследок
нажимаем кнопку "Advanced", где активируем опцию "Yiled CPU on poll" (иначе
ничего не заработает).
Осталось реализовать возможность загрузки гостевой системы с
включенным режимом удаленной отладки. Для этого в boot.ini (будем считать, что в
качестве гостевой системы используется Windows XP) необходимо вставить новую
строку для запуска системы, добавив два важных ключа /debugport и /baudrate:
[operating systems] multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /fastdetect multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional - Debug" /fastdetect /debugport=com1 /baudrate=115200
Во время следующей перезагрузки необходимо в загрузчике выбрать
ту версию системы, для которой мы включили режим отладки. Остается настроить сам
отладчик, но для этого нужно лишь во время запуска передать ему параметры
именованного канала:
windbg -b -k com:pipe,port=\\.\pipe\com_1,resets=0
Вот теперь можно запускать IOCTL Fuzzer в режиме фаззинга, не
опасаясь BSOD’а на основной системе. Выполняем произвольные манипуляции с
тестируемым ПО до тех пор, пока отладчик не сообщит нам о возникновении
необрабатываемого исключения (это значит, что в обычных условиях, скорее всего,
это закончилось бы аварийным завершением работы системы).
Далее необходимо возобновить выполнение кода на виртуальной
машине (в случае с WinDbg надо просто нажать F5), после чего ОС, работающая на
виртуальной машине, запишет аварийный дамп (crash dump) на диск. Готово: теперь
у нас есть подробные логи, дамп и сам запрос, который привел к падению. Дело за
малым — понять, как это можно эксплуатировать :).
А как же веб-фаззеры?
Я намеренно не стал упоминать в рамках этой статьи так
называемые web-based фаззеры, которые работают на уровне HTTP и заваливают
веб-сервер специально составленными запросами в поиске ошибок веб-приложения.
Такая опция есть в каждом втором сканере веб-безопасности, которые мы не так
давно рассматривали в рамках цикла "Инструменты
пентестера". Если говорить об универсальных платформах для создания фаззеров,
то грех не вспомнить о фреймворке
Sulley,
представленном на Blackhat'е в 2007 году. К сожалению, с тех самых пор он и не
развивается, но несмотря на это остается эффективным решением.
Каждый отдельный фаззер с его помощью конструируется отдельно,
но в отличие от Peach, где все описывается декларативно в XML-файле, здесь тебе
придется написать немного кода на Python. Есть еще один популярный конструктор
фаззеров — проект
SPIKE, но подружиться с ним смогут только те, кто хорошо знает язык C.
Помимо этого можно было долго говорить о фаззерах для поиска уязвимостей в
ActiveX, COM-объектах и где угодно еще. Но это не главное. Важно понять, что во
многих местах поиск уязвимостей можно автоматизировать: именно с помощью
фаззинга находится большое количество багов в современных браузерах и смежных с
ними продуктов. А если есть понимание того, где может быть выявлена ошибка и как
ее искать, то фаззер уже несложно написать самому или подобрать готовое решение.
WARNING
Информация представлена в ознакомительных целях. Использование
ее для противозаконных действий может обернуться уголовным преследованием. Ни
автор, ни редакция за такие последствия ответственности не несут.
|