0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p><em>Эта статья<a>вышла в 2016 году</a>, но она всё ещё актуальна и будет полезна всем, кто интересуется безопасностью информационных систем.</em></p>
1
<p><em>Эта статья<a>вышла в 2016 году</a>, но она всё ещё актуальна и будет полезна всем, кто интересуется безопасностью информационных систем.</em></p>
2
<p>В прошлом месяце в Microsoft выпустили бюллетень по безопасности<strong><a>MS16-051</a></strong>в рамках ежемесячного<strong>Patch Tuesday</strong>(Май, 2016 год). Он посвящён уязвимостям Internet Explorer, в том числе уязвимости<strong><a>Scripting Engine Memory Corruption</a></strong>(CVE-2016-0189), использованной в таргетированных атаках в Южной Корее<a>[1]</a>.</p>
2
<p>В прошлом месяце в Microsoft выпустили бюллетень по безопасности<strong><a>MS16-051</a></strong>в рамках ежемесячного<strong>Patch Tuesday</strong>(Май, 2016 год). Он посвящён уязвимостям Internet Explorer, в том числе уязвимости<strong><a>Scripting Engine Memory Corruption</a></strong>(CVE-2016-0189), использованной в таргетированных атаках в Южной Корее<a>[1]</a>.</p>
3
<p>Сегодня мы проанализируем патч и выясним, в чём заключается уязвимость, а потом создадим эксплойт для проверки концепции.</p>
3
<p>Сегодня мы проанализируем патч и выясним, в чём заключается уязвимость, а потом создадим эксплойт для проверки концепции.</p>
4
<h2>Пропатченный vs. Непропатченный</h2>
4
<h2>Пропатченный vs. Непропатченный</h2>
5
<p>Мы используем<strong><a>BinDiff</a></strong>для сравнения пропатченной и непропатченной версии<strong>vbscript.dll</strong>. Как можно увидеть на скриншоте ниже, в патче изменилось только несколько функций:</p>
5
<p>Мы используем<strong><a>BinDiff</a></strong>для сравнения пропатченной и непропатченной версии<strong>vbscript.dll</strong>. Как можно увидеть на скриншоте ниже, в патче изменилось только несколько функций:</p>
6
<p>Наиболее подозрительное изменение произошло в функции<strong>AccessArray</strong>. Исследуем её в IDA:</p>
6
<p>Наиболее подозрительное изменение произошло в функции<strong>AccessArray</strong>. Исследуем её в IDA:</p>
7
<h3>Апрель vs Май</h3>
7
<h3>Апрель vs Май</h3>
8
<p>Видите разницу? Патч добавил блокировку массива до того, как код получит к нему доступ. Был добавлен код для снятия блокировки в случае ошибки и больше никаких изменений в функции сделано не было.</p>
8
<p>Видите разницу? Патч добавил блокировку массива до того, как код получит к нему доступ. Был добавлен код для снятия блокировки в случае ошибки и больше никаких изменений в функции сделано не было.</p>
9
<p>Теперь обратим внимание на политику безопасности, связанную с функциями вроде IsUnsafeAllowed.</p>
9
<p>Теперь обратим внимание на политику безопасности, связанную с функциями вроде IsUnsafeAllowed.</p>
10
<h3>Апрель vs Май</h3>
10
<h3>Апрель vs Май</h3>
11
<p>И снова изменения весьма очевидны. До патча IsUnsafeAllowed вызывал функцию, которая всегда возвращает ноль без проверки политики, а пропатченный код вызывает указатель функции, находящийся в QueryProtectedPolicyPtr. Функция InitializeProtectedPolicy инициализирует указатель при помощи GetProcAddress.</p>
11
<p>И снова изменения весьма очевидны. До патча IsUnsafeAllowed вызывал функцию, которая всегда возвращает ноль без проверки политики, а пропатченный код вызывает указатель функции, находящийся в QueryProtectedPolicyPtr. Функция InitializeProtectedPolicy инициализирует указатель при помощи GetProcAddress.</p>
12
<h3>Анализ</h3>
12
<h3>Анализ</h3>
13
<p>Мы обнаружили две уязвимости, исправленные в этом патче. Посмотрим, можем ли мы использовать их для создания эксплойта.</p>
13
<p>Мы обнаружили две уязвимости, исправленные в этом патче. Посмотрим, можем ли мы использовать их для создания эксплойта.</p>
14
<h4>Уязвимость #1 - отсутствие SafeArray блокировки в AccessArray</h4>
14
<h4>Уязвимость #1 - отсутствие SafeArray блокировки в AccessArray</h4>
15
<p>Раз патч добавил код для блокировки массива, значит злоумышленник мог каким-то образом изменить массив в процессе обращения к нему, чтобы предположения о свойствах массива (например, Dims или cbElements) не совпадали.</p>
15
<p>Раз патч добавил код для блокировки массива, значит злоумышленник мог каким-то образом изменить массив в процессе обращения к нему, чтобы предположения о свойствах массива (например, Dims или cbElements) не совпадали.</p>
16
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<p>В основном цикле код начинает с самого правого измерения индексов массива и вычисляет указатели, данные индексам. Обратите внимание, что если<strong>variant-тип</strong>индекса VT_I2 или VT_I4, то значения считываются как короткие и длинные соответственно. Однако для всех других variant-типов, rtVariantChangeTypeEx вызывается для оценки индекса. Когда в эту функцию передаётся объект<strong>javascript</strong>, она извлекает значение, вызывая<strong>valueOf</strong>целевого объекта. Если предоставить объект, у которого есть выбранная нами valueOf-функция, мы можем запустить код<strong>vbscript</strong>или<strong>javascript</strong>внутри<strong>rtVariantChangeTypeEx</strong>.</p>
16
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<p>В основном цикле код начинает с самого правого измерения индексов массива и вычисляет указатели, данные индексам. Обратите внимание, что если<strong>variant-тип</strong>индекса VT_I2 или VT_I4, то значения считываются как короткие и длинные соответственно. Однако для всех других variant-типов, rtVariantChangeTypeEx вызывается для оценки индекса. Когда в эту функцию передаётся объект<strong>javascript</strong>, она извлекает значение, вызывая<strong>valueOf</strong>целевого объекта. Если предоставить объект, у которого есть выбранная нами valueOf-функция, мы можем запустить код<strong>vbscript</strong>или<strong>javascript</strong>внутри<strong>rtVariantChangeTypeEx</strong>.</p>
17
// exploit & triggerBug are defined in vbscript var o; o = {"valueOf": function () { triggerBug(); return 1; }}; setTimeout(function() {exploit(o);}, 50);<p>Можем использовать это для изменения размера массива, который мы сейчас индексируем! Например, представьте, что у нас есть двумерный массив со следующими размерами:</p>
17
// exploit & triggerBug are defined in vbscript var o; o = {"valueOf": function () { triggerBug(); return 1; }}; setTimeout(function() {exploit(o);}, 50);<p>Можем использовать это для изменения размера массива, который мы сейчас индексируем! Например, представьте, что у нас есть двумерный массив со следующими размерами:</p>
18
ReDim Preserve A(1, 2000)<p>Затем мы обращаемся к массиву вроде A(1, 2), idx в функции<strong>AccessArray</strong>вычисляется как 1 + (2 * (2 - 0)), что равно 5. Это умножается на<strong>cbElements</strong>, что всегда равно<strong>sizeof(VARIANT)</strong>= 16, потому что массивы в<strong>vbscript</strong>содержат<strong>variants: 80</strong>. Наконец, это добавляется к указателю (<strong>pvData</strong>) для возврата данных, указанных A(1, 2).</p>
18
ReDim Preserve A(1, 2000)<p>Затем мы обращаемся к массиву вроде A(1, 2), idx в функции<strong>AccessArray</strong>вычисляется как 1 + (2 * (2 - 0)), что равно 5. Это умножается на<strong>cbElements</strong>, что всегда равно<strong>sizeof(VARIANT)</strong>= 16, потому что массивы в<strong>vbscript</strong>содержат<strong>variants: 80</strong>. Наконец, это добавляется к указателю (<strong>pvData</strong>) для возврата данных, указанных A(1, 2).</p>
19
<p>Обычно, это не проблема, потому что выделенный буфер составляет 16 * 2 * 2001 == 64032 байтов. Однако, это смещение выходит за пределы допустимого, если размеры буфера уменьшить. Другими словами, мы можем обратиться к A(1, 2), когда массив определен как A(1, 1).</p>
19
<p>Обычно, это не проблема, потому что выделенный буфер составляет 16 * 2 * 2001 == 64032 байтов. Однако, это смещение выходит за пределы допустимого, если размеры буфера уменьшить. Другими словами, мы можем обратиться к A(1, 2), когда массив определен как A(1, 1).</p>
20
<p>Перекрывая освобожденную память после ресайза массива с нашей строкой эксплойта, мы можем создать строки и варианты<strong>vbscript</strong>для достижения<strong>out-of-bound</strong>примитива на чтение/запись. Это позволяет нам получить адрес объекта, прочитать память по адресу и записать память в адрес, многократно вызывая баг.</p>
20
<p>Перекрывая освобожденную память после ресайза массива с нашей строкой эксплойта, мы можем создать строки и варианты<strong>vbscript</strong>для достижения<strong>out-of-bound</strong>примитива на чтение/запись. Это позволяет нам получить адрес объекта, прочитать память по адресу и записать память в адрес, многократно вызывая баг.</p>
21
<h2>Уязвимость #2 - обход IsUnsafeAllowed</h2>
21
<h2>Уязвимость #2 - обход IsUnsafeAllowed</h2>
22
<p>До патча функция IsUnsafeAllowed всегда возвращала 1, потому что COleScript::OnEnterBreakPoint - dummy-функция, которая всегда возвращает 0. Патч исправил ошибку и QueryProtectedPolicy выполняется корректно, если она доступна системе (поддерживается только в Windows 8.1 и позже).</p>
22
<p>До патча функция IsUnsafeAllowed всегда возвращала 1, потому что COleScript::OnEnterBreakPoint - dummy-функция, которая всегда возвращает 0. Патч исправил ошибку и QueryProtectedPolicy выполняется корректно, если она доступна системе (поддерживается только в Windows 8.1 и позже).</p>
23
<p><em>Во второй части подробно рассмотрим обход SafeMode с уязвимостями #1 и #2 и приведём доказательства нашей концепции. Не пропустите!</em></p>
23
<p><em>Во второй части подробно рассмотрим обход SafeMode с уязвимостями #1 и #2 и приведём доказательства нашей концепции. Не пропустите!</em></p>
24
24