HTML Diff
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-&gt;vt ) { v14 = curVar-&gt;iVal; } else if ( VT_I4 == curVar-&gt;vt ) { v14 = curVar-&gt;lVal; } else { v22 = 0; v18 = rtVariantChangeTypeEx(curVar, &amp;v22, 0x400, 2, 3u, v20, v21); if ( v18 &lt; 0 ) return CScriptRuntime::RecordHr(a4, v18, v19, v20, v21); v14 = v23; } v15 = v14 - v25-&gt;lLbound; // lLbound is always 0 if ( v15 &lt; 0 || v15 &gt;= v25-&gt;cElements ) return CScriptRuntime::RecordHr(a4, 0x8002000B, v25, v20, v21); numDim = (numDim - 1); idx = v15 + v11; if ( numDim &lt;= 0 ) break; ++v25; v11 = v25-&gt;cElements * idx; curVar_ = (a4 + 16); a4 = (a4 + 16); } *v24 = arr-&gt;pvData + idx * arr-&gt;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-&gt;vt ) { v14 = curVar-&gt;iVal; } else if ( VT_I4 == curVar-&gt;vt ) { v14 = curVar-&gt;lVal; } else { v22 = 0; v18 = rtVariantChangeTypeEx(curVar, &amp;v22, 0x400, 2, 3u, v20, v21); if ( v18 &lt; 0 ) return CScriptRuntime::RecordHr(a4, v18, v19, v20, v21); v14 = v23; } v15 = v14 - v25-&gt;lLbound; // lLbound is always 0 if ( v15 &lt; 0 || v15 &gt;= v25-&gt;cElements ) return CScriptRuntime::RecordHr(a4, 0x8002000B, v25, v20, v21); numDim = (numDim - 1); idx = v15 + v11; if ( numDim &lt;= 0 ) break; ++v25; v11 = v25-&gt;cElements * idx; curVar_ = (a4 + 16); a4 = (a4 + 16); } *v24 = arr-&gt;pvData + idx * arr-&gt;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 &amp; triggerBug are defined in vbscript var o; o = {"valueOf": function () { triggerBug(); return 1; }}; setTimeout(function() {exploit(o);}, 50);<p>Можем использовать это для изменения размера массива, который мы сейчас индексируем! Например, представьте, что у нас есть двумерный массив со следующими размерами:</p>
17 // exploit &amp; 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