Чип-тюнингкурс с нуля

Модуль 09. Реверс прошивки: Ghidra + ИИ-ассистент#

Уровень 4. Этот модуль нужен, когда описаний нет, автопоиск дал мусор, а карту найти надо. Он же отличает калибровщика от «прошивальщика» окончательно.

Задача НЕ в том, чтобы понять весь код ЭБУ. Задача узкая: найти адреса карт и понять, что именно они делают.


09.1 Что такое Ghidra и почему она#

Ghidra — бесплатный дизассемблер и декомпилятор от NSA. Берёт машинный код и показывает его как читаемый псевдо-C. Для нас важны три возможности:

  1. Декомпилятор — вместо ассемблера видите C-подобный код.
  2. Xrefs (перекрёстные ссылки) — «кто вызывает эту функцию», «кто читает этот адрес». Это главный инструмент.
  3. Скрипты (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 по кнопке круиз-контроля, отключить проверку, вставить свой лимит. Техника:

  1. Найти «пустую» область флеш (заполнена 0xFF).
  2. Написать свой код (на ассемблере целевой архитектуры).
  3. Заменить одну инструкцию в целевом месте на переход (jump) в свой код.
  4. В своём коде выполнить вытесненную инструкцию + свою логику, вернуться назад.

Это классический hook / patch. Требует знания ассемблера конкретной архитектуры и очень аккуратной работы с чек-суммами. Начинать с этого нельзя — это верхний уровень.


Практическое задание#

  1. Установите Ghidra, откройте открытый ROM с известной архитектурой. Определите базовый адрес по таблице векторов.
  2. Найдите функцию с максимумом входящих ссылок. Опишите её словами (сначала сами, потом с ИИ). Совпало ли ваше мнение с ИИ?
  3. Найдите в коде паттерн «последовательность ограничений min()». Выпишите адреса всех участвующих карт — это ваши ограничители.
  4. Найдите функцию контрольной суммы. Определите: какой диапазон адресов она проверяет и где хранится эталон.

Следующий модуль: 10. Логирование и сопоставление

КПЦ — Комплекс Промышленной Цифровизации · kpc-control.ru

Материал носит образовательный характер. Работы, влияющие на состав отработавших газов и экологический класс ТС, а также корректировка показаний одометра курсом не рассматриваются как допустимая практика — см. модуль 00.