Connect with us

Разработка

Как одна строчка кода может сломать ваш iPhone

Уведомления Darwin еще проще, поскольку они являются частью слоя CoreOS. Они представляют собой низкоуровневый механизм для простого обмена сообщениями между процессами в операционных системах Apple.

Опубликовано

/

     
     

Это история о том, как я нашел одну из моих любимых уязвимостей в iOS. Она стала одной из моих любимых из-за того, насколько просто было реализовать эксплойт для нее. Кроме того, она использует устаревший публичный API, который до сих пор используется во многих компонентах операционных систем Apple, и о котором многие разработчики никогда не слышали.

Уведомления Darwin

Большинство разработчиков iOS, скорее всего, привыкли к NSNotificationCenter, а большинство разработчиков Mac — к NSDistributedNotificationCenter. Первый работает только в рамках одного процесса, второй позволяет обмениваться простыми уведомлениями между процессами с возможностью включения строки с дополнительными данными для передачи вместе с уведомлением.

Уведомления Darwin еще проще, поскольку они являются частью слоя CoreOS. Они представляют собой низкоуровневый механизм для простого обмена сообщениями между процессами в операционных системах Apple. Вместо объектов или строк каждое уведомление может иметь связанное с ним состояние, которое представляет собой UInt64 и обычно используется только для обозначения булевой истинности или ложности, указывая 0 или 1.

Простым вариантом использования API может быть процесс, который просто хочет уведомить другие процессы о каком-то событии, в этом случае он может вызвать функцию notify_post, которая принимает строку, обычно являющуюся реверсом DNS, например com.apple.springboard.toggleLockScreen.

Процессы, заинтересованные в получении такого уведомления, могут зарегистрироваться с помощью функции notify_register_dispatch, которая будет вызывать блок в заданной очереди каждый раз, когда другой процесс разместит уведомление с указанным именем.

Процесс, заинтересованный в отправке дарвиновского уведомления с состоянием, должен сначала зарегистрировать для него хэндл, что можно сделать, вызвав функцию notify_register_check, которая принимает имя уведомления и указатель на Int32, где функция возвращает маркер, который можно использовать для вызова notify_set_state, которая также принимает значение UInt64 для состояния.

Через тот же механизм notify_register_check процесс, который хочет получить состояние уведомления, может вызвать notify_get_state, чтобы получить текущее состояние. Это позволяет использовать дарвиновские уведомления для определенных типов событий, но при этом хранить некоторое состояние, которое любой процесс в системе может запросить в любой момент времени.

Уязвимость

Любой процесс в операционных системах Apple, включая iOS, может зарегистрироваться для получения уведомлений Darwin, находясь в своей песочнице, без необходимости получения специальных прав. Это имеет смысл, учитывая, что некоторые системные фреймворки, используемые сторонними приложениями, полагаются на уведомления Darwin для обеспечения важной функциональности.

Учитывая, что объем передаваемых через них данных очень ограничен, уведомления Darwin не представляют значительного риска утечки конфиденциальных данных, даже несмотря на то, что API является общедоступным, а приложения, находящиеся в «песочнице», могут регистрироваться для получения уведомлений.

Однако, как и любой процесс в системе может зарегистрироваться для получения уведомлений Darwin, то же самое справедливо и для их отправки.

Подводя итог, можно сказать, что уведомления Darwin:

  • Не требуют особых привилегий для получения
  • Не требуют специальных привилегий для отправки
  • Доступны как общедоступный API
  • Не имеют механизма проверки отправителя.

Учитывая эти свойства, мне стало интересно, есть ли на iOS места, использующие дарвиновские уведомления для мощных операций, которые потенциально могут быть использованы в качестве атаки типа «отказ в обслуживании» из приложения, находящегося в песочнице.

Вы читаете эту статью, так что уже в целом понятно — ответ «да».

Доказательство концепции: EvilNotify

Задавшись этим вопросом, я взял свежую копию root файловой системы iOS — кажется, одну из ранних бета-версий iOS 18 — и начал искать процессы, использующие notify_register_dispatch и notify_check.

Я быстро нашел кучу таких процессов и создал тестовое приложение под названием EvilNotify, которое можно было использовать для тестирования.

Как одна строчка кода может сломать ваш iPhone

К сожалению, у меня больше нет уязвимого устройства, которое я мог бы использовать для записи видео на устройстве, но демонстрация в Симуляторе выше показывает большую часть того, на что способно такое доказательство концепции. Некоторые из функций не работают в симуляторе, поэтому я не смог продемонстрировать их в видео.

В конце видео вы можете увидеть намек на то, каким был конечный отказ в обслуживании, но позвольте мне упомянуть все остальные вещи, которые можно было делать. Имейте в виду, что все они повлияют на всю систему, даже если пользователь принудительно выйдет из приложения.

  • Вызвать появление значка «обнаружение жидкости» в строке состояния
  • Вызвать отображение статуса подключения Display Port на динамическом острове
  • Блокировать общесистемные жесты для вытягивания Центра управления, Центра уведомлений и экрана блокировки
  • Заставить систему игнорировать Wi-Fi и использовать вместо него сотовое соединение
  • Блокировать экран
  • Запустить  UI «Идет передача данных», который не позволял использовать устройство до тех пор, пока пользователь не отменит это
  • Имитировать вход и выход устройства из «режима потери» Find My, вызывающие диалоговое окно ввода пароля Apple ID для повторного включения Apple Pay
  • Запустить устройство в режиме «идет восстановление» (restore in progress)

«Идет восстановление»

Поскольку я искал атаку типа «отказ в обслуживании», последний вариант показался мне наиболее перспективным, поскольку из него не было иного выхода, кроме нажатия кнопки «Перезагрузка», что всегда приводило к перезагрузке устройства.

Кроме того, он был довольно изящным, поскольку состоял из одной строки кода:

notify_post(«com.apple.MobileSync.BackupAgent.RestoreStarted»)

Вот и все! Этой единственной строки кода было достаточно, чтобы на устройстве появилось сообщение «Restore in Progress». Операция неизбежно завершалась неудачей после тайм-аута, поскольку устройство фактически не восстанавливалось, и единственным средством решения этой проблемы было нажатие кнопки «Restart», что приводило к перезагрузке устройства.

Если заглянуть в двоичные файлы, то SpringBoard наблюдал за этим уведомлением для запуска UI. Уведомление срабатывает, когда устройство восстанавливается из локальной резервной копии через подключенный компьютер, но, как было написано ранее, любой процесс может отправить уведомление и обманом заставить систему перейти в этот режим.

Отказ в обслуживании: VeryEvilNotify

Теперь, когда у меня было дарвиновское уведомление, способное стать отказом в обслуживании, мне оставалось только придумать, как запустить его многократно при перезагрузке устройства.

Поначалу это казалось довольно сложным, поскольку приложения на iOS имеют очень ограниченные возможности для фоновой работы, и довольно много API с побочными эффектами не могут работать, когда приложение не находится на переднем плане. Последнее, как я выяснил, не является проблемой, так как я мог убедиться, что notify_post работает, даже когда приложение не находится на переднем плане.

Что касается возможности отправлять уведомление снова и снова при многократной перезагрузке устройства, я не был так уверен, но у меня было предположение, что расширение приложения с наибольшей вероятностью справится с этой задачей.

Некоторые типы расширений сторонних приложений могут запускаться до первой разблокировки на устройствах iOS, поэтому я решил попробовать тип расширения, с которым я хорошо знаком, и создал расширение виджета в новом приложении, которое назвал «VeryEvilNotify».

Расширения-виджеты периодически пробуждаются iOS в фоновом режиме. У них есть ограниченное количество времени для создания снепшотов и таймлайнов, которые система затем отображает в различных местах, включая экран блокировки, главный экран, центр уведомлений и центр управления.

Поскольку виджеты широко распространены в системе, при установке и запуске нового приложения, содержащего расширение виджета, система с большим желанием выполняет расширение виджета. Это позволяет подготовить виджеты приложения к тому, чтобы пользователь мог выбрать и добавить их в различные поддерживаемые места.

