13 ноября 2025
God Mode On: исследователи запустили Doom на головном устройстве автомобиля, удаленно атаковав его модем

Эксплуатация уязвимости, обнаруженной в модеме, который устанавливается в головное устройство некоторых автомобилей, позволила экспертам Kaspersky ICS CERT получить полный контроль над системой.
Введение
Представьте себя водителем, который мчится по автостраде за рулем своего новенького электромобиля. И вдруг видит, что вместо карты с маршрутом или меню управления различными функциями на весь огромный мультимедийный дисплей запускается Doom – культовый 3D-шутер всех времен и народов, – и осознает, что кто-то в эту игру прямо сейчас пытается играть, управляя персонажем удаленно. Это не сон и не разгулявшаяся фантазия, а вполне достижимая в современных реалиях ситуация, что более чем наглядно продемонстрировали эксперты Kaspersky ICS CERT.
Мы знаем, что интернет вещей играет важную роль в современном мире, где к Сети подключены не только смартфоны и ноутбуки, но и заводы, автомобили, поезда, и даже самолеты. Большую часть времени подключение осуществляется через мобильные сети передачи данных 3G/4G/5G с помощью модемов, установленных в этих транспортных средствах и устройствах. При этом все чаще модемы интегрируются в систему на кристалле (System on Chip, SoC), которая способна выполнять одновременно несколько функций, используя модемный процессор (Communication Processor, CP) и процессор приложений (Application Processor, AP). Операционная система общего назначения, такая как Android, вполне может работать на AP, в то время как CP, предназначенный для взаимодействия с мобильной сетью, обычно реализуется на базе специализированных ОС. При этом взаимосвязь между AP, CP и RAM на этом кристалле на уровне микроархитектуры представляет собой «черный ящик» и известна только производителю, хотя от этого зависит безопасность всей SoC.
Считается, что обход механизмов безопасности 3G/LTE – это исключительно академическая задача, поскольку при подключении пользовательского устройства (User Equipment, UE) к базовой станции сотовой связи (Evolved Node B, eNB) устанавливается безопасный канал связи. Даже если кто-то сможет обойти эти механизмы, обнаружить уязвимость в модеме и выполнить на нем свой код, это скорее всего не поставит под угрозу бизнес-логику устройства. Эта логика (например, пользовательские приложения, история браузера, звонки и SMS на смартфоне) находится на АР и предположительно не может быть доступна с модема. Или может?
Чтобы выяснить это, мы провели исследование безопасности современной SoC Unisoc UIS7862A, оснащенной встроенным 2G/3G/4G-модемом. Такую SoC можно встретить в китайских и отечественных мобильных устройствах или, что более интересно, в головных устройствах современных китайских автомобилей, которых на дорогах страны становится все больше. Головное устройство – один из ключевых объектов автомобиля, и нарушение его информационной безопасности ставит под угрозу не только сохранность пользовательских данных, но и безопасность дорожного движения.
В ходе своего исследования мы обнаружили несколько критических уязвимостей на разных уровнях стека сотовых протоколов модема Unisoc UIS7862A. В этой статье речь идет об уязвимости переполнения стека в реализации протокола 3G RLC (CVE-2024-39432). Эта уязвимость может быть использована для удаленного выполнения кода на ранних этапах подключения, причем до активации каких-либо защитных механизмов.
При этом получение возможности выполнения своего кода на модеме – лишь точка входа для полной удаленной компрометации всего SoC. Наши дальнейшие усилия были направлены на получение доступа к AP. Мы обнаружили несколько способов получить такой доступ, в том числе с использованием аппаратной уязвимости в виде скрытого периферийного DMA-устройства (Direct Memory Access) для выполнения lateral movement внутри SoC. Это позволило нам установить собственный патч к работающему ядру Android и выполнить произвольный код на AP с наивысшими привилегиями. Подробности представлены в соответствующих разделах.
Получение встроенного программного обеспечения модема
Модем, ставший предметом нашего исследования, мы обнаружили на плате головного устройства одного китайского автомобиля.

