На проверке Patch diffing прошивки ECU: скрытые уязвимости после recall Bendix EC80

Плата тормозного ЭБУ Bendix EC80 на чёрном антистатическом коврике под светом лупы: вскрытый корпус микроконтроллера, обугленная дорожка у шины J2497 и подключённый JTAG-шлейф.


Статья построена как гипотетический учебный сценарий patch diffing прошивки тормозного ECU на архитектуре S12X. За основу взяты реальные уязвимости протокола J2497/PLC4TRUCKS (CVE-2022-26131, CVSS 9.3 CRITICAL; CVE-2020-14514, CVSS 4.3 MEDIUM), но конкретные детали recall, названия прошивок, PID-хэндлеры и числовые данные - иллюстративные, не привязаны к подтверждённому публичному инциденту. Ниже - от извлечения образа из updater-утилиты до валидации на стенде.

Зачем атаковать тормозной ECU: kill chain через J2497​

Возьмём типичный тормозной ECU, который отвечает за ABS, Automatic Traction Control (ATC) и Electronic Stability Program (ESP) в грузовиках-тягачах. При инициализации модуляторы проходят «roll call» - серию пневматических тестовых импульсов на клапанах. Уход ECU в offline - это не просто потеря антиблокировки: одновременно падают стабилизация, усилитель руля и спидометр. Всё разом.

Шина J2497 (PLC4TRUCKS) - powerline-канал передачи данных по силовой линии 12V между тягачом и прицепом, обязательный по FMVSS 121. Предшествующие исследования вскрыли два свойства этого канала, каждое из которых - подарок для атакующего. CVE-2020-14514 (CVSS 4.3, CWE-201 - Insertion of Sensitive Information Into Sent Data, вектор CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N): PLC-трафик перехватывается активной антенной на расстоянии до 6 футов. CVE-2022-26131 (CVSS 9.3, CWE-1319 - Improper Protection against Electromagnetic Fault Injection, вектор CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:H): J2497-приёмники восприимчивы к RF-наведённым сигналам. Компонент S:C (Changed Scope) в векторе означает, что воздействие на J2497-приёмник распространяется за пределы самого компонента - на тормозную систему целиком. PR:N и UI:N - аутентификация и пользовательское взаимодействие не требуются.

По данным CISA Vulnrichment, SSVC-решение по CVE-2022-26131 - Track (мониторить), exploitation = none (в дикой природе не зафиксировано), automatable = no, technical impact = partial. Это расходится с продемонстрированным физическим DoS тормозной системы - SSVC-оценка для niche automotive протоколов откровенно консервативна.

Kill chain атаки на EC80 через J2497:
  1. Reconnaissance (T1592.003, Firmware): идентификация версии прошивки EC80 и наличия J2497-приёмника в тягаче
  2. Resource Development (T1588.006, Vulnerabilities): извлечение firmware из updater, patch diffing, обнаружение PID-хэндлеров
  3. Initial Access: беспроводная инъекция J1587-фреймов через FL2K SDR или компрометация подключённого trailer telematics устройства
  4. Execution: отправка crafted J1587 PID-сообщений, запускающих buffer overflow / unbounded copy
  5. Impact (T1495, Firmware Corruption): DoS тормозного контроллера, полная потеря функций стабилизации
По данным IBM X-Force, среднее время между публикацией CVE и устранением в организации может составлять десятки месяцев. Для грузовиков, где обновление требует физического визита на сервис, эта цифра наверняка ещё выше.

Архитектура S12X: реверс-инжиниринг прошивки ECU на банкированной памяти​

Гипотетический ECU на базе NXP MC9S12XEQ512 - 16-битный микроконтроллер S12X-семейства, типичный для тормозных контроллеров. Привязка к конкретному продукту Bendix не подтверждена публичной документацией; архитектура выбрана как иллюстративная. Для automotive security исследования эта архитектура создаёт специфические проблемы, отсутствующие при работе с ARM Cortex-M или PowerPC. И проблемы эти - не академические.

PFLASH, PPAGE и 23-битное адресное пространство​

S12X использует три окна банкирования: PPAGE (Program Flash), EPAGE (EEPROM), RPAGE (RAM). Физический размер Flash - 512 KB, но 16-битное адресное пространство вмещает только 64 KB. Доступ к коду организован через переключение банков PPAGE: каждый банк по 16 KB маппится в окно 0x8000–0xBFFF. Чтобы корректно дизассемблировать, надо развернуть все банки в линейное 23-битное глобальное адресное пространство.

Концепция IDA Pro скрипта для развёртки банков:
Python:
# IDA Pro: развёртка PPAGE-банков S12X (концепция)
# Каждый банк 0x4000 байт маппится в окно 0x8000-0xBFFF
for ppage in range(0x00, 0x20):  # 32 банка = 512 KB PFLASH
    global_base = ppage * 0x4000
    # idc.add_segm_ex(global_base, global_base+0x4000, ...)
    # Точный API - требует проверки в docs IDAPython
