Skip to content

Latest commit

 

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ChargeSploit

ChargeSploit is a security testing tool for the V2G communication stack used in EV charging. It implements ISO 15118-2 and DIN SPEC 70121 at both EVCC and SECC side, so you can watch every message on the wire and swap in your own behaviour at any step of the communication.

demo

What it does

  • Simulate the EVSE. Full session from SDP discovery through shutdown, every step subclassable. Running as EVCC is also supported.
  • Build test cases by subclassing. Override one step, the rest of the session runs unchanged. New handler = one file + one registry line (see Extending).
  • Answer SDP discovery. Responds to SECC Discovery Protocol multicast and announces the V2G endpoint.
  • TLS Support Wraps the V2G channel in TLS with your own certificate set.

Requirements

  • Python 3.9 or newer.
  • A Java runtime, JRE or JDK 11+. The EXI codec that encodes and decodes V2G messages is a Java library (lib/EXICodec.jar) bridged into Python over py4j, so java has to be on your PATH at runtime.
  • py4j, which is vendored under lib/python/ and put on the path automatically. Nothing to install.
  • Root, in most cases. V2G runs on IPv6 link-local on a given interface and binds low ports, so you normally start the tool with sudo.
  • An interface with an IPv6 link-local address. CCS uses fe80::/10.

Setup

git clone https://github.com/konicst1/ChargeSploit.git
cd ChargeSploit

# 1. Check that Java is there
java -version

# 2. Generate the TLS certificate (only needed if you use --tls)
./scripts/generate_certs.sh

There's no build step. The EXI codec spins up a JVM the first time it's used.

Usage

The entry point is main.py. Pick a mode (--mode), a handler (--handler), and the interface to run on (-i).

# Charging station, standards-compliant DIN, DC only, on eth0
sudo python3 main.py --mode evse --handler normal_din --dc -i eth0

# Charging station, ISO 15118-2, offering AC and DC (the default)
sudo python3 main.py --mode evse --handler normal_iso -i eth0

# Same thing, over TLS
sudo python3 main.py --mode evse --handler normal_iso --tls -i eth0

Flags:

Flag Meaning
--mode {evse,evcc} Run as the charging station or the vehicle. Default evse.
--handler NAME Which behaviour to run. Default normal_iso.
-i, --interface Network interface for V2G. Default eth0.
--ac / --dc / --acdc Energy transfer offered. Default --acdc.
--sdp-mode NAME How SDP discovery gets answered. Default normal.
--tls Wrap the V2G channel in TLS.
--debug / --log NAME Verbose logging / write a log file under logs/.

Whatever --handler and --sdp-mode values your build accepts come straight from src/registry.py. Run main.py --help for the live list.

Architecture

The tool is built in layers, and each layer only talks to the one below it. That split is the point: you can change what the tool says (a handler) without touching how it says it (transport, EXI, framing).

graph TD
    CLI["main.py (CLI)"] --> REG["registry.py"]
    CLI --> CTRL["Controller (EVSE / EVCC)"]

    CTRL --> H["Handler — what to answer at each step"]
    CTRL --> SDP["SDP mode — how to answer discovery"]

    CTRL --> TCP["TCP / TLS (V2G session)"]
    SDP --> UDP["UDP (SDP discovery)"]

    TCP --> V2GTP["V2GTP framing"]
    UDP --> V2GTP
    V2GTP --> EXI["EXI codec"]
    EXI -->|py4j| JAR["EXICodec.jar (JVM)"]

    H --> TPL["JSON templates + enums"]
    H --> V2GTP
Loading

main.py resolves the handler and SDP mode from registry.py and builds the controller. The controller owns the sockets and the session loop. The handler is a routing table (_dispatch) mapping message types to methods; the base class does decode, dispatch, and encode — a concrete handler just fills in responses. Transport is UDP for SDP discovery and TCP/TLS for the V2G session. V2GTP frames each message; the EXI codec bridges to Java over py4j. Responses are built from JSON templates in src/messages/.

Handler classes

Every concrete handler inherits from an abstract base that owns the plumbing. On the EVSE side the two standards-compliant handlers, NormalDINHandler and NormalISOHandler, sit directly under MessageHandler. Anything more specific subclasses one of those two and overrides only the step it changes. TeslaHandler is the shipped example: NormalDINHandler with a single step overridden.

classDiagram
    class MessageHandler {
        <<abstract>>
    }
    MessageHandler <|-- NormalDINHandler
    MessageHandler <|-- NormalISOHandler
    NormalDINHandler <|-- TeslaHandler
Loading

SDP modes

Discovery follows the same pattern as handlers. An SDP mode is one class under SDPMode that decides how a SECC discovery request is answered; NormalMode is the shipped baseline, and a new mode is the same subclass-and-register step.

classDiagram
    class SDPMode {
        <<abstract>>
    }
    SDPMode <|-- NormalMode
    SDPMode <|-- EvilMode
Loading

Extending

Adding behaviour is two steps: write the class, then register it. A handler is a routing table, message type to the method that answers it, plus those methods. The base class handles decode, dispatch, and encode, so your subclass only declares the table and fills in the responses.

class MyHandler(MessageHandler):
    SUPPORTED_PROTOCOLS = [Namespace.ISO_15118_2_MSG_DEF]

    def __init__(self, charging_mode="ACDC"):
        super().__init__(charging_mode)
        self.templates = TemplateLoader(ISO_TEMPLATE_DIR)
        # routing table: message type -> the method that answers it
        self._dispatch = {
            ISOV2GMessage.SESSION_SETUP_REQ: self._handle_session_setup,
            # ...one line per message you answer
        }

    def get_handler_name(self):
        return "MyHandler"

    async def _handle_session_setup(self, payload, session):
        req = self._get_body(payload, ISOV2GMessage.SESSION_SETUP_REQ)
        msg = self.templates.load(ISOTemplate.SESSION_SETUP_RES)
        # ...fill in the response...
        return msg

For a variant of something that already exists, subclass the concrete handler and override only the method that changes. See tesla_handler.py, which is NormalDINHandler with one step swapped out.

The full walk-through (handlers, SDP modes, and message templates) is in docs/EXTENDING.md.

A note on responsible use

ChargeSploit talks to real charging hardware and vehicle battery management systems. The standards-compliant handlers are fine to point at equipment you own. If you extend it to emit abnormal traffic, such as out-of-range voltages and currents or illegal state transitions, run that on a low-voltage bench only, against hardware you're authorized to test, with the DC source confirmed low-voltage first.

About

EV Charging security testing tool

Topics

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages