ECU Flashing

Flashing is replacing the program in an ECU's non-volatile memory with a new one, for example when installing a software update at the dealer. Almost every ECU splits its memory into two section. A small bootloader runs first on power-up and is the only code that can erase and rewrite the much larger application area. The split also makes flashing recoverable: an application validity flag is set only once the new image is transferred and checked (e.g. CRC or proper signature validation). An interrupted flash leaves the bootloader in control and the flash can simply be retried. The reprogramming sequence uses the UDS services from the diagnostics chapter.

The Flash Sequence

A typical UDS reprogramming runs through the same services in a fixed order. Scroll through them below: the memory map on the right tracks where the program counter is, what the application region holds, and whether the ECU currently considers its application valid.

  1. 0Normal operation

    On every boot the bootloader runs a quick self-check, sees a valid application, and hands control to it. The car drives. This is the state we start and end in.

  2. 1Diagnostic Session ControlSID 0x10 · 0x02

    DiagnosticSessionControl with sub-function 0x02 (programmingSession) moves the ECU into the programming session. The application stops running and the ECU (re)starts in the bootloader, the only code allowed to erase and write flash.

  3. 2Security AccessSID 0x27

    The bootloader will not touch flash until the tester proves it is authorised. SecurityAccess runs a seed/key challenge; only an unlocked tester may continue.

  4. 3Erase memorySID 0x31 · 0xFF00

    A RoutineControl startRoutine with routine identifier 0xFF00 (eraseMemory) tells the bootloader to wipe the application region. This also clears the validity flag to not OK, so the old code is gone: if power is lost from here on, the ECU has only its bootloader left.

  5. 4Request downloadSID 0x34

    RequestDownload declares the address, the (un)compressed size, and the data-format byte of the image about to be sent. The region is already erased and waiting for it.

  6. 5Transfer data · block 1SID 0x36

    TransferData carries the first block. Each block is sized to the bootloader buffer and tagged with a block-sequence counter (here 0x01) that increments and wraps so a dropped or repeated block is caught.

  7. 6Transfer data · block NSID 0x36

    Each further block continues where the last left off and bumps the block counter. A real image is hundreds or thousands of these requests back to back.

  8. 7Transfer data · last blockSID 0x36

    The final block lands and the application region is full. The new code is all there, but the validity flag is still set to not OK.

  9. 8Request transfer exitSID 0x37

    RequestTransferExit closes the transfer. The bootloader can now checksum what it received and compare it against the size it was promised in step 4.

  10. 9Check programming dependenciesSID 0x31 · 0xFF01

    A second RoutineControl (routine identifier 0xFF01) runs the final integrity and signature checks. If they pass, the validity flag flips to OK and the image is marked bootable. Execution is still in the bootloader, though: nothing runs the new application yet.

  11. 10ECU resetSID 0x11 · 0x01

    ECUReset (sub-function 0x01, hardReset) restarts the control unit. The bootloader runs its self-check, sees a valid Application V2, and hands control to it. The ECU is back to normal operation, now running the new firmware.

Bootloader
Application OK
Empty SpaceApplication

The shape of the sequence is always the same, even when the details differ between manufacturers:

  • DiagnosticSessionControl (0x10 0x02) drops the ECU into the programming session (sub-function 0x02), which reboots it into the bootloader. The functional application is no longer running.
  • SecurityAccess (0x27) gates everything that follows. Until the tester passes the seed/key challenge, the bootloader refuses to erase or write a single byte. This is the lock most reverse engineering effort goes into, and it gets its own section below.
  • RoutineControl 0xFF00 (0x31) is the standard eraseMemory routine: it tells the bootloader to wipe the application region. From this point until the new image is validated the ECU is not driveable, so a power cut here leaves only the bootloader, which is why a botched flash "bricks" a unit.
  • RequestDownload (0x34) declares the address, the uncompressed/compressed size, and the data-format byte of the image about to be sent. The region is already erased and waiting.
  • TransferData (0x36) carries the firmware itself, split into blocks sized to the ECU's buffer, each prefixed with a block-sequence counter that increments and wraps so the bootloader can detect a dropped or duplicated block. A full image is many 0x36 requests back to back, filling the region a block at a time.
  • RequestTransferExit (0x37) ends the transfer and lets the bootloader checksum what it actually received against the size it was promised.
  • RoutineControl 0xFF01 (0x31) runs the "check programming dependencies" routine: the integrity and (where present) signature checks that decide whether the new image is allowed to be marked bootable. 0xFF01 is the standard identifier from the ISO specification, but the actual routine ID varies by manufacturer.
  • ECUReset (0x11) restarts the unit. The bootloader boots the freshly validated image, and the ECU is back to normal operation on the new firmware.

The exact byte layout of each of these requests, including the addressAndLengthFormatIdentifier and the block-counter handling, is covered service by service in the UDS chapter.

Security Access Algorithms

The lock on the whole sequence is service 0x27, SecurityAccess. It is a classic challenge-response: the tester asks for a seed, the ECU returns a random value, the tester transforms that seed into a key with a secret algorithm, and the ECU grants access only if the returned key matches what it computed itself.

SID 0x27 — SecurityAccess

🔒 locked
Tester(client)
ECU(server)
  1. 1. Request seed
    27 01
  2. 2. Send seed
    67 01 12 34 45 …
  3. 3. Compute key
    key = f(seed)
  4. 4. Send key
    27 02 AB CD EF …
  5. 5. Accept / reject
    67 02

The tester opens security level 0x01 by asking for a seed. An odd sub-function (0x01) means requestSeed.

The security rests entirely on the tester not being able to compute the key without knowing the secret. Two extra measures make brute-forcing harder: after a number of wrong keys the ECU arms a (time-delay) lockout, and many ECUs require a minimum delay between power-on and the first seed request, which also masks a weak random number generator that would otherwise hand out the same seed every boot. None of that helps if the algorithm itself is weak or if the secret is recoverable, which is the usual situation.

Breaking Weak Seed/Key Schemes

For decades manufacturers built their seed/key transforms in-house and kept them secret, and that secrecy was the whole defence. In the paper "Beneath the Bonnet" researchers reverse engineered the ciphers out of the firmware of four major manufacturers and found the same family of weaknesses everywhere [1]. All of them were lightly modified Galois LFSRs with a tiny internal state (24 or 32 bits) and proprietary, never-reviewed constructions:

  • The Ford cipher used a 24-bit challenge and response. Its modified Galois LFSR was seeded with a constant (0xC541A9) baked into the firmware, and the output was mixed against a 64-bit register made of the 24-bit challenge and a 40-bit secret shared across every ECU of that type. Two captured seed/key pair leaks enough of that secret to reconstruct the cipher. The researchers showed it can even be recovered with nothing but access to the diagnostic interface by an efficient brute-forcing strategy.
  • An Audi gateway used a 32-bit transform whose entire internal state was just the challenge itself, so the only entropy came from a fixed 32-bit tap constant (0x04C11DB7, the CRC-32 polynomial). On top of that weakness, one unit in this family shipped with a hardcoded backdoor: reply to any seed with the constant 0xCAFFE012 and the ECU unlocks regardless of the algorithm.

A state that small, combined with secrets that are shared rather than per-unit, means a handful of recorded seed/key pairs (or a firmware dump) is enough to recover the algorithm and forge keys at will. The paper's conclusion is blunt: proprietary cryptography with a small internal state is not a substitute for a real keyed primitive [1].

Volkswagen Group: SA2

Volkswagen's modern programming uses what is called the SA2 seed/key. Instead of hard-coding one algorithm per ECU, the key computation is shipped as a short bytecode program, the SA2 script, that lives inside the flash container (the FRF / ODX flashdaten covered in the OEM Update Files chapter). A tiny stack-based virtual machine in the tester loads the seed into a register and runs the opcodes against it: load constants, add and subtract, XOR, rotate and shift, and conditional jumps, ending by returning the transformed register as the key. For example, one published script turns the seed 0x1a1b1c1d into the key 0x6a37f02e [2].

Making the algorithm data rather than code is good engineering: a new ECU can ship a new script without updating the tester. But it has an obvious consequence for security. The script travels inside the flash file, and those files are downloadable through ODIS and the erWin portals, so anyone who has the flashdaten has the algorithm. Open-source implementations extract the SA2 bytecode straight from the container and execute it to compute keys for a live ECU [2][3].

References

[1]abJan Van den Herrewegen, Flavio D. Garcia. Beneath the Bonnet: a Breakdown of Diagnostic Security. ESORICS 2018, 23rd European Symposium on Research in Computer Security, Springer LNCS 11098, 2018.