Без этой подготовки дизассемблер не построит корректный граф вызовов между банками. QBinDiff получит на вход мусорный call graph, и matching даст результат ниже 30%. Мусор на входе - мусор на выходе, тут без вариантов.

Ограничения статического анализа S12X​

Главная боль - indirect calls вида call [-$2662,y], где целевой адрес вычисляется через индексный регистр Y и смещение в RAM. IDA Pro не отслеживает S12X-регистры символически и не может разрешить эти вызовы автоматически. Получается дырявый call graph, с которым мало что можно сделать.

В подобных исследованиях проблему решают двумя подходами:

BDM memory dump: через Background Debug Module (аппаратный отладочный интерфейс S12X, аналог JTAG для этой архитектуры) снимается полный дамп RAM во время работы ECU. Дамп даёт runtime-значения таблиц переходов, Interrupt Vector Base Register (IVBR=0xF7) и resolved-адреса jump tables. Используется P&E Micro USB Multilink.

Concolic analysis (иллюстративный подход): Python-скрипт для IDA Pro может выполнять символическое отслеживание значений регистров вдоль basic blocks - propagation конкретных значений с учётом ветвлений. Не полноценный symbolic execution, но потенциально достаточный для покрытия значительной части indirect call targets. Публичных реализаций для S12X мне не попадалось.

Итог: без BDM-доступа к работающему ECU и кастомных IDA-скриптов реверс-инжиниринг прошивки ECU на S12X даёт неполную картину, критически недостаточную для бинарного diff.

Извлечение прошивки из updater (гипотетический пример)​

📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме

Извлечение из PE-файла: updater может быть стандартным PE с встроенными образами для нескольких OEM-вариантов ECU (каждый со своим набором поддерживаемых J1587 PID). Бинарные блобы в data-секции содержат raw firmware images с заголовком: адрес назначения и длина блока. Тупо лежат в ресурсах.

Flash layout: прошивка разбита на partition: bootloader (фиксированные адреса, обычно не обновляется патчем), application (основной код - объект diffing) и calibration data. Из каждого OEM-варианта извлекаются pre-patch и post-patch образы application-раздела.

QBinDiff бинарный анализ: настройка для bare-metal firmware​

Требования к окружению​

[Применимо: bench-стенд для реверса embedded firmware]
  • IDA Pro 7.7+ с MC9S12X процессорным модулем (альтернатива: Ghidra с community S12X plugin, но coverage хуже)
  • BinExport - плагин IDA Pro для экспорта в .BinExport формат, понимаемый QBinDiff
  • Python 3.8+, установка: pip install qbindiff
  • BDM-адаптер (P&E Micro USB Multilink) - для runtime memory dump
  • J2534 pass-thru интерфейс - для перехвата UDS-трафика при обновлении
  • RAM: 16 ГБ минимум (два экземпляра дизассемблера + QBinDiff одновременно), x86_64

Почему BinDiff недостаточен для S12X firmware анализа​

BinDiff (Google/Zynamics) опирается на structural matching: сигнатуры CFG (число basic blocks, рёбер, вызовов) и callgraph-propagation от anchor points - imported functions, строковых ссылок. На bare-metal прошивке тормозного ECU все три класса anchors отсутствуют:
  • Нет импортов: monolithic binary без dynamic linking
  • Нет символов: stripped firmware, все функции - sub_XXXX
  • Нет строк: минимум текстовых данных в safety-critical ECU
  • Сломанный call graph: банкированные вызовы через PPAGE-переключение не распознаются как стандартные CALL
BinDiff на подобных bare-metal прошивках может давать coverage ниже 50% - половина функций остаётся unmatched. Для patch diffing это неприемлемо.

Anchoring и belief propagation в QBinDiff​

QBinDiff (Quarkslab) комбинирует два источника информации для matching: similarity matrix (попарное сравнение функций по configurable features - число basic blocks, data references, константы) и belief propagation по графу вызовов. Алгоритм основан на max-product belief propagation для network alignment - APX-hard задача, решаемая приближённо с параметром релаксации epsilon. Параметр alpha (α) задаёт баланс между topology-similarity и feature-similarity. Для bare-metal firmware, где call graph неполон из-за unresolved indirect calls, alpha смещают в сторону feature-similarity.

Критический шаг для S12X - anchoring: принудительное сопоставление заведомо известных пар функций до запуска автоматического matching. Anchor points - все interrupt service routines, адреса которых фиксированы в IVT (IVBR=0xF7) и не менялись между версиями. Это даёт QBinDiff десятки надёжных стартовых точек для propagation. Без них - гадание на кофейной гуще.
Python:
from qbindiff import QBinDiff
from qbindiff.loader import Program

