
CVE-2018-4280: Уязвимость замены портов Mach в launchd на macOS 10.13.5, приводящая к локальному повышению привилегий и обходу SIP.
launchd-portrep — это эксплойт для уязвимости замены портов в launchd, начальном процессе пользовательского пространства и демоне управления службами в macOS. Отправляя специально сформированное сообщение Mach на загрузочный порт, можно вынудить launchd освободить своё право отправки для любого порта Mach, к которому атакующий также имеет право отправки. Это позволяет атакующему выдавать себя за любую службу launchd, к которой он может обратиться, перед остальной системой.
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 и слегка отредактированный для удобства чтения:
kern_return_t __fastcall
catch_mach_exception_raise( // (a) Служебная процедура
mach_port_t exception_port, // вызывается со значениями
mach_port_t thread, // непосредственно из сообщения
mach_port_t task, // Mach, отправленного клиентом.
unsigned int exception, // Порты thread и task могут
mach_exception_data_t code, // быть произвольными правами
unsigned int codeCnt) // отправки.
{
kern_return_t kr; // eax@1 MAPDST
kern_return_t result; // eax@10
int pid; // [rsp+14h] [rbp-43Ch]@1
char codes_str[1024]; // [rsp+20h] [rbp-430h]@5
__int64 __stack_guard; // [rsp+420h] [rbp-30h]@1
__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 ( codeCnt )
{
do
{
__snprintf_chk(codes_str, 0x400uLL, 0, 0x400uLL, "0x%llx", *code);
++code;
--codeCnt;
}
while ( codeCnt );
}
launchd_log_2(
0LL,
3LL,
"Исключение уровня хоста: pid = %d, thread = 0x%x, "
"type exception = 0x%x, codes = { %s }",
pid,
thread,
exception,
codes_str);
kr = deallocate_mach_port(thread); // (b) Порт "thread", отправленный
if ( kr ) // в сообщении, освобождается.
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
kr = deallocate_mach_port(task); // (c) Порт "task", отправленный в
if ( kr ) // сообщении, освобождается.
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
result = 0;
if ( *__stack_chk_guard_ptr == __stack_guard )
{
LOBYTE(result) = exception == 10; // (d) Если тип исключения — 10
result *= 5; // (EXC_CRASH), то возвращается
} // ошибка KERN_FAILURE.
return result; // MIG снова освободит порты.
} //
Это двойное освобождение имён портов проблематично, поскольку процесс может установить любые порты в качестве портов задачи и потока в сообщении об исключении. Launchd не проверяет, соответствуют ли полученные права отправки реальному потоку и задаче; порты могут быть, например, правами отправки на порты, уже находящиеся в IPC-пространстве launchd. Тогда двойное освобождение приведёт к тому, что launchd потеряет пользовательскую ссылку на один из своих собственных портов.
Эта ошибка может быть использована для освобождения права отправки launchd на любой порт Mach, к которому атакующий процесс также имеет право отправки. В частности, если атакующий процесс может найти системную службу через launchd, то он может освободить право отправки launchd на эту службу, а затем выдать себя за эту службу перед остальной системой. После этого существует множество способов получения системных привилегий.
Эта ошибка является менее общей версией CVE-2016-7637 — проблемы обработки пользовательских ссылок на порты Mach в XNU, обнаруженной Иэном Биром, которая позволяла процессам освобождать порты Mach в других процессах. Иэн Бир использовал эту уязвимость в macOS, заменяя право отправки launchd на конечную точку com.apple.CoreServices.coreservicesd и выдавая себя за coreservicesd перед остальной системой. Coreservicesd является привлекательной целью, потому что это одна из немногих служб, которым клиенты отправляют свой порт задачи в сообщении Mach. Заменив право отправки launchd на coreservicesd своим собственным портом и затем запуская привилегированных клиентов, чтобы те обращались к coreservicesd и общались с ним, он смог получить порт задачи привилегированного процесса и выполнить код внутри этого процесса.
Поскольку поведение macOS не изменилось, я в основном скопировал стратегию эксплуатации Иэна Бира для данной уязвимости. Мы отправляем сообщения об исключениях в launchd, содержащие порт службы coreservicesd, до тех пор, пока не освободим право отправки launchd на этот порт. Мы можем обнаружить, когда право освобождено, снова вызвав bootstrap_look_up() для этой службы: если launchd возвращает недействительное имя порта, значит, мы успешно освободили право отправки launchd на порт. Затем мы многократно регистрируем и отменяем регистрацию большого количества служб в launchd, пока одна из зарегистрированных служб не получит то же имя порта Mach в IPC-пространстве launchd, что и исходный порт coreservicesd. В этот момент любой процесс, который ищет com.apple.CoreServices.coreservicesd в launchd, получит право отправки на нашу поддельную службу вместо реальной coreservicesd. Затем мы запускаем MITM-сервер на порту поддельной службы, проверяя все порты Mach в сообщениях, полученных от клиентов, прежде чем пересылать их реальной coreservicesd. На этом этапе мы отправляем сообщение sysdiagnose, заставляя его запустить tailspin, что приводит к подключению sysdiagnose к нашему поддельному порту coreservicesd и отправке нам своего порта задачи. Поскольку sysdiagnose имеет право task_for_pid-allow, мы теперь можем получить порт задачи для любого процесса.
Чтобы (в основном) восстановить нормальное функционирование системы, мы используем sysdiagnose для получения порта задачи launchd, а затем используем порт задачи launchd, чтобы заменить право отправки launchd на наш поддельный порт службы обратно на право отправки на реальную coreservicesd. Таким образом, будущие клиенты смогут действительно достичь coreservicesd.