Модуль 09. Реверс прошивки: Ghidra + ИИ-ассистент#
Уровень 4. Этот модуль нужен, когда описаний нет, автопоиск дал мусор, а карту найти надо. Он же отличает калибровщика от «прошивальщика» окончательно.
Задача НЕ в том, чтобы понять весь код ЭБУ. Задача узкая: найти адреса карт и понять, что именно они делают.
09.1 Что такое Ghidra и почему она#
Ghidra — бесплатный дизассемблер и декомпилятор от NSA. Берёт машинный код и показывает его как читаемый псевдо-C. Для нас важны три возможности:
- Декомпилятор — вместо ассемблера видите C-подобный код.
- Xrefs (перекрёстные ссылки) — «кто вызывает эту функцию», «кто читает этот адрес». Это главный инструмент.
- Скрипты (Python/Java) — можно автоматизировать извлечение списка карт.
Альтернатива — IDA Pro (лучше поддержка редких архитектур, но дорого).
09.2 Подготовка: три вещи, без которых ничего не работает#
1. Процессорная архитектура#
| ЭБУ | MCU | Поддержка в Ghidra |
|---|---|---|
| Bosch ME7, EDC15, EDC16 | Infineon C167 / ST10 | Через сторонний модуль |
| Bosch MED17, EDC17, MD1/MG1 | Infineon TriCore | Через сторонний модуль (ghidra-tricore и др.) |
| Denso (Subaru, Toyota) | Renesas SH7055 / SH7058 / SH72xx | Есть в Ghidra штатно (SuperH) |
| Siemens/Continental SIMOS | Infineon TriCore / C167 | Модуль |
| Ford, некоторые Siemens | Freescale MPC5xx (PowerPC) | Штатно |
| Современные (2015+) | Renesas RH850 / V850 | Модуль |
| Delphi | Motorola/NXP HC12, MPC | Частично штатно |
Практический совет для обучения: начинайте с Denso SH7058 (Subaru). Архитектура поддерживается Ghidra из коробки, файлы открыты, и всё сообщество RomRaider можно использовать для самопроверки.
2. Базовый адрес загрузки#
Бинарник — это образ памяти, и он начинается не с нуля. Если задать неверную базу, все ссылки будут «в никуда».
Как найти:
- Таблица векторов прерываний — обычно в самом начале файла:
массив 32-битных значений, похожих друг на друга старшими байтами.
00000000 00 00 10 00 00 00 20 44 00 00 20 44 00 00 20 44
^^^^^^^ похоже на адреса в диапазоне 0x00001000-0x00002xxx
- Reset vector — первое значение обычно указывает на точку входа.
- Строки/указатели — найдите ASCII-строку, найдите 32-битные
значения, указывающие на неё; разность даст смещение базы.
- Документация на MCU — где начинается флеш (например, у TriCore
часто 0x80000000, у SH7058 0x00000000).
3. Разбор памяти#
Настройте в Ghidra блоки памяти: FLASH (код+данные, RO), RAM (переменные, RW), периферия. Без разделения RAM/FLASH декомпилятор путает переменные с константами.
09.3 Ключевой приём: функция интерполяции#
Это 80 % ценности всего модуля.
В любом ЭБУ есть небольшой набор универсальных функций доступа к картам:
// Псевдокод типичной библиотеки Bosch
uint16 kl_lookup (const uint8 *table, uint16 x); // 2D, 8-бит
uint16 kf_lookup (const uint8 *table, uint16 x, uint16 y); // 3D, 8-бит
uint16 kf_lookup16 (const uint16 *table, uint16 x, uint16 y); // 3D, 16-бит
Эти функции вызываются сотни раз во всей прошивке. Найдя одну из них, вы получаете список всех карт целиком — через xrefs.
Как её найти#
Признаки функции интерполяции в декомпиляторе: - принимает указатель и 1–2 числовых аргумента; - первым делом читает 1–2 байта по указателю — это размерности (кол-во точек по X и Y); - дальше идёт цикл поиска по возрастающей последовательности (поиск позиции на оси); - дальше — умножение разностей и деление (собственно интерполяция); - очень много входящих ссылок (xrefs) — сотни.
Практический порядок:
1. Автоанализ Ghidra → список функций.
2. Отсортировать по количеству входящих ссылок (Window → Function Call Trees
или скрипт).
3. Верхние 10 функций — кандидаты: там будут интерполяторы, работа
с таймерами, чтение АЦП.
4. Открыть декомпилятор, найти ту, что похожа на описание выше.
Структура таблицы в памяти (типично для Bosch)#
[адрес карты]
+0 : uint8 nX — количество точек по X
+1 : uint8 nY — количество точек по Y (для 3D)
+2 : оси или указатели на оси
+... : данные nX × nY
Разные производители — разные варианты (оси отдельно, размерности зашиты в код, отдельная структура-дескриптор), но идея та же.
Извлечение всего списка#
Написав простой Ghidra-скрипт, который проходит по всем xrefs интерполятора и вытаскивает значение первого аргумента (адрес карты), вы за минуту получаете полный каталог карт прошивки:
0x0081A400 16x12 uint8 вызвана из FUN_00812340
0x0081A5C0 16x12 uint8 вызвана из FUN_00812340
0x0081A780 8x8 uint16 вызвана из FUN_00815AB0
...
Дальше остаётся понять, что каждая делает.
09.4 Как понять назначение карты по коду#
Смотрите не на карту, а на то, что происходит с результатом:
// Пример декомпилированного фрагмента
uVar1 = kf_lookup(&DAT_0081A400, nmot, rl); // ← наша карта
uVar2 = kl_lookup(&DAT_0081B200, tans); // коррекция по температуре воздуха
uVar3 = kl_lookup(&DAT_0081B240, twat); // коррекция по температуре ОЖ
iVar4 = uVar1 + uVar2 + uVar3 - knock_retard; // ← вычитается откат детонации!
if (iVar4 > 0x50) iVar4 = 0x50; // clamp
DAT_RAM_ignition_angle = iVar4; // ← записывается в переменную
Из этого фрагмента вы узнали:
- 0x0081A400 — основная карта УОЗ, оси (обороты × наполнение);
- 0x0081B200 и 0x0081B240 — коррекции УОЗ по IAT и ОЖ;
- есть верхний clamp 0x50 — вот почему ваша правка карты не давала
эффекта выше определённого значения;
- откат детонации вычитается после — значит правка карты не защитит
от отката.
Это качественно другой уровень понимания, чем «нашёл красивую таблицу в WinOLS».
Опорные точки для поиска смысла#
| Ищем | По чему находим |
|---|---|
| Переменная оборотов | Читается сотнями функций, обновляется в обработчике прерывания от ДПКВ |
| Переменная λ | Рядом с чтением АЦП канала зонда, участвует в PI-регуляторе |
| Управление форсункой | Пишет в регистры таймера захвата/сравнения |
| УОЗ | Пишет в регистр таймера, участвует в вычитании отката детонации |
| Наддув | PID-регулятор с целевым значением и ШИМ на выходе |
| Ограничители момента | Много вызовов min() подряд над разными картами — очень характерный паттерн |
| Чек-сумма | Цикл по большому диапазону адресов + сравнение |
| Диагностика/DTC | Функция с параметром-номером ошибки, вызываемая отовсюду |
Паттерн арбитража момента узнаётся мгновенно:
tq = tq_driver;
if (tq > lim_gear) tq = lim_gear;
if (tq > lim_temp) tq = lim_temp;
if (tq > lim_clutch) tq = lim_clutch;
if (tq > lim_max) tq = lim_max;
Нашли это — нашли все ограничители момента разом. См. модуль 02.5.
09.5 ИИ-ассистент: как использовать правильно#
Современные модели хорошо читают декомпилированный C и хорошо распознают паттерны управления двигателем. Это ускоряет работу в разы. Но у метода есть жёсткие границы.
Что ИИ делает хорошо#
| Задача | Пример запроса |
|---|---|
| Объяснить назначение функции | «Вот декомпилированная функция из прошивки ЭБУ бензинового мотора Bosch. Опиши, что она делает, и предположи назначение каждого аргумента и каждой таблицы» |
| Опознать паттерн | «Является ли этот фрагмент функцией билинейной интерполяции по таблице? Если да, опиши формат таблицы в памяти» |
| Предложить имена | «Переименуй переменные в осмысленные имена в терминах управления ДВС» |
| Написать Ghidra-скрипт | «Напиши Ghidra Python-скрипт, который найдёт все xrefs на функцию по адресу X и выведет значение первого аргумента, если это константа» |
| Разобрать алгоритм чек-суммы | «Что вычисляет этот цикл? Приведи эквивалент на Python» |
| Предположить множители | «Значения карты raw 0..255, оси — обороты и наполнение. Какие factor/offset типичны для карты УОЗ у Bosch?» |
| Помочь с архитектурой | «Как настроить Ghidra для TriCore? Какой базовый адрес типичен?» |
Что ИИ делает плохо — и где вас подведёт#
❌ Выдумывает адреса. Модель может уверенно сказать «карта УОЗ по адресу 0x8021C400». Она этого не знает. Проверяйте всё.
❌ Выдумывает множители. «factor 0.75, offset −24» — правдоподобно, но конкретно для вашей прошивки может быть неверно. Проверяется только на машине.
❌ Путает семейства ЭБУ. Знания об именах DAMOS у неё общие, привязка к вашей конкретной ревизии — нет.
❌ Не видит контекста всей прошивки. Она читает то, что вы дали.
Рабочая методика: ИИ как ускоритель, не как источник истины#
1. ВЫ находите функцию в Ghidra
2. ВЫ копируете декомпилированный код
3. ИИ объясняет и предлагает гипотезы + имена
4. ВЫ проверяете гипотезу в Ghidra (xrefs, соседний код)
5. ВЫ проверяете правкой на машине (модуль 08, метод 6)
6. Только теперь это знание попадает в вашу базу
Правило: ИИ даёт вам гипотезы быстрее. Проверять их всё равно вам. Незаписанное в машину и не проверенное логом — не факт, а мнение.
Хороший промпт vs плохой#
❌ Плохо:
«Найди карту УОЗ в прошивке Bosch MED17.5»
Модель не видела ваш файл. Ответ будет выдуманным.
✅ Хорошо:
«Ниже — декомпилированная Ghidra функция из прошивки бензинового ЭБУ Bosch, архитектура TriCore. Она вызывается 340 раз из разных мест. 1) Опиши, что делает функция построчно. 2) Является ли она обобщённой процедурой доступа к калибровочной таблице? Если да — опиши предполагаемый формат таблицы в памяти (где размерности, где оси, где данные). 3) Что означает каждый аргумент? Не додумывай адреса — работай только с приведённым кодом. [код]»
✅ Хорошо:
«Вот фрагмент кода. Результат
kf_lookup(&DAT_x, a, b)складывается с двумя другими lookup'ами, из суммы вычитается переменная, которая инкрементируется в обработчике датчика детонации, и результат ограничивается сверху константой 0x50. Какая величина, скорее всего, вычисляется? Обоснуй.»
09.6 Пошаговый практический маршрут#
День 1 Установить Ghidra. Открыть Subaru SH7058 ROM (архитектура
поддерживается штатно). Задать SuperH SH-2A, big-endian, база 0.
Запустить автоанализ. Посмотреть, что получилось.
День 2 Найти функцию с наибольшим числом входящих ссылок.
Прочитать её в декомпиляторе. Скормить ИИ. Понять, интерполятор
ли это.
День 3 Написать (или попросить ИИ написать) скрипт, извлекающий все
адреса карт через xrefs интерполятора.
День 4 Сравнить полученный список с открытыми определениями RomRaider.
Сколько совпало? Это ваша объективная оценка.
(Именно поэтому мы начали с Subaru — есть с чем сверяться.)
День 5+ Перейти на ЭБУ, для которого определений нет. Повторить.
Верифицировать правками на машине.
09.7 Когда реверс не нужен#
Будьте прагматичны. Ghidra нужна, если: - определений нет и не будет; - надо изменить поведение, а не число (map-switch, отключение функции, обход ограничения, вставка своей логики); - надо найти алгоритм чек-суммы; - надо понять, почему правка карты не даёт эффекта (clamp/коррекция); - вы хотите делать то, что не умеют конкуренты.
Не нужна, если: - есть DAMOS/XDF; - задача типовая и решается за 20 минут известным способом; - клиент платит 8000 ₽, а реверс займёт 3 дня.
09.8 Изменение кода (кратко)#
Иногда нужно не число, а поведение: добавить map-switch по кнопке круиз-контроля, отключить проверку, вставить свой лимит. Техника:
- Найти «пустую» область флеш (заполнена 0xFF).
- Написать свой код (на ассемблере целевой архитектуры).
- Заменить одну инструкцию в целевом месте на переход (
jump) в свой код. - В своём коде выполнить вытесненную инструкцию + свою логику, вернуться назад.
Это классический hook / patch. Требует знания ассемблера конкретной архитектуры и очень аккуратной работы с чек-суммами. Начинать с этого нельзя — это верхний уровень.
Практическое задание#
- Установите Ghidra, откройте открытый ROM с известной архитектурой. Определите базовый адрес по таблице векторов.
- Найдите функцию с максимумом входящих ссылок. Опишите её словами (сначала сами, потом с ИИ). Совпало ли ваше мнение с ИИ?
- Найдите в коде паттерн «последовательность ограничений min()». Выпишите адреса всех участвующих карт — это ваши ограничители.
- Найдите функцию контрольной суммы. Определите: какой диапазон адресов она проверяет и где хранится эталон.
Следующий модуль: 10. Логирование и сопоставление