• XSS.stack #1 – первый литературный журнал от юзеров форума

Fuzzing Искусство фаззинга - WinAFL. Бинари винды. Дуб, где мертвец звал на бунт людей.

AFANX

CD-диск
Пользователь
Регистрация
24.11.2022
Сообщения
13
Реакции
34
1715781330501.png


Ку, киберпанки. На днях решил заняться вопросом фаззинга ПЕшек. Находил статьи на хакере и почти везде идет обзор гугловского форка AFL++ - WinAFL. Однако, во всех этих статьях нет практики, чтобы чисто попоробовать. Буквально на днях нашел на английском языке обзор инструмента WinAFL и посчитал, что это будет преступлением не поделиться переводом.

Автор оргинала: 2ourc3
Оригинальная статья: The art of fuzzing: Windows Binaries

Введение​

Сегодня мы погрузимся в grey-box fuzzing, тестируя двоичные файлы Windows с закрытым исходным кодом. Этот тип фаззинга позволяет проверить цель, не имея доступа к ее исходному коду. Зачем заниматься таким типом фаззинга? Поскольку он требует больше настроек и продвинутых навыков, меньше людей склонны искать уязвимости / уметь их находить. Таким образом, это расширяет возможности для вас, исследователя уязвимостей, по поиску новых необнаруженных узявимостей.
Чтобы добиться этого, нам придется преодолеть несколько трудностей:
  1. Инструментирование кода.
  2. Найти релевантную функцию для фаззинга.
  3. Модификация/патчинг бинарного кода, чтобы сделать его пригодным для фаззинга.
Существует множество решений для проведения фаззинга на двоичных файлах Windows, однако в этой статье мы остановимся исключительно на WinAFL. WinAFL предлагает три типа инструментария:
  1. Динамическое инструментирование через DynamoRIO. Динамическое инструментирование - это изменение инструкций программы во время ее выполнения.
  2. Статический инструментарий через Syzygy - статический инструментарий, изменяющий инструкции программы во время компиляции.
  3. Аппаратная трассировка с помощью Intel PTrace - аппаратная функция, которая асинхронно записывает поток управления программой.
Хотя у каждого метода есть свои плюсы и минусы, сегодня мы сосредоточимся на использовании динамической инструментации через DynamoRIO. Ниже приведено описание рабочего процесса WinAFL + DynamoRIO, который будет выполняться при фаззинге целевого бинарного файла.

1715781397498.png


Компиляция WinAFL​

  1. Если вы собираете с поддержкой DynamoRIO, загрузите и соберите исходные тексты DynamoRIO или загрузите бинарный пакет DynamoRIO Windows с сайта https://github.com/DynamoRIO/dynamorio/releases
  2. Если вы собираете с поддержкой Intel PT, извлеките сторонние зависимости, выполнив git submodule update --init --recursive из каталога исходных текстов WinAFL
  3. Откройте командную строку Visual Studio (или Visual Studio x64 Win64 Command Prompt, если вам нужна 64-битная сборка). Обратите внимание, что вам нужна 64-битная сборка winafl.dll, если вы проверяете 64-битные цели, и наоборот.
  4. Перейдите в каталог, содержащий исходный код
  5. Введите следующие команды. Измените флаг -DDynamoRIO_DIR, чтобы он указывал на местоположение ваших файлов DynamoRIO cmake (либо полный путь, либо относительно исходного каталога)
Для 32-битной сборки:
Bash:
mkdir build32
cd build32
cmake -G"Visual Studio 16 2019" -A Win32 .. -DDynamoRIO_DIR=..\path\to\DynamoRIO\cmake -DINTELPT=1
cmake --build . --config Release


Для 64-битной сборки:
Bash:
mkdir build64
cd build64
cmake -G"Visual Studio 16 2019" -A x64 .. -DDynamoRIO_DIR=..\path\to\DynamoRIO\cmake -DINTELPT=1
cmake --build . --config Release

Параметры конфигурации сборки

Поддерживаются следующие параметры конфигурации cmake:
  1. -DDynamoRIO_DIR=..\path\to\DynamoRIO\cmake- необходим для сборки клиента DynamoRIO winafl.dll.
  2. -DINTELPT=1 - включить режим Intel PT. Для получения дополнительной информации см. https://github.com/googleprojectzero/winafl/blob/master/readme_pt.md
  3. -DUSE_COLOR=1 - поддержка цвета (Windows 10 Anniversary edition или выше)
  4. -DUSE_DRSYMS=1 - поддержка Drsyms (используйте символы, если они доступны для получения -target_offset из -target_method). Известно, что включение этого параметра приводит к проблемам в Windows 10 v1809, но есть обходные пути, см. #145

