Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Discussion] Further protocol reversing #16

Open
zivillian opened this issue Sep 20, 2022 · 16 comments
Open

[Discussion] Further protocol reversing #16

zivillian opened this issue Sep 20, 2022 · 16 comments

Comments

@zivillian
Copy link
Contributor

I started to look into the protocol and stumbled upon the undetermined XOR algorithm - so I decided to dig deeper. Instead of looking at PCAPs or the responses from the car I started implementing a car simulator and connected the official app to it. As of now it looks promising, since I already found that the XOR value is only dependent on the Init connection packet (not the IP, or MAC or anything else I can think of). By modifying the Init connection packet content, I can reproducably change the XOR used by the app.

As of now, it looks like the app uses the data of the Init connection packet AND 0x00080808080808080800 while the first byte has to be 0x01 and the last byte is discarded. So we get 8 relevant bits, which is the byte used for determining the initial XOR value. All three of the following init packets result in the exact same XOR value (0x66) used by the app:

5e 0c 00 01507a37cfe11583d801 8d
5E 0C 00 01000800080000000800 83
5E 0C 00 01F7FFF7FFF7F7F7FFF7 32

After looking at the app (using my scissor ;-) I found three constants to enable debug logging in the app. The modified app can be found here.

By looking at the log output I can tell that the one byte from the init connection packet is logged directly as security_key and used further to create the XOR value by some kind of KEY_MAP lookup:

iMobile2: send sync time-------------------------------
iMobile2: -------security_key------ 138
iMobile2: send: E5 04 01 01 00 EB
iMobile2: -------KEY_MAP[0]------ 102
iMobile2: recv: 5E 0C 00 01 50 7A 37 CF E1 15 83 D8 01 8D
iMobile2: -------security_key------ 128
iMobile2: send: E5 04 01 01 00 EB
iMobile2: -------KEY_MAP[0]------ 128
iMobile2: recv: 5E 0C 00 01 00 00 00 00 00 00 00 08 00 73
iMobile2: send sync time-------------------------------
iMobile2: -------security_key------ 0
iMobile2: send: E5 04 01 01 00 EB
iMobile2: -------KEY_MAP[0]------ 0
iMobile2: recv: 5E 0C 00 01 00 00 00 00 00 00 00 00 00 6B
iMobile2: send Mac Addr
iMobile2: -------security_key------ 192
iMobile2: send: E5 04 01 01 00 EB
iMobile2: -------KEY_MAP[0]------ 115
iMobile2: recv: 5E 0C 00 01 00 00 00 00 00 00 08 08 00 7B

So the bits are interpreted with the LSB first:

0x0008000800000008 -> 0b_1000_1010 -> 138
0x0000000000000008 -> 0b_1000_0000 -> 128
0x0000000000000000 -> 0b_0000_0000 -> 0
0x0000000000000808 -> 0b_1100_0000 -> 192

My biggest obstacle right now is that I have only remote access to a car, so it would be great if someone could trace the registration process (so I can at least add a static example to my vehicle emulator). The log output of a registered app would be helpful as well.

To create the log file, you would need to install the modified app (which may remove the registration and all the data associated with the app - at least it did for me) and enable USB debugging. The log can then be extracted with adb logcat -s iMobile2.

If you don't trust my binary, you can also create your own, by using the commands and changes documented in the history of the app repo.

@zivillian
Copy link
Contributor Author

It look's like I've found the method which validates the received packets. The comments are my interpretation - maybe it helps:

private static int b(byte[] paramArrayOfbyte, int paramInt1, int paramInt2) {		//Check packet!!! - paramInt1 = start of packet; paramInt2 = length
  if (paramArrayOfbyte[paramInt1] == d.j && paramArrayOfbyte[paramInt1 + 3] < 100)	//PING RESP with SEQ < 0x64
    return d.n; //0
  if (paramArrayOfbyte[paramInt1] != d.h)						//!=0x6f - register changed
    return d.s; //6
  byte b1 = paramArrayOfbyte[paramInt1 + 1];						//length
  if (b1 != paramInt2 - 2)								//invalid length?
    return d.s; //6
  byte b2 = paramArrayOfbyte[paramInt1 + 2];
  if (b2 != d.l && b2 != d.m)								//ACK field has to be 0 or 1
    return d.s; //6
  byte b3 = paramArrayOfbyte[paramInt1 + 3];
  if (b2 == d.l) {									//request?
    if (b3 >= d.o0 || (b3 < 0 && b3 != -64))						//> 0x37 || ( < 0 && != 0xc0(ECU Version) )
      return d.r; //5
  } else if (b3 >= d.P0) {								//register > 0x1f
    return d.q; //4
  } 
  if (d.a(paramArrayOfbyte, paramInt1) != paramArrayOfbyte[paramInt1 + paramInt2 - 1])	//invalid checksum?
    return d.p; //2
  if (b2 == d.l) {									//request?
    g g = d.a(b3);
    if (g == null)									//??unknown register?
      return d.r; //5
    if (g.b != b1 - 3)									//??register size?
      return d.o; //1
  } 
  return d.n; //0
}

@buxtronix
Copy link
Owner

This is awesome, getting this part figured out would be a huge help to getting the protocol working reliably! I will try to get some time to try the modified app to check registration, and with some luck can get you the data needed to try and implement registration, then there is no need for the official app any more at all. Exciting!

@buxtronix
Copy link
Owner

I've not been able to get the modified app to run :(

I get the brief initial splash screen, then a blank screen. Logcat shows the following:

- waiting for device -
--------- beginning of system
--------- beginning of main
09-26 14:18:54.171  3576  3576 W iMobile2: on iMobile2 create
09-26 14:18:55.172  3576  3601 W iMobile2:  LoadAppSet
09-26 14:18:55.177  3576  3601 W iMobile2: load all message in LoadAPPSet
09-26 14:18:55.218  3576  3576 W iMobile2: class com.inventec.iMobile2.Login_Sel created
09-26 14:18:55.219  3576  3576 W iMobile2: Application not loaded
09-26 14:18:55.219  3576  3576 W iMobile2: on getDeviceInfo
09-26 14:18:55.225  3576  3576 W iMobile2: screen width:1080 height:1794 scale:2.625 density:2.625
09-26 14:18:55.226  3576  3576 W iMobile2: Application loaded
09-26 14:18:55.227  3576  3576 W iMobile2: loadAll
09-26 14:18:55.236  3576  3576 W iMobile2: load all message
09-26 14:18:55.236  3576  3576 W iMobile2: language is en
09-26 14:18:55.389  3576  3576 W iMobile2: Login_Sel Create
09-26 14:18:55.406  3576  3576 W iMobile2: SIGNATURE不一致
09-26 14:18:55.406  3576  3576 W iMobile2: APP has been modified!!
09-26 14:18:55.541  3611  3611 W iMobile2: class com.inventec.iMobile2.Login_Sel created
09-26 14:18:55.547  3611  3611 W iMobile2: Application not loaded
09-26 14:18:55.548  3611  3611 W iMobile2: on getDeviceInfo
09-26 14:18:55.558  3611  3611 W iMobile2: screen width:1080 height:1794 scale:2.625 density:2.625
09-26 14:18:55.559  3611  3611 W iMobile2: Application loaded
09-26 14:18:55.560  3611  3611 W iMobile2: loadAll
09-26 14:18:55.565  3611  3611 W iMobile2: load all message
09-26 14:18:55.565  3611  3611 W iMobile2: language is en
09-26 14:18:55.757  3611  3611 W iMobile2: Login_Sel Create
09-26 14:18:55.774  3611  3611 W iMobile2: SIGNATURE不一致
09-26 14:18:55.774  3611  3611 W iMobile2: APP has been modified!!
09-26 14:18:55.879  3637  3637 W iMobile2: class com.inventec.iMobile2.Login_Sel created
09-26 14:18:55.886  3637  3637 W iMobile2: Application not loaded
09-26 14:18:55.887  3637  3637 W iMobile2: on getDeviceInfo
09-26 14:18:55.896  3637  3637 W iMobile2: screen width:1080 height:1794 scale:2.625 density:2.625

It repeats essentially forever.

@zivillian
Copy link
Contributor Author

That is interesting:

09-26 14:18:55.774 3611 3611 W iMobile2: SIGNATURE不一致
09-26 14:18:55.774 3611 3611 W iMobile2: APP has been modified!

I'll try to find and remove this check, but since I'm not getting this error, can you try purging all app data and caches? I guess the app stores a checksum somewhere on the device and validates on subsequent launches.

@zivillian
Copy link
Contributor Author

@buxtronix I've published a new version which should pass the app modification test ;-)

@buxtronix
Copy link
Owner

Here's the registration debug log:

phev-registration-debug.txt

@zivillian
Copy link
Contributor Author

The XOR value is logged as s_key and r_key and seems to change with each send or received message (but not ping). This means it may be different for send and receive.

@zivillian
Copy link
Contributor Author

zivillian commented Oct 3, 2022

Found it. The following code generates a byte array with the XOR values used.

byte[] CalculateXorTable(int security_key)
{
    var result = new byte[256];
    for (int i = 0; i < result.Length; i++)
    {
        result[i] = (byte)i;
    }
    var index = 0;
    for (int i = 0; i < 256; i++)
    {
        index += result[i];
        index += security_key;
        index %= 256;
        var temp = result[i];
        result[i] = result[index];
        result[index] = temp;
    }
    return result;
}

The security_key is the argument for this function. The byte sequences match the values for s_key and r_key from your log.

The next question is, when and how a new security_key is calculated. From your log I would say, that it's rotated after 60 ping packets, but that's just a wild guess.

@buxtronix
Copy link
Owner

phev-update-debug.txt
phev-parkinglights-debug.txt

I've attached two more debug files. One is a general refresh of the car status from the app, and the other is the log from when the parking lights are turned on from the app. Perhaps they might give more insight?

@zivillian
Copy link
Contributor Author

zivillian commented Oct 4, 2022

I finally found the method which calculates (and logs) the security_key.
From that it is clear, that the security_key is only changed, when the app received a packet of type 0x5e or 0x4e with unset ACK byte. 0x4e switches to register layout B, 0x5e to layout A.
The security_key is then calculated from the LSB of each byte as stated above. The following code calculates the security_key for a packet:

byte CalculateSecurityKey(byte[] packet)
{
    var result = (packet[4] & 0x8) >> 3;
    result |= (packet[5] & 0x8) >> 2;
    result |= (packet[6] & 0x8) >> 1;
    result |= (packet[7] & 0x8);
    result |= (packet[8] & 0x8) << 1;
    result |= (packet[9] & 0x8) << 2;
    result |= (packet[10] & 0x8) << 3;
    result |= (packet[11] & 0x8) << 4;
    return (byte)result;
}

This can then be used to calculate the XOR KEY_MAP.
The index into the KEY_MAP starts at 0 and is tracked separate for sending and receiving packets. For sent packets it is increased after each packet of type 0xF6. For received packets the index is incremented after each packet of type 0x6F. After 255 it just overflows to zero again. The indices are reset to zero when a new security_key is calculated.

I would say the algorithm for XOR is now well known. Next step is to understand the registration process.

@buxtronix
Copy link
Owner

I notice a few instances where security_key is changed without 0x5e or 0x4e, and I can't see a valid packet that it's derived from.

Example from one of the uploaded debugs:

10-04 21:01:15.631 19913 14857 W iMobile2: send: E5 04 00 01 00 EA
10-04 21:01:15.633 19913 14857 W iMobile2: recv: 00 00 00 00 00 00
10-04 21:01:15.676 19913 14857 W iMobile2: -------security_key------ 161
10-04 21:01:15.677 19913 14857 W iMobile2: send: E5 04 01 01 00 EB
10-04 21:01:15.679 19913 14857 W iMobile2: -------KEY_MAP[0]------ 155
10-04 21:01:15.680 19913 14857 W iMobile2: recv: 42 18 1D 1D 1C 78 5E 0C 00 01 D8 83 D0 F2 37 CF E1 1D 01 8D
10-04 21:01:15.682 19913 14857 W iMobile2: 1
10-04 21:01:15.683 19913 14857 W iMobile2: 2

I can try to fetch more from the app when ext near the car.

@zivillian
Copy link
Contributor Author

This is misleading, because the app is logging multiple packets in a single row:

10-04 21:01:15.680 19913 14857 W iMobile2: recv: 42 18 1D 1D 1C 78 5E 0C 00 01 D8 83 D0 F2 37 CF E1 1D 01 8D

The relevant packet starts at byte 7: 5E 0C 00 01 D8 83 D0 F2 37 CF E1 1D 01 8D

@buxtronix
Copy link
Owner

Ah that explains it.

So I have implemented the xor algorithm in a local copy of phev2mqtt and it's looking really good at least against captured data, it all matches up. I'll try later with a live connection to the car too.

This basically means that the "start" packets that were assumed are actually security key init packets. Not sure if the types and consts should be renamed...

@buxtronix
Copy link
Owner

Tested against my MY18 and it's working!

Applied these changes in 3db6f83

Certainly more reliable than the previous method of guessing, however I do still occasionally get something going out of sync, but similar to what I got before.

time="2022-10-05T20:33:36+11:00" level=debug msg="%PHEV_TCP_SEND_MSG%: [fe] REGISTER ACK  (reg 0x0c data 00 xor 254)"
s_key fe->5b
r_key 5b->1e
time="2022-10-05T20:33:36+11:00" level=debug msg="%PHEV_TCP_RECV_MSG%: [5b] REGISTER NTFY (reg 0x0d data 01 xor 91)"
time="2022-10-05T20:33:36+11:00" level=debug msg="%PHEV_TCP_SEND_MSG%: [5b] REGISTER ACK  (reg 0x0d data 00 xor 91)"
s_key 5b->1e
r_key 1e->05
time="2022-10-05T20:33:36+11:00" level=debug msg="%PHEV_TCP_RECV_MSG%: [1e] REGISTER NTFY (reg 0x1d data 5e000000 xor 30)"
time="2022-10-05T20:33:36+11:00" level=debug msg="%PHEV_TCP_SEND_MSG%: [1e] REGISTER ACK  (reg 0x1d data 00 xor 30)"
s_key 1e->05
time="2022-10-05T20:33:50+11:00" level=debug msg="%PHEV_TCP_RECV_MSG%: [05] START RESP18  (orig: 2f03000133 xor 5)"
time="2022-10-05T20:33:50+11:00" level=debug msg="%PHEV_TCP_SEND_MSG%: [05] PING REQ      (id a xor 5)"
time="2022-10-05T20:33:50+11:00" level=debug msg="%PHEV_TCP_RECV_MSG%: [05] PING RESP     (id a xor 5)"
time="2022-10-05T20:33:50+11:00" level=debug msg="%PHEV_TCP_RECV_MSG%: [05] BAD ENCODING  (exp: 0x89)"
time="2022-10-05T20:34:05+11:00" level=debug msg="%PHEV_TCP_RECV_MSG%: [05] START RESP18  (orig: 2f03000133 xor 5)"
time="2022-10-05T20:34:05+11:00" level=debug msg="%PHEV_TCP_SEND_MSG%: [05] PING REQ      (id a xor 5)"
time="2022-10-05T20:34:05+11:00" level=debug msg="%PHEV_TCP_RECV_MSG%: [05] Cmd: 0xcc () (len 6), Register 0x7f, Data: 89"
time="2022-10-05T20:34:05+11:00" level=debug msg="%PHEV_TCP_RECV_MSG%: [05] BAD ENCODING  (exp: 0x38)"
time="2022-10-05T20:34:20+11:00" level=debug msg="%PHEV_TCP_RECV_MSG%: [05] START RESP18  (orig: 2f03000133 xor 5)"
time="2022-10-05T20:34:20+11:00" level=debug msg="%PHEV_TCP_SEND_MSG%: [05] PING REQ      (id a xor 5)"
time="2022-10-05T20:34:20+11:00" level=debug msg="%PHEV_TCP_RECV_MSG%: [05] Cmd: 0xcc () (len 6), Register 0xce, Data: 38"
time="2022-10-05T20:34:20+11:00" level=debug msg="%PHEV_TCP_RECV_MSG%: [05] BAD ENCODING  (exp: 0x2d)"
time="2022-10-05T20:34:35+11:00" level=debug msg="%PHEV_TCP_RECV_MSG%: [05] START RESP18  (orig: 2f03000133 xor 5)"

@buxtronix
Copy link
Owner

I have also opened #18 to reverse engineer the registration process.

@zivillian zivillian changed the title [Discussion] Further protocol reversing - XOR algorithm [Discussion] Further protocol reversing Oct 7, 2022
@zivillian
Copy link
Contributor Author

It's interesting, that the procotol has more features than the app. E.g. the app supports a climate timer duration of 40 minutes if reported by the car, but does not offer this option in the UI:

grafik

I send the following packet to the app: 6F13000500FF03020000000000000000000000018C

I can't tell what the car would say to such a timer...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants