
CVE-2018-4241: переполнение кучи ядра XNU из-за плохой проверки границ в MPTCP для iOS 11 - 11.3.1 выпущено Яном Биром.
@i41nbeer
mptcp_usr_connectx является обработчиком системного вызова connectx для семейства сокетов AP_MULTIPATH.
Логика этой функции неверно обрабатывает sockaddr источника и назначения, которые не являются AF_INET или AF_INET6:
// verify sa_len for AF_INET:
if (dst->sa_family == AF_INET &&
dst->sa_len != sizeof(mpte->__mpte_dst_v4)) {
mptcplog((LOG_ERR, "%s IPv4 dst len %u\n", __func__, dst->sa_len), MPTCP_SOCKET_DBG, MPTCP_LOGLVL_ERR);
error = EINVAL;
goto out;
}
// verify sa_len for AF_INET6:
if (dst->sa_family == AF_INET6 &&
dst->sa_len != sizeof(mpte->__mpte_dst_v6)) {
mptcplog((LOG_ERR, "%s IPv6 dst len %u\n", __func__, dst->sa_len), MPTCP_SOCKET_DBG, MPTCP_LOGLVL_ERR);
error = EINVAL;
goto out;
}
// code doesn't bail if sa_family was neither AF_INET nor AF_INET6
if (!(mpte->mpte_flags & MPTE_SVCTYPE_CHECKED)) {
if (mptcp_entitlement_check(mp_so) < 0) {
error = EPERM;
goto out;
}
mpte->mpte_flags |= MPTE_SVCTYPE_CHECKED;
}
// memcpy with sa_len up to 255:
if ((mp_so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING)) == 0) {
memcpy(&mpte->mpte_dst, dst, dst->sa_len);
}
Изучая структуру, которую вы переполняете, вы замечаете, что можете затронуть оба поля здесь:
if (mpte->mpte_itfinfo_size > MPTE_ITFINFO_SIZE) _FREE(mpte->mpte_itfinfo, M_TEMP);
mpte_itfinfo_size находится непосредственно перед mpte_itfinfo.
При инициализации структуры указатель mpte_itfinfo указывает на небольшой встроенный массив. Если добавляется больше подпотоков, чем туда помещается, они вместо этого помещаются в буфер кучи, и mpte_itfinfo будет указывать на него.
Если бы у вас была другая ошибка (например, ошибка раскрытия кучи ядра из async_wake), вы могли бы перезаписать поле mpte_itfinfo любым допустимым объектом зоны, и оно было бы освобождено (на самом деле, вы также могли бы перезаписать его смещением внутри этого объекта для еще большего веселья!)
Однако у нас этого нет.
Вместо этого другой подход — частично перезаписать указатель. Если мы частично перезапишем его нулевыми байтами, мы можем указать его на значение, выровненное по границе 256 байт, 65 Кбайт, 16 Мбайт или 4 Гбайт.
В этом эксплойте я выбираю перезапись 3 байт нулями, что приведет к kfree адреса mpte_itfinfo, округленного вниз до следующей границы 16 Мбайт.
Поток эксплуатации выглядит следующим образом:
Поочередно выделяйте 16 Мбайт ipc_kmsgs, затем кучу сокетов mptcp. Цель здесь — получить выделение kalloc.2048 на этой границе 16 Мбайт.
Используйте ошибку, чтобы освободить одно из ipc_kmsgs, переместив эту страницу в промежуточный список и поместив выровненное по 16 Мбайт выделение в список свободных страниц kalloc.2048.
Выделите кучу заполненных каналов по 2047 байт; резервные буферы для этих каналов будут взяты из kalloc.2048, надеюсь, включая наш выровненный по 16 Мбайт адрес.
Запустите ошибку второй раз, освободив тот же адрес, а затем выделите кучу предварительно выделенных буферов ipc_kmsg из kalloc.2048.
Теперь у нас, надеюсь, есть ipc_kmsg (в который мы можем отправлять сообщения и затем получать) и буфер канала (который мы можем читать и записывать), перекрывающиеся друг с другом.
Я использую трюк с портом исключения потока из extra_recipe, чтобы отправлять сообщения в предварительно выделенный буфер ipc_kmsg. Каждый раз мы проверяем каждый из каналов, чтобы увидеть, содержит ли какой-нибудь из них сообщение. Когда мы находим правильную пару (ipc_kmsg, канал), мы можем переписать сообщение, чтобы отправить себе фиктивный порт, который находится внутри буфера канала. Я структурирую этот фиктивный порт как в async_wake (который я основал на yalu 10.2 от @qwertyoruiopz и @marcograss), чтобы получить примитив раннего чтения ядра.
Используя примитив чтения ядра, я нахожу задачу ядра и создаю фиктивный порт, который позволяет более легко читать/записывать память ядра через mach_vm_read/mach_vm_write.
Предостережение: Для подключения сокетов mptcp требуется право com.apple.developer.networking.multipath, которое требует сертификата разработчика Apple, который может купить любой желающий.
Надёжность: Это инструмент для исследований в области безопасности и очень далёк от совершенства. Однако он должен работать большую часть времени, и когда он работает, он должен хорошо очищать ресурсы, чтобы не произошло паники позже.
Чтобы повысить вероятность его работы:
Поддерживаемые устройства: Он должен работать на iOS 11.0 - 11.3.1 включительно. Я тестировал на: iPod Touch 6g, iPhone 6s, iPhone SE, iPhone 7, iPhone 8
API: #include "sploit.h" и вызовите go() для запуска эксплойта. Если он сработал, вы можете использовать функции из kmem.h для чтения и записи памяти ядра
Примечания: Несколько человек публично сравнили эту ошибку с патчем (или их 0day был исправлен ;) читайте их материалы для более подробной информации: @elvanderb сделал короткий доклад об этой ошибке на rump.beer в Париже 31 мая: https://www.rump.beer/2018/slides/ios_48h.pdf @jaakerblom опубликовал рабочий эксплойт на github 1 июня: https://github.com/potmdehex/multipath_kfree Техника Джона похожа на мою, но он делает переполнение на два байта, а не на три, и заменяет другими объектами. хорошая работа!