ea / bosch_headunit_root

Documentation and code for rooting and extending a Bosch car head unit (lcn2kai)
394 stars 57 forks source link

figure out IOSC #13

Closed ea closed 3 years ago

ea commented 3 years ago

Linux and RTOS communicate with each other over an interface called IOSC. OSAL library seems to implement an abstraction layer above it. We should reverse engineer users of libosal to figure out how it's supposed to be used so additional utilities can be written to get more interesting data from RTOS.

raburton commented 3 years ago

This is very interesting, I'm trying to follow various bit of code that uses this through but it's not that easy. The various gui apps seems to make extensive use of libosal, I already found multiple references in code I looked at that verified the map data signatures and encryption of the speed camera updates. I can't work out why though, but perhaps this was all incidental. It looks like there is some tracing system on the other side, so maybe it was just making use of that and nothing to do with the core functionality.

Do you know what /dev/kds is for? There are various other references to devices in /dev which I had found in the code previously, but didn't exist on the linux side, so now I guess these must all be over on the rtos side. e.g. /dev/registry which seems to hold config (and looks modelled on the windows registry from key names I've seen). Why is that on the rtos side though? Maybe it's needed on both and felt better to share a single one instead of duplicating it in each os.

From your test code it looks like you send a request code and the buffer length(?) as the first two words. We get back the same request code in the first word and the return length (I don't have many valid examples to extrapolate that from yet) in the next word. Then it looks like there is a 3rd word (always zero so far) before the data starts. I have 0xdfe: returns 0x18 as length and the string "MYCAR", I assume null terminated and null padded to 0x18. This is the bluetooth device name when pairing. 0xdff: length 0x0e and the string CM3118EXXXXXXX (where XXXXXXX is the numeric head unit device id, as shown in the speed camera updates device info screen, although there it that has an extra digit on the end (checksum?) 0x1214 returns a length of 0x28, which is more than the specified buffer size. I haven't checked if the data is truncated, hopefully it is or I'm not sure what the point of passing in the buffer length is (assuming that's what it is). Don't know what that data is yet. 0x1215 returns a length of 0x10 unknown data 0x1216 did something but I didn't capture it

Got a reboot somewhere after that, skipped up to 0x1300 and got nothing till next reset. I wonder if flooding the rtos with requests is just upsetting it and it reboots deliberately to try and recover from the flood of requests. Might try adding in a small delay and see if rate limiting it works better.

raburton commented 3 years ago

Ok, few extra bits. I tested the longer response for 0x1214. Despite passing 0x18 as the second parameter I still get back 0x28 bytes of data, so if we are passing in the length of the buffer it's being ignored. If this second parameter isn't the buffer size, what is it? Maybe it is the size, but only used for longer non-fixed length fields, and a minimum buffer size is assumed (and not checked for certain queries). Totally guessing. Also noticed the response to 0xdfe is zero padded to 0x16 chars, not to the end, then there are two more chars "FQ" in my case. 0x1216 returns 0x10 bytes of unknown data. Btw, do you know the significance of the 0xf6 on the ioread call? I've seen other values used. Actually, it'd make a lot more sense if this is the length of the buffer, being the next parameter after the buffer itself in a read call. Can't keep going out to the car in the rain, that's enough for tonight.

raburton commented 3 years ago

pushed a few changes to the test code

here is some sample output for a couple of codes mentioned above:

0dfe 0018 0000:
4d594341520000000000000000000000
0000000000004651
MYCAR...........
......FQ
0dff 000e 0000:
434d333131384531313131313131
CM3118E1111111

(device id edited to all 1s)

raburton commented 3 years ago

I've got it to stop causing a reboot When you load libosal it creates an exit hander, which reboots the system - I suppose none of the headunit apps are supposed to exit, so if they do it thinks something has gone wrong. It can be disabled by touching /opt/bosch/disable_reset.txt You still get a big dump to the terminal though, which you have to scroll back past to get to see your output.

raburton commented 3 years ago

I've made a start decoding some of the data available from KDS...

// code 0x0d60, len 0x04
struct ECORoute {
    uint8  FuelType : 1;          // byte 0
    uint8  DragCoefficient : 7;
    uint8  TransmissionType : 2;  // byte 1
    uint8  unused : 6;
    uint16 crc;                   // byte 2-3
};

Example 20c078ea FuelType = 0 (from an unleaded car) DragCoefficient = 20 TransmissionType = 3 (from a manual 6 speed car) crc = 0xea78


