
CVE-2018-4280: Уязвимость замены порта Mach в launchd на iOS 11.2.6, приводящая к обходу песочницы, повышению привилегий и обходу проверки подписи кода.
Blanket — это эксплойт для обхода песочницы, нацеленный на iOS 11.2.6, хотя основная уязвимость была исправлена только в iOS 11.4.1. Он эксплуатирует уязвимость замены порта Mach в launchd (CVE-2018-4280), а также несколько меньших уязвимостей в других службах, чтобы выполнить код внутри процесса ReportCrash, который не имеет песочницы, работает от root и имеет привилегию task_for_pid-allow. Это дает Blanket контроль над каждым процессом, работающим на телефоне, включая критически важные для безопасности, такие как amfid.
Эксплойт состоит из нескольких этапов. В этом README будет объяснена основная уязвимость и этапы обхода песочницы шаг за шагом.
Исследуя отчеты о сбоях на iOS, я обнаружил уязвимость замены порта Mach в launchd. Вызывая сбой определенным образом, процесс может заставить ядро отправить сообщение Mach в launchd, что приводит к чрезмерному освобождению права отправки на порт Mach в пространстве IPC launchd. Это позволяет атакующему выдавать себя за любую службу launchd, которую он может найти, перед остальной системой, что открывает множество путей для повышения привилегий.
Эта уязвимость также присутствует на macOS, но срабатывание уязвимости на iOS затруднено из-за проверок в launchd, которые гарантируют, что сообщение об исключении Mach поступает от ядра.
Launchd мультиплексирует несколько различных обработчиков сообщений Mach через свой основной порт, включая обработчик MIG для сообщений об исключениях. Если процесс отправляет сообщение mach_exception_raise или mach_exception_raise_state_identity на свой собственный порт начальной загрузки, launchd получит и обработает это сообщение как исключение на уровне хоста.
К сожалению, обработка этих сообщений в launchd содержит ошибки. Если тип исключения — EXC_CRASH, то launchd освободит порты потока и задачи, отправленные в сообщении, а затем вернет KERN_FAILURE из сервисной процедуры, что заставит систему MIG снова освободить порты потока и задачи. (Предполагается, что если сервисная процедура возвращает успех, то она приняла владение всеми ресурсами в сообщении Mach, а если сервисная процедура возвращает ошибку, то она не приняла владение ни одним из ресурсов.)
Вот код из сервисной процедуры launchd для сообщений mach_exception_raise, декомпилированный с помощью IDA/Hex-Rays и слегка отредактированный для удобочитаемости:
cC
kern_return_t __fastcall
catch_mach_exception_raise( // (a) The service routine is
mach_port_t exception_port, // called with values directly
mach_port_t thread, // from the Mach message
mach_port_t task, // sent by the client. The
exception_type_t exception, // thread and task ports could
mach_exception_data_t code, // be arbitrary send rights.
mach_msg_type_number_t codeCnt)
{
__int64 __stack_guard; // ST28_8@1
kern_return_t kr; // w0@1 MAPDST
kern_return_t result; // w0@4
__int64 codes_left; // x25@6
mach_exception_data_type_t code_value; // t1@7
int pid; // [xsp+34h] [xbp-44Ch]@1
char codes_str[1024]; // [xsp+38h] [xbp-448h]@7
__stack_guard = *__stack_chk_guard_ptr;
pid = -1;
kr = pid_for_task(task, &pid);
if ( kr )
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
if ( current_audit_token.val[5] ) // (b) If the message was sent by
{ // a process with a nonzero PID
result = KERN_FAILURE; // (any non-kernel process),
} // the message is rejected.
else
{
if ( codeCnt )
{
codes_left = codeCnt;
do
{
code_value = *code;
++code;
__snprintf_chk(codes_str, 0x400uLL, 0, 0x400uLL, "0x%llx", code_value);
--codes_left;
}
while ( codes_left );
}
launchd_log_2(
0LL,
3LL,
"Host-level exception raised: pid = %d, thread = 0x%x, "
"exception type = 0x%x, codes = { %s }",
pid,
thread,
exception,
codes_str);
kr = deallocate_port(thread); // (c) The "thread" port sent in
if ( kr ) // the message is deallocated.
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
kr = deallocate_port(task); // (d) The "task" port sent in the
if ( kr ) // message is deallocated.
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
if ( exception == EXC_CRASH ) // (e) If the exception type is
result = KERN_FAILURE; // EXC_CRASH, then KERN_FAILURE
else // is returned. MIG will
result = 0; // deallocate the ports again.
}
*__stack_chk_guard_ptr;
return result;
}
Вот что делает этот код:
1. Эта функция является служебной процедурой Mach для сообщений об исключениях `mach_exception_raise`: она вызывается непосредственно системой Mach, когда launchd обрабатывает сообщение об исключении Mach `mach_exception_raise`. Аргументы служебной процедуры извлекаются из сообщения Mach и, следовательно, контролируются отправителем сообщения.
2. В пункте (b) launchd проверяет, что сообщение об исключении Mach было отправлено ядром. Аудит-токен отправителя содержит PID отправляющего процесса в поле 5, которое будет равно нулю только для ядра. Если сообщение было отправлено не ядром, оно отклоняется.
3. Порты потока и задачи из сообщения явно освобождаются в пунктах (c) и (d).
4. В пункте (e) launchd проверяет, является ли тип исключения `EXC_CRASH`, и если да, возвращает `KERN_FAILURE`. Предполагается, что это делается для того, чтобы не обрабатывать сообщения `EXC_CRASH`, вероятно, чтобы ReportCrash вызывался как обработчик тел. Однако возврат `KERN_FAILURE` в этой точке приведет к повторному освобождению портов задачи и потока при последующей очистке сообщения об исключении. Это означает, что эти два порта будут освобождены дважды.
Чтобы эта уязвимость была полезной, мы хотим освободить право отправки launchd'а на сервис Mach, который он предоставляет, чтобы затем выдать себя за этот сервис перед остальной системой. Это означает, что нам нужно, чтобы порты задачи и потока в сообщении об исключении на самом деле были правами отправки на порт сервиса Mach, который мы хотим освободить в launchd. Затем, отправив launchd'у вредоносное сообщение об исключении и освободив порт сервиса, мы попытаемся повторно использовать то же имя порта, но на этот раз для порта Mach, на который у нас есть право получения. Таким образом, когда клиент попросит launchd дать ему право отправки на порт Mach для сервиса, launchd вместо этого даст ему право отправки на наш порт, позволяя нам выдать себя за этот сервис перед клиентом. После этого существует множество различных путей получения привилегий системы.
### Запуск уязвимости