Статья построена как гипотетический учебный сценарий 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:
- Reconnaissance (T1592.003, Firmware): идентификация версии прошивки EC80 и наличия J2497-приёмника в тягаче
- Resource Development (T1588.006, Vulnerabilities): извлечение firmware из updater, patch diffing, обнаружение PID-хэндлеров
- Initial Access: беспроводная инъекция J1587-фреймов через FL2K SDR или компрометация подключённого trailer telematics устройства
- Execution: отправка crafted J1587 PID-сообщений, запускающих buffer overflow / unbounded copy
- Impact (T1495, Firmware Corruption): DoS тормозного контроллера, полная потеря функций стабилизации
Архитектура 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
Ограничения статического анализа 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
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 = удалены патчем
Типичные уязвимости тормозного 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% |
| Изменённые (не удалённые) | 12 | 15 | 18 |
| Новые в post-patch | 3 | 4 | 3 |
Различие в числе удалённых функций между 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, где можно отработать методику без риска что-нибудь затормозить в реальном мире.