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.
- 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.
- 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, sojavahas to be on yourPATHat 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.
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.shThere's no build step. The EXI codec spins up a JVM the first time it's used.
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 eth0Flags:
| 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.
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
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/.
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
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
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 msgFor 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.
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.
