В прошлой части мы немало узнали о внутреннем устройстве прошивки. Но ни грубая сила видеокарты, ни интеллект моих скриптов на Питоне не помогли: алгоритм генерации 64-битного ключа так и остался тайной, а мотоцикл не взломанным. Что же, в этой части нас наконец-то ждет удача, а на десерт предложу разбор прошивки BCM с двойным разоблачением.
Глава 1. Заграница нам поможет
В жизни есть много интересных дел: моделирование в Blender, рыбалка на судака (которую Антон Павлович Чехов считал выше и слаще любви), охота на утку, созерцание моря и поездка за грибами на эндуро. Возможно, за всеми этими делами я бы и забросил попытки взломать мотоцикл, но неожиданно получил письмо из-за границы.
К письму были приложены записи работы дилерского софта Harley Davidson. Это было интересно, но совершенно бесполезно: в записях фигурировали те же самые 64-битные коды. Я было посетовал на это, но мне ответили, что фирменному софту не нужен Интернет для подключения к мотоциклу и рекомендовали поглядеть на Eagle Pro Street Tuner[1].
На каком-то форуме лежал файл SuperTuner11.002.0000.exe, который я не стал устанавливать, а просто распаковал установщик и поглядел, что внутри. Внутри была программа на dotNet и файлы данных Microsoft Access, защищенные паролем.
Поискав имена файлов баз данных внутри файлов дистрибутива, нашел библиотеку EfiTuner.DBAgent.dll, в котором лежал наивный и доверчивый код:
public static class CalibrationsConnection
{
private static IDatabaseHelper m_a;
static CalibrationsConnection()
{
string dataDirectory = NolanDirectoryHelper.GetDataDirectory();
CalibrationsConnection.m_a = DataAccessLayerProvider.CreateDataHelper(
(DataBaseTypes)4,"SuperTuner_Cals.mdb",
dataDirectory, string.Empty, a());
}
public static DataTable ExecuteTable(string query)
{
return CalibrationsConnection.m_a.ExecuteTable(query);
}
private static string a()
{
StringBuilder stringBuilder = new StringBuilder("1G04");
stringBuilder.Append("32o0TH3j270P");
stringBuilder.Remove(8, 4);
stringBuilder.Append("34x");
return stringBuilder.ToString();
}
}
Пароль к базам данных был найден! Открыв базы данных и посмотрев таблички, мне сразу стало жутко интересно, зачем в таблицах нужна колонка Seed. Поискав слово Seed, я нашел файл EfiTuner.VehicleDataService.dll, а в нем вот такие функции:
public IModuleKey GetKeyFromSeed(byte[] seed)
{
return CModuleSecurity.GetKeyValue(seed, this.a);
}
Код GetKeyValue() с именами таблиц
internal static IModuleKey GetKeyValue(byte[] A_0, IVehicleModuleData A_1)
{
int num = 4;
string text = default(string);
IModuleKey result = default(IModuleKey);
while (true)
{
switch (num)
{
default:
if ((text = A_1.Security.Value.Trim().ToUpper()) != null)
{
num = 8;
break;
}
goto case 9;
case 2:
num = 9;
break;
case 8:
num = 3;
break;
case 3:
if (!(text == "LEGACY DLL"))
{
num = 6;
break;
}
result = (IModuleKey)(object)new CModuleKey(a(A_0), (UnlockMethods)0);
num = 1;
break;
case 9:
result = (IModuleKey)(object)new CModuleKey(a(A_0, A_1.Security), (UnlockMethods)0);
num = 7;
break;
case 6:
num = 5;
break;
case 5:
if (text == "MY2011")
{
if (true)
{
}
result = (IModuleKey)(object)new CModuleKey(a(A_0, A_1.Security), (UnlockMethods)1);
num = 0;
}
else
{
num = 2;
}
break;
case 0:
case 1:
case 7:
return result;
}
}
}
Увидев в коде знакомое имя таблицы, я почувствовал, что только что, пользуясь в основном Far Manager и немного ILSpy, я приблизился разгадке алгоритма практически в плотную. Побродив внутри кода, я не увидел никакой криптографии. Не буду приводить его полностью, в целом он выглядит как-то так:
Значительная часть алгоритма генерации пароля
public byte[] LookupTable(int seed)
{
byte[] result = default(byte[]);
try
{
while (true)
{
IL_0027:
string query = "SELECT Key1, Key2 FROM " + this.a + " WHERE Seed = " + seed;
DataRow dataRow = VehicleCommsConnection.ExecuteRow(query);
byte[] array = new byte[2]
{
Convert.ToByte(dataRow["Key1"], CultureInfo.InvariantCulture),
Convert.ToByte(dataRow["Key2"], CultureInfo.InvariantCulture)
};
int num = 0;
while (true)
{
switch (num)
{
case 0:
if (this.a.Equals("MY2006"))
{
num = 3;
continue;
}
result = a(seed, array);
num = 2;
continue;
case 3:
result = array;
num = 1;
continue;
case 1:
goto end_IL_0010;
case 2:
goto end_IL_0010;
}
goto IL_0027;
continue;
end_IL_0010:
break;
}
break;
}
}
catch (Exception)
{
result = null;
}
return result;
}
Если кто-то пожелает восстановить алгоритм, уверен, что много времени это не займет. Правда, за алгоритмом придется тащить 256 килобайт из двух таблиц. Ну или заставить AI найти в этих таблицах какую-то логику. Кто умеет так делать, расскажите, что получилось.
В целом, можно было считать, что защита взломана, если бы не одно: версия программки была старенькой и с новыми мотоциклами не работала.
Глава 2. И снова мы узнаем новое про UDS
Чтобы скачать свежий софт, на сайте Harley Davidson нужно ввести номер своего адаптера (Vehicle Communications Interface, VCI)[2]. К счастью, люди иногда перепродают свои адаптеры, и выкладывают их фотографии со всех сторон. Нам нужен номер с обратной стороны устройства под штрих-кодом. Так я добыл файл StreetTuner23.001.5000.exe.
Распаковав файл, убедился, что базы данных на месте, и даже структура приложения сохранилась, и классы все примерно те же, хотя над файлами поработал обфускатор. Вот так теперь выглядел код, возвращающий пароль к базе данных:
private static string D7hBtsQ3O()
{
while (false)
{
_ = ((object[])null)[0];
}
StringBuilder stringBuilder = new StringBuilder(QT1UFLyg4AHce6xZWQ.vxjIEvsECu(6382));
stringBuilder.Append(QT1UFLyg4AHce6xZWQ.vxjIEvsECu(6396));
stringBuilder.Remove(9, 4);
stringBuilder.Append(QT1UFLyg4AHce6xZWQ.vxjIEvsECu(6420));
return stringBuilder.ToString();
}
Признаюсь, я не стал особо копаться в коде. ILSpy экспортировал мне его в проект Visual Studio C#. И сделал это очень неплохо (пришлось установить все-таки Street Tuner на компьютер). Нужно было почистить какие-то странные не компилируемые конструкции, типа принудительного вызова конструктора базового класса, который (вызывается и так, без принуждения) генерировался с синтаксической ошибкой:
public CRoadtestGetEventDataCommand()
{
while (false)
{
_ = ((object[])null)[0];
}
VGBw0HDkD6ecRxNgrT.tLgadbEzkTrgY();
base._002Ector();
}
Повозился немного, и через час библиотека StreetTuner.VehicleDataService.dll скомпилировалась. Я поменял тип проекта в настройках с библиотеки на исполняемый файл и добавил в класс CVehicleModule метод Main(). И теперь я мог вызывать все что хотел, включая GetKeyFromSeed() или GetKeyValue(). Например, так:
static int Main(string[] args)
{
byte[] seed = { 0xf6,0xfe,0xbe,0x1f,0x11,0xb1,0xa8,0x1f }; // see Part 2, Chapter 3
IVehicleModuleData vehicleModuleData = new VehicleModuleData("41000677", 2016, null);
IModuleKey k = CModuleSecurity.GetKeyValue(seed, vehicleModuleData);
Console.WriteLine(ByteArrayToString(k.KeyValue));
return 0;
}
Убедившись, что могу генерировать ключи, я печально вздохнул: сколько усилий и машинного времени потратил впустую, попросту ничего не зная о программном обеспечении. Дел-то, оказывается, было всего на пару часов!
Ковыряясь в своем “кейгене”, обратил внимание, что в IVehicleModuleData есть TestSets (подгружаются из базы данных при создании экземпляра класса), которые содержат описания идентификаторов данных (DID) сервиса UDS 0x22. Показанным на скриншоте откровением 0xF190 “Vehicle VIN” никого не удивишь, он относится к стандартным.
Но что вы скажите на: Battery Voltage, Throttle Position, Engine Temperature, Intake Air Temperature, Engine Temperature Sensor, Intake Air Temperature Sensor, Throttle Position Sensor 1, Throttle Position Sensor 2, Idle Set Speed, Engine Speed, Spark Advance Rear, Spark Advance Front, Twistgrip Position, Manifold Absolute Pressure Sensor, Twistgrip Sensor 1, Twistgrip Sensor 2, Desired Air/Fuel, Idle Air Control Steps, Manifold Absolute Pressure, Injector Time Front, Injector Time Rear, Front Spark Knock Retard, Rear Spark Knock Retard, Front O2 Sensor Volts, Rear O2 Sensor Volts, Front Closed Loop Integrator, Rear Closed Loop Integrator, Front Adaptive Fuel Value, Rear Adaptive Fuel Value, Vehicle Speed, VE Front, VE New Front, VE New Rear и VE Rear передаваемых в DID 0x200? Хоть садись и свою утилиту пиши. Точно не хуже фирменной будет!
И вот тут можно было бы поставить точку, но я отправил зарубежным друзьям свои находки, а они мне прислали в ответ прошивку BCM.
Глава 3. BCM и NEC/Renesas
Body Control Module (BCM) мы немного обсуждали в предыдущей части. К этому модулю подключена антенна беспроводного ключа. Если ключ находится в зоне доступа или водитель ввел пятизначный PIN-код (пользуясь кнопками поворотников), то модуль BCM подает питание на ECM, разрешая запуск двигателя.
Сердцем модуля служит микроконтроллер Renesas UPD70F3620 в корпусе LQFP100 с ПЗУ 128 кб и 8 кб ОЗУ, а энергонезависимая память 2 кб находится во внешнем чипе 25L160. Ее дамп мне тоже прислали.
Несмотря на то, что микроконтроллер 32-битный, в прошлом году он окончил свой жизненный цикл. Купить его в РФ крайне проблематично, а у китайцев в этом корпусе есть только UPD70F3628, на который найти хоть какой-то даташит в принципе невозможно. На F3620-F3622 документация есть, на все что выше – нет. Отладочных плат и отладчиков тоже нет. Minicube2 давно снят с производства, и купить его можно, похоже, только на AliExpress за $200.
Почувствовав себя дикарем, которому в руки попал артефакт давно исчезнувшей цивилизации, решил, что не буду городить огород и пытаться заказать на Али чип с программатором, разводить под чип плату, и пробовать запустить на ней прошивку. А просто поковыряю ее в Ghidra, благо в ней есть поддержка чипов серии NEC/Renesas V850, к которой и относится наш микроконтроллер.
Микроконтроллер оказался тоже не без сюрпризов. Во-первых, у него есть регистр R0, в котором всегда 0 и в который ничего нельзя записать. Он активно используется в коде, хотя к этому быстро привыкаешь и не обращаешь внимания:
000072a0 25 06 00 00 00 00 mov 0x0, tp
000072a6 24 06 00 d0 ff 03 mov 0x3ffd000, gp
000072ac 23 06 60 ef ff 03 mov 0x3ffef60, sp
000072b2 3e 06 00 d0 ff 03 mov 0x3ffd000, ep
000072b8 20 a6 ff 00 movea 0xff, r0, r20
000072bc 35 06 ff ff 00 00 mov 0xffff, r21
Во-вторых, ПЗУ начинается с адреса 0, что не очень удобно: если в регистр загрузили число 100, это может оказаться и адресом функции, и смещением данных в ПЗУ, и чем угодно.
В-третьих, ОЗУ у него начинается с адреса 0x3FFD000. И, наверное, от 3FFD у нас бы рябило в глазах, но на помощь приходит регистр GP, который используется как база при обращении к памяти. Поэтому Ghidra выдает нам вот такой чудесный код, глядя на который хочется закурить:
switch((uint)(byte)(&DAT_000010a2)[unaff_gp] % 10) {
case 0:
*(int *)(&DAT_000010ac + unaff_gp) = *(int *)(&DAT_000010ac + unaff_gp) + 1;
К счастью, среди читателей предыдущей части оказался автор репозитория с процессорным модулем V850 для Ghidra[3], который подсказал: находим инициализацию регистра GP (см. листинг выше), выделяем весь дизассемблированный код Ctrl+A, нажимаем Crtl+R и указываем значения регистров GP=0x3FFD000, TP=0 и перезапускаем анализ. Код становится читабельным:
switch((uint)DAT_03ffe0a2 % 10) {
case 0:
DAT_03ffe0ac = DAT_03ffe0ac + 1
В процессорном модуле для Ghidra, увы, не было опять регистров CAN. Решив в этот раз не лениться, скачал фирменную среду разработки с загадочным названием “CS+ for CA, CX” (не вру, так прямо и написано в заголовке окна!)[4]. В ней я не обнаружил заголовочных файлов (сюрприз!), зато нашел файл DF3620.800, в котором по смещению 0xdbc8 были все 606 регистров CAN с адресами, начальными значениями и прочими сведениями.
Небольшим скриптом сгенерировал дополнения для процессорного модуля по этому файлу:
<default_symbols>
<symbol name="C0GMCTRL" address="0x03fec000" entry="false">
<symbol name="C0GMCS" address="0x03fec002" entry="false">
<symbol name="C0GMCONF" address="0x03fec004" entry="false">
<symbol name="C0GMABT" address="0x03fec006" entry="false">
<symbol name="C0GMABTD" address="0x03fec008" entry="false">
<symbol name="TFC0GTCNT" address="0x03fec030" entry="false">
...
Обратили внимание, как близко адреса регистров CAN находятся от адресов оперативной памяти? Попробуйте угадать, с какого адреса начинаются регистры SPI ? Не угадаете: 0xFFFFFD00 (CB0CTL0). В общем, много интересных идей посещало разработчиков, приятно иметь дело с творческими людьми. Прочитать руководство и по чипу, и по системе команд снова пришлось. Если и Вам будет любопытно, начните с инструкции CALLT.
Когда мы познакомились с микроконтроллером поближе, Ghidra стала выдавать вполне приятный и читаемый код:
ushort SPI_RW(uint spi_num,ushort write_word)
{
char timeout;
ushort ret_val;
char spi_idx;
timeout = 25;
spi_idx = *(char *)((spi_num & 0xff) * 4 + 0x6a4);
ret_val = 0;
if (spi_idx == 0) {
if (((int)CB0STR & 0x80U) == 0x80) {
do {
timeout = timeout + -1;
if (((int)CB0STR & 0x80U) != 0x80) break;
} while (timeout != 0);
}
ret_val = (ushort)CB0RX;
CB0TX = (char)write_word;
}
else if (spi_idx == 1) {
if (((int)CB1STR & 0x80U) == 0x80) {
do {
timeout = timeout + -1;
if (((int)CB1STR & 0x80U) != 0x80) break;
} while (timeout != 0);
}
ret_val = (ushort)CB1RX;
CB1TX = (char)write_word;
}
return ret_val;
}
Ну, а как найти таблицу сообщений CAN внутри прошивки, мы уже обсуждали в предыдущих частях. Помните, там в CAN ID 0x532 присылали однажды PIN ?
Глава 4. Очень тонкая шутка
В ноябре 2024 появилась статья[5], как же узнать PIN мотоцикла Harley Davidson. Так вот, краткое содержание: разбираем BCM, выпаиваем чип 25L160, снимаем дамп памяти и отправляем его на один из форумов, где собрались гуру, которые знают секрет, как расшифровать и вытащить пин из дампа памяти. Или второй способ: купить их прибор, он умеет это делать. Статью даже на русский перевели[6].
Я как раз случайно набрел на эту статью и сидел глупо улыбаясь, когда в кабинет поднялась жена. Это был тот случай, когда было очень смешно, но смеяться в голос было нельзя: если за три минуты не можешь объяснить шутку, то скорее лишь встревожишь окружающих. Но жене пришлось во всем сознаться.
Существуют заводские широко известные коды 12345 и 31313 для разблокировки мотоциклов Harley Davidson. Мотоциклы приходили с этими кодами с завода. Их, конечно, можно и нужно поменять. И в двух имеющихся у меня дампах EEPROM по смещению 0xAB они прямо торчали, как тополи на Плющихе, без всякого шифрования: в одном 12345, а в другом 31313. Как на заказ.
Но хотелось доказательств. Таблице идентификаторов отправляемых BCM сообщений CAN по адресу 0x44a6 соответствует таблица областей оперативной памяти по адресу 0x3614, используемых для хранения данных сообщений. Из этих таблиц можно сопоставить, что данные сообщения с CAN ID 0x532 (в котором я как-то заметил PIN-код) собираются по адресу 0x3FFDF1C.
Сборка сообщения с CAN ID 0x532
void CAN_532_Fill_Security_Code(void)
{
int iVar1;
int iVar2;
bool bVar3;
int iVar4;
uint uVar5;
uint uVar6;
byte bVar7;
byte pin [5];
byte bStack_1;
iVar4 = 0;
do { /* Fill the PIN buffer with 0x0A */
pin[iVar4] = 0xa;
iVar2 = iVar4 + 1;
iVar1 = iVar4 + -4;
iVar4 = iVar2;
} while (iVar1 < 0 != (iVar2 < 0 && -1 < iVar1) || iVar2 == 5);
bVar7 = 0;
if (DAT_03ffdf14 == 2) {
iVar4 = 0;
do { /* Or copy from 0x3ffd2ab */
uVar5 = (uint)*(byte *)(iVar4 + 0x3ffd2ab);
if (((int)(uVar5 - 1) < 0) || (-1 < (int)(uVar5 - 9) && uVar5 != 9)) {
pin[iVar4] = 0xb;
}
else pin[iVar4] = *(byte *)(iVar4 + 0x3ffd2ab);
iVar2 = iVar4 + 1;
iVar1 = iVar4 + -4;
iVar4 = iVar2;
} while (iVar1 < 0 != (iVar2 < 0 && -1 < iVar1));
bVar3 = true;
if ((int)(DAT_03ffd839 - 5) < 0) bVar7 = DAT_03ffd839 + 1;
}
else {
bVar3 = false;
if (DAT_03ffdf15 == 4) {
uVar5 = 0;
do { /* Or copy from 0x3ffdf16 */
uVar6 = (uint)*(byte *)(uVar5 + 0x3ffdf16);
if ((((int)(uVar6 - 1) < 0) || (-1 < (int)(uVar6 - 9) && uVar6 != 9)) &&
((uVar6 != 0 || (uVar5 != DAT_03ffdf1b)))) {
pin[uVar5] = 0xb;
}
else pin[uVar5] = *(byte *)(uVar5 + 0x3ffdf16);
uVar6 = uVar5 + 1;
iVar4 = uVar5 - 4;
uVar5 = uVar6;
} while (iVar4 < 0 != ((int)uVar6 < 0 && -1 < iVar4));
bVar3 = true;
if (((int)(DAT_03ffdf1b-5) < 0) && ((DAT_03ffdf1b != 0 || (pin[0] != 0)))) {
bVar7 = DAT_03ffdf1b + 1;
}
}
} /* Copy PIN to message 0x532 buffer */
if (bVar3) DAT_03ffdf22 = DAT_03ffdf22 | 0x10;
else DAT_03ffdf22 = DAT_03ffdf22 & 0xef;
DAT_03ffdf1c = bVar7 & 0xf | (byte)((pin[0] & 0xf) << 4);
DAT_03ffdf1d = (byte)((pin[1] & 0xf) << 4) | pin[2] & 0xf;
DAT_03ffdf1e = (byte)((pin[3] & 0xf) << 4) | pin[4] & 0xf;
DAT_03ffdf1f = DAT_03ffdf1f & 0xf | (byte)((bStack_1 & 0xf) << 4);
}
Зная адрес, легко нашлась функция, готовившая данные к отправке. При нормальной работе, PIN не передается в сообщении 0x532, вместо него передается недействительный код 0xAAAAA. Но при некоторых условиях, как можно увидеть в листинге, в сообщении передается код, хранящийся по адресу 0x3ffd2ab или по адресу 0x3ffdf16.
В прошивке не так много вызовов чтения SPI, так что мы без труда находим функцию, в которой видно, как по SPI по адресу 0 из EEPROM считывается 608 байт по адресу 0x3ffd200, и предполагаемый PIN (хранящийся в EEPROM по смещению 0xAB) оказывается в аккурат по адресу 0x3ffd2ab:
void FUN_00007d26(void)
{
ushort *in_CTBP;
(*(code *)((int)in_CTBP + (uint)*in_CTBP))();
FUN_00007904(1);
SPI1_Read(0,&DAT_03ffd200,0x260);
SPI1_Read(0x7f0,&DAT_03ffd460,0x10);
FUN_00007904(0);
(*(code *)((int)in_CTBP + (uint)in_CTBP[0x1e]))();
DAT_03ffd693 = DAT_03ffd693 & 0xdf;
}
Вот такая смешная шутка: на скриншотах в статье был виден код мотоцикла 11111, без всякого шифрования, которого там и нет.
Послушав меня, жена сказала, что я хорошо устроился: никогда не понято, работаю я или же маюсь херней. И на всякий случай уточнила, много ли людей оценило шутку по достоинству.
Глава 5. Заглянем на прощанье в UDS
Осталось понять, как работает чудо-прибор из статьи. Подозрения, конечно, пали на UDS. Таблица сервисов расположена прямо под списком принимаемых сообщений CAN и просто бросается в глаза (если что-то начинается с 0x10, а заканчивается на 0x85, то что бы это могло быть еще?).
UDS_SERVICES_TBL
000044e8 10 00 00 00 ddw 10h
000044ec 0e af 01 00 addr On_UDS_0x10_Diag_Session_2
000044f0 01 01 01 00 ddw 10101h
000044f4 11 00 00 00 ddw 11h
000044f8 c0 af 01 00 addr LAB_0001afc0
000044fc 00 01 01 00 ddw 10100h
00004500 14 00 00 00 ddw 14h
00004504 30 b0 01 00 addr LAB_0001b030
00004508 01 01 01 00 ddw 10101h
0000450c 19 00 00 00 ddw 19h
00004510 88 b3 01 00 addr LAB_0001b388
00004514 01 01 01 01 ddw 1010101h
00004518 22 00 00 00 ddw 22h
0000451c 40 b4 01 00 addr On_UDS_0x22_Read_Data_2
00004520 01 01 01 01 ddw 1010101h
00004524 23 00 00 00 ddw 23h
00004528 78 b5 01 00 addr LAB_0001b578
0000452c 00 00 01 01 ddw 1010000h
00004530 27 00 00 00 ddw 27h
00004534 e0 b6 01 00 addr On_UDS_0x27_Security_Access_2
00004538 00 01 01 01 ddw 1010100h
0000453c 28 00 00 00 ddw 28h
00004540 94 b8 01 00 addr On_UDS_0x28_Comm_Control_2
00004544 00 00 01 00 ddw 10000h
00004548 2a 00 00 00 ddw 2Ah
0000454c de b9 01 00 addr LAB_0001b9de
00004550 00 00 01 01 ddw 1010000h
00004554 2e 00 00 00 ddw 2Eh
00004558 7a bc 01 00 addr LAB_0001bc7a
0000455c 00 00 01 01 ddw 1010000h
00004560 2f 00 00 00 ddw 2Fh
00004564 00 be 01 00 addr LAB_0001be00
00004568 00 00 01 01 ddw 1010000h
0000456c 3d 00 00 00 ddw 3Dh
00004570 d4 c3 01 00 addr LAB_0001c3d4
00004574 00 00 01 01 ddw 1010000h
00004578 3e 00 00 00 ddw 3Eh
0000457c 4e c5 01 00 addr On_UDS_0x3e_Tester_Present_2
00004580 01 01 01 00 ddw 10101h
00004584 85 00 00 00 ddw 85h
00004588 d2 c5 01 00 addr On_UDS_0x85_DTC_Settings_2
0000458c 00 00 01 00 ddw 10000h
В функции обработки сервиса 0x22 “Read Data” в самом начале находим указатель на таблицу поддерживаемых идентификаторов:
Функция On_UDS_0x22_Read_Data_2()
void On_UDS_0x22_Read_Data_2(undefined4 *param_1)
{
byte bVar1;
bool bRet;
uint uVar2;
int iVar3;
undefined *puVar4;
word *pwVar5;
word *pwVar6;
byte did_idx;
undefined2 did;
undefined4 uStack_14;
undefined4 uStack_10;
uStack_10 = 0;
uStack_14 = 3;
if (_DAT_03ffe360 < 3) {
uStack_14 = 0x13000003;
}
else {
did = CONCAT11(*(undefined *)(_DAT_03ffe35c + 1),*(undefined *)(_DAT_03ffe35c + 2));
pwVar6 = &UDS_22_DID_TABLE_BIG_START;
bRet = Table_Find_Idx_by_DID((dword *)&UDS_22_DID_TABLE_BIG_START,0x14,0x55,did,&did_idx);
if (bRet) {
bVar1 = *(byte *)((int)pwVar6 + (short)(ushort)did_idx * 0x14 + 3);
if ((*(byte *)((int)pwVar6 + (short)(ushort)did_idx * 0x14 + 3) & 1) == 0) {
uStack_14 = CONCAT13(0x31,(undefined3)uStack_14);
}
else {
uVar2 = (uint)((bVar1 & 0x10) != 0);
if (((((bVar1 & 4) == 0) && (uVar2 == 0)) ||
(((bVar1 & 4) != 0 && (iVar3 = FUN_000152d8(), iVar3 != 0)))) ||
((uVar2 != 0 && ((DAT_03ffdf25 & 1) == 0)))) {
pwVar5 = pwVar6 + (short)(ushort)did_idx * 10;
bVar1 = *(byte *)(pwVar5 + 1);
if (bVar1 != 0) {
uVar2 = 0;
puVar4 = *(undefined **)(pwVar5 + 2);
do {
*(undefined *)(_DAT_03ffe35c + uVar2 + 3) = *puVar4;
uVar2 = uVar2 + 1 & 0xff;
puVar4 = puVar4 + 1;
} while (uVar2 < bVar1);
pwVar5 = pwVar6 + (short)(ushort)did_idx * 10;
}
/* WARNING: Could not recover jumptable at 0x0001b554. Too many branches */
/* WARNING: Treating indirect jump as call */
(**(code **)(pwVar5 + 8))();
return;
}
uStack_14 = CONCAT13(0x33,(undefined3)uStack_14);
}
}
else {
uStack_14 = CONCAT13(0x31,(undefined3)uStack_14);
}
}
*param_1 = uStack_14;
return;
}
В таблице идентификаторов (я привел ее в читаемый вид, уж больно она длинная) находим знакомый адрес 0x03FFD2AB, данные по этому адресу пришлют на запрос DID 0x141 в сервисе UDS 0x22:
Таблица поддерживаемых DID
DID |
Адрес |
Длинна |
0x0101 |
0x03FFD212 |
12 |
И действительно, отправляем запрос на DID 0x141 к BCM (никаких уровней доступа не требуется) через сервис UDS 0x22 и получаем наш код на блюдечке:
T 0x7e2 03 3e 00 00 aa aa aa aa
R 0x7ea 03 7e 00 00 00 00 00 00
T 0x7e2 03 22 01 41 aa aa aa aa
R 0x7ea 10 09 62 01 41 03 01 03
T 0x7e2 30 00 64 00 00 00 00 00
R 0x7ea 21 01 03 00 00 00 00 00
Positive 0x0141: 03 01 03 01 03 00 ......
Заключение
Наверное, и не было никакой великой тайны, и не было “не ломаемой защиты последнего поколения мотоциклов Harley Davidson”. Те, кто следил за темой, скачали в свое время обновление Street Tuner и увидели, что туда добавили еще одну табличку для “нового поколения”, пожали плечами и добавили ее в свой код. Не было компаний, которые разорились, потому что не смогли адаптировать свои продукты для новых мотоциклов. Увы, рынок этот достаточно специфический и конкурировать на рынке диагностики с производителем мотоциклов непросто. Так что неудивительно, что третьесторонние продукты уходят с рынка.
Но не будем грустить об этом: если долго и профессионально искать Великую Тайну, она обязательно встретится. Главное, посылать правильные сигналы во вселенную.
Интересно, конечно, глянуть еще прошивку тахометра. Пусть там и нет никакой тайны, но любопытно, как он выводит текст на экран. Было бы неплохо, если бы при включении он писал PRIVET SASHA.
Ах, да. Пароль от новых баз ICwN1zD2LIqlAy9J.
[1] Eagle Pro Street Tuner https://www.harley-davidson.com/us/en/shop/screamin-eagle-pro-street-tuner/p/41000008C
[2] Street Tuner PC Software https://streetperformancetuner.harley-davidson.com/StreetTuner.aspx
[3] V850 Processor Module for Ghidra https://github.com/esaulenka/ghidra_v850/
[4] JTAG access to V850 https://pcmhacking.net/forums/viewtopic.php?t=8037
[5] 2 Ways to Read Harley Davidson Motorcycle PIN Code http://blog.obdii365.com/2024/11/26/read-harley-davidson-motor-pin-code-for-all-keys-lost/
[6] 2 способа прочитать PIN-код https://dzen.ru/a/Z2VEN91twiGTs8FD
Robocopold
Отлично!Все как и ожидалось! Вскрыли все ж таки.Кстати по BCM....В той же SPI лежит код метки(FOB) и CRC связанный с этим кодом(для мотоциклов после 2020 выпуска).Вот с контролькой все ни как не разобрался:(
agorlach77 Автор
Контролька - контрольная сумма для FOB ? ...пообщался сейчас по UDS с блоком ABS и левой ручкой.
Robocopold
Да,но она появилась в Спорстерах с 2019 года,в Софтейлах с 2020.Кстати на почте дамп проца от панели приборов