Login Register

Vida CEM swapping

A mid-size luxury crossover SUV, the Volvo XC90 made its debut in 2002 at the Detroit Motor Show. Recognized for its safety, practicality, and comfort, the XC90 is a popular vehicle around the world. The XC90 proved to be very popular, and very good for Volvo's sales numbers, since its introduction in model year 2003 (North America). P2 platform.
Post Reply
vtl
Posts: 4723
Joined: 16 August 2012
Year and Model: 2005 XC70
Location: Boston
Has thanked: 114 times
Been thanked: 603 times

Re: Vida CEM swapping

Post by vtl »

regula wrote: 17 Feb 2025, 00:43 I need help with getting cem pin from P2 2006. Cem number 30786889.
Using quality components i built the Teensy hardware by vtl diagram and it communicates with the cem with no problems. Only thing is that only the 0 byte (first pin number) is showing up with consistency. Byte [1] and [2] are always random and the pin is not cracked.
What could be wrong?
Show us what you've built? And how you crack the CEM: in car, on the bench?

Treur
Posts: 126
Joined: 16 November 2024
Year and Model: 2007 V70
Location: Estonia
Has thanked: 3 times
Been thanked: 6 times

Post by Treur »

vtl wrote: 26 Jan 2025, 20:02
Treur wrote: 26 Jan 2025, 06:16 vtl, what you think about STM32H743VGT6 MCU? I want to try to port your cracker to this one.
As long as it runs at ~200MHz and has built-in (in SOC, not over SPI) CAN controllers it will do it.
Something is already starting to form...
photo_2025-02-17_23-04-27.jpg
We built this miracle with CKaev

I would like to make j2534 out of it. It is already starting to process USB commands, but I need to figure out how to write an API.

regula
Posts: 3
Joined: 17 February 2025
Year and Model: 2006 XC70
Location: EE

Post by regula »

It looks ugly, i know, but wires are not touching each other, everything is soldered, copper wires are used, CAN wires to OBD are twisted, quality 12V->5V step-down converter is used for sufficient power, nothing is ordered from Ali and are bought from local electronic shop.
Still only the [0] byte is found with 100% consistency, [1] and [2] are always different.

Code: Select all

[1] byte latency runs



best candidates ordered by latency:

0: 07 lat = 87460

1: 00 lat = 87308

2: 88 lat = 87293

3: 78 lat = 87276

4: 98 lat = 87276

5: 82 lat = 87256

6: 54 lat = 87254

7: 30 lat = 87254

8: 74 lat = 87250

9: 38 lat = 87248

10: 50 lat = 87248

11: 84 lat = 87246

12: 02 lat = 87246

13: 80 lat = 87242

14: 60 lat = 87242

15: 70 lat = 87228

16: 81 lat = 87228

17: 41 lat = 87224

18: 94 lat = 87222

19: 03 lat = 87220

20: 90 lat = 87216

21: 08 lat = 87214

22: 68 lat = 87212

23: 83 lat = 87210

24: 28 lat = 87200

25: 44 lat = 87196

26: 01 lat = 87190

27: 10 lat = 87190

28: 58 lat = 87188



********************



best candidates ordered by latency:

0: 47 lat = 174782

1: 89 lat = 174742

2: 22 lat = 174737

3: 75 lat = 174736

4: 07 lat = 174720

5: 93 lat = 174712

6: 46 lat = 174692

7: 29 lat = 174672

8: 32 lat = 174668

9: 73 lat = 174648

10: 26 lat = 174610

11: 13 lat = 174528

12: 80 lat = 174528

13: 12 lat = 174486

14: 00 lat = 174464

15: 17 lat = 174460

16: 08 lat = 174458

17: 41 lat = 174344

18: 01 lat = 174336

19: 06 lat = 174288

20: 10 lat = 174284

21: 28 lat = 174278

22: 50 lat = 174266

23: 70 lat = 174244

24: 48 lat = 174234

25: 30 lat = 174234

26: 58 lat = 174232

27: 94 lat = 174230

28: 60 lat = 174212



**********************



best candidates ordered by latency:

0: 89 lat = 436973

1: 93 lat = 436834

2: 07 lat = 436757

3: 75 lat = 436746

4: 73 lat = 436696

5: 46 lat = 436648

6: 47 lat = 436648

7: 29 lat = 436584

8: 22 lat = 436582

9: 32 lat = 436564

10: 26 lat = 436444

11: 13 lat = 436340

12: 17 lat = 436318

13: 12 lat = 436188

14: 00 lat = 436162

15: 80 lat = 435904

16: 08 lat = 435814

17: 28 lat = 435752

18: 10 lat = 435720

19: 70 lat = 435608

20: 41 lat = 435604

