Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Fuzzing исследование уязвимостей методом грубой силы.pdf
Скачиваний:
1127
Добавлен:
13.03.2016
Размер:
5.96 Mб
Скачать

Разработка

229

Разработка

Теперь, когда мы познакомились с исключительными свойствами фаз$ зинга форматов файлов на платформе Windows, настало время для соз$ дания фаззера. Мы проанализируем весь мыслительный процесс, про$ текавший во время проектирования FileFuzz, и в конце используем FileFuzz для определения широко разрекламированной уязвимости интерфейса GDI+ JPEG, описанного в информационном бюллетене по вопросам безопасности Microsoft MS04$028.

Подход

Создавая FileFuzz, мы хотели разработать удобное в использовании графическое приложение, которое позволило бы конечному пользова$ телю осуществлять фаззинг, не получая информации о серии аргумен$ тов командной строки. Мы хотели создать приложение, работающее по принципу «наведи и щелкни», настолько интуитивно понятное, на$ сколько это возможно. Также мы ставили перед собой цель создать ин$ струмент, который был бы конкретно приспособлен к особенностям фаззинга формата файла на платформе Windows, поэтому в список обя$ зательных свойств не входила совместимость с разными платформами.

Выбор языка

Учитывая цели проекта, мы в очередной раз выбрали для нашей раз$ работки платформу .NET. Это позволило создать пользовательский графический интерфейс, затратив минимум усилий, и сосредоточить$ ся на функциональной стороне проекта. Графический пользователь$ ский интерфейс и все функции по созданию файла были написаны на языке C#. Функции отладчика мы решили написать на C, поскольку он позволил нам без каких$либо проблем взаимодействовать с про$ граммным интерфейсом Windows. Платформа .NET позволила вопло$ тить все эти проектные решения, поскольку она рассчитана на проек$ ты, использующие несколько разных языков программирования, со$ вместимых с библиотекой .NET.

Устройство

Мы уже достаточно говорили о том, как мы бы проектировали File$ Fuzz. Настало время пройтись по отдельным этапам процесса разра$ ботки. Было бы непрактично описывать каждый фрагмент кода, но мы разберем несколько наиболее важных участков. Для того чтобы полностью разобраться в устройстве FileFuzz, советуем вам скачать исходный код с сайта www.fuzzing.org.

230

Глава 13. Фаззинг формата файла: автоматизация под Windows

Создание файла

Необходимо было сделать так, чтобы FileFuzz работал со всеми форма$ тами файлов Windows, и мы поняли, что понадобятся различные под$ ходы к двоичным и к текстовым файлам ASCII. Файл Read.cs содер$ жит целиком код для чтения допустимых файлов, а файл write.cs за$ нимается созданием файлов фаззинга.

Чтение исходных файлов

FileFuzz применяет в фаззинге подход грубой силы, поэтому мы начи$ наем с чтения заведомо корректных файлов. Данные из допустимого файла читаются и хранятся для последующего видоизменения при создании файлов фаззинга. К счастью, библиотека .NET делает зада$ ния вроде чтения файлов сравнительно безболезненными. Мы исполь$ зуем различные подходы при чтении двоичных и текстовых файлов ASCII. Для наших целей мы использовали класс BinaryReader для чте$ ния двоичных файлов и хранения данных в байтовом массиве. Чтение текстовых файлов ASCII схоже с чтением двоичных файлов, но в дан$ ном случае мы использовали класс StreamReader. Кроме того, то, что по$ лучилось, мы храним в строковой переменной, а не в байтовом масси$ ве. Далее приведен конструктор для класса Read:

private BinaryReader brSourceFile; private StreamReader arSourceFile; public byte [] sourceArray;

public string sourceString; private int sourceCount; private string sourceFile;

public Read(string fileName)

{

sourceFile = fileName; sourceArray = null; sourceString = null; sourceCount = 0;

}

sourceArray будет использоваться хранения байтового массива двоич$ ных массивов, которые мы читаем, в то время как sourceString будет использоваться для хранения содержимого текстового файла ASCII.

Запись в файлах фаззинга

После того как файлы были прочитаны, нам необходимо видоизме$ нить их и сохранить получившиеся файлы для запуска с помощью объектного приложения. Как уже упоминалось, FileFuzz делит созда$ ние файла на следующие четыре типа, основываясь на используемом подходе к видоизменению файла:

все байты;

диапазон;

Разработка

231

ширина;

глубина.

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

Выполнение приложения

Код, ответственный за начало процесса выполнения объектного при$ ложения, находится в файле Main.cs, как показано в приведенном да$ лее участке кода. Однако если вы взглянете на него, то поймете, что все это сравнительно просто, потому что код, по сути дела, не отвечает непосредственно за выполнение объектного приложения, он отвечает за запуск встроенного отладчика, который, в свою очередь, отвечает за выполнение объектного приложения. Далее отладчик crash.exe рас$ сматривается подробно.

Мы начинаем с создания нового экземпляра класса Process. Затем внутри функции executeApp() начинаем цикл, для того чтобы запус$ тить каждый из ранее созданных файлов фаззинга. В течение каждого прохода цикла мы устанавливаем необходимые атрибуты процесса, включая имя процесса, который должен быть выполнен. Имя, как уже говорилось, всегда будет crash.exe, независимо от того, что подвергает$ ся фаззингу, поскольку командное приложение crash.exe, в свою оче$ редь, должно выполнить объектное приложение. В этот момент управ$ ление передается crash.exe, и окончательные результаты возвращают$ ся crash.exe с помощью стандартного вывода и стандартной ошибки и отображаются в окне с текстом rtbLog, являющемся основным вы$ ходным окном в FileFuzz:

Process proc = new Process();

public Execute(int startFileInput, int finishFileInput, string targetDirectoryInput, string fileExtensionInput, int applicationTimerInput, string executeAppNameInput, string executeAppArgsInput)

{

startFile = startFileInput; finishFile = finishFileInput;

targetDirectory = targetDirectoryInput; fileExtension = fileExtensionInput; applicationTimer = applicationTimerInput; executeAppName = executeAppNameInput; executeAppArgs = executeAppArgsInput; procCount = startFile;

232

Глава 13. Фаззинг формата файла: автоматизация под Windows

}

public void executeApp()

{

bool exceptionFound = false;

//Initialize progress bar if (this.pbrStart != null)

{

this.pbrStart(startFile, finishFile);

}

while (procCount <= finishFile)

{

proc.StartInfo.CreateNoWindow = true; proc.StartInfo.UseShellExecute = false; proc.StartInfo.RedirectStandardOutput = true; proc.StartInfo.RedirectStandardError = true; proc.StartInfo.FileName = "crash.exe";

proc.StartInfo.Arguments = executeAppName + " " + applicationTimer + " " + String.Format(executeAppArgs, @targetDirectory + procCount.ToString() + fileExtension);

proc.Start(); //Update progress bar

if (this.pbrUpdate != null)

{

this.pbrUpdate(procCount);

}

//Update counter

if (this.tbxUpdate != null)

{

this.tbxUpdate(procCount);

}

proc.WaitForExit();

//Write std output to rich text box log

if (this.rtbLog != null && (proc.ExitCode == 1 || proc.ExitCode == 1))

{

this.rtbLog(proc.StandardOutput.ReadToEnd());

this.rtbLog(proc.StandardError.ReadToEnd()); exceptionFound = true;

}

procCount++;

}

//Clear the progress bar if (this.pbrStart != null)

{

this.pbrStart(0, 0);

}

//Clear the counter

if (this.tbxUpdate != null)

{

this.tbxUpdate(0);

Разработка

233

}

if (exceptionFound == false) this.rtbLog("No excpetions found\n\n");

exceptionFound = false;

}

Обнаружение исключительных ситуаций

Как уже говорилось, FileFuzz содержит функции отладки в форме crash.exe, автономного отладчика, написанного на C, который исполь$ зует функции отладки, встроенные в программный интерфейс Win$ dows. В нем также используется libdasm, общедоступная библиотека, помогающая при обработке кода дизассемблирования. Как видно из приведенного далее отрывка кода, сначала выполняется проверка, чтобы удостовериться в том, что crash.exe прошел как минимум через три аргумента. Для FileFuzz этими аргументами являются имя и путь к приложению, с которым осуществляется фаззинг, время ожидания до прекращения работы объектного приложения и, наконец, дополни$ тельные аргументы командной строки плюс имя файла фаззинга, ко$ торый необходимо обработать. Вслед за этим значение времени ожида$ ния переводится из строкового типа в целочисленный, и аргумент ко$ мандной строки окончательно создается и хранится в символьном мас$ сиве. Затем объектное приложение запускается с помощью команды

CreateProcess и набора флагов DEBUG_PROCESS:

if (argc < 4)

{

fprintf(stderr, "[!] Usage: crash <path to app> <milliseconds> <arg1> [arg2 arg3 argn]\n\n");

return 1;

}

//convert wait time from string to integer. if ((wait_time = atoi(argv[2])) == 0)

{

fprintf(stderr, "[!] Milliseconds argument unrecognized: %s\n\n", argv[2]); return 1;

}

//create the command line string for the call to CreateProcess(). strcpy(command_line, argv[1]);

for (i = 3; i < argc; i++)

{

strcat(command_line, " "); strcat(command_line, argv[i]);

}

//

// launch the target process.

//

ret = CreateProcess(NULL,

// target file name.

234

Глава 13. Фаззинг формата файла: автоматизация под Windows

 

command_line,

// command line options.

 

NULL,

// process attributes.

 

NULL,

// thread attributes.

 

FALSE,

// handles are not inherited.

 

DEBUG_PROCESS,

// debug the target process and all spawned children.

 

NULL,

// use our current environment.

 

NULL,

// use our current working directory.

 

&si,

// pointer to STARTUPINFO structure.

 

&pi);

// pointer to PROCESS_INFORMATION structure.

printf("[*] %s\n", GetCommandLine()); //Print the command line

if (!ret)

{

fprintf(stderr, "[!] CreateProcess() failed: %d\n\n", GetLastError()); return 1;

}

Сейчас crash.exe может следить за исключительными ситуациями и создавать о них записи. В следующем участке кода будет видно, что до тех пор, пока не истекло время ожидания, мы будем следить за со$ бытиями отладки. Когда мы обнаруживаем подобное событие, то узна$ ем номер интересующего нас потока и определяем тип произошедшей исключительной ситуации. С помощью оператора выбора мы осущест$ вляем поиск трех типов исключительных ситуаций, представляющих интерес: нарушение доступа к памяти, ошибки деления на ноль и пе$ реполнения стека. Затем распечатываем релевантную информацию от$ ладки, для того чтобы помочь конечному пользователю определить, стоит ли дальше анализировать исключительную ситуацию. Исполь$ зуя библиотеку libdasm, мы декодируем место, где произошла исклю$ чительная ситуация, вредоносный операционный код и значения ре$ гистров на момент сбоя:

while (GetTickCount() start_time < wait_time)

{

if (WaitForDebugEvent(&dbg, 100))

{

// we are only interested in debug events.

if (dbg.dwDebugEventCode != EXCEPTION_DEBUG_EVENT)

{

ContinueDebugEvent(dbg.dwProcessId, dbg.dwThreadId, DBG_CONTINUE); continue;

}

// get a handle to the offending thread.

if ((thread = OpenThread(THREAD_ALL_ACCESS, FALSE, dbg.dwThreadId)) == NULL)

{

fprintf(stderr, "[!] OpenThread() failed: %d\n\n", GetLastError()); return 1;

}

Разработка

235

// get the context of the offending thread. context.ContextFlags = CONTEXT_FULL;

if (GetThreadContext(thread, &context) == 0)

{

fprintf(stderr, "[!] GetThreadContext() failed: %d\n\n", GetLastError());

return 1;

}

// examine the exception code.

switch (dbg.u.Exception.ExceptionRecord.ExceptionCode)

{

case EXCEPTION_ACCESS_VIOLATION: exception = TRUE;

printf("[*] Access Violation\n"); break;

case EXCEPTION_INT_DIVIDE_BY_ZERO: exception = TRUE;

printf("[*] Divide by Zero\n"); break;

case EXCEPTION_STACK_OVERFLOW: exception = TRUE;

printf("[*] Stack Overflow\n"); break;

default:

ContinueDebugEvent(dbg.dwProcessId, dbg.dwThreadId, DBG_CONTINUE);

}

// if an exception occurred, print more information. if (exception)

{

// open a handle to the target process.

if ((process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dbg.dwProcessId)) == NULL)

{

fprintf(stderr, "[!] OpenProcess() failed: %d\n\n", GetLastError());

return 1;

}

//grab some memory at EIP for disassembly. ReadProcessMemory(process, (void *)context.Eip, &inst_buf, 32, NULL);

//decode the instruction into a string.

get_instruction(&inst, inst_buf, MODE_32); get_instruction_string(&inst, FORMAT_INTEL, 0,

inst_string,sizeof(inst_string));

// print the exception to screen.

printf("[*] Exception caught at %08x %s\n", context.Eip, inst_string); printf("[*] EAX:%08x EBX:%08x ECX:%08x EDX:%08x\n", context.Eax,

context.Ebx, context.Ecx, context.Edx);

printf("[*] ESI:%08x EDI:%08x ESP:%08x EBP:%08x\n\n", context.Esi,

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]