|
Номер на фото |
|
|
1 |
Контроллер Realtek RTL8761ATV 802.11b/g/n 2.4G с интерфейсами Wireless LAN (WLAN) и USB (стандарты USB 1.0/1.1/2.0) |
|
2 |
Микросхема SPRD UMW2652 BGA WiFi |
|
3 |
Микросхема 55966 TYADZ 21086 |
|
4 |
Радиочастотный трансивер SPRD SR3595D (Unisoc) |
|
5 |
Видеодекодер Techpoint TP9950 |
|
6 |
UNISOC UIS7862A |
|
7 |
Внутренний накопитель BIWIN BWSRGX32H2A-48G-X, Package200-FBGA, ROM Type – Discrete, ROM Size – LPDDR4X, 48G |
|
8 |
Карта памяти SCY E128CYNT2ABE00 EMMC 128G/JEDEC |
|
9 |
Контроллер питания SPREADTRUM UMP510G5 |
|
10 |
Чип-шунт FEI.1s LE330315 USB2.0 |
|
11 |
Синхронный понижающий DC-DC преобразователь с внутренней компенсацией SCT2432STER |
Используя сведения об аппаратном устройстве модема, мы выпаяли и считали встроенную мультимедийную карту памяти, содержащую полный образ его операционной системы. Затем мы проанализировали полученный образ.
Удаленный доступ в модем (CVE-2024-39431)
Исследуемый модем, как и любой современный модем, реализует нескольких стеков протоколов: 2G, 3G, LTE. Очевидно, чем больше протоколов поддерживает устройство, тем больше потенциальных точек входа (векторов атаки) оно имеет. При этом чем ниже в стеке сетевой модели OSI находится уязвимость, тем серьезнее последствия ее эксплуатации, поэтому мы решили провести анализ механизмов фрагментации пакетов данных на уровне доступа к среде передачи (протокол RLC).
Нас заинтересовал именно этот протокол, потому что он используется для установки безопасного зашифрованного канала передачи данных между базовой станцией и модемом, по нему, в частности, передаются данные протокола NAS (Non-Access Stratum). Таким образом, наличие уязвимости удаленного выполнения кода (Remote Code Execution, RCE) позволяет исполнить свой код на модеме в обход всех существующих механизмов защиты коммуникации в 3G.

Протокол RLC использует три различных режима передачи: TM, UM и AM. Нас интересует только режим UM (Unacknowledged Mode), потому что в нем стандарт 3G предусматривает как сегментацию данных, так и конкатенацию нескольких небольших фрагментов высокоуровневых данных (Protocol Data Unit, PDU) в один фрейм канального уровня. Это сделано для максимальной утилизации канала передачи. На уровне RLC пакеты называются блоками служебных данных (Service Data Unit, SDU)1.
Чтобы найти в прошивке функции обработки SDU среди огромного количества различных функций (примерно 75 000), достаточно поискать константы, используемые в процессе работы (0х7FFF, 0x7FFC, 0x7FFB). Среди всех мест, где встречаются эти константы, мы выделяем те, в которых они осмысленно применяются в контексте исполнения кода. В итоге получаем ограниченный набор функций, включая функцию обработки входящего SDU-пакета.
При обработке принятого SDU-пакета происходит разбор полей его заголовка. Сам пакет состоит из обязательного заголовка, необязательных заголовков и данных. Обработка необязательного заголовка заключается в последовательном проходе по каждому полю. Для этого используется функция get_data_offset. Она разбирает необязательные заголовки, собирает размеры фрагментов данных внутри части данных и возвращает указатель на данные внутри SDU-пакета. Важно: информация о размере всех конкатенированных PDU внутри SDU, согласно анализу кода операционной системы модема, хранится на стеке!

Внутри алгоритм последовательно обрабатывает каждое поле заголовка. Признаком конца необязательных заголовков служит младший бит (E bit), равный 0. Если же он равен 1, то обработка продолжается. В процессе обработки данные записываются в переменную, находящуюся на стеке вызывающей функции. При этом количество необязательных заголовков не ограничено. Глубина стека составляет 0хВ4 байт. Размер пакета, который можно парсить (количество заголовков, каждый заголовок – это запись 2 байт на стек), ограничен размером SDU-пакета в 0х5F0 байт.