Поиск жертвы​


Найти подходящую цель для фаззинга не всегда просто. Главное - найти достаточно сложное программное обеспечение, чтобы его можно было протестировать, но при этом достаточно доступное, чтобы вы могли понять, что именно нужно тестировать и какие функции интересны.
Одной из хороших стратегий является поиск программ, которые, как известно, содержат уязвимости и реагируют на них в рамках программы раскрытия информации. Хороший способ найти такие программы - заглянуть на сайт Zero Day Initiative.

1715781626994.png


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

Вы можете скачать файл здесь, пароль: "bushido". https://drive.google.com/file/d/1c-cOuzYbC-gOFW91a2EHKNpZTiPrVdBP/view?usp=sharing. В приложении будет лежать этот таргет.

Патчинг бинаря для обеспечения возможности фаззинга​


К сожалению, во многих программах используются диалоговые окна, в которых пользователю предлагается ответить на вопрос перед выполнением определенной задачи, например "Этот файл уже существует. Хотите ли вы перезаписать его?" и т.д.

1715781680886.png


Это делает процесс фаззинга невозможным, так как требует от пользователя взаимодействия с диалоговым окном, что не позволит фаззеру нормально работать. Именно поэтому мы сейчас ищем, как улучшить/исправить бинарник, чтобы сделать его фаззабельным!

Скачайте и установите Ghidra, запустите приложение, затем создайте каталог проекта и проект. Импортируйте файл vulnerable_reader.exe, нажмите на "Options..." и включите "Load Local Libraries From Disk".

1715781703688.png


После загрузки библиотек вы можете начать реверс, нажав клавишу Enter или дважды щелкнув на имени файла, в результате чего появится диалоговое окно "Анализ", которое можно настроить.
Для данного упражнения менять ее не нужно, однако я приглашаю вас изучить доступные опции и их возможности. После нажатия кнопки OK вы увидите код разборки двоичного файла, вам нужно будет немного подождать, пока Ghidra проанализирует весь двоичный файл, индикатор выполнения можно найти в правом нижнем углу экрана:

1715781725060.png


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

Теперь, когда анализ выполнен, мы можем посмотреть на программу. Исследовать этот бинарник будет довольно просто, так как мы уже знаем одну строку, используемую в диалоговом окне, давайте откроем Search > Program text, затем введем "You clicked Yes!", в fields включим all fields, а в блоке включим all blocks, затем нажмем Search all и дважды щелкнем по первой находке в результатах.

1715781757638.png


Мы видим, что возможны два варианта: либо функция позволяет выбрать "да" и закрыть, либо "нет" и закрыть. Реального назначения у этой функции нет, однако она не позволяет программе продолжить выполнение до того, как вы нажмете на кнопку мыши, и, следовательно, не дает вам запустить ее.

Интересной информацией, на которую стоит обратить внимание, являются XREFS, которые соответствуют месту, где вызывается эта функция (FUN_00401000). Здесь мы видим, что функция вызывается FUN_00401130, давайте дважды щелкнем и посмотрим, что это за функция.

1715781784245.png


Похоже, что эта функция, по сути, является нашей главной функцией. Она принимает два параметра в качестве аргументов и передает их второй функции. Первая функция отвечает за диалоговое окно.

Давайте заменим инструкцию "CALL FUN_00401000" на инструкцию NOP:

1715781805911.png


Как видите, теперь после нашей инструкции идет куча "?". Это потому, что начальная инструкция была больше, чем инструкция NOP (в шестнадцатеричном формате: 90), поэтому нам нужно заменить "?" на инструкцию NOP, чтобы соблюсти падинг. Дополнительная информация https://en.wikipedia.org/wiki/Data_structure_alignment

Результат должен выглядеть следующим образом:

1715781847511.png


Теперь экспортируйте программу в PE-файл, нажмите File > Export Program, затем выберите Original File и укажите правильный путь.

Запустим программу и посмотрим, не появится ли диалоговое окно снова:

1715781877923.png


Браво! Имейте в виду, что в большинстве программ требуются гораздо более сложные взаимодействия, и этот курс не посвящен реверс-инжинирингу. Однако большой аспект успешного фаззинга заключается в устранении того, что делает фаззер медленнее, и GUI является большой частью этого. Если вы хотите продолжить исследования в области фаззинга, вы должны иметь некоторый интерес к RE.

Смещение функций​

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

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

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

Вот схема, иллюстрирующая этот процесс.

1715781933715.png


Как выбрать таргет-функцию?

Таргет-функция должна выполнять эти действия в течение всего времени своего существования:
  1. Откройте входной файл. Это должно происходить внутри целевой функции, чтобы вы могли читать новый входной файл для каждой итерации, поскольку входной файл переписывается между запусками таргет-функции.
  2. Разобрать его (чтобы можно было измерить охват разбора файла).
  3. Закройте входной файл. Это важно, потому что если входной файл не закрыт, WinAFL не сможет его переписать.
  4. Вернуться нормально (чтобы WinAFL мог "поймать" этот возврат и перенаправить выполнение. "Возврат" через ExitProcess() и тому подобное работать не будет)
Как найти виртуальное смещение функции?
  1. Статический анализ с помощью таких инструментов, как Ghidra и radare2
  2. Отладка кода с помощью WinDBG или x64dbg (установка точек останова и анализ параметров функций во время выполнения).
  3. Использование вспомогательных инструментов, таких как мониторы API, мониторы процессов и инструменты покрытия, такие как ProcMon.
Поиск смещений с помощью статического анализа с Ghidra

Двоичный файл содержит несколько строк, одна из них - "Failed to open file", давайте щелкнем меню Search, затем Program Text и найдем это предложение:

1715782030222.png


Давайте нажмем кнопку Search all и изучим результат:

1715782045298.png


Давайте дважды щелкнем по первому вхождению в пространство имен FUN_00401060

1715782063515.png


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

C-подобный:
void __cdecl FUN_00401060(int argc, int argv)
{
  uint openResult;
  uint readResult;
  WCHAR fileContentBuffer[6]; // Buffer to store file content
  uint localVariable;

  localVariable = DAT_0041c040 ^ (uint)&stack0xfffffffc;

  if (argc < 2) {
    FUN_00401130((int)s_Usage:_%s_<filename>_0041c000); // Print usage message
  }
  else {
    openResult = FID_conflict:__open(*(char **)(argv + 4), 0x8000); // Open file specified in argv[1]
 
    if ((int)openResult < 0) {
      FUN_00401130((int)s_Failed_to_open_file:_%s_0041c018); // Print error message if file opening fails
    }
    else {
      while (readResult = FUN_00406348(openResult, fileContentBuffer, 10), 0 < (int)readResult) {
        FUN_00401130((int)&DAT_0041c034); // Print file contents
      }
      FUN_00407b70(openResult); // Close the file
    }
  }
  FUN_0040116a(localVariable ^ (uint)&stack0xfffffffc); // Some additional function call
  return;
}

Звучит как совпадение! Теперь давайте найдем смещение этой функции. Это довольно просто, давайте щелкнем правой кнопкой мыши на функции и покажем байт. Мы видим, что адрес функции - 0x00401060, а базовый адрес - 0x0040000, поэтому смещение функции - 0x01060.

Шпора GHIDRA: https://ghidra-sre.org/CheatSheet.html

Подготовьте среду для фаззинга​


Фаззинг бинарных файлов - довольно ресурсоемкая задача, и вот несколько вещей, которые вы можете сделать, чтобы подготовить свое окружение к бесперебойной работе фаззинга:
  1. Отключение автоматической отладки
  2. Отключение антивирусного сканирования

Оптимизация​

Наличие хорошего корпуса входных данных - очень важный аспект фаззинга. WinAFL предлагает два варианта оптимизации корпуса с помощью c-min.py. Примеры использования:
  1. Типичное использование
winafl-cmin.py -D D:\DRIO\bin32 -t 100000 -i in -o minset -covtype edge -coverage_module m.dll -target_module test.exe -target_method fuzz -nargs 2 — test.exe @@

  1. Запуск с сохранением крашей только с 4 рабочими с рабочим каталогом:
winafl-cmin.py -C –dry-run -w 4 –working-dir D:\dir -D D:\DRIO\bin32 -t 10000 -i in -i C:\fuzz\in -o out_mini -covtype edge -coverage_module m.dll -target_module test.exe -target_method fuzz -nargs 2 — test.exe @@

  1. Чтение из определенного файла
winafl-cmin.py -D D:\DRIO\bin32 -t 100000 -i in -o minset -f foo.ext -covtype edge -coverage_module m.dll -target_module test.exe -target_method fuzz -nargs 2 — test.exe @@

  1. Чтение из определенного файла с шаблоном
winafl-cmin.py -D D:\DRIO\bin32 -t 100000 -i in -o minset -f prefix-@@-foo.ext -covtype edge -coverage_module m.dll -target_module test.exe -target_method fuzz -nargs 2 — test.exe @@

  1. Типичное применение со статическими приборами
winafl-cmin.py -Y -t 100000 -i in -o minset — test.exe @@

Запуск winafl-cmin.py может занять некоторое время, поэтому наберитесь терпения.