Расширение виджета — это, в конечном счете, просто процесс, который может выполнять код, поэтому я добавил вышеупомянутую строку кода в свое расширение виджета. Я настроил расширение так, чтобы оно включало все возможные типы виджетов, просто для того, чтобы iOS выполняла его как можно быстрее.

Но тут возникла проблема: расширения виджетов создают плейсхолдеры, снепшоты и втаймлайны, которые затем кэшируются системой для сохранения ресурсов. Эти расширения не работают в фоновом режиме постоянно, и даже если расширение запрашивает очень частые обновления, система будет соблюдать бюджет времени и задерживать обновления, если расширение пытается запрашивать их слишком часто.

Чтобы обойти эту проблему, я решил сделать так, чтобы мое расширение виджета всегда зависало вскоре после запуска функции 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()
        }
    }
}

/// Override extension entry point to ensure the exploit code is always run whenever
/// our extension gets woken up by the system.
@main
struct VeryEvilWidgetEntryPoint {
    static func main() {
        notify_post("com.apple.MobileSync.BackupAgent.RestoreStarted")

        VeryEvilWidgetBundle.main()
    }
}

С этим расширением виджетов, как только я устанавливал приложение VeryEvilNotify на свое устройство для исследования безопасности, появлялся пользовательский интерфейс «Восстановление в процессе», а затем он падал с предложением перезагрузить систему.

После перезагрузки, как только SpringBoard инициализировался, расширение будилось системой, так как она не могла создать никаких сущностей виджетов, что заново приводило к началу процесса.

В результате устройство становилось полностью неработоспособным, что требовало стирания и восстановления из резервной копии. Я подозреваю, что если приложение попадет в резервную копию и устройство будет восстановлено из нее, то ошибка в конечном итоге сработает снова, что сделает ее еще более эффективной в качестве отказа в обслуживании.

Моя теория заключалась в том, что iOS имеет какой-то механизм повторных попыток при сбое расширения виджета, который, очевидно, имеет какой-то механизм дросселирования. Я все еще думаю, что это так, но что-то во времени падения расширения и запуска восстановления, а затем сбоя, вероятно, не позволило такому механизму работать.

Удовлетворенный своим доказательством концепции, я сообщил о проблеме в Apple.

Хронология

Ниже приведена краткая хронология событий для этого отчета об уязвимости. Были дополнительные обновления статуса через автоматические сообщения от системы отчетов о безопасности Apple, которые я не включил для краткости.

  • 26 июня 2024 года: первоначальный отчет отправлен в Apple
  • 27 сентября 2024 г.: получено сообщение от Apple, информирующее меня о том, что устранение проблемы находится в процессе
  • 28 января 2025 года: проблема отмечена как решенная, право на получение вознаграждения подтверждено
  • 11 марта 2025 года: ошибке присвоен номер CVE-2025-24091, она устранена в iOS/iPadOS 18.3
  • Сумма вознаграждения за ошибку: $17 500

Несмотря на то, что CVE уже присвоен, и Apple предоставила ссылку, по которой должно быть опубликовано уведомление и авторство, этого пока не произошло. Мне сообщили, что новость будет опубликована в ближайшее время, но вы можете прочитать адвизори ниже, если он еще не будет опубликована к моменту выхода этого сообщения.

Как одна строчка кода может сломать ваш iPhone

Обратите внимание на то, что в рекомендации упоминается, что «для чувствительных уведомлений теперь требуются ограниченные права доступа», что намекает на то, в чем заключалось смягчение последствий. Подробнее об этом вы можете прочитать в следующем разделе.

Смягчение последствий

Как указано 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 выяснилось, что право на получение прав впервые появилось в iOS 18.2 build 22C5125e, она же iOS 18.2 beta 2.

Первыми на это пошли backupd, BackupAgent2 и UserEventAgent. Все они получили права, связанные с уведомлением системы о восстановлении устройства, что позволило смягчить наиболее вопиющий эксплойт, представленный в моем доказательстве концепции.

На протяжении различных бета-версий и релизов iOS 18 все больше и больше процессов стали использовать новые права на ограниченные уведомления, и с выходом iOS 18.3 все проблемы, продемонстрированные в моем PoC, были решены.

Источник

Если вы нашли опечатку - выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.
Telegram

Популярное

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: