Skip to content
AutoST by Zyberum GmbH
Menu
FuzzingUDS

A UDS fuzzing methodology that finds real bugs

How to fuzz an ECU over UDS without wasting runs: where to inject, how to detect a crash, how to make a finding reproducible, and how to stay on the bench.

Tom Zaubermann · Published · 9 min read

Fuzzing an ECU over UDS means sending malformed and unexpected diagnostic messages and watching for the moment the ECU stops behaving. Done well it finds parser bugs, state-machine faults and resource exhaustion that no positive test would reach. Done badly it burns hours sending traffic the ECU rejects in the first byte. The difference is method: inject where the ECU actually parses, detect a crash from outside, and make every finding reproducible. This is a bench activity only, never a vehicle in traffic.

Where to inject

A UDS request over CAN is carried by ISO-TP (ISO 15765-2) and shaped as a service identifier, an optional sub-function, and a payload. Each layer is a place a parser can go wrong, and each deserves a different mutation strategy.

TargetWhat you mutateWhat it exercises
Service IDThe SID byte, including non-standard valuesThe service dispatcher
Sub-functionThe sub-function byte and its reserved bitsPer-service branching
PayloadLength and content of the data bytesThe service’s own parser
ISO-TP framingFirst-frame length, flow control, consecutive frame sequenceThe transport layer below UDS

The ISO-TP layer is the one people forget, and it is where we most often see a reset. A first frame can declare a length of up to 4095 bytes; an ECU that allocates that buffer before checking the real data, or that mishandles a sequence-number gap, can be knocked over without a single valid UDS byte.

tx 10 0F FF 01 02 ...   first frame claiming 0xFFF = 4095 bytes
tx 21 ...               consecutive frame, wrong sequence number

A sensible strategy, not pure randomness

Fully random bytes mostly produce 7F xx 11 or 7F xx 13 and teach you little. A productive run narrows the field first:

  1. Enumerate first. Know which services answer in which session before you fuzz. Fuzzing a service that is not supported is wasted effort.
  2. Seed with valid structure. Start from well-formed requests the ECU accepts, then mutate outward. A payload that is almost valid reaches deeper code than one that is rejected immediately.
  3. Fuzz length aggressively. Off-by-one, zero-length and oversized payloads find buffer bugs faster than content mutation.
  4. Hold a session open. Re-send TesterPresent or re-enter the session periodically, because a service you want to fuzz may close its session mid-run.
  5. Keep an exclusion list. Exclude 0x11 ECUReset, 0x27 SecurityAccess and 0x34/0x36 download services unless you mean to, or the run resets the ECU constantly and you learn nothing.

Detecting a crash from outside

You cannot see the ECU’s memory, so detection is indirect. Three signals, combined, catch almost everything:

  • Liveness. After every iteration, send a TesterPresent (3E 00) and expect 7E 00. No answer, or a wrong one, means the ECU stopped responding or reset.
  • DTC monitor. Optionally read the DTC count (19 01) and flag any change. A new fault code during a fuzz run points at an internal error even if the ECU stays alive.
  • Timeouts and transport errors. A request that times out or produces an ISO-TP error is recorded with its exact payload.
tx <fuzzed request>
tx 3E 00           liveness check
(no response)      -> candidate crash at this iteration

A single missed liveness check is a candidate, not a confirmed finding. Confirm it by replaying the last payloads, because benches have glitches too.

Making a finding reproducible

A crash you cannot reproduce is a story, not a bug report. Drive the run from a stored pseudo-random seed so the entire sequence is deterministic: same seed, same payloads, same order. Record per finding the iteration number, the timestamp, the monitor that caught it, the triggering payload, and a short history of the payloads leading up to it. That package is what a developer needs to fix it, and what an ISO/SAE 21434 verification report needs as evidence.

On an on-board charger we fuzzed over UDS, the ECU reset on a specific oversized 0x2E WriteDataByIdentifier payload. Because the run was seeded, we replayed the exact iteration and the developers reproduced it on their own bench the same day.

Staying safe

Fuzzing can put an ECU into a fault state, leave it in a stuck session, or brick it. Three rules keep that from turning into an incident:

  • Run on an isolated bench, never on a vehicle that could move or a system whose failure could hurt someone.
  • Keep a recovery path: a known-good flash, a power cycle procedure, a way out of the programming session.
  • Treat the programming session and reset services with care, and exclude them unless the test target is the flashing path itself.

AutoST’s fuzzing engine implements this methodology directly: seeded, reproducible UDS and CAN runs, a TesterPresent liveness check after every iteration, an optional DTC monitor, per-finding payload capture, and an explicit bench-only posture. The method matters more than the tool, but the tool is what makes it repeatable across every ECU and every build.

FAQ

Frequently asked questions

What is the difference between UDS fuzzing and CAN fuzzing?

UDS fuzzing mutates structured diagnostic messages over ISO-TP: service identifiers, sub-functions and payloads. CAN fuzzing sends raw frames on arbitration IDs. UDS fuzzing reaches the diagnostic logic, CAN fuzzing reaches everything that parses a frame, including code below the diagnostic stack.

How does a fuzzer know the ECU crashed?

It cannot see inside, so it watches from outside: a TesterPresent liveness check after every iteration, an optional DTC-count monitor, and timeouts or transport errors on the fuzzed request. A stopped response, a reset or a new fault code is the signal.

Why does a fuzzing run need a seed?

A stored pseudo-random seed makes the whole run reproducible. Without it, a crash you saw once is a crash you can never show a developer again. With it, you replay the exact sequence and hand over the payload that triggered it.

Call usBook a demo

Pick a time that suits you

Open in a new tab