Not sure who do I write this for, but never mind
I figured out how the challenge response protocol works. The challenge recalculation for the next round is still a mystery (but it seems irrelevant anyhow, what counts is the challenge that one gets), but on the surface this is what happens. The ECU sends:
05 [4 bytes of challenge] [2 bytes of ECU CS]
Code box replies:
AB [2 bytes of code box CS] 00 00 00 00
The calculation of both CS values is done following the C code I reconstructed (the immo key is fake, but I verified this with the data from my car):
#include <stdlib.h>
#include <stdio.h>
// Sample ECU request: 05 [93 9E B4 C3] [0B 4A]
// Code box reply: AB [0A 6E] 00 00 00 00
int main() {
// Calculation keys from Flash
unsigned char keys[] = {
// key 1
0xD7, 0x74, 0x24, 0x91, 0x83, 0x65, 0xBC, 0x4D, 0xE3, 0x55, 0x38, 0xFB, 0x76, 0x2C, 0x8D, 0x70,
0xF5, 0x23, 0x85, 0xE5, 0x8C, 0x1C, 0xFC, 0x6E, 0xA7, 0x22, 0xB9, 0x33, 0x39, 0x7C, 0x48, 0x0A,
// key 2
0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01,
// key 3
0x66, 0x66, 0x66, 0x66, 0x99, 0x99, 0x99, 0x99, 0x66, 0x66, 0x66, 0x66, 0x99, 0x99, 0x99, 0x99,
0x99, 0x99, 0x99, 0x99, 0x66, 0x66, 0x66, 0x66, 0x99, 0x99, 0x99, 0x99, 0x66, 0x66, 0x66, 0x66
};
// Immo data bytes from the ECU EEPROM, row 04/05, bytes 2..7
unsigned char immo[] = { 0x6D, 0x28, 0x36, 0xA9, 0xCA, 0xE4 };
// Sample challenge from the request
unsigned char chal[] = { 0x93, 0x9E, 0xB4, 0xC3 };
unsigned char temp[] = {
chal[3] ^ immo[0] ^ 0x04, chal[2] ^ immo[1] ^ 0x00,
chal[1] ^ immo[2] ^ 0x08, chal[0] ^ immo[3] ^ 0x0E
};
int ee_key = ((immo[4] | (immo[5] << ) ^ 0x0011) << 2;
unsigned int cs_ecu = 0, cs_expected = 0;
int count = 0x54;
while(count > 0) {
int index_data = ((temp[3] & 0x84) | (temp[2] & 0x28) | (temp[1] & 0x50) | (temp[0] & 0x03)) & 0xFF;
int index1 = 0x20+(index_data & 0x07);
int index2 = (index_data >> 3) & 0x1F;
unsigned int t = 2 * ((temp[0] & 0xFF) | ((temp[1] << & 0xFF00) | ((temp[2] << 16) & 0xFF0000) | ((temp[3] << 24) & 0xFF000000));
temp[0] = t & 0xFF; temp[1] = (t >> & 0xFF; temp[2] = (t >> 16) & 0xFF; temp[3] = (t >> 24) & 0xFF;
if(keys[0x28+index2] & keys[index1]) temp[0] |= 0x01;
if(count <= 0x0E) {
cs_expected <<= 1;
if(keys[index2] & keys[index1]) cs_expected |= 0x01;
}else if(count <= 0x22) {
if(ee_key & 0x8000) temp[0] ^= 0x01;
ee_key <<= 1;
}else if (count <= 0x2E) {
cs_ecu <<= 1;
if(keys[index2] & keys[index1]) cs_ecu |= 0x01;
}
count--;
}
cs_ecu &= 0x0FFF;
cs_expected &= 0x3FFF;
printf("CS ECU:\t\t\t%04X\nExpected response CS:\t%04X\n", cs_ecu, cs_expected);
return 0;
}
The ECU CS proves the possession of the right immo code to the code box, the expected CS proves to the ECU the possession of the right code by the code box.
If anyone cares. I am yet to test if I can actually unlock the immo on my bench ECU this way. The next step is to reverse engineer the virgin reset code.