- PVSM.RU - https://www.pvsm.ru -

Большинство разработчиков под iOS наверняка привыкли к NSNotificationCenter, а разработчики под Mac — к NSDistributedNotificationCenter. Первый механизм работает внутри одного процесса, а второй позволяет процессам обмениваться простыми уведомлениями с возможностью включать в них строку дополнительных данных.
Darwin Notification API ещё проще, так как является частью слоя CoreOS. Этот API предоставляет механизм для простого обмена сообщениями между процессами в ОС Apple. Вместо объектов или строк с каждым уведомлением может быть связано state, имеющее тип UInt64 и обычно используемое только для указания логического значения true либо false (1 или 0).
В качестве простого примера использования этого API можно рассмотреть процесс, желающий уведомить другие процессы о некотором событии. Для этого он может вызвать функцию notify_post, которая получает строку, обычно представляющую инвертированное значение DNS вроде com.apple.springboard.toggleLockScreen.
Процессы, заинтересованные в получении такого уведомления, могут зарегистрироваться с помощью функции notify_register_dispatch, которая будет отправлять в заданную очередь диспетчеризации блок, как только другой процесс опубликует уведомление с указанным именем.
Процесс, который хочет разместить уведомление Darwin с состоянием, должен сначала зарегистрировать соответствующего обработчика. Для этого ему нужно вызвать функцию notify_register_check. Эта функция получает имя уведомления и указатель на Int32, куда ей нужно вернуть токен, необходимый для вызова notify_set_state, которая тоже получает в качестве состояния значение UInt64.
Через тот же механизм notify_register_check процесс, который хочет получить состояние уведомления, вызывает notify_get_state. Это позволяет использовать уведомления Darwin для определённых видов событий, но также хранить некое состояние, которое любой процесс в системе сможет в любой момент запросить.
Любой процесс в ОС Apple, включая iOS, может без дополнительных требований прямо из своей собственной среды зарегистрироваться для получения любых уведомлений Darwin. И это имеет смысл с учётом того, что некоторые системные фреймворки, используемые сторонними приложениями, опираются на уведомления Darwin для реализации важной функциональности.
Поскольку через уведомления Darwin передаётся очень ограниченный объём данных, они не представляют значительного риска утечки чувствительных данных, несмотря на использование публичного API и возможность их получения автономными приложениями. Однако, поскольку любой процесс в системе может зарегистрироваться для получения уведомлений Darwin, то же касается и их отправки.
Итак, уведомления Darwin:
Учитывая все эти свойства, мне стало интересно, есть ли в iOS места, где такие уведомления используются для важных операций, и которые потенциально можно эксплуатировать для реализации DoS-атаки из автономного приложения.
Ну и поскольку вы читаете эту статью, то ответ, очевидно «да».
Озадачившись этим вопросом, я взял свежую копию корневой файловой системы iOS — одну из первых бета версий iOS 18 — и начал искать процессы, которые используют notify_register_dispatch и notify_check.
Я быстро нашёл множество таких процессов и создал тестовое приложение, которое назвал «EvilNotify».

Ролик можно посмотреть в оригинале статьи [1]
К сожалению, у меня больше нет уязвимого устройства, которое бы можно было использовать для записи полноценного видео, но в демо iOS Simulator выше я продемонстрировал всё основное, что мне удалось реализовать. Некоторые процессы в симуляторе не работают, поэтому на видео их нет.
В конце ролика вы можете заметить, какой в итоге получилась DoS-атака, но я хочу также перечислить всё остальное, чего мне удалось добиться. Имейте в виду, что любое из этих действий могло повлиять на всю систему, даже если бы пользователь закрыл приложение.
Поскольку я искал способ совершить DoS-атаку, последний пункт выглядел наиболее перспективным, так как выйти из этого режима можно только через нажатие кнопки «Перезапуск», что всегда приведёт к перезагрузке устройства.
Да и в целом это был весьма лаконичный вариант, включавший всего одну строку кода:
notify_post("com.apple.MobileSync.BackupAgent.RestoreStarted")
Вот оно! Этой строки будет достаточно, чтобы устройство вошло в режим «восстановления». По истечении периода ожидания эта операция будет неизбежно проваливаться, так как по факту устройство не восстанавливается, и исправить это можно лишь путём перезагрузки.
Судя по информации SpringBoard, это уведомление активирует UI. Оно создаётся при восстановлении устройства из локальной резервной копии или через подключённый компьютер, но, как я выяснил ранее, отправить его мог любой процесс, заставив систему войти в режим восстановления.
Теперь, когда я нашёл уведомление Darwin, которое потенциально можно было превратить в DoS-атаку, нужно было лишь придумать способ активировать его циклически после перезагрузки устройства.
Поначалу эта задача казалась мне довольно сложной, так как приложения iOS очень ограничены в возможностях совершать действия из фонового режима, и довольно много API с побочными эффектами не работают, когда приложение неактивно. Второй момент меня не беспокоил, так как я смог подтвердить работоспособность notify_post, даже когда приложение находилось в фоновом режиме.
Что же касается возможности повторно отправлять уведомления после перезагрузки устройства, то здесь я не был уверен, но догадывался, что самым вероятным кандидатом на успех будет расширение приложения.
Некоторые типы сторонних расширений могут запускаться ещё до первой разблокировки устройства iOS, так что я решил попробовать знакомый мне вид расширения и создал виджет в новом приложении, которое назвал «VeryEvilNotify».
Виджеты периодически активируются ОС в фоновом режиме. У них есть ограниченный промежуток времени для генерации снимков системы и таймлайна, которые затем ОС выводит в различных местах, включая экран блокировки, домашний экран, центр уведомлений и центр управления.
Поскольку виджеты используются в системе повсеместно, то при установке и запуске нового приложения, включающего такое расширение, система без раздумий его выполняет. Это делает такие виджеты готовыми к добавлению пользователем в различные подходящие места.
В своей сути виджет — это просто процесс, который может выполнять код, поэтому я добавил вышеупомянутую строку кода в своё расширение. Это расширение я настроил так, чтобы оно включало всевозможные типы виджетов, с целью максимально подтолкнуть систему выполнить его как можно раньше.
Но возникла проблема: расширения виджетов создают плейсхолдеры, снимки и таймлайн, которые затем кэшируются системой для экономии ресурсов. Они не работают фоном постоянно, и даже если такое расширение запрашивает очень частое обновление, система установит временные рамки и будет просто это обновление задерживать.
Для обхода этого я решил сделать так, чтобы мой виджет всегда падал вскоре после выполнения функции notify_post. Для этого я вызывал функцию Swift fatalError() в каждом из методов TimelineProvider.
Вызов notify_post совершался внутри точки входа расширения перед делегированием дальнейшего выполнения его окружению:
import WidgetKit
import SwiftUI
import notify
struct VeryEvilWidgetBundle: WidgetBundle {
var body: some Widget {
VeryEvilWidget()
if #available(iOS 18, *) {
VeryEvilWidgetControl()
}
}
}
/// Переопределение точки входа расширения, чтобы код эксплойта запускался при каждом его пробуждении системой.
@main
struct VeryEvilWidgetEntryPoint {
static func main() {
notify_post("com.apple.MobileSync.BackupAgent.RestoreStarted")
VeryEvilWidgetBundle.main()
}
}
Когда виджет был готов, как только я установил VeryEvilNotify на свой экспериментальный девайс, отобразился режим «Восстановление», после провала которого последовало диалоговое окно с предложением перезапустить систему.
После перезагрузки система активировала расширение следом за инициализацией SpringBoard, поскольку до этого момента оно не могло создавать какие-либо записи виджета, и это всякий раз приводило к повторению всего процесса.
Результатом стало программное окирпичивание устройства, требующее очистки системы и восстановления из резервной копии. Думаю, если бы приложение оказалось в бэкапе, и устройство восстановили из него, баг продолжил бы срабатывать, став ещё более эффективной DoS-атакой.

Посмотреть ролик можно в оригинале статьи [1]
Я ожидал, что в iOS на случай падения виджета сработает некий механизм повтора попытки, что определённо привело бы к срабатыванию ограничения частоты вызовов. И, мне кажется, так и есть, просто тот тайминг, с которым происходило падение расширения и начиналось восстановление системы с последующим провалом наверняка не дал этому ограничению возможности сработать.
Довольный тем, что мой эксперимент удался, я сообщил о проблеме в Apple.
Ниже я привёл общую хронологию формирования этого отчёта об уязвимости. Были ещё дополнительные обновления статуса посредством автоматических сообщений системы Apple, которые я не стал включать.
Несмотря на то, что уязвимости уже был присвоен номер CVE, и в Apple даже создали ссылку, где должно появиться её описание с упоминанием причастных к раскрытию, этого ещё не произошло. Мне сообщили, что вскоре эти сведения опубликуют, но я на всякий случай приведу информацию об уязвимости ниже.
Обратите внимание на фразу «чувствительные уведомления теперь требуют использования ограниченных допусков», намекающую на то, как именно проблема была исправлена. Подробнее о самом исправлении я расскажу ниже.
Как сказано в справке, для отправки чувствительных уведомлений Darwin процесс теперь должен обладать ограниченным допуском. Причём имеется в виду не просто допуск, позволяющий отправлять любые чувствительные уведомления, а допуск в виде префикса com.apple.private.darwin-notification.restrict-post.<notification>.
Насколько я понял, бегло изучив дизассемблированный код, «ограниченным» уведомление делает приставка com.apple.private.restrict-post. в его имени.
Например, уведомление com.apple.MobileBackup.BackupAgent.RestoreStarted теперь отправляется по адресу com.apple.private.restrict-post.MobileBackup.BackupAgent.RestoreStarted, после чего notifyd сначала проверяет, чтобы отправляющий процесс обладал допуском com.apple.private.darwin-notification.restrict-post.MobileBackup.BackupAgent.RestoreStarted, и только потом позволяет это уведомление отправить.
Процессы, которые наблюдают опубликованное уведомление, также будут использовать новое имя с префиксом com.apple.private.restrict-post, чтобы никакое случайное приложение или процесс без допуска не могли разместить уведомление, способное оказать серьёзное побочное воздействие на систему.
У меня не было возможности покопаться в старых релизах iOS, чтобы найти конкретную версию, где эта защита была введена впервые. Но с помощью ipsw-diffs [3] мне удалось выяснить, что описанный механизм допуска появился в iOS 18.2 build 22C5125e [4], то есть iOS 18.2 beta 2.
Первыми его начали использовать процессы backupd, BackupAgent2 и UserEventAgent, получая допуски, связанные с уведомлением системы о восстановлении устройства. Таким образом была закрыта самая грубая уязвимость из продемонстрированных в моём эксперименте.
При последующем выходе различных бета-версий iOS 18 и прочих релизов новый допуск для уведомлений стали получать и другие процессы. А с выходом iOS 18.3 были решены и все другие указанные мной проблемы.
Автор: Bright_Translate
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/ios/419272
Ссылки в тексте:
[1] оригинале статьи: https://rambo.codes/posts/2025-04-24-how-a-single-line-of-code-could-brick-your-iphone
[2] обновлением iOS/iPadOS 18.3: https://support.apple.com/122066
[3] ipsw-diffs: https://github.com/blacktop/ipsw-diffs
[4] iOS 18.2 build 22C5125e: https://github.com/blacktop/ipsw-diffs/blob/fee5b3c8c18e4639e74677dd3cc1fa80203e64f6/18_2_22C5109p__vs_18_2_22C5125e/Entitlements.md?plain=1#L2336
[5] Источник: https://habr.com/ru/companies/ruvds/articles/907502/?utm_source=habrahabr&utm_medium=rss&utm_campaign=907502
Нажмите здесь для печати.