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

Subaru SSM4 2020-2022 #25

Open
jnewb1 opened this issue May 26, 2023 · 80 comments
Open

Subaru SSM4 2020-2022 #25

jnewb1 opened this issue May 26, 2023 · 80 comments

Comments

@jnewb1
Copy link

jnewb1 commented May 26, 2023

ECU Name
87501AN010

(Subaru 2020-2022 legacy/outback eyesight module. Probably also applicable to other subaru cars and ECUs.)

Source file
SSM4 can unlock this ecu. There are two dlls in this zip file. CMD_TwoPhasesOfCertification within CMD_FhiCAan seems to be the entrypoint for the seed/key exchange. I have attached a debugger with IDA and I can step through the process, but not quite sure where to go from there. There appears to be a database lookup for each ECU.

VsmDataLib.zip

Additional context

I captured some seed/key exchanges on my CAN bus. They are 16 byte seed/key pairs using the extended CAN format. They could be byte or frame swapped, I'm not 100% percent sure, but I'll work on verifying that.

(0xf8ee1609ec9ab91c8358060fdebc3e06,0xaa21c0117cf3dcb6bc1f7a7f90ee78ca),
(0x63c1fbe1248b0ab0d8a0fb8606e37b13,0x56da42fe1e9c6f936fda699ed253a2d7),
(0xd13dae85a592bb91672da8fe8315a1a3,0x8b5148b5ff3a00db72117760bc6e43c4),
(0xfeff4608b40e174f9fff21b3dc7fb46d,0xf5f7466d8e316f640e381fad5532ec14),
(0x02d3766aadc6621e12d50ddc2adc5038,0xdb2a108b77e977f8d7b0871a6eceb4b8),
(0xc01ef055314fb71fc0794b3440ec9577,0x0a6fe147394f74fc36655e2d99b1eb4a),
(0xecc763b6f9d31719eabcd5a6e3e1319e,0x88a2eb332caf13e138520424fbb46891),
(0x215c2719701b6de846e7461987134079,0x67b339ff36495fe6c8ab25efb82fb756)

@jnewb1 jnewb1 changed the title Subaru SSM4 Seed/Key algorithm Subaru SSM4 2020-2022 May 26, 2023
@jnewb1
Copy link
Author

jnewb1 commented May 26, 2023

Here's a seed/key exchange i captured:

message as hex description
0227030000000000 Seed request sent by diag tool
10126703f8ee1609 Seed First Frame, size 16, data 0x026703f8ee1609 (seed response, packet 0: f8ee1609)
21ec9ab91c835806 Seed Consecutive frame, packet 1: ec9ab91c835806
220fdebc3e060000 Seed Consecutive frame, packet 2: fdebc3e06
FULL SEED f8ee1609ec9ab91c8358060fdebc3e06
10122704aa21c011 Key First Frame, size 16, data 0x022704aa21c011 (key response, packet 0: aa21c011)
217cf3dcb6bc1f7a Key Consecutive frame, packet 1: 7cf3dcb6bc1f7a
227f90ee78ca0000 Key Consecutive frame, packet 2: 7f90ee78ca
FULL KEY aa21c0117cf3dcb6bc1f7a7f90ee78ca
0267040000000000 Unlock success

@jglim
Copy link
Owner

jglim commented May 26, 2023

Hello and welcome, Justin,

I took a look at the library, and while I was not able to follow the calls, I have a hunch that it's an AES-based algo.

Here are some notes from looking at CMD_FhiCan, I hope they might come in helpful:


0x1000D8C0 : send channel message; describes what the client is writing to the ECU
- ecx: rx buffer
- ebp+0x10: tx buffer
- ebp+0x14: size (in: tx size, out: rx size)

For example, CMD_TwoPhasesOfCertification calls external functions for unlock commands, then appears to send 31 01 02 00 in the function at 0x10029470. If this function also handles the 27 xx messages, it would be a helpful debugging lead for working backwards to find the algo.


Possibly CMD_SecurityAccessAES_AB @ 0x10007400, or CMD_SecurityAccess2018CY1 @ 0x10007990?
Expected seed length is 16, key length is 16 which matches captured frames.
Seems to handle 2702/2703, 2704/2705
Neither have direct xrefs, but they could be imported elsewhere or called indirectly through GetProcAddress

AES initialization is done at 0x1004A610, and both aes-based algos call this common function

  • context at edi,
  • key buffer at ebp+8
  • key size (always 16) at ebp+0xC.

If a breakpoint at the initialization is hit, the key can be likely plucked out from the memory.


There also appears to be a bunch of non-aes algos (CMD_SecurityAccess_Ocpt, CMD_SecurityAccess) that do not match your traces as they take a 4 byte seed and return a 4 byte key.

@Feezex
Copy link
Contributor

Feezex commented May 26, 2023

27 03 > seed request level 3 > 0000000000
67 03 >seed > f8ee1609
21 > ec9ab91c835806
22 > 0fdebc3e060000
27 04 > seed request level 4 > aa21c011
......
67 04 > 0000000000

Very similar to daimler

@jnewb1
Copy link
Author

jnewb1 commented May 26, 2023

Thank you for looking, that is super helpful!

Possibly CMD_SecurityAccessAES_AB @ 0x10007400, or CMD_SecurityAccess2018CY1 @ 0x10007990? Expected seed length is 16, key length is 16 which matches captured frames. Seems to handle 2702/2703, 2704/2705 Neither have direct xrefs, but they could be imported elsewhere or called indirectly through GetProcAddress

I'll look for references to those functions. Whole SSM4 program has like 25 dlls, but I can try and find it

If a breakpoint at the initialization is hit, the key can be likely plucked out from the memory.

Do you think its a constant AES key for all cars, or does it somehow use the ECU address in a lookup table? I can hookup the debugger and try and capture that key, but I'm not super experienced in IDA.

There also appears to be a bunch of non-aes algos (CMD_SecurityAccess_Ocpt, CMD_SecurityAccess) that do not match your traces as they take a 4 byte seed and return a 4 byte key.

Older Subarus have much shorter seed/keys, so that is most likely what that is for. This seed/key pair is from a 2018 crosstrek

0x019797
0x057028

@jglim
Copy link
Owner

jglim commented May 26, 2023

The keys should be variant-specific. Both aes-based algos begin by initiating a 22 xx xx ReadDataByIdentifier request (0x1000DF60), presumably to fetch the ECU variant. They will then fetch the variant-specific aes key via ApiTable::CVsmRsKeyTableAES in VsmDatabase.dll.

(Before digging deeper into the key tables, it might be good to confirm if this is indeed the correct algo first.)

Got it w.r.t. older seed/keys, thanks! Incidentally, those also seem variant-specific, using ApiTable::CVsmRsKeyTable this time.


@Feezex It does look very much like UDS

ECU << `27 03`
ECU >> `67 03 F8 EE 16 09 EC 9A B9 1C 83 58 06 0F DE BC 3E 06`
ECU << `27 04 AA 21 C0 11 7C F3 DC B6 BC 1F 7A 7F 90 EE 78 CA`
ECU >> `67 04`

@jnewb1
Copy link
Author

jnewb1 commented May 26, 2023

looks like CMD_SecurityAccess2018CY1 is the one. have you used the trace recorder in ida? I'd like to be able to just record a full trace of the exchange and send it to you.

image
image

@jnewb1
Copy link
Author

jnewb1 commented May 26, 2023

The keys should be variant-specific. Both aes-based algos begin by initiating a 22 xx xx ReadDataByIdentifier request (0x1000DF60), presumably to fetch the ECU variant. They will then fetch the variant-specific aes key via ApiTable::CVsmRsKeyTableAES in VsmDatabase.dll.

Lots of RDBI. Here is one of them that is long, and also has two different requests with different length responses (back to back)

0x0322f18200000000
RDBI REQUEST: 0x00f1
0x100d62f182000065
0x219a001f40203100
RDBI RESPONSE: 0x00f1 0x820000659a001f40203100 (11)
0x0322f19700000000
RDBI REQUEST: 0x00f1
0x102362f197457965
0x2153696768742053
0x22797374656d2020
0x2320202020202020
0x2420202020202020
0x2520000000000000
RDBI RESPONSE: 0x00f1 0x9745796553696768742053797374656d2020202020202020202020202020202020 (33)

Heres another one?

0x0322101e00000000
RDBI REQUEST: 0x0010
0x100962101e052500
0x2100026400000000
RDBI RESPONSE: 0x0010 0x1e052500000264 (7)

Here's a list of unique DID's that appear before the seed request:

0x0002 - 2 byte response
0x0020 - 2 byte response
0x0010 - 7 byte response
0x00ff - 5 byte response
0x00f1 - 33 or 11 byte response
0x0022, 0x0024, 0x0030, 0x0031, 0x0040 - 0x0044 - 5 byte response (probably just diagnostic data)

there is also an unanswered request for 0x0070?

@jglim
Copy link
Owner

jglim commented May 26, 2023

The IDA version that I am running is rather dated, unfortunately I don't think trace recorder is going to work out.

Instead, can you place a breakpoint during the key initialization (see below) and go through the security access?

When the breakpoint is hit, read the ESI register which contains a pointer to the key. Read the process memory at the location specified by ESI; the first 16 bytes should be the raw, un-expanded aes key.

image

I haven't seen an aes IV around, if the vendor is using regular, unmodified aes, I would think that this is aes-ecb.

@jnewb1
Copy link
Author

jnewb1 commented May 26, 2023

esi: 0x7692e7932f23a901568ddfa5ff580625

image

@jglim
Copy link
Owner

jglim commented May 26, 2023

I haven't been able to get anything out of aes-ecb. There could be a few reasons,

  • Maybe it isn't ecb (different cipher mode?). I played with the values in CyberChef but none of the other cipher modes produced anything meaningful.
  • Maybe their "aes" is slightly modified e.g. byte ordering
  • Maybe I picked a wrong spot to dump the key. I'll check the disassembly again. If those bytes are different when dumped again, then I definitely made a mistake there.
testing aeskey 76 92 E7 93 2F 23 A9 01 56 8D DF A5 FF 58 06 25
enc 96 EC 1D AE 8C 2B BE 76 8D 41 FE 00 BE A2 05 5B
dec 50 94 43 D2 69 E9 E8 BF 34 78 EB 4D 5C 5B 0A AA

testing aeskey 25 06 58 FF A5 DF 8D 56 01 A9 23 2F 93 E7 92 76 (reversed)
enc 18 DD C1 E4 45 12 AE B7 85 82 9B 1A F7 B8 4A 98
dec C3 D8 41 38 D3 D1 9B 9B 67 5B 30 B0 0F CA 34 03

@jnewb1
Copy link
Author

jnewb1 commented May 26, 2023

I ran it several times and got the same value every time, even after restarting the car. But I also get the same result when trying to query any other ECU on the car, so unless they all share the same key, that might be something else.

@jnewb1
Copy link
Author

jnewb1 commented May 26, 2023

i wrote something to test a bunch of possible things that could be flipped, none of them worked though

from Crypto.Cipher import AES

seed_key_gen2 = [
    (0xf8ee1609ec9ab91c8358060fdebc3e06,0xaa21c0117cf3dcb6bc1f7a7f90ee78ca),
    (0x63c1fbe1248b0ab0d8a0fb8606e37b13,0x56da42fe1e9c6f936fda699ed253a2d7),
    (0xd13dae85a592bb91672da8fe8315a1a3,0x8b5148b5ff3a00db72117760bc6e43c4),
    (0xfeff4608b40e174f9fff21b3dc7fb46d,0xf5f7466d8e316f640e381fad5532ec14),
    (0x02d3766aadc6621e12d50ddc2adc5038,0xdb2a108b77e977f8d7b0871a6eceb4b8),
    (0xc01ef055314fb71fc0794b3440ec9577,0x0a6fe147394f74fc36655e2d99b1eb4a),
    (0xecc763b6f9d31719eabcd5a6e3e1319e,0x88a2eb332caf13e138520424fbb46891),
    (0x215c2719701b6de846e7461987134079,0x67b339ff36495fe6c8ab25efb82fb756),
]

seed_key_gen1 = [
    (0x19797, 0x57028)
]

possible_secret_keys = [
    0x7692e7932f23a901568ddfa5ff580625,
    0x558bec8b4d0885c9750633c05dc20400,
    0xd8d012189079455fe80dc30fb0c7f70c
]

possible_methods = [
    AES.MODE_CBC,
    AES.MODE_ECB,
    AES.MODE_CFB,
    AES.MODE_OFB,
    AES.MODE_CTR,
    #AES.MODE_OPENPGP,
    AES.MODE_CCM,
    AES.MODE_EAX,
    #AES.MODE_SIV,
    AES.MODE_GCM,
    AES.MODE_OCB
]

def unlock_gen2(seed, secret_key, method, secret_key_byteorder, seed_byteorder, key_byteorder, seed_as_key=False, encrypt=False):
    secret_key = secret_key.to_bytes(16, byteorder=secret_key_byteorder)
    seed = seed.to_bytes(16, byteorder=seed_byteorder)

    if seed_as_key:
        cipher = AES.new(seed, method)
        if encrypt:
            key = cipher.encrypt(secret_key)
        else:
            key = cipher.decrypt(secret_key)
    else:
        cipher = AES.new(secret_key, method)
        if encrypt:
            key = cipher.encrypt(seed)
        else:
            key = cipher.decrypt(seed)

    key = int.from_bytes(key, byteorder=key_byteorder)

    return key

possible_byteorder = ["little", "big"]
booleans = [True, False]

max_error = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
best_error = max_error
best_set = None

if __name__ == "__main__":

    for pair_to_use in range(len(seed_key_gen2)):
        for method in possible_methods:
            for secret_key in possible_secret_keys:
                for seed_byteorder in possible_byteorder:
                    for secret_key_byteorder in possible_byteorder:
                        for key_byteorder in possible_byteorder:
                            for seed_as_key in booleans:
                                for encrypt in booleans:
                                    seed = seed_key_gen2[pair_to_use][0]
                                    real_key = seed_key_gen2[pair_to_use][1]
                                    key = unlock_gen2(seed, secret_key, method, secret_key_byteorder, seed_byteorder, key_byteorder, seed_as_key, encrypt)

                                    if key == real_key:
                                        raise Exception("LFG!!")
                                
                                    error = abs(real_key - key)

                                    if error < best_error:
                                        best_error = error
                                        best_set = (pair_to_use, method, secret_key, seed_byteorder, secret_key_byteorder, key_byteorder, seed_as_key, encrypt, key, real_key)


    print("no bueno...")
    print(f"{max_error:032x}")
    print(f"{best_error:032x}")
    print(best_set)

@jglim
Copy link
Owner

jglim commented May 27, 2023

The aes looks like vanilla ECB. I've ripped the aes-specific code out from the dll into a single assembly file.
aes.zip

This builds with masm32 V11 (masm32v11r):

c:\masm32\bin\ml /c /coff /Cp aes_dbg.asm
c:\masm32\bin\link /SUBSYSTEM:CONSOLE /LIBPATH:c:\masm32\lib aes_dbg.obj
>aes_dbg.exe

Cipher : 96EC1DAE8C2BBE768D41FE00BEA2055B
Plain  : F8EE1609EC9AB91C8358060FDEBC3E06
Key    : 7692E7932F23A901568DDFA5FF580625

The cipher 96EC1DAE8C2BBE768D41FE00BEA2055B matches my aes-ecb encrypt test above. The ripped instructions are almost fully identical to those in the dll, down to the register level. I'm guessing that there might have been a hiccup during the key dumping process.


Could you perhaps try this instead:

  • Place a breakpoint at 0x1004A8C0 (aes encrypt, after key expansion)
  • Go through the unlock, the breakpoint should be hit and the process should freeze
  • Register ECX will hold a pointer to the 16-byte original seed from the ecu (check if it matches? if not, there's an issue here)
  • Register EDI will hold a pointer to a ~252-byte expanded key. Can you dump this value?

@jnewb1
Copy link
Author

jnewb1 commented May 27, 2023

I ran it a couple times:

ESI: 7692e7932f23a901568ddfa5ff580625
ECX: 8759e486dcf72a0659c7b07ba523bae9
EDI: 7692e7932f23a901568ddfa5ff5806251dfdd88532de71846453ae219b0ba804343f2a9106e15b1562b2f534f9b95d3066732e086092751d02208029fb99dd1980b2fa07e0208f1ae2000f331999d22a7e071fd39e2790c97c279ffa65be4dd0f0e46f9e6ec3ff5712e460ad775a2d7d0e3c906b60ff6f3c721b0f91054122ec0daf5e006d50313c1f4b3ead1a0a1c417133dda21c63ec9e0328d2331922ce72d4b89d76c8db71e8cbf3a3dbd2d16da9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000

ESI: 7692e7932f23a901568ddfa5ff580625
ECX: 3cf9b9c8a1027379c84720dee7ded657
EDI: 7692e7932f23a901568ddfa5ff5806251dfdd88532de71846453ae219b0ba804343f2a9106e15b1562b2f534f9b95d3066732e086092751d02208029fb99dd1980b2fa07e0208f1ae2000f331999d22a7e071fd39e2790c97c279ffa65be4dd0f0e46f9e6ec3ff5712e460ad775a2d7d0e3c906b60ff6f3c721b0f91054122ec0daf5e006d50313c1f4b3ead1a0a1c417133dda21c63ec9e0328d2331922ce72d4b89d76c8db71e8cbf3a3dbd2d16da9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000

ESI: 7692e7932f23a901568ddfa5ff580625
ECX: 4172e4fedcaa0ee05f4a7d3b5587dce3
EDI: 7692e7932f23a901568ddfa5ff5806251dfdd88532de71846453ae219b0ba804343f2a9106e15b1562b2f534f9b95d3066732e086092751d02208029fb99dd1980b2fa07e0208f1ae2000f331999d22a7e071fd39e2790c97c279ffa65be4dd0f0e46f9e6ec3ff5712e460ad775a2d7d0e3c906b60ff6f3c721b0f91054122ec0daf5e006d50313c1f4b3ead1a0a1c417133dda21c63ec9e0328d2331922ce72d4b89d76c8db71e8cbf3a3dbd2d16da9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000

ESI: 7692e7932f23a901568ddfa5ff580625
ECX: e40df21a14d989053877f21361fd185c
EDI: 7692e7932f23a901568ddfa5ff5806251dfdd88532de71846453ae219b0ba804343f2a9106e15b1562b2f534f9b95d3066732e086092751d02208029fb99dd1980b2fa07e0208f1ae2000f331999d22a7e071fd39e2790c97c279ffa65be4dd0f0e46f9e6ec3ff5712e460ad775a2d7d0e3c906b60ff6f3c721b0f91054122ec0daf5e006d50313c1f4b3ead1a0a1c417133dda21c63ec9e0328d2331922ce72d4b89d76c8db71e8cbf3a3dbd2d16da9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000

Perhaps you should also verify my methodology:
(I tried with true and false for the use_dbg flag, both got same results)

Breakpoint at 77E1A: (push esi)

addr = get_reg_value("esi")
key = get_bytes(addr, 16, False)

print(f"ESI: {key.hex()}")

Breakpoint at 77E36: (call unk_5F4BA8C0)

addr = get_reg_value("ecx")
seed = get_bytes(addr, 16, False)
print(f"ECX: {seed.hex()}")

addr = get_reg_value("edi")
expanded_key = get_bytes(addr, 252, False)
print(f"EDI: {expanded_key.hex()}")

image

@jnewb1
Copy link
Author

jnewb1 commented May 27, 2023

Aha! I found the key!

33e63ca0431153460c18f1064c70fe41

Turns out the authentication is done twice, the second time gets the correct key. Maybe the first request is to some other module in the car? We will probably have to figure that out.

def unlock_gen2(seed):
    secret_key = 0x33e63ca0431153460c18f1064c70fe41

    secret_key = secret_key.to_bytes(16, byteorder='big')
    seed = seed.to_bytes(16, byteorder='big')

    cipher = AES.new(secret_key, AES.MODE_ECB)
    key = cipher.encrypt(seed)

    return int.from_bytes(key, byteorder='big')

@jglim
Copy link
Owner

jglim commented May 27, 2023

Your methodology looks good. ESI (aes key) and the first 16 bytes of EDI (expanded key) should point to different address and still remain identical, which is correct in your captures.

I ran those values with the ripped code from the DLL, and the expanded key fully matches your captures too, so at least that isn't a worry.

image


Here's a few more breakpoint suggestions to validate that the aes bits are working. The new additions include capturing the original seed response from the ecu, as well as the completed key response to the ecu.

Breakpoints, in order:

  • 0x10007E1A: before expansion, ESI -> original key[16]. Should remain at 7692e7932f23a901568ddfa5ff580625
  • 0x1004A8C0: before encryption,
    • EDI -> full key schedule[252];
    • ECX -> seed component[16]. ECX - 2 will show the entire message (e.g. 67 0X .. ..)
  • 0x10007EE6: after encryption, EDX -> unlock message[18] (prefixed by 27 0X ..)

Right before I could post, I just saw your reply with the correct key. I'm still sharing the original content to confirm that your methodology is fine. Awesome to hear that you're on the right track!

@jnewb1
Copy link
Author

jnewb1 commented May 27, 2023

Right before I could post, I just saw your reply with the correct key. I'm still sharing the original content to confirm that your methodology is fine. Awesome to hear that you're on the right track!

Thank you so much for the help! You are truly incredible.

I will do some more experimenting. I’m curious what the first unlock thing is for, and if that part is required to unlock this ECU.

if you are curious, this is for an open source driving assistant program called openpilot: https://github.com/commaai/openpilot

@jglim
Copy link
Owner

jglim commented May 27, 2023

Glad that I could be of help. Good luck with your work on openpilot; that project does seem very fascinating.

On multiple unlocks, I've seen some ecus require a security access progression, like 27 09, 27 0A then 27 0B, 27 0C. If the messages are addressed to the same target, and use different levels, this could be a reason.

I'll find time to add a new subaru aes algo, along with a definition for 33e63ca0431153460c18f1064c70fe41. Should you happen to figure out more keys e.g. from CVsmRsKeyTable, I would be excited to hear more about it too.

@jnewb1
Copy link
Author

jnewb1 commented May 27, 2023

I just verified that this algorithm does in fact unlock the ECU, and I am able to read the data that I was trying to read from the ECU! To support more than just my car, we will probably need to find more keys from the CVsmRsKeyTable, so I will let you know when we stumble across those!

@jglim
Copy link
Owner

jglim commented May 30, 2023

I've just added an AES provider in c4f693d , and also dumped the AES keys that I could find; the earlier AES key 7692E7932F23A901568DDFA5FF580625 is present as Subaru_2FEA/Subaru_2FE9. The dumping part is still quite preliminary and I might need a while more to remove the windows-specific CryptAPI bits.

"Value": "7692E7932F23A901568DDFA5FF580625",

@jglim
Copy link
Owner

jglim commented Jun 14, 2023

As a follow up to the earlier post, this snippet decrypts the Subaru XML definitions without depending on MS CryptoAPI:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace SubaruXmlDecrypt
{
    class Program
    {
        static readonly string HelpText = "Specify/dragdrop a file or directory onto this application. The file(s) will be decrypted in-place (overwritten)";

        static byte[] AesManagedDecrypt(byte[] cipher)
        {
            byte[] Key = {
                0x46, 0x6A, 0x41, 0xAA, 0x5C, 0xE0, 0xFA, 0xF8, 0xF0, 0x2F, 0x15, 0x48, 0xEC, 0xE2, 0xC2, 0xCD,
                0x1B, 0x5B, 0x01, 0x84, 0xA9, 0x3F, 0xF7, 0x62, 0xBF, 0x14, 0x65, 0xF8, 0x10, 0xD4, 0x8C, 0x9E,
            };
            byte[] IV = {
                0xA8, 0xB0, 0xC8, 0xC9, 0x6F, 0x9B, 0xAF, 0xB8,  0xBE, 0xC2, 0xC2, 0xA0, 0x89, 0x85, 0xB4, 0x8C,
            };

            AesManaged aes = new AesManaged();
            aes.Mode = CipherMode.CBC;
            aes.IV = IV;
            aes.Key = Key;
            aes.Padding = PaddingMode.PKCS7;

            return aes.CreateDecryptor().TransformFinalBlock(cipher, 0, cipher.Length);
        }
        
        static void Main(string[] args)
        {
            if (args.Length != 1)
            {
                Console.WriteLine($"Expecting one arg, received {args.Length}:");
                foreach (var arg in args)
                {
                    Console.WriteLine($"> {arg}");
                }
                PrintHelp();
            }
            else 
            {
                string path = args[0];
                DecryptFileOrDirectory(path);
            }
            Console.ReadKey();
        }

        static void PrintHelp() 
        {
            Console.WriteLine(HelpText);
        }

        static void DecryptFileOrDirectory(string path) 
        {
            if (Directory.Exists(path))
            {
                foreach (var file in Directory.GetFiles(path))
                {
                    File.WriteAllBytes(file, AesManagedDecrypt(File.ReadAllBytes(file)));
                    Console.WriteLine($"Decrypted {file}");
                }
                Console.WriteLine($"Decrypting all files in {path}");
            }
            else if (File.Exists(path))
            {
                File.WriteAllBytes(path, AesManagedDecrypt(File.ReadAllBytes(path)));
                Console.WriteLine($"Decrypted {path}");
            }
            else
            {
                PrintHelp();
            }
        }
    }
}

The XML files are comprehensive and readable; apart from the AES keys, they also include the "regular" standard security algo key components:

image

There's also a bunch of "SpecialFunctionKey" credentials which doesn't seem related to ECU unlocking but could come in handy too:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<SpecialFunctionKeyTable Version="18.00a" Brand="SUBARU" EcuId="12022">
  <SpecialFunctionKey Id="1" ReplaceId="F_GST" ReplaceType="2" />
  <SpecialFunctionKey Id="2" ReplaceId="F_GST" ReplaceType="2" />
  <SpecialFunctionKey Id="1" ScreenId="3" FunctionId="902" Keycode="$0000C048" Password="3689" />
  <SpecialFunctionKey Id="2" ScreenId="3" FunctionId="902" Keycode="$0000C04A" />
</SpecialFunctionKeyTable>

@jnewb1
Copy link
Author

jnewb1 commented Jun 27, 2023

The XML files are comprehensive and readable; apart from the AES keys, they also include the "regular" standard security algo key components:

What do you think the "Version" field is for? I was looking at trying to dump the ECU ROM, but no luck with the default key. I don't think its the security level, because that's included elsewhere in other ECU's.

<KeyTableAES Version="1" CryptKey="$33,$E6,$3C,$A0,$43,$11,$53,$46,$0C,$18,$F1,$06,$4C,$70,$FE,$41" />
<KeyTableAES Version="16" CryptKey="$A8,$08,$35,$0D,$2B,$AF,$20,$84,$F0,$A5,$C1,$07,$90,$A5,$D5,$06" />
<KeyTableAES Version="32" CryptKey="$29,$5A,$6E,$BA,$EC,$78,$D7,$74,$D8,$AA,$C1,$E0,$B3,$B5,$75,$0C" />

maybe a different AES mode or something?

@jglim
Copy link
Owner

jglim commented Jun 28, 2023

I haven't seen any directly referenced "Version" strings yet, though I have a hunch that it might be related to the 22 xx xx request that is sent directly before key computation. (CMD_FhiCan.dll, 10007A3B)

If you're feeling adventurous, there's a bunch of AES keys in https://github.com/jglim/UnlockECU/blob/main/UnlockECU/db.json under "Provider": "SubaruSecurityAccess2018CY1". There are about 33 unique entries, so it might be feasible to try them all and work backwards from there if there is a successful key.

469A20AB308D5CA64BCD5BBE535BD85F
E8CC52D5D8F20706424813126FA7ABDD
E40EAFE45074A0A0BE9917B0BF59F99B
9A711B32ACC0FF4089A72545416470C6
7D89DDE1C95A224ED723E04496F4C0AE
5C9F97ADE51DA6D0609D49BB05A417E9
66D5364A0D2D45C67E8DB120ED471438
37490E2C46C57F8CB22FEC48133965D6
AEA6D9296786430A22A084EBA4498EE3
0B4BB2772B2119F733FC8C3A7304F022
92BF3DA9EADC2EB50E1A7BC13983899D
AECD1B5CDDE980BFC6869AB2D3DAEA1A
00F17B760F53C30CBFFB54AD808F497A
76E19EDB3027B6C51C8E90CED15E59BA
8C15AC1E0EDFB84D8A1C1526CDFF0850
1615239AB78925A41F18E6AA07673581
0F84E1C95C8277A370E6CE71BFA6831D
9F18E548DCA83D6BAED30ECAAE2D0DDD
6A673120695FD8431286925F8601F955
8B1103CEFC35D915FDA0D869542854DC
1BF6344AA6256C70E45D8754CD653FB2
7692E7932F23A901568DDFA5FF580625
1B8E5CE1CA38AEB6B4AC07C62FC8D769
33E63CA0431153460C18F1064C70FE41
A808350D2BAF2084F0A5C10790A5D506
295A6EBAEC78D774D8AAC1E0B3B5750C
BCB8039CCDFCF5C82D4803E50F399F26
9978D5FA22B0C4E78B5D6451B2F7B7B5
35371DE1F774FC0123A8AE67A725D1BB
3C74A203B92DC74A0BC9D36489223F0A
7168633A55270B3BA4E873BBBC248EC6
18852C9509C8610F3E894CD8F23EB30D
0D816973C75E8B38B920061FCEDBCBAE

As for different AES modes, the algo in CMD_SecurityAccessAES_AB seems to use similar AES parameters except it decrypts the seed (CMD_SecurityAccess2018CY1 encrypts the seed)

@jglim
Copy link
Owner

jglim commented Jun 28, 2023

Also on a tangent, I grabbed the only copy of SSM4 that I could find online, which ships with a third-party "ssm4_loader.exe". The program seems to run fine without using the loader application as the activation dialog comes with a working skip button.

I'm still looking into a more permanent solution to this; If you happen to be using the same version of SSM4 (26.6.0), could you help read out these values:

(1) Product ID, as shown on the activation dialog
(2) The value at HKLM\SOFTWARE\WOW6432Node\SUBARU CORPORATION\SUBARU Select Monitor 4\Conf\Device1

@jnewb1
Copy link
Author

jnewb1 commented Jun 28, 2023

If you happen to be using the same version of SSM4

I believe I have the same version, except the skip button is greyed out on mine. I'm pretty sure how ssm4_loader works is that it reads the memory of the program and enables the skip button, then clicks it.

@jglim jglim mentioned this issue Nov 1, 2023
@OmegaKZ
Copy link

OmegaKZ commented Dec 10, 2023

Hello.
Based on your information I decoded the XML files from SSM4 and was able to access some ECUs in my 2017 Subaru Forester.
Also, using IDA, I was able to “extract” the Seed-Key algorithm used for some ECUs (in particular the METER or MFD).

I'm currently trying to access EyeSight cameras to read the firmware or Coding.
I was able to obtain the firmware data that is used to update the EyeSight (from Subaru FlashWrite software), but the firmware is sent to EyeSight in encrypted form and EyeSight decrypts the firmware when it receives all the data.

I want to try to read the firmware from the EyeSight for its further modification.
Maybe you have some experience in this direction or maybe you managed to read the firmware from the EyeSight?

@jglim
Copy link
Owner

jglim commented Dec 12, 2023

Hey OmegaKZ,

I don't have the expertise to answer your question, but I'd like to suggest this thread where jnewb1 and Manevolent are also looking at Subaru-related encryption.

These are their respective repos:

@jglim
Copy link
Owner

jglim commented Dec 22, 2023

The ecu could be soft-bricked and potentially recoverable. On some MB ecus, a prerequisite for flashing is to send an erase command first, then start writing in the firmware. If the process is interrupted or if the firmware is modified, the ecu should remain in a failsafe boot mode that can still receive firmware over the bus.

Here's a summarized transcript of a flash session on a specific MB ecu:


// enter bootloader mode
host: 00 00 06 0a  10 02
ecu : 00 00 04 81  7f 10 78 // pending
ecu : 00 00 04 81  50 02 00 14 00 c8


// read variant id (14e8), session=2
host: 00 00 06 0a  22 f1 00
ecu : 00 00 04 81  62 f1 00 01 14 e8 02


// request unlock seed
host: 00 00 06 0a  27 05
ecu : 00 00 04 81  67 05 15 3b cb 47 ef b6 9e 12


// send key
host: 00 00 06 0a  27 06 20 5a c3 78
ecu : 00 00 04 81  67 06


// write fingerprint (date = 19 02 21, vendor MB, fingerprint 00000000 blockid=01)
host: 00 00 06 0a  2e f1 5a  01  00 04  15 02 13  00 00 00 00
ecu : 00 00 04 81  6e f1 5a


// erase selected logical block  <--------
host: 00 00 06 0a  31 01 ff 00
ecu : 00 00 04 81  7f 31 78 // pending
ecu : 00 00 04 81  71 01 ff 00 00


// request download:
//...............  req     alfid    dest 0xF2000    size c428
host: 00 00 06 0a  34 00   44       00 0f 20 00     00 00 c4 28
//...............  ack     alfid    maxbs = 0xf02
ecu : 00 00 04 81  74      20       0f 02


// transfer data:
host: 00 00 06 0a  36 01 .. .. .. .. (truncated)
ecu : 00 00 04 81  7f 36 78 // pending
ecu : 00 00 04 81  76 01

host: 00 00 06 0a  36 02 .. .. .. .. (truncated)
ecu : 00 00 04 81  7f 36 78 // pending
ecu : 00 00 04 81  76 02

host: 00 00 06 0a  36 03 .. .. .. .. (truncated)
ecu : 00 00 04 81  7f 36 78 // pending
ecu : 00 00 04 81  76 03
// (more transfer requests, truncated)


// request transfer exit:
host: 00 00 06 0a  37
ecu : 00 00 04 81  77


// verify firmware signature
host: 00 00 06 0a  31 01 ff 01  85 03 0b a5  1b c7 33 e4 cd 5c f1 9a c6 ab 0f 75 66 2c ed c6 cb 75 56 4e a0 d9 67 e2 5a 25 a0 10 1a 45 f0 dc d0 39 93 81 84 99 7e 4d 1d f3 f4 a6 11 75 77 16 8a ae ab 00 18 6f 40 e2 20 d5 ec 67 5b ce ed e6 76 5e 97 e1 02 b5 e4 12 e4 e2 19 c4 55 08 54
ecu : 00 00 04 81  7f 31 78 // pending
ecu : 00 00 04 81  7f 31 78 // pending
ecu : 00 00 04 81  7f 31 78 // pending
ecu : 00 00 04 81  71 01 ff 01 00


// hardreset
host: 00 00 06 0a  11 01
ecu : 00 00 04 81  51 01


// enter diag session
host: 00 00 06 0a  10 03
ecu : 00 00 04 81  50 03 00 14 00 c8

Btw I noticed that you were attempting the ReadMemoryByAddress while in a boot session, where it was rejected. The same is true for MB ecus -- ReadMemoryByAddress only works in a extended session and after the seed/key unlock is completed.

@Manevolent
Copy link

Manevolent commented Dec 22, 2023

Thank you both! You're probably spot on about the boot mode. Looking at that routine 255, 44 seems a lot like 4 bytes + 4 bytes indicator, and 00010000 looks an awful lot like the end of the bootloader, and 003F0000 looks right too at 4128768 bytes.

I'll take care of recovering it in due time, but I do have the delidded ECU mounted and used SSM4 to pair it to the vehicle. It might be delidded, but the vehicle is fine and drives happily otherwise. I now have a third ECU on the way for more testing and a possible clone to keep stock on backups so I have one that isn't exposed to the elements I can always fall back to.

I haven't actually tried more of these avenues yet; will get the OEM ECU taken care of first and then go from there. Very excited to get more traction on a real flash dump. I have a few options on the OEM; Cobb will flash it back for me, or I could play with the tooling a bit more. For this round, I'll let Cobb repair it, but in the future I likely will be doing it myself provided we make more progress.

@Manevolent
Copy link

Manevolent commented Dec 22, 2023

So I was able to determine that although 0x61 isn't available (yet?), there are more levels that are:

Specifically, level 1 (which we know can flash). If you're in Level 1, 3 and 5 do not answer with seeds.
Level 3
and Level 5

No other seed requests return INVALID_MESSAGE_LEN_OR_FORMAT

Unlocking ECU...
7A2 0x27 UDSSecurityAccessRequest seed=0 key=(empty)
7AA 0x7F UDSNegativeResponse sid=27 reason=SUB_FUNCTION_NOT_SUPPORTED
7A2 0x27 UDSSecurityAccessRequest seed=1 key=(empty)
7AA 0x67 UDSSecurityAccessResponse seed=1 key=57E1C6C66EB26E567BE4C58F4BE6D3BA
7A2 0x27 UDSSecurityAccessRequest seed=2 key=(empty)
7AA 0x7F UDSNegativeResponse sid=27 reason=INVALID_MESSAGE_LEN_OR_FORMAT
7A2 0x27 UDSSecurityAccessRequest seed=3 key=(empty)
7AA 0x67 UDSSecurityAccessResponse seed=3 key=54761E4C7BED30928842A33EA7F82AF1
7A2 0x27 UDSSecurityAccessRequest seed=4 key=(empty)
7AA 0x7F UDSNegativeResponse sid=27 reason=INVALID_MESSAGE_LEN_OR_FORMAT
7A2 0x27 UDSSecurityAccessRequest seed=5 key=(empty)
7AA 0x67 UDSSecurityAccessResponse seed=5 key=1003D48A8D3113D0419C3179C3D0D55A
7A2 0x27 UDSSecurityAccessRequest seed=6 key=(empty)
7AA 0x7F UDSNegativeResponse sid=27 reason=INVALID_MESSAGE_LEN_OR_FORMAT

For Level 3, the correct key is 469A20AB308D5CA64BCD5BBE535BD85F (thank you! got that from this repo). However, we get REQUEST_OUT_OF_RANGE for that Level when requesting memory. Level 3 cannot enter a programming session. Level 3 can enter an extended session where I tested reading memory and got the out of range response.

For Level 5, the correct key is E8CC52D5D8F20706424813126FA7ABDD. Same issue with REQUEST_OUT_OF_RANGE. Level 5 cannot enter a programming session, but can enter an extended session (where read memory was tested).

Also, if I enter a level, I am unable to access any other levels (including 0x61; I wasn't sure if I had to go to level 1, 3, or 5, and then jump to 61).

Update: Level 3 does allow you to set the VIN. Level 1 does not.

@Manevolent
Copy link

Manevolent commented Dec 23, 2023

I think I am making progress!!

I data-logged some tools I have, and I noticed they were setting dynamically defined data identifiers using func=2, or define by memory address. The data returned by these DIDs is not encrypted as far as I know. This gave me a great example to work from to create DIDs, and it turns out you can set each DID for up to exactly 0x64, or 100 bytes. The memory addresses were high, though, so I wanted to read at, say, 0x00000000. This gives you SECURITY_ACCESS_DENIED (getting somewhere...). Turns out Level #1 cannot give you access to this, but Level #3 (in my prior post) DOES. I think this means I can read all memory using the dynamically define data identifier service.

REQUEST_OUT_OF_RANGE is returned if you ask for any more than 0x64 bytes back.

I will put this code in a loop and report back with what I have. For now, the first several bytes in memory at offset 0x00000000 is:

8388062A0000000000000000006A00008388062A0000000000000000006A00001BE2062A0000000000000000006A00008388062A0000000000000000006A00008388

UPDATE:
I am fairly confident I have dumped cleartext flash, including bootloader. Positions in the encrypted data match perfectly with the memory regions, indicating that flash is mapped from 0x0000000 <=> 0x0000000 in memory. I am confirming this as I see patterns in the encrypted data change exactly where the memory dump indicates data changes. Also, as soon as you fall out of the flash region, we get very clear OUT_OF_RANGE errors indicating there is no memory mapped.

Here are some example cipher pairs (clear left, encrypted right) we can test with later as I am still dumping data:
00000000 is encrypted to 98A49DE7
0A0A0A0A is encrypted to 8AB4791C
0201FFFF is encrypted to F135FA11
EEEEEEEE is encrypted to 393D667E
FFFFFFFF is encrypted to 941478D7

@Manevolent
Copy link

Manevolent commented Dec 23, 2023

@jglim @Feezex

Here is the flash data in the clear for a 2022 USDM WRX MT (very recent OEM calibration) with bootloader included:

memory.bin.zip

@jnewb1
Copy link
Author

jnewb1 commented Dec 23, 2023

Woah, nice! Wonder if we can reverse the fw encryption from that dump

@Manevolent
Copy link

Woah, nice! Wonder if we can reverse the fw encryption from that dump

I think we can, I've been digging in Ghidra (using https://github.com/esaulenka/ghidra_v850) and have found a few interesting things:

  1. AES implementation

  2. SHA1 implementation

  3. MD5 implementation

  4. This very unusual function:

    public static int function(int symbol) {
    int uVar1;
    symbol = << (symbol & 3) ^ symbol;
    uVar1 = symbol >> 3 ^ symbol ^ symbol >> 4 ^ symbol << 4 ^ symbol << 3;
    uVar1 = 0x65ac9365 << (uVar1 & 3) ^ uVar1;
    uVar1 = uVar1 >> 3 ^ uVar1 ^ uVar1 >> 4 ^ uVar1 << 4 ^ uVar1 << 3;
    uVar1 = 0x65ac9365 << (uVar1 & 3) ^ uVar1;
    return uVar1 << 4 ^ uVar1 ^ uVar1 << 3 ^ uVar1 >> 4 ^ uVar1 >> 3;
    }

That constant hits this on GH: https://github.com/morkt/GARbro/blob/master/ArcFormats/Escude/ArcBIN.cs

Bunch of odd things in here, maybe some folks will know a little better but I'll keep digging

@jnewb1
Copy link
Author

jnewb1 commented Dec 24, 2023

Sick! I’ll have to try it on my eyesight module. I used the dynamic read by identifier before but hadn’t thought of reading the full memory with it, that’s super cool!

@Manevolent
Copy link

Manevolent commented Dec 24, 2023

98A49DE7

Sweet and good luck to you! I had to use security level 3, and my define looked like this:

0x2C 0xF3 0x00 0x02 0x14 0xFF 0xFF 0xFF 0xFF 0x10
Where,
0x2C Dynamically Define Data by Identifier, and
0xF3 0x00 Desired DID, and
0x02 Define by Memory Address, and
0xFF 0xFF 0xFF 0xFF = Memory address, and
0x10 is size (up to 0x64 on the ECU)

If it's the same, flash should start and be aligned at 0x00000000 and should at some point give you very consistent OUT_OF_RANGE errors, likely right around where we've seen programming files for that module end. There are some small regions in the middle of the flash that might report that error, too, but they're small on the engine module.

@OmegaKZ
Copy link

OmegaKZ commented Dec 24, 2023

I will try to read data from my cameras, using this way.

Now i log SSM4 data monitor read process, and it wherry similar with this "Dynamically Define Data by Identifier" read algorithm.
Message from ssm4 looks like:
0x2C 0x01 0xF3 0x00 0x41 0x35 0x01 0x06 where:
0x2C Dynamically Define Data by Identifier
0x01 - Unknown.
0xF3 0x00 Desired DID
0x41 - looks lite data or location type (internal rom, eeprom)
0x35 - data or parameter ident
0x01 - address start
0x06 - length of data (can't be more than parameter data length)

98A49DE7

Sweet and good luck to you! I had to use security level 3, and my define looked like this:

0x2C 0xF3 0x00 0x02 0x14 0xFF 0xFF 0xFF 0xFF 0x10 Where, 0x2C Dynamically Define Data by Identifier, and 0xF3 0x00 Desired DID, and 0x02 Define by Memory Address, and 0xFF 0xFF 0xFF 0xFF = Memory address, and 0x10 is size (up to 0x64 on the ECU)

If it's the same, flash should start and be aligned at 0x00000000 and should at some point give you very consistent OUT_OF_RANGE errors, likely right around where we've seen programming files for that module end. There are some small regions in the middle of the flash that might report that error, too, but they're small on the engine module.

@Manevolent
Copy link

I will try to read data from my cameras, using this way.

Now i log SSM4 data monitor read process, and it wherry similar with this "Dynamically Define Data by Identifier" read algorithm. Message from ssm4 looks like: 0x2C 0x01 0xF3 0x00 0x41 0x35 0x01 0x06 where: 0x2C Dynamically Define Data by Identifier 0x01 - Unknown. 0xF3 0x00 Desired DID 0x41 - looks lite data or location type (internal rom, eeprom) 0x35 - data or parameter ident 0x01 - address start 0x06 - length of data (can't be more than parameter data length)

98A49DE7

Sweet and good luck to you! I had to use security level 3, and my define looked like this:
0x2C 0xF3 0x00 0x02 0x14 0xFF 0xFF 0xFF 0xFF 0x10 Where, 0x2C Dynamically Define Data by Identifier, and 0xF3 0x00 Desired DID, and 0x02 Define by Memory Address, and 0xFF 0xFF 0xFF 0xFF = Memory address, and 0x10 is size (up to 0x64 on the ECU)
If it's the same, flash should start and be aligned at 0x00000000 and should at some point give you very consistent OUT_OF_RANGE errors, likely right around where we've seen programming files for that module end. There are some small regions in the middle of the flash that might report that error, too, but they're small on the engine module.

That 0x1 looks like "define by identifier" sub function which you may not want, the function that got me access to memory was 0x2 in that position https://embetronicx.com/tutorials/automotive/uds-protocol/data-transmission-in-uds-protocol/ and I had to have Level 3 access

@OmegaKZ
Copy link

OmegaKZ commented Dec 24, 2023

That 0x1 looks like "define by identifier" sub function which you may not want, the function that got me access to memory was 0x2 in that position https://embetronicx.com/tutorials/automotive/uds-protocol/data-transmission-in-uds-protocol/ and I had to have Level 3 access

I try to session with EyeSight and no luck:

//TESTER TOOL PRESENT
OUT: 00 00 07 87 3E 00
IN: 00 00 07 8F 7E 00

//START $03 SESSION
OUT: 00 00 07 87 10 03
IN: 00 00 07 8F 50 03

//REQUEST $03 SEED
OUT: 00 00 07 87 27 03
IN: 00 00 07 8F 67 03 FC C7 03 68

//SEND $03 KEY
OUT: 00 00 07 87 27 04 4A D3 3D E0
IN: 00 00 07 8F 67 04

//REQEST DID VALUE
OUT: 00 00 07 87 2C 02 F3 00 FF FF FF FF 64 where:
• 00 00 07 87 - EyeSight CM CAN Address
• 2C - Function: Dynamically Define Data by Identifier
• 02 - Sub function: Read by memory Address
• F3 00 - DID Address to put data in
• FF FF FF FF - Memory Address to Read
• 64 - Length (size) to read.

IN: 00 00 07 8F 7F 2C 12
Here i receive Negative responce (NRC) with code 12 (SUB_FUNCTION_NOT_SUPPORTED)

I try to use 03 - Sub function instead 02 (Read by memory Address) and got NRC 13 (INVALID_MESSAGE_LEN_OR_FORMAT)

@Manevolent
Copy link

Manevolent commented Dec 24, 2023

That 0x1 looks like "define by identifier" sub function which you may not want, the function that got me access to memory was 0x2 in that position https://embetronicx.com/tutorials/automotive/uds-protocol/data-transmission-in-uds-protocol/ and I had to have Level 3 access

I try to session with EyeSight and no luck:

//TESTER TOOL PRESENT OUT: 00 00 07 87 3E 00 IN: 00 00 07 8F 7E 00

//START $03 SESSION OUT: 00 00 07 87 10 03 IN: 00 00 07 8F 50 03

//REQUEST $03 SEED OUT: 00 00 07 87 27 03 IN: 00 00 07 8F 67 03 FC C7 03 68

//SEND $03 KEY OUT: 00 00 07 87 27 04 4A D3 3D E0 IN: 00 00 07 8F 67 04

//REQEST DID VALUE OUT: 00 00 07 87 2C 02 F3 00 FF FF FF FF 64 where: • 00 00 07 87 - EyeSight CM CAN Address • 2C - Function: Dynamically Define Data by Identifier • 02 - Sub function: Read by memory Address • F3 00 - DID Address to put data in • FF FF FF FF - Memory Address to Read • 64 - Length (size) to read.

IN: 00 00 07 8F 7F 2C 12 Here i receive Negative responce (NRC) with code 12 (SUB_FUNCTION_NOT_SUPPORTED)

I try to use 03 - Sub function instead 02 (Read by memory Address) and got NRC 13 (INVALID_MESSAGE_LEN_OR_FORMAT)

Dang, okay. So I think this means we have to use the flash we have (or any other modules we can find), decrypt that flash, then figure out what key yours might be using and decrypt that flash, too. I am pretty sure we have the algorithm somewhere in the flash dump I sent, it's just a matter of digging it out.

Also did you try entering an Extended Session? I had to do that, too, to get that to work

@OmegaKZ
Copy link

OmegaKZ commented Dec 24, 2023

Also did you try entering an Extended Session? I had to do that, too, to get that to work

I think no. How to do this?

@Manevolent
Copy link

Also did you try entering an Extended Session? I had to do that, too, to get that to work

I think no. How to do this?

Try sending frame 0x10 0x03 after successful Level 3 seed/key where
0x10 Diagnostic Session Control
0x03 Extended session

@Manevolent
Copy link

Manevolent commented Dec 24, 2023

Turns out I was wrong; we don't need DIDs to read memory on the ECU, but you definitely can do it.

What works is (again, level 3 security access within an extended session):
0x23 0x14 0xFF 0xFF 0xFF 0xFF 0x10, Where:
0x23 Read Memory by Address
0x14 (0x1 size byte and 0x4 address byte)
0xFF 0xFF 0xFF 0xFF Memory address
0x10 Size definition

What threw me off is that the address and size seem to be backwards from the definition in the 0x14 byte.

@OmegaKZ
Copy link

OmegaKZ commented Dec 25, 2023

Turns out I was wrong; we don't need DIDs to read memory on the ECU, but you definitely can do it.

What works is (again, level 3 security access within an extended session):

I was try this, but no luck. I receve SERVICE_NOT_SUPPORTED.

Here is full log of transmission:

OUT:       00 00 07 87 3E 00                             //TOOL PRESENT
IN:           00 00 07 8F 7E 00                             //DEVICE PRESENT

OUT:       00 00 07 87 10 01                              //REQUES $01 MODE
IN:          00 00 07 8F 50 01                              //$01 MODE OK

OUT:        00 00 07 87 10 03                             //REQUES $03 MODE
IN:           00 00 07 8F 50 03                             //$03 MODE OK

OUT:        00 00 07 87 27 03                              //REQUES SEED FOR $03 MODE
IN:           00 00 07 8F 67 03 96 B9 8A E5          //SEED FOR $03 MODE [96 B9 8A E5]

OUT:        00 00 07 87 27 04 B7 DD 71 E0         //REQEST ACCESS WITH KEY [B7 DD 71 E0]
IN:           00 00 07 8F 67 04                              //ACCESS TO $03 MODE GRANTED

OUT:        00 00 07 87 23 14 FF FF FF FF 10       // READ MEMORY BY ADDRESS [0x23], ADDRESS:[FF FF FF FF], LENGTH:[10]
IN:           00 00 07 8F 7F 23 11                          // [7F] - NRC, [23] ServiceID, [11] - SERVICE NOT SUPORTED 

@Manevolent
Copy link

Manevolent commented Dec 26, 2023

I think I found the best candidate so far as to what the firmware encryption algorithm may be. I have spent the last 2 days digging, and digging, and I have found numerous candidates but have all led to dead ends so far. However, I think the function starting at offset 0x0000f26e is the best candidate for how the encryption works. Here is why I think that:

  • I suspect that the function we are after is in the bootloader + user boot regions, which ranges from 0x00000000 to 0x10000 (65KB). COBB does change the 3 AES keys (levels 1, 3, & 5 respectively) when they flash (which, by the way, I STRONGLY suspect are at offset 0x14bec0-0x14bef0, as this are the only memory-unreadable 48 byte sequence in the ENTIRE flash). They probably would have decided to change this algorithm, too, if they could, but it does not change when the AccessPort is uninstalled.
  • We already know that the cipher uses a 32bit/4byte block size, and the cipher demonstrated at this offset matches that behavior. The calling function at offset 0x0000f476 does indeed operate at this cadence; 4 bytes at a time.
  • Engineers who work on these ECU's, but have not wanted to give clear answers for sake of their own proprietary work, have admitted to me before that the algorithm we are after is a Feistel-type cipher. If I am understanding https://en.wikipedia.org/wiki/Feistel_cipher right, then:

param_1_copy = (uVar1 ^ param_1_copy >> 0x10) + param_1_copy * 0x10000;

Does appear to be the "crossover" where the red/green lines switch over, if L0 and R0 are both 16 bit segments of param_1, and the last operation in a loop is to, indeed, cross the data over before looping again. K0..n would likely be the data referenced in RAM starting at 0xfef1dbbc. Also, note that only L0 (AKA param_1_copy >> 0x10) is the only side XOR'd, which matches the Wikipedia article as well. In practice, that would mean "uVar1" is the result of F. This makes sense, as in the disassembled code, 'F' (in regards to the Wikipedia article) does appear to be the result of K0..n's associated lookup.

I'll keep trying to figure this function out, but if anyone else is interested you might want to have a peek in IDA/Ghidra as well!

If we can figure the algorithm out, we will be able to decrypt any flashes. K0..n may only be so large; we can likely brute-force the remaining flashes such as EyeSight.

@OmegaKZ
Copy link

OmegaKZ commented Dec 27, 2023

I think I found the best candidate so far as to what the firmware encryption algorithm may be.

As far as I understand, all this applies only to the engine ECU? It will not be possible to decrypt/encrypt the Camera (EyeSight) firmware using this algorithm, or there will be a completely different keys that we do not have.

I also couldn’t read the firmware from the EyeSight using any of the methods (the cameras are blocked or I’m doing something wrong).

@Manevolent
Copy link

Manevolent commented Dec 27, 2023

I think I found the best candidate so far as to what the firmware encryption algorithm may be.

As far as I understand, all this applies only to the engine ECU? It will not be possible to decrypt/encrypt the Camera (EyeSight) firmware using this algorithm, or there will be a completely different keys that we do not have.

I also couldn’t read the firmware from the EyeSight using any of the methods (the cameras are blocked or I’m doing something wrong).

I think you're right, because I've just finished reversing the algorithm (encrypt + decrypt), but even the keys that I found in the firmware don't match up with what is flashed to the vehicle. It is now up to us individually to find the 64-bit key pairs used for each control unit/module I suspect. At least we have the algorithm, now, though.

Here is the prototype: https://github.com/atlas-tuning/utilities/blob/main/java/src/main/java/com/github/manevolent/atlas/ssm4/SubaruDITFlashEncryption.java

Keep in mind what I have right now is very preliminary; the RH850 has some unusual byte endianness so there is a good chance it doesn't line up perfectly right now. It does produce the correct data with the ECU's routine being emulated/debugged with Ghidra, however.

Here's what I've found out:

  1. The keys are 4 16-bit shorts, so it's a 64-bit key. This will be very tough/impossible to brute force.
  2. The keys I found in my memory dump are the keys that are used/were used to encrypt outgoing flash data, not incoming flash data. It looks like the keys I will need are different. Perhaps the method isn't in use and as such the keys are out of date, or maybe it'll be of use.

I strongly believe the flash data I've seen in this thread so far all traces back to the same algorithm that I have shared. This appears to be the "new" flash encryption algorithm for Subaru Renesas-based ECUs. Sadly, though, what this does mean is that unless you can find that 64-bit key, you're going to have a bad time. So, memory access is probably going to be an absolute must.

@Feezex
Copy link
Contributor

Feezex commented Dec 27, 2023

Send me one of ecu's, ill read it fully.
Have you tried to inspect SSM4? it must store communication parameters, avaliable commands for all ecu's and maybe even more.

@OmegaKZ
Copy link

OmegaKZ commented Dec 27, 2023

Send me one of ecu's, ill read it fully. Have you tried to inspect SSM4? it must store communication parameters, avaliable commands for all ecu's and maybe even more.

SSM don't have any interesting keys or protocols for flashing(programming) eyesight. I found key algorithm in SSM for diag session $03 for eyesight, IPC, MFD. We can try search Flash codes or something else in FlashWrite, but i think we don't found any interesting there.

How will you planing to read EyeSight CPU? I think CPU is locked from external (ISP, UART) read.

@Manevolent
Copy link

Manevolent commented Dec 27, 2023

Send me one of ecu's, ill read it fully. Have you tried to inspect SSM4? it must store communication parameters, avaliable commands for all ecu's and maybe even more.

SSM don't have any interesting keys or protocols for flashing(programming) eyesight. I found key algorithm in SSM for diag session $03 for eyesight, IPC, MFD. We can try search Flash codes or something else in FlashWrite, but i think we don't found any interesting there.

How will you planing to read EyeSight CPU? I think CPU is locked from external (ISP, UART) read.

You may also have different luck with some of the vendor-specific SIDs I found in the ECU code:
0x1 through 0xa are all legal, handled SIDs on the engine ECU. Perhaps the EyeSight module also supports some of these, and perhaps some could offer memory access? Here are all the memory offsets for each function on my ECU I could find. I am busy working on finding the right algorithm implementation for my ECU, so I don't have time to get through each vendor-specific SID, but perhaps you might find some new way to obtain cleartext flash with these:

SID 0x1 (unknown) is handled at code 315fb8
SID 0x2 (unknown) is handled at code 316236
SID 0x3 (unknown) is handled at code 31649c
SID 0x4 (unknown) is handled at code 3164e6
SID 0x5 (unknown) is handled at code 31a060
SID 0x6 (unknown) is handled at code 3183c8
SID 0x7 (unknown) is handled at code 318578
SID 0x8 (unknown) is handled at code 318876
SID 0x9 (unknown) is handled at code 3192a8
SID 0xa (unknown) is handled at code 319406
SID 0x10 (Diagnostic Session Control) is handled at code 31a75e
SID 0x19 (Read DTC Information) is handled at code 31c0c4
SID 0x22 (Read Data by ID) is handled at code 31b252
SID 0x23 (Read Memory by Address) is handled at code 31a3f4
SID 0x27 (Security Access) is handled at code 31ac4a
SID 0x2a (unknown) is handled at code 31bf20
SID 0x2c (Dynamically Define Data Identifier) is handled at code 31b340
SID 0x2e (Write Data by Identifier) is handled at code 31bb9a
SID 0x2f (unknown) is handled at code 321e84
SID 0x31 (Routine Control) is handled at code 323296
SID 0x3d (unknown) is handled at code 31a47e
SID 0x3e (Tester Present) is handled at code 31ae18
SID 0x85 (Control DTC Settings) is handled at code 3218e0
SID 0x28 (Communication Control) is handled at code 321964

image

@Manevolent
Copy link

Manevolent commented Dec 27, 2023

Correction: I have figured out how to decrypt flash for my ECU. The 64 bit I have is correct, I just had the operations backwards. Encryption = decryption, and vice-versa.

Here is how I did it with my code I pushed and referenced earlier:

int attempt = 0x98A49DE7;
byte[] encrypted_data_out = new byte[4];
SubaruDITFlashEncryption.feistel_encrypt( // "Encryption" is used wrongly, here
                    attempt,
                    encrypted_data_out,
                    SubaruDITFlashEncryption.ENGINE_ECU_KEYS_ENCRYPTION // "Encryption" is used wrongly, here
);
ByteBuffer buffer = ByteBuffer.wrap(encrypted_data_out);
int result = buffer.getInt();
result == 0x00000000; // This is correct

This means that WRX ECU's from 2015-2023 are now wide open for what I think is the first time in the open source community! Very exciting. Now, the real work can begin...

I'd like to try decrypting your flash, next, @OmegaKZ by doing a bunch of different things with the data I have. I can't brute force a 64 bit key, but I'd at least like to try different variations/permutations of the key I have, perform a key-scan through my flash, etc. It most likely won't work, but I'll give it my best shot!

Update: sadly I haven't been able to figure out the key for that module from what I have. Back to thinking we need the memory/flash from the device in order to decrypt that module. On the other hand, my code and related tests are complete so once we do have candidates to test with, we have a working program to test keys against.

@SergArb
Copy link

SergArb commented Nov 16, 2024

Hello All!
I have Subaru Levorg 2021 ECU on my bench. Successfully got access with level 1, 3 and 5.
But can't find out what offset and length to use for 0x23 and 0x35 commands?

With level 1, 0x34 accepted: 0x34 0x04 0x44 0x08 0xFA 0xC0 0x00 0x00 0x3D 0x3F 0x00
Like in previous 1N83M 4MB ECU's.

But 0x35 didn't accepted: 0x35 0x04 0x44 0x08 0xFA 0xC0 0x00 0x00 0x3D 0x3F 0x00
Says "11 - Service not supported"

@SergArb
Copy link

SergArb commented Nov 17, 2024

Started reading with 0x23, start address = 0x0. 7F 23 31 changed to 7F 23 33 from 0x08FAC000. 33 - Security Access Denied. Nothing was readed. So i think right offset the same - 0x08FAC000. But why 0x23 doesn't read it with all security levels 1, 3, 5?
Maybe i need to get a few security levels at once to have reading granted?

@SergArb
Copy link

SergArb commented Nov 23, 2024

Got full ROM with OBK... No one is answered...

@jglim
Copy link
Owner

jglim commented Nov 23, 2024

Sorry about that, unfortunately I am not experienced enough in Subaru ECUs to offer any meaningful help. Thanks for pointing out the lead about OBK too, hopefully that helps someone else.

FWIW If I had to guess based on my experience with MB, if testerpresent messages are not sent, the ECU will exit the session and revert to an unauthenticated state. If you've been sending testerpresent messages and this is still an issue, it's probably a Subaru-specific issue.

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

6 participants