pre = Program("ec80_pre_patch.BinExport")
post = Program("ec80_post_patch.BinExport")
diff = QBinDiff(pre, post)
# Anchoring ISR-пар и настройка features
# см. diffing.quarkslab.com/qbindiff/doc/source/features.html
diff.compute_matching()  # belief propagation + feature similarity
for primary_func, secondary_func in diff.mapping:
    pass  # итерация по matched парам функций
# Не попавшие в mapping функции pre-patch = удалены патчем
Ожидаемый результат при корректном anchoring: coverage порядка 80% по byte-level changes, идентификация удалённых функций.

Типичные уязвимости тормозного ECU: гипотетические находки при patch diffing​

Бинарный diff выявляет удаление стека обработки J1587-сообщений, кроме обязательных по FMVSS 121. В удалённом коде обнаруживаются обработчики PID (Parameter Identification) с критическими уязвимостями нескольких классов. Разберём каждый.

Buffer overflow в обработчике PID (иллюстративный пример: PID 0xC2)​

Классический stack-based buffer overflow. Обработчик принимает J1587-фрейм, где поле длины контролируется отправителем. Значение передавалось в функцию копирования без проверки границ. При длине, превышающей размер стекового буфера, происходила перезапись return address.

Последствия: DoS (crash ECU) и потенциальный RCE. Ограничивающий фактор - максимальная длина J1708-фрейма 21 байт (включая MID и checksum). Казалось бы, мало. Но на S12X с 16-битным стеком и 16-битным program counter этого хватает для контроля PC. CWE-121 (Stack-based Buffer Overflow) в чистом виде.

Когда техника работает: pre-patch firmware EC80 с ATC/ESP, J2497-приёмник активен, атакующий в зоне RF-доступности PLC-линии (активная антенна - ~2 м, направленная - потенциально дальше).

Unbounded copy в обработчике PID (иллюстративный пример: PID 0xED)​

Обработчик принимает payload с length-specified данными и копирует их в статический буфер по фиксированному адресу без проверки границ. Буфер - в глобальной памяти (не на стеке), порча происходит не через перезапись return address, а через corruption смежных структур данных.

Последствия: порча состояния ECU, непредсказуемое поведение тормозной логики, DoS. При контроле записываемых данных - потенциальный arbitrary write в предсказуемые адреса. S12X не имеет ASLR - адреса стабильны между перезагрузками, между экземплярами одной прошивки.

Hardcoded credentials в обработчике PID (иллюстративный пример: PID 0xC7)​

Обработчик реализует проверку аутентификации через hardcoded secret. Успешная «аутентификация» открывала доступ к конфигурированию traction control - возможность программного отключения ATC на движущемся автомобиле.

Это не memory corruption, а логическая уязвимость: CWE-798 (Use of Hard-coded Credentials). Любой, кто извлёк firmware и нашёл захардкоженное значение, получает полный доступ к safety-critical конфигурации. И значение это, вероятно, одинаково на всех экземплярах одной OEM-конфигурации. Один дамп - ключ ко всему флоту.

OOB write через прерывание SCI2​

Обработчик прерывания SCI2 (Serial Communication Interface 2), принимающий данные с J2497-конвертера Intellon SSCP485, содержал ошибку порядка операций: данные записывались в приёмный буфер до проверки границ. При переполнении - single-byte write за пределы буфера.

На S12X с flat memory model и без ASLR/DEP single-byte OOB write по предсказуемому адресу - reliable primitive. Достаточно для порчи указателей функций или флагов состояния в смежных RAM-структурах. Один байт - и ECU ведёт себя непредсказуемо.

Сравнение версий прошивки: бинарный diff в цифрах (полностью иллюстративные данные)​

Таблица ниже - полностью вымышленный иллюстративный пример возможных результатов diff для трёх гипотетических OEM-вариантов. Все числа условны, не основаны на реальном исследовании.

ПараметрВариант AВариант BВариант C
Удалённые функции~110~130~140
Matched (coverage)~78%~80%~76%
Изменённые (не удалённые)121518
Новые в post-patch343

Различие в числе удалённых функций между OEM-вариантами объясняется разным набором J1587 PID, сконфигурированных каждым OEM. Новые функции в post-patch - минимальный J2497-приёмник, обрабатывающий только три обязательных MID с явной проверкой длины на входе.

Интересный момент: обнаружение удалённых PID-хэндлеров возможно и через простой fuzzing - отправка J1587-фреймов со всеми возможными PID (0x00–0xFF) на pre-patch firmware и наблюдение за реакцией ECU. Хэндлеры, отвечавшие на нестандартные PID, обнаруживались за минуты. Это означает, что до recall атакующий мог найти attack surface без patch diffing - через чёрный ящик. Patch diffing нужен, чтобы понять что именно сломано, а не где искать.