// code: 0x0d70, len 0x18
struct BTName {
    uint8  Name[16];
    uint16 crc;
};

Example 4d59434152000000 0000000000000000 0000000000004651 Name = "MYCAR" (null padded, max len 15 null temrinated or 16 if not???) crc = 0x5146


// code: 0x0d5e, len: 0x1b
struct PartsNumber {
    uint8  PartNumber[5];
    uint8  ConfigurationHash[20];
    uint16 crc;
};

Example 335a4c3742...... ................ ................ ..b0e9 PartNumber = "3ZL7B" (appears fixed length, not null terminated) ConfigurationHash = (hidden, in case unique) crc = 0xe9b0


// code: 0x0d61, len: 0x04
struct CameraSystem {
    uint8  CameraSystem : 3;        // byte 0
    uint8  AnticipatoryLine : 1;
    uint8  unused : 4;
    uint8  Guideline;               // byte 1
    uint16 crc;                     // byte 2-3
};

Example 80008b83 CameraSystem = 4 (from car with birds eye view, 4 camera system) AnticipatoryLine = 0 Guideline = 0 crc = 0x838b


// code: 0x0d59, len: 0x0a
struct VehicleInformation {
    uint8  unknown1;                        // byte 0
    uint16 VehicleType;                     // byte 1-2
    uint8  CANGeneration1 : 1;              // byte 3
    uint8  Region : 4;
    uint8  SteeringWheel : 3;
    uint8  SteeringPosition : 1;            // byte 4
    uint8  unknown2 : 1;
    uint8  ShowClock : 1;
    uint8  MeterClockSynchronisation : 1;
    uint8  PlatformType : 1;
    uint8  DistanceUnitsSupported : 2;
    uint8  unknown3 : 1;
    uint8  OpeningAnimation : 2;            // byte 5
    uint8  unknown4 : 6;
    uint8  OffRoadInformation : 1;          // byte 6
    uint8  unknown5 : 1;
    uint8  VehicleConfiguration : 1;
    uint8  HEV1 : 1;
    uint8  AntiTheft : 1;
    uint8  DrivingType : 2;
    uint8  JudgementForOP : 1;
    uint8  VoiceRecognition : 1;            // byte 7
    uint8  unknown6 : 1;
    uint8  CAN : 1;
    uint8  AUXKind1 : 2;
    uint8  ISA : 1;
    uint8  unknown7 : 2;
    uint16 crc;                             // byte 8-9
};
enum Regions {USA = 1, CAN, EUR, PRC, GCC, RUS, ASR, ARG, BRA, SAF, MEX, THI, TKY};

Example 00124c19bc400928 8c5d ... Region = 3 (from UK car) ... crc = 0x5d8c

raburton commented 3 years ago

I haven't worked out the CRC algorithm yet. I've coded up a C version of the one I found in the application, but it doesn't seem to get called to verify these structures, so I don't know for sure if it's the right one. Where I have seen it called for other purposes it takes a starting value of 0, but using this it does not produce the a crc that matches the ones in my own data. I can use brute force to work out a starting value to get the right CRC out for any particular data structure, but each one seems to be different and that doesn't seem likely.

raburton commented 3 years ago
code: 0x0106, len: 0x07
uint8 DeviceSerialNumber[7];

code: 0x1216, len: 0x10
uint8 CryptedFileAesKey[16];

used for .ntq alert files


code: 0x0105
code: 0x010e
code: 0x0d52

used for ipod control


code: 0x0104, len: 0x08
uint8 TelematicsPassword[8];

Not seen yet for real. Password appears to be encrypted/hashed.

ea commented 3 years ago

Awesome work. I am not sure what KDS could stand for , but all the retrieved data seems to be static. I'll try to correlate it with some stuff in the RTOS binary. From my notes , I've seen the following /dev/kds addresses :

0xD59 VehicleInformation 0xD61 CameraSystem 0xDFE BTName 0xD53 SystemConfig2FormerAudio 0xD60 ECORoute 0xD30-0xD38 SDSECNR (what could this acronym mean?) 0xD52 SystemConfiguration 0xD40-0xD48 PhoneECNR 0xD5D FMAMTunerParameter 0xD5F AudioParamterSet 0xD5B , 0xD5C - DABParameterSet 0xD5E PartsNumber

The structure of returned data can easily be inferred from Vtables of corresponding classes as all have obvious getters in the binary. I pulled the above ones from smartphone support app.

