УДК 004.056
ФАЗЗИНГ. ПОИСК УЯЗВИМОСТЕЙ В ПРОГРАММНОМ ОБЕСПЕЧЕНИИ БЕЗ НАЛИЧИЯ ИСХОДНОГО КОДА
Илья Олегович Томилов
Сибирский государственный университет геосистем и технологий, 630108, Россия, г. Новосибирск, ул. Плахотного, 10, магистрант кафедры наносистем и оптотехники, тел. (961)225-78-98, e-mail: [email protected]
Александр Владимирович Трифанов
Сибирский государственный университет геосистем и технологий, 630108, Россия, г. Новосибирск, ул. Плахотного, 10, аспирант, тел. (923)257-37-48, e-mail: [email protected]
В данной работе рассматривается применение технологии фаззинг (Fuzzing) для тестирования программного обеспечения на наличие уязвимостей, когда вместо ожидаемых входных данных программе передаются случайные или специально сформированные данные. Рассмотрены основные типы, методы, этапы фаззинга и их эффективность. Продемонстрирован пример использования фаззера при поиске реальных уязвимостей, которые можно либо учесть и исправить, тем самым повысив безопасность приложения, либо используя их атаковать данное программное обеспечение.
Ключевые слова: фаззинг, поиск уязвимостей в приложениях, технологии автоматизированного тестирования программного обеспечения, тестирование безопасности.
FUZZING. GRAY-BOX METHOD
Ilya O. Tomilov
Siberian State University of Geosystems and Technologies, 630108, Russia, Novosibirsk, 10 Plakhotnogo St., undergraduate of the Department of Nanosystems and Optical Engineering, tel. (961)225-78-98, e-mail: [email protected]
Aleksandr V. Trifanov
Siberian State University of Geosystems and Technologies, 630108, Russia, Novosibirsk, 10 Plakhotnogo St., graduate student, tel. (923)257-37-48, e-mail: [email protected]
In this paper, we consider the use of Fuzzing technology to test software for vulnerabilities, when instead of the expected input data, random or specially generated data is transmitted to the program. The main types, methods, stages of fuzzing and their effectiveness are considered. An example is shown of using the phaser in the search for real vulnerabilities, which can either be taken into account and corrected, thereby increasing the security of the application, or using them to attack this software.
Key words: Fuzzing, vulnerability scanning in applications, automated software testing technologies, security testing.
В современных приложениях актуальна тема уязвимостей различного рода: переполнение буфера, утечки памяти, плохое шифрование, недостаточная проверка входных данных. Некоторые разработчики программного обеспечения в своей деятельности успешно применяют статический анализ кода (на эта-
пе компиляции), а также динамический (в процессе выполнения кода)[2].При анализе компилируемого кода с точки зрения безопасности, под динамическим анализом часто подразумевают именно фаззинг. Преимуществом фаззинга является практически полное отсутствие ложных срабатываний, что довольно часто встречается при использовании статических анализаторов [3].
Фаззинг - это технология автоматизированного тестирования программного обеспечения с целью выявления потенциальных уязвимостей, которая охватывает большое количество граничных случаев путем порождения некорректных входных данных. В качестве входных данных при этом могут выступать обрабатываемые приложением файлы, информация, передающаяся по сетевым протоколам, функции прикладного интерфейса и т. д.
На стыке методов структурного и функционального тестирования находится метод «серого ящика». При тестировании данным методом исследователь не имеет полной спецификации программы и исходных кодов, как это бывает при тестировании методом «белого ящика», однако знаний о системе больше чем при тестировании методом «черного ящика».
Часто подразумевается, что знание о тестируемом программном обеспечении получаются в ходе реверсивной инженерии. На основе полученных таким путем знаний планируется и выполняется ряд мероприятий по тестированию [4].
В зависимости от метода генерации данных принято разделять подход к фаззингу на два класса:
- мутация данных. Новые данные получаются за счет незначительных изменений существующих данных;
- генерирование данных. Данные подготавливаются заранее, на основе протоколов или в соответствие с заданными правилами.
Следующие типы фаззинга, так или иначе, относятся к одному из вышеперечисленных классов:
- использование заранее подготовленных текстовых данных. Используется для тестирования реализации протоколов. Вместе с формальным описанием протокола разработчик может подготовить ряд текстовых данных, которые должны надлежащим образом обрабатываться программой, реализующей протокол;
- использование случайных данных. Это наименее эффективный из возможных подходов. Целевой программе передается большое количество случайных данных. При возникновении сбоя, очень сложно определить ее причину;
- ручное изменение данных протокола. Исследователю известен протокол, и он пытается добиться аномального поведения исследуемого программного обеспечения за счет внесения ошибочных данных. В связи с отсутствием автоматизации мало эффективен;
- полный перебор мутаций данных, подготовленных в соответствие с протоколом. Подход уменьшает объем тестов за счет использования знаний о протоколе. Однако в данные вносятся всевозможные мутации за счет этого объем данных велик;
- осознанное внесение изменений в данные подготовленные в соответствие с протоколом. Для проведения данного вида тестирования должны быть проведены дополнительные исследования с целью определения, какие части данных должны оставаться константами, а какие изменяться. Подход является наиболее интеллектуальным, однако увеличивается затрата времени исследователя.
По типу воздействия фазеры можно разделить на несколько классов: локальные, удаленные, в памяти и универсальные.
Локальные фаззеры делятся на следующие типы:
- фаззеры командной строки. Используются для выявления ошибок, связанных с разбором входных параметров программ;
- фаззеры переменных окружения. Используются для выявления ошибок, связанных с обработкой данных, получаемых через переменные окружения;
- фаззеры файлов. Используются для тестирования программного обеспечения, принимающего файлы в качестве входных данных;
Удаленные фаззеры бывают следующих типов:
- фаззеры сетевых протоколов. В зависимости от сложности протокола применяются фаззеры соответствующей сложности;
- фаззеры web-приложений. Получили особую актуальность с развитием Web 2.0;
- фаззеры web-браузеров. Тестируется правильность разбора, как HTML-тэгов, так и других поддерживаемых расширений. Особо стоит выделить фаззеры com-объектов поддерживаемых браузерами.
Для демонстрации использования фаззера на практике, рассмотрим уязвимость, которая была найдена с помощью IOCTL Fuzzer. В качестве тестируемого приложения будет выступать антивирусный продукт - Trend Micro Titanium aximum Security [1].
Вскоре после запуска фаззера происходит аварийное завершение работы системы, в выводе удаленного отладчика режима ядра отображается следующее сообщение, c информацией о последнем обработанном фаззером IOCTL запросе:
'C:\Program Files\Trend Micro\AMSP\coreServiceShell.exe' (PID: 7 92)'\Device\TmComm' (0x81d6a030)
[\SystemRoot\system32\DRIVERS\tmcomm.sysIOCTL Code: 0x9000402b, Method: METHOD_NEITHER
InBuff: 0x018fc080, InSize: 0x0000004c
OutBuff: 0x018fc080, OutSize: 0x0000004c
Как видно, в данном фрагменте фигурируют имена драйвера и процесса TrendMicro. Приступим к реверсингу уязвимого драйвера tmcomm.sys, начав с процедуры обработки IRP запросов к устройствам данного драйвера:
int stdcall sub 1E8BE(intDriverObject, char *Irp) { _
StackLocation = *((_DWORD *)Irp + 24); IoStatus = Irp + 28; *((_DWORD *)Irp + 7) = 0;
MajorFunction = *(_BYTE *)StackLocation; if (MajorFunction == 2) goto LABEL_12;
if (MajorFunction> 0xDu)
{
// обработка IRP запросовтипа IRP_MJ_DEVICE_CONTROL
if (MajorFunction<= 0xFu) {
// извлечениепараметров IOCTL запросаизструктуры IO STACK LOCATION Type3InputBuffer = *(_DWORD *)(StackLocation + 0x10); UserBuffer = *((_DWORD *)v6 + 15);
InputBufferLength = *(_DWORD *)(StackLocation + 8); OutputBufferLength = *(_DWORD *)(StackLocation + 4); v27 = IoStatus; v24 = OutputBufferLength; // дальнейшаяобработка IOCTL запроса
v5 = sub_1F7A6(*(_DWORD *)(StackLocation + 0xC), (int)&InputBufferLength);
gotoLABEL_9;
}
// ...
}
// ...
returnv5;
}
Как видно по приведенному псевдокоду, обработка IOCTL запросов осуществляется в процедуре sub_1F7A6():
int stdcall sub 1F7A6(intControlCode, int a2) {
v7 = 0xC00000BBu; v6 = 0;
v2 = ExGetPreviousMode(); v3 = 0;
if (off_34CB4) {
v4 = 0;
// Поиск процедуры дальнейшей обработки IOCTL запроса по значению // ControlCode. Для 0x9000402b (значение, которое было выявленно при фаззинге)
// будет вызвана процедура sub 1FF38()
while (*(int *)((char *)&dword_34CB0 + v4) != ControlCode) {
++v3;
v4 = 8 * v3;
if (!*(&off_34CB4 + 2 * v3)) goto LABEL_7;
}
v6 = *(&off_34CB4 + 2 * v3);
}
LABEL_7:
if (v2 == UserMode) {
// проверка входного и выходного буферов
ProbeForRead(*(const void **)(a2 + 8), *(_DWORD *)a2, 1u);
ProbeForWrite(*(PVOID *)(a2 + 12), *(_DWORD *)(a2 + 4), 1u); }
Полный код всех процедур, которые участвуют в обработке IOCTL запроса, приводиться не будет по причине его громоздкости.
В ходе проведенного реверсинга было выяснено, что IOCTL запрос с кодом 0x9000402b используется для вызова из пользовательского приложения оригинальных обработчиков тех системных вызовов, которые были перехвачены драйвером антивирусной защиты. При этом во входном буфере по нулевому смещению находится байт, значение которого определяет то, какой системный вызов следует вызвать (например, для NtCreateFile() это значение равно 0x2713). Все остальное пространство входного буфера используется для хранения указателей на структуры (UNICODE_STRING, OBJECT_ATTRIBUTES и другие), которые следует заполнить и передать в качестве параметров для системного вызова.
Уязвимость, позволяющая выполнить произвольный код с наивысшими привилегиями, содержится в функции, которая непосредственно осуществляет системный вызов. Она заключается в отсутствии проверок рассмотренных выше указателей на параметры системного вызова:
int thiscall sub 288AA(void *this, intInputBuffer, int a3, int a4) {
// извлечение параметров для системного вызова из входного буфера
v18 = *(_DWORD *)(InputBuffer + 56);
v17 = *(_DWORD *)(InputBuffer + 32);
v12 = *(_DWORD *)(InputBuffer + 36);
v14 = *(_DWORD *)(InputBuffer + 40);
v15 = *(_DWORD *)(InputBuffer + 44);
v11 = (HANDLE *)(a3 + 8);
ObjAttr = *(_DWORD *)(a3 + 0x3C);
v10 = (int)this;
StringBuffer = *(_DWORD *)(InputBuffer + 0xC); v13 = *(_DWORD *)(InputBuffer + 52); UnicodeString = *(_DWORD *)(InputBuffer + 0x44); v19 = *(struct _IO_STATUS_BLOCK **)(a3 + 0x40); StringLen = *(_DWORD *)(InputBuffer + 0x14); v16 = *(_DWORD *)(InputBuffer + 48);
if (StringBuffer&&UnicodeString&&ObjAttr&& v19 &&StringLen) {
// заполнениеструктуры UNICODE_STRING
*(_DWORD *)(UnicodeString + 4) = StringBuffer; *(_WORD *)(UnicodeString + 2) = StringLen; *(_WORD *)UnicodeString = StringLen; // заполнениеструктуры OBJECT_ATTRIBUTES *(_DWORD *)ObjAttr = 24; *(_DWORD *)(ObjAttr + 4) = 0; *(_DWORD *)(ObjAttr + 12) = 0x240u; *(_DWORD *)(ObjAttr + 8) = UnicodeString; *(_DWORD *)(ObjAttr + 16) = 0; *(_DWORD *)(ObjAttr + 20) = 0; // вызов оригинального обработчика системного вызова NtCreateFile()
result = sub_185C2(v10, v11, v12, (OBJECT_ATTRIBUTES *)ObjAttr, v19, 0, v13, v14, v15, v16, 0, 0, a4); if (result < 0) *v11 = (HANDLE)-1;
}
else {
result = 0xC000000Du;
*(_DWORD *)(InputBuffer + 4) = 0xC000000Du;
}
returnresult;
}
Таким образом, путем передачи уязвимому драйверу специальным образом сформированного буфера атакующий может переписать произвольным значением произвольный байт памяти в пространстве ядра.
Стоит отметить, что рассмотренная уязвимость, не смотря на возможность локального выполнения произвольного кода в пространстве ядра, имеет низкую степень опасности. Это связанно с тем, что необходимое для эксплуатации уязвимости устройство \Device\TmComm может быть открыто только пользователем с наивысшими привилегиями.
Таким образом, уязвимость представляет исключительно образовательную ценность, и ее эксплуатация в реальных условиях является бессмысленной.
Обнаружение даже одной уязвимости в популярном приложении за короткий срок в автоматическом режиме можно считать свидетельством достаточной эффективности технологии. Долгое время считалось, что фаззинг является слишком тяжеловесным подходом к обнаружению программных дефектов, и полученные результаты не оправдывают затраченных усилий и ресурсов. Однако, современные тенденции развития индустрии производства программного обеспечения позволяют по-новому взглянуть на эту проблему. Тестирование безопасности в общем, и фаззинг в частности, нужны нам для того, чтобы, после выпуска продукта на рынок не подвергать пользователей атакам злоумышленников нашедших уязвимости, не терять престиж компании, и как следствие, не подвергаться значительным финансовым затратам.
БИБЛИОГРАФИЧЕСКИЙ СПИСОК
1. Обновление программы IOCTLFuzzer [Электронный ресурс]. - Режим доступа: http://blog.cr4.sh/2010/12/обновление-программы-ioctl-fuzzer.html.
2. Автоматическое тестирование программ [Электронный ресурс]. - Режим доступа: https://habrahabr.ru/post/128503/.
3. Быстрый security-oriented fuzzing c AFL [Электронный ресурс]. - Режим доступа: https://habrahabr.ru/post/259671/.
4. Саттон М., Грин А., Амини П. Fuzzing: исследование уязвимостей методом грубой силы [Электронный ресурс]. - Режим доступа: http://i.booksgid.com/web/online/42526.
© И. О. Томилов, А. В. Трифанов, 2017