Анализ патча CVE-2016-0189. Часть 1 | OTUS
⚡ Подписка на курсы OTUS!
Интенсивная прокачка навыков для IT-специалистов!
Подробнее

Курсы

Программирование
Backend-разработчик на PHP
-9%
Алгоритмы и структуры данных
-9%
Team Lead
-6%
Архитектура и шаблоны проектирования Разработчик IoT
-13%
C# Developer. Professional
-9%
HTML/CSS
-11%
C# ASP.NET Core разработчик
-5%
Kotlin Backend Developer
-8%
iOS Developer. Professional
-8%
Java Developer. Professional JavaScript Developer. Professional Базы данных Android Developer. Professional Framework Laravel Cloud Solution Architecture Highload Architect Reverse-Engineering. Professional Vue.js разработчик Agile Project Manager VOIP инженер Scala-разработчик Супер-практикум по использованию и настройке GIT Symfony Framework Java Developer. Basic Unity Game Developer. Professional Супер-интенсив Azure
Инфраструктура
Экспресс-курс «IaC Ansible»
-10%
Administrator Linux.Basic
-10%
Мониторинг и логирование: Zabbix, Prometheus, ELK
-10%
Экспресс-курс «CI/CD или Непрерывная поставка с Docker и Kubernetes»
-30%
Administrator Linux. Professional
-6%
Дизайн сетей ЦОД
-13%
NoSQL Основы Windows Server MS SQL Server Developer Инфраструктурная платформа на основе Kubernetes Cloud Solution Architecture Highload Architect Разработчик голосовых ассистентов и чат-ботов VOIP инженер Супер-практикум по работе с протоколом BGP Супер - интенсив по паттернам проектирования Супер - интенсив по Kubernetes Супер-интенсив "Tarantool"
Специализации Курсы в разработке Подготовительные курсы
+7 499 938-92-02

Анализ патча CVE-2016-0189. Часть 1

ISS_Deep_19.4_site-5020-966e21.png

Эта статья вышла в 2016 году, но она всё ещё актуальна и будет полезна всем, кто интересуется безопасностью информационных систем.

В прошлом месяце в Microsoft выпустили бюллетень по безопасности MS16-051 в рамках ежемесячного Patch Tuesday (Май, 2016 год). Он посвящён уязвимостям Internet Explorer, в том числе уязвимости Scripting Engine Memory Corruption (CVE-2016-0189), использованной в таргетированных атаках в Южной Корее [1].

Сегодня мы проанализируем патч и выясним, в чём заключается уязвимость, а потом создадим эксплойт для проверки концепции.

Пропатченный vs. Непропатченный

Мы используем BinDiff для сравнения пропатченной и непропатченной версии vbscript.dll. Как можно увидеть на скриншоте ниже, в патче изменилось только несколько функций:

1-20219-7bde3a.png

Наиболее подозрительное изменение произошло в функции AccessArray. Исследуем её в IDA:

2-20219-ca905d.png

Апрель vs Май

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

Теперь обратим внимание на политику безопасности, связанную с функциями вроде IsUnsafeAllowed.

3-20219-5a4b58.png 4-20219-ee50b5.png

Апрель vs Май

И снова изменения весьма очевидны. До патча IsUnsafeAllowed вызывал функцию, которая всегда возвращает ноль без проверки политики, а пропатченный код вызывает указатель функции, находящийся в QueryProtectedPolicyPtr. Функция InitializeProtectedPolicy инициализирует указатель при помощи GetProcAddress.

5-20219-4bc014.png

Анализ

Мы обнаружили две уязвимости, исправленные в этом патче. Посмотрим, можем ли мы использовать их для создания эксплойта.

Уязвимость #1 — отсутствие SafeArray блокировки в AccessArray

Раз патч добавил код для блокировки массива, значит злоумышленник мог каким-то образом изменить массив в процессе обращения к нему, чтобы предположения о свойствах массива (например, Dims или cbElements) не совпадали.

  while ( 1 )
  {
    curVar = VAR::PvarCutAll(curVar_);
    if ( VT_I2 == curVar->vt )
    {
      v14 = curVar->iVal;
    }
    else if ( VT_I4 == curVar->vt )
    {
      v14 = curVar->lVal;
    }
    else
    {
      v22 = 0;
      v18 = rtVariantChangeTypeEx(curVar, &v22, 0x400, 2, 3u, v20, v21);
      if ( v18 < 0 )
        return CScriptRuntime::RecordHr(a4, v18, v19, v20, v21);
      v14 = v23;
    }
    v15 = v14 - v25->lLbound;                   // lLbound is always 0
    if ( v15 < 0 || v15 >= v25->cElements )
      return CScriptRuntime::RecordHr(a4, 0x8002000B, v25, v20, v21);
    numDim = (numDim - 1);
    idx = v15 + v11;
    if ( numDim <= 0 )
      break;
    ++v25;
    v11 = v25->cElements * idx;
    curVar_ = (a4 + 16);
    a4 = (a4 + 16);
  }
  *v24 = arr->pvData + idx * arr->cbElements;   // cbElements == 16

В основном цикле код начинает с самого правого измерения индексов массива и вычисляет указатели, данные индексам. Обратите внимание, что если variant-тип индекса VT_I2 или VT_I4, то значения считываются как короткие и длинные соответственно. Однако для всех других variant-типов, rtVariantChangeTypeEx вызывается для оценки индекса. Когда в эту функцию передаётся объект javascript, она извлекает значение, вызывая valueOf целевого объекта. Если предоставить объект, у которого есть выбранная нами valueOf-функция, мы можем запустить код vbscript или javascript внутри rtVariantChangeTypeEx.

// exploit & triggerBug are defined in vbscript
var o;
o = {"valueOf": function () {
        triggerBug();
        return 1;
    }};
setTimeout(function() {exploit(o);}, 50);

Можем использовать это для изменения размера массива, который мы сейчас индексируем! Например, представьте, что у нас есть двумерный массив со следующими размерами:

ReDim Preserve A(1, 2000)

Затем мы обращаемся к массиву вроде A(1, 2), idx в функции AccessArray вычисляется как 1 + (2 * (2 - 0)), что равно 5. Это умножается на cbElements, что всегда равно sizeof(VARIANT) = 16, потому что массивы в vbscript содержат variants: 80. Наконец, это добавляется к указателю (pvData) для возврата данных, указанных A(1, 2).

Обычно, это не проблема, потому что выделенный буфер составляет 16 * 2 * 2001 == 64032 байтов. Однако, это смещение выходит за пределы допустимого, если размеры буфера уменьшить. Другими словами, мы можем обратиться к A(1, 2), когда массив определен как A(1, 1).

Перекрывая освобожденную память после ресайза массива с нашей строкой эксплойта, мы можем создать строки и варианты vbscript для достижения out-of-bound примитива на чтение/запись. Это позволяет нам получить адрес объекта, прочитать память по адресу и записать память в адрес, многократно вызывая баг.

Уязвимость #2 — обход IsUnsafeAllowed

До патча функция IsUnsafeAllowed всегда возвращала 1, потому что COleScript::OnEnterBreakPoint — dummy-функция, которая всегда возвращает 0. Патч исправил ошибку и QueryProtectedPolicy выполняется корректно, если она доступна системе (поддерживается только в Windows 8.1 и позже).

Во второй части подробно рассмотрим обход SafeMode с уязвимостями #1 и #2 и приведём доказательства нашей концепции. Не пропустите!

Не пропустите новые полезные статьи!

Спасибо за подписку!

Мы отправили вам письмо для подтверждения вашего email.
С уважением, OTUS!

Автор
0 комментариев
Для комментирования необходимо авторизоваться