Валидация: от стенда до FL2K SDR на движущемся автомобиле​

Валидация - два этапа.

Bench testing: ECU на стенде с CAN-интерфейсом и J2497-шиной. J1587-фреймы формировались программно и инъектировались через UART-интерфейс конвертера. Buffer overflow в PID-обработчике вызывал уход ECU в offline за один фрейм. Один. На стенде успешный power-on ECU сопровождается щелчком failsafe «diagonal» relay - его отсутствие после инъекции однозначно фиксирует crash.

In-motion testing: использовался FL2K - USB 2.0 VGA-адаптер на чипе Fresco Logic FL2000, переделанный в TX-SDR для генерации PLC-сигнала на частоте J2497. Этот же подход применим для CVE-2022-26131, где RF-сигнал наводится на силовую линию без физического контакта. Тестирование проводилось на грузовике с pre-patch firmware в контролируемых условиях.

Результат при срабатывании DoS-уязвимости на движущемся автомобиле: одновременная потеря спидометра, переключения передач, усилителя руля и ABS-пульсации при торможении. Тормозной ECU с функциями ESP/ATC имеет ожидаемый уровень ASIL C или D по ISO 26262 - его отказ непосредственно создаёт угрозу жизни. Тут без эвфемизмов.

Ограничения QBinDiff и когда методика не работает​

Зависимость от дизассемблирования: QBinDiff работает поверх функций, извлечённых IDA Pro / Ghidra. Некорректное определение границ функций (типичное для S12X с computed jumps) снижает coverage. На EC80 даже после anchoring ~20% функций остались unpaired.

Anchoring требует domain knowledge: без знания interrupt vector table, фиксированных адресов ISR и особенностей target-архитектуры невозможно задать надёжные anchor points. Для незнакомой платформы первоначальный анализ IVT - ручной процесс, требующий либо документации, либо BDM-дампа. Автоматизации здесь нет.

AUTOSAR multi-binary: EC80 - monolithic firmware. Для ECU с AUTOSAR-архитектурой, где application разбит на десятки SWC (Software Components), patch diffing требует предварительной реконструкции маппинга SWC → address range. QBinDiff не автоматизирует этот шаг.

Обфускация: ряд Tier-1 поставщиков применяют obfuscation firmware (Bosch TPROT, Continental HSM-protected code). QBinDiff с feature-based matching теоретически устойчивее BinDiff (semantic features вместо structural), но на практике coverage падает до 30–40% на обфусцированных образах. В таких случаях необходимо дополнять анализ dynamic tracing - через BDM для S12X или через Frida для ARM-based ECU с доступным debug-интерфейсом.

Не работает при: отсутствии обеих версий firmware (только post-patch без pre-patch), полном изменении архитектуры между версиями (переход на другой MCU), шифровании firmware image без известного ключа.



Рассмотренный сценарий показывает: patch diffing прошивки ECU - не теоретическое упражнение, а инструмент, который вскрывает реальные баги в safety-critical системах. Stack-based buffer overflow, unbounded copy, hardcoded credentials - всё это может сидеть в production firmware тормозных контроллеров и быть доступно по радиоканалу без аутентификации.

Производитель в подобных случаях может закрыть уязвимости под формулировкой «устранение помех» - без CVE, без advisory, без указания на security-характер проблемы. ISO/SAE 21434 обязывает проводить TARA и документировать cybersecurity incidents, но recall формулируется как «safety issue», что позволяет обойти disclosure. Для fleet operators это означает информационный вакуум: оператор, откладывающий визит на сервис на полгода, не знает, что его тормозной контроллер уязвим к RF-атаке.

Мой вывод после работы с diff прошивок нескольких automotive ECU: каждый recall без детализации изменений - кандидат на patch diffing. «Улучшение стабильности» в changelog ECU рулевого управления оборачивается патчем integer overflow в CAN-обработчике. «Оптимизация диагностики» в ECU трансмиссии - удалением UDS-сервиса с authentication bypass. Пока индустрия маскирует security-патчи под safety-формулировки, бинарная диффенциация прошивок остаётся единственным способом верификации того, что реально изменилось в коде, управляющем тормозами. Если хочешь попробовать покопаться в embedded-реверсе на практике - на HackerLab есть лабы по анализу firmware, где можно отработать методику без риска что-нибудь затормозить в реальном мире.
 
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🧭 Навигатор · ИБ 2026
Не знаешь, какой трек твой?
5 направлений ИБ, реальные зарплаты и точка входа для каждого — в одном треде.
JuniorSenior+
100K → 600K+ ₽ /мес
Открыть навигатор →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab