DEV Community

Cover image for Abandoning Abstractions: Manually Crafting EtherNet/IP Packets Almost Broke Me
404Saint
404Saint

Posted on

Abandoning Abstractions: Manually Crafting EtherNet/IP Packets Almost Broke Me

By RUGERO Tesla (@404Saint).

There is a persistent illusion in Industrial Control Systems (ICS) security research: that high-level libraries, abstraction frameworks, or protocol tooling give you a real understanding of Operational Technology (OT) behavior.

They don’t.

They hide the architecture.

Determined to understand what actually happens when a Programmable Logic Controller (PLC) receives a control-plane command, I built an EtherNet/IP and Common Industrial Protocol (CIP) sandbox from scratch. No Scapy. No protocol wrappers. Just raw sockets, a Linux loopback interface, a cpppo simulator, and a passive monitoring tool (enip_monitor.py) capturing traffic in real time.

It looked clean on paper. Then I reached the application layer.

And things stopped behaving like theory.


The Reality of the “Industrial Abstraction Layer”

If you come from Modbus or traditional IT networking, you’re used to linear memory spaces—fixed registers, predictable offsets, and flat addressing.

EtherNet/IP and CIP discard that model entirely.

Instead, they introduce a structured object system wrapped inside multiple encapsulation layers:


+-----------------------------------------------------------+
| EtherNet/IP Encapsulation Header (24 bytes)               |
| → Session control, commands (0x0065, 0x006F)              |
+-----------------------------------------------------------+
| Common Packet Format (CPF)                                |
| → Routing, addressing, and transport segmentation         |
+-----------------------------------------------------------+
| CIP Application Layer                                     |
| → Service codes (0x4C, 0x4D, 0x10, etc.)                  |
+-----------------------------------------------------------+

Enter fullscreen mode Exit fullscreen mode

To communicate with a PLC at the wire level, your code must:

  • Establish a session using RegisterSession (0x0065)
  • Wrap all subsequent requests in SendRRData (0x006F)
  • Encode routing information inside CPF structures
  • Construct symbolic or logical paths for the CIP Message Router
  • Ensure strict byte alignment across nested payload layers

A single mistake in any layer breaks everything silently.


The Wall: Fragmentation and State Confusion

Basic tag reads (0x4C) and writes (0x4D) against my test dataset (SAINT_DATA) worked as expected.

Then I moved into fragmented transfers using:

  • 0x52 (Read Fragmented)
  • 0x53 (Write Fragmented)

This is where things stopped being predictable.

I was manually tracking offsets, element counts, and buffer boundaries across multiple frames. The client appeared to execute correctly:


[+] Attribute Write Resolved: write_frag.elements
[+] Attribute Write Resolved: write_frag.offset
[+] Attribute Write Resolved: write_frag.data
[+] Attribute Write Resolved: service

Enter fullscreen mode Exit fullscreen mode

But nothing changed on the wire.

No values. No errors. No feedback.

Just silent failure.

At this level, there is no stack trace. If a single byte in the CPF or path encoding is wrong, the CIP Message Router doesn’t explain itself—it simply drops the inner execution.


The Breakthrough: Observing the Wire Directly

The turning point came from running enip_monitor.py alongside my client scripts, capturing loopback traffic in real time.

Instead of trusting the client’s internal state logs, I started trusting the packet stream.

Then I triggered a deliberate failure by querying a non-existent tag (NON_EXISTENT_TAG).

The monitor immediately exposed the actual system behavior:


[!] SESSION REGISTRATION DETECTED | Handle: 0xe946f379 | Status: 0
[+] INDUSTRIAL MONITORING ALERT
Source IP   : 127.0.0.1
Session     : 0xf6d8e0cf
CIP Service : Unconnected Send (Router)
Target      : NON_EXISTENT_TAG

Enter fullscreen mode Exit fullscreen mode

The PLC responded with a clear General Status Code 0x05 (Path Destination Unknown).

That moment mattered.

It confirmed the mismatch between what the client believed it sent and what the wire actually carried. Once I aligned the fragmentation offsets correctly, the payload began committing cleanly.

The system was never broken. The assumptions were.


Core Takeaway

This project proved something simple but important:

Abstraction layers do not guarantee understanding. They often remove it.

EtherNet/IP and CIP are optimized for speed, interoperability, and extensibility. In doing so, they expose a deeply structured but highly implicit execution model where correctness depends entirely on precise packet construction.

From a security perspective, this matters because:

  • The protocol trusts correctly formatted packets by default
  • Symbolic and object-level structures are externally observable
  • Error states leak structural information about internal routing logic
  • Enumeration and interaction require no authentication in many legacy configurations

Defending these systems requires working at the wire level:

  • Building packet-aware detection logic (e.g., Suricata/Snort rules)
  • Monitoring encapsulation and CIP service behavior directly
  • Enforcing strict network segmentation and device mode control
  • Deploying CIP Security where supported

Closing Thought

If you want to understand OT systems properly, don’t start with tooling.

Start with bytes.

Raw sockets are frustrating, slow, and unforgiving but they expose what every abstraction layer tries to hide.


Repository

All scripts, validation flows, and 13 supporting technical notes are documented here:

👉 https://github.com/404saint/industrial-protocol-labs/


Disclaimer

This research was conducted in a controlled, isolated laboratory environment for educational and defensive security purposes only. All protocol interaction, traffic generation, and packet analysis were performed against a local simulation stack and loopback interface.

This work is intended to support industrial cybersecurity understanding, detection engineering, and protocol analysis. It is not intended for use against production systems or any environment without explicit authorization and appropriate safety controls.

Always ensure compliance with applicable laws, operational safety requirements, and organizational policies when working with industrial control systems.

Top comments (0)