Skip to content

CAN Bus Module

Automotive CAN bus offensive module built on the MCP2515 SPI controller. Supports passive sniffing, frame injection, ISO-TP transport, UDS diagnostics, OBD-II decoding, fuzzing, and replay.

Authorization Required

CAN bus interaction with vehicles must be performed only on owned hardware or with explicit written authorization. Unauthorized access to vehicle networks is illegal. Never fuzz on a live vehicle — use isolated test benches only.

Configuration

Enable in idf.py menuconfig → Espilon Bot Configuration → Modules → CAN Bus Module (MCP2515)

Config flag: CONFIG_MODULE_CANBUS


Overview

The CAN Bus module provides a full automotive attack stack from low-level SPI communication to application-layer diagnostics. Each layer is independently configurable via Kconfig.

┌──────────────────────────────────────────────────────┐
│  cmd_canbus.c         — C2 command handlers (27 cmds)│
│    ↕                                                 │
│  canbus_uds.c         — UDS (ISO 14229) diagnostics  │
│  canbus_obd.c         — OBD-II PID decoder           │
│  canbus_fuzz.c        — Fuzzing engine               │
│    ↕                                                 │
│  canbus_isotp.c       — ISO-TP (ISO 15765-2)        │
│    ↕                                                 │
│  canbus_driver.c      — MCP2515 SPI driver + RX task │
│    ↕                                                 │
│  canbus_config.c      — NVS persistence              │
│    ↕                                                 │
│  ESP-IDF SPI Master   — Hardware SPI bus             │
└──────────────────────────────────────────────────────┘

Hardware Requirements

Component Role Cost
MCP2515 module CAN 2.0B controller + TJA1050 transceiver ~3 EUR
ESP32 Main MCU (any variant with SPI) ~5 EUR

Most MCP2515 modules include the TJA1050 CAN transceiver. Check the oscillator crystal (8 MHz or 16 MHz — must match Kconfig CANBUS_OSC_MHZ).

Wiring

Default GPIO mapping (configurable in menuconfig):

MCP2515 Module       ESP32 (VSPI)
──────────────       ────────────
VCC            →     3.3V
GND            →     GND
CS             →     GPIO 5
MOSI (SI)      →     GPIO 23
MISO (SO)      →     GPIO 19
SCK            →     GPIO 18
INT            →     GPIO 4  (active low)

Connect CAN_H and CAN_L to the target bus. For OBD-II: pin 6 (CAN_H) and pin 14 (CAN_L).


Configuration

Kconfig Options

Option Default Description
CONFIG_MODULE_CANBUS n Enable the CAN bus module
CANBUS_SPI_HOST 3 (VSPI) SPI host: 2=HSPI, 3=VSPI
CANBUS_PIN_MOSI 23 SPI MOSI GPIO
CANBUS_PIN_MISO 19 SPI MISO GPIO
CANBUS_PIN_SCK 18 SPI SCK GPIO
CANBUS_PIN_CS 5 SPI Chip Select GPIO
CANBUS_PIN_INT 4 MCP2515 interrupt GPIO
CANBUS_OSC_MHZ 8 Oscillator frequency (8 or 16 MHz)
CANBUS_DEFAULT_BITRATE 500000 Default bus speed (bps)
CANBUS_SPI_CLOCK_HZ 10000000 SPI clock (max 10 MHz)
CANBUS_RECORD_BUFFER 512 Frame ring buffer (64-2048)
CANBUS_ISO_TP y ISO-TP transport layer
CANBUS_UDS y UDS diagnostic services (requires ISO-TP)
CANBUS_OBD y OBD-II PID decoder (requires ISO-TP)
CANBUS_FUZZ y CAN fuzzing engine

Supported Bitrates

Bitrate Use Case 8 MHz 16 MHz
1 Mbps High-speed CAN - Yes
500 kbps Standard automotive Yes Yes
250 kbps J1939 (trucks) Yes Yes
125 kbps Low-speed CAN Yes Yes
100 kbps Diagnostic Yes Yes

Core Commands

can_start

Initialize the MCP2515 and start the CAN bus.

Syntax:

c2:> send <device_id> can_start [bitrate] [mode]

Parameters:

Parameter Type Required Description
bitrate int No Bus speed in bps (default: 500000)
mode string No normal (default), listen, loopback

Modes:

  • normal: Full TX/RX participation on bus
  • listen: RX only, no ACK sent (stealth sniff)
  • loopback: Self-test mode, TX frames loop back to RX

can_stop

Stop the CAN bus and put MCP2515 in config mode.

c2:> send <device_id> can_stop

can_send

Send a single CAN frame.

c2:> send <device_id> can_send <id_hex> <data_hex>

Example:

c2:> send esp001 can_send 0x7DF 0201000000000000

can_filter_add / can_filter_del / can_filter_list / can_filter_clear

Manage software filters. When filters are active, only matching CAN IDs are processed.

c2:> send <device_id> can_filter_add <id_hex>
c2:> send <device_id> can_filter_del <id_hex>
c2:> send <device_id> can_filter_list
c2:> send <device_id> can_filter_clear

can_status

Show bus state, configuration, and error counters.

c2:> send <device_id> can_status

Response:

state=running mode=normal bitrate=500000 osc=8MHz
rx=1234 tx=56 rx_err=0 tx_err=0 bus_err=0 overflow=0
filters: 0x7E0 0x7E8

can_sniff

Stream CAN frames to C2 for a specified duration. Async.

c2:> send <device_id> can_sniff [duration_s]

Default duration: 10 seconds. Frames arrive in format: CAN|<ts>|<id>|<dlc>|<data>.


can_record / can_dump / can_replay

Record frames to local ring buffer, dump to C2, or replay on bus.

c2:> send <device_id> can_record [duration_s]    # Record to buffer (async)
c2:> send <device_id> can_dump                    # Send buffer to C2 (async)
c2:> send <device_id> can_replay [speed_pct]      # Replay: 100=realtime, 0=max (async)

UDS Diagnostic Commands

Requires CONFIG_CANBUS_UDS=y

can_scan_ecu

Discover ECUs on the bus by sending TesterPresent to standard ID ranges. Async.

c2:> send <device_id> can_scan_ecu

Scans 0x7E0-0x7E7 and 0x700-0x7DF. Reports discovered ECUs as ECU|<tx_id>|<rx_id>.


can_uds

Send a raw UDS request. Async.

c2:> send <device_id> can_uds <tx_id> <service_hex> [data_hex]

Example:

# TesterPresent to ECU 0x7E0
c2:> send esp001 can_uds 0x7E0 3E 00

can_uds_session

Open a UDS diagnostic session.

c2:> send <device_id> can_uds_session <tx_id> <type>
Type Session
1 Default
2 Programming
3 Extended diagnostic

can_uds_read

Read a Data Identifier (DID) from an ECU. Async.

c2:> send <device_id> can_uds_read <tx_id> <did_hex>

Common DIDs: F190 (VIN), F191 (hardware version), F187 (part number).


can_uds_dump

Read memory from an ECU by address. Streams data to C2. Async.

c2:> send <device_id> can_uds_dump <tx_id> <addr_hex> <size>

can_uds_auth

Request SecurityAccess seed from an ECU. Async.

c2:> send <device_id> can_uds_auth <tx_id> [level]

Default level: 1. Reports the seed bytes for offline key computation.


OBD-II Commands

Requires CONFIG_CANBUS_OBD=y

can_obd

Query a single OBD-II PID and return the decoded value. Async.

c2:> send <device_id> can_obd <pid_hex>

Example:

c2:> send esp001 can_obd 0C
# Response: Engine RPM = 2450.25 rpm

c2:> send esp001 can_obd 0D
# Response: Vehicle Speed = 60 km/h

Common PIDs: 05 (coolant temp), 0C (RPM), 0D (speed), 11 (throttle), 2F (fuel level).


can_obd_vin

Read the Vehicle Identification Number (Mode 09, PID 02). Async.

c2:> send <device_id> can_obd_vin

can_obd_dtc

Read Diagnostic Trouble Codes (Mode 03). Async.

c2:> send <device_id> can_obd_dtc

Reports DTC codes like P0300, C0035, B0100, U0001.


can_obd_supported

List which PIDs the vehicle supports. Async.

c2:> send <device_id> can_obd_supported

can_obd_monitor / can_obd_monitor_stop

Continuously stream PID values to C2. Async.

c2:> send <device_id> can_obd_monitor <pid_hex,...> [interval_ms]
c2:> send <device_id> can_obd_monitor_stop

Example:

# Stream RPM and speed every 500ms
c2:> send esp001 can_obd_monitor 0C,0D 500

Fuzzing Commands

Requires CONFIG_CANBUS_FUZZ=y

Warning

Never fuzz on a live vehicle or production CAN bus. Use isolated test benches only. Fuzzing can cause unpredictable behavior including loss of vehicle control.

can_fuzz_id

Iterate through CAN IDs with a fixed payload. Async.

c2:> send <device_id> can_fuzz_id [start_hex] [end_hex] [delay_ms]

Default: 0x000 to 0x7FF, 10ms delay.


can_fuzz_data

Mutate data bytes for a fixed CAN ID. Async.

c2:> send <device_id> can_fuzz_data <id_hex> [seed_hex] [delay_ms]

can_fuzz_random

Send random CAN IDs with random data. Async.

c2:> send <device_id> can_fuzz_random [delay_ms] [count]

can_fuzz_stop

Stop any active fuzzing operation.

c2:> send <device_id> can_fuzz_stop

Frame Format

Frames streamed to C2 use the format:

CAN|<timestamp_ms>|<id_hex>|<dlc>|<data_hex>

Example: CAN|1708000123456|0x123|8|DEADBEEF01020304

Special Markers

Marker Meaning
SNIFF_END End of sniff session
DUMP_START\|<count> Beginning of frame dump
DUMP_END End of frame dump
UDS_RSP\|<rx_id>\|<hex> UDS response
ECU\|<tx_id>\|<rx_id> Discovered ECU

C3PO Integration

REST API

CAN frames are automatically stored server-side (ring buffer, 10,000 max).

Endpoint Method Description
/api/can/frames GET List frames (params: device_id, can_id, limit, offset)
/api/can/stats GET Frame stats (params: device_id)
/api/can/frames/export GET Download CSV (params: device_id)

TUI Commands

can stats [device_id]           # Frame count, unique CAN IDs
can frames [device_id] [limit]  # Display last N frames
can clear                       # Clear frame store

Usage Examples

Basic Sniffing

c2:> send esp001 can_start 500000 listen     # Stealth mode
c2:> send esp001 can_sniff 30                # Stream 30 seconds
c2:> send esp001 can_stop

Record and Replay

c2:> send esp001 can_start 500000 listen
c2:> send esp001 can_record 60               # Record 60 seconds
c2:> send esp001 can_stop

c2:> send esp001 can_start 500000 normal     # Switch to TX mode
c2:> send esp001 can_replay 100              # Replay at real-time speed

Vehicle Diagnostics

c2:> send esp001 can_start 500000
c2:> send esp001 can_obd_supported           # What does the car support?
c2:> send esp001 can_obd 0C                  # Engine RPM
c2:> send esp001 can_obd 0D                  # Speed
c2:> send esp001 can_obd_vin                 # VIN number
c2:> send esp001 can_obd_dtc                 # Trouble codes
c2:> send esp001 can_obd_monitor 0C,0D 500   # Stream RPM + speed

ECU Exploration

c2:> send esp001 can_start 500000
c2:> send esp001 can_scan_ecu                         # Find ECUs
c2:> send esp001 can_uds_session 0x7E0 3              # Extended session
c2:> send esp001 can_uds_read 0x7E0 F190              # Read VIN via DID
c2:> send esp001 can_uds_auth 0x7E0 1                 # Security seed
c2:> send esp001 can_uds_dump 0x7E0 0x00000000 4096   # Dump 4KB

Troubleshooting

MCP2515 not detected

  • Verify wiring (CS, MOSI, MISO, SCK)
  • Check CANBUS_OSC_MHZ matches crystal (8 vs 16 MHz)
  • Try loopback: can_start 500000 loopback — if it works, bus wiring is the issue

No frames received

  • Confirm bus speed matches target (500k for cars, 250k for trucks)
  • Use listen mode: can_start 500000 listen
  • Check CAN_H / CAN_L connections and 120 ohm termination
  • can_status — high RX errors indicate speed mismatch

Bus-off state

  • TEC exceeded 255 — MCP2515 disconnected from bus
  • can_stop then can_start to reset
  • Fix wiring or speed mismatch

RX overflow

  • Bus traffic exceeds processing speed
  • Add filters: can_filter_add <id>
  • Increase CANBUS_RECORD_BUFFER in menuconfig

Previous: Honeypot | Next: OTA Updates