Announcement

Collapse
No announcement yet.

Flashing the MK60 with ZCP software

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    #91
    Oh shit, I might have found CCP commands in the PT(?)-CAN table.

    Table decodes out to a bunch of CAN IDs that I was going through and, after some indirection, I found a read of the presumed-CCP EEPROM flag through a function pointer. Fortunately 0x797 was the towards the top of the table

    Looking through the payload handler:

    Click image for larger version

Name:	Screenshot 2026-10-30 at 1.28.47 AM.png
Views:	216
Size:	410.7 KB
ID:	614066
    Click image for larger version

Name:	Screenshot 2026-10-30 at 1.20.47 AM.png
Views:	217
Size:	190.1 KB
ID:	614064
    tbl_0f2eb:
    Click image for larger version

Name:	Screenshot 2026-10-30 at 1.21.40 AM.png
Views:	200
Size:	97.2 KB
ID:	614065
    Click image for larger version

Name:	Screenshot 2026-10-30 at 1.49.32 AM.png
Views:	214
Size:	435.5 KB
ID:	614067

    (I still don't know what the `_DAT_00400948` check is. Presumably a `wheel_speed == 0` check since it comes from RAM?)

    So now we know the second conditional in `can_rx_797_payload()` will evaluate to true (because the CCP flag is set). If we go to the function that gets evaluated in the conditional, we see something that looks suspiciously like an interpreter:

    Click image for larger version

Name:	Screenshot 2026-10-30 at 2.24.00 AM.png
Views:	213
Size:	543.1 KB
ID:	614069

    Looking at a CCP command ref (e.g. https://mxhelp.danlawinc.com/ccp_xcp_reference.htm), the `UPLOAD` function (0x4) is immediately interesting. Follow the function call in `case 4:` and jackpot:

    Click image for larger version

Name:	Screenshot 2026-10-30 at 2.05.16 AM.png
Views:	199
Size:	99.9 KB
ID:	614070
    Click image for larger version

Name:	Screenshot 2026-10-30 at 2.05.39 AM.png
Views:	209
Size:	218.6 KB
ID:	614068

    An unauthed, unbounded read in a loop that writes to a buffer (ignore the decompiled C code in that last screenshot, it's not looking all that great). Looks like the `UPLOAD` function to me!

    Lots of words and code to say that I was wrong and I'm now pretty convinced that CCP is supported on the e5 AND it can dump flash.

    Since the EEPROM flag is present on the MK60 dump, I'm pretty confident that it will be supported there too. Amazing.

    I know what the first thing I'm going to do when I get that MK60 is!
    2002 Topasblau M3 - Coupe - 6MT - Karbonius CSL Airbox - SSV1 - HJS - Mullet Tune - MK60 Swap - E86 Front Triangulation - ZCP Rack - Nogaros - AutoSolutions - 996 Brembos - Slon - CMP - VinceBar - Koni - Eibach - BlueBus - Journal

    2012 Alpinweiss 128i - Coupe - 6AT - Slicktop - Manual Seats - Daily - Journal

    Comment


      #92
      Oh baby! Now we’re getting somewhere
      ‘02 332iT / 6 | ‘70 Jaguar XJ6 electric conversion

      Comment


        #93
        Nice work Heinz - this is epic! very much enjoying following this along!

        Originally posted by heinzboehmer View Post
        (ignore the decompiled C code in that last screenshot, it's not looking all that great)
        FWIW pretty much all of the weirdness in the Ghidra C-code view can be resolved by making sure your function parameters (number and datatype) and return value (datatype) are correct. For example
        Code:
        int can_rx_797_payload(void)
        I'm willing to bet if you follow where it's called the return value isn't actually used. If so, and you modify the function prototype in Ghidra to be a void return type you'll see a bunch of that weirdness clean itself up.

        2005 ///M3 SMG Coupe Silbergrau Metallic/CSL bucket seats/CSL airbox/CSL console/6 point RACP brace/Apex ARC-8s
        Build Thread | Community Patch | MSS54 DS2 Tool

        Comment


          #94
          Originally posted by karter16 View Post
          FWIW pretty much all of the weirdness in the Ghidra C-code view can be resolved by making sure your function parameters (number and datatype) and return value (datatype) are correct. For example
          Code:
          int can_rx_797_payload(void)
          I'm willing to bet if you follow where it's called the return value isn't actually used. If so, and you modify the function prototype in Ghidra to be a void return type you'll see a bunch of that weirdness clean itself up.
          That's good to know! First time using Ghidra for this kind of work, so still stumbling through it. Will try to fix all this weirdness now.

          Edit: Oh man, it's so much nicer with these fixes. Thanks! Repo updated with this in mind, so any .0pa you feed the workflow will generate these nice Ghidra projects automagically.
          Last edited by heinzboehmer; 10-30-2026, 02:51 PM.
          2002 Topasblau M3 - Coupe - 6MT - Karbonius CSL Airbox - SSV1 - HJS - Mullet Tune - MK60 Swap - E86 Front Triangulation - ZCP Rack - Nogaros - AutoSolutions - 996 Brembos - Slon - CMP - VinceBar - Koni - Eibach - BlueBus - Journal

          2012 Alpinweiss 128i - Coupe - 6AT - Slicktop - Manual Seats - Daily - Journal

          Comment


            #95
            Nice work heinzboehmer!

            Comment


              #96
              Non-M MK60 sent by George has arrived. I'm still waiting on some pins to finish up the bench harness, so haven't poked at the CCP thing yet, but I will soon. These are supposedly non-flashable, but we'll see about that

              I did immediately cut it open, though, just to see what I could learn from probing the PCB:

              Click image for larger version  Name:	20261102_193700.jpg Views:	0 Size:	181.8 KB ID:	614481

              Lowest hanging fruit in my mind was to figure out the M track mode input. Bry5on did some digging into this earlier, so a lot of this is just redoing his work. Here's the pin that would be used for M track mode and the DSC button input. Keep in mind that this is on the non-M board, the ZCP board has slightly different values for the components:

              Click image for larger version  Name:	Screenshot 2026-11-04 at 12.48.46 AM.png Views:	0 Size:	145.4 KB ID:	614482

              Math checks out with some previous measurements.

              Poked around and found a few other input pins with similar layouts:

              Click image for larger version  Name:	Screenshot 2026-11-04 at 12.50.53 AM.png Views:	0 Size:	38.3 KB ID:	614483
              Click image for larger version

Name:	Screenshot 2026-11-07 at 11.54.30 AM.png
Views:	91
Size:	240.4 KB
ID:	614778

              The last two are interesting. Same exact layout as pin 38, but with some more circuitry attached at the point that would usually be grounded. This makes me think the TI IC is keeping the pin attached to R14 pulled _up_ when the MK60 is powered on, but I haven't tested. I think that makes sense, cause otherwise you'd have a permanent pull up line running all the way to the brake fluid reservoir and the parking brake.

              Anyway, I think getting the M track mode input working on a non-ZCP module is doable. The shunt resistor is different, but the RC filter is the same. If the input is too touchy, the SW debounce can be tweaked (once I figure out the reflashing of these modules) to make up for it. Worst case, you can cut a small access hole into the cover and swap the resistor out.

              Originally posted by karter16 View Post
              Re the TI chip I think it’s maybe a custom ASIC for controlling the hardware, it sits on the headers side of the board and some very similar part numbers are labeled on some 3rd party sites as “Teves PCU60”. I’m guessing “PCU” stands for “Pump Control Unit” or something similar.
              I think you might be right. At least one of the pins on the TI chip is connected to the network of transistors/MOSFETs right by the pump connector, so that would make sense.
              Last edited by heinzboehmer; 11-07-2026, 11:58 AM.
              2002 Topasblau M3 - Coupe - 6MT - Karbonius CSL Airbox - SSV1 - HJS - Mullet Tune - MK60 Swap - E86 Front Triangulation - ZCP Rack - Nogaros - AutoSolutions - 996 Brembos - Slon - CMP - VinceBar - Koni - Eibach - BlueBus - Journal

              2012 Alpinweiss 128i - Coupe - 6AT - Slicktop - Manual Seats - Daily - Journal

              Comment


                #97
                Updated the pin 9/27 schematic above after some more probing. I'm not at all convinced it's correct in the areas around Q1/Q2 (they both look so wrong, but I probed it a million times and this is what the meter says), but that section isn't really relevant at all to the button inputs.

                I think the general idea is this:
                • R14 goes to an `enable` output from the TI IC, which is likely just high whenever the module is booted. This pulls up the Pin 9 and Pin 27 inputs as well.
                • I bet R15 goes to an input in the TI IC. Probably used for monitoring pump current or short/open circuit faults.
                • R16 is probably another input. Highly likely that it controls the pump.
                Again, the schematic doesn't really agree with the above logic, but I trust this general idea more than my probing. The module is built with a multi layer board, so tracing stuff is super tedious and easy to mess up.

                Anyway, harness stuff has all arrived. I need to sit down this weekend and get it built. For this early prototype, I'm going to use a V4 Gauge.S board I have lying around, since it's ESP32 based and it has all the HW needed to do both K line and CAN communications. I found this, which would be perfect for in car flashing, but I haven't heard back from the creator yet: https://github.com/muki01/OBD2_K-line_Reader (scroll down to "Custom PCBs" for pictures of the thing)

                I'd like to avoid designing a custom PCB or building this around a J2534 pass through adapter ($$$ for the real ones), because I think it will limit the audience significantly. If I can make an off the shelf product (like the `muki01` device or a Gauge.S) work for reading/flashing, then anyone can buy one, flash custom FW on it and do as they please with their MK60.

                Just for clarity, a normal K+DCAN cable will not work for this because CCP is not a supported protocol on them. It's essentially impossible to find diagnostic type cables that support it, so you're left with crafting and sending out the CAN packets yourself. Easiest way to do this is have HW that (electrically) translates CAN to something a computer can understand (serial over USB or whatever), then have the computer do all the protocol logic. For this project, the HW will just act as a dumb pass through.

                The other option is to reflash an ELM327 bluetooth adapter with custom FW, similar to what's described in the ediabaslib repo: https://github.com/uholeschak/ediaba...p-obd-firmware

                This option is attractive cause those adapters are cheap and easy to find, but there are some drawbacks. It's a little tricky to get a genuine ELM327 with a real CAN controller (not transceiver) and there's also something that just doesn't sit well with me about trying to hit all these timing target over bluetooth. I know the protocol can handle it in theory, but man, my faith in bluetooth stability is basically nonexistent.

                Anyway, going forward with the Gauge.S route for now, will reevaluate the `muki01` device once I hear back from the creator.

                Also, here's some closeups I took of the board, just for posterity:

                Click image for larger version

Name:	20261102_193627.jpg
Views:	87
Size:	205.2 KB
ID:	614782
                Click image for larger version

Name:	20261102_193630.jpg
Views:	90
Size:	231.3 KB
ID:	614783
                Click image for larger version

Name:	20261102_193700.jpg
Views:	87
Size:	181.8 KB
ID:	614784
                Click image for larger version

Name:	20261102_203253.jpg
Views:	88
Size:	219.2 KB
ID:	614785
                Click image for larger version

Name:	20261104_182322.jpg
Views:	86
Size:	190.9 KB
ID:	614786
                2002 Topasblau M3 - Coupe - 6MT - Karbonius CSL Airbox - SSV1 - HJS - Mullet Tune - MK60 Swap - E86 Front Triangulation - ZCP Rack - Nogaros - AutoSolutions - 996 Brembos - Slon - CMP - VinceBar - Koni - Eibach - BlueBus - Journal

                2012 Alpinweiss 128i - Coupe - 6AT - Slicktop - Manual Seats - Daily - Journal

                Comment


                  #98
                  Might just be worth using a candlelight interface or something and just 'speak' CAN frames directly? Lot of hardware out there that can be flashed with that firmware and pretty cheap. Open source too if certain mods make CCP (and other stuff) easier.

                  Comment


                    #99
                    Originally posted by terra View Post
                    Might just be worth using a candlelight interface or something and just 'speak' CAN frames directly? Lot of hardware out there that can be flashed with that firmware and pretty cheap. Open source too if certain mods make CCP (and other stuff) easier.
                    The one thing with those CAN devices is that most of them don't speak K line and you need to enable CCP over K line first. I could use two separate devices, one for each protocol, but that feels a bit messy. Gauge.S can speak both directly with a bit of custom FW.

                    I actually already have "working" FW and an accompanying desktop CLI/GUI to drive the read/flash. It's unit tested and appears to work, but I haven't finished up the harness, so I haven't tested on the HW. Soon though.

                    The K line/CAN passthrough FW is super simple, so it should be very easy to port to other ESP32-S3 based boards. It seems like a new implementation of those types of boards pops up every few days now 🙂
                    2002 Topasblau M3 - Coupe - 6MT - Karbonius CSL Airbox - SSV1 - HJS - Mullet Tune - MK60 Swap - E86 Front Triangulation - ZCP Rack - Nogaros - AutoSolutions - 996 Brembos - Slon - CMP - VinceBar - Koni - Eibach - BlueBus - Journal

                    2012 Alpinweiss 128i - Coupe - 6AT - Slicktop - Manual Seats - Daily - Journal

                    Comment


                      Ah I see.

                      Realistically CAN isn't exposed on the OBDII port anyway, so in my mind it doesn't seem completely unreasonable to have two separate devices, but I definitely get the desire to have an all-in-one solution so to speak.

                      Comment


                        Originally posted by terra View Post
                        Ah I see.

                        Realistically CAN isn't exposed on the OBDII port anyway, so in my mind it doesn't seem completely unreasonable to have two separate devices, but I definitely get the desire to have an all-in-one solution so to speak.
                        That's a fair point. Fortunately running CAN to the OBD port is pretty easy. I made this quick harness to tap into the steering angle sensor a while back. Gives me CAN at the OBD port and for the Gauge.S that lives permanently in my car.

                        Click image for larger version

Name:	20240417_120743.jpg
Views:	49
Size:	65.3 KB
ID:	614975

                        In my mind, if you're gonna be messing with the MK60 in your car, it's worth doing the setup work so that you can just plug into the OBD port in the future.

                        I can't really think of an easier spot to grab CAN from, but open to suggestions!
                        2002 Topasblau M3 - Coupe - 6MT - Karbonius CSL Airbox - SSV1 - HJS - Mullet Tune - MK60 Swap - E86 Front Triangulation - ZCP Rack - Nogaros - AutoSolutions - 996 Brembos - Slon - CMP - VinceBar - Koni - Eibach - BlueBus - Journal

                        2012 Alpinweiss 128i - Coupe - 6AT - Slicktop - Manual Seats - Daily - Journal

                        Comment


                          Very exciting!!
                          Phoenix Yellow e46m3 Build Thread
                          Orient Blue E46 330i ZHP k24/dct/turbo Build Thread

                          Comment


                            Some updates. Bench harness is finished (well, mostly, still need to design and print a case for the Gauge.S so it isn't just hanging out there). I made it in a couple parts to mimic the on-car setup. This means I can unplug from the bench OBD port and plug into my car to test with the exact same SW/HW:

                            Click image for larger version  Name:	20261113_153615.jpg Views:	0 Size:	224.4 KB ID:	615391

                            With it finished, I started poking around. First thing I tried was the E5 method of enabling CCP. From the disassembly, these are the KWP commands that need to be sent to enable (over CAN on the E5 for sure, I tried K line on the E46, other variants might also use K line):

                            Code:
                            10 87 - Enters ECUAdjustmentMode
                            27 FB - Gets seed for auth level 0x7E
                            27 FC <computed_key> - Get auth level 0x7E
                            31 31 01 - Enable CCP
                            However, the E46 module does not respond to either the `27 FB` or `31 31 01` commands. I thought this might be an auth issue, so I built a quick scanner to scan for all available KWP functions at all available auth levels:

                            Code:
                            Full capability scan: auth levels, then every service space at each level.
                            Entered session 0x87. Scanning SecurityAccess: requestSeed (27 <odd>) at 01,03,..FF - a 67 reply with a non-zero seed means that level exists.
                            27 03: SEED 0x48B4 -> grants level 0x02.
                            27 07: SEED 0x48C4 -> grants level 0x04.
                            27 FD: SEED 0x4C6E -> grants level 0x7F.
                            Seed-granting levels: 27 03=L0x02, 27 07=L0x04, 27 FD=L0x7F.​
                            
                            ...
                            
                            31 (RoutineControl, 00 control byte) sweep 00..FF:
                              31 20: 7F 31 22 - EXISTS.
                              31 21: 7F 31 22 - EXISTS.
                              31 22: 7F 31 22 - EXISTS.
                              31 23: 71 positive - WORKS.
                            
                            ...
                            
                            10 (StartDiagnosticSession) sweep 00..FF:
                              10 87: 50 positive - WORKS.​​
                            So there's no equivalent seed/key pair for level 0x7E on the E46 and no equivalent function exposed to enable CCP. It's possible that there's a CCP enable command that isn't exposed unless you're at the right auth level, but if does exist, I can't see it at any of the 0x02, 0x04 or 0x07F auth levels.

                            I have it on good authority that the MK60E1 does not have CCP compiled in and behaves very similarly, which is a bummer for us. It's beginning to look like the E46 MK60 didn't get CCP compiled in ☹️

                            On the E5, `31 31 01` writes a persistent flag to the EEPROM, which should enable CCP at every boot (there's a SW handler that soft-disables it if it doesn't receive any CCP commands within some timeout, but let's not worry about that now). So, in theory, if this feature does exist, I could write the EEPROM flag (doable under the 0x7F auth level), power cycle the module and it would come up with CCP enabled. Catch here is that the E5 EEPROM is bigger than the E46 EEPROM, so if this flag does exist, it's at a completely different offset. I blindly poked around and wrote some bits to the EEPROM, but wasn't able to trigger anything. Maybe worth revisiting in the future.

                            There's still some paths forward, though. Namely, the bootloader. First thing I looked at was how the E5 gets to the bootloader. I think I briefly referenced it before, but I understand it better now, so here's the full flow:

                            Code:
                            10 87 - Enters ECUAdjustmentMode
                            1A 89 - Reads info about the unit (`unit_key`) that is needed to compute `auth_key`
                            31 07 03 <nonce32> - Returns `rand_num` used to compute `auth_key`
                            31 08 <auth_key> - Sends auth_key, which is computed by `MD5(fw_key || nonce32 || unit_key || rand_num || key)`
                            10 85 - Enters ECUProgrammingMode, i.e. triggers a SW reset into the bootloader
                            Note that `fw_key` is static in all the E5 FWs I've looked at, so its likely (hopefully) the same on the E46 module.

                            Also note that the FW explicitly checks if you're exactly at auth level 5 and denies the `10 85` request if you're at any other (higher or lower).

                            Unfortunately, as you can see from the E46 probe scan above, neither `1A 89` nor `31 07` exist. It's almost as if BMW didn't mean for these modules to be programmed outside the factory...

                            I need to go run my probe scan against an E5 just to be triple sure that the tooling is working correctly and the functions and auth levels are actually missing on the E46 module. Will do that on my E82 soon.

                            Worth noting that all of the above was tested on both the bench non-M MK60 and my car's ZCP MK60.

                            But there's still hope! Two hopes actually:
                            1. `10 85` does not jump directly to the bootloader. It writes a flag to the EEPROM, then triggers a SW reset. When the module is coming back up, it sees the flag and enters the bootloader instead of the application. So similar to CCP, if I can figure out the E46 offset, this might be a viable path.
                            2. I still haven't chased the UART serial handler (robots called it `mfg_test_cycle`, which I think they stole from the other repo, but whatever, I'll call it that too). As far as I can tell, its RX buffer is shared by K line KWP and the FW decides when it's gonna process KWP commands vs mfg_test_cycle commands. It looks like the FW listens to mfg_test_cycle commands only at very early boot, then switches to KWP, so I need to figure out the timing to test this.
                            A bit of a discouraging update, I know, but there's still some things to try.

                            I also haven't forgotten that MK does have a way to flash the E46 module without opening it up. Admittedly, it's driving me a little crazy knowing that there is some way to do it and I still have zero clues about what that is, but it also gives me hope that it can be replicated.

                            https://github.com/heinzboehmer/MK60...sembly-Process updated with some disassembly refinements and some analysis docs that document all of the above and more.
                            Last edited by heinzboehmer; Today, 06:50 PM.
                            2002 Topasblau M3 - Coupe - 6MT - Karbonius CSL Airbox - SSV1 - HJS - Mullet Tune - MK60 Swap - E86 Front Triangulation - ZCP Rack - Nogaros - AutoSolutions - 996 Brembos - Slon - CMP - VinceBar - Koni - Eibach - BlueBus - Journal

                            2012 Alpinweiss 128i - Coupe - 6AT - Slicktop - Manual Seats - Daily - Journal

                            Comment


                              (oh and a quick side note)

                              I haven't poked around on the MK60 with NCS expert enough to know if this is useful, but, as mentioned above, the 0x7F auth level lets you do any arbitrary write to the EEPROM. I think stuff like steering ratio and brake bias are stored there, so you should be able set those to whatever values you want.

                              I would highly recommend studying the E5 disassembly before blindly poking around the EEPROM, just to make sure the change will not have adverse side effects.
                              2002 Topasblau M3 - Coupe - 6MT - Karbonius CSL Airbox - SSV1 - HJS - Mullet Tune - MK60 Swap - E86 Front Triangulation - ZCP Rack - Nogaros - AutoSolutions - 996 Brembos - Slon - CMP - VinceBar - Koni - Eibach - BlueBus - Journal

                              2012 Alpinweiss 128i - Coupe - 6AT - Slicktop - Manual Seats - Daily - Journal

                              Comment


                                Originally posted by heinzboehmer View Post
                                Worth noting that all of the above was tested on both the bench non-M MK60 and my car's ZCP MK60.
                                The rumor mill says your zcp mk60 supports flashing, but the non-m does not. Have you tried to do ccp against your zcp module? Dunno if this is a hardware limitation or software…
                                Phoenix Yellow e46m3 Build Thread
                                Orient Blue E46 330i ZHP k24/dct/turbo Build Thread

                                Comment

                                Working...
                                X