Я обнаружил уязвимость, похоже, затронувшую все телефоны Google Pixel: вы можете дать мне любое заблокированное устройство Pixel, и я верну его вам разблокированным. Баг устранили в обновлении безопасности 5 ноября 2022 года.
Проблема позволяла атакующему с физическим доступом к телефону обойти меры защиты экрана блокировки (отпечаток пальца, PIN и так далее), получив полный доступ к устройству пользователя. Уязвимость зафиксирована как CVE-2022-20465; она может затронуть и устройства Android других производителей. Мои рекомендации по патчу и сырой баг-репорт, отправленные Google, можно найти здесь: feed.bugs.xdavidhu.me.
Глава 1: я забываю свой PIN-код SIM-карты
Я очень рад, что этот баг устранили. Это была самая серьёзная уязвимость из найденных мною, настолько крупная, что я начал по-настоящему переживать о сроках её устранения и о том, чтобы самому сохранять её в тайне. Возможно, я преувеличиваю, но не так уж давно ФБР сражалась с Apple почти по той же самой причине.
Я обнаружил этот баг после 24-часового путешествия. Когда я добрался до дома, заряд аккумулятора Pixel 6 составлял всего 1%. Я как раз отправлял серию текстовых сообщений, когда он разрядился. Подумав, что если не закончу разговор, то это будет выглядеть как неловкая шутка, я ринулся к зарядному устройству и снова включил телефон.
Pixel включился и запросил PIN-код SIM-карты. Обычно я его знаю, но на этот раз мне не удалось вспомнить его правильно. Понадеявшись, что смогу разобраться, я попробовал несколько комбинаций, но ввёл три неверных PIN-кода, после чего SIM-карта заблокировалась. Теперь для разблокировки мне нужен был PUK-код.
Я залез в шкаф, смог найти упаковку от SIM-карты, соскрёб покрытие и нашёл PUK-код. После ввода PUK-кода Pixel попросил меня задать новый PIN. Я сделал это и после успешного завершения процесса появился экран блокировки. Но что-то было не так:
Сразу после перезагрузки вместо обычного значка блокировки появился значок сканирования отпечатка пальца. Телефон принял отпечаток моего пальца, чего не должно было случиться, ведь после перезапуска пользователь для разблокировки устройства должен ввести хотя бы раз PIN-код или пароль экрана блокировки.
После принятия отпечатка пальца телефон застрял на странном сообщении «Pixel is starting…» и продолжал показывать его, пока я снова не перезагрузился.
Мысленно я отметил, что это странно и что это может как-то влиять на безопасность, поэтому мне стоит потом изучить вопрос. Честно говоря, я не очень люблю находить подобные поведения, когда я не ищу их специально, потому что когда такое случается, я ощущаю одержимость необходимостью исследования. Мне казалось, я должен убедиться, что за этой странностью нет никаких серьёзных проблем, упущенных другими. Оказалось, что в данном случае они были.
Глава 2: что произошло?
Как и пообещал себе, на следующий день я приступил к изучению этого поведения. Перезапустил телефон, ввёл три раза неверный PIN-код, ввёл PUK-код, выбрал новый PIN и снова пришёл к тому же состоянию «Pixel is starting…».
Я много раз экспериментировал с этим процессом, но однажды забыл перезагрузить телефон и просто продолжил работать в обычном разблокированном состоянии. Даже не осознавая, что делаю, я заблокировал его, выполнил горячую замену из лотка SIM-карты и произвёл процесс сброса PIN-кода SIM-карты.
Как и раньше, я ввёл PUK-код и выбрал новый PIN-код. На этот раз телефон заглючил, и я оказался на своём домашнем экране. Что? Но ведь раньше он был заблокирован?
Это было пугающе странно. Я повторил процесс. Заблокировал телефон, достал и вставил SIM-карту в лоток, сбросил PIN… И снова оказался на домашнем экране. ЧЕГО?
У меня начали трястись руки. КАКОГО ЧЁРТА? ОН РАЗБЛОКИРОВАЛСЯ САМ?
Немного успокоившись, я понял, что мне и в самом деле удалось полностью обойти экран блокировки на Pixel 6 с самыми новыми патчами. Я достал свой старый Pixel 5 и попробовал воспроизвести баг на нём. Тоже сработало.
Вот процесс разблокировки в действии:
Так как атакующий может воспользоваться собственной SIM-картой с блокировкой PIN-кодом, для эксплойта этого бага не нужно ничего, кроме физического доступа. Атакующий может просто заменить SIM в устройстве жертвы, выполнить эксплойт с SIM-картой, имеющей блокировку PIN-кодом, для которой у атакующего есть PUK-код.
Глава 3: реакция Google
Я отправил отчёт. Это был самый короткий отчёт в моей жизни, он состоял всего из пяти простых шагов.
Google (точнее, Android VRP) проверила и зафиксировала внутренний баг за 37 минут. Это действительно впечатляюще. К сожалению, после этого качество и частота ответов начали снижаться.
Так как по официальному тикету общение шло не особо активно, на протяжении срока жизни этого бага я иногда получал полуофициальную информацию от сотрудников Google. На самом деле, я предпочитаю получать новости только по официальному каналу (по тикету бага), которые мог бы раскрыть, но поскольку я общался с отдельными сотрудниками, мне удавалось получать лишь фрагменты информации.
Стоит также заметить, что после отправки отчёта я изучил таблицу вознаграждений Android VRP, в которой говорится, что если сообщить о способе обхода экрана блокировки, затрагивающем несколько устройств или все устройства [Pixel], то можно получить максимальную награду в $100 тысяч. Так как мой отчёт соответствовал всем перечисленным параметрам, я уже начал думать, что есть большой шанс получить за этот баг вознаграждение в $100 тысяч.
После проверки бага, по сути, компания замолчала на месяц. Я слышал, что тикет могли закрыть как дублирующийся. Очевидно, кто-то уже сообщал о баге до меня, хотя именно мой отчёт заставил компанию зашевелиться. Наверно, при обработке первого отчёта что-то пошло не так. И в самом деле, спустя 31 день после отправки отчёта меня разбудил пришедший на почту автоматический ответ, в котором говорилось, что «Android Security Team считает, что это повторный отчёт о проблеме, о которой ранее сообщил другой внешний исследователь». Это был переломный для суммы вознаграждения момент — цена бага упала с $100 тысяч до $0. Я не мог ничего поделать, кроме как смириться с тем, что мой баг теперь считается повтором и что они не заплатят.
После отправки моего отчёта прошло почти два месяца тишины. В 59-й день я попинговал тикет, запросив обновление статуса, и получил шаблонный ответ о том, что компания всё ещё работает над исправлением.
Перенесёмся в сентябрь, спустя три месяца после отправки отчёта. Я приехал в Лондон на мероприятие Google для исследователей багов под названием ESCAL8. Только что вышел патч за сентябрь 2022 года, я обновил телефон и однажды вечером в номере отеля попытался воспроизвести баг. Я надеялся, что они уже устранили его. Но нет. Я по-прежнему мог разблокировать телефон.
Это событие привело меня в ужас. Мне показалось, что устранение бага больше волнует и беспокоит меня, чем саму компанию Google. А такого быть не должно. Поэтому в тот вечер я решил обратиться к другим сотрудникам Google, которые посещали мероприятие с нами.
На следующий день я объяснил ситуацию многим людям, и даже провёл живое демо с несколькими телефонами Pixel в офисе Google. Это был тот ещё опыт. У нас не нашлось инструмента для извлечения SIM. Сначала я попробовал достать её иголкой, но каким-то образом умудрился проколоть палец в нескольких местах и рука начала кровить. Инженер Google наложил мне на палец пластырь. (Кто ещё может таким похвастаться?) Игла не подошла, поэтому я стал просить помощи у зрителей, и одна очень добрая женщина дала нам свои серьги. Это помогло! Мы поменяли SIM-карты и не без труда смогли разблокировать устройства. Теперь, когда окружающих, похоже, начала беспокоить эта проблема, я стал чувствовать себя лучше.
Я на следующий день после того, как проколол себе палец
Я установил дедлайн публикации бага на 15 октября, однако команда Android VRP сообщила, что баг не будет пропатчен в октябре. Они планировали всё на декабрь. Мне показалось, что это слишком долго, учитывая важность бага, и я решил придерживаться своего октябрьского дедлайна.
После разговора с несколькими сотрудниками Google об этом дедлайне участник команды Android VRP лично прокомментировал тикет и попросил меня договориться о созвоне насчёт этого бага. Мы устроили созвон в Meet с несколькими людьми, они были очень милы и выслушали всю мою историю о том, как я месяцами находился в неведении, получая лишь шаблонные ответы (даже сообщение о повторе, снизившее награду с $100 тысяч до $0), и как я ощущал, что этот баг больше беспокоит меня, чем Google. Они сказали, что исправление теперь запланировано на ноябрь, а не на декабрь. Однако мой дедлайн всё равно был в октябре.
Спустя две недели после этого созвона я получил новое сообщение, подтверждавшее исходную информацию, которая у меня была. В нём говорилось, что хотя мой отчёт и был повторным, так получилось только потому, что именно благодаря ему они начали работать над исправлением. Поэтому они решили сделать исключение и выплатить награду в $70 тысяч за обход экрана блокировки. Я же решил (ещё до известий о награде), что слишком опасаюсь публиковать информацию о живом баге, и что поскольку исправление выпустят меньше чем через месяц, оно на самом деле того не стоит. Я решил подождать исправления.
Полный текст переговоров можно прочитать на feed.bugs.xdavidhu.me.
В конечном итоге, хотя поначалу этот баг для меня, как для хакера, был не очень приятным опытом, после того, как я громко «закричал» о нём, его заметили и начали реально стремиться исправить ошибку. Надеюсь, компания справедливо вознаградила и первого исследователя, обнаружившего проблему. В конце концов, мне кажется, Google справилась довольно неплохо, хотя срок жизни бага всё равно показался мне слишком долгим.
Но вы можете судить об этом сами.
Глава 4: что было причиной бага?
Так как Android является опенсорсной операционной системой, коммит с устранением этой проблемы и всеми изменениями в коде выложен публично:
Первое, что меня удивило при первом изучении коммита — это количество изменённых файлов. Раньше я думал, что этот баг можно исправить единственным изменением, удалением некорректной строки кода, отвечающей за срабатывание разблокировки. Но всё было не так просто:
Прочитав описание коммита и изменения в коде, я решил, что могу приблизительно представить, что происходит внутри системы. Учтите, что я не разработчик Android, поэтому описание будет высокоуровневым.
Похоже, что в Android есть концепция «экран безопасности». Экраном безопасности могут быть различные элементы: экран ввода PIN-кода, экран сканирования отпечатка пальца, экран ввода пароля или, как в нашем случае, экран ввода PIN-кода и PUK-кода SIM-карты.
Эти экраны безопасности можно можно накладывать «поверх» друг друга. Например, когда телефон был заблокирован и нужно было ввести PIN-код SIM-карты, экран безопасности ввода PIN-кода SIM находился поверх экрана безопасности сканирования отпечатка пальца.
После успешного сброса PUK-кода SIM компонент сброса PUK-кода вызывал для «стека экранов безопасности» функцию .dismiss()
, что заставляло устройство закрыть текущий и показать экран безопасности, находящийся «под ним» в стеке. В нашем примере это был экран безопасности сканирования отпечатка пальца.
Так как функция .dismiss()
просто закрывала текущий экран безопасности, она была уязвима к условиям гонки. Представьте, что произошло бы, если бы что-то в фоновом режиме изменило текущий экран безопасности до того, как компонент сброса PUK-кода добрался до вызова .dismiss()
. Закрыл ли бы компонент PUK-кода не относящийся к нему экран безопасности, когда он наконец вызвал бы .dismiss()
?
Похоже, именно это и произошло. Какая-то другая часть системы в фоновом режиме отслеживала состояние SIM-карты, и обнаружив изменение, обновила информацию о том, какой экран безопасности активен в текущий момент. Кажется, этот фоновый компонент делал обычный экран, например, экран сканирования отпечатка, активным экраном безопасности ещё до того, как компонент PUK-кода мог добраться до своего вызова функции .dismiss()
. К тому моменту, когда компонент PUK вызывал функцию .dismiss()
, она на самом деле закрывала экран безопасности сканирования отпечатка, вместо того, чтобы закрыть экран безопасности ввода PUK-кода, как это изначально требовалось. А вызов .dismiss()
для экрана безопасности сканирования отпечатка приводил к разблокировке телефона.
Похоже, инженеры Android решили отрефакторить функцию .dismiss()
так, чтобы она требовала дополнительный параметр, в котором вызывающий её компонент мог указать, какой тип экрана безопасности он хочет закрыть. В нашем случае компонент PUK теперь явным образом вызывает .dismiss(SecurityMode.SimPuk)
, чтобы закрывались только экраны безопасности типа SimPuk
. Если текущий активный экран безопасности не является экраном SimPuk
(возможно, потому что, как в нашем случае, его изменил какой-то фоновый компонент), то функция закрытия ничего не сделает.
Мне кажется, это довольно изящное и надёжное решение для защиты от подобных проблем, а также от будущих условий гонки. Я не ожидал, что из-за этого бага придётся вносить такие серьёзные изменения в код Android.
Автор:
PatientZero