ea commented 3 years ago

Over the weekend , I've been digging through OSAL_s32Message* APIs and their usage, but don't have a working example just yet. I can see some magic words associated with different juicy looking data, but haven't been successful at setting up a full OSAL messaging client just yet. A little more digging and testing ...

ea commented 3 years ago

Meanwhile , i've added a similar tool that shows how to walk the registry device. Please pay no attention to the questionable C, it's that way for easy testing of various structures.

raburton commented 3 years ago

I've got the crc sussed out. I couldn't find any crc algorithm in the binaries that would produce crcs that matched the values in the real data. I started to think that perhaps they had tried to obfuscate the crc for some reason. I'd tried using non-standard starting values, and tried xoring the output without finding anything. Then I tried drvBtAsipCrc from libosal_linux_so.so still not joy, so I tried to brute force all possible starting values and all possible xor outputs at the end, against multiple data items to see if I could find an overlap - and I did. A modified version of drvBtAsipCrc passed the data and an initial value of 0x07f0 and final xor of 0x07f0 produces the correct checksums. I was feeling pretty pleased with myself, after hours of work!, and decided to google this number to see if it had any significance. I didn't find any, but I did come across a tool call reveng which can help find crc algorithms from data and it was instantly able to solve it (aaah!) in a different way. Apparently the algorithm is CRC-16/IBM-SDLC with it's standard parameters (none of which are 0x07f0). It's fascinating that a different (but obviously related) crc algorithm could produce the same results with a particular change to initial and xor.

Anyway, here it is:

unsigned short crc16(unsigned char* data, int len) {
    unsigned short crc = 0xffff;
    for (int i = 0, x; i < len; i++) {
        x = crc ^ data[i];
        x = (x ^ (x << 4)) & 0xFF;
        crc = (crc >> 8) ^ (x << 8) ^ (x >> 4) ^ (x << 3);
    }
    return (crc ^ 0xffff);
}
ea commented 3 years ago

Figured out messaging mechanism. Partially at least. One listens on a queue and can wait for messages to be delivered. Haven't yet figured out the structure of the received message notification , but it contains who it comes from , message code and some sort of a pointer which is used to look up the actual message contents inside shared memory. Haven't yet figured out quite how to actually retrieve the message content. To be continued...

raburton commented 3 years ago

Have you tried writing to kds? I've tried replacing the BT device name (seems to be stored in two different kds locations, so I changed both). A call to OSAL_s32IOControl(fp, 2, 1), after opening, appears to make it writeable. The write works fine, and a read back shows the updated value, but after a reboot they have gone back to the old value. The only write to kds I've found in any of the modules is to store the telematic password, so it doesn't look like it's a common thing but it should be possible, and I'm sure that password would be expected to persist.

ea commented 3 years ago

I have not tried to write to KDS device yet , for fear of bricking my device. I wonder if there some sort of caching involved that would return the updated value but not make it persist after a reboot. Maybe it requires some sort of a flush or sync...

ea commented 3 years ago

I have noticed that OSAL_IOOpen is sometimes called with an argument other than 4. Probably depends on weather the target is under RTOS or linux. For example, if you try do OSAL_IOOpen("/dev/root/proc/mounts",4) it fails, but OSAL_IOOpen("/dev/root/proc/mounts",1) passes and actually reads out linux's /proc/mounts. I've been trying to figure out how exactly the directory reading functions work, in order to be able to list what structure the RTOS sees.

ea commented 3 years ago

It's actually very simple

    int r = OSALUTIL_prOpenDir("/dev/ffs");
    printf("%x\n\n\n",r);
    int dr; 
    while(dr = OSALUTIL_prReadDir(r)){
    printf("%s\n",dr);
}

But doesn't work for / , i guess that's not how RTOS works with these. I should check out some t-kernel internals...

ea commented 3 years ago

Cleaned up a bunch of stuff and now have a properly working messages listener. Now to figure out what/where the fun ones are.

ea commented 3 years ago

whoops, didn't mean to close it just yet

ea commented 3 years ago

@raburton i just added a code sample that lets you write things to KDS, I successfully managed to change the bluetooth name of on my test device. The trick was indeed to flush the write by an additional IOCTL.

Additionally, I've documented everything I got so far about interaction between Linux and RTOS in "rtos_interaction.md". I'll do some more cleanup and rearranging and finally close this issue , soon :)

ea commented 3 years ago

Closing issue , see docs/rtos_interaction.md and rtos_interaction/