В итоге эксплуатация возможна всего лишь одним пакетом, в котором количество заголовков превышает глубину стека (90 заголовков). Важно отметить, что именно в этой функции стековая канарейка отсутствует, и при переполнении стека как раз в этой функции можно подменить адрес возврата и значения некоторых non-volatile регистров. Однако подмена возможна только на значение, оканчивающееся на единицу в бинарном виде (младший бит равен 1). Примечательно, что исполнение происходит на ARM в режиме Thumb, поэтому все адреса возврата должны иметь младший бит, равный 1. Совпадение? Возможно.
Так или иначе, первая же отправка dumm-пакета SDU с соответствующим количеством «правильных» заголовков привела к перезагрузке устройства. Однако на тот момент у нас не было возможности получить информацию о том, где и почему произошло падение (хотя мы предполагаем, что причиной стала попытка передачи управления по адресу 0хААВВССDD, взятому из нашего пакета). Посмотрим, что с этим можно сделать.
Закрепление в системе
Первое и самое важное наблюдение: мы знаем, что указатель на только что полученный SDU-пакет хранится в регистре R2. Можно использовать технику возвратно-ориентированного программирования (Return Oriented Programming, ROP), чтобы выполнить свой код. Однако остается вопрос: можем ли мы вообще исполнить свой код?
Чтобы убедиться в том, что мы действительно можем исполнить свой код на стороне модема, воспользуемся доступным обработчиком АТ-команд. Поскольку нам неизвестен текущий адрес фрейма стека, на котором лежат наши данные, неизвестно, исполняем ли стек вообще, и мы не уверены, доступны ли секции кода для записи (хотя догадываемся, что нет), то самый надежный способ – переписать данные в области RAM-памяти. Для этого ищем подходящую функцию среди доступных АТ-команд. Первая подходящая команда – SPSERVICETYPE.

Далее необходимо использовать ROP-гаджеты, чтобы переписать адрес 0х8CE56218, не нарушая при этом дальнейшую работу алгоритма обработки входящих SDU-пакетов. Для этого достаточно обеспечить возврат в функцию, из которой был вызван обработчик SDU-пакета, потому что она вызывается как callback, а значит, отсутствует связь по данным на стеке. Учитывая, что эта функция добавила на стек всего 0х2С байт, необходимо уложиться в этот размер.

Найдя подходящую ROP-цепочку, мы запускаем SDU-пакет, содержащий ее в качестве полезной нагрузки. В результате в консоли АТ-команды SPSERVICETYPE видим вывод 0xAABBCCDD. Наш код работает!

Далее, по аналогии, мы вводим адрес фрейма стека, на котором находятся наши данные. Однако, к сожалению, оказывается, что стек не является исполняемым. Теперь перед нами стоит задача разобраться с настройкой MPU на модеме. Для этого, снова используя метод ROP-цепочек, генерируем код, который будет читать таблицу MPU по DWORD за раз. Спустя множество итераций мы получаем следующую таблицу.

По таблице видно, что секция кода замаплена, как мы и предполагали, только на исполнение. Попытка изменить настройку приводит к появлению еще одной ROP-цепочки, только теперь в таблице, на неиспользуемом месте, эта же секция замаплена с правами записи. Это возможно благодаря особенностям программирования MPU, а именно наличию механизма overlap и тому факту, что регион с большим ID имеет более высокий приоритет.

Остается воспользоваться указателем на наши данные (он все еще лежит в R2) и пропатчить только что разблокированную на запись секцию кода. Вопрос в том, что именно патчить. Самый простой путь – пропатчить обработчик протокола NAS, добавив в него свой код. Для этого воспользуемся одной из команд протокола NAS – MM information2. С ее помощью можно за один раз переслать большой объем данных, а в ответ получить один байт данных по команде MM status.
В итоге нам удается не только получить возможность исполнения своего кода на стороне модема, но и установить с ним полноценную двухстороннюю связь, используя высокоуровневый протокол NAS в качестве способа доставки сообщений. В данном случае это пакет MM Status с полем cause, равным 0хАА.

Однако исполнение своего кода на стороне модема не позволяет получить доступ к пользовательским данным. Или позволяет?
Lateral Movement внутри SoC
Исследование внутреннего устройства модемного процессора в контексте микроархитектуры SoC выявляет множество потенциальных векторов атак на AP. При анализе внутреннего устройства модема мы сразу заметили, что в нем используются реальные, а не виртуальные адреса RAM. Кроме того, наше внимание привлекли адреса, по которым располагалось ядро операционной системы AP.

Возникает гипотеза: что, если CP и AP используют одно и то же адресное пространство, ведь оперативная память в SoC, вероятно, реализована как единый аппаратный компонент? Анализ Device Tree на стороне AP еще больше укрепил предположение, что физическое адресное пространство у СР и АР, скорее всего, совпадает (сравните с таблицей MPU, представленной выше).

Остается лишь проверить достоверность гипотезы. Для этого нужно снова пропатчить таблицу MPU, на это раз добавив запись, которая разрешает доступ на чтение и запись к памяти, начиная с адреса 0х80000000. В результате нам удается замапить адресное пространство АР в адресное пространство СР. В качестве доказательства концепции мы патчим первую страницу ядра Linux.

Казалось бы, на этом можно остановиться: теперь у нас есть возможность исполнения своего кода не только на модеме, но и на AP. Но остается вопрос: а есть ли другой способ?
В поисках ответа мы решили исследовать доступную аппаратную периферию на стороне модема. Особый интерес для нас представляет контроллер DMA. Анализ кода показывает, что все контроллеры DMA располагаются в области памяти модема, начиная с адреса 0х20000000. Однако любая попытка чтения памяти по этим адресам приводит к DataAbort на уровне аппаратного ядра. В чем причина?
Анализируем код, отвечающий за работу с контроллером DMA. Примечательно, что некоторые его участки, не имеют вызовов, будто их забыли удалить из релизной версии операционной системы модема. Используя фрагменты «забытого» кода, мы получаем доступ к контроллеру DMA. Оказывается, большая часть периферии физически отключена от питания. Для ее включения необходимо выполнить запись в специальный аппаратный регистр.

Помимо включения, для работы контроллера DMA нужно также подать на него тактовый сигнал clock. За это отвечает другой аппаратный регистр в том же регионе памяти.

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

К сожалению, в отличии от таблиц MPU, программное обновление не защищает от этой микроархитектурной особенности. Очевидно, предоставляемые ею возможности позволяют нарушить безопасность AP, имея всего лишь одну уязвимость уровня RCE на модеме.
Разработка эксплойта под AP
После получения возможности извне модифицировать оперативную память AP (либо посредством исполнения кода на модеме и перенастройки его MPU, либо с помощью DMA-периферии) перед нами встает вопрос: как исполнить свой код на AP и закрепиться в его операционной системе, имея лишь возможность читать и писать его память?
Для демонстрации (Proof of Concept) в качестве полезной нагрузки мы пробуем установить и запустить на AP игру Doom. Реализуемая схема выглядит следующим образом: часть полезной нагрузки, работающая на модеме, последовательно локализует ключевые структуры ядра Linux, после чего внедряет в него собственный код для выполнения необходимых действий. Весь процесс должен происходить без участия пользователя и обходить стандартные механизмы безопасности Android.

Этап 1: Поиск базового адреса ядра Linux
Первым шагом атаки является определение базового адреса загруженного в память ядра Linux. В современных версиях Android ядро часто использует технологию рандомизации адресного пространства (Address Space Layout Randomisation, ASLR), тем не менее его базовый адрес легко найти с помощью поиска по известным сигнатурам. В нашем случае это даже не требуется, поскольку базовый адрес ядра всегда равен 0x80080000 (PA) или 0xffffff8008080000 (VA).
Этап 2: Локализация таблицы kallsyms
После обнаружения базового адреса ядра необходимо найти таблицу символов ядра (kallsyms). Эта таблица содержит адреса всех экспортируемых функций и переменных ядра, что позволяет обнаружить адреса требуемых для проведения атаки объектов.
Таблица kallsyms обычно располагается в секции .rodata и имеет характерную структуру:
- массив адресов символов;
- массив имен символов;
- индексная таблица;
- таблица типов символов.
При этом массив адресов символов может использовать относительную адресацию вместо абсолютной для экономии памяти на 64-битных системах, а строки с именами символов могут быть дополнительно сжаты. В нашем случае оба этих механизма (относительная адресация и сжатие имен символов) были использованы, что затрудняло поиск таблицы в памяти ядра. Тем не менее, таблицу удалось найти с использованием эвристического алгоритма, который «собирает» ее по данным, которые считает правильными, и проверяет целостность после сборки.
В нашем случае нужно найти следующие критически важные для проведения атаки символы:
- sys_call_table – таблица системных вызовов;
- call_usermodehelper – функция запуска пользовательских процессов из ядра;
- selinux_enforcing – флаг состояния SELinux.
Для распаковки имен символов ядра воспользуемся следующим кодом.

Этап 3: Выбор системного вызова для перехвата
Таблица системных вызовов (sys_call_table) является ключевым элементом для атаки. В нашей системе она расположена по адресу 0x809D2000 (PA) или 0xffffff80089d2000 (VA). Эта таблица представляет собой массив указателей на функции-обработчики системных вызовов. Каждый элемент таблицы соответствует определенному системному вызову, идентифицируемому номером. Эти номера – фиксированные для каждой архитектуры, для ARM64 их можно посмотреть, например, на Chromium OS Docs.
Нас интересует системный вызов getpriority с номером 141 (0x8d). Соответствующий элемент в таблице находится по смещению 141*8=0x468=1128 байт. Таким образом, указатель на обработчик getpriority расположен по адресу 0x809D2468 (PA).
Этап 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.
Шелл-код представляет собой предварительно скомпилированный позиционно независимый машинный код для архитектуры ARM64, в конце которого размещена таблица указателей на нужные элементы, заполняемая на стороне модема.

В коде также содержится таблица переменных окружения, необходимых для корректного функционирования пользовательских процессов am (Activity Manager), pm (Package Manager) и других.

Этап 8: Модификация таблицы системных вызовов
Финальным шагом является замена указателя на обработчик системного вызова getpriority в таблице sys_call_table на адрес нашего шелл-кода, предварительно размещенного в свободной области в секции кода ядра. После успешной модификации наш код автоматически выполняется при следующем вызове getpriority. Поскольку этот системный вызов периодически используется разными компонентами Android, активация происходит в течение короткого времени без дополнительного вмешательства.
В конце работы шелл-код восстанавливает состояние регистров процессора и вызывает оригинальный системный вызов getpriority.

В результате работы всей цепочки эксплойтов мы получаем возможность исполнить свой код на стороне AP.

Теперь мы можем с уверенностью сказать, что головные устройства некоторых китайских автомобилей годятся для игры в Doom. При этом весь процесс осуществляется удаленно и без участия пользователя, успешно обходя все существующие механизмы безопасности Android и ядра Linux благодаря прямому доступу к памяти.
Заключение

Эксплуатация всего лишь одной уязвимости на стороне модема позволила получить полный контроль (God Mode) над всем SoC. Важно отметить: хотя данная уязвимость может быть исправлена программно, обнаруженные проблемы микроархитектурного уровня возможно исправить только в будущих партиях конкретного SoC. Возникает резонный вопрос: характерна ли эта аппаратная «особенность» только для конкретного типа SoC?
Захватив SoC, злоумышленник получает не только возможность контролировать информационный поток между устройством и внешним миром, но и практически неограниченный доступ к наиболее важным компонентам конечного устройства. При компрометации SoC, используемой в головном устройстве автомобиля, злоумышленник вряд ли ограничится установкой Doom. Он может получить удаленный доступ к пользовательским данным, в частности к записи голоса через встроенный микрофон, или развить атаку на подключаемые мобильные устройства. Более того, в случае ошибки в конфигурации бортового шлюза шины CAN он может получить возможность удаленного воздействия на другие автомобильные блоки3.
Проблема усугубляется еще и тем, что при обнаружении серьезной уязвимости в модеме потребуется значительное время на обновление всех устройств, использующих данный SoC. При этом в некоторых устройствах функция удаленного обновления может быть не предусмотрена. В таком случае установка обновления потребует дополнительных усилий и затрат со стороны производителя конечного устройства, поскольку каждый уязвимый SoC придется обновлять вручную.
- См. стандарт ETSI TS 136 322. ↩︎
- См. стандарт ETSI TS 124 008, стр. 393. ↩︎
- Об угрозах удаленного воздействия на автомобили читайте в материалах «Тренды информационной безопасности современного автомобиля» и «Исследователи безопасности – главный мотиватор автопроизводителей инвестировать в защиту своих продуктов». ↩︎