Запуск фаззера​


Мы запачили бинарь, чтобы сделать его пригодным для фаззинга, нашли смещение функции, которую хотим протестировать, теперь давайте повеселимся и запустим фаззер! WinAFL предлагает различные опции, давайте перечислим их:
  1. t - таймаут на одну итерацию фаззинга. Если он не завершен, WinAFL перезапускает программу;
  2. D - путь к DynamoRIO
  3. coverage_module - Модуль(и), записывающий(ие) покрытие.
  4. target_module - Модуль целевой функции.
  5. target_offset - Виртуальное смещение функции для фаззинга от начала модуля;
  6. fuzz_iterations - Итерации фаззинга перед перезапуском выполнения программы.
  7. call_convention - указание конвекции вызова: sdtcall, cdecl и thiscall.
  8. nargs - количество аргументов, которые принимает функция для фаззинга. Указатель this (используемый в конвенции вызова thiscall) также считается аргументом.
    ВАЖНО! Помните, что нужно использовать правильную версию AFL для цели, которую вы хотите профаззить! Здесь мы будем использовать 32-битную версию!
Поскольку наш бинарь предназначен для открытия и чтения из текстового файла, создайте папку in и поместите в нее текстовый файл с простой фразой в качестве содержимого.
Теперь давайте перейдем в каталог сборки WinAFL_32 и выполним следующую команду:

afl-fuzz.exe -i in -o out -t 10000 -D C:\WinAFL\DynamoRIO\bin32 -- -fuzz_iterations 500 -coverage_module vulnerable_reader.exe -target_module vulnerable_reader.exe -target_offset 0x01060 -nargs 3 -call_convention thiscall -- vulnerable_reader.exe @@

Если все прошло успешно, вы должны увидеть, как появляется эта красота:

1715782261670.png


Теперь дело за временем. Дайте фаззеру поработать несколько минут, после чего вы должны увидеть, как появляются краши.

1715782295688.png


Анализ краша​


Здесь WinAFL очень быстро обнаружил сбой. Я специально создал бинарник, очень простой для аварийного завершения работы, чтобы этот учебник было интересно делать. Как вы видите, WinAFL присваивает файлу сбойного кода имя со статусом и типом сбоя. Вы можете найти их в вашем каталоге out > crashes:

1715782344438.png


Очевидно, что это Stack BoF, поскольку программа была специально разработана для этого. Однако давайте откроем ее в WinDBG и проведем анализ первопричины сбоя.

Запустите WinDBG и нажмите File > Launch Executable (advanced), затем укажите путь к уязвимому бинарному файлу как Executable и файл crash_id как Arguments, затем нажмите Go, чтобы запустить программу.

1715782365714.png


Как вы можете видеть, WinDBG сразу же кричит, что обнаружен выход за пределы буфера стека. Если вы хотите узнать больше об анализе первопричин с помощью WinDBG, предлагаю вам это замечательное видео: https://hardik05.wordpress.com/2021...th-time-travel-debugging-with-windbg-preview/.


Эксплуатация​

​Эта статья не предназначена для того, чтобы научить вас эксплуатации, однако существует множество очень хороших ресурсов на эту тему, и мне показалось интересным перечислить некоторые из них здесь:

Ссылки​


Untitled.png
 

Вложения

  • vulnerable_reader.zip
    63.6 КБ · Просмотры: 12
Следует понимать что главное что мы ищем плохо сделанную валидацию входящих данных.
Шаг. 1 надо найти inputs(точки, где программа берет любые данные из вне, чтение из файлов, сокетов, пользовательский ввод, получение некоторых структур от внешних апи которые можно будет подделывать, любые входящие данные из вне). На апи открытия и закрытия каналов нам практически плевать, нам нужны функции чтения.
Шаг. 2 имеет смысл посмотреть что это за тип данных, и как прога его проверяет, что бы фазить предметно(много ли вы нафазити если там в начале идет проверка меджика). Без реверса это почти гарантированная потеря времени, ну или я заблуждаюсь =)
 
Последнее редактирование:
Пожалуйста, обратите внимание, что пользователь заблокирован
Без реверса это почти гарантированная потеря времени, ну или я заблуждаюсь =)
На чем-то сложном 100%. Я недавно реверсил входящий буфер - структура 0x200 байт. В ней поля состоят из строк, указателей, хэндлов и пр. Я сомневаюсь, что какой-либо фаззер сможет сделать покрытие такого бинаря без реверса и точечной обработки полей.

В итоге руками нашел логический баг. Он не вызывал никаких крашей, следовательно фаззер его бы просто не обнаружил.
 
Последнее редактирование:


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх