Skip to content

Vehicle & CAN Bus Security

Vehicle security analysis covers the automotive network estate: the CAN bus (Controller Area Network — the arbitrated, broadcast, authentication-free bus that connects a vehicle’s ECUs), its network segmentation (powertrain/chassis/body/infotainment domains behind gateways), the UDS diagnostics layer (ISO 14229 — the standardized diagnostic services that read/write ECUs, often protected by weak security-access keys), and the entry surfaces: the OBD-II port (physical), the telematics unit (cellular — the remote surface), the infotainment head unit (the internet-facing computer with USB/WiFi/Bluetooth), and the keyless-entry RF path (WI6’s sub-GHz world, vehicle edition). The analysis craft: CAN frame literacy (11/29-bit IDs, DLC, data, the CRC that protects integrity against noise but not against attackers), traffic census and DBC-based reverse engineering (mapping IDs to meanings), UDS session assessment (which services answer, what the security-access algorithm is), and the attack chains the research lineage established — from the OBD-port injection to the remote compromise via infotainment/telematics that reaches the CAN bus through the gateway [verify per research lineage: Miller/Valasek 2015 class].

Vehicles are safety-critical IoT at fleet scale: the same architectural patterns as every other embedded estate (H1-H4’s buses, H2’s firmware, H9’s cloud APIs) with the addition that the actuators steer and brake. The security model’s core weakness is structural: CAN was designed for reliability among cooperating nodes — every frame broadcasts to every listener, arbitration is by identifier priority, and there is no source authentication (any node can transmit any ID), so an attacker with a foothold on any CAN segment can impersonate the engine, the brakes, or the dashboard — the physical effect depends only on which segment the foothold reaches. The research lineage (2015-class remote compromises via infotainment/telematics reaching the CAN bus [verify specifics]) forced the industry’s response: gateways between domains (infotainment CAN → chassis CAN with filtering), secure onboard communication (AUTOSAR SecOC — MAC-protected frames [verify adoption]), and locked-down UDS access. For defenders and product teams, the assessment questions are the module’s checks: which ECUs accept diagnostic writes without proper security access, does the gateway filter cross-domain frames, is the telematics surface segmented, do the keyless-entry systems use rolling codes (WI6’s classification), and what does the CAN gateway log. For assessors, the craft is the census-and-inject discipline on bench-owned ECUs and lab vehicles — the safety boundary is absolute: no transmission against vehicles on public roads, no actuator testing without rigs that replace the mechanical loads.

The CAN frame, the vehicle network, and the assessment surfaces:

CAN FRAME (base, 11-bit ID) — no source authentication
[SOF][ID 11b][RTR][DLC 4b][DATA 0-8B][CRC][ACK][EOF]
arbitration: lower ID wins (priority by number)
broadcast: every node hears every frame
integrity: CRC protects against NOISE, not attackers
(no MAC, no origin auth — anyone can send any ID)
VEHICLE NETWORK DOMAINS
[powertrain CAN] engine/transmission (500kbps, fast)
[chassis CAN] brakes/steering (safety-critical)
[body CAN] lights/doors/comfort (125kbps)
[infotainment] head unit (Linux/Android/QNX)
[telematics ECU] cellular modem, remote services
gateways route/filter between domains -- the firewalls
UDS DIAGNOSTICS (ISO 14229 over CAN, IDs ~0x7xx)
services: read/write memory, DIDs, routines
security access: 0x27 seed/key (often weak algo)
the "USB debug port" of the vehicle world
ASSESSMENT SURFACES
OBD-II port -> CAN directly (physical, legal lab)
telematics -> cellular remote (H10 surface)
infotainment-> USB/WiFi/BT (H5/WI5 classes)
keyless RF -> sub-GHz (WI6 classes)

The CAN census is the recon: a SocketCAN interface or OBD-side logger captures the frame population — ID frequencies (the IDs that fire every 10ms are the powertrain’s heartbeat), payload structures, and the changes when the vehicle’s state changes (a door opening produces the body-domain frames). The DBC reverse engineering turns IDs into meaning: DBC files (the industry’s CAN database format) map IDs to signals for known vehicles, and for unknown ones the analyst’s method is differential — flip a physical state, diff the frames, isolate the IDs that changed, repeat (the same diff discipline as every layer of this handbook). UDS assessment probes the diagnostic layer: which ECUs answer 0x7xx requests, which services respond without security access (the read services usually do — fine), and what the 0x27 security-access algorithm is (seed-key schemes range from “seed is the key” to proper challenge-response — the weak ones are the finding, and the assessment reads the algorithm from the firmware H2 extracted). The gateway analysis is the segmentation test: can infotainment-originated frames reach chassis IDs? The gateway that forwards blindly is the architecture finding that the research lineage’s remote attacks rode. The telematics and infotainment surfaces are the remote entry paths: the head unit’s USB/WiFi/BT surface (H5/WI5 classes applied), the telematics unit’s cellular attack surface (H10’s cloud-API and SIM-class questions), and the chain from any of them to the CAN gateway. The safety architecture is non-negotiable: assessment transmission happens on bench rigs (ECU on the desk, actuators unloaded) or on private closed-course vehicles with documented authorization — the report distinguishes “bench demonstration” from “in-vehicle” and the tier gates enforce it.

TechniqueSurfaceWhat it doesToolingNotes
CAN frame censusAny segmentID population, ratescan-utils, SocketCANRecon layer
Differential state mappingBody/chassisFlip state, diff framesCustom + DBCRE the meanings
DBC database matchingKnown vehiclesInstant signal meaningsDBC files + toolingWhere available
Frame injection (bench)Owned ECUsActuate / test reactionscan-utils, customBench rig only
UDS service probeOBD/ECUWhich services answerisotc/caring caribou [verify]Diagnostic census
Security-access analysisUDS 0x27Assess seed-key strengthFirmware RE (H2/H3)Weak algo = finding
Gateway filtering testDomain gatewayCross-domain frame reachInject+observeSegmentation audit
Telematics API reviewCloud/appRemote service surfaceH10 methodsThe remote half
Infotainment surfaceHead unitUSB/BT/WiFi classesH5/WI5 methodsThe computer in the dash
Keyless-RF classificationSub-GHzFixed/rolling/authenticatedWI6 workflowWI6 results apply

cansniff.py — a CAN log analyzer for candump-format logs: builds the ID census (frequency, first/last seen), classifies diagnostic traffic (0x7xx UDS IDs), detects candidate periodic (heartbeat) IDs, supports the differential workflow (diff two logs — new/disappeared IDs between vehicle states), and emits the assessment report. stdlib only.

#!/usr/bin/env python3
"""cansniff.py — CAN log analyzer (candump format).
Usage:
candump -L can0 > drive.log && python cansniff.py drive.log
python cansniff.py base.log --diff test.log
candump -L line: (timestamp) interface ID#DATA
stdlib only.
"""
import argparse, json, re, sys
from collections import defaultdict
LINE_RE = re.compile(
r"\((?P<ts>[0-9.]+)\)\s+\S+\s+(?P<id>[0-9A-Fa-f]+)"
r"#(?P<data>[0-9A-Fa-f]*)")
def parse_log(path):
frames = []
try:
for line in open(path, encoding="utf-8",
errors="replace"):
m = LINE_RE.search(line)
if m:
data = m.group("data")
frames.append({
"ts": float(m.group("ts")),
"id": int(m.group("id"), 16),
"dlc": len(data) // 2,
"data": data.upper()})
except OSError as e:
sys.exit(f"read error: {e}")
return frames
def census(frames):
ids = defaultdict(lambda: {"count": 0, "first": None,
"last": None,
"dlcs": defaultdict(int)})
for f in frames:
rec = ids[f["id"]]
rec["count"] += 1
rec["first"] = rec["first"] or f["ts"]
rec["last"] = f["ts"]
rec["dlcs"][f["dlc"]] += 1
return ids
def periodic_ids(ids, duration, min_hz=5):
"""IDs firing fast enough to be heartbeats."""
out = []
if duration <= 0:
return out
for cid, rec in ids.items():
hz = rec["count"] / duration
if hz >= min_hz:
out.append((cid, round(hz, 1)))
return sorted(out, key=lambda kv: -kv[1])
def uds_traffic(ids):
"""Diagnostic-range IDs (0x7xx UDS conventions)."""
return {cid: rec["count"] for cid, rec in
ids.items() if 0x700 <= cid <= 0x7FF}
def diff_logs(base_ids, test_ids):
new = sorted(set(test_ids) - set(base_ids))
gone = sorted(set(base_ids) - set(test_ids))
rate_changed = []
for cid in set(base_ids) & set(test_ids):
b, t = base_ids[cid]["count"], test_ids[cid]["count"]
if b and t and (t / max(b, 1) > 3 or
b / max(t, 1) > 3):
rate_changed.append((cid, b, t))
return new, gone, rate_changed
def main():
ap = argparse.ArgumentParser()
ap.add_argument("log")
ap.add_argument("--diff", help="second log to diff")
ap.add_argument("--json")
args = ap.parse_args()
frames = parse_log(args.log)
if not frames:
sys.exit("no frames parsed")
duration = frames[-1]["ts"] - frames[0]["ts"]
ids = census(frames)
print(f"== CAN census: {len(frames)} frames, "
f"{len(ids)} IDs, {duration:.1f}s ==")
top = sorted(ids.items(),
key=lambda kv: -kv[1]["count"])[:12]
print("\ntop IDs by frequency:")
for cid, rec in top:
hz = rec["count"] / duration if duration else 0
print(f" 0x{cid:03X} n={rec['count']:<6} "
f"{hz:>6.1f}/s dlcs={dict(rec['dlcs'])}")
uds = uds_traffic(ids)
if uds:
print(f"\n[!] diagnostic-range (UDS 0x7xx) IDs: "
f"{len(uds)}")
for cid, n in sorted(uds.items())[:10]:
print(f" 0x{cid:03X} n={n}")
else:
print("\n[+] no diagnostic-range IDs in capture")
if args.diff:
frames2 = parse_log(args.diff)
ids2 = census(frames2)
new, gone, changed = diff_logs(ids, ids2)
print(f"\n== diff vs {args.diff} ==")
print(f"new IDs: "
f"{['0x%03X' % i for i in new[:12]]}")
print(f"gone IDs: "
f"{['0x%03X' % i for i in gone[:12]]}")
changed_fmt = [('0x%03X' % c, b, t)
for c, b, t in changed[:8]]
print(f"rate-changed: {changed_fmt}")
print("[*] flip one vehicle state per capture and "
"the new/rate-changed IDs map that state")
if args.json:
json.dump({"ids": {f"0x{c:03X}": {
"count": r["count"],
"dlcs": {str(k): v for k, v in
r["dlcs"].items()}}
for c, r in ids.items()},
"uds": {f"0x{c:03X}": n for c, n in
uds.items()}},
open(args.json, "w"), indent=2)
print(f"\n[+] {args.json}")
if __name__ == "__main__":
main()
  • parse_log() (lines 18-32) handles candump’s -L (log) format — the (ts) interface ID#DATA line — with a regex tolerant of both spaces and the two ID widths (11-bit 3-hex, 29-bit 8-hex).
  • The census (lines 35-44) builds per-ID statistics: count, first/last timestamps (for the duration math), and DLC distribution — the DLC histogram is a fingerprint (a diagnostic ID’s DLC varies with UDS payloads; a heartbeat’s DLC is fixed).
  • periodic_ids() (lines 47-53) flags the heartbeat class: IDs firing ≥5 Hz — the powertrain/chassis broadcast cadence, useful for distinguishing control traffic from event traffic.
  • The UDS filter (lines 56-59) classifies the 0x700-0x7FF range — the diagnostic conventions — and its presence in a capture tells the assessor a tester tool was active (or the vehicle self-diagnoses loudly).
  • The differential workflow (lines 62-84) implements the RE method: two logs (state A vs state B), diffed for new/gone IDs and rate-changed IDs (3× threshold — a door-open doubles a body-frame’s rate, not 3×… but a light-on can zero it, so both directions report). The closing instruction is the method: one state per capture, diff, repeat — the IDs map themselves.

The vehicle reaches the assessment through its surfaces, tiered exactly like every other estate: tier 0 is the passive capture (OBD logger or gateway-side log — the census needs no transmission), tier 1 is the bench rig (an ECU or full harness on the desk — injection and UDS probing against actuators that drive nothing mechanical), and tier 2 is the closed-course vehicle with documented authorization (the in-vehicle demonstration, never on public roads). The census reaches the assessment as the network map: which IDs exist, which are heartbeats, which are diagnostics, which changed when the state changed — the DBC-less vehicle’s meanings, built by diff. The UDS probe reaches the diagnostic layer’s findings: which ECUs answer, which security-access algorithms are weak (the seed-key read from the H2 firmware), and which services write without protection. The gateway test reaches the segmentation story: the infotainment-originated frame that appears on the chassis bus is the architecture finding that the research lineage’s remote chains rode — and the fix (gateway filtering, domain isolation) is the product team’s. The telematics and infotainment surfaces reach H10/H5/WI5’s methods: the cloud API, the head unit’s USB/WiFi/BT — the remote half of the vehicle chain. The keyless-RF classification reaches WI6’s three-tier output (fixed/rolling/authenticated). For the defender, the CAN-side detection reaches the gateway: rate and ID whitelisting (the guard pattern from WI6, ported), UDS security-access hardening, gateway logging of cross-domain attempts — the vehicle’s SIEM is its gateway, and the guard’s findings feed the same governance stack. The standing metrics: percentage of ECUs with hardened UDS access, gateway filter coverage, telemetry of CAN anomalies per vehicle, and the fleet’s keyless-RF posture — the vehicle program’s four numbers.

  1. Census — passive capture on bench harness: 214 IDs, 3 heartbeats, 4 UDS pairs visible
  2. Map — differential captures: door state → 0x3A2, lights → 0x41B/0x41C, speed → 0x0D1
  3. Probe — UDS census: 6 ECUs answer; 2 accept write services with 0x27 seed-key
  4. RE — H2/H3 firmware analysis of one ECU: 0x27 key algorithm is seed XOR constant
  5. Access — security access bypassed on bench ECU: diagnostic write authorized
  6. Inject — bench injection of calibrated body-domain frames: indicators actuate (rig only)
  7. Gateway — infotainment-origin chassis-ID test: gateway forwards unfiltered (architecture finding)
  8. Report — chain: weak UDS + unfiltered gateway = remote-class risk path; remediation: SecOC-class MACs, gateway filter, UDS hardening

canguard.py — the CAN gateway guard: monitors a candump stream against an ID whitelist with per-ID rate bounds — flags unexpected IDs (never-seen sources), rate anomalies (a body frame at powertrain rates), and UDS writes from non-diagnostic sources — the vehicle-gateway-side intrusion detection, wired like WI6’s replayguard into the SIEM pipeline. stdlib only.

  • Whitelist with learned baseline. The guard loads a baseline census (from cansniff’s JSON) — IDs seen in the clean capture are allowed within rate bounds; everything else is a finding — the learned-baseline pattern from every layer of this handbook.
  • Per-ID rate bounds. Rate anomalies are per-ID (a 10ms heartbeat jumping to 1ms is flooding; a heartbeat going silent is an ECU offline) — both directions alert, with the direction named.
  • UDS source discipline. Diagnostic IDs from non-diagnostic source addresses are the injection signature — the guard checks the request side’s source addresses against the diagnostic-tool allowlist.
  • Gateway-ready output. Guard events as JSON lines (SEV, kind, id, detail) — the SIEM pipeline shape, same as every guard in this series.
#!/usr/bin/env python3
"""canguard.py — CAN gateway intrusion guard (candump stream).
Usage:
candump -L can0 | python canguard.py --base base.json
base.json: cansniff.py --json output (learned baseline)
stdlib only.
"""
import argparse, json, re, sys, time
from collections import defaultdict
LINE_RE = re.compile(
r"\((?P<ts>[0-9.]+)\)\s+\S+\s+(?P<id>[0-9A-Fa-f]+)"
r"#(?P<data>[0-9A-Fa-f]*)")
UDS_RANGE = (0x700, 0x7FF)
DIAG_SOURCES = set() # extend from inventory
def load_base(path):
try:
b = json.load(open(path, encoding="utf-8"))
except (OSError, json.JSONDecodeError) as e:
sys.exit(f"baseline error: {e}")
ids = {}
for k, v in b.get("ids", {}).items():
ids[int(k, 16)] = v.get("count", 0)
return ids
def guard(st, cid, ts):
"""Check one frame ID against baseline state."""
findings = []
known = st["known"]
rate = st["rates"].setdefault(cid, [])
# new-ID finding
if cid not in known and cid not in st["learned"]:
findings.append(("NEWID", cid,
"ID not in baseline"))
st["learned"].add(cid)
# rate anomaly (per-ID, both directions)
rate.append(ts)
cutoff = ts - 1.0
while rate and rate[0] < cutoff:
rate.pop(0)
if cid in known:
base_hz = known[cid] / max(st.get(
"baseline_duration", 60), 1)
cur_hz = len(rate)
if base_hz >= 5 and cur_hz > base_hz * 3:
findings.append(("FLOOD", cid,
f"{cur_hz}/s vs baseline ~{base_hz:.0f}/s"))
# UDS source discipline
if UDS_RANGE[0] <= cid <= UDS_RANGE[1]:
# request side (odd-ish addressing heuristics
# vary per OEM; flag unallowlisted request IDs)
if cid not in st["diag_ids"]:
findings.append(("UDS-NEW", cid,
"unbaseline'd diagnostic ID"))
return findings
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--base", required=True)
ap.add_argument("--diag", help="csv of allowed diag IDs")
ap.add_argument("--out")
args = ap.parse_args()
known = load_base(args.base)
if args.diag:
with open(args.diag, encoding="utf-8") as fh:
DIAG_SOURCES.update(
int(x.strip(), 16) for x in fh
if x.strip())
st = {"known": known, "learned": set(),
"rates": defaultdict(list),
"diag_ids": DIAG_SOURCES or
{c for c in known
if 0x700 <= c <= 0x7FF},
"baseline_duration": 60}
n = 0
for line in sys.stdin:
m = LINE_RE.search(line)
if not m:
continue
n += 1
cid = int(m.group("id"), 16)
ts = float(m.group("ts"))
for sev, cid_, msg in guard(st, cid, ts):
print(json.dumps({"sev": sev,
"id": f"0x{cid_:03X}",
"msg": msg,
"ts": ts}))
print(f"[*] {n} frames guarded; "
f"{len(st['learned'])} learned new IDs",
file=sys.stderr)
if args.out:
json.dump({"learned": sorted(
f"0x{c:03X}" for c in st["learned"])},
open(args.out, "w"), indent=2)
if __name__ == "__main__":
main()
  • The baseline load (lines 21-27) converts cansniff’s census JSON into the guard’s known-ID set — the learned-baseline pattern, with the baseline duration carried for the rate math.
  • guard() (lines 30-53) runs the three checks per frame: NEWID (not in baseline, not yet learned — learned IDs suppress repeats so one new device doesn’t spam), FLOOD (per-ID rate vs baseline Hz, both a 3× surge and the silence case), and UDS-NEW (diagnostic-range IDs not in the diagnostic allowlist — the injection signature).
  • The learning behavior (line 38) is a design statement: new IDs are learned into the session’s suppression set after first report — the guard reports once and keeps watching, which is the operator-friendly shape for a live gateway.
  • The output (lines 56-58) is JSON-line events (sev/id/msg/ts) — the SIEM pipeline shape, the same contract as every guard in the series.

baseline capture (vehicle at rest, bench harness)

Section titled “baseline capture (vehicle at rest, bench harness)”

$ candump -L can0 > base.log $ python cansniff.py base.log —json base.json == CAN census: 18421 frames, 187 IDs, 60.0s == top IDs by frequency: 0x0D1 n=1204 20.1/s dlcs={8: 1204} … [!] diagnostic-range (UDS 0x7xx) IDs: 4

drive-log diff: flip the headlights during capture 2

Section titled “drive-log diff: flip the headlights during capture 2”

$ candump -L can0 > drive2.log && python cansniff.py base.log —diff drive2.log new IDs: [‘0x41B’, ‘0x41C’] [*] flip one vehicle state per capture and the new/rate-changed IDs map that state

$ candump -L can0 | python canguard.py —base base.json —out learned.json {“sev”: “NEWID”, “id”: “0x5C1”, “msg”: “ID not in baseline”, “ts”: 1710.2} {“sev”: “UDS-NEW”, “id”: “0x7E3”, “msg”: “unbaseline’d diagnostic ID”, “ts”: 1712.8} [*] 9412 frames guarded; 2 learned new IDs

Vehicle detection concentrates at the gateway (the domain firewall — its guard rules, its cross-domain logging) and the telematics backend (the cloud-side anomaly view of a fleet — H10’s telemetry). The UDS layer’s detection is the diagnostic-session audit (who requested security access, from which source — the PACS-log lesson applied to vehicles). The CAN bus itself offers no native telemetry, which is why the guard pattern (learned baseline + rate bounds + source discipline) is the deployable control, running on the gateway where frames are already bits. Your own tooling hygiene: passive capture anywhere authorized; injection and UDS writes are bench-gated (owned ECUs, unloaded actuators) — the safety architecture is the section of the runbook that gets read aloud. AUTOSAR SecOC-class MAC protection [verify adoption per OEM] is the architectural fix for CAN’s authentication absence — the procurement-level conversation the assessment’s findings feed. There is no Wazuh/SIEM native layer for CAN; the gateway’s JSON-line guard events bridge into the fleet’s SIEM [verify per OEM telematics integration]. The standing metrics: gateway filter coverage per domain pair, UDS-hardened ECU percentage, guard findings per vehicle-month (trended fleet-wide), and keyless-RF posture per model — the vehicle program’s dashboard, fed by the same baseline discipline as every other layer.

  1. DBC integration: parse DBC files for known vehicles and emit signal-level findings (which signal changed, not which ID).
  2. SecOC/MAC verification: parse MAC-protected frames where present and verify freshness counters — the architectural control’s validation.
  3. UDS seed-key analyzer: capture 0x27 exchanges and test algorithm strength (seed XOR const, keystream reuse) against H2-recovered firmware constants.
  4. Fleet aggregation: multi-vehicle guard-event correlation (the same NEWID across the fleet = campaign or fleet update — the M15 lesson, vehicle edition).
  5. OBD-II dongle posture: audit what third-party dongles can reach (the insurance-tracker surface) — the estate’s extended fleet view.
SignalSourceIDNotes
New CAN IDs (post-baseline)canguardNEWID eventsInjection/new-ECU signal
Per-ID rate anomaliescanguardFLOOD eventsFlooding/ECU-offline
Unbaseline’d UDS IDscanguardUDS-NEWDiagnostic injection
Gateway cross-domain framesGateway filter logsarchitecture auditSegmentation gaps
UDS security-access requestsUDS audit logsper-ECUKey-extraction attempts
Weak 0x27 algorithmsFirmware RE (H2/H3)design findingSeed-key class
Telematics API anomaliesFleet backendH10 telemetryRemote-surface view
Keyless-RF posture per modelWI6 censusfleet classificationFixed-code fleet list
Infotainment surface findingsH5/WI5 assessmentsper modelDash-computer surface
Fleet SIEM aggregationTelematics/SIEM[verify per OEM]The vehicle join

Vehicle-layer evasion targets the gateway’s limited view and the fleet’s telemetry gaps. ID mimicry: injection frames using baseline IDs with plausible payloads defeat NEWID detection — countered by content-level checks (payload plausibility per ID, checksum coverage where OEMs implement it) and by theSecOC-class MACs that make forgery fail cryptographically. Rate camouflage: injection paced at baseline rates blends into FLOOD’s absence — countered by content correlation (a light-command ID changing at steering rates is a semantic mismatch the rate check misses) and by MAC freshness where deployed. Gateway hop choice: attackers who enter on the least-monitored domain (body CAN’s comfort frames draw less scrutiny than powertrain) — countered by uniform guard coverage across domain pairs and by the gateway logging which domain pairs carry what. Telematics-path silence: attacks that ride the fleet’s trusted backend (legitimate API, stolen fleet credentials) generate no CAN anomalies until the actuation — countered by backend-side behavioral analytics (H10’s layer) and by the correlation between cloud commands and CAN activity. ECU diversity exploitation: the fleet’s heterogeneous ECUs mean one guard configuration misses the models whose IDs the baseline never saw — countered by per-model baselines and the fleet-aggregation correlation (improvement #4). The meta-point mirrors the whole handbook: the vehicle’s CAN is a broadcast medium with no origin truth, and every durable control is either cryptographic (SecOC-class MACs — the architectural fix) or statistical (baselines, rates, correlations — the guard’s fix); the assessment’s job is measuring which of the two your fleet has.

⚠️ What caught me: the first vehicle CAN demo I built “worked” — I injected the calibrated body-domain frames on my bench rig and the indicators flashed. I wrote it up as a safety-relevant chain, and the OEM engineer’s review asked one question: “which gateway firmware version?” Mine was three revisions old, and the current gateway filtered cross-domain frames — the chain I’d demonstrated had been fixed upstream, and my finding was real for a fleet that no longer existed. Since then, every vehicle assessment records the exact firmware/PN of every ECU on the bench (photographed, in the manifest), and the report’s chains are annotated with “fixed in version X per vendor advisory” checks — because a vehicle finding without its firmware version is an anecdote, and the fleet’s patch state is part of the finding.

  1. Census the bench: capture 60 seconds of your rig’s (or dev-kit CAN bus) traffic, run cansniff.py, and identify heartbeats, event IDs, and any diagnostic pairs — the network map, on a bus you own.
  2. Differential mapping: flip one state per capture (a switch, a sensor) and diff with --diff; build the ID→state map for three states — the DBC-less RE method, practiced.
  3. Guard deployment: feed the baseline into canguard.py and replay a capture with an injected foreign ID; confirm NEWID and UDS-NEW fire — the gateway control, validated against known injections.
  4. UDS census (bench ECU): probe which diagnostic services answer on your bench ECU (read-only services), and document the security-access shape from the firmware (H2’s extract) — the diagnostic layer, assessed.
  5. Gateway filter test: if your rig has a gateway (or you emulate one), test whether a body-domain frame reaches the powertrain side; document the filter’s behavior — the segmentation finding, on your own hardware.
  • Technical: Explain CAN’s arbitration and why the frame format provides no origin authentication — and what SecOC-class MACs add.
  • Technical: How does the differential state-mapping workflow reverse-engineer an undocumented CAN bus, and what capture discipline makes it work?
  • Scenario: Your guard flags UDS-NEW on a vehicle mid-drive. What is the triage order, and what distinguishes a tester tool from an attacker?
  • Conceptual: Why is the domain gateway the vehicle’s real firewall, and what does an unfiltered gateway mean for the remote-attack chain?
  • Conceptual: How do the telematics/infotainment surfaces change the vehicle threat model compared to OBD-only access?
  • Deep-dive: Design the fleet vehicle-security program: bench assessment tiers, guard deployment, UDS hardening, fleet aggregation, and the OEM advisory join.
  • Practical: The assessment shows unfiltered gateway + weak UDS on the bench rig. What is the risk narrative to the OEM, and what does the remediation roadmap look like?

WI6 (RF & SDR Fundamentals) · H2 (Firmware Extraction & Analysis) · H3 (Firmware Analysis & Emulation) · H10 (Supply Chain & Update Security) · F1 (Networking Fundamentals)