By RUGERO Tesla (@404Saint).
A few weeks back, I wrote an article about how easy it is to take control of an unauthenticated PLC if you have network access. But pointing a script at a port and watching a variable change is only step one. It doesn’t tell you how the communication actually functions when it hits the wire.
If you read standard OT security textbooks, they give you high-level theoretical overviews. If you look at vendor documentation, they hide everything inside closed-source graphical interfaces.
I got tired of the abstraction. I wanted to see the raw bytes.
So, I built a lab environment called raw-industrial-protocols using nothing but lightweight Python socket twins running over a local Linux loopback interface. No heavy simulators. No proprietary vendor suites. Just standard network sockets injecting raw hex streams directly onto the wire to mimic, capture, and dissect the exact binary handshakes used by the critical infrastructure powering our world.
This is Layer Zero. And it's the trailer for a massive new standalone series where we will deeply study discovery, exploitation, defensive analysis, and protocol hardening for every major industrial suite.
Here is what happens when you drop beneath the software layer and look at the raw bytes.
The Industrial Wire Protocol Matrix
Before diving into the payloads, we have to look at how these stacks align. Most IT engineers assume industrial traffic is completely alien. In reality, it's just structured payloads riding on top of standard OSI transport models:
| Protocol | Default Port | Transport Layer | Framing Envelope | Baseline Security |
|---|---|---|---|---|
| Modbus/TCP | 502 |
TCP | MBAP Header (7 Bytes) | Explicitly None |
| DNP3 | 20000 |
TCP / UDP | Custom EPA Link/Transport Layer | Cleartext / Optional Auth |
| EtherNet/IP | 44818 |
TCP / UDP | Encapsulation Header (24 Bytes) | Cleartext Session Tracking |
| S7Comm | 102 |
TCP | TPKT (RFC 1006) $\rightarrow$ COTP (ISO 8073) | Cleartext by Design |
| OPC UA | 4840 |
TCP | Native Binary Layer | Configurable (None to Sign/Encrypt) |
Phase 1: Modbus/TCP — The Defacto Standard
Modbus/TCP is the grandfather of industrial automation. Engineered in the late 70s for serial links and later wrapped in a TCP envelope, it strips away old serial CRC checksums and introduces a 7-byte MBAP (Modbus Application Protocol) header.
When a client queries a PLC to read 10 holding registers starting at memory offset 0, it pushes this exact 12-byte payload into the raw TCP stream:
00 01 00 00 00 06 01 03 00 00 00 0A
-
00 01(Transaction ID): Sequential identifier to pair requests and responses. -
00 00(Protocol ID): Fixed zero-value reserved strictly for Modbus/TCP. -
00 06(Length): A 16-bit integer confirming 6 bytes follow. -
01(Unit ID): Routing drop index to cross hardware gateways. -
03(Function Code): The explicit instruction mapping toRead Holding Registers. -
00 00(Starting Address): Memory offset register zero. -
00 0A(Quantity): Requests exactly 10 contiguous registers.
Phase 2: DNP3 — The Grid Anchor
The Distributed Network Protocol (DNP3) runs our electrical grids and water systems. Because it was designed to maintain reliable telemetry over unstable radio links, it implements a highly complex, rugged EPA (Enhanced Performance Architecture) stack. This structure maps out an explicit custom Data Link and pseudo-Transport layer directly over standard TCP packets.
A primary master node checking the basic Link Status of an outstation drops this precise 10-byte structure onto the wire:
05 64 05 C9 00 00 01 00 4D 50
-
05 64(Start Bytes): Fixed magic hex header marking the start of a valid DNP3 frame. -
05(Length): Identifies that 5 user data bytes follow (excluding the trailing CRC block). -
C9(Control Byte): Bitmask declaring frame parameters;0xC9triggers a primary link status request. -
00 00/01 00(Addresses): 16-bit Little-Endian addresses mapping the Destination RTU ($0$) and Source Master ($1$). -
4D 50(Data Link CRC): Crucial algebraic checksum validating the header before processing continues.
Phase 3: EtherNet/IP & CIP — The Session Hijack Surface
EtherNet/IP takes the Common Industrial Protocol (CIP) and adapts it for modern industrial enterprise routing. To handle communication safely, it uses a fixed 24-byte Encapsulation Header that sits on top of all commands to manage sessions.
Default implementations pass tracking numbers and session handles in completely unauthenticated cleartext. If you are on-path, you can sniff these registration handles out of the air and hijack the connection entirely.
Before any industrial tag parsing can occur, the engineering client must run a Register Session routine, sending a 28-byte layout:
65 00 04 00 00 00 00 00 00 00 00 00 41 41 41 41 41 41 41 41 00 00 00 00 01 00 00 00
-
65 00(Command ID): Maps explicitly to theRegister Sessioninstruction primitive. -
04 00(Length): Indicates 4 trailing data bytes follow the header layout. -
00 00 00 00(Session Handle): Empty tracking block. The physical controller generates this unique token and populates it in the response. -
41 41 41 41 41 41 41 41(Sender Context): Fixed 8-byte diagnostic tracking phrase that the receiving PLC must echo back completely unchanged.
Phase 4: S7Comm — Proprietary Transport Nesting
S7Comm is the core protocol powering Siemens S7-300 and S7-400 programmatic lines. It communicates through a nested ISO transport layout to enforce safety boundaries. It doesn't write directly to raw TCP. Instead, it embeds your data inside a TPKT (RFC 1006) frame acting as a packetizer, which then wraps around a COTP (ISO 8073) connection management block.
The absolute baseline initialization frame requests a COTP Connection Request (CR). The 22-byte string looks like this:
03 00 00 16 11 E0 00 00 00 01 00 C0 01 0A C1 02 01 00 C2 02 02 00
-
03 00 00 16(TPKT Header): Preloads Version 3 and maps a total upcoming envelope volume of 22 bytes ($0x0016 = 22$). -
11(COTP Length): Confirms 17 parameter description bytes follow. -
E0(PDU Type): Declares this frame as an explicit Connection Request. -
C2 02 02 00(Destination TSAP): The crucial structural target variable mapping the targeted PLC rack and slot placement configuration directly.
Phase 5: OPC UA — The Plaintext Backdoor
OPC UA (Open Platform Communications Unified Architecture) is the modern IT/OT cryptographic bridge. It discards legacy layouts, register boundaries, and old Windows-bound DCOM dependencies to offer an advanced, object-oriented information model. It brings robust security concepts into industrial environments, including X.509 certificate exchanges and strict cryptographic signing algorithms.
But here is the field reality: During commissioning, active troubleshooting, or in older brownfield updates, engineers often configure the security policy parameter profile to None.
When this happens, the entire secure object-oriented stream falls back to completely unencrypted plaintext, exposing raw session tokens and node structural definitions to anyone sniffing the wire.
Before any secure channel configuration occurs, the client fires a native binary Hello (HEL) string spanning a standard 32-byte layout:
48 45 4C 46 20 00 00 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 00 00
-
48 45 4C(Message Type): ASCII text indicators translating toHEL. -
46(Chunk Type): ASCII characterF(Final chunk), signaling a self-contained transmission window. -
20 00 00 00(Message Size): Little-Endian unsigned integer establishing an explicit 32-byte frame layout boundary ($0x20 = 32$). -
00 00 01 00(Buffer Sizes): Standard 32-bit little-endian capacity properties negotiating buffer windows up to $65,536\text{ bytes}$.
The Troubleshooting Hell: The Alignment Check Mismatch
When you hand-craft low-level injection scripts using raw hex strings, you don't get the luxury of automated libraries handling data formatting for you. You hit the real friction points.
During development, I spent hours staring at tshark capturing frames completely cleanly, but refusing to render the application-layer dissection tree for OPC UA. It would drop the protocol mapping entirely and fall back to displaying a generic, raw TCP Data payload block.
The Lesson:
Network protocol analysis engines use strict validation rules. In my initial script configuration, I manually declared an internal size field block of 32 bytes (\x20\x00\x00\x00), but physically only appended 28 bytes of parameters to the raw socket array.
The engine's protocol parser calculates lengths rigidly. When it detected an alignment mismatch between the stated header length and the actual wire byte count, it flagged the packet as malformed and dropped the deep application tree to preserve data integrity.
Knowing how to troubleshoot alignment errors down to the physical byte level is a critical skill for deep protocol fuzzing and industrial vulnerability research.
What's Next?
This laboratory establishes the absolute foundation of our protocol analysis roadmap. Now that we can communicate with these stacks natively at Layer Zero without relying on heavy vendor software wrappers, we can start moving higher up the chain.
If you want the full step-by-step setup guides, the functional Python server/client twins, and the complete low-level reference manual, the entire project is open-source and ready to clone:
👉 Get the Code and Lab Manuals: github.com/404saint/raw-industrial-protocols
In the upcoming installments of this series, we will take each of these five protocols individually and run comprehensive deep dives exploring active network discovery, technical exploitation paths, deep packet defensive monitoring, and production protocol hardening.

Top comments (0)