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

Анализ патча 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 комментариев
Для комментирования необходимо авторизоваться
Популярное
Сегодня тут пусто