Как взломать Harley Davidson. Часть 2
В прошлой части дизассемблировали код прошивки ECM последнего поколения мотоциклов в Ghidra, подправили его и запустили на тестовой плате Aurix TC275 Lite Kit, получили возможность обмениваться с ним сообщениями CAN и отлаживать его в winIDEA. Теперь настало время посмотреть, что же там интересного.
Глава 1. Цифровая шина мотоцикла
ECM, Engine Harness, 2022 Softail
Вначале чуть-чуть теории. Мотоцикл имеет несколько электронных блоков, объединенных шиной CAN работающей на стандартной скорости 500 кбит/с:
Electronic Control Module (ECM) или же Electronic Control Unit (ECU) управляют двигателем и к этому блоку непосредственно подключаются датчики двигателя.
Похожий по названию Hydraulic Control Unit (HCU или EHCU) управляет ABS (тормозной системой).
Body Control Module (BCM) управляет периферией: светом, звуковым сигналом, топливной помпой и прочим. К нему же подключена антенна радиоключа.
Instrument Module (IM) — это цифровой комбинированный прибор, тахометр с индикаторными лампами и небольшим дисплеем, для вывода сообщений и кодов ошибок.
Left и Right Hand Control Module (LHCM и RHCM) — пульты управления (ручки газа и сцепления с кнопками). Положение ручки газа и нажатия кнопок передается на шину в цифровом виде. Сцепление механическое, но левый пульт имеет дискретный датчик выжима сцепления, его показания также передаются по шине CAN.
Все желающие разобраться подробнее могут поискать в сети Service Manual с номером 94000936, он включает подробные диаграммы проводки и иллюстрации. Одновременно отметим, как изящно (всего два разъема, 6 и 8 контактов) вся эта каша из проводов утекает на другие страницы диаграммы на иллюстрации выше, спасибо шине CAN.
В этой статье, напомню, ковыряем прошивку ECM с аппаратным номером 41000706 и номерами 41001165 или 41000677 в каталоге запчастей (прошивки идентичные, не ручаюсь за настройки), которую удалось запустить на тестовой плате.
Это не очень поможет нам угнать мотоцикл, так как аксессуары (разъем USB и диагностический коннектор DLC) получают питание от BCM либо после ввода кода доступа с пультов, либо после получения сигнала от радиоключа. Вероятно, и питание модуля ECM (также получаемое от BCM) осуществляется с задержкой и, в любом случае, ECM не участвует в авторизации пользователя.
Но однажды я видел на шине CAN код доступа в открытом виде и, конечно, любопытно будет посмотреть прошивку BCM, если она попадёт в руки.
Глава 2. Сообщения CAN
Сообщения CAN в прошивке мотоцикла
Имея некоторый навык, обнаружить списки поддерживаемых ECM сообщений несложно, хотя их идентификаторы в прошивке имеют битовый сдвиг:
CAN_ID_RX XREF[1]: CAN_Load_Rx_Masks:8010db1c(*)
80109d4a 80 9f dw 9F80h 0x7E0 UDS
80109d4c 7c 9f dw 9F7Ch 0x7DF
80109d4e 1c 97 dw 971Ch 0x5C7
80109d50 04 97 dw 9704h 0x5C1 Clock
80109d52 00 97 dw 9700h 0x5C0 Total run
80109d54 e4 96 dw 96E4h 0x5B9
80109d56 c0 95 dw 95C0h 0x570 Buttons #2
80109d58 40 95 dw 9540h 0x550 Buttons #1
80109d5a c8 94 dw 94C8h 0x532 PIN
80109d5c c4 94 dw 94C4h 0x531 Static data
80109d5e c0 94 dw 94C0h 0x530 Battery and indicators
80109d60 9c 94 dw 949Ch 0x527
80109d62 88 94 dw 9488h 0x522
80109d64 84 94 dw 9484h 0x521 ABS Speed
80109d66 80 94 dw 9480h 0x520 ABS
Разбить принимаемые и передаваемые сообщения можно на пять групп.
Первая, 0×50X. Сообщения фирменного протокола Harley Davidson, в которых передаются данные от сенсоров, кнопок и т.п. Если не подключено диагностическое оборудование, других сообщений на шине мотоцикла мы не увидим. В них никакого второго дна нет, сиди и прикидывай, какой байтик температуру чего может обозначать. Две трети в интернете уже раскопали, можно легко найти.
Вторая: 0×6F0, 0×6F4. Я не большой знаток диагностических протоколов, и затрудняюсь назвать протокол данных сообщений. Более того, этот протокол в прошивке отключен, хотя код обработки сообщений сохранился. Основан он на однобайтных командах и запросах. Ниже кусочек обработки запроса 0×6F0, вдруг его кто-то узнает. Это, правда, не имеет особого практического смысла, так как принимаются эти сообщения на другом интерфейсе CAN микроконтроллера, который не подключен к проводке мотоцикла (и, возможно, даже не выведен на разъем ECM).
Фрагмент обработки запроса 0×6F0
case 0xd4:
bVar8 = DAT_7000ade1;
if ((*(ushort *)(data_8 + 2) & 0xff) < (ushort)bVar8) {
cVar10 = FUN_80111594(data_8[4]);
goto joined_r0x801119b8;
}
cVar10 = '\"';
goto LAB_80111dae_preexit;
case 0xd5:
cVar10 = FUN_8011155c(*(ushort *)(data_8 + 2) & 0xff);
goto joined_r0x801119b8;
case 0xd6:
Init_Vars_0();
goto LAB_80111dba_exit;
case 0xd7:
break;
case 0xd8:
break;
case 0xd9:
DAT_7000add8 = 8;
DAT_7000add1 = 1;
DAT_7000add3 = 1;
DAT_7000add2 = 7;
Ram7000add4 = 7;
Ram7000add6 = 0;
goto LAB_80111dba_exit;
case 0xda:
DAT_7000add8 = 8;
DAT_7000add6 = 0;
bVar8 = DAT_7000ade1;
Ram7000add2 = (ushort)bVar8;
DAT_7000add1 = 0x43;
Ram7000add4 = 0;
DAT_7000add7 = 0;
goto LAB_80111dba_exit;
case 0xdb:
break;
case 0xdc:
break;
Третья: 0×404–0×40D. Данная группа, похоже, также относится к внутренним сообщениям Harley Davidson. Включить их можно, установив второй бит по адресу 0×6010001d через службу UDS. Но данную возможность, увы, вырезали из релиза. Ссылка на функцию осталась, код функции остался, вот только сопоставления службе UDS в таблице больше нет. Вымаран при компиляции жестоким #ifdef. Данные сообщения я не изучал, но, например, в первых четырех байтах сообщения 0×40D передается контрольная сумма части прошивки, начинающейся с адреса 0×800a0000. Имея отладчик, конечно, мы можем легко эти сообщения включить.
Пример сообщений 0×404–0×40D
404 00 00 00 C8 64 64 CE 00
405 00 00 00 00 00 00 00 00
406 00 00 00 00 00 00 80 80
407 00 00 00 00 00 00 00 00
408 11 00 00 64 64 00 00 00
409 69 62 64 64 00 86 43 71
40a 00 00 8B 70 70 86 16 06
40b 80 80 80 80 00 00 00 00
40c 27 00 6E 00 64 00 00 00
40d 28 BB DB 48 01 22 01 20
Четвертая: 0×6E8. Данное сообщение не передается блоком ECM при нормальной работе, и даже в диагностических записях я его не видел. Беглое изучение прошивки показало, что оно, скорее всего, также «вымарано» из релиза. И даже никакого кода, имеющего к нему отношение, не сохранилось.
Пятая: 0×7E0, 0×7DF и 0×7E8. Сообщения протоколов ODB II (On-board diagnostics) и UDS (Unified Diagnostic Services), по которым общается диагностическое оборудование. Протоколы открытые, но часть данных описывается в документах стандартов как «Vehicle Manufacturer Specific». Иными словами, только производитель знает, что скрывается за некоторыми данными. Именно через эти протоколы осуществляется перепрошивка ECM и передаются все остальные секретики.
Именно тут находятся ворота в сказочный мир с десятками служб. Если во всех остальных сообщениях каждый байт имеет свое конкретное значение (положение ручки газа, температура двигателя и т.п.), то здесь протокол гораздо сложнее, и запрос/ответ могут состоять из нескольких (иногда десятков) сообщений CAN.
Глава 3. Что же все-таки ломаем?
Читайте первоисточники
Вы, вероятно, прочли предыдущую часть статьи. И, вероятно, прочли уже половину этой статьи, посвященной взлому мотоцикла. При этом что конкретно мы будем взламывать, до сих пор непонятно. Подождите совсем чуть-чуть, сейчас Вы об этом узнаете.
Протокол UDS предлагает набор служб, от названия которых захватывает дух: «Read Memory By Address», «Write Data By Identifier», «Request File Transfer», «Control DTC Settings» и, помимо многих прочих, самая манящая: «Security Access».
Мы не будем подробно останавливаться на протоколе. Очень хорошее введение можно посмотреть на сайте CSS Electronics[1], есть и хорошее введение у CAN Hacker на русском языке[2], а серьезным интересантам можно рекомендовать найти подборку стандартов ISO 15765, ISO 16844 и ISO 14229, где все тоже неплохо и структурированно изложено.
Службы UDS позволяют получить доступ к чтению многих данных без какого-либо контроля доступа. Так, через службу «Read Data By Identifier» (номер 0×22) мотоцикл делится 57 параметрами (VIN, дата прошивки и т.п.), и только один из них требует специального уровня доступа.
Специального уровня доступа, разумеется, требуют службы, позволяющие читать и записывать память ECM. И так, позвольте вам представить нашего соперника: службу «SecurityAccess» (0×27).
7E0 02 27 01 00 00 00 00 00 TOOL> SECURITY LEVEL #1 REQUESTED
7E8 10 0A 67 01 F6 FE BE 1F ECM>> SEED #1 IS F6 FE BE 1F....
7E0 30 00 00 00 00 00 00 00 TOOL> PLEASE, CONTINUE
....
7E8 21 11 B1 A8 1F FE BE 1F ECM>> .... 11 B1 A8 1F
....
7E0 10 0A 27 02 2A 99 16 03 TOOL> THE PASSWORD #1 IS 2A 99 16 03...
7E8 30 08 04 A8 1F FE BE 1F ECM>> PLEASE, CONTINUE
....
7E0 21 85 A4 2D D3 00 00 00 TOOL> .... 85 A4 2D D3
7E8 02 67 02 A8 1F FE BE 1F ECM>> ACCESS #1 GRANTED
Все очень просто: диагностическая утилита запрашивает уровень доступа (в мотоцикле их три) и в ответ получает восьмибайтное число (seed), в ответ на которое она должна сообщить пароль из восьми байт.
Хорошая новость: мотоцикл всегда присылает один и тот же seed. Располагая дампом прошивки, очень легко найти, где он хранится. И найти код, который его высылает. Плохая новость: рядом с seed хранится и пароль. Всего в прошивке хранятся три пары, для трех уровней доступа.
Располагая тремя прошивками, я собрал восемнадцать восьмибайтных чисел. Ниже двенадцать из них.
Пары сид-пароль от трех мотоциклов
Seed #1 8001c020
ECU1: 1f a8 b1 11 1f be fe f6
ECU2: b8 df 49 81 b3 5c f1 a3
ECU3: B5 C8 30 FC 00 28 5B 54
Seed #2: 8001c028
ECU1: 4e c4 01 bb 51 c3 e1 9a
ECU2: 2c e6 d8 77 c8 97 6d b6
ECU3: 59 E7 D9 0E 6E 54 C3 02
Password #1 8001c053
ECU1: d3 2d a4 85 03 16 99 2a
ECU2: 3b 39 74 53 17 bf 12 20
ECU3: 3F 71 63 2C 83 68 7E DF
Password #2: 8001c04b
ECU1: f4 3d 80 2e 83 96 ca a6
ECU2: 4a 85 8c 55 4e 28 dc df
ECU3: 91 69 C4 B7 00 94 01 F6
Вот, собственно, их и нужно взломать. Генерировать пароль по заданному seed. Тогда мы сможем делать с мотоциклом все, что угодно.
Была слабая надежда, что цифры как-то привязаны к VIN. Увы. Смена VIN через службу «Write Data By Identifier» (0×2E) хоть и требует уровня доступа, но пары seed-password при этом не меняются. В прошивке вообще нет кода, который бы их менял. Увы.
Смена номера VIN через UDS
T 0x7e0 03 3e 00 00 aa aa aa aa
R 0x7e8 03 7e 00 00 4e e1 c3 51
T 0x7e0 02 10 01 aa aa aa aa aa
R 0x7e8 06 50 01 00 32 01 f4 51
T 0x7e0 02 10 03 aa aa aa aa aa
R 0x7e8 06 50 03 00 32 01 f4 51
---------------------------------------------------
T 0x7e0 02 27 01 aa aa aa aa aa
R 0x7e8 10 0a 67 01 f6 fe be 1f
T 0x7e0 30 00 64 00 00 00 00 00
R 0x7e8 21 11 b1 a8 1f fe be 1f
Answer (8): f6 fe be 1f 11 b1 a8 1f ........
T 0x7e0 10 0a 27 02 2a 99 16 03
R 0x7e8 30 08 14 a8 1f fe be 1f
T 0x7e0 21 85 a4 2d d3 aa aa aa
R 0x7e8 02 67 02 a8 1f fe be 1f
Unlocked, next level is 3
---------------------------------------------------
T 0x7e0 02 27 03 aa aa aa aa aa
R 0x7e8 10 0a 67 03 9a e1 c3 51
T 0x7e0 30 00 64 00 00 00 00 00
R 0x7e8 21 bb 01 c4 4e e1 c3 51
Answer (8): 9a e1 c3 51 bb 01 c4 4e ...Q...N
T 0x7e0 10 0a 27 04 a6 ca 96 83
R 0x7e8 30 08 14 c4 4e e1 c3 51
T 0x7e0 21 2e 80 3d f4 aa aa aa
R 0x7e8 02 67 04 c4 4e e1 c3 51
Unlocked, next level is 5
---------------------------------------------------
T 0x7e0 10 14 2e f1 90 35 48 44
R 0x7e8 30 08 14 c4 4e e1 c3 51
T 0x7e0 21 31 59 4c 4b 34 35 4d
T 0x7e0 22 42 30 31 32 33 34 35
R 0x7e8 03 6e f1 90 4e e1 c3 51
VIN changed to '5HD1YLK45MB012345'
Что же делать? Направления виделось два. Первый, это ломать UDS. Возможно, в каком-то сервисе есть дырка, удастся сорвать стек и получить контроль над кодом. Второй, это призрачная надежда, что алгоритм какой-то простенький. Например, MD5 или CRC64. Ну может же быть такое?
Глава 4. Зовем сто тысяч всадников
GPU смело бросается на невидимый алгоритм
Не то, чтобы я был наивен. Но у меня был опыт взлома судовых двигателей Mercury и MerСruiser. В том случае задача была проще: 32-битные пары, а не 64-битные. И у меня был фирменный адаптер. К прошивке добраться не мог, но мог притворяться двигателем и собрать миллион-другой пар, вместо восемнадцати, по всему диапазону. Я потом на них долго глядел и написал в сумраке какой-то гениальный алгоритм, который поместился в 15 килобайт и имел точность более 99%. Про искусственный интеллект в те времена еще никто не слышал. Как тогда это вышло, до сих пор не пойму. Не иначе, как сигнал во вселенную послал.
Как мы можем генерировать «надежное» 64-битное число X по 64-битному числу Y? Берем любую, но желательно 64-битную хэш-функцию[3] (она возвращают 64-битное число по аргументу любой длинны). И все: X = func (Y). Или можем взять 128 битный алгоритм, такой как MD5, и взять из результата первые восемь байт. Или последние. Или серединку. Например, X = get64(md5(Y)).
Популярных алгоритмов не так много. Как сделать, чтобы взломщик не перебрал их все и не угадал алгоритм? Очень просто, добавим «соль» (статическая часть, добавляемая к переменному аргументу), которая и станет нашим секретом. Например: X = md5(Y+«Harley is the best»). Теперь мы даже можем рассказать взломщику, какой алгоритм используем.
Но не рассказывайте взломщику слишком много. Такую соль, как показана выше, можно взломать по словарю из 10000 слов за сутки[4] на современном домашнем компьютере (не пробовал, но верю).
Как можно еще усложнить взломщику жизнь, не выдумывая очень длинной «соли» с буквами в разном регистре, цифрами и знаками? Да очень просто. Добавим еще строчку, где в цикле сто раз повторим X=md5(X). Хотите, сыпаните еще и соли сюда.
В общем, нам должно оооочень сильно повезти. Широко используемых хэш-функций чуть ли не десяток, плюс к ним можно добавить алгоритмы типа CRC64, которые (в зависимости от используемого полинома) тоже могут давать приемлемые результаты. Насыпьте сюда соли и неопределенный уровень вложенности алгоритмов. А мы не знаем ни алгоритма, ни соли, ни вложенности. Ни-че-го.
Есть в интернете сервисы, которые выкатывают тебе на введенные данные контрольные суммы по разным алгоритмам и результаты хэш-функций. Сразу два десятка не странице. Я их, конечно, попробовал, но не помогло. Это было бы слишком просто.
Скачал что-то в исходниках, что очень быстро для CRC64 восстанавливает начальное значение алгоритма по известным X, Y и полиному. Не помню, чем именно закончилось, но понятно, что ничем. Скачал hashcat, но и он мне не помог.
Идей же, выходящих за возможности популярных программ для взлома хэшей, было много. Вот как бы я писал генератор случайных пар X: Y? Если X = func (Y), почему бы не использовать для генерации ряда уже имеющийся код: Y2 = func (Y1+X1) ? А может быть, Y это не случайное число, а вычисленное по серийному номеру ECM? Такие идеи впечатлительным людям по десять штук за ночь приходят, и когда ты с ними просыпаешься в пять утра, они кажутся тебе очень недурными, хоть надевай тапки и беги проверять.
Решил писать свое, так как никогда не программировал под CUDA, а было интересно.
Прошлым летом пришлось мне взламывать брелок с алгоритмом Keeloq, для этого алгоритма есть очень красивый проект на C++[5], который лет за 300 на обычном ПК с хорошей видеокартой поможет восстановить секретный код производителя.
Впрочем, код этого проекта оказался избыточен. Выяснилось, что все распараллеливается буквально на одной странице и много кода писать не придется. Код, который выполняется на GPU, надо пометить макросом __device__. Код, который выполняется на GPU, а вызывается с CPU, надо пометить как __global__. Если мы хотим функцию и на GPU, и на CPU, надо пометить ее как __host__ __device__. Плюс пара библиотечных функций, которые загружают в память GPU данные и ждут завершения запущенного кода.
Устанавливаем Visual Studio, устанавливаем CUDA Toolkit, в мастере нового проекта выбираем CUDA. Вот и все. Исполняющийся на GPU экземпляр кода (нить или поток) может вычислить свой «порядковый номер» и по нему задать себе фронт работ. Практически все, что мне было нужно, я подчерпнул из древней статьи на Хабре.
Вдруг вам интересен любительский код для CUDA
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include
#include
unsigned long long* remote_data;
unsigned long long local_data[3+0x100] = {0x000000123456789A,0,0}; // src,crc,result,crc_tbl[0x100]
void init_crc64_table(void) {
unsigned long long crc, b, c, i;
for (c = 0; c < 0x100; c++) {
crc = c;
for (i = 0; i < 8; i++) {
b = crc & 1;
crc >>= 1;
crc ^= (0 - b) & 0xd800000000000000ull; // crc64 iso
}
local_data[3+c] = crc;
}
}
__host__ __device__ inline unsigned long long crc64(unsigned char* data, unsigned long long* table) {
size_t size = 8;
unsigned long long crc = 0xffffffffffffffff;
while (size--) crc = (crc >> 8) ^ table[(crc & 0xff) ^ *data++] ;
return crc ^ 0xffffffffffffffff;
}
__global__ void remote_calc(unsigned int offset,unsigned long long *data) {
unsigned int global_thread_index = blockIdx.x * blockDim.x * blockDim.y * blockDim.z + threadIdx.x;
unsigned long long start = (unsigned long long)global_thread_index * 0x100000 + offset;
if (global_thread_index % 1024 == 0) printf(".");
for (unsigned long long a = start; a < start + 0x100000; a++)
if (crc64((unsigned char*)&a, data + 3) == data[1]) { data[2]++; printf("!"); }
}
int main() {
dim3 gridSize = dim3(2048, 1, 1); // adjust to your GPU capabilitues, on RTX 4070 Ti
dim3 blockSize = dim3(512, 1, 1); // 60*1536=92160 threads will be started simultaneously
cudaEvent_t syncEvent;
cudaMalloc((void**)&remote_data, sizeof(unsigned long long) * 0x103);
init_crc64_table();
local_data[1] = crc64((unsigned char*) &local_data[0],local_data+3);
cudaMemcpy(remote_data, local_data, sizeof(unsigned long long) * 0x103, cudaMemcpyHostToDevice);
cudaEventCreate(&syncEvent);
remote_calc<<>>(0,remote_data);
cudaEventRecord(syncEvent, 0);
cudaEventSynchronize(syncEvent);
cudaMemcpy(&local_data, remote_data, sizeof(unsigned long long) * 0x103, cudaMemcpyDeviceToHost);
printf("CRC is %sfound [%llu]\r\n", (local_data[2] == 1) ? "" : "NOT ", local_data[2]);
cudaEventDestroy(syncEvent);
cudaFree(remote_data);
return (local_data[2] == 1);
}
Я не производил измерений или сравнений производительности CPU и GPU. Тут нужно учитывать слишком многое для корректного сравнения и довольно глубоко погрузиться в тему. Но распараллеливание кода на GPU в связке Visual Studio + CUDA Toolkit мне показалось не сложнее (а может быть, даже проще), чем создание многопоточного приложения для CPU. Возможно, это утверждение из серии «блок-флейта — это инструмент, на котором легко научиться играть плохо», но к прекрасному я прикоснулся.
Программка выше (из которой я выкинул проверку ошибок и многое другое для краткости) за 10 секунд считает более триллиона CRC64 (далеко не самым быстрым способом) бросая в бой на RTX 4070 Ti сразу почти 100.000 потоков и пытаясь подобрать исходное 64-битное число по его CRC64.
Мои хитрые программки трудились несколько месяцев, проверяли разные завиральные идеи и загружая RTX 4070Ti на 100%. Но ничего толкового они, как и ожидалось, сделать не смогли. О чем я не сожалею: в казино я не хожу, а тут сыграл в метод Монте-Карло с огромным удовольствием.
Глава 5. Настоящие хакеры срывают стек
Для исследования нужны подходящие инструменты
И хотя GPU был загружен на 100%, CPU был в моем распоряжении практически весь. Более того, я, наверное, расстроился бы, если б мои примитивные фокусы с видеокартой удались. Ведь настоящие хакеры находят уязвимость в коде, а потом пишут эксплойт. Конечно, было бы обидно, если бы меня всего этого лишили. К тому же, одну уязвимость в коде я нашел практически сразу.
Реализация службы UDS «Read Memory By Address» (0×23) не содержит проверки на длину запрашиваемых данных. 512 байт она возвращает без проблем, но запрос более 575 байт приводит к переполнению буфера и перезагрузке ECM. Одна беда, чтобы воспользоваться этой службой, уже нужно знать пароль и получить доступ. Не подходит.
Код обработки сообщений UDS весьма запутан. Чтобы его распутать, пришлось написать небольшую программку, которая объединяет несколько таблиц в дампе памяти и подтягивает к ним таблицу символов, экспортированную из Ghidra. Поэтому названия типа Something_About_Update_Total_Run прошу простить, именовал функции как Бог на душу положит.
UDS SID => idx1 => StrTblIdx => func
0x01 ( 1) => 0x00 ( 0) => 0x00 ( 0) => 0x80107108 ECU_Return_Data_ID1
0x02 ( 2) => 0x01 ( 1) => 0x01 ( 1) => 0x80107114 ECU_Return_Data_ID2
0x03 ( 3) => 0x02 ( 2) => 0x02 ( 2) => 0x80107120 thunk_FUN_800fe1d4
0x04 ( 4) => 0x03 ( 3) => 0x03 ( 3) => 0x80107124 FUN_80107124
0x06 ( 6) => 0x04 ( 4) => 0x04 ( 4) => 0x8010712C FUN_8010712c
0x07 ( 7) => 0x05 ( 5) => 0x05 ( 5) => 0x80107134
0x09 ( 9) => 0x06 ( 6) => 0x06 ( 6) => 0x80107138 ECU_Return_Data_ID9
0x10 (16) => 0x07 ( 7) => 0x07 ( 7) => 0x8010B800 FUN_8010b800
subfunction 1 => 2, 7 => 0x80100000
subfunction 2 => 2, 8 => 0x8010B808
subfunction 3 => 2, 9 => 0x8010B810
0x11 (17) => 0x08 ( 8) => 0x0A ( 10) => 0x80107144 FUN_80107144
subfunction 1 => 2, 10 => 0x80100000
subfunction 3 => 2, 11 => 0x80107168
0x14 (20) => 0x09 ( 9) => 0x0C ( 12) => 0x8010718C
0x19 (25) => 0x0A ( 10) => 0x0D ( 13) => 0x801071BC
subfunction 1 => 2, 13 => 0x80100000
subfunction 2 => 2, 14 => 0x801071C8 FUN_801071c8
subfunction 5 => 2, 15 => 0x801071D0 thunk_FUN_800fed04
subfunction 6 => 2, 16 => 0x801071D4
subfunction 10 => 2, 17 => 0x801071E0
subfunction 15 => 2, 18 => 0x801071E4
subfunction 16 => 2, 19 => 0x801071EC
subfunction 17 => 2, 20 => 0x801071F8
0x22 (34) => 0x0B ( 11) => 0x15 ( 21) => 0x8010BD74 UDS_Service_0x22_Process
0x23 (35) => 0x0C ( 12) => 0x16 ( 22) => 0x80107204 FUN_80107204
0x27 (39) => 0x0D ( 13) => 0x17 ( 23) => 0x8010B8E0 ECU_Get_Seed2_2
subfunction 1 => 2, 23 => 0x80100000
subfunction 2 => 2, 24 => 0x8010B938 ECU_Check_Secure_Pwd2_2
subfunction 3 => 2, 25 => 0x8010B940 ECU_Get_Seed2_4
subfunction 4 => 2, 26 => 0x8010B948 ECU_Check_Secure_Pwd2_4
subfunction 99 => 2, 27 => 0x8010B950 ECU_Get_Seed2_8
subfunction 100 => 2, 28 => 0x8010B958 ECU_Check_Secure_Pwd2_8
0x28 (40) => 0x0E ( 14) => 0x1D ( 29) => 0x8010B9E0 FUN_8010b9e0
subfunction 0 => 2, 29 => 0x80100000
subfunction 1 => 2, 30 => 0x8010B9E8 FUN_8010b9e8
subfunction 2 => 2, 31 => 0x8010B9F0
subfunction 3 => 2, 32 => 0x8010B9F8 FUN_8010b9f8
0x2E (46) => 0x0F ( 15) => 0x21 ( 33) => 0x801073A8 Something_About_Update_Total_Run
subfunction 16 => 2, 33 => 0x80100000
subfunction 241 => 2, 34 => 0x80107464 VIN_Set
0x2F (47) => 0x10 ( 16) => 0x29 ( 41) => 0x80107650
subfunction 5 => 2, 41 => 0x80100000
0x31 (49) => 0x11 ( 17) => 0x34 ( 52) => 0x8010BB94
0x34 (52) => 0x12 ( 18) => 0x35 ( 53) => 0x801077B0
0x36 (54) => 0x13 ( 19) => 0x36 ( 54) => 0x801077B4
0x37 (55) => 0x14 ( 20) => 0x37 ( 55) => 0x801077B8
0x3E (62) => 0x15 ( 21) => 0x38 ( 56) => 0x8010BA04
subfunction 0 => 2, 56 => 0x80100000
0x85 (133) => 0x16 ( 22) => 0x39 ( 57) => 0x801077BC FUN_801077bc
- Unused 0x801074BC Set_ECU_Programming_Date
- Unused 0x80107500 FUN_80107500
- Unused 0x80107544 FUN_80107544
- Unused 0x80107588 FUN_80107588
- Unused 0x801075CC UDS_Update_Protected_Data
- Unused 0x8010760C ECU_Set_Number
- Unused 0x80107670
- Unused 0x80107690
- Unused 0x801076B0
- Unused 0x801076D0
- Unused 0x801076F0
- Unused 0x80107710
- Unused 0x80107730
- Unused 0x80107750
- Unused 0x80107770 FUN_80107770
- Unused 0x80107790 FUN_80107790
- Unused 0x801077CC FUN_801077cc
Из полезного в листинге мы видим номер службы UDS, потом несколько ненужных цифр, а затем адрес, по которому находится обработчик данной службы и его название в моем проекте Ghidra. Для некоторых служб указаны подфункции и их обработчики (первая подфункция вместо 0×80100000 на самом деле использует обработчик по адресу службы).
Служба 0×2E «Write Data By Identifier» не имеет подфункций, за номером службы в запросе UDS следует идентификатор данных (два байта), которые следует записать. В эту таблицу старший байт идентификатора почему-то попал: 241 — это старший байт идентификатора VIN 0xF190 (см. код смены VIN в Главе 3). Все мы используем какие-то костыли в коде.
В самом конце «unused», это службы UDS, которые Harley Davidson выкинула из релиза. Адреса функций остались в одной из таблиц, но с какой службой они были связаны, установить уже невозможно. Функция, которую я назвал UDS_Update_Protected_Data, уже упоминалась выше: именно ей можно включить отправку сообщений CAN с идентификаторами 0×404–0×40D.
Вероятно, где-то в природе есть софт, который тестирует службы UDS. У меня такого не было, как и автомобильных адаптеров, с которыми он мог бы работать, и приходилось писать код на Python, который, через имеющийся у меня яхтенный адаптер CAN, пытался найти брешь в коде. Это было трудоемко.
Были проверены сервисы 1, 2 и 9 попытками отправить им слишком длинный запрос или получить от них слишком длинный ответ. То же с сервисом 0×22. Проверил поверхностно код других сервисов, но брешь не находилась. Я знаю, что она где-то есть и ждет меня, но разные жизненные обстоятельства отвлекли меня от поиска уязвимости.
Но я не сдался. Мне очень хочется сделать эксплойт для Harley Davidson. Больше, чем вилли.
Продолжение следует
И хотя удалось узнать уже очень много, взломать с наскока мотоцикл не получилось. Оба направления атаки провалились. Но мы обязательно взломаем мотоцикл в следующей части, когда вселенная опять сделает нам подарок и помощь неожиданно появиться со стороны, откуда ее совсем не ждали.
[1] UDS Explained — A Simple Intro https://www.csselectronics.com/pages/uds-protocol-tutorial-unified-diagnostic-services
[2] Протокол UDS https://canhacker.ru/protocol-uds/
[3] Хеш-функция https://ru.wikipedia.org/wiki/%D0%A5%D0%B5%D1%88-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F
[4] Анализ стойкости пользовательских паролей https://securelist.ru/password-brute-force-time/109798/
[5] CudaKeeloq https://github.com/X-Stuff/CudaKeeloq/