Наши продукты

Все выпуски в PDF →
  • Годовая подписка на Xakep.ru

    Все статьи, PDF-архив, скидки и другие бонусы

    6000₽ в год

    Подписаться
  • Ежеквартальный «Хакер» #4

    Печатный выпуск за август–ноябрь 2026 года

    2000₽

    Заказать
  • Ежеквартальный «Хакер» #3

    Печатный выпуск за май–июль 2026 года

    2000₽

    Заказать
  • Спецвыпуск «Хакера» #4

    Избранные материалы 2021–2022 годов на бумаге

    2000₽

    Заказать
Экс­трен­ный вызов — единс­твен­ная фун­кция смар­тфо­на, которая дол­жна работать всег­да: без SIM-кар­ты, без денег на сче­те, с заб­локиро­ван­ного экра­на. Сегод­ня раз­берем уяз­вимость, поз­воля­ющую при­ложе­нию без еди­ного раз­решения бло­киро­вать звон­ки в служ­бу спа­сения.

Мы прой­дем весь путь: от устрой­ства экс­трен­ных служб в ОС и внут­реннос­тей AppOpsService до трех бай­пасов и обхо­да политик SELinux.

warning

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

Android работа­ет боль­ше чем на трех мил­лиар­дах устрой­ств — это поряд­ка 70% всех смар­тфо­нов на пла­нете. И на каж­дом из них есть ров­но одна фун­кция, которая обя­зана отра­ботать при любом рас­кла­де, — вызов экс­трен­ных служб.

Google это прек­расно понима­ет: в мат­рице оцен­ки серь­езности уяз­вимос­тей Android есть отдель­ная стро­ка Targeted prevention of access to emergency services, целенап­равлен­ное пре­дот­вра­щение дос­тупа к экс­трен­ным служ­бам, и она сра­зу отно­сит подоб­ное явле­ние к уров­ню High. При этом таких слу­чаев за всю исто­рию Android набира­ется мень­ше десят­ка.

Од­ну из подоб­ных уяз­вимос­тей мне уда­лось най­ти. Она получи­ла иден­тифика­тор CVE-2025-22431, катего­рию High и зат­ронула сра­зу три мажор­ные вер­сии Android. Ата­кующее при­ложе­ние при этом не зап­рашива­ет ни одно­го раз­решения. В этой статье мы раз­берем, почему при звон­ке на 911 мик­рофон вклю­чает­ся сам, при чем тут AppOpsService и какие бай­пасы приш­лось сде­лать, что­бы прев­ратить все это в работа­ющий экс­пло­ит.

 

Что такое экстренные службы в Android

Для начала крат­кий обзор — ина­че будет непонят­но, что имен­но мы лома­ем.

Emergency dialer — это сис­темная зво­нил­ка, дос­тупная в том чис­ле с заб­локиро­ван­ного экра­на. Про­изволь­ный номер отту­да не наберешь — толь­ко экс­трен­ные и те кон­такты, которые поль­зователь сам занес в соот­ветс­тву­ющую катего­рию. Доз­вон при этом работа­ет через любую дос­тупную сеть и без SIM-кар­ты: это­го тре­бует стан­дарт 3GPP TS 22.101. А какой номер счи­тать экс­трен­ным, сис­тема берет из базы, которая лежит пря­мо в про­шив­ке, в фай­ле packages/services/Telephony/ecc/input/eccdata.txt.

Вто­рой инте­ресу­ющий нас ком­понент — при­ложе­ние Personal Safety, пре­дус­танов­ленное на Pixel. Глав­ный вход в него — Emergency SOS: пятик­ратное нажатие кноп­ки питания запус­кает цепоч­ку дей­ствий, которую ты нас­тра­иваешь сам.

Emergency SOS заранее показывает, что именно телефон сделает в экстренной ситуации
Emergency SOS заранее показы­вает, что имен­но телефон сде­лает в экс­трен­ной ситу­ации

Из этой цепоч­ки нас инте­ресу­ют ров­но три пун­кта:

  • Record emergency video — экс­трен­ная виде­оза­пись, тре­бует камеру и мик­рофон;
  • Emergency sharing — отправ­ка геопо­зиции доверен­ным кон­тактам, не работа­ет без вклю­чен­ной геоло­кации;
  • Car Crash Detection — детект ДТП, ждет от тебя голосо­вую коман­ду, то есть сно­ва нужен мик­рофон.

Объ­еди­няет их одно общее свой­ство: все три пун­кта упи­рают­ся в сен­сор, который поль­зователь мог заранее вык­лючить. И если сен­сор заб­локиро­ван, то Personal Safety чес­тно поп­росит его вклю­чить — вот таким диало­гом.

Так Personal Safety просит разблокировать камеру перед стартом экстренной видеозаписи
Так Personal Safety про­сит раз­бло­киро­вать камеру перед стар­том экс­трен­ной виде­оза­писи

Вот в этот момент — момент вклю­чения сен­сора ради экс­трен­ной служ­бы — устрой­ство и ока­зыва­ется уяз­вимым. Но с сис­темной зво­нил­кой все нам­ного инте­рес­нее: там поль­зовате­лю вооб­ще ничего не нуж­но нажимать.

 

Микрофон, который включается сам

Нач­нем с наб­людения, с которо­го все завер­телось. В Android есть Privacy controls: в штор­ке быс­трых нас­тро­ек мож­но заб­локиро­вать мик­рофон и камеру гло­баль­но, для всех при­ложе­ний сра­зу, нев­зирая ни на какие выдан­ные раз­решения.

Бло­киру­ем мик­рофон, набира­ем 911 — и зво­нок спо­кой­но про­ходит. Более того, если пос­ле звон­ка заг­лянуть в штор­ку, то ока­жет­ся, что мик­рофон уже не заб­локиро­ван: сис­тема вклю­чила его сама.

Слева микрофон заблокирован пользователем, в середине идет экстренный вызов, справа система сообщает, что микрофон занят звонком
Сле­ва мик­рофон заб­локиро­ван поль­зовате­лем, в середи­не идет экс­трен­ный вызов, спра­ва сис­тема сооб­щает, что мик­рофон занят звон­ком

Это логич­но: в экс­трен­ной ситу­ации дис­петчер дол­жен тебя слы­шать и парано­идаль­ный тум­блер в штор­ке не дол­жен сто­ить поль­зовате­лю жиз­ни. Нап­ример, человек мог заб­локиро­вать мик­рофон еще утром и прос­то забыть об этом. Google, кста­ти, чес­тно пре­дуп­режда­ет юзе­ра пря­мо в Privacy controls, в опи­сании пун­кта Microphone access, толь­ко мел­ким шриф­том:

For apps and services. If this setting is off, microphone data may still be shared when you call an emergency number.

Воп­рос в том, как имен­но сис­тема это дела­ет, и ответ на него ока­зал­ся доволь­но неп­рият­ным.

 

SensorPrivacyService и колбэк экстренного вызова

При заг­рузке system_server под­нима­ет боль­ше сот­ни сис­темных служб, и одна из них — SensorPrivacyService. Она управля­ет сос­тоянием при­ват­ности сен­соров, мик­рофона и камеры, и имен­но к ней обра­щает­ся SystemUI, ког­да ты давишь на тум­блер в штор­ке.

В коде служ­бы есть пере­опре­делен­ный метод onBootPhase, который отра­баты­вает при заг­рузке сис­темы. Как мож­но обна­ружить, в нем соз­дает­ся экзем­пляр при­ват­ного внут­ренне­го клас­са:

com.android.server.sensorprivacy.SensorPrivacyService$CallStateHelper

А в конс­трук­торе хел­пера регис­три­рует­ся кол­бэк OutgoingEmergencyStateCallback через TelephonyManager#registerTelephonyCallback. Кол­бэк пере­опре­деля­ет метод onOutgoingEmergencyCall, и внут­ри вызыва­ется этот метод:

SensorPrivacyService.CallStateHelper#onEmergencyCall

Вот как это выг­лядит:

frameworks/base/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
private void onEmergencyCall() {
synchronized (mCallStateLock) {
if (!mIsInEmergencyCall) {
mIsInEmergencyCall = true;
if (mSensorPrivacyServiceImpl
.isToggleSensorPrivacyEnabled(TOGGLE_TYPE_SOFTWARE, MICROPHONE)) {
mSensorPrivacyServiceImpl.setToggleSensorPrivacyUnchecked(
TOGGLE_TYPE_SOFTWARE, mCurrentUser, OTHER, MICROPHONE, false);
mMicUnmutedForEmergencyCall = true;

Как ука­зано в докумен­тации TelephonyCallback, метод сра­баты­вает на любой экс­трен­ный вызов с любой SIM-кар­той, вклю­чая сце­нарий без SIM вооб­ще. Код про­веря­ет, заб­локиро­ван ли сей­час мик­рофон, и если да — меня­ет сос­тояние при­ват­ности сен­сора. Далее цепоч­ка вызовов при­водит нас к при­ват­ному методу SensorPrivacyService#setGlobalRestriction:

frameworks/base/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
private void setGlobalRestriction(int sensor, boolean enabled) {
switch(sensor) {
case MICROPHONE:
mAppOpsManagerInternal.setGlobalRestriction(OP_RECORD_AUDIO, enabled,
mAppOpsRestrictionToken);
mAppOpsManagerInternal.setGlobalRestriction(
OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, enabled, mAppOpsRestrictionToken);
mAppOpsManagerInternal.setGlobalRestriction(OP_PHONE_CALL_MICROPHONE, enabled,
mAppOpsRestrictionToken);
// ...

В коде метода вид­но, что огра­ниче­ния сни­мают­ся сра­зу для пяти опе­раций, свя­зан­ных с мик­рофоном:

  • OP_RECORD_AUDIO (op code = 27);
  • OP_PHONE_CALL_MICROPHONE (100);
  • OP_RECEIVE_AMBIENT_TRIGGER_AUDIO (120);
  • OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO (121);
  • OP_RECEIVE_SANDBOX_TRIGGER_AUDIO (136).

Та­ким обра­зом, был сде­лан вывод, что при звон­ке в служ­бу спа­сения сис­тема сни­мает огра­ниче­ния на дос­туп к мик­рофону — и сни­мает их гло­баль­но, а не для одной толь­ко зво­нил­ки. Клю­чевое сло­во здесь Global. Соот­ветс­твен­но, теперь мы подош­ли к тому момен­ту, ког­да сто­ит рас­крыть роль AppOpsService в сис­теме, что­бы понять, чем имен­но это гро­зит нам при звон­ке из сис­темно­го dialer’а.

 

AppOpsService: разрешения поверх разрешений

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

Системная служба спрашивает пермишен у PermissionManagerService, а право на операцию — у AppOpsService
Сис­темная служ­ба спра­шива­ет пер­мишен у PermissionManagerService, а пра­во на опе­рацию — у AppOpsService

У при­ложе­ния может быть пер­мишен, а опе­рация при этом зап­рещена — и дос­тупа оно не получит.

info

AppOpsService — это сис­темная служ­ба, которая работа­ет поверх обыч­ной сис­темы раз­решений Android. Даже если поль­зователь выдал при­ложе­нию RECORD_AUDIO, служ­ба может зап­ретить кон­крет­ную опе­рацию, и при­ложе­ние не запишет ни секун­ды зву­ка. Так­же она ведет цен­тра­лизо­ван­ный аудит: кто, ког­да и как дол­го обра­щал­ся к мик­рофону.

Кро­ме того, каж­дой чувс­тви­тель­ной опе­рации соот­ветс­тву­ет свой код, и чис­ло их рас­тет от релиза к релизу: сей­час в кон­стан­те _NUM_OP из AppOpsManager содер­жится 179. Опе­раций при этом замет­но боль­ше, чем раз­решений: у мик­рофона один пер­мишен в манифес­те и целых пять раз­ных appops.

Те­перь о том, что с опе­раци­ями мож­но делать. Работать с ними мож­но через AppOpsManager: startOp запус­кает дли­тель­ную опе­рацию, finishOp завер­шает ее, noteOp отме­чает разовое обра­щение, checkOp про­веря­ет пра­ва. Нап­ример, имен­но startOp зажига­ет тот самый зеленый кру­жок в углу экра­на.

 

Прокси-операции и матрешка AttributionSource

А теперь самое инте­рес­ное. AppOpsService поз­воля­ет запус­кать опе­рации не толь­ко нап­рямую, но и через пос­редни­ка. Пос­редник — это при­ложе­ние, которо­му дос­туп к дан­ным сам по себе не нужен, но оно ини­циирует опе­рацию от лица дру­гого пакета. В докумен­тации AppOpsManager это опи­сано как «report that an application has started executing a long-running operation on behalf of another application». Так­же сто­ит отме­тить, что для это­го в AppOpsManager име­ется отдель­ная трой­ка методов — startProxyOp, finishProxyOp и noteProxyOp.

Что­бы служ­ба не путалась, кто кому кем при­ходит­ся, в сис­теме завели класс AttributionSource. Устро­ен он как мат­решка: свер­ху всег­да прок­си, внут­ри, в поле next, — ини­циатор, он же proxied package. Каж­дый уро­вень несет имя пакета, UID, тег атри­буции и бин­дер‑токен.

Нап­ример, пос­мотреть на живую прок­си‑опе­рацию мож­но пря­мо через dumpsys:

$ adb shell dumpsys appops
Package com.example.app:
RECORD_AUDIO (allow):
apkappcontext=[
Access: [fg-tpd] proxy[uid=10164, pkg=com.android.systemui, attributionTag=null]
]

Чи­тает­ся это так: дос­туп к мик­рофону зап­росил com.example.app, он же proxied package и конеч­ный получа­тель дос­тупа. А опе­рацию от его име­ни запус­тил com.android.systemui — пос­редник, которо­му сам мик­рофон не нужен.

И вот теперь самое важ­ное для всей ата­ки: уни­каль­ность запущен­ной опе­рации скла­дыва­ется из пяти парамет­ров — это имя пакета, UID, код опе­рации, device ID (для основно­го устрой­ства всег­да 0) и тег атри­буции. Этот тег показы­вает, какой имен­но модуль при­ложе­ния дер­нул опе­рацию, и в иде­але дол­жен быть зарегис­три­рован в манифес­те при­ложе­ния с исполь­зовани­ем эле­мен­та <attribution android:tag="..." />.

За­пом­ни эту пятер­ку. Через пару раз­делов она ста­нет для нас глав­ным инс­тру­мен­том ата­ки.

 

Три судьбы операции

Ког­да при­ложе­ние про­сит запус­тить опе­рацию, служ­ба отве­чает одним из режимов: MODE_ALLOWED — мож­но, MODE_IGNORED — нель­зя, MODE_DEFAULT — решай сам. Есть еще MODE_ERRORED и MODE_FOREGROUND, но нам они не понадо­бят­ся. Одна­ко воз­вра­щаемое зна­чение — это толь­ко полови­на исто­рии, и гораз­до важ­нее то, что при этом про­исхо­дит внут­ри самой служ­бы.

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии