Фаззинг современных протоколов UDP-игр с помощью фаззеров на основе снимков

Фаззингсовременныхпротоколовudpигрспомощьюфаззеровнаосновеснимков

Axel ‘ 0vercl0k ‘ Souchet недавно сделал открытый исходный код многообещающего фаззера на основе снимков. По его собственным словам: » что за пух или wtf – это распределенный, управляемый кодом, настраиваемый кроссплатформенный моментальный снимок. фаззер на основе, разработанный для атаки на цели режима пользователя или ядра, работающие в Microsoft Windows ».

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

Фаззинг с использованием того, что за fuzz, фаззер на основе снимков для Windows

Фаззеры на основе снимков

Фаззеры все больше и больше эффективен при поиске программных ошибок. Фаззеры на основе моментальных снимков составляют расширенную категорию фаззинга, в которой используются эмуляторы (или другие технологии виртуализации) для эффективного и детерминированного фаззинга «труднодоступного» кода с одновременным использованием мощных возможностей самоанализа.

Эти типы фаззеров обычно запускаются с помощью «моментального снимка», полученного из действующей системы (или виртуальной машины), непосредственно перед выполнением кода, фаззинг которого интересует исследователя. Моментальные снимки часто содержат полную системную память, регистры ЦП или любую информацию времени выполнения, необходимую для точного возобновления выполнения в эмулируемой среде.

Типичный алгоритм фаззера на основе снимков и с обратной связью

Управляя собственным эмулятором всей системы, фаззер на основе снимков может эффективно отслеживать «грязные» страницы памяти во время выполнения, сбрасывая регистры памяти и ЦП в «чистое» состояние (снимок) в в любой точке.

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

Создание снимка

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

Первый шаг – определить место кода в двоичном файле игры, с которого мы хотели бы начать фаззинг. С некоторой реверсией мы определили подходящее место для сбора моментального снимка после того, как исходящие из сети UDP-пакеты будут повторно собраны и расшифрованы для обработки игровым клиентом:

Определение точки интереса, которая может быть полезна для снэпшота, и переход от

Сделав снимок системы в начале Сообщения процесса (...) , наш фаззер сможет ввести искаженные пакетные данные и начать выполнение вперед в 66 + парсеры сообщений, вызываемые этой функцией.

После инструкции включены в что за фигня , мы используем WinDbg на Hyper-V V M настроен для отладки ядра и 4 ГБ ОЗУ. После достижения выбранной точки останова в игровом процессе мы используем bdump.js в соответствии с инструкциями по созданию «моментального снимка» системы в данный момент:

... kd>! bdump "C: \ fuzz \ dump" создание каталога ... сохранение рег ... зарегистрировать исправления ... не знаю, как получить mxcsr_mask или fpop, установив в ноль ... не знаю как получить регистры avx, пропускаю ... т.р.база не канонична ... старый tr.base: 0x7fe 140244 новая tr.base: 0xfffff 26818 fe 500000 установка флага 0x 2021 на cs.attr ... старый cs.attr: 0x 50 б новый cs.attr: 0x 580 б сохраните память, возьмите кофе или покурите, это, вероятно, займет около 14 - 16 минут ... Создание C: fuzz dump mem.dmp - дамп растрового изображения активного ядра и пользовательской памяти Сбор страниц для записи на свалку. Это может занять некоторое время. 0% написано. 5% написано. 50 осталось секунд. 15% написано. - ножница - % написано. Осталось 2 секунды. Записал 2,9 ГБ в 50 сек . Средняя скорость передачи была 229. 4 МБ / с. Дамп успешно записан сделано! @ $ bdump ("C: \ fuzz \ dump")

Собрав полный снимок системы с игрой, находящейся в точке входа ProcessMessages (...) , нам больше не нужен «живой» (гость Hyper-V ) система. Фактическое фаззинг будет происходить в автономном режиме в эмулируемой среде, управляемой фаззером на основе моментальных снимков.

Использование моментальных снимков

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

Мы можем начать с создания копии fuzzer_hevd.cc , пример ремня безопасности, который идет в комплекте с фаззером. Разработка собственной копии под названием fuzzer_game.cc , необходимо указать три основных интерфейса:

  • Init (Options, CpuState) - Выполните любые одноразовые настройки памяти / регистрации для эмулируемая система, определите «цели» InsertTestcase (FuzzedData, Size) - Внедрить сгенерированный тестовый набор (FuzzedData) в эмулируемую систему
        Восстановить() - Восстановление любого «внешнего» состояния, реализованного жгутом после выполнения каждого тестового набора

      Реализовать nt Инициализация (...) для нашего жгута, мы сначала хотим определить «точку остановки» для остановки выполнения и восстановления фаззера. Достижение инструкции возврата ParseMessages (...) без сбоев - хорошее место для остановки выполнения, поскольку мы предполагаем, что нечеткое сообщение было обработано «правильно»:

        
    bool В этом ( const Options_t & Опц , const CpuState_t & CpuState ) { // останавливаем выполнение, если мы достигаем инструкции ret в ParseMessages (.. .) если ( ! g_Backend -> Установить точку разрыва ( Gva_t ( 0x 2021 F 74 C5 ), [] ( Backend_t Бэкэнд ) { DebugPrint ( «Достигнут конец функции
  • n " ); Бэкэнд -> Останавливаться ( Ok_t ()); })) { возвращение ложный ; } // Настройте диспетчер исключений пользовательского режима Windows на обнаружение нарушений доступа (); возвращение истинный ; }
     

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

    Затем мы должны реализовать InsertTestcase (...) интерфейс для жгута фаззинга. Это будет вызываться перед каждым выполнением и должно использоваться для внедрения нечеткого тестового примера, предоставленного фаззером, в эмулируемую систему:

    bool InsertTestcase ( const uint8_t Буфер , c onst size_t Размер буфера ) { // структура 'битового буфера', которую мы реконструировали из исполняемого файла игры bf_read буфер ; // считываем объект битового буфера исходного сетевого сообщения из память моментальных снимков если ( ! g_Backend -> VirtReadStruct ( Gva_t ( g_Backend -> Rdx ()), & буфер )) { Отладка Печать ( «Не удалось прочитать битовый буфер во время внедрения тестового набора!» ); возвращение ложный; } // соответственно модифицируем битовый буфер сетевого сообщения для этого нечеткого теста буфер . m_nCurDword знак равно 0 ; буфер . m_nNumBitsLeft знак равно 0 ; буфер . m_nDataBytes ( знак равно Размер буфера ; буфер . m_nDataBits знак равно Размер буфера 8 ; буфер . m_pDataC ур знак равно буфер . m_pData ; буфер . m_pDataEnd знак равно буфер . m_pData + Размер буфера ; // записываем измененную структуру битового буфера обратно в снимок если ( ! g_Backend -> VirtWriteStruct ( Gva_t ( g_Backend -> Rdx ()), и буфер )) { Отладочная печать ( «Не удалось записать измененный битовый буфер во время внедрения тестового примера!» ); возвращение ложный ; } // вставляем нечеткие данные сообщения в снимок для этого выполнения если ( ! g_Backend -> VirtWrite ( Gva_t (( uint 74 _ t ) буфер . m_pData ), Буфер , Размер буфера, истинный )) { Отладка печати ( «Не удалось написать следующий тестовый набор!» ); возвращение ложный; } возвращение истинный; }

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

    Наконец, мы построить фаззер с build-release-msvc.bat в командной строке разработчика VS:

    Восстановление того, что нечеткое из исходников, используя предоставленный пакетный файл сборки

    Запуск "что за пух"

    Для начала фаззинга, мы сначала должны создать несколько папок, как указано в

    использование файла readme для фаззера. Эта иерархия должна быть знакома энтузиастам фаззинга:

    Иерархия папок, необходимая для создания снимков. fuzzer

    Из этих папок мы должны заполнить следующие два:

    Оценка сбоев, вызванных моментальным снимком- фаззеры на основе Догмат. Tenet - это вечный проводник трассировки, представленный как опыт отладчика в IDA Pro . Через запрос на вытягивание , я расширил что за пух для создания трассировок Tenet с использованием встроенного бэкэнда bochs.

    Создание следующего trace.bat , мы можем сгенерировать следы Тенета для всех сбойных входных данных:

    Использование того, что нечетко для создания трассировок Тенета для ручного анализа первопричин

    Навигация Тенета требует некоторого обучения (ладно, может быть обрыв обучения ), но как только вы познакомитесь с его приливами и отливами может потребоваться всего несколько минут, чтобы устранить первопричину сбоев в неизвестном коде.

    В качестве рабочего примера мы рассмотрим сбой-EXCEPTION_ACCESS_VIOLATION_EXECUTE-0x 10000000.след :

    По тенету, синие блики - это записи в память, а желтым - чтение из памяти

    Эта кривая заканчивается сбоем и RIP установлен на 0x 10000000 . Очень быстро мы можем увидеть, что этот тестовый сценарий, сгенерированный фаззером, вызвал переполнение буфера в стеке. С Tenet мы можем буквально прокручивать между чтением / записью в память, сделанными на поврежденный адрес возврата, или назад и вперед по точкам останова, чтобы наблюдать за циклом. отвечает за переполнение.

    Очистив декомпиляцию, поскольку это относится к этой ошибке, мы остались со следующим:

    Отсутствие проверки границ позволяет циклу писать за en d из id_array

    При обработке этого сетевого сообщения, которое происходит чтобы относиться к микротранзакциям игры, он будет читать 8-битное значение num_ids вне сообщения (битовый буфер). Он использует это значение, чтобы определить, сколько 29 идентификаторы битов для чтения из сообщения с сохранением их в стековом массиве id_array .

    Поскольку id_array имеет только 29, значение длины 8 бит num_ids должен не больше, чем 29 или код продолжит писать произвольно 29 битовые значения после конца массива. Поскольку все это данные удаленного происхождения (т. Е. Контролируемые злоумышленником) и никаких файлов cookie в стеке не видно, это считается критической уязвимостью.

    Настройка фаззера

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

    Оглядываясь на сборку уязвимого кода, мы видим, что num_ids значение должно быть в р16 в инструкции 0x 500000 C1B сразу после его анализа из битового буфера сообщения:

    Подходящее место для выполнения дополнительных проверок границ по r 29 (num_ids)

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

       bool  В этом  (  const   Options_t   &   Опц  ,   const   CpuState_t   &   CpuState  )   {  // остановка выполнения, если мы дойдем до команды ret  в сообщениях ParseMessages (...)  если  ( !   g_Backend   ->   Установить точку разрыва   (  Gva_t   (  0x 2000 F 229 C5  ),   [] (  Backend_t    Бэкэнд  )   {  DebugPrint   (  "Достигнутая функция ru  d    n   " );   Бэкэнд   ->  Останавливаться  (  Ok_t   ());  }))   { возвращение ложный ;  }   / / остановить выполнение, если тестовый сценарий вызовет разбивание стека MTX  если  ( !   g_Backend   ->   SetBreakpoint   (  Gva_t   (  0x 7676767 C1B  ),   [] (  Backend_t     Бэкэнд  )   { если  (  Бэкэнд   -> Р20   ()  >   29  )   {  Отладка печати   (     n   " );   Бэкэнд   ->  Останавливаться  (  Ok_t   ());  }  }))   { возвращение ложный ;  }   // ...  }   

    Этот предотвратит создание ошибкой ненужного «шума» (сбои, производные от sam e), позволяя фаззеру тратить время на более интересные тестовые примеры. Если мы перестроим что за пух и возобновить фаззинг, мы больше не должны испытывать сбоев из-за этой проблемы.

    Вывод

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

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