21: 48 lat = 435528

22: 50 lat = 435466

23: 01 lat = 435465

24: 06 lat = 435456


**********************



best candidates ordered by latency:

0: 93 lat = 873767

1: 07 lat = 873662

2: 47 lat = 873631

3: 73 lat = 873463

4: 89 lat = 873434

5: 75 lat = 873420

6: 46 lat = 873352

7: 32 lat = 873300

8: 26 lat = 873254

9: 22 lat = 873178

10: 29 lat = 872958

11: 13 lat = 872598



********************



best candidates ordered by latency:

0: 73 lat = 1747664

1: 75 lat = 1747494

2: 47 lat = 1747438

3: 07 lat = 1747289

4: 89 lat = 1747157

5: 93 lat = 1746962



*******************



best candidates ordered by latency:

0: 75 lat = 2621155

1: 73 lat = 2620998

2: 47 lat = 2620783


******************



best candidates ordered by latency:

0: 75 lat = 3494636

1: 73 lat = 3494403

...

pin[1] choose candidate: 75






[2] byte latecny runs:



best candidates ordered by latency:

0: 86 lat = 87432

1: 19 lat = 87402

2: 46 lat = 87382

3: 29 lat = 87370

4: 15 lat = 87368

5: 76 lat = 87358

6: 26 lat = 87352

7: 92 lat = 87338

8: 95 lat = 87334

9: 85 lat = 87332

10: 51 lat = 87328

11: 38 lat = 87324

12: 62 lat = 87322

13: 96 lat = 87322

14: 23 lat = 87320

15: 94 lat = 87320

16: 72 lat = 87320

17: 99 lat = 87316

18: 13 lat = 87314

19: 39 lat = 87312

20: 22 lat = 87310

21: 75 lat = 87308

22: 49 lat = 87308

23: 24 lat = 87306

24: 93 lat = 87306

25: 56 lat = 87304

26: 91 lat = 87304

27: 36 lat = 87304

28: 60 lat = 87302



*******************



best candidates ordered by latency:

0: 60 lat = 174678

1: 96 lat = 174373

2: 70 lat = 174364

3: 56 lat = 174344

4: 09 lat = 174332

5: 48 lat = 174322

6: 39 lat = 174319

7: 67 lat = 174310

8: 13 lat = 174310

9: 65 lat = 174303

10: 34 lat = 174300

11: 29 lat = 174296

12: 62 lat = 174286

13: 76 lat = 174286

14: 22 lat = 174284

15: 36 lat = 174280

16: 17 lat = 174276

17: 11 lat = 174273

18: 21 lat = 174268

19: 72 lat = 174265

20: 61 lat = 174256

21: 51 lat = 174254

22: 77 lat = 174242

23: 12 lat = 174226

24: 44 lat = 174218

25: 15 lat = 174214

26: 38 lat = 174210

27: 57 lat = 174208

28: 66 lat = 174208



*******************



best candidates ordered by latency:

0: 60 lat = 436392

1: 70 lat = 435830

2: 77 lat = 435780

3: 17 lat = 435724

4: 34 lat = 435711

5: 96 lat = 435702

6: 51 lat = 435699

7: 65 lat = 435662

8: 36 lat = 435654

9: 39 lat = 435566

10: 44 lat = 435559

11: 11 lat = 435554

12: 21 lat = 435542

13: 09 lat = 435541

14: 22 lat = 435532

15: 72 lat = 435530

16: 67 lat = 435530

17: 29 lat = 435503

18: 12 lat = 435500

19: 48 lat = 435496

20: 62 lat = 435486

21: 13 lat = 435484

22: 76 lat = 435426

23: 56 lat = 435386

24: 61 lat = 435310


*********************



best candidates ordered by latency:

0: 60 lat = 873316

1: 70 lat = 871852

2: 17 lat = 871418

3: 51 lat = 871408

4: 77 lat = 871390

5: 36 lat = 871332

6: 65 lat = 871314

7: 11 lat = 871264

8: 44 lat = 871234

9: 39 lat = 871050

10: 34 lat = 871041

11: 96 lat = 870904


********************



best candidates ordered by latency:

0: 60 lat = 1746026

1: 70 lat = 1743214

2: 17 lat = 1742760

3: 77 lat = 1742627

4: 36 lat = 1742418

5: 51 lat = 1741912


*******************



best candidates ordered by latency:

0: 60 lat = 2619869

1: 70 lat = 2615543

2: 17 lat = 2613718


*******************



best candidates ordered by latency:

0: 60 lat = 3492739

1: 70 lat = 3487020

...

pin[2] choose candidate: 60

Candidate PIN 84 75 60 -- -- -- : brute forcing bytes 3 to 5 (3 bytes), will take up to 646 seconds

Progress: 0%..5%..10%..15%..20%..25%..30%..35%..40%..45%..50%..55%..60%..65%..70%..75%..80%..85%..90%..95%..

PIN is NOT cracked in 2077.82 seconds

done
IMG_0490.jpg
IMG_0489.jpg
1.jpg

vtl
Posts: 4723
Joined: 16 August 2012
Year and Model: 2005 XC70
Location: Boston
Has thanked: 114 times
Been thanked: 603 times

Post by vtl »

regula wrote: 18 Feb 2025, 13:17 It looks ugly, i know, but wires are not touching each other, everything is soldered, copper wires are used, CAN wires to OBD are twisted, quality 12V->5V step-down converter is used for sufficient power, nothing is ordered from Ali and are bought from local electronic shop.
Still only the [0] byte is found with 100% consistency, [1] and [2] are always different.
Transceivers still can be fake.

Things to try:

- set clock rate to 180 MHz
- sirloin's one_pass branch: https://github.com/cmolson/volvo-cem-cr ... e/one_pass
- cem_freq_align branch: https://github.com/vtl/volvo-cem-cracke ... freq_align

alfons38
Posts: 4
Joined: 26 April 2021
Year and Model: s80
Location: Denmark
Has thanked: 1 time
Been thanked: 1 time

Post by alfons38 »

Now is working :)

But have questions about ground on schematics.

Where connection ground?

And can i have switch to change between master and p3 script without pc?
Attachments
schematic_LCD.png
schematic_LCD.png (93.74 KiB) Viewed 4146 times

al1Volvo
Posts: 34
Joined: 22 March 2024
Year and Model: Volvo V50 2011
Location: France
Has thanked: 3 times
Been thanked: 13 times

Post by al1Volvo »

You only need to connect all ground together with your power ground.

Regarding the switch you cannot switch between branch without refactoring the code to merge both branch in one.

Treur
Posts: 126
Joined: 16 November 2024
Year and Model: 2007 V70
Location: Estonia
Has thanked: 3 times
Been thanked: 6 times

Post by Treur »

vtl wrote: 18 Feb 2025, 13:43
regula wrote: 18 Feb 2025, 13:17 It looks ugly, i know, but wires are not touching each other, everything is soldered, copper wires are used, CAN wires to OBD are twisted, quality 12V->5V step-down converter is used for sufficient power, nothing is ordered from Ali and are bought from local electronic shop.
Still only the [0] byte is found with 100% consistency, [1] and [2] are always different.
Transceivers still can be fake.

Things to try:

- set clock rate to 180 MHz
- sirloin's one_pass branch: https://github.com/cmolson/volvo-cem-cr ... e/one_pass
- cem_freq_align branch: https://github.com/vtl/volvo-cem-cracke ... freq_align
What are the differences between your code and the code of these forks?

dikidera
Posts: 1304
Joined: 15 August 2022
Year and Model: S60 2005
Location: Galaxy far far away
Has thanked: 67 times
Been thanked: 175 times

Post by dikidera »

Now that I have resumed my re work on the TCM, I have created a few logs with variables which I consider to be state changes of the physical transmission state. Of course might not be, but it does signal that the transmission is progressing along a shift or is holding a certain state steady.

Quite a few variables have a state that begins(usually) from h'A or 16 all the way to h'63 or 99. the increments are not linear from 16 to 17, but in particular steps, e'g from h'A to h'14 to h'1E or even h'18. I've seen 5E, 5D.
Well h'14 = A + A and h'14 += A = h'1E so maybe that is important but who knows.

Now this could be anything. My first thought was this represented the model of the valve body. E.g Shift valves, modulators, clutches, bands,sprags etc in various states, but it could entirely be something else. Of course there are at least 10 more I have not logged.

Image

h'101 = 1-2 upshift process I've made a slight mistake in the image, and E2 is the next possible gear. The 3-2 process is actually something like h'207 so like the sign bit set, or something similar. In any case, anything h'1XX is upshift, anything h'2xx is downshift processing.

For anyone interested my 2-3 shift is measured at 2.5 seconds, 4.1 times longer than the standard 500-600ms it should only take.

I plan to optimize my logger to fetch RPM from the CAN bus rather than query the TCM and remove the torque logging as it isn't important for me to know it.
The limitation is that the TCM logic goes like so:

<main tcm loop for shifting>
<send state data to CAN for ECM>
<process D2 commands>

any state changes that happen within 'main tcm loop' for shifting will be overwritten by the time 'process D2 commands' is executed.

Here is an IDA python plugin which I requested the various AI tools to produce(since my time is better spent elsewhere). The purpose of the plugin is to find two strings or disassembled instructions within a single basic block. It doesn't do flow analysis, but it gets the job done.

Code: Select all

import idaapi
import idautils
import idc

def find_instructions_in_basic_blocks():
    # Get search strings from user
    str1 = idaapi.ask_str("", 0, "Enter first instruction pattern:")
    if not str1:
        return
    
    str2 = idaapi.ask_str("", 0, "Enter second instruction pattern:")
    if not str2:
        return

    str1_lower = str1.lower()
    str2_lower = str2.lower()
    matches = []

    # Iterate through all functions
    for func_ea in idautils.Functions():
        function = idaapi.get_func(func_ea)
        flowchart = idaapi.FlowChart(function)

        # Check each basic block
        for block in flowchart:
            found_first = False
            found_second = False
            
            # Iterate through instructions in block
            ea = block.start_ea
            while ea < block.end_ea:
                disasm = idc.GetDisasm(ea).lower()
                
                if str1_lower in disasm:
                    found_first = True
                if str2_lower in disasm:
                    found_second = True
                
                # Early exit if both found
                if found_first and found_second:
                    func_name = idc.get_func_name(func_ea)
                    matches.append((
                        func_name,
                        func_ea,
                        block.start_ea,
                        block.end_ea
                    ))
                    break
                
                ea = idc.next_head(ea, block.end_ea)

    # Print results
    print(f"\nFound {len(matches)} matching basic blocks:")
    for idx, (name, func_ea, start, end) in enumerate(matches, 1):
        print(f"\nMatch {idx}:")
        print(f"  Function: {name} ({func_ea:x})")
        print(f"  Block range: {start:x} - {end:x}")
        
        # Print instructions from the matching block
        print("  Instructions:")
        ea = start
        while ea < end:
            print(f"    {ea:x}: {idc.GetDisasm(ea)}")
            ea = idc.next_head(ea, end)

    if not matches:
        print("\nNo blocks found containing both patterns")

# Execute the script
if __name__ == "__main__":
    find_instructions_in_basic_blocks()
Last edited by dikidera on 25 Feb 2025, 16:21, edited 1 time in total.

vtl
Posts: 4723
Joined: 16 August 2012
Year and Model: 2005 XC70
Location: Boston
Has thanked: 114 times
Been thanked: 603 times

Post by vtl »

Treur wrote: 21 Feb 2025, 03:13
vtl wrote: 18 Feb 2025, 13:43 - sirloin's one_pass branch: https://github.com/cmolson/volvo-cem-cr ... e/one_pass
- cem_freq_align branch: https://github.com/vtl/volvo-cem-cracke ... freq_align
What are the differences between your code and the code of these forks?
One_pass does something to interrupts, but I forgot what. It works better with P1 CEMs and some of P2 CEMs, but fails on others.

CEM freq align was an attempt to recover a latency precision lost in a chain of transitions through multiple clock domains.

MCU inside CEM runs at certain clock speed (clock domain), the latency we want to observe is originated in this domain. The signal is then passing through the CAN-bus, which works at a different, much lower clock speed. The original latency signal gets distorted by undergoing this transition in CAN transceiver. CAN transceiver on the receiving side is interlocked with the sender, but it has a significant jitter*. Latency signal gets distorted yet again. Next, there's ARM MCU in Teensy working at its own frequency, which adds more noise to the signal. What the cracker code operates with is a very dirty latency signal that it tries to detect using primitive statistic function. I wish I knew math better, the whole detection algorithm can be improved a lot.

Anyways, by knowing CEM MCU clock speed we can recalculate statistical buckets in that clock domain and place latency measured into the buckets in a manner that aligns with CEM's instructions execution rate better. Say, we have a signal jitter X, but we can't know how much jitter each part of the system contributed to the final X. By forcibly mapping the latency over frequency of originating domain we reduce uncertainty about how many more instructions did the CEM run for this pin code sequence. This algo cracked some hard to crack CEMs better, but, again, reduced the success rate for others.

No silver bullet.

[*] The pin 2 (where we measure the latency) was originally connected directly to CAN low signal just for the reason to avoid one clock domain transition, which improves detection probability, but is unsafe for MCU electrically.
Last edited by vtl on 16 Oct 2025, 08:51, edited 3 times in total.

dikidera
Posts: 1304
Joined: 15 August 2022
Year and Model: S60 2005
Location: Galaxy far far away
Has thanked: 67 times
Been thanked: 175 times

Post by dikidera »

I have concluded that automotive MCUs are very resistant to electrical instability. You don't want to know what even happened to my ETM, as I was connected to the MCU pins directly. There was some ground issue. And it survived just fine.

Post Reply
  • Similar Topics
    Replies
    Views
    Last post