Jump to content
Click here if you are having website access problems ×

Exploiting vulnerabilities in MEMS3 to deploy & execute arbitrary code utilities in RAM.


revilla

Recommended Posts

Here's something I just posted on my own website. It's a bit heavy reading but maybe some of the more techy people on here might find it interesting.

Download Link: http://andrewrevill.co.uk/Downloads/MEMS3Tools.zip

Exploiting vulnerabilities in MEMS3 to deploy & execute arbitrary code utilities in RAM.

In recent releases of MEMS3 Mapper I’ve added a number of “maintenance utilities” allowing the user to perform specific maintenance tasks on the MEMS3 ECU. These include:

·Reading & saving the contents of RAM. This is useful for diagnostic and development work I’m doing.

·Reading & saving the contents of the microcontroller module registers. As above.

·Reading & saving the ECU boot loader.

·Reading and saving the contents of the serial EEPROM (configuration memory). This allows an ECU to be fully cloned, including all of the immobiliser matching and adaptations etc.

·Writing a new boot loader to the ECU. See at the end of this article.

·Writing the contents of the serial EEPROM (configuration memory). ECU cloning as above.

·Erasing the firmware.

·Erasing the coding records. This can be necessary in order to allow Rover T4 system recognise the ECU as a factory new unit, as it will only program factory new units for Rover 75s and MG ZTs.

·Erasing the map.

·Erasing the serial EEPROM (configuration memory).

·Performing a full factory wipe of the ECU, returning it to new condition.

All of these have been achieved by adding permanent functionality to the ECU firmware in the form of patches. These patches needed to be inserted into a copy of the ECU firmware, which was then written to the ECU in order to give the additional capabilities required to support the above.

As these are all “one-off” maintenance tasks, what I really wanted to be able to do was to send small temporary programs from the PC to the ECU, load them into RAM (temporary memory) and execute them; then reboot the ECU to erase them completely from memory. This would be a much more lightweight solution, would mean that MEMS3 Mapper would never have to deal with old or out-of-date patches and would carry a significantly lower risk of having any permanent effects on the ECU.

Arbitrary code injection clearly poses a big risk in terms of security and robustness for any IT system and the Rover engineers had clearly taken considerable care to prevent the ECU from being called upon to do anything illegal; memory address ranges passed to routines are carefully validated to ensure that regions which might affect the stable operation of the ECU are not modifiable. There are no messages which allow arbitrary software to be executed; only the pre-defined routines in the ECU can be accessed by identifier codes, rather than by address. Although the KWP2000 standard do specify some services which can be used for this kind of things, most manufacturers implement them on development ECUs only and their specification call for them to be specifically excluded from production ECUs.

The designers had carefully closed off all of the methods of an “attack” on the ECU … except one! After a lot of searching and some careful planning and crafting, it was just enough in order to let me get into the ECU to do what I wanted to do.

Here is how the exploit works:

Writing to RAM

The first thing I needed to be able to do was to find some way of writing to temporary RAM.

The ECU does provided services for writing to RAM, however these are all too heavily locked down on production ECUs to allow me to anything clever:

·Service $3D “Write Memory by Address” – Sounds promising, by contains extensive checking of the addresses to be written to and prevents writes which might be used to subvert the normal operation of the ECU.

·Service $3B “Write Data by Local Identifier” – Allows a lot of specific variables and data records to be written, but does not allow writes to arbitrary addresses.

The ECU provides a set of services specifically for writing the main permanent ROM:

·Service $34 “Request Download”, Service $36 “Transfer Data” and Service $37 “Request Transfer Exit” – These three are used together to set up a programming operation, write the data and end the operation when complete.

… and here is where the security slips up. It is just about possible to get these to work on RAM addresses. You can use them to write to RAM addresses, but you have to live with following conditions and side-effects:

·The transfer routines include all of the functionality to unlock ROM addresses for programming. Writing to ROM is not as simple as writing to RAM. Each word written is preceded by an “unlock sequence” which is used to prepare the ROM to receive a programming write. These unlock sequences consist of a pattern of consecutive writes of specific code words to specific addresses. This functionality is baked into the routines that provided these services and so there is no way to avoid them. This causes a problem:

o   When the first word is written, the code executes an unlock sequence against the ROM. The ROM is then expecting the next write to be a word of programming data. But the programming data is written to RAM and is never seen by the ROM, which is left still expecting a programming write.

o   When the second word is written, the code again executes an unlock sequence against the ROM, but the ROM is expecting a programming write. The result is that the ROM misinterprets the first write of the unlock sequence as programming data and writes an unwanted word into permanent ROM.

o   After looking carefully at the code, and checking a number of ECUs after test writes, it was clear that this would ALWAYS just result in the word $AAAA being written permanently to address $10AAAA (the first write of the unlock sequence is to write the byte $AA to $10AAAA, but the ROM is configured to expect a word-sized write at this point and so the byte goes to both bytes of the corresponding word, so two bytes are corrupted at $10AAAA and $10AAAB with the word value $AAAA)

o   Address $10AAAA is in the boot loader sector of ROM. However, it is at least 4kB above the highest address used for code in any boot loader ever used by MG Rover. It is also around 13kB below the lowest address $10E000 used for calibration data for the boot loader, so luckily it sits right in the middle of a large block of unused memory and writes to it can be completely ignored. Corrupting the single word at $10AAAA is completely immaterial to the functionality of the ECU.

·The transfer routines include code which waits for the programming cycle in the ROM to complete after each write. Luckily again, this code will usually work OK. The ROM includes a status register. When you write to a word in ROM, until the write has finished, reading any address will get you the status register instead of the data at that address. The high bit of the status register contains the INVERSE of the data written; if you write a 1, you are therefore guaranteed tread a 0 from the status register while the programming cycle is executing and then once it is finished, you read the programmed data which is 0. If you write a 0, you read a 1 from the status register until it completes, when you start reading a 0. So the code usually just waits until it sees the bit it programmed in the high bit, and for RAM this happens immediately anyway so it reacts as though the programming cycle completed immediately. This does however seem to create potential timing issues which I think are behind the next point:

·Occasionally, and for reasons that I’ve not been able to fully understand yet, using the above services on RAM addresses actually causes the ECU to lock up. Actually, it’s rather more than occasionally, and the chances of a lock-up are largely determined by how much work you’ve done reading and writing the ROM recently. When the ECU does lock up, it seems to go into a tight loop. The ECU contains a watchdog timer system which is designed to detect this kind of thing happening. It is the responsibility of the ECU software to continually reset the watchdog timer to let the ECU know that it is still operating normally. If the watchdog timer does not get reset for a certain (very short) period of time, it reboots the ECU to restore normal operation. This means there’s still a way of getting around the lockup issue. We just need to write the code in such a way that it attempts a write to RAM; if it gets a normal response back from ECU, all is well. If it gets no response or an incomplete response, it needs to assume that ECU has rebooted. It then needs to wait for a short time (I’ve used 2 seconds as there’s no really hurry, a lot shorter would probably still work) to allow all communications functions to time out and the ECU to reset, then re-attempt the write. Although I’ve NEVER seen a write to RAM lock up after the second attempt, immediately after a clean reboot, for robustness in my code I’ve provided for up to 3 retries.

·The above lock up and retry issue means that you can’t really rely on these services to write to RAM in the middle of an ongoing process, or in places where it is critical that it should work firs time, but there is a way in which we can use them to do what we need reliably, as will be described below. We do need to remember though that if we are making a sequence of writes to RAM to build up some sort of structure, if any one of them fails then the ECU will reset and this wipes the RAM back to all $00 bytes, so we need to repeat the entire sequence again on a retry, not just the write that failed.

·When you write a block of bytes to RAM, it turns out that the internal routines only write ONE WORD at a time; they then return, and on the next call (to the subroutine, on the next pass around the boot loader main loop) they will write the next word if the previous write has completed. So we can only write one word at a time, and all of the subroutines then return before the next word gets written. As I will describe below, this turns out to be a significant limitation.

·All of this is very messy. It’s not the sort of thing you would accept in normal programming. But we’re hacking! So we have to take advantage of what is available and what we can turn to our advantage and work around the limitations by carefully structuring the processes we adopt.

Before writing to ROM, we would normally have to ask the ECU for a “Programming Session”. The way in which this is actually implemented is that the firmware exits and the ECU returns back to running the boot loader only. The boot loader variables are much smaller than the engine management data, and occupy only the lower part of the available RAM. In order to use these services we also need to call for a Programming Session (in a General Session, these services just return a negative response; the ECU is basically just refusing to overwrite the firmware while it is running, which makes sense). Once we have a Programming Session, most of the upper portion of the system RAM is available for our own use.

Executing Code

So we have as described above a crude but potentially usable way of writing arbitrary program code into RAM.

What we don’t yet have is a mechanism to get the ECU to execute it. The ECU does not allow the execution of arbitrary code at RAM addresses using any standard services, so we need to find a way to trick the ECU into executing our code.

The ECU boot loader (which is what will be running in our Programming Session, although much of this also applies to the main firmware too) consists of some initialisation code, followed by a loop which just executes indefinitely. Inside this loop the code calls all of the functions which handle all of the internal state machine management, including communications, ROM programming etc. These are structured as a number of major subroutines which do things loosely like “Handle Communications”, “Handle Programming”. Each of these will call a number of lower level subroutines which do things like “Check For New Messages”, “Reply To Previous Message”, “Check Previous Write Complete”, “Write Next Word” etc. In fact there is a whole bushy tree of subroutines which make up the boot loader code.

When each of these subroutines is called, the microprocessor needs to know where to go back to, to continue execute after the subroutine completes. So when an instruction to call a subroutine is encountered, the microprocessor puts the address of the next instruction onto the top of a stack. When the subroutine completes, it ends with an RTS instruction (Return from Subroutine). This reads the top address off the stack and jumps to that address to continue execution at the point immediately the call to the subroutine.

If a subroutine calls other subroutines, the return addresses are also placed on the stack and removed again as the lower level subroutines terminate.

… and the stack is in RAM, and we can write to RAM …

So the “trick” to get the ECU to execute our code is to overwrite one of these return addresses on the stack with the entry point to our own code. When the subroutine terminates, the microprocessor will attempt to return to where it left off, but in fact it will get the address of our code and so will “return” to executing the code we wrote into RAM previously.

We don’t need to worry about whether this leaves the stack a bit messed up because the plan is that the code we will be writing to the ECU will, once started, just take over the whole ECU. It will not run “under” the boot loader or firmware, it will never attempt to return back to them, it will instead run as a complete self-contained application, so it is free to wipe out any data structures in RAM and start again from clean, basically to boot the ECU again into the custom code, and when it has done its job it will simply reboot the ECU, which will wipe it clean out of memory and restart the boot loader and firmware from cold.

The only way in which we can attack the stack in this way is to overwrite the return address for a subroutine that is currently executing. As the only way we can write to RAM is using the standard ROM writing services described above, that means using these services to overwrite their own return addresses on the stack. This means that at the end of the write, when the subroutine for writing to ROM “returns”, it will actually be fooled into jumping directly into our code instead. And the simplest way of finding a fixed address to attack that will always work is to attack the very bottom entry on the stack; this is the return address where the top level subroutine in the tree returns to continue execution of the main loop.

But …

Here is where we hit a big snag. I mentioned earlier that the memory writing routines write ONE WORD AT A TIME and returns back to the main loop after each word is written. The return address we need to write is TWO WORDS long, and both of these words will be different between the entry point for our code (which is in RAM, in the range $00000000 to $00001DFF) and the original return address (which is in boot loader code, in the range $00100000 to $001FFFFF). So any attempt to overwrite the return address will fail, because after writing one word out of two (and therefore making a nonsensical address), the subroutine will return and jump to the partially written address, which will just crash the ECU.

But there’s another trick. We need to attack the stack in two places …

If we look at the code which starts up the ECU, we see the following. It has clearly been written in a higher level language (probably C) and compiled, so it has a structure imposed on it that it hand written machine does not necessarily adhere to. In machine code, you can jump between addresses at random, but higher level languages impose the idea of procedures and functions, and you call these and they return in an orderly manner. So as a result, the entry to the boot loader main loop as made as a SUBROUTINE CALL, even though it will NEVER RETURN. In theory, according to the structure of the language, it COULD return, and therefore there is a return address for it on the stack. You can see the call being made highlighted in red below, using a BSR instruction “Branch to Subroutine”.

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

                             * CONDITIONALLY EXECUTE FIRMWARE                            

                             *                                                            

                             * Checks that the firmware and maps are valid.               *

                             * Checks that the command to abort firmware execution has  ..

                             * Transfers control to the firmware through a permanent j  ..

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

                             undefined  funConditionallyExecuteFirmware ()

             undefined         D0b:1          <RETURN>

                             funConditionallyExecuteFirmware                 XREF[1]:     FUN_00123f84:00100622 ©  

        001007ca 08  01  00  00    btst.l     0x0 ,D1

        001007ce 66  16           bne.b      LAB_001007e6

        001007d0 0c  80  00       cmpi.l     #0x0 ,D0

                 00  00  00

        001007d6 66  0e           bne.b      LAB_001007e6

        001007d8 20  7c  00       movea.l    #labStartOfFirmware ,A0

                 11  00  00

        001007de 20  68  00  04    movea.l    (offset  ->labFirmwareMainEntryPoint ,A0),A0      = 0011562e

        001007e2 4e  d0           jmp        (A0=>labFirmwareMainEntryPoint )

        001007e4 60  04           bra.b      LAB_001007ea

                             LAB_001007e6                                    XREF[2]:     001007ce (j) , 001007d6 (j)  

        001007e6 61  00  00  10    bsr.w      funBootLoaderMainLoop                           undefined funBootLoaderMainLoop

                             LAB_001007ea                                    XREF[1]:     001007e4 (j)  

        001007ea 4e  75           rts


So we know that there will be one return address right at the bottom of the stack that will never be read. Although the subroutines for writing memory return back to the main loop after writing each word, the main loop never exits and never returns, so that top level return address just sits there being ignored.

We can happily overwrite that return address, one word at a time, as it is never used. We don’t get the problem of the ECU trying to jump to it when it is half written. So we can overwrite that return address with the entry point into our code. But hang on a minute, what use is that if it is never used? Well, it is never used because the main loop never normally returns, but what if we could somehow force the main loop to return, after we had written the top level return address? And it turns out we can. This is where the second point of attack on the stack comes into play. The actual main loop is (exactly, in all boot loaders ever used, luckily they changed very little across the boot loader versions ever used so we don’t have lots of variations to cope with) 78 bytes long, and the complete subroutine is only 124 bytes long. And although it never actually exits, it was compiled as a function with a return at the end, so there’s an RTS instruction at the end of it (which it just never gets to as it keeps going around the loop forever), and the function that appears immediately before it in the ROM also ends with an RTS instruction. As the last byte of an address spans a range of 256 contiguous bytes, that means THERE IS ALWAYS AN RTS INSTRUCTION AT AN ADDRESS WITH THE SAME FIRST 3 BYTES AS THE REAL RETURN ADDRESS, and whose address differs only in the last byte! So we can trick the ECU into jumping to the RTS at the end of the main loop by overwriting our own return address, and we only need to write the last word – so there’s no problem with the ECU jumping to a half written address any more.

Here’s a complete dump of the standard boot loader main loop, and the code that leads up to it. The loop actually starts at $0010081A with two instructions which reset the watchdog timer on each pass, and ends at $00100868, with a branch back to the start of the loop again at $0010081A. The only way out of the loop is the RTS at $00100872, and the only way to reach that is the branch at $0010086C, but immediately before that is the unconditional branch back to the start of the loop, so it’s just a compiler artefact and will never be reached.

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

                             *                           FUNCTION                         

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

                             undefined  funBootLoaderMainLoop? ()

             undefined         D0b:1          <RETURN>

                             funBootLoaderMainLoop?                          XREF[4]:     00100418 (*) ,

                                                                                          FUN_00123f84:0010060c © ,

                                                                                          funConditionallyExecuteFirmware:

                                                                                          FUN_00102748:0010277c ©  

        001007f8 2a  7c  00       movea.l    #0x10e000 ,A5

                 10  e0  00

        001007fe 61  ff  00       bsr.l      FUN_00105dbe                                     undefined FUN_00105dbe()

                 00  55  be

        00100804 11  fc  00       move.b     #0x0 ,(PFPAR ).w                                  = ??

                 00  fa  1f

        0010080a 46  fc  20  00    move       #0x2000 ,SR

        0010080e 08  f8  00       bset.b     0x5 ,(BYTE_00000415 ).w

                 05  04  15

        00100814 08  f8  00       bset.b     0x3 ,(BYTE_00000414 ).w

                 03  04  14

                             LAB_0010081a                                    XREF[1]:     00100868 (j)  

        0010081a 11  fc  00       move.b     #0x55 ,(SWSR ).w                                  = ??

                 55  fa  27

        00100820 11  fc  00       move.b     #-0x56 ,(SWSR ).w                                 = ??

                 aa  fa  27

        00100826 61  ff  00       bsr.l      FUN_0010370e                                     undefined FUN_0010370e()

                 00  2e  e6

        0010082c 61  ff  00       bsr.l      FUN_0010292a                                     undefined FUN_0010292a()

                 00  20  fc

        00100832 61  ff  00       bsr.l      FUN_001058a4                                     undefined FUN_001058a4()

                 00  50  70

        00100838 42  38  04  07    clr.b      (BYTE_00000407 ).w

        0010083c 31  fc  00       move.w     #0x2 ,(BYTE_000002dc ).w

                 02  02  dc

        00100842 11  fc  00       move.b     #0x1 ,(BYTE_00000407 ).w

                 01  04  07

                             LAB_00100848                                    XREF[1]:     0010086e (j)  

        00100848 08  38  00       btst.b     0x4 ,(BYTE_0000057e ).w

                 04  05  7e

        0010084e 67  10           beq.b      LAB_00100860

        00100850 0c  b8  00       cmpi.l     #0x0 ,(BYTE_00000260 ).w

                 00  00  00

                 02  60

        00100858 67  06           beq.b      LAB_00100860

        0010085a 61  ff  00       bsr.l      FUN_001058a4                                     undefined FUN_001058a4()

                 00  50  48

                             LAB_00100860                                    XREF[2]:     0010084e (j) , 00100858 (j)  

        00100860 0c  78  00       cmpi.w     #0x0 ,(BYTE_000002dc ).w

                 00  02  dc

        00100866 6e  06           bgt.b      LAB_0010086e

        00100868 60  00  ff  b0    bra.w      LAB_0010081a

        0010086c 60  04           bra.b      LAB_00100872

                             LAB_0010086e                                    XREF[1]:     00100866 (j)  

        0010086e 60  00  ff  d8    bra.w      LAB_00100848

                             LAB_00100872                                    XREF[1]:     0010086c (j)  

        00100872 4e  75           rts

 

Highlighted in red is the top level subroutine call (to address $0010292A) that ends up handling the ROM writing functions and highlighted in green is the return address from the call $00100832, which appear as the second entry on the stack, the first entry being the return address FROM the main loop which would be taken if it ever returned, through the RTS instruction highlighted in purple at $00100872.

So the attack is structured as follows:

·We write our code routine into free RAM starting at address (say at $00000C00).

·We write the entry point (maybe $00001100) address to the unused, bottom stack entry.

·We write the address of the RTS instruction $00100872 to the next stack entry.

·This is only one byte different from the original address $00100832.

·As the writing function returns, it is tricked into jumping to $100872.

·This causes the main loop to “return” to the address we wrote previously.

·Our code gets executed.

There is one final twist to getting the code executed.

Some of the maintenance routines I’m implementing do things like “Erase Firmware” and “Erase Map”.

In the ECU startup code, it checks whether valid firmware and map are loaded and only executes the firmware if so. If not, it aborts the firmware load and jumps back to loading the boot loader. But in doing so it, it takes a slightly different route to the boot loader entry point, leaving one additional level of unused return address on the stack. So depending on whether the ECU decided it could or could not load the firmware when it booted, we may have two different sets of stack addresses to attack.

This is not a great problem for us though, as in the second case the bottom entry on the stack is truly unused and overwriting it will do nothing.

The custom code written to write back a modified response message (in the case of custom agents that include all of the communications functions) or to simply cut off the response message being sent (in the case of simpler custom agents that don’t). This means that we can tell whether out attack has succeeded by looking whether we got a standard response to our RAM write message (attack failed) or a modified response (attack succeeded).

So the attack proceeds as follows:

·Write the code to $0000C00.

·Write the entry point to $000001FC (last stack entry – note that the stack actually grows DOWNWARD on a 68000 system, and the ECU initialisation code sets the stack pointer SP to $00000400, so when pushing the first return address to the stack it decrements this by 4 and writes the address at $000001FC).

·Write $0872 to $000001FA (lower word of return address from writer subroutine – note that as the stack grows DOWNWARD, when the next return address is pushed the stack pointer SP is again decremented by 4 to $000001F8, the return address is then written to into the 4 bytes at $000001F8 … $000001FB, with the lower word starting at $000001FA).

·If we get a custom response, job done and the custom agent code has taken over the ECU.

·If we get a standard response …

·Write the entry point to $000001F8 (last-but-one stack entry, 4 bytes lower address than the first attempt).

·Write $0872 to $000001F6 (lower word of return address from writer subroutine, again 4 bytes lower address than the first attempt).

·If we get a custom response, job done and the custom agent code has taken over the ECU.

·If we get a standard response … something has gone wrong!

Putting all of this strategy together and using the ROM write messages with the retry strategy discussed above finally gives us a somewhat complicated, but ultimately very reliable way of writing out own code into the ECUs temporary RAM and executing it. This can all be wrapped up in a way that the user is completely unaware of the complexities going on behind the scenes.

Taking Over

All of the tasks this will be used to implement are one-off maintenance tasks. We need to do something to the ECU, then reboot it (to wipe ourselves out of memory and start it running normally again). We don’t need to worry about working alongside the ECUs native code, with a running engine (you can’t request a Programming Session with a running engine, the ECU will simply refuse the request). We can just take over completely.

We don’t need to worry about how to initialise all of the hardware modules in the ECU either; the boot loader will already have done all of that at the point we take over (unless of course we want to change the configuration of something, for example to implement our own custom communications suite or something – we can’t use the ECU’s built-in communications from inside our custom agents as by taking full control of the ECU we are effectively disabling all of the native functions).

So we can act like a stand-alone 68000 CPU application and start afresh, assuming the hardware is all set up for us.

The first thing that each custom agent starts with is a vector table. Any 68000 application normally starts with a vector table. This contains all of the interrupt and exception vectors, that tell the microprocessor where to jump to when specific conditions arise (these might be error conditions or just events, such as new byte being received over the OBDII port which needs to be processed, or regular “clock tick” from the periodic interrupt timer). In order to take full control, we need to make sure that the microprocessor isn’t being pointed back to the native code every time an event happens. All of the interrupt vectors which are assigned in the native boot loader code are replaced by pointers to dummy “do nothing” interrupt handlers. This means that when the Serial Communications Interface module finishes sending the response message byte, or receives a new character from the OBDII port, or when the periodic interrupt timer “clock” ticks, these interrupts are handled by my code which just ignores them, does nothing and returns. Some of the more complicated custom agents which need to be able to communicate using messages replace the Serial Communications Interface (SCI) interrupt handler with a real handler of their own, rather than a dummy handler.

The entry point code disables the SCI and then waits for the current byte transmission to complete (this ensures that the PC code gets the response it expected to signify that the custom agent has been launched). It then immediately sets up any dynamic addresses in the vector table (to make it a lot quicker to write the custom agents to the ECU, where the custom agents typically contain very little code and a large vector table, the whole vector table is not written directly to the ECU but is built up in code as the agent starts) and wipes the whole of its variables area in RAM to $00 (it could be loading into memory previously used by something else and so otherwise variables might start with random values), then switches the VBR (Vector Base Register) to point to its own vector table.

At this point it has complete control and can set about doing the task it was designed to accomplish.

When the task is finished, the agent reboots the ECU. How to do this correctly is not quite as simple as it sounds. The CPU does implement a RESET machine code instruction, but this does NOT do what you might expect; it asserts the RESET signal from the CPU to tell all of the peripheral chips to reset, but it does not reset the CPU itself, which then continues execution at the next instruction. I could set about reloading the boot loader, but in the end I decided the simplest, cleanest and most reliable way was to execute a BGND instruction. This is designed to put the CPU into background debugging mode, where an external BDE debugger can take control of the CPU, but with no debugger attached it is regarded as an Illegal Instruction Exception, which causes the ECU to execute a full reset.

Communications & Non-Communications Agents

The custom software agents are broadly of two kinds.

·Non-Communications Agents typically do one simple job as soon as they are loaded, then reboot. These include:

o   Writing a new boot loader to the ECU.

o   Erasing the firmware.

o   Erasing the coding records.

o   Erasing the map.

o   Erasing the serial EEPROM (configuration memory).

o   Performing a full factory wipe of the ECU, returning it to new condition.

·Communications Agents typically contain a message-based communications suite of their own. These are larger programs and are used for more complex operations or where data is to be sent between the PC and the ECU. Once loaded, they wait for messages and respond to them. They also have a timeout function, ensuring that if they don’t receive a message in a given timeframe they reboot the ECU anyway; this prevents the ECU from getting stuck in a custom state if communications are lost, if the user cancels etc. The PC application sends messages in a timely manner to achieve the tasks required, then sends a message to tell the agent to reboot the ECU when done. These include:

o   Reading & saving the contents of RAM.

o   Reading & saving the contents of the microcontroller module registers.

o   Reading & saving the ECU boot loader.

o   Reading and saving the contents of the serial EEPROM (configuration memory).

o   Writing the contents of the serial EEPROM (configuration memory).

The Results

In order to use any of this, you will need to download the latest version (5.87 or later) of my MEMS3 Tools from here: https://andrewrevill.co.uk/Downloads/MEMS3Tools.zip

 All of the above detail is pretty much hidden from the user. The overall result of all of these underlying mechanisms is that all of those tasks described above now no longer require any kind of custom patch to be installed on the ECU in advance. So for example you can just select the menu item Read & Save Boot Loader while connected to a completely standard ECU and it will just work. Internally it will take care of deploying the custom agent to RAM and triggering the above mechanisms but the user will just see the boot loader being read as before.

This is a much better user experience, a much simpler process to follow, takes a lot less time and presents much less risk of a permanently installed firmware patching having unwanted side effects. Previously it was actually necessary to install a patched firmware to invoke the function to erase the firmware!

There is one further slight twist. Some of the original custom patches that this functionality replaces were slightly more flexible, for example the option to read the module registers originally allowed you to read the registers on a running ECU, even with a running engine, which was useful when developing things like the live mapping patch.

The latest versions of MEMS3 Mapper include a Wizard called (Firmware) Unrestricted Read which modifies the current projects firmware to remove all restrictions on the address ranges which may be read using the standard functions. This provides a very light weight mechanism to allow you to modify the firmware to support all of the read options without custom agents, on a running engine. The corresponding functions in MEMS3 Mapper therefore try to read the memory using standard functions first. If the firmware has been patched by the wizard, this will succeed. If not, it will be rejected by the ECU and the code then resorts to deploying custom agents.

Boot Loader Updates

The ECU code consists of two completely separate applications:

·The boot loader. The job of the boot loader is to manage the processes around loading, verifying and launching the firmware.

·The firmware. The job of the firmware is to run the ECU under normal conditions. It handles all of the engine management features of the ECU etc.

When writing an updated firmware to the ECU, the firmware is shut down and we are talking to the boot loader application.

The boot loader application barely changed across the whole lifetime of MEMS3. The boot loaders on turbo, automatic, manual and VVC ECUs are all basically the same (in many cases the only bytes different are the part number and boot ID). The only differences are in some VERY early ECUs (prior to version bootp009, I’ve only ever seen bootp004 on an MG ZT 1.4 NNN100682 where it looks as though the boot loader was still in development and the rest of the ECU code was still very buggy too) and the very last VVC 160 ECUs (which have version bootp039 with some small additional features).

In most real-life cases, there is nothing to be gained from updating the boot loader on a ECU. It won’t have any impact whatsoever on engine management as the firmware takes over to run all of those functions, but for my development purposes, this could be very useful; for example I if could increase the communication Baud rates in a copy the boot loader and write that to an ECU, I could make the whole development and testing cycle a lot quicker (the standard boot loader uses 9600 and 10400 Baud, in testing I’ve shown the electronics are reliable up to considerably higher Baud rates until eventually the noise internal filtering starts to filter out the signal, but for development and testing purposes I could easily increase the ISO Baud rate by a factor of 4 to 41600 Baud – I go as far as 72800 Baud in the Live Mapping patch, but only for visual display data where it really doesn’t matter if there’s the occasional error).

Updating the boot loader is not something that the standard ECU code supports. It’s not really possible to erase and rewrite the boot loader while talking to the running boot loader. It’s also something you would not undertake lightly. It has to go right, first time, every time. If anything goes wrong with the boot loader update, or if it is interrupted part way through, the ECU will no longer be able to communicate and is therefore no longer capable of being programmed with a new boot loader to fix the issue. It will effectively be dead, permanently bricked and not recoverable even through my Recover Bricked ECU function, requiring disassembly, desoldering of the ROM chip, reprogramming on the bench and reassembly.

Using the custom RAM agent mechanism described here, I decided to add a facility to safely update the boot loader. It works as follows:

·It tells you what it’s about to do and confirms that you want to proceed.

·It checks that the boot loader already installed is a recognised version which is capable of supporting the custom RAM agent system. If not, it won’t allow you to proceed.

·It prompts you to select a boot loader file to write. It then checks that the boot loader you have selected also meets the above requirements. This prevents you from flashing a boot loader which will not then allow you to flash it back to how it was again.

·It does a few basic safety checks on the boot loader file. Is it for a petrol ECU or a diesel ECU? Is the file size correct? etc. Unfortunately it cannot protect you against flashing a damaged boot loader or one containing coding errors that will disable the ECU, but it can make sure you’re not doing making a silly and obvious mistake and selecting the wrong file.

·It erases the firmware. This is to give it enough space to store both the old and new boot loaders in order to allow the actual update to take place as a self-contained operation within the ECU, so that there is no danger of the update being interrupted if the connection the with the PC is lost. It does mean that you have to flash a firmware and map when finished, but that’s just routine functionality of MEMS3 Mapper so no problem.

·It erases the map. Strictly speaking it doesn’t need to do this, but having a map and no firmware is an unnatural state, in that the stock code doesn’t allow you to erase the firmware without also erasing the map, so I’ve done it to keep the ECU in a state that the stock ECU could might expect to see.

·It downloads the new boot loader into the erased firmware space on the ROM. In order to avoid confusing the ECU boot loader code which checks whether a firmware is loaded, it loads it at a higher address ($00120000) than that at which the firmware would normally be loaded ($00110000). All of the ECUs checks for firmware signatures etc. happen within the first few bytes in the $00110000 sector, so if the process is interrupted or the ECU is powered off and on again at any stage up until this point, it will just see itself as having no firmware or map and will go into programming mode as normal, running the original boot loader which is still intact.

·Once everything is in place, it displays a large red warning to the user to say that the boot loader is about to be interrupted and the process MUST NOT be interrupted during the final stage (which only takes between around 4 and 6 seconds) and asks for final confirmation. The user is still safely able to cancel the operation at this stage, and the ECU will be ready for regular programming.

·If the user confirms that they want proceed, it uses the exploit mechanism described in this article to deploy a small custom agent into RAM and execute it from there. This is a non-communications type agent, it does not depend on anything outside of the ECU, and can start its work immediately as soon as it is loaded. Even if the exploit mechanism fails (it always works!), the result would be that the custom agent would just not be triggered and the old boot loader would remain intact, so it is fail-safe.

·The RAM agent erases the old boot loader, copies the new boot loader from the high firmware area into the boot loader area, then erases the firmware area again (by using the next ROM sector address of $00120000 for the copy of the firmware, we don’t need to erase the first sector from $00110000 to $0011FFFF which improves performance a bit). All of this time, the large red warning remains visible to tell the user not to interrupt the processs. In fact there’s only really a window of 1 and 2 seconds between starting the firmware sector erase and finishing the internal copy during which interrupting the power to the ECU would actually cause damage.

·When it’s finished, the RAM agent reboots the ECU, which boots up on the new boot loader code. The PC application queries the ECU identity information and displays a dialog to the user showing the new boot ID in place.

What’s Left of Custom Patches?

MEMS3 Mapper still uses the original custom firmware patch mechanism for anything that is designed to be permanently resident on the ECU (as opposed to doing a one-off task). At the moment this includes the Live Mapping system, Dual-Map Live Switching and the Debugging system.

Link to comment
Share on other sites

A postscript:

I said in my original article that I was supporting all MEMS3s except for some VERY early Rover 75 ECUs. I didn't think many people would have those.

Within about an hour of posting it on my site, I had an email from someone who had tried it and found it wasn't working, so I got him to send me details of his ECU and guess what ... it was the early NNN100682 with boot loader bootp004 that I wasn't supporting. A bit more asking around and it seems it's actually relatively common on earlier Rover 75s and MG ZTs, which are the cars most in need of the coding erase function etc.

I did some head scratching. bootp004 just didn't seem to support the memory writing services reliably. So I came up with the idea of making up some MEMS3 Mapper project files which, in place of firmware, had a boot loader update program. So basically a copy of a newer boot loader and the program code I was using to update the boot loader, all wrapped up with just enough signatures and version numbers to look to the ECU like a normal firmware. The idea was you could then write one of those to the ECU in the normal way, the ECU would think it had a valid firmware and map and so would try to run it, but the "firmware" would actually just update the boot loader, delete itself and then reboot the ECU.

I got all of that working very neatly in the end. I now have some "Boot Loader Update Package" project files which you can write to the ECU as a normal firmware, but in fact they just update the boot loader to something later. And because they don't rely on any of the hacks I was doing, they work fine on the early ECUs, and once you've updated them to a newer boot loader version, all of the other stuff works fine with them.

 And then ... I discovered it was all unnecessary!

It turns out that the only thing stopping all of the hacks that I had developed working on these older ECUs was that the implementation of the ISO communications protocol in the early boot loaders was buggy / badly broken / a bit unfinished.

MEMS3 Mapper supports two different protocols, the standard ISO KWP2000 protocol at 10400 Baud and a proprietary Rover BMW version using a different message format at 9600 Baud. And the Rover BMW protocol worked fine on the early ECUs!

So long as you select Rover BMW as the protocol, everything I had developed and described in my article works just fine on even the earliest MEMS3 ECUs.

In my code I've already had to work around a number of bugs in the ISO protocol on earlier ECUs, where messages from the ECU come back with missing checksums, wrong checksums or just corrupted. In some cases you can even see where it builds up the message in a buffer then sends it from the wrong starting address, so you get a few bytes missing from the start of the message and a few bytes of junk stuck on the end.

In the next release I think I'll take out all of the boot loader version number checks and the horrible workarounds and replace them with a prompt suggesting you switch to Rover BMW protocol if you try to connect to a very early ECU using ISO protocol. ISO protocol is still the best one to choose for later ECUs as it has a special "wake-up" signal that it uses to grab the ECUs attention at the start of a conversation, which tends to pull the ECU out of anything if it gets stuck. It also has a built-in timeout, which means if it gets into a situation where it's waiting for something and the connection is lost, it doesn't sit waiting for ever.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...