stihl не предоставил(а) никакой дополнительной информации.
Мы изучили безопасность типичного «мозга» современного автомобиля — SoC со встроенным сотовым модемом. Найденная низкоуровневая ошибка позволила удаленно выполнить код на ранней стадии соединения — до установки защищенного канала связи. Следом мы получили доступ к процессору приложений и смогли запустить произвольный код с максимальными привилегиями — то есть получили полный контроль над SoC.
Сегодня все больше устройств подключено к сети напрямую — через сотовый модем, а не через домашний роутер. И все чаще эти модемы интегрируются в однокристальные системы в виде специализированного модемного ядра (CP) под управлением ОС реального времени.
В то же время на другом ядре той же системы на чипе может работать операционная система общего назначения, такая как Android. Это ядро часто называется процессором приложений (AP). При этом тонкости взаимосвязи между AP, CP и оперативной памятью на уровне микроархитектуры известна только производителю, хотя от этого зависит безопасность всей SoC.
Мы привыкли считать, что обход механизмов безопасности в сетях 3G/LTE — это задача скорее академическая. Ведь при подключении между пользователем и базовой станцией сотовой связи устанавливается защищенный канал связи. Даже если кто‑то сможет обойти эти механизмы, обнаружить уязвимость в модеме и выполнить на нем произвольный код, это не должно поставить под угрозу бизнес‑логику устройства. Эта логика (например, пользовательские приложения, история браузера, звонки и SMS на смартфоне) работает на АР и предположительно не может быть доступна с модема. Или может?
Чтобы выяснить это, мы провели исследование безопасности современной системы на чипе Unisoc UIS7862A, оснащенной встроенным 2G/3G/4G-модемом. Например, такие SoC можно встретить в мобильных устройствах или, что более интересно, в головных устройствах современных автомобилей, уверенно захватывающих рынок РФ. Безопасность головного устройства автомобиля — это безопасность не только данных, но и дорожного движения.
Мы обнаружили несколько критических уязвимостей на разных уровнях стека сотовых протоколов модема Unisoc UIS7862A. Сегодня речь пойдет о самой интересной из них, а именно — переполнении стека в реализации протокола 3G RLC (Для просмотра ссылки Войдиили Зарегистрируйся), которая может быть использована для удаленного выполнения кода на ранних этапах подключения до активации каких‑либо защитных механизмов.
При этом выполнение кода на модеме — лишь точка входа, первый шаг к полной удаленной компрометации всей SoC. На пути к этоу мы нашли сразу несколько способов получить доступ к AP, в том числе аппаратную уязвимость в виде скрытого периферийного устройства DMA.
В итоге мы смогли пропатчить ядро Android прямо во время работы и в результате выполнить произвольный код с наивысшими привилегиями в системе.
Для просмотра ссылки Войдиили Зарегистрируйся
В соответствии с номерами на фото:
Чем нас заинтересовал именно этот протокол? Все дело в том, что именно он используется для установки безопасного зашифрованного канала передачи данных между базовой станцией (БС) и модемом (по нему, в частности, «бегает» NAS). Таким образом, наличие уязвимости типа RCE в нем позволит получить безусловное исполнение своего кода на модеме в обход всех механизмов защиты коммуникации в 3G.
Для просмотра ссылки Войдиили Зарегистрируйся
В протоколе RLC используются три режима передачи: TM, UM и AM. При этом нас сейчас будет интересовать только один из них, а именно режим UM — unacknowledged mode. В стандарте 3G предусмотрена разбивка данных на фрагменты и наоборот — объединение нескольких небольших фрагментов высокоуровневых данных (PDU) в один фрейм канального уровня.
Для просмотра ссылки Войдиили Зарегистрируйся
Сделано это из соображений максимальной утилизации канала передачи. На уровне RLC пакеты называются SDU.
Чтобы найти в прошивке функции их обработки среди огромного числа функций (~75 000), достаточно поискать используемые в процессе работы константы (0х7FFF, 0x7FFC, 0x7FFB). Затем среди всех мест, где встретятся эти константы, выделяем те, в которых они осмысленно используются в контексте исполнения кода. И в итоге получим ограниченный набор функций, в том числе функцию обработки входящего пакета SDU.
При обработке такого пакета происходит разбор полей его заголовка. Сам пакет состоит из обязательного заголовка (SN + E) и из необязательных заголовков (LI + E) и данных.
Для просмотра ссылки Войдиили Зарегистрируйся
Обработка необязательного заголовка заключается в последовательном проходе по каждому полю LI. Для этого используется функция get_data_offset. Она должна разобрать необязательные заголовки LI, собрать размеры кусков данных внутри части данных и вернуть указатель на данные внутри пакета SDU. Самое важное — информация о размере всех конкатенированных PDU внутри SDU, согласно анализу кода ОС модема, хранятся на стеке!
Для просмотра ссылки Войдиили Зарегистрируйся
Внутри алгоритм будет честно обрабатывать каждое поле заголовка. При этом признак конца необязательных заголовков — младший бит, равный нулю (E bit). Если он равен единице, то обработка продолжается дальше. В процессе обработки происходит запись данных в переменную, которая расположена на стеке предыдущей функции. Ограничений на число необязательных заголовков нет. Глубина стека — 0хВ4 байт. Размер пакета, который можно парсить (число заголовков, каждый заголовок — это запись 2 байт на стек), ограничен размером пакета SDU в 0х5F0 байт.
Для просмотра ссылки Войдиили Зарегистрируйся
В итоге эксплуатация возможна всего лишь одним пакетом, в котором число заголовков больше, чем глубина стека (90 заголовков). При этом именно в этой функции стековая канарейка отсутствует, а при переполнении стека именно в этой функции можно подменить адрес возврата и некоторые из non-volatile-регистров.
Однако подмена возможна только значением, оканчивающимся на единицу в бинарном виде (младший бит равен 1). Самое красивое в этом то, что мы исполняемся на ARM в режиме Thumb, а значит, все адреса возврата должны иметь младший бит, равный единице! Совпадение? Возможно...
В любом случае первая посылка dummy-пакета SDU с соответствующим числом «правильных» заголовков привела к перезагрузке устройства. Однако на тот момент у нас не было возможности получить информацию о том, где именно произошло падение и почему (хотя мы и догадывались, что причиной была ошибка передачи управления по адресу 0хААВВССDD, взятому из нашего пакета). Давай разбираться, что с этим можно сделать.
Чтобы убедиться в том, что мы действительно можем исполнить свой код на стороне модема, воспользуемся доступным нам для взаимодействия обработчиком AT-команд. Так как нам неизвестен текущий адрес фрейма стека, на котором лежат наши данные, а также потому, что мы не знаем, исполняем ли стек вообще, и, кроме того, мы не знаем (но догадываемся), замаплены ли секции кода в режиме записи (нет), самый верный способ — это переписать данные в области RAM-памяти. Для этого ищем подходящую функцию среди доступных AT-команд. Первая подходящая команда — это SPSERVICETYPE.
Для просмотра ссылки Войдиили Зарегистрируйся
Далее нам необходимо использовать ROP-гаджеты для того, чтобы переписать адрес 0х8CE56218, при этом не нарушив дальнейшую работу алгоритма обработки входящих SDU-пакетов. Как это сделать? Для этого достаточно обеспечить возврат в функцию, из которой был вызов функции обработки SDU-пакета, потому что она вызывается как callback и за счет этого отсутствует связь по данным на стеке. С учетом того, что эта функция добавила на стек всего 0х2С байт, придется уложиться в этот размер.
Для просмотра ссылки Войдиили Зарегистрируйся
Итак, найдя подходящую ROP-цепочку, мы запускаем наш SDU-пакет, в котором в качестве payload находится она. И в итоге видим наш вывод 0xAABBCCDD в консоли AT-команды SPSERVICETYPE. Наш код работает!
Для просмотра ссылки Войдиили Зарегистрируйся
Далее по аналогии мы можем вывести значение адреса фрейма стека, на котором находятся наши данные. Однако, к сожалению, оказалось, что стек не является исполняемым. Теперь перед нами встала задача разобраться с настройкой MPU на модеме. Для этого уже известным методом ROP-цепочек генерируем код, который будет читать таблицу MPU по DWORD за один раз... И много итераций спустя у нас имеется такая таблица.
Для просмотра ссылки Войдиили Зарегистрируйся
Как видно из рисунка, секция кода замаплена, как и ожидалось, только на исполнение. Но что, если мы можем поменять эту настройку? Еще одна ROP-цепочка, и теперь в таблице на неиспользуемом месте эта же секция замаплена с правами записи. Это работает благодаря особенности программирования MPU, а именно за счет наличия механизма overlap и того факта, что регион с большим ID имеет больший приоритет.
Для просмотра ссылки Войдиили Зарегистрируйся
Остается воспользоваться указателем на наши данные (помнишь, что он все еще лежит в R2?) и, наконец, пропатчить только что разблокированную на запись секцию кода. Но что будем патчить? Самый простой путь — это пропатчить обработчик NAS-протокола, добавив в него свой. Для этого воспользуемся одной из команд протокола NAS, гарантированно используемой у нас, — MM Information. С ее помощью можно пересылать за один раз большое число данных, а в ответ получить один байт данных через команду MM Status.
Для просмотра ссылки Войдиили Зарегистрируйся
В итоге нам удалось не только получить возможность исполнения своего кода на стороне модема, но и установить с ним полноценную двухстороннюю связь, используя при этом высокоуровневый протокол NAS в качестве способа доставки наших сообщений. В данном случае пакет MM Status с полем cause, равным нашему 0хАА.
Для просмотра ссылки Войдиили Зарегистрируйся
Но исполнение своего кода на стороне модема не дает возможности получить доступ к пользовательским данным. Или дает?
Для просмотра ссылки Войдиили Зарегистрируйся
А что, если модем и АР используют одно и то же адресное пространство? Ведь RAM-память в SoC, скорее всего, реализована как единый аппаратный компонент. Анализ device tree на стороне AP еще больше убедил в том, что, вероятнее всего, физическое адресное пространство у СР и АР одно и то же (сравни с таблицей MPU выше).
Для просмотра ссылки Войдиили Зарегистрируйся
Остается только проверить, действительно ли это так. Для этого нужно снова пропатчить таблицу MPU, на этот раз добавив строку, которая разрешает доступ к памяти на чтение и запись, начиная с 0х80000000. В итоге нам удалось замапить адресное пространство АР в адресное пространство СР. И в качестве PoC мы решили пропатчить на лету первую страницу ядра Linux.
Для просмотра ссылки Войдиили Зарегистрируйся
Кажется, что на этом можно уже остановиться: мы можем исполнить свой код не только на СР, но и на АР. Но может быть, есть еще какой‑нибудь способ?
Для ответа на этот вопрос мы исследовали доступную аппаратную периферию на стороне СР. Конечно же, наиболее значимым для исследования был DMA. Анализируя код, мы обнаружили, что все DMA находятся в области памяти СР 0х20000000. Однако попытка чтения памяти по этим адресам приводила лишь к ошибке DataAbort на уровне аппаратного ядра СР. В чем же дело?
Мы стали внимательнее анализировать код, отвечающий за работу с DMA. Примечательно, что некоторые участки кода, работающие с DMA, не имели вызовов, как будто их забыли «убрать» из релизной версии ОС СР. Благодаря фрагментам кода из таких участков нам удалось получить доступ к DMA. Оказалось, что большая часть периферии была физически отключена от питания. Чтобы ее включить, нужно выполнить запись в специальный аппаратный регистр.
Для просмотра ссылки Войдиили Зарегистрируйся
Но DMA оказалось недостаточно просто включить, необходимо подать на него clock. За это отвечает другой аппаратный регистр в том же регионе памяти.
Для просмотра ссылки Войдиили Зарегистрируйся
В итоге нам удалось разблокировать доступ к неиспользуемому в ОС СР DMA, с помощью которого мы точно так же, как ранее через таблицы MPU, смогли перезаписать первую страницу ОС АР.
Для просмотра ссылки Войдиили Зарегистрируйся
К сожалению, в отличие от таблиц MPU от этой микроархитектурной особенности невозможно защититься программным обновлением, а возможности, которые она предоставляет, явно позволяют нарушить безопасность АР, имея всего одну уязвимость уровня RCE на CP.
В качестве полезной нагрузки для демонстрации (proof of concept) мы решили попробовать установить и запустить игру Doom на AP. Схема, которую нам необходимо реализовать, выглядит так: часть полезной нагрузки, работающая на CP, последовательно ищет ключевые структуры ядра Linux, после чего внедряет в него собственный код для выполнения требуемых действий. Весь процесс должен происходить без участия пользователя и обходить стандартные механизмы безопасности Android.
Для просмотра ссылки Войдиили Зарегистрируйся
Таблица kallsyms обычно располагается в секции .rodata и имеет характерную структуру:
В нашем случае необходимо найти следующие критически важные для проведения атаки символы:
Для просмотра ссылки Войдиили Зарегистрируйся
Каждый элемент таблицы соответствует определенному системному вызову, идентифицируемому номером. Номера фиксированы для каждой архитектуры, и для ARM64 они Для просмотра ссылки Войдиили Зарегистрируйся.
Нас интересует системный вызов getpriority с номером 141 (0x8d). Соответствующий элемент в таблице находится по смещению 141 * 8 = 0x468 = 1128 байт. Значит, указатель на обработчик getpriority расположен по адресу 0x809D2468 (PA). Почему именно getpriority? Мы выяснили, что программы в Android время от времени вызывают эту функцию, но при этом не слишком часто. А это в теории позволит избежать проблем, связанных с гонками и многократными вызовами нашего кода при перехвате этого вызова.
/system/bin/am start -n com.eltechs.originaldoom/.doomDemo.DoomDemo
Перед этим шелл‑код должен выполнить ряд важных действий:
Для просмотра ссылки Войдиили Зарегистрируйся
В коде также содержится таблица переменных окружения, нужная для корректной работы пользовательских процессов вроде activity manager или пакетного менеджера.
Для просмотра ссылки Войдиили Зарегистрируйся
В конце работы наш шелл‑код восстанавливает состояние регистров процессора и вызывает изначальный getpriority.
Для просмотра ссылки Войдиили Зарегистрируйся
В результате работы всей цепочки эксплоитов мы получили возможность исполнить свой код на стороне АР.
Для просмотра ссылки Войдиили Зарегистрируйся
Теперь мы можем с уверенностью сказать, что головные устройства некоторых современных автомобилей могут запускать Doom. При этом весь процесс, как и было задумано, происходит без участия пользователя и обходит стандартные механизмы безопасности Android и ядра Linux благодаря прямому доступу к памяти.
или Зарегистрируйся
Эксплуатация всего лишь одной уязвимости на стороне СР позволила нам открыть «god mode» во всей SoC. Конкретную уязвимость можно исправить программно, но обнаруженные проблемы микроархитектурного уровня исправить можно будет только в следующих партиях SoC. Да и только ли в этой SoC есть такая аппаратная «фича»?
Захватив SoC, злоумышленник может не только контролировать информационный поток между устройством и внешним миром, но и получить практически неограниченный доступ к наиболее важным компонентам конечного устройства.
Например, при компрометации SoC, используемой в автомобиле, злоумышленник, конечно же, не будет ограничен установкой Doom. Он может получить удаленный доступ к пользовательским данным, например к записи голоса через встроенный микрофон, или вообще развить атаку дальше — на подключаемые мобильные устройства, используя уязвимости в сервисах CarPlay и AndroidAuto. Кроме того, в случае ошибки в конфигурации бортового шлюза шины CAN он может получить возможность удаленного воздействия на другие автомобильные блоки.
Проблему усугубляет то, что при обнаружении серьезной уязвимости в CP может понадобиться значительное время на обновление всех устройств, в которых установлен тот же чип. В каких‑то устройствах удаленное обновление может быть вообще не заложено как функция. Тогда установка обновления требует дополнительных усилий и затрат со стороны производителя — каждое устройство придется обновлять вручную.
warning
Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.Сегодня все больше устройств подключено к сети напрямую — через сотовый модем, а не через домашний роутер. И все чаще эти модемы интегрируются в однокристальные системы в виде специализированного модемного ядра (CP) под управлением ОС реального времени.
В то же время на другом ядре той же системы на чипе может работать операционная система общего назначения, такая как Android. Это ядро часто называется процессором приложений (AP). При этом тонкости взаимосвязи между AP, CP и оперативной памятью на уровне микроархитектуры известна только производителю, хотя от этого зависит безопасность всей SoC.
Мы привыкли считать, что обход механизмов безопасности в сетях 3G/LTE — это задача скорее академическая. Ведь при подключении между пользователем и базовой станцией сотовой связи устанавливается защищенный канал связи. Даже если кто‑то сможет обойти эти механизмы, обнаружить уязвимость в модеме и выполнить на нем произвольный код, это не должно поставить под угрозу бизнес‑логику устройства. Эта логика (например, пользовательские приложения, история браузера, звонки и SMS на смартфоне) работает на АР и предположительно не может быть доступна с модема. Или может?
Чтобы выяснить это, мы провели исследование безопасности современной системы на чипе Unisoc UIS7862A, оснащенной встроенным 2G/3G/4G-модемом. Например, такие SoC можно встретить в мобильных устройствах или, что более интересно, в головных устройствах современных автомобилей, уверенно захватывающих рынок РФ. Безопасность головного устройства автомобиля — это безопасность не только данных, но и дорожного движения.
Мы обнаружили несколько критических уязвимостей на разных уровнях стека сотовых протоколов модема Unisoc UIS7862A. Сегодня речь пойдет о самой интересной из них, а именно — переполнении стека в реализации протокола 3G RLC (Для просмотра ссылки Войди
При этом выполнение кода на модеме — лишь точка входа, первый шаг к полной удаленной компрометации всей SoC. На пути к этоу мы нашли сразу несколько способов получить доступ к AP, в том числе аппаратную уязвимость в виде скрытого периферийного устройства DMA.
В итоге мы смогли пропатчить ядро Android прямо во время работы и в результате выполнить произвольный код с наивысшими привилегиями в системе.
Получаем ВПО модема
Модем мы обнаружили на плате головного устройства одного автомобиля. Давай посмотрим, что там есть еще.Для просмотра ссылки Войди
В соответствии с номерами на фото:
- Realtek RTL8761ATV 802.11b/g/n 2.4G single-chip that integrates Wireless LAN (WLAN) and a network USB interface (USB 1.0/1.1/2.0 compatible) controller.
- SPRD UMW2652 BGA WiFi.
- 55966 TYADZ 21086.
- SPRD SR3595D RF Transceiver Spreadtrum (Unisoc).
- Techpoint TP9950 Video Decoder Chip.
- UNISOC UIS7862A.
- BIWIN BWSRGX32H2A-48G-X, Package 200-FBGA, ROM Type — Discrete, ROM Size — LPDDR4X, 48G.
- SCY E128CYNT2ABE00 EMMC 128G/JEDEC.
- SPREADTRUM UMP510G5 Power Management Ic.
- FEI.1s LE330315 USB2.0 Shunt chip IC.
- SCT SCT2432STER Synchronous Step-down DCDC Converter with Internal Compensation.
Удаленный доступ в модем (CVE-2024-39431)
Как любой современный модем, наш включал в себя реализацию сразу нескольких стеков протоколов: 2G, 3G, LTE. А как известно, чем больше протоколов реализует устройство, тем больше потенциальных точек входа, то есть векторов атаки. И чем ниже в стеке модели OSI находится уязвимость, тем серьезнее последствия ее эксплуатации. Поэтому мы решили проанализировать механизмы фрагментации пакетов данных на уровне доступа к среде передачи (протокол RLC).Чем нас заинтересовал именно этот протокол? Все дело в том, что именно он используется для установки безопасного зашифрованного канала передачи данных между базовой станцией (БС) и модемом (по нему, в частности, «бегает» NAS). Таким образом, наличие уязвимости типа RCE в нем позволит получить безусловное исполнение своего кода на модеме в обход всех механизмов защиты коммуникации в 3G.
Для просмотра ссылки Войди
В протоколе RLC используются три режима передачи: TM, UM и AM. При этом нас сейчас будет интересовать только один из них, а именно режим UM — unacknowledged mode. В стандарте 3G предусмотрена разбивка данных на фрагменты и наоборот — объединение нескольких небольших фрагментов высокоуровневых данных (PDU) в один фрейм канального уровня.
Для просмотра ссылки Войди
Сделано это из соображений максимальной утилизации канала передачи. На уровне RLC пакеты называются SDU.
Чтобы найти в прошивке функции их обработки среди огромного числа функций (~75 000), достаточно поискать используемые в процессе работы константы (0х7FFF, 0x7FFC, 0x7FFB). Затем среди всех мест, где встретятся эти константы, выделяем те, в которых они осмысленно используются в контексте исполнения кода. И в итоге получим ограниченный набор функций, в том числе функцию обработки входящего пакета SDU.
При обработке такого пакета происходит разбор полей его заголовка. Сам пакет состоит из обязательного заголовка (SN + E) и из необязательных заголовков (LI + E) и данных.
Для просмотра ссылки Войди
Обработка необязательного заголовка заключается в последовательном проходе по каждому полю LI. Для этого используется функция get_data_offset. Она должна разобрать необязательные заголовки LI, собрать размеры кусков данных внутри части данных и вернуть указатель на данные внутри пакета SDU. Самое важное — информация о размере всех конкатенированных PDU внутри SDU, согласно анализу кода ОС модема, хранятся на стеке!
Для просмотра ссылки Войди
Внутри алгоритм будет честно обрабатывать каждое поле заголовка. При этом признак конца необязательных заголовков — младший бит, равный нулю (E bit). Если он равен единице, то обработка продолжается дальше. В процессе обработки происходит запись данных в переменную, которая расположена на стеке предыдущей функции. Ограничений на число необязательных заголовков нет. Глубина стека — 0хВ4 байт. Размер пакета, который можно парсить (число заголовков, каждый заголовок — это запись 2 байт на стек), ограничен размером пакета SDU в 0х5F0 байт.
Для просмотра ссылки Войди
В итоге эксплуатация возможна всего лишь одним пакетом, в котором число заголовков больше, чем глубина стека (90 заголовков). При этом именно в этой функции стековая канарейка отсутствует, а при переполнении стека именно в этой функции можно подменить адрес возврата и некоторые из non-volatile-регистров.
Однако подмена возможна только значением, оканчивающимся на единицу в бинарном виде (младший бит равен 1). Самое красивое в этом то, что мы исполняемся на ARM в режиме Thumb, а значит, все адреса возврата должны иметь младший бит, равный единице! Совпадение? Возможно...
В любом случае первая посылка dummy-пакета SDU с соответствующим числом «правильных» заголовков привела к перезагрузке устройства. Однако на тот момент у нас не было возможности получить информацию о том, где именно произошло падение и почему (хотя мы и догадывались, что причиной была ошибка передачи управления по адресу 0хААВВССDD, взятому из нашего пакета). Давай разбираться, что с этим можно сделать.
Закрепляемся в системе
Первое и самое важное наблюдение — мы знаем, что указатель на наш только что полученный пакет SDU хранится в регистре R2. Таким образом, можно использовать технику ROP для того, чтобы скопировать свой код в какое‑нибудь место, где он гарантированно получит возможность исполниться. Но можем ли мы вообще выполнить свой код?Чтобы убедиться в том, что мы действительно можем исполнить свой код на стороне модема, воспользуемся доступным нам для взаимодействия обработчиком AT-команд. Так как нам неизвестен текущий адрес фрейма стека, на котором лежат наши данные, а также потому, что мы не знаем, исполняем ли стек вообще, и, кроме того, мы не знаем (но догадываемся), замаплены ли секции кода в режиме записи (нет), самый верный способ — это переписать данные в области RAM-памяти. Для этого ищем подходящую функцию среди доступных AT-команд. Первая подходящая команда — это SPSERVICETYPE.
Для просмотра ссылки Войди
Далее нам необходимо использовать ROP-гаджеты для того, чтобы переписать адрес 0х8CE56218, при этом не нарушив дальнейшую работу алгоритма обработки входящих SDU-пакетов. Как это сделать? Для этого достаточно обеспечить возврат в функцию, из которой был вызов функции обработки SDU-пакета, потому что она вызывается как callback и за счет этого отсутствует связь по данным на стеке. С учетом того, что эта функция добавила на стек всего 0х2С байт, придется уложиться в этот размер.
Для просмотра ссылки Войди
Итак, найдя подходящую ROP-цепочку, мы запускаем наш SDU-пакет, в котором в качестве payload находится она. И в итоге видим наш вывод 0xAABBCCDD в консоли AT-команды SPSERVICETYPE. Наш код работает!
Для просмотра ссылки Войди
Далее по аналогии мы можем вывести значение адреса фрейма стека, на котором находятся наши данные. Однако, к сожалению, оказалось, что стек не является исполняемым. Теперь перед нами встала задача разобраться с настройкой MPU на модеме. Для этого уже известным методом ROP-цепочек генерируем код, который будет читать таблицу MPU по DWORD за один раз... И много итераций спустя у нас имеется такая таблица.
Для просмотра ссылки Войди
Как видно из рисунка, секция кода замаплена, как и ожидалось, только на исполнение. Но что, если мы можем поменять эту настройку? Еще одна ROP-цепочка, и теперь в таблице на неиспользуемом месте эта же секция замаплена с правами записи. Это работает благодаря особенности программирования MPU, а именно за счет наличия механизма overlap и того факта, что регион с большим ID имеет больший приоритет.
Для просмотра ссылки Войди
Остается воспользоваться указателем на наши данные (помнишь, что он все еще лежит в R2?) и, наконец, пропатчить только что разблокированную на запись секцию кода. Но что будем патчить? Самый простой путь — это пропатчить обработчик NAS-протокола, добавив в него свой. Для этого воспользуемся одной из команд протокола NAS, гарантированно используемой у нас, — MM Information. С ее помощью можно пересылать за один раз большое число данных, а в ответ получить один байт данных через команду MM Status.
Для просмотра ссылки Войди
В итоге нам удалось не только получить возможность исполнения своего кода на стороне модема, но и установить с ним полноценную двухстороннюю связь, используя при этом высокоуровневый протокол NAS в качестве способа доставки наших сообщений. В данном случае пакет MM Status с полем cause, равным нашему 0хАА.
Для просмотра ссылки Войди
Но исполнение своего кода на стороне модема не дает возможности получить доступ к пользовательским данным. Или дает?
Lateral movement внутри SoC
Внутреннее устройство СР в контексте микроархитектуры SoC — огромная тема для исследований, и здесь возможны самые разные векторы атаки на AP. Анализируя внутреннее устройство модема, мы первым делом обратили внимание на то, что в модеме используются реальные адреса RAM, а не виртуальные. Кроме того, наше внимание привлекли адреса, по которым располагалось ядро ОС АР.Для просмотра ссылки Войди
А что, если модем и АР используют одно и то же адресное пространство? Ведь RAM-память в SoC, скорее всего, реализована как единый аппаратный компонент. Анализ device tree на стороне AP еще больше убедил в том, что, вероятнее всего, физическое адресное пространство у СР и АР одно и то же (сравни с таблицей MPU выше).
Для просмотра ссылки Войди
Остается только проверить, действительно ли это так. Для этого нужно снова пропатчить таблицу MPU, на этот раз добавив строку, которая разрешает доступ к памяти на чтение и запись, начиная с 0х80000000. В итоге нам удалось замапить адресное пространство АР в адресное пространство СР. И в качестве PoC мы решили пропатчить на лету первую страницу ядра Linux.
Для просмотра ссылки Войди
Кажется, что на этом можно уже остановиться: мы можем исполнить свой код не только на СР, но и на АР. Но может быть, есть еще какой‑нибудь способ?
Для ответа на этот вопрос мы исследовали доступную аппаратную периферию на стороне СР. Конечно же, наиболее значимым для исследования был DMA. Анализируя код, мы обнаружили, что все DMA находятся в области памяти СР 0х20000000. Однако попытка чтения памяти по этим адресам приводила лишь к ошибке DataAbort на уровне аппаратного ядра СР. В чем же дело?
Мы стали внимательнее анализировать код, отвечающий за работу с DMA. Примечательно, что некоторые участки кода, работающие с DMA, не имели вызовов, как будто их забыли «убрать» из релизной версии ОС СР. Благодаря фрагментам кода из таких участков нам удалось получить доступ к DMA. Оказалось, что большая часть периферии была физически отключена от питания. Чтобы ее включить, нужно выполнить запись в специальный аппаратный регистр.
Для просмотра ссылки Войди
Но DMA оказалось недостаточно просто включить, необходимо подать на него clock. За это отвечает другой аппаратный регистр в том же регионе памяти.
Для просмотра ссылки Войди
В итоге нам удалось разблокировать доступ к неиспользуемому в ОС СР DMA, с помощью которого мы точно так же, как ранее через таблицы MPU, смогли перезаписать первую страницу ОС АР.
Для просмотра ссылки Войди
К сожалению, в отличие от таблиц MPU от этой микроархитектурной особенности невозможно защититься программным обновлением, а возможности, которые она предоставляет, явно позволяют нарушить безопасность АР, имея всего одну уязвимость уровня RCE на CP.
Разрабатываем эксплоит для АР
После того как мы получили возможность извне модифицировать оперативную память AP (исполняя код на CP и перенастраивая его MPU или с помощью DMA-периферии), возник вопрос: как исполнить свой код на AP и закрепиться в его операционной системе, имея только возможность читать и писать его память?В качестве полезной нагрузки для демонстрации (proof of concept) мы решили попробовать установить и запустить игру Doom на AP. Схема, которую нам необходимо реализовать, выглядит так: часть полезной нагрузки, работающая на CP, последовательно ищет ключевые структуры ядра Linux, после чего внедряет в него собственный код для выполнения требуемых действий. Весь процесс должен происходить без участия пользователя и обходить стандартные механизмы безопасности Android.
Для просмотра ссылки Войди
Этап 1: ищем базовый адрес ядра Linux
Первый шаг атаки — определение базового адреса, загруженного в память ядра Linux. В современных версиях Android ядро часто использует ASLR, но базовый адрес найти не проблема — нужно только поискать по известным сигнатурам. В нашем случае даже этого можно было не делать, так как базовый адрес ядра всегда равен 0x80080000 (PA) или 0xffffff8008080000 (VA).Этап 2: ищем адрес таблицы kallsyms
Теперь нам необходимо найти таблицу символов ядра (kallsyms). Эта таблица содержит адреса всех экспортируемых функций и переменных ядра, что позволяет обнаружить адреса требуемых для проведения атаки объектов.Таблица kallsyms обычно располагается в секции .rodata и имеет характерную структуру:
- массив адресов символов;
- массив имен символов;
- индексная таблица;
- таблица типов символов.
В нашем случае необходимо найти следующие критически важные для проведения атаки символы:
- sys_call_table — таблица системных вызовов;
- call_usermodehelper — функция запуска пользовательских процессов из ядра;
- selinux_enforcing — флаг состояния SELinux.
Для просмотра ссылки Войди
Этап 3: выбираем системный вызов для перехвата
Таблица системных вызовов (sys_call_table) — ключевой элемент для атаки. В нашей системе она расположена по адресу 0x809D2000 (PA) или 0xffffff80089d2000 (VA). Эта таблица представляет собой массив указателей на функции — обработчики системных вызовов.Каждый элемент таблицы соответствует определенному системному вызову, идентифицируемому номером. Номера фиксированы для каждой архитектуры, и для ARM64 они Для просмотра ссылки Войди
Нас интересует системный вызов getpriority с номером 141 (0x8d). Соответствующий элемент в таблице находится по смещению 141 * 8 = 0x468 = 1128 байт. Значит, указатель на обработчик getpriority расположен по адресу 0x809D2468 (PA). Почему именно getpriority? Мы выяснили, что программы в Android время от времени вызывают эту функцию, но при этом не слишком часто. А это в теории позволит избежать проблем, связанных с гонками и многократными вызовами нашего кода при перехвате этого вызова.
Этап 4: ищем адрес функции call_usermodehelper
Получение адреса функции call_usermodehelper — несложная задача после того, как обнаружена и реконструирована таблица символов ядра (kallsyms). В нашей системе эта функция располагается по адресу 0xffffff80080bfe00. Этот механизм позволяет драйверам ядра запускать пользовательские процессы, и именно его мы будем использовать для загрузки, установки и запуска APK-файла с Doom.Этап 5: отключаем SELinux
Для успешной атаки необходимо временно (или постоянно!) отключить SELinux, так как в противном случае он будет мешать запуску пользовательских процессов с помощью User Mode Helper. Поэтому мы ищем в таблице символов ядра адрес глобальной переменной selinux_enforcing. Если установить ее в 0, можно отключить принудительное применение политик SELinux.Этап 6: ищем область для внедрения кода
Теперь нам нужно найти подходящее место в памяти ядра для размещения нашего шелл‑кода. То есть найти неиспользуемую «дырку» в секции кода ядра, где наши инструкции не нарушали бы работу системы. У нас секция кода занимает область 0x80080800–0x809d0000 и имеет размер 0x94f800 байт. Путем статического анализа мы нашли подходящую по размеру область свободной памяти по адресу 0x809cb000 — близко к концу секции кода ядра. Так как память выделяется страницами, в конце секций часто можно найти свободное пространство, использование которого никак не повлияет на работоспособность.Этап 7: создаем и внедряем шелл-код
Задача шелл‑кода состоит в том, чтобы выполнить несколько команд в пространстве пользователя. Так, для запуска установленного приложения можно использовать activity manager (am):/system/bin/am start -n com.eltechs.originaldoom/.doomDemo.DoomDemo
Перед этим шелл‑код должен выполнить ряд важных действий:
- защита от повторного выполнения;
- сохранение контекста процессора;
- настройка переменных окружения, необходимых для работы package/activity manager;
- вызов оригинального обработчика getpriority.
Для просмотра ссылки Войди
В коде также содержится таблица переменных окружения, нужная для корректной работы пользовательских процессов вроде activity manager или пакетного менеджера.
Для просмотра ссылки Войди
Этап 8: меняем таблицу системных вызовов
Финальный шаг — замена указателя на обработчик системного вызова getpriority в таблице sys_call_table адресом нашего шелл‑кода, предварительно размещенного в свободной области в секции кода ядра. После успешной модификации наш код будет автоматически выполнен при следующем вызове getpriority. Поскольку этот сискол периодически используется разными компонентами Android, активация произойдет скоро и без дополнительного вмешательства.В конце работы наш шелл‑код восстанавливает состояние регистров процессора и вызывает изначальный getpriority.
Для просмотра ссылки Войди
В результате работы всей цепочки эксплоитов мы получили возможность исполнить свой код на стороне АР.
Для просмотра ссылки Войди
Теперь мы можем с уверенностью сказать, что головные устройства некоторых современных автомобилей могут запускать Doom. При этом весь процесс, как и было задумано, происходит без участия пользователя и обходит стандартные механизмы безопасности Android и ядра Linux благодаря прямому доступу к памяти.
Заключение
Для просмотра ссылки ВойдиЭксплуатация всего лишь одной уязвимости на стороне СР позволила нам открыть «god mode» во всей SoC. Конкретную уязвимость можно исправить программно, но обнаруженные проблемы микроархитектурного уровня исправить можно будет только в следующих партиях SoC. Да и только ли в этой SoC есть такая аппаратная «фича»?
Захватив SoC, злоумышленник может не только контролировать информационный поток между устройством и внешним миром, но и получить практически неограниченный доступ к наиболее важным компонентам конечного устройства.
Например, при компрометации SoC, используемой в автомобиле, злоумышленник, конечно же, не будет ограничен установкой Doom. Он может получить удаленный доступ к пользовательским данным, например к записи голоса через встроенный микрофон, или вообще развить атаку дальше — на подключаемые мобильные устройства, используя уязвимости в сервисах CarPlay и AndroidAuto. Кроме того, в случае ошибки в конфигурации бортового шлюза шины CAN он может получить возможность удаленного воздействия на другие автомобильные блоки.
Проблему усугубляет то, что при обнаружении серьезной уязвимости в CP может понадобиться значительное время на обновление всех устройств, в которых установлен тот же чип. В каких‑то устройствах удаленное обновление может быть вообще не заложено как функция. Тогда установка обновления требует дополнительных усилий и затрат со стороны производителя — каждое устройство придется обновлять вручную.
