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

В Android последний скопированный элемент сохраняется в основной клип. Любое приложение может сохранять текстовую информацию при помощи показанного ниже кода:
val clipboard: ClipboardManager? =
ContextCompat.getSystemService(this, ClipboardManager::class.java)
val clip = ClipData.newPlainText("", text)
clipboard?.setPrimaryClip(clip)
А вот, как приложение может её считывать:
val clipboard: ClipboardManager? =
ContextCompat.getSystemService(this, ClipboardManager::class.java)
clipboard.primaryClip?.getItemAt(0)?.text.toString()
Это глобальная переменная с методами get и set, ничего особо сложного. При копировании в буфер обмена данные остаются в нём, пока не будут перезаписаны новым значением или устройство не перезагрузится. Есть ли у этого процесса какие-то ограничения?
Долгое время никаких ограничений не было. До Android 12 приложения могли получать доступ к данным буфера обмена, не уведомляя об этом пользователя, и даже читать их в фоновом режиме, что позволяло рекламным SDK собирать информацию об интересах и действиях пользователям в различных приложениях, особенно если он делился ссылками через буфер обмена.
Эта проблема оставалась актуальной не только для Android, но и для iOS. Сначала Apple предложила если не решение, то хотя бы что-то, позволявшее пользователям осознать проблему. В iOS 14 небольшое окно показывает, что конкретное приложение считывает данные буфера обмена.

В Android 12 Google воспользовалась тем же решением и годом позже добавила похожий механизм: внизу экрана отображается небольшое окошко:

Взглянув на код [1], можно увидеть отрисовку простого всплывающего уведомления (которое знакомо всем разработчикам под Android):
Binder.withCleanCallingIdentity(() -> {
try {
CharSequence callingAppLabel = mPm.getApplicationLabel(
mPm.getApplicationInfoAsUser(callingPackage, 0, userId));
String message =
getContext().getString(R.string.pasted_from_clipboard, callingAppLabel);
Slog.i(TAG, message);
Toast toastToShow;
if (SafetyProtectionUtils.shouldShowSafetyProtectionResources(getContext())) {
Drawable safetyProtectionIcon = getContext()
.getDrawable(R.drawable.ic_safety_protection);
toastToShow = Toast.makeCustomToastWithIcon(getContext(),
UiThread.get().getLooper(), message,
Toast.LENGTH_SHORT, safetyProtectionIcon);
} else {
toastToShow = Toast.makeText(
getContext(), UiThread.get().getLooper(), message,
Toast.LENGTH_SHORT);
}
toastToShow.show();
} catch (PackageManager.NameNotFoundException e) {
// do nothing
}
});
Также в коде присутствует список исключений для показа уведомления:
if (clipboard.primaryClip == null) {
return;
}
if (Settings.Secure.getInt(getContext().getContentResolver(),
Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS,
(mShowAccessNotifications ? 1 : 0)) == 0) {
return;
}
// Don't notify if the app accessing the clipboard is the same as the current owner.
if (UserHandle.isSameApp(uid, clipboard.primaryClipUid)) {
return;
}
// Exclude special cases: IME, ContentCapture, Autofill.
if (isDefaultIme(userId, callingPackage)) {
return;
}
if (mContentCaptureInternal != null
&& mContentCaptureInternal.isContentCaptureServiceForUser(uid, userId)) {
return;
}
if (mAutofillInternal != null
&& mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId)) {
return;
}
if (mPm.checkPermission(Manifest.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION,
callingPackage) == PackageManager.PERMISSION_GRANTED) {
return;
}
// Don't notify if already notified for this uid and clip.
if (clipboard.mNotifiedUids.get(uid)) {
return;
}
К разрешению Manifest.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION имеют доступ только системные приложения. То же самое относится к изменению Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS. Однако к системным приложениям в устройствах Android относятся и предустановленные сторонние приложения. То есть эти приложения с лёгкостью могут обойти такую меру защиты.
Эта мера защиты работала в Android и iOS для всех остальных приложений. Сразу после обновления до Android 12 я заметил, что многие мои приложения считывают значение из буфера обмена, но вскоре после этого прекратили это делать.
Значит ли это, что существует какой-то способ обхода данного механизма? На первый взгляд, всё выглядит безопасно: сервис управляет созданием всплывающих уведомлений (toast) и общается с другими приложениями при помощи механизма межпроцессной коммуникации (inter-process communication, IPC) под названием Binder. Отменить или изменить отрисовываемое другим приложением всплывающее уведомление никак нельзя. Но можно ли отрисовать что-нибудь поверх него?
Благодаря системе безопасности Android — это всё-таки невозможно. Все отрисовываемые приложением окна по умолчанию будут находиться под системными окнами. Если только у приложения нет одного разрешения.
Разрешение SYSTEM_ALERT_WINDOW [2] позволяет отрисовывать приложения поверх остальных приложений. Вот несколько примеров:
Разрешение необходимо дать в отдельном разделе экрана App Info.

Если задуматься, объяснение выглядит довольно пугающе.

Позволить этому приложению отображаться поверх других используемых приложений. Приложение будет видеть точки, которых вы касаетесь, и изменять отображаемое на экране
Зловредные программы использовали (а может, и продолжают использовать) это разрешение для отрисовки своих экранов поверх запускаемых банковских приложений и кражи учётных данных, введённых пользователем в поддельные окна.
Перерисовывать небольшое всплывающее уведомление при помощи SYSTEM_ALERT_WINDOW — это как палить из пушки по воробьям. Но многие люди используют всплывающие функции мессенджеров наподобие Facebook* и Telegram, а также видеоприложений наподобие YouTube. Давайте проверим, что происходит, если у приложения есть это разрешение, и оно попробует отрисовать что-то поверх исходного всплывающего сообщения.
При наличии разрешения в целом идея проста: можно использовать LayoutInflater, чтобы выполнить inflate (создание объекта в коде) любого окна. После этого созданное окно передаётся WindowManager (отвечающему за отрисовку окон). Эту задачу выполняет показанный ниже код: он отрисовывает прозрачное окно со структурой, описанной в файле dummy_view.xml.
val windowManager: WindowManager =
applicationContext.getSystemService(WINDOW_SERVICE) as WindowManager
val params = WindowManager.LayoutParams()
params.format = PixelFormat.TRANSLUCENT
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
params.isFitInsetsIgnoringVisibility = true
}
params.flags = (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
val view = LayoutInflater.from(applicationContext).inflate(R.layout.dummy_view, null)
windowManager.addView(view, params)
Давайте отрисуем на всплывающем уведомлении небольшой белый прямоугольник.

Несмотря на появление прозрачного белого прямоугольника, сообщение под ним остаётся видимым. Прямоугольник почти прозрачный из-за того, что параметр type WindowManager должен быть как минимум TYPE_APPLICATION_OVERLAY [4]. Это минимальный тип для отрисовки поверх; остальные относятся к системным приложениям и приложениям звонков. В документации говорится следующее: «Система может в любой момент менять позицию, размер или видимость этих окон, чтобы снизить визуальную хаотичность и регулировать использование ресурсов».
Android снова обхитрил нас, сделав окно полупрозрачным. Однако давайте ещё раз подумаем над этой проблемой: у нас есть полупрозрачное окно. Как сделать его менее прозрачным? Очевидное решение — отрисовать поверх него несколько дополнительных окон. Давайте добавим поверх этого окна ещё два белых прямоугольника:

Сработало! Благодаря трём слоям, исходное всплывающее уведомление едва заметно, но при необходимости можно добавить ещё окон. При помощи небольших изменений они могут выглядеть как настоящее всплывающее уведомление. Благодаря Android и его опенсорсному коду [5] это поведение можно быстро воспроизвести:

«Это приложение абсолютно точно не вставляет ничего из буфера обмена»
Поэтому можно перерисовать всплывающее уведомление или другим уведомлением, или любым другим окном. Полное сокрытие исходного уведомления позволяет не сообщать пользователю о действиях с буфером обмена. Любое приложение с разрешением SYSTEM_ALERT_WINDOW может считывать данные из буфера обмена, не уведомляя об этом пользователя. Мы смогли убедиться в этом в самой свежей версии Android 14.
Стоит заметить, что некоторые поставщики уже предприняли меры для решения этой проблемы и не позволяют перерисовывать системные всплывающие уведомления. Например, конкретно этот способ не работает на новых устройствах Samsung с последними версиями One UI. Вы можете проверить, уязвимо ли ваше устройство, запустив представленное ниже демо.
Мы подготовили простое приложение, позволяющее увидеть эту методику в действии. Можно использовать его для проверки того, уязвимо ли ваше устройство к этой атаке. Демо предназначено для устройств с Android 12 и выше. Его исходный код [6] открыт и доступен для изучения.

При работе с информационной безопасностью (как и с любой другой безопасностью) критически важно найти подходящий баланс между безопасностью и удобством применения. Слишком строгие меры могут отпугнуть пользователей от продукта, и тот же результат может ждать при отсутствии мер защиты. Это касается и буфера обмена. С точки зрения конфиденциальности и безопасности улучшенный механизм в Android должен, как минимум, походить на механизм в браузерах: запускаемый в браузерах код на JavaScript (не их расширения) может иметь доступ к данным буфера обмена только в случае действия, выполняемого пользователем.
Google реализовал в Android простую меру защиты, удерживающую пользователей от перехода на iOS. Он ограничил доступ к считыванию значений из буфера обмена без уведомлений для всех приложений, но оставил его для себя. Доступ компании к буферу обмена позволяет ей иметь ценный источник информации для таргетированной рекламы и сохранения позиции на рынке.
Автор:
ru_vds
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/android/387751
Ссылки в тексте:
[1] код: https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/clipboard/ClipboardService.java;l=103?q=ClipboardService&ss=android%2Fplatform%2Fsuperproject%2Fmain
[2] SYSTEM_ALERT_WINDOW: https://developer.android.com/reference/android/Manifest.permission#SYSTEM_ALERT_WINDOW
[3] Значки чатов: https://www.facebook.com/help/messenger-app/1611232179138526?cms_platform=android-app&helpref=platform_switcher
[4] TYPE_APPLICATION_OVERLAY: https://developer.android.com/reference/android/view/WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY
[5] опенсорсному коду: https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/widget/ToastPresenter.java?q=ToastPresenter&ss=android%2Fplatform%2Fsuperproject%2Fmain
[6] исходный код: https://github.com/fingerprintjs/blog-android-clipboard-security-demo
[7] Источник: https://habr.com/ru/companies/ruvds/articles/769320/?utm_source=habrahabr&utm_medium=rss&utm_campaign=769320
Нажмите здесь для печати.