Анализ патча CVE-2016-0189. Часть 1
Эта статья вышла в 2016 году, но она всё ещё актуальна и будет полезна всем, кто интересуется безопасностью информационных систем.
В прошлом месяце в Microsoft выпустили бюллетень по безопасности MS16-051 в рамках ежемесячного Patch Tuesday (Май, 2016 год). Он посвящён уязвимостям Internet Explorer, в том числе уязвимости Scripting Engine Memory Corruption (CVE-2016-0189), использованной в таргетированных атаках в Южной Корее [1].
Сегодня мы проанализируем патч и выясним, в чём заключается уязвимость, а потом создадим эксплойт для проверки концепции.
Пропатченный vs. Непропатченный
Мы используем BinDiff для сравнения пропатченной и непропатченной версии vbscript.dll. Как можно увидеть на скриншоте ниже, в патче изменилось только несколько функций:
Наиболее подозрительное изменение произошло в функции AccessArray. Исследуем её в IDA:
Апрель vs Май
Видите разницу? Патч добавил блокировку массива до того, как код получит к нему доступ. Был добавлен код для снятия блокировки в случае ошибки и больше никаких изменений в функции сделано не было.
Теперь обратим внимание на политику безопасности, связанную с функциями вроде
Апрель vs Май
И снова изменения весьма очевидны. До патча
Анализ
Мы обнаружили две уязвимости, исправленные в этом патче. Посмотрим, можем ли мы использовать их для создания эксплойта.
Уязвимость #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-типов,
// 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
До патча функция
Во второй части подробно рассмотрим обход SafeMode с уязвимостями #1 и #2 и приведём доказательства нашей концепции. Не пропустите!