<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: 404Saint</title>
    <description>The latest articles on DEV Community by 404Saint (@null_saint).</description>
    <link>https://dev.to/null_saint</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3798991%2F0fd61c5d-8316-4d48-971a-49fc6e6de204.png</url>
      <title>DEV Community: 404Saint</title>
      <link>https://dev.to/null_saint</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/null_saint"/>
    <language>en</language>
    <item>
      <title>Inside S7comm: From ISO-on-TCP to PLC Memory</title>
      <dc:creator>404Saint</dc:creator>
      <pubDate>Mon, 21 Sep 2026 15:46:59 +0000</pubDate>
      <link>https://dev.to/null_saint/inside-s7comm-from-iso-on-tcp-to-plc-memory-19f9</link>
      <guid>https://dev.to/null_saint/inside-s7comm-from-iso-on-tcp-to-plc-memory-19f9</guid>
      <description>&lt;p&gt;&lt;em&gt;By RUGERO Tesla (&lt;a href="https://github.com/404saint" rel="noopener noreferrer"&gt;@404Saint&lt;/a&gt;).&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;S7comm is one of those protocols that looks simple until you start following the conversation all the way down. At first, there is just a TCP connection to port 102, then TPKT appears, then COTP, then Siemens-specific connection parameters, then the S7comm session has its own negotiation, then a memory address becomes meaningful, then a single function code can turn a packet from a read request into a write operation. That was exactly what I wanted to investigate.&lt;/p&gt;

&lt;p&gt;After working through Modbus TCP, EtherNet/IP, DNP3, BACnet, OPC UA, IEC 104, IEC 61850, and PROFINET, S7comm felt like the right protocol to finish this part of the research with. The earlier projects taught me to establish the architecture before touching the wire, but this time, I wanted to stay on the wire.&lt;/p&gt;

&lt;p&gt;The laboratory target was a Snap7 server exposing a Siemens S7-style communication interface on TCP/102. I built the research client myself, constructed the protocol messages, captured the exchanges, and then started deliberately deviating from the expected conversation, and that ended up being the most interesting.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Stack Before the First S7 Packet
&lt;/h2&gt;

&lt;p&gt;Before looking at an S7comm packet, there is a small stack that needs to be understood.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────┐
│          S7comm              │
├──────────────────────────────┤
│           COTP               │
├──────────────────────────────┤
│        TPKT / RFC 1006       │
├──────────────────────────────┤
│            TCP               │
├──────────────────────────────┤
│            IP                │
├──────────────────────────────┤
│         Ethernet             │
└──────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The TCP endpoint is normally port &lt;code&gt;102&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;TPKT provides the ISO-on-TCP framing. COTP provides the transport session semantics above it. S7comm then uses that established channel for Siemens-specific application services. That matters during research because an apparently simple S7comm exchange can actually contain several different state machines. I started with  the COTP connection because it was the lowest S7-specific piece I could directly control. &lt;/p&gt;




&lt;h2&gt;
  
  
  Establishing the Session
&lt;/h2&gt;

&lt;p&gt;The first experiment constructed a COTP Connection Request manually.&lt;/p&gt;

&lt;p&gt;The target was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;127.0.0.1:102
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The destination TSAP represented the lab configuration's Rack 0, Slot 2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dst-TSAP: 0x0102
Src-TSAP: 0x0100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client transmitted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;03 00 00 16
11 E0
00 00 00 01
00 C2 02 01 02
C1 02 01 00
C0 01 09
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response came back as a COTP Connection Confirm. The important part was seeing the layers line up in an actual capture rather than simply accepting what a high-level S7 library told me was happening.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwtfigvt045x14jel0kvv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwtfigvt045x14jel0kvv.png" alt="Figure 1" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. Captured S7comm session establishment showing the TPKT/COTP connection sequence between the research client and Snap7 target.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The TPKT header reported a 22-byte frame.&lt;/p&gt;

&lt;p&gt;The COTP TPDU changed from: &lt;code&gt;0xE0&lt;/code&gt; for the Connection Request to: &lt;code&gt;0xD0&lt;/code&gt; for the Connection Confirm. The reference fields were also visible directly in the exchange.&lt;/p&gt;

&lt;p&gt;One detail that caught my attention was the TPDU size parameter: &lt;code&gt;C0 01 09&lt;/code&gt;. This indicates a COTP TPDU size of 512 bytes. That number is easy to confuse with the S7 PDU size negotiated later. They are different parts of the communication model.&lt;/p&gt;

&lt;p&gt;I also tested unusual destination TSAP values. A destination TSAP of &lt;code&gt;0x01FF&lt;/code&gt; was accepted by the Snap7 target. In the lab's Siemens-style interpretation, that corresponds to Rack 7, Slot 31.&lt;/p&gt;

&lt;p&gt;Another unusual value, &lt;code&gt;0x0F02&lt;/code&gt;, was also accepted. That result is useful, but only within the boundaries of the experiment. The Snap7 server accepting those values does not establish that a physical Siemens PLC would accept the same combinations. At this point, the transport session existed and the actual S7 conversation had barely started.&lt;/p&gt;




&lt;h2&gt;
  
  
  Teaching the PLC How We Want to Talk
&lt;/h2&gt;

&lt;p&gt;Once the COTP session was established, the next step was S7comm PDU negotiation. This was where the protocol started becoming much more interesting. The Setup Communication exchange negotiated a maximum S7 PDU size of: &lt;code&gt;480 bytes&lt;/code&gt;. The session also reported one parallel calling job and one parallel called job.&lt;/p&gt;

&lt;p&gt;The negotiated value was visible directly in the response rather than being inferred from the later packets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqtsdkdgresagnrmppcpg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqtsdkdgresagnrmppcpg.png" alt="Figure 2." width="800" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2. S7comm Setup Communication followed by a ReadVar request targeting DB3.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The next request used the ReadVar service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ROSCTR: 0x01
Function: 0x04
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The variable specification identified an S7-Any pointer.&lt;/p&gt;

&lt;p&gt;The target was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Area:      DB
Area ID:   0x84
DB number: 3
Offset:    0
Count:     16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server returned an Ack-Data PDU with: &lt;code&gt;Return code: 0xFF&lt;/code&gt; and 16 bytes of data.&lt;/p&gt;

&lt;p&gt;The lab DB3 contents were:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;01 01 01 01 01 01 01 01
01 01 01 01 01 01 01 01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was the first point where the protocol stopped looking like an abstract collection of headers, because now I had a memory model. I could specify an area, a DB number, an offset, and a length. The PLC-style target would interpret those fields and return the corresponding data and that made the next question fairly obvious.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happens when the operation changes from reading memory to modifying it?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Reading Was Only the Beginning
&lt;/h2&gt;

&lt;p&gt;The S7comm WriteVar service uses function: &lt;code&gt;0x05&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For the first write experiment, I targeted DB3 at offset zero and supplied 16 bytes of &lt;code&gt;0xFF&lt;/code&gt;, and the request was accepted. The response returned: &lt;code&gt;0xFF&lt;/code&gt; for the write item.&lt;/p&gt;

&lt;p&gt;The Snap7 server confirmed the operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write request, Area : DB3, Start : 0, Size : 16 --&amp;gt; OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this laboratory configuration, there was no additional authentication step protecting that memory write. That is an important observation, but it needs to be scoped correctly. This demonstrates unauthenticated memory modification against the Snap7 target used in the lab. It does not establish that a particular physical Siemens PLC would accept the same request under the same conditions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ruecgeulz0cgjxnbl6o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ruecgeulz0cgjxnbl6o.png" alt="Figure 3." width="800" height="769"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3. Wire-level validation of the WriteVar experiments, including the successful DB3 write and subsequent boundary-testing request.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I then moved the write far outside the DB boundary. DB3 was 1024 bytes. The next request targeted offset: &lt;code&gt;65000&lt;/code&gt; and the server rejected it.&lt;/p&gt;

&lt;p&gt;The response returned: &lt;code&gt;0x05&lt;/code&gt; and the server reported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write request, Area : DB3, Start : 65000, Size : 4 --&amp;gt; Out of range
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was a useful contrast. The same service that allowed a valid write also enforced the memory boundary when the requested address was outside the configured DB. So the experiment wasn't simply “send WriteVar and everything works.” There was an actual implementation boundary and that becomes important later.&lt;/p&gt;




&lt;h2&gt;
  
  
  Trying the Control Plane
&lt;/h2&gt;

&lt;p&gt;The next experiment moved away from ordinary memory access.&lt;/p&gt;

&lt;p&gt;S7comm UserData uses: &lt;code&gt;ROSCTR: 0x07&lt;/code&gt; among other functions. I constructed a CPU control request intended to exercise the PLC STOP control path and the request was transmitted to the Snap7 server. There was no successful response. Instead, the connection was dropped. The important result here is what the experiment did &lt;strong&gt;not&lt;/strong&gt; prove. The packet was successfully constructed and transmitted but the Snap7 demo server did not implement the requested CPU control function.&lt;/p&gt;

&lt;p&gt;That means the experiment demonstrated the structure and transmission of the control-plane request, along with the emulator's implementation boundary. It did not demonstrate that a physical Siemens PLC would enter STOP as a result. That is easy to lose when packet-level research gets compressed into a headline.&lt;/p&gt;




&lt;h2&gt;
  
  
  Asking the PLC About Itself
&lt;/h2&gt;

&lt;p&gt;After reading and writing memory, I moved into S7 diagnostics. S7comm UserData provides access to System Status List information, commonly referred to as SZL data.&lt;/p&gt;

&lt;p&gt;The first query used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SZL ID: 0x0011
Index: 0x0000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Snap7 target returned the request successfully.&lt;/p&gt;

&lt;p&gt;The response exposed module identification information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;6ES7 315-2EH14-0AB0
6ES7 315-2EH14-0AB0
Boot Loader
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv5hu459rmmq78082rkpw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv5hu459rmmq78082rkpw.png" alt="Figure 4." width="799" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 4. SZL UserData exchanges used to enumerate module-identification information from the simulated S7 target.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is where diagnostic enumeration becomes interesting from a security perspective. A protocol does not need to provide an obvious “enumerate device” command to expose useful information. If diagnostic services can return module identifiers, firmware information, hardware details, or other system metadata, those services become part of the information-disclosure surface.&lt;/p&gt;

&lt;p&gt;I tested another SZL identifier: &lt;code&gt;0x0111&lt;/code&gt; but this time the target did not provide the requested object. The response indicated that the object was unavailable, matching the Snap7 server's own &lt;code&gt;NOT AVAILABLE&lt;/code&gt; result. &lt;/p&gt;

&lt;p&gt;Again, the implementation boundary matters. The absence of a response for this identifier does not tell us what every Siemens CPU would do. It tells us what this implementation did when presented with the request.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Happens When We Ignore the Conversation's Rules?
&lt;/h2&gt;

&lt;p&gt;This was the point where the research became much more interesting.&lt;/p&gt;

&lt;p&gt;By now, the expected session flow was familiar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TCP
  ↓
COTP CR / CC
  ↓
Setup Communication
  ↓
S7comm services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So I decided to remove one of those steps. Instead of establishing COTP, negotiating the S7 PDU parameters, and then issuing a ReadVar request, I established the COTP session and immediately sent the S7comm read. No Setup Communication, and the Snap7 server responded. The ReadVar operation returned live DB3 data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiq5jqifzm50wrfezl2te.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiq5jqifzm50wrfezl2te.png" alt="Figure 5." width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 5. Phase 5 capture showing the state-manipulation and held-open session experiments against the Snap7 target.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That was one of the most interesting findings in the entire project. The expected protocol sequence existed conceptually, but the implementation did not enforce it for this read operation.&lt;/p&gt;

&lt;p&gt;The packet capture made the sequence very clear:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COTP connection
      ↓
ReadVar
      ↓
Setup Communication
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The S7comm service arrived before the negotiation that was supposed to precede it. At this point, I stopped asking what the protocol was supposed to do and started asking what the implementation would actually tolerate.&lt;/p&gt;

&lt;h3&gt;
  
  
  The write experiment was different
&lt;/h3&gt;

&lt;p&gt;I repeated the same idea with WriteVar. This time the server processed the request but returned: &lt;code&gt;0x05&lt;/code&gt; for the write item. The subsequent fully negotiated read showed that DB3.0 remained unchanged. So there was no pre-Setup write bypass demonstrated here.&lt;/p&gt;

&lt;p&gt;The results were asymmetric:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Before Setup Communication&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ReadVar&lt;/td&gt;
&lt;td&gt;Accepted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WriteVar&lt;/td&gt;
&lt;td&gt;Reached service, rejected with &lt;code&gt;0x05&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU control&lt;/td&gt;
&lt;td&gt;Not implemented by Snap7 target&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is useful than simply calling the implementation “vulnerable.” The state enforcement was incomplete for the tested read path, while the write path did not produce a successful modification.&lt;/p&gt;




&lt;h2&gt;
  
  
  Holding the Connections Open
&lt;/h2&gt;

&lt;p&gt;The last experiment looked at resource handling. I created 50 sessions, completed the expected negotiation, then sent a ReadVar request with a TPKT length deliberately larger than the actual payload. Each socket was kept open for approximately 15 seconds. All 50 connection attempts were established and held. The server remained available after the sockets were released.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Connections attempted: 50
Successfully held open: 50
Failed before hold: 0

Total wall time: 15.01s
Server liveness: UP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That result does &lt;strong&gt;not&lt;/strong&gt; demonstrate a denial of service. It demonstrates that the tested server tolerated the controlled connection load and malformed-length condition for the duration of the experiment without becoming unavailable. But that is still useful. Connection handling, timeout behavior, parser robustness, and state management are all part of the attack surface of an industrial protocol implementation.&lt;/p&gt;

&lt;p&gt;And real Siemens products have historically had security advisories involving port 102, including connection exhaustion and other protocol-handling issues. Siemens has also published advisories for vulnerabilities affecting S7 communication over TCP/102. The lesson from the lab is therefore: &lt;strong&gt;Protocol state and resource handling deserve to be tested independently of application-level functionality.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Emulator Boundary
&lt;/h2&gt;

&lt;p&gt;This was probably the most important discipline in the whole project. A protocol emulator is incredibly useful. It gives me something I can repeatedly connect to, instrument, reset, and abuse without worrying about damaging production equipment. But it also creates a boundary around the evidence.&lt;/p&gt;

&lt;p&gt;The experiments demonstrated that the Snap7 implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;accepted the tested unauthenticated WriteVar request against DB3&lt;/li&gt;
&lt;li&gt;enforced the configured DB boundary for an out-of-range write&lt;/li&gt;
&lt;li&gt;returned SZL &lt;code&gt;0x0011&lt;/code&gt; identification data&lt;/li&gt;
&lt;li&gt;processed a ReadVar request before Setup Communication&lt;/li&gt;
&lt;li&gt;rejected the tested pre-Setup WriteVar operation&lt;/li&gt;
&lt;li&gt;did not implement the tested CPU control request&lt;/li&gt;
&lt;li&gt;remained available after the controlled 50-session experiment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But they did not demonstrate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;that a physical Siemens PLC would accept the same unauthenticated write&lt;/li&gt;
&lt;li&gt;that a physical PLC would enter STOP from the constructed control request&lt;/li&gt;
&lt;li&gt;that 50 held-open sessions constitute a denial-of-service condition&lt;/li&gt;
&lt;li&gt;that every S7-300, S7-400, S7-1200, or S7-1500 behaves this way&lt;/li&gt;
&lt;li&gt;that the tested Snap7 state-machine behavior represents Siemens' implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping that boundary intact makes the research stronger. It is very easy to turn a packet capture into a claim about an entire product family, but I would rather have a smaller claim that I can actually defend.&lt;/p&gt;




&lt;h2&gt;
  
  
  From S7comm to S7CommPlus
&lt;/h2&gt;

&lt;p&gt;Classic S7comm also provides a useful reference point for understanding why Siemens introduced additional security mechanisms into newer generations.&lt;/p&gt;

&lt;p&gt;Modern S7-1200 and S7-1500 systems include configurable access protection and newer secure communication mechanisms. Siemens documentation describes protection levels that can restrict reading, writing, and CPU state changes, depending on the configured access level.&lt;/p&gt;

&lt;p&gt;Siemens has also documented TLS-based secure PG/HMI communication in newer firmware and TIA Portal environments. For example, its S7-1500 communication documentation lists TLS 1.2 and TLS 1.3 support for newer firmware versions. The security model has therefore moved considerably beyond the classic S7comm environment. The protocol stack itself is still relevant, though.&lt;/p&gt;

&lt;p&gt;TCP/102, TPKT, and COTP remain important pieces of the communication path. What changes is what happens above that foundation: session establishment, authentication, integrity protection, confidentiality, authorization, and the handling of protected operations. That atters because securing the application layer does not make the lower layers irrelevant. An attacker still needs to understand how the device establishes communication before they can meaningfully investigate what is protected above it.&lt;/p&gt;

&lt;p&gt;There is also no single timeless “S7CommPlus” behavior that can be applied to every modern Siemens CPU. Protocol generations, firmware versions, configuration, and enabled communication paths matter. Siemens itself has documented the transition toward TLS-protected communication and has issued security advisories involving legacy communication mechanisms. One advisory, for example, describes weaknesses in legacy key protection and notes the introduction of individual device passwords and TLS-protected PG/PC and HMI communication in newer TIA Portal and CPU versions.&lt;/p&gt;

&lt;p&gt;That makes the contrast more useful than simply saying that S7comm is “old” and S7CommPlus is “secure.” The interesting question is what security properties were added, where they were added, and which communication paths still depend on legacy mechanisms.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security Characteristics
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Observation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Transport&lt;/td&gt;
&lt;td&gt;S7comm laboratory communication used TCP/102 with TPKT and COTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session establishment&lt;/td&gt;
&lt;td&gt;COTP CR/CC exchange exposed TSAP and reference parameters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDU negotiation&lt;/td&gt;
&lt;td&gt;Setup Communication negotiated a 480-byte S7 PDU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory access&lt;/td&gt;
&lt;td&gt;ReadVar successfully accessed DB3 in the Snap7 lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory modification&lt;/td&gt;
&lt;td&gt;WriteVar successfully modified DB3 without authentication in the tested configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bounds enforcement&lt;/td&gt;
&lt;td&gt;Out-of-range DB3 write was rejected with &lt;code&gt;0x05&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diagnostics&lt;/td&gt;
&lt;td&gt;SZL &lt;code&gt;0x0011&lt;/code&gt; returned module identification data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State enforcement&lt;/td&gt;
&lt;td&gt;Snap7 accepted ReadVar before Setup Communication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write state handling&lt;/td&gt;
&lt;td&gt;Pre-Setup WriteVar did not successfully modify memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU control&lt;/td&gt;
&lt;td&gt;Tested UserData control request was not implemented by Snap7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource handling&lt;/td&gt;
&lt;td&gt;50 held-open test sessions did not make the Snap7 target unavailable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Modern security contrast&lt;/td&gt;
&lt;td&gt;Newer Siemens platforms provide configurable access protection and secure communication mechanisms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This result is a much clearer picture than I had when I started. S7comm is not simply “TCP port 102.” It is a layered conversation with its own transport setup, session negotiation, memory model, diagnostic services, and state assumptions. And once those assumptions become visible on the wire, they become testable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing the Loop
&lt;/h2&gt;

&lt;p&gt;The most satisfying part of this research was not finding one magic packet. It was watching the protocol reveal itself one layer at a time. First, a COTP connection, then a negotiated PDU, then a memory read, then a write, then diagnostics, then deliberately breaking the expected order and watching the implementation decide what it would tolerate.&lt;/p&gt;

&lt;p&gt;After IEC 104, IEC 61850, and PROFINET, S7comm felt like the natural place to finally cross the boundary I had been preparing for throughout the series. &lt;/p&gt;

&lt;h2&gt;
  
  
  Research Repository
&lt;/h2&gt;

&lt;p&gt;The complete laboratory, research notes, scripts, packet captures, and screenshots are available in the &lt;strong&gt;industrial-protocol-labs&lt;/strong&gt; repository:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://github.com/404saint/industrial-protocol-labs/tree/main/s7comm-research&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The experiments were performed against a local Snap7-based laboratory target. They should not be interpreted as evidence of identical behavior on physical Siemens PLCs.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>network</category>
      <category>opensource</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Reproducing PROFINET in the Lab: Discovery, AR Establishment, and Cyclic RT Boundaries</title>
      <dc:creator>404Saint</dc:creator>
      <pubDate>Sat, 12 Sep 2026 03:29:25 +0000</pubDate>
      <link>https://dev.to/null_saint/reproducing-profinet-in-the-lab-discovery-ar-establishment-and-cyclic-rt-boundaries-3oi0</link>
      <guid>https://dev.to/null_saint/reproducing-profinet-in-the-lab-discovery-ar-establishment-and-cyclic-rt-boundaries-3oi0</guid>
      <description>&lt;p&gt;&lt;em&gt;By RUGERO Tesla (&lt;a href="https://github.com/404saint" rel="noopener noreferrer"&gt;@404Saint&lt;/a&gt;).&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;I approached this research with the same general workflow I have used for the other industrial protocols in my lab. Start with the architecture, identify a usable implementation, reproduce the visible protocol behavior, inspect the traffic, and then move toward security-relevant experiments. PROFINET made that process more complicated than I expected.&lt;/p&gt;

&lt;p&gt;The first challenge was getting the laboratory into a useful state. I spent a considerable amount of time working through Linux namespaces, virtual interfaces, routing, and the behavior of the available open-source implementation. Once the environment was working, the next challenge appeared: some parts of PROFINET were easy to observe, while other parts depended on controller/device behavior that the available stack did not fully expose. That changed the shape of the research.&lt;/p&gt;

&lt;p&gt;I could still examine discovery, application-relation traffic, cyclic Real-Time framing, and crafted Layer 2 inputs. I simply had to be more careful about what each experiment actually established.&lt;/p&gt;

&lt;p&gt;The goal of this project became straightforward:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build a reproducible PROFINET laboratory, examine its observable protocol surfaces, and document the point where packet construction, implementation output, and verified endpoint behavior stop answering the same question.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Laboratory Scope
&lt;/h2&gt;

&lt;p&gt;The laboratory used Linux network namespaces to isolate the controller and device sides of the experiment. The main implementation was &lt;code&gt;p-net&lt;/code&gt;, supported by custom Python and Scapy-based harnesses, packet captures, and Wireshark/TShark analysis.&lt;/p&gt;

&lt;p&gt;The work followed five phases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;DCP and LLDP discovery&lt;/li&gt;
&lt;li&gt;DCE/RPC application-relation traffic&lt;/li&gt;
&lt;li&gt;Cyclic Real-Time frame analysis&lt;/li&gt;
&lt;li&gt;Layer 2 traffic generation, netload, and malformed inputs&lt;/li&gt;
&lt;li&gt;A specification-driven review of PROFINET security mechanisms&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first four phases involved direct laboratory experimentation. The final phase connected those observations to PROFINET’s historical Security Class model and newer secure application mechanisms.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Protocol Surfaces
&lt;/h2&gt;

&lt;p&gt;Before working with the captures, I needed a practical model of how the main protocol components fit together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Controller / Engineering System
              |
              | DCP and LLDP
              v
     Device identity and topology
              |
              | DCE/RPC
              v
     Application Relationship
              |
              | AR and IOCR configuration
              v
       Cyclic Real-Time I/O
              |
              v
       Process data and state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DCP provides Layer 2 discovery and configuration functions. LLDP contributes neighboring-device and topology information. DCE/RPC over UDP port &lt;code&gt;34964&lt;/code&gt; carries acyclic application-relation traffic, including structures associated with AR and IOCR configuration. Cyclic Real-Time communication uses PROFINET’s EtherType, &lt;code&gt;0x8892&lt;/code&gt;, and carries process data directly over Ethernet.&lt;/p&gt;

&lt;p&gt;These components are connected. Discovery identifies a device, but discovery alone does not create a cyclic I/O relationship. AR-related traffic has meaning within the context of a controller/device relationship. A cyclic RT frame can have a recognizable layout, but its acceptance still depends on endpoint state and implementation behavior. That relationship between architecture and packet meaning guided the rest of the research.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evidence and Interpretation
&lt;/h2&gt;

&lt;p&gt;I kept three forms of evidence separate throughout the experiments.&lt;/p&gt;

&lt;p&gt;✔ The packet captures established things such as EtherTypes, MAC addresses, UDP ports, DCE/RPC fields, frame IDs, payload lengths, timing, and whether responses appeared.&lt;/p&gt;

&lt;p&gt;✔ Application output added information about discovery results, parsed structures, reported state, and local transmission behavior.&lt;/p&gt;

&lt;p&gt;✔ Security conclusions were then drawn from those observations, with care around what remained unverified. For example, a captured crafted frame proves that the frame reached the capture point. It does not tell me whether the target accepted it, rejected it, ignored it, or processed it in some other way.&lt;/p&gt;

&lt;p&gt;That sounds obvious, but it becomes easy to lose track of when a script reports a successful send and the capture looks exactly like the frame that was intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase One: DCP and LLDP Discovery
&lt;/h2&gt;

&lt;p&gt;The first phase established the identity and Layer 2 visibility of the device under test. The &lt;code&gt;p-net&lt;/code&gt; device was placed in a separate namespace and queried from the controller side using DCP Identify traffic. The response exposed the station name, vendor and device identifiers, and active IP configuration.&lt;/p&gt;

&lt;p&gt;The device reported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Station name: rt-labs-dev
Vendor ID:    0x0493
Device ID:    0x0002
IP address:   192.168.1.20
Subnet mask:  255.255.255.0
Gateway:      192.168.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The DCP traffic used EtherType &lt;code&gt;0x8892&lt;/code&gt;. The station-name information appeared under the Name of Station option, using option &lt;code&gt;0x02&lt;/code&gt; and suboption &lt;code&gt;0x02&lt;/code&gt;. The result was useful immediately. I could identify the device and retrieve its configuration without relying on an IP-based management service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3ljoedkuc1cq9do1j2iu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3ljoedkuc1cq9do1j2iu.png" alt="DCP discovery audit output" width="800" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. DCP discovery audit showing the station name, vendor and device identifiers, and active IP configuration reported by the laboratory device.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing a station-name update
&lt;/h2&gt;

&lt;p&gt;After discovery, I tried changing the station name to: &lt;code&gt;tesla-pnet-node&lt;/code&gt;. The device continued reporting: &lt;code&gt;rt-labs-dev&lt;/code&gt;.  The update did not take effect. The behavior was consistent with runtime write protection, although the unchanged station name alone did not reveal the exact reason for the failure. This was a useful early reminder that a request and its intended effect are separate things. The request could be generated and transmitted successfully while the configuration remained unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  LLDP topology information
&lt;/h2&gt;

&lt;p&gt;LLDP provided another view of the same Layer 2 environment.&lt;/p&gt;

&lt;p&gt;The captured information included:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chassis ID:
P-Net Sample Application  12345 Abcdefghijk    007                  3 V  0  2  0

Port ID:
port-001.rt-labs-dev

System Name:
p-net IO-Device

TTL:
20 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLDP frames used EtherType &lt;code&gt;0x88CC&lt;/code&gt; and the standard LLDP multicast destination: &lt;code&gt;01:80:c2:00:00:0e&lt;/code&gt;. The TTL represented the validity lifetime of the advertised information. I treated it as an information lifetime rather than assuming it described the transmission interval.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4exvqjnhd7g6bc52mpvk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4exvqjnhd7g6bc52mpvk.png" alt="LLDP topology output" width="800" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2. LLDP topology information observed from the laboratory device, including chassis identity, port identity, system name, and advertisement lifetime.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Security relevance
&lt;/h2&gt;

&lt;p&gt;The discovery phase exposed several useful protocol surfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Station-name identity&lt;/li&gt;
&lt;li&gt;Vendor and device identifiers&lt;/li&gt;
&lt;li&gt;IP configuration&lt;/li&gt;
&lt;li&gt;Layer 2 device visibility&lt;/li&gt;
&lt;li&gt;Neighbor and port information&lt;/li&gt;
&lt;li&gt;Configuration-related operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An actor with suitable Layer 2 access could use these mechanisms to learn about devices and their network context. The experiment did not establish a vulnerability by itself, but it showed how much information is available before any higher-level communication relationship is created.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase Two: DCE/RPC and Application-Relation Traffic
&lt;/h2&gt;

&lt;p&gt;DCP discovery identifies a device. It does not establish the communication relationship required for cyclic I/O.&lt;/p&gt;

&lt;p&gt;The second phase moved into DCE/RPC traffic over UDP port &lt;code&gt;34964&lt;/code&gt;. The custom harness constructed a connect request containing structures associated with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ARBlockReq&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IOCRBlockReq&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AlarmCRBlockReq&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The request used this ARUUID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;08019a8a-9f6f-4bb1-ad46-0da8698a5bc5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The observed DCE/RPC interface UUID was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4a823108-a078-11d0-b21a-00a0241a673b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The packet capture showed a controller-to-device request followed by a device-to-controller response.&lt;/p&gt;

&lt;p&gt;The capture independently established:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DCE/RPC version 4&lt;/li&gt;
&lt;li&gt;Request packet type &lt;code&gt;0x00&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Response packet type &lt;code&gt;0x02&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Interface UUID&lt;/li&gt;
&lt;li&gt;Opnum &lt;code&gt;122&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Response association with the original request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The response contained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dcerpc.request_in: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That field associated the response with the first request in the capture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjy0ovbdf7x509j07xpz0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjy0ovbdf7x509j07xpz0.png" alt="DCE/RPC application-relation exchange" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3. DCE/RPC request and response observed during the application-relation experiment over UDP port 34964.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I also captured the broader exchange to examine the surrounding traffic and the sequence reported by the implementation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd71yrsdrfgmnkqalaakf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd71yrsdrfgmnkqalaakf.png" alt="DCE/RPC full handshake" width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 4. Expanded view of the DCE/RPC application-relation exchange and associated traffic observed during the laboratory handshake.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AR-related structures and state progression
&lt;/h2&gt;

&lt;p&gt;The application output provided additional context for the AR-related blocks and reported a state sequence resembling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IDLE
  → CONNECT
  → PARAM
  → CONFIG
  → APPL-READY
  → RUN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I treated this as implementation and test behavior. It helped explain what the application believed was happening during the exchange, but I did not present it as a universal wire-level state machine for every PROFINET implementation. TShark exposed the DCE/RPC fields, but it did not independently decode every individual PROFINET block field in the request and response. The block-level interpretation therefore came from the combined evidence of the harness, implementation output, and packet structure. That combination was enough to make the exchange useful for research, while still leaving some endpoint-side questions open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security relevance
&lt;/h2&gt;

&lt;p&gt;The experiment exposed several questions relevant to application-relation security:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How are ARUUID values validated?&lt;/li&gt;
&lt;li&gt;How does an implementation bind an AR to a device and controller?&lt;/li&gt;
&lt;li&gt;How are duplicate or conflicting relationships handled?&lt;/li&gt;
&lt;li&gt;What prevents replay or injection of relationship-management traffic?&lt;/li&gt;
&lt;li&gt;How does the implementation behave when relationship resources are exhausted?&lt;/li&gt;
&lt;li&gt;Which parts of the exchange receive authentication or integrity protection?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ARUUID identified a communication relationship. It did not function as a credential. The observed exchange also did not demonstrate mutual cryptographic authentication at the DCE/RPC layer. Questions around injection, hijacking, replay, and resource exhaustion would require additional endpoint-side experiments and implementation-specific evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase Three: Cyclic Real-Time Framing
&lt;/h2&gt;

&lt;p&gt;The third phase examined cyclic Real-Time traffic over PROFINET EtherType &lt;code&gt;0x8892&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At this point, the available &lt;code&gt;p-net&lt;/code&gt; configuration became a limiting factor. It did not produce a usable cyclic RT stream for the experiment I wanted to perform. Rather than leave the wire format unexplored, I built a separate traffic generator to examine frame structure, field placement, timing, and controlled payload changes.&lt;/p&gt;

&lt;p&gt;The generator was deliberately limited in scope. It could construct and transmit candidate RT frames, but it did not establish an AR, negotiate IOCRs, maintain a process image, or demonstrate receiver-side acceptance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observed frame layout
&lt;/h2&gt;

&lt;p&gt;The generated frames were 60 bytes long and followed this layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ethernet destination       6 bytes
Ethernet source            6 bytes
EtherType                  2 bytes
Frame ID                   2 bytes
IO/application data       40 bytes
Cycle Counter              2 bytes
DataStatus                 1 byte
TransferStatus             1 byte
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The capture contained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frame count:       515
Frame length:      60 bytes
EtherType:         0x8892
Frame ID:          0x8000
Application data:  40 bytes
Cycle Counter:     0x0000
DataStatus:        0x35
TransferStatus:    0x00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The relative PROFINET RT payload layout was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Offset 0–1:   Frame ID
Offset 2–41:  IO/application data
Offset 42–43: Cycle Counter
Offset 44:    DataStatus
Offset 45:    TransferStatus
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One representative frame contained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Offset 14:  80 00
Offset 16:  01 59
Offset 18:  01 02 03 04
Offset 56:  00 00
Offset 58:  35
Offset 59:  00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Correcting the cycle-counter interpretation
&lt;/h2&gt;

&lt;p&gt;One of the more useful moments in this phase came from checking the changing values against the actual field offsets. The sequential values initially looked like possible cycle-counter values. When I checked their position in the frame, they were inside the application-data region. The actual cycle-counter field remained: &lt;code&gt;00 00&lt;/code&gt;. The changing values belonged to application data.&lt;/p&gt;

&lt;p&gt;This was a good example of why protocol analysis needs more than a changing byte sequence. Field position, length, surrounding structure, and protocol semantics all have to agree before a value can be assigned a meaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controlled payload insertion
&lt;/h2&gt;

&lt;p&gt;The generator inserted: &lt;code&gt;FF 00 AA 55&lt;/code&gt; into the application-data region. The special frames began at capture frame 346. The sequence appeared at absolute offsets 18 through 21, placing it inside application data rather than inside the cycle counter or status fields. The cycle counter remained unchanged. DataStatus and TransferStatus also remained unchanged.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Furmxbik541hd5zvo63w2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Furmxbik541hd5zvo63w2.png" alt="Cyclic RT analysis" width="800" height="744"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 5. Cyclic PROFINET RT analysis showing the observed frame structure, application-data changes, cycle-counter position, and status fields.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;No PROFINET diagnostics or alarms appeared in the capture. All 515 packets used the same EtherType and Frame ID.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timing observations
&lt;/h2&gt;

&lt;p&gt;The experiment did not produce the expected 144-millisecond silence. The maximum observed packet gap was approximately: &lt;code&gt;33.972 ms&lt;/code&gt;. Around the controlled payload event, a normal interval of approximately 32 milliseconds was divided into shorter intervals surrounding the inserted frame. The capture therefore showed a timing change around the event, rather than a complete communication silence.&lt;/p&gt;

&lt;p&gt;I used a watchdog model of: &lt;code&gt;32 ms × 3 = 96 ms&lt;/code&gt; as a laboratory assumption for discussing possible timeout behavior. That value was not treated as a universal PROFINET threshold, and the experiment did not demonstrate a watchdog transition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security relevance
&lt;/h2&gt;

&lt;p&gt;The experiment demonstrated that a host with Layer 2 access can construct candidate PROFINET RT frames and place controlled values inside the application-data region. It did not establish that a real controller or device accepted those frames, modified its process image, triggered a diagnostic, or changed state.&lt;/p&gt;

&lt;p&gt;The cycle counter and status fields provide continuity and communication-state information, but they do not provide cryptographic authentication. A capable Layer 2 actor can construct plausible field values. Whether an endpoint accepts those values depends on the implementation and the communication state surrounding the frame. PROFIsafe was outside the scope of this experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase Four: Layer 2 Traffic, Netload, and Malformed Inputs
&lt;/h2&gt;

&lt;p&gt;The fourth phase used an independent Scapy-based harness to generate three categories of Layer 2 traffic:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A netload-oriented burst&lt;/li&gt;
&lt;li&gt;DCP-like station-name packets&lt;/li&gt;
&lt;li&gt;Malformed RT-like frames&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal was to observe the traffic-generation path and inspect the resulting captures. I also wanted to see whether the target produced any visible response, diagnostic, or recovery behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Netload-oriented burst
&lt;/h2&gt;

&lt;p&gt;The harness targeted approximately 15,000 packets per second. The actual measured transmission rate was approximately: &lt;code&gt;4,014.46 packets per second&lt;/code&gt;. The experiment transmitted 500 frames and recorded no kernel or interface drops. The resulting burst lasted approximately: &lt;code&gt;108.536 ms&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The capture showed 499 packet intervals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Minimum: 159.979 µs
Maximum: 1.708031 ms
Mean:    217.507 µs
Median:  205.040 µs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The intended rate was therefore not reproduced. The result told me more about the behavior of the traffic-generation path than about how a PROFINET endpoint would handle a genuine Class III load. That limitation was useful to record because the harness output alone could have made the experiment look more successful than the measurements supported.&lt;/p&gt;

&lt;h2&gt;
  
  
  DCP-like station-name packets
&lt;/h2&gt;

&lt;p&gt;The harness transmitted three crafted station-name packets from a rogue source MAC address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aa:bb:cc:dd:ee:ff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The packets claimed the station name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rt-labs-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The payload included:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;03 01 00 01 00 01 00 08 02 02 00 0b
72 74 2d 6c 61 62 73 2d 64 65 76
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I refer to these as DCP-like station-name packets because they did not include the conventional DCP frame identifier &lt;code&gt;FE FD&lt;/code&gt;. Calling them valid DCP Identify Responses would have gone beyond what the packet structure supported. The capture showed all three transmitted frames, but no subsequent response appeared.&lt;/p&gt;

&lt;h2&gt;
  
  
  Malformed RT-like frames
&lt;/h2&gt;

&lt;p&gt;The harness also transmitted five frames using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frame ID: 0x00FF
Payload:  10 bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frames appeared in the capture. No target-side diagnostic, rejection, recovery action, or state change was observed.&lt;/p&gt;

&lt;p&gt;The complete capture contained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;500 netload-oriented frames
3 DCP-like station-name frames
5 malformed RT-like frames
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All captured traffic used EtherType &lt;code&gt;0x8892&lt;/code&gt;. No IP, ARP, UDP, TCP, ICMP, or LLDP traffic appeared in the capture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5iba9rtli6fv8a2ubp2z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5iba9rtli6fv8a2ubp2z.png" alt="Phase 4 traffic audit" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 6. Phase 4 traffic audit showing the captured Layer 2 burst, DCP-like station-name packets, and malformed RT-like frames.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Security relevance
&lt;/h2&gt;

&lt;p&gt;The phase demonstrated that crafted Layer 2 traffic could be generated and captured in the isolated laboratory. The captures did not reveal how the target parsed, rejected, ignored, or processed each frame. The harness labels described the intended test stages, while the PCAP established what was transmitted and visible at the capture point. That left the endpoint-side outcome unresolved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase Five: Historical Security Classes and Newer Secure Mechanisms
&lt;/h2&gt;

&lt;p&gt;The final phase connected the laboratory observations to PROFINET’s published security architecture.&lt;/p&gt;

&lt;p&gt;The research question was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How does the security architecture defined by PROFINET specifications address the discovery, application-relation, cyclic RT, and Layer 2 attack surfaces observed experimentally, and how has that architecture evolved from the historical Security Class 1/2/3 model toward newer secure application mechanisms?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The comparison covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DCP and LLDP discovery&lt;/li&gt;
&lt;li&gt;DCE/RPC and application relations&lt;/li&gt;
&lt;li&gt;Cyclic RT traffic&lt;/li&gt;
&lt;li&gt;Layer 2 injection and malformed traffic&lt;/li&gt;
&lt;li&gt;Netload robustness&lt;/li&gt;
&lt;li&gt;Historical Security Classes 1, 2, and 3&lt;/li&gt;
&lt;li&gt;Newer secure application mechanisms&lt;/li&gt;
&lt;li&gt;Secure provisioning and device identity&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Historical Security Classes
&lt;/h2&gt;

&lt;p&gt;The historical model described three broad security classes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security Class 1:&lt;/strong&gt; robustness&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Class 2:&lt;/strong&gt; integrity and authenticity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Class 3:&lt;/strong&gt; confidentiality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This model provided a useful way to discuss increasing security requirements. It should, however, be kept separate from newer PROFINET security terminology.&lt;/p&gt;

&lt;h2&gt;
  
  
  Newer security architecture
&lt;/h2&gt;

&lt;p&gt;Published PROFINET specification material describes newer mechanisms and architectural changes, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secure-capable successors for protocols without secure variants&lt;/li&gt;
&lt;li&gt;SXP over Layer 2&lt;/li&gt;
&lt;li&gt;SXP over Layer 3&lt;/li&gt;
&lt;li&gt;Secure provisioning&lt;/li&gt;
&lt;li&gt;IDevID and certificate-based device identity&lt;/li&gt;
&lt;li&gt;Secure SXP over TCP&lt;/li&gt;
&lt;li&gt;Security-related GSDML application classes&lt;/li&gt;
&lt;li&gt;SecureAccess&lt;/li&gt;
&lt;li&gt;SecureRealtime&lt;/li&gt;
&lt;li&gt;Updated robustness terminology&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The newer architecture addresses particular communication and application requirements through more specific mechanisms.&lt;/p&gt;

&lt;p&gt;The experimental observations can be mapped to those directions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Observed surface&lt;/th&gt;
&lt;th&gt;Experimental concern&lt;/th&gt;
&lt;th&gt;Related security direction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DCP and LLDP&lt;/td&gt;
&lt;td&gt;Device identity and topology exposure&lt;/td&gt;
&lt;td&gt;Discovery hardening and controlled access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DCE/RPC and AR&lt;/td&gt;
&lt;td&gt;Relationship establishment and trust&lt;/td&gt;
&lt;td&gt;Secure-capable successors and authenticated mechanisms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cyclic RT&lt;/td&gt;
&lt;td&gt;Injection and freshness concerns&lt;/td&gt;
&lt;td&gt;SecureRealtime-related protection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Layer 2 traffic&lt;/td&gt;
&lt;td&gt;Injection, malformed input, and netload&lt;/td&gt;
&lt;td&gt;Robustness requirements, segmentation, and access control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Device identity&lt;/td&gt;
&lt;td&gt;Trust in device descriptions&lt;/td&gt;
&lt;td&gt;IDevID, certificates, and secure provisioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Historical security classes&lt;/td&gt;
&lt;td&gt;Robustness, integrity, authenticity, and confidentiality&lt;/td&gt;
&lt;td&gt;Newer security profiles and application classes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This phase remained specification-driven. The newer mechanisms were not implemented or validated in the laboratory.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Laboratory Established
&lt;/h2&gt;

&lt;p&gt;The research established a reproducible environment for examining several PROFINET protocol surfaces.&lt;/p&gt;

&lt;p&gt;The experiments demonstrated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DCP discovery and device identity enumeration&lt;/li&gt;
&lt;li&gt;Station-name and IP configuration visibility&lt;/li&gt;
&lt;li&gt;LLDP chassis, port, and system information&lt;/li&gt;
&lt;li&gt;DCE/RPC request and response behavior over UDP/34964&lt;/li&gt;
&lt;li&gt;AR-related application output and reported state progression&lt;/li&gt;
&lt;li&gt;Cyclic RT frame structure&lt;/li&gt;
&lt;li&gt;Actual cycle-counter and DataStatus positions&lt;/li&gt;
&lt;li&gt;Controlled application-data changes in generated RT frames&lt;/li&gt;
&lt;li&gt;Transmission of crafted Layer 2 traffic&lt;/li&gt;
&lt;li&gt;Measured behavior of the traffic-generation path&lt;/li&gt;
&lt;li&gt;A specification-driven comparison with historical and newer security mechanisms&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Remained Outside the Laboratory’s Reach
&lt;/h2&gt;

&lt;p&gt;Several questions required a more complete controller/device environment or a different implementation.&lt;/p&gt;

&lt;p&gt;The experiments did not establish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Universal PROFINET behavior across vendors&lt;/li&gt;
&lt;li&gt;Complete controller/device interoperability&lt;/li&gt;
&lt;li&gt;Endpoint acceptance of every crafted frame&lt;/li&gt;
&lt;li&gt;Process-image modification&lt;/li&gt;
&lt;li&gt;Target-side diagnostics or recovery&lt;/li&gt;
&lt;li&gt;Successful Class III netload reproduction&lt;/li&gt;
&lt;li&gt;Cryptographic protection of every observed legacy exchange&lt;/li&gt;
&lt;li&gt;PROFIsafe behavior&lt;/li&gt;
&lt;li&gt;Production-equivalent resilience&lt;/li&gt;
&lt;li&gt;The behavior of a complete commercial PROFINET controller/device pair&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These limits define the boundary of the results. They do not erase the discovery, packet analysis, implementation work, or corrections made during the research.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This study established a reproducible laboratory for examining PROFINET discovery, application-relation traffic, cyclic RT framing, and selected Layer 2 inputs.&lt;/p&gt;

&lt;p&gt;The available open-source implementation supported meaningful wire-level analysis, but it did not expose enough controller/device behavior to verify every intended endpoint-side effect. The separate cyclic RT generator made frame construction, field placement, and timing measurable, but it could not substitute for a functioning PROFINET IO-Device relationship. &lt;/p&gt;

&lt;p&gt;The most valuable part of the process was learning to recognize that boundary while the experiments were still in progress. A generated frame can look correct in a capture. A script can report a successful transmission. An implementation can print a state transition. Those observations become much more useful when I know exactly which part of the protocol they represent and which questions they leave unanswered.&lt;/p&gt;

&lt;p&gt;The research therefore ended with a clearer understanding of both PROFINET and the laboratory itself. I could reproduce discovery behavior, inspect application-relation traffic, analyze cyclic RT fields, generate controlled Layer 2 inputs, and compare those surfaces with the protocol’s security architecture. I could also identify where further claims would require a more complete endpoint implementation. That is, to me, a meaningful result for protocol security research.&lt;/p&gt;

&lt;p&gt;The protocol remains the subject. The implementation is the instrument. Sometimes the instrument exposes a complete interaction. Sometimes it gives only a partial view. The researcher’s job is to understand what the instrument can establish, preserve the evidence, and avoid turning an intended experiment into a claim that the laboratory never demonstrated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;DCP and LLDP expose useful device identity and topology information at Layer 2.&lt;/li&gt;
&lt;li&gt;DCP discovery does not establish the application relationship required for cyclic I/O.&lt;/li&gt;
&lt;li&gt;DCE/RPC traffic over UDP/34964 provides an observable surface for examining AR-related communication.&lt;/li&gt;
&lt;li&gt;An ARUUID identifies a communication relationship but is not a credential.&lt;/li&gt;
&lt;li&gt;Cyclic RT frame construction does not prove endpoint acceptance or process-image modification.&lt;/li&gt;
&lt;li&gt;Changing values inside a frame does not make those values the cycle counter. Field offsets and protocol semantics must agree.&lt;/li&gt;
&lt;li&gt;The observed RT cycle counter remained &lt;code&gt;0x0000&lt;/code&gt;, while changing values were located inside application data.&lt;/li&gt;
&lt;li&gt;The netload experiment measured the limits of the traffic-generation path rather than reproducing a complete Class III resilience test.&lt;/li&gt;
&lt;li&gt;DCP-like and malformed frames were captured, but target-side processing was not established.&lt;/li&gt;
&lt;li&gt;Historical Security Classes 1, 2, and 3 should be separated from newer PROFINET security terminology.&lt;/li&gt;
&lt;li&gt;Packet captures, application output, and specification material answer different parts of the research question.&lt;/li&gt;
&lt;li&gt;A completed protocol study does not need to produce an exploit in every phase.&lt;/li&gt;
&lt;li&gt;Understanding what the laboratory can and cannot prove is part of the research result.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reproduce the Research
&lt;/h2&gt;

&lt;p&gt;The complete laboratory setup, research notes, custom scripts, packet captures, and supporting material are available in the repository below. If you want to go beyond the observations in this article, the repository is the place to start reproducing the experiments yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/404saint/industrial-protocol-labs/tree/main/profinet-research" rel="noopener noreferrer"&gt;https://github.com/404saint/industrial-protocol-labs/tree/main/profinet-research&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>architecture</category>
      <category>cybersecurity</category>
      <category>network</category>
    </item>
    <item>
      <title>IEC 61850 Security Research: From SCL and MMS to GOOSE, Sampled Values, and the Process Bus</title>
      <dc:creator>404Saint</dc:creator>
      <pubDate>Tue, 01 Sep 2026 11:55:08 +0000</pubDate>
      <link>https://dev.to/null_saint/iec-61850-security-research-from-scl-and-mms-to-goose-sampled-values-and-the-process-bus-bnp</link>
      <guid>https://dev.to/null_saint/iec-61850-security-research-from-scl-and-mms-to-goose-sampled-values-and-the-process-bus-bnp</guid>
      <description>&lt;p&gt;&lt;em&gt;By RUGERO Tesla (&lt;a href="https://github.com/404saint" rel="noopener noreferrer"&gt;@404Saint&lt;/a&gt;).&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;IEC 61850 is another one of those protocols that becomes more interesting the deeper you go.&lt;/p&gt;

&lt;p&gt;At first glance, it looks like another industrial protocol to dissect: identify the transport, decode the messages, enumerate the data model, and look for places where authentication or authorization is missing and that description turns out to be inadequate.&lt;/p&gt;

&lt;p&gt;IEC 61850 is an ecosystem of communication models rather than a single protocol. MMS operates over the station bus and provides a comparatively conventional client/server interface. GOOSE and Sampled Values operate directly over Ethernet and introduce an entirely different set of assumptions around multicast, timing, state, sequencing, and data integrity. That difference became the central theme of this research.&lt;/p&gt;

&lt;p&gt;I spent several days building an isolated IEC 61850 laboratory around &lt;code&gt;libIEC61850&lt;/code&gt;, examining SCL configuration, MMS sessions, control operations, GOOSE, Sampled Values, and process-bus behavior. The work combined packet captures, custom Python tooling, native library examples, malformed-input testing, and direct observation of the simulated IEDs. There was also an unexpected historical component.&lt;/p&gt;

&lt;p&gt;During the research, Parker McCauley pointed me toward Bruce Muschlitz, a former UCA conformance-testing participant, with a simple instruction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ask Bruce about the UCA decision dart board.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That conversation eventually led to an older 9-2LE Sampled Value capture, a legacy Excel visualization tool, and several observations about how real conformance testing used to be performed. So this article is both a protocol-security study and a record of how the investigation evolved.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Establishing the IEC 61850 Architecture
&lt;/h2&gt;

&lt;p&gt;Before touching packets, I wanted a clear model of what I was actually testing.&lt;/p&gt;

&lt;p&gt;The laboratory was divided conceptually into three communication areas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    IEC 61850 Laboratory
                           │
          ┌────────────────┼────────────────┐
          │                │                │
          ▼                ▼                ▼
       SCL /          Station Bus       Process Bus
   Configuration          │                │
                          │          ┌─────┴─────┐
                          │          │           │
                         MMS       GOOSE         SV
                          │          │           │
                       TCP/102    Ethernet     Ethernet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference matters because MMS and the process-bus protocols do not provide the same security boundaries. MMS uses ISO-on-TCP and exposes an application-layer model containing logical devices, logical nodes, data objects, and control points. GOOSE and Sampled Values are much closer to the wire. They are Layer 2 Ethernet protocols identified by EtherTypes rather than TCP or UDP ports.&lt;/p&gt;

&lt;p&gt;That immediately creates different questions for a security researcher.&lt;/p&gt;

&lt;p&gt;For MMS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What can a client discover?&lt;/li&gt;
&lt;li&gt;Can it establish a session anonymously?&lt;/li&gt;
&lt;li&gt;What objects are exposed?&lt;/li&gt;
&lt;li&gt;Which operations require authorization?&lt;/li&gt;
&lt;li&gt;Are control operations protected?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For GOOSE and SV:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who is allowed to publish?&lt;/li&gt;
&lt;li&gt;How is publisher state represented?&lt;/li&gt;
&lt;li&gt;How are sequence numbers interpreted?&lt;/li&gt;
&lt;li&gt;What happens when frames originate from an unexpected source?&lt;/li&gt;
&lt;li&gt;Can subscribers distinguish legitimate traffic from manipulated traffic?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture primer established these questions before the experiments began.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. SCL: The Configuration Is Part of the Attack Surface
&lt;/h2&gt;

&lt;p&gt;The first formal phase examined the SCL configuration rather than immediately attacking the network protocols. IEC 61850 systems rely heavily on configuration describing IED capabilities, logical nodes, communication parameters, datasets, and relationships between devices.&lt;/p&gt;

&lt;p&gt;That means some of the information required to understand the operational system may exist before a single application-layer packet is exchanged.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhdd21l9ypcpsxqwev81a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhdd21l9ypcpsxqwev81a.png" alt="SCL configuration analysis" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. SCL configuration analysis from the laboratory. The configuration provided the structural baseline used for subsequent MMS and process-bus experiments.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This was important for the rest of the research because the later experiments were not performed against an abstract IEC 61850 endpoint. They were performed against a specific configured data model. The configuration therefore became the reference against which observed behavior could be interpreted.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. MMS: Finding the Station-Bus Attack Surface
&lt;/h2&gt;

&lt;p&gt;The next phase moved into MMS. The laboratory IED exposed MMS through ISO-on-TCP on port &lt;code&gt;102&lt;/code&gt;. Once a connection could be established, the interesting question was no longer whether TCP/102 was reachable. It was what the server was willing to disclose and execute after the connection. The first step was enumeration.&lt;/p&gt;

&lt;p&gt;A custom Python harness was used to inspect the MMS information model and map the exposed hierarchy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MMS
 │
 ├── Logical Devices
 │      │
 │      └── Logical Nodes
 │              │
 │              └── Data Objects
 │                      │
 │                      └── Data Attributes
 │
 └── Control Objects
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgw9xk3hgrss3icvp5gz4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgw9xk3hgrss3icvp5gz4.png" alt="MMS enumeration" width="799" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2. MMS data-model enumeration performed against the laboratory IED.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The important observation was that the server exposed a meaningful operational model after session establishment. This matters because IEC 61850 does not merely expose registers in the way a simple legacy industrial protocol might. The client is interacting with a structured information model. Once that model is known, the next question becomes much more interesting: &lt;strong&gt;Which parts of that model can actually be manipulated?&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. MMS Control: Discovery Became Manipulation
&lt;/h2&gt;

&lt;p&gt;The third phase focused on control operations. The initial attempt to write directly to a control value did not behave as a naive writable register would. The server rejected the operation because of its data-typing and control semantics. That failure was useful.&lt;/p&gt;

&lt;p&gt;It demonstrated that a failed raw write does not necessarily mean the underlying control point is protected. IEC 61850 provides higher-level control services through the ACSI model, and the correct operation path produced a very different result.&lt;/p&gt;

&lt;p&gt;The laboratory demonstrated that a properly constructed control operation could alter the simulated control state without an authentication mechanism being enforced by the tested configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fisyji2148drpemenscj1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fisyji2148drpemenscj1.png" alt="MMS control abuse" width="799" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3. Laboratory observation of MMS control operations against the simulated IED.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This became one of the more important distinctions in the research:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A protocol implementation can correctly enforce its application semantics while still providing insufficient security authorization.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The server knew that the request had to be structured correctly. It did not necessarily know whether the requester should have been allowed to perform it. That is central to industrial protocol security.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Leaving TCP Behind: The Process Bus
&lt;/h2&gt;

&lt;p&gt;At this point the research became considerably more interesting. MMS had given me a structured client/server interaction model but GOOSE and Sampled Values did not.&lt;/p&gt;

&lt;p&gt;Both operate directly over Ethernet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ethernet
   │
   ├── 0x88B8 → GOOSE
   │
   └── 0x88BA → Sampled Values
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The laboratory used Linux network namespaces and a virtual Ethernet bridge to create an isolated process-bus environment.&lt;/p&gt;

&lt;p&gt;The basic topology was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              br-processbus
              /            \
             /              \
        ns-ied1            ns-sub1
          │                   │
      veth-ied1           veth-sub1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The IED and subscriber could therefore exchange Layer 2 traffic without involving the physical network. That gave me a controlled environment for examining the actual Ethernet frames.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnp4389qg0dbiatfyueok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnp4389qg0dbiatfyueok.png" alt="Process bus analysis" width="800" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 4. Process-bus traffic observed inside the isolated laboratory bridge.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This was also where the security model changed. There was no TCP session to authenticate. There was no client/server connection in the conventional sense. A subscriber observes Ethernet frames and interprets them according to the protocol semantics. That makes source identity, state numbers, sequence numbers, timing, and cryptographic protection much more important.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. GOOSE State Is More Than Payload
&lt;/h2&gt;

&lt;p&gt;GOOSE frames contain state and sequencing information that allows subscribers to reason about publisher behavior.&lt;/p&gt;

&lt;p&gt;Among the fields examined were:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stNum
sqNum
TAL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;stNum&lt;/code&gt; represents the state number while &lt;code&gt;sqNum&lt;/code&gt; tracks sequencing within a state.&lt;/p&gt;

&lt;p&gt;The Time Allowed to Live value provides another important piece of information about the expected timing behavior of a message. These fields are not merely metadata. They influence how a subscriber interprets the stream. That means manipulating them can potentially affect the subscriber's view of system state even when the underlying payload appears structurally valid. The research therefore moved from passive dissection into controlled anomaly injection.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. GOOSE and SV Injection
&lt;/h2&gt;

&lt;p&gt;The fifth phase tested how the process-bus environment reacted to deliberately manipulated traffic.&lt;/p&gt;

&lt;p&gt;The experiments included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unauthorized source MAC addresses;&lt;/li&gt;
&lt;li&gt;extreme &lt;code&gt;stNum&lt;/code&gt; values;&lt;/li&gt;
&lt;li&gt;sequence-number manipulation;&lt;/li&gt;
&lt;li&gt;quality-field manipulation;&lt;/li&gt;
&lt;li&gt;Sampled Value frequency anomalies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu0b5tpgjja7o8wl4k406.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu0b5tpgjja7o8wl4k406.png" alt="GOOSE state-number poisoning" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 5.  Controlled GOOSE state-number manipulation using an unauthorized source.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The purpose was not to demonstrate a production attack. The laboratory was used to determine how the simulated protocol participants interpreted abnormal frames. A structurally valid frame can therefore become interesting from a security perspective even when the Ethernet frame itself is perfectly ordinary.&lt;/p&gt;




&lt;h2&gt;
  
  
  7.1 Quality Metadata
&lt;/h2&gt;

&lt;p&gt;One experiment modified the quality information associated with the transmitted measurement.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4gtvvqv3l8wi7ubso1ap.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4gtvvqv3l8wi7ubso1ap.png" alt="GOOSE quality manipulation" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 6. Controlled quality-field manipulation during process-bus testing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This illustrates an important property of industrial telemetry: &lt;strong&gt;The numerical value is only part of the information being transmitted.&lt;/strong&gt; A measurement may be accompanied by metadata describing its quality or validity. An attacker who changes the metadata does not necessarily need to modify the numerical value itself to influence how downstream systems interpret the measurement. That creates a useful difference between: &lt;code&gt;Value integrity + Metadata integrity.&lt;/code&gt; They both matter.&lt;/p&gt;




&lt;h2&gt;
  
  
  7.2 Sampled Value Frequency
&lt;/h2&gt;

&lt;p&gt;The same principle appeared in Sampled Values. Sampled Value streams are time-dependent. Their usefulness depends not only on the numbers being transmitted, but also on the temporal characteristics of the stream.&lt;/p&gt;

&lt;p&gt;One controlled experiment introduced a frequency mismatch into the SV traffic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs0e8kwy7iz0yr5nbqln1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs0e8kwy7iz0yr5nbqln1.png" alt="SV frequency anomaly" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 7. Controlled Sampled Value frequency anomaly observed during the process-bus experiment.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This became particularly interesting later, when I began studying the historical 9-2LE material provided by Bruce Muschlitz.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. The Unexpected Detour: A Conversation With UCA History
&lt;/h2&gt;

&lt;p&gt;The IEC 61850 research took an unexpected turn before the formal security work was finished. Parker McCauley had commented on one of my research updates and, after seeing IEC 61850 in the list, told me, "Good luck on IEC 61850." When I replied that I was diving into GOOSE and MMS and would bring PCAPs, he suggested I contact Bruce Muschlitz and ask about the &lt;strong&gt;UCA decision dart board&lt;/strong&gt;. And I did.&lt;/p&gt;

&lt;p&gt;Bruce explained that he had worked in UCA conformance testing and that one of his major contributions involved the initial accreditation of testers. One detail immediately caught my attention. A significant part of that accreditation process involved manually inspecting PCAP logs to verify that testers were generating the correct stimulus and correctly interpreting the device-under-test response. There was no sophisticated automated pipeline doing the work for them.&lt;/p&gt;

&lt;p&gt;And Bruce had built an Excel-based tool to visualize Sampled Value data because raw packet streams were not particularly useful for understanding waveform behavior.&lt;/p&gt;

&lt;p&gt;He eventually sent me two historical artifacts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Template_SV_viewer_rev4p0.xlsm
Sample240.pcap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That changed the direction of one part of the research.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Reconstructing a Legacy 9-2LE Sampled Value Analysis Workflow
&lt;/h2&gt;

&lt;p&gt;The Excel tool was designed specifically around the fixed dataset structure of IEC 61850-9-2LE. Rather than simply opening the spreadsheet and treating it as a black box, I decided to reproduce the relevant analysis path in Python.&lt;/p&gt;

&lt;p&gt;The pipeline became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sample240.pcap
      │
      ▼
Ethernet
      │
      ▼
VLAN detection
      │
      ▼
EtherType 0x88BA
      │
      ▼
ASN.1 / BER
      │
      ▼
SV ASDU
      │
      ▼
0x87 seqData
      │
      ▼
9-2LE fixed dataset
      │
      ├── Ia
      ├── Ib
      ├── Ic
      ├── In
      ├── Va
      ├── Vb
      ├── Vc
      └── Vn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the 9-2LE capture, the fixed dataset made the parsing problem unusually deterministic.&lt;/p&gt;

&lt;p&gt;Each of the eight measurement channels occupies eight bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4 bytes → measurement
4 bytes → quality
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;giving:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8 channels × 8 bytes = 64 bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parser extracted the values, tracked &lt;code&gt;smpCnt&lt;/code&gt;, and reconstructed the signals as time-series data. The result was much easier to understand than a terminal full of integer values. The three-phase relationships became visible. The frequency, the neutral channel became visible, and anomalies that would be easy to miss in a packet table became obvious when represented as waveforms.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Why Bruce Used Excel
&lt;/h2&gt;

&lt;p&gt;There was an amusing practical lesson here.&lt;/p&gt;

&lt;p&gt;My first instinct was essentially:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why Excel?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Python, Scapy, Matplotlib and a few lines of code were enough to build a native analysis pipeline. Bruce's answer was much more practical. Many engineering workstations used for conformance testing were heavily restricted. Installing a new application could be difficult. Excel, however, was already there. That meant an Excel macro could be deployed to systems where installing a dedicated analysis application was not realistic.&lt;/p&gt;

&lt;p&gt;Bruce himself admitted that Python would have been preferable if there had been a free choice. That was a useful reminder that engineering tooling is often constrained by the environment in which it has to operate, not by what would be technically elegant.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. What the Historical Capture Revealed
&lt;/h2&gt;

&lt;p&gt;The supplied &lt;code&gt;Sample240.pcap&lt;/code&gt; became the baseline for the independent parser. The capture contained a 9-2LE Sampled Value stream with the fixed dataset structure described above.&lt;/p&gt;

&lt;p&gt;The reconstructed measurements showed recognizable three-phase behavior and allowed the current and voltage channels to be viewed as continuous signals rather than isolated packet fields.&lt;/p&gt;

&lt;p&gt;That difference is important because,  Wireshark is excellent at telling you that a field contains a value.&lt;/p&gt;

&lt;p&gt;It is much less useful for answering questions such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Is this waveform periodic?

Are the phases behaving consistently?

Is the frequency correct?

Is the neutral channel behaving as expected?

Did the signal suddenly become noise?

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bruce had made the same observation from a conformance-testing perspective. One of the common anomalies he encountered was simply bad input data: a source that was producing noise when it should have been producing a meaningful signal.&lt;/p&gt;

&lt;p&gt;Another was using the wrong input frequency, such as 50 Hz where 60 Hz was expected. Those are not necessarily protocol parsing errors, they are measurement-integrity problems, and packet inspection alone can make them surprisingly difficult to see.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. One Important Correction to the 9-2LE Parser
&lt;/h2&gt;

&lt;p&gt;Bruce also caught an important limitation in my initial parser design. I had been treating the &lt;code&gt;0x87&lt;/code&gt; tag as though it represented a universally fixed 64-byte payload. That is only appropriate when the 9-2LE fixed dataset is already known. It does &lt;strong&gt;not&lt;/strong&gt; generalize to newer IEC 61869-9 implementations where dataset contents are declared through configuration.&lt;/p&gt;

&lt;p&gt;He also pointed out that a frame may contain multiple &lt;code&gt;0x87&lt;/code&gt; TLVs, particularly at higher sampling rates. That matters because a parser that simply searches for the first &lt;code&gt;0x87&lt;/code&gt; tag and consumes 64 bytes is not a general IEC 61850 Sampled Value parser. It is a &lt;strong&gt;9-2LE-specific parser&lt;/strong&gt;. That difference is now explicit in the research. This is exactly the sort of implementation boundary that is easy to miss when working only from a single capture.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. The Dataset Design Question
&lt;/h2&gt;

&lt;p&gt;The historical discussion also exposed an interesting design trade-off. Bruce described an earlier idea involving a fixed dataset intended to make merging units and subscribers effectively plug-and-play.&lt;/p&gt;

&lt;p&gt;The concept was attractive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Merging Unit
     │
     │ fixed dataset
     ▼
Any compatible subscriber
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The subscriber would already know what each position represented. The eventual direction taken by IEC 61869-9 instead required more reliance on dataset configuration, including information available through SCL. That saves bits on the wire but moves some complexity into configuration and interpretation.&lt;/p&gt;

&lt;p&gt;It is a classic engineering trade-off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Simpler wire format
        vs.
More flexible configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neither choice is inherently a security vulnerability, but every additional layer of configuration and interpretation creates another boundary that security researchers and conformance testers need to understand.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. What the Research Actually Demonstrated
&lt;/h2&gt;

&lt;p&gt;After several days in the laboratory, the IEC 61850 attack surface looked considerably different from when I started. The research demonstrated that the protocol stack has several distinct security boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  MMS
&lt;/h3&gt;

&lt;p&gt;The laboratory showed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;session establishment without native identity authentication in the tested configuration;&lt;/li&gt;
&lt;li&gt;structured information-model enumeration;&lt;/li&gt;
&lt;li&gt;exposed control objects;&lt;/li&gt;
&lt;li&gt;successful control execution without an enforced authentication boundary;&lt;/li&gt;
&lt;li&gt;correct rejection of malformed or semantically invalid operations in some cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GOOSE
&lt;/h3&gt;

&lt;p&gt;The experiments examined:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Layer 2 multicast delivery;&lt;/li&gt;
&lt;li&gt;publisher state;&lt;/li&gt;
&lt;li&gt;sequence tracking;&lt;/li&gt;
&lt;li&gt;Time Allowed to Live;&lt;/li&gt;
&lt;li&gt;unauthorized source behavior;&lt;/li&gt;
&lt;li&gt;manipulated state numbers;&lt;/li&gt;
&lt;li&gt;manipulated quality metadata.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sampled Values
&lt;/h3&gt;

&lt;p&gt;The research examined:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Layer 2 transmission;&lt;/li&gt;
&lt;li&gt;ASN.1 BER structure;&lt;/li&gt;
&lt;li&gt;fixed 9-2LE dataset parsing;&lt;/li&gt;
&lt;li&gt;sample-counter continuity;&lt;/li&gt;
&lt;li&gt;waveform reconstruction;&lt;/li&gt;
&lt;li&gt;phase relationships;&lt;/li&gt;
&lt;li&gt;frequency behavior;&lt;/li&gt;
&lt;li&gt;quality metadata.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most important conclusion is not that IEC 61850 is "insecure." That would be an unnecessarily broad claim. The more useful conclusion is that &lt;strong&gt;different parts of the IEC 61850 ecosystem rely on very different trust assumptions&lt;/strong&gt;, and those assumptions need to be examined independently.&lt;/p&gt;




&lt;h2&gt;
  
  
  15. Protocol Integrity Is Not Measurement Integrity
&lt;/h2&gt;

&lt;p&gt;One of the strongest lessons from the research was the need to separate two concepts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protocol integrity
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EtherType
VLAN
ASN.1 / BER structure
ASDU structure
Dataset length
Sequence fields
Quality fields
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Measurement integrity
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Waveform continuity
Amplitude
Frequency
Phase relationship
Channel consistency
Neutral behavior
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A frame can be perfectly valid from a protocol perspective while carrying a measurement that makes no physical sense. The inverse is also true. An unusual waveform does not automatically mean that the packet was maliciously modified. Equipment faults, bad configuration, synchronization problems, sensor problems, and capture artifacts can all produce unusual telemetry.&lt;/p&gt;

&lt;p&gt;That became one of the central principles of this research:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A valid packet is not necessarily a trustworthy measurement.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  16. What I Did Not Claim
&lt;/h2&gt;

&lt;p&gt;There is a temptation in protocol-security research to turn every interesting observation into a vulnerability claim. I deliberately avoided doing that here. The laboratory used simulated IEC 61850 components and controlled packet injection. The GOOSE and SV manipulation experiments demonstrate how the tested environment responded to abnormal traffic. They do not establish that every commercial IED or merging unit will behave the same way. Likewise, the MMS observations apply to the specific &lt;code&gt;libIEC61850&lt;/code&gt; configuration used in the laboratory. The historical &lt;code&gt;Sample240.pcap&lt;/code&gt; establishes a useful analytical baseline, but it does not represent every legitimate 9-2LE waveform. The research therefore treats implementation behavior, protocol behavior, and physical-system behavior as separate layers.&lt;/p&gt;




&lt;h2&gt;
  
  
  17. Why the PCAPs Matter
&lt;/h2&gt;

&lt;p&gt;The PCAP files are not included merely because packet captures look impressive in a research repository. They are the evidence behind the observations.&lt;/p&gt;

&lt;p&gt;For each major phase, the repository contains the corresponding capture alongside the analysis script and screenshots. That makes it possible to move through the research in the same direction I did. The screenshots provide a quick visual record, the PCAPs provide the raw evidence, the scripts provide the experimental mechanism, and the notes explain what the evidence means.&lt;/p&gt;




&lt;h2&gt;
  
  
  18. A Few Lessons From the Laboratory
&lt;/h2&gt;

&lt;p&gt;The most useful lessons from this research were not individual packet fields.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start with the architecture
&lt;/h3&gt;

&lt;p&gt;Jumping directly into packets makes complex protocols harder to understand. The SCL model, communication planes, logical nodes, datasets, and service relationships provide the context required to interpret the packets correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Treat different communication planes differently
&lt;/h3&gt;

&lt;p&gt;MMS, GOOSE, and Sampled Values are not simply three encodings of the same protocol. They operate under different transport and trust assumptions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Capture everything
&lt;/h3&gt;

&lt;p&gt;A terminal output can tell you that something happened, but a PCAP can show exactly what happened on the wire.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visualize telemetry
&lt;/h3&gt;

&lt;p&gt;For Sampled Values in particular, numerical output is not enough, awaveform can reveal a problem that is almost invisible in a packet table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separate implementation behavior from specification behavior
&lt;/h3&gt;

&lt;p&gt;A laboratory implementation can accept something that the specification does not intend, or reject something that a researcher expected to work. Both observations are valuable, but they should not be confused.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preserve the ugly parts of the research
&lt;/h3&gt;

&lt;p&gt;Some of the most useful observations came from things that initially did not work. The MMS connection problems, virtual-interface behavior, parser assumptions, and 9-2LE-specific limitations all became useful once they were understood. A reproducible research record should document those boundaries instead of pretending the experiment was perfectly linear.&lt;/p&gt;




&lt;h2&gt;
  
  
  19. Final Findings
&lt;/h2&gt;

&lt;p&gt;The IEC 61850 laboratory produced several concrete observations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SCL provides valuable structural context for interpreting the operational attack surface.&lt;/li&gt;
&lt;li&gt;The tested MMS configuration allowed session establishment without an authentication boundary.&lt;/li&gt;
&lt;li&gt;The exposed MMS information model could be enumerated and used to identify operational control objects.&lt;/li&gt;
&lt;li&gt;Properly structured control operations could alter simulated control state without authentication being enforced by the tested configuration.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GOOSE operates directly over Layer 2 Ethernet, creating a fundamentally different security model from MMS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GOOSE state and sequencing fields provide important context for subscriber behavior and therefore deserve security monitoring.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Controlled manipulation of GOOSE state and quality information demonstrated the relevance of metadata integrity, not just payload integrity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sampled Value analysis benefits substantially from waveform reconstruction rather than packet-level inspection alone.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;9-2LE's fixed dataset makes lightweight parsing practical, but that assumption must not be generalized to newer IEC 61850 Sampled Value implementations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Protocol validity and measurement plausibility are separate security properties.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Historical conformance-testing practice reinforces the value of PCAP inspection and visualization when validating industrial communication behavior.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The observations are specific to the laboratory implementation and configuration and should not be generalized to all IEC 61850 deployments.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  20. Closing
&lt;/h2&gt;

&lt;p&gt;IEC 61850 turned out to be exactly as difficult as I was warned. Not because the packets are impossible to decode, but because there is much more to the system than the packets. MMS introduces a structured application model. GOOSE introduces Layer 2 event communication and state tracking. Sampled Values introduce continuous telemetry where the meaning of a packet depends partly on what the signal looks like over time. SCL ties much of that behavior back to configuration, and security has to account for all of it.&lt;/p&gt;

&lt;p&gt;The most useful result of the research was therefore not a list of malformed frames or unauthenticated operations. It was a better model of where trust actually exists. And perhaps the most unexpected part was discovering that the same discipline used in modern packet-level security research was already being applied years ago by engineers manually inspecting PCAPs for conformance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Repository
&lt;/h2&gt;

&lt;p&gt;The complete laboratory, research notes, scripts, PCAP captures, and supporting screenshots are available in the &lt;a href="https://github.com/404saint/industrial-protocol-labs/tree/main/iec61850-research" rel="noopener noreferrer"&gt;accompanying repository.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The laboratory was conducted entirely against locally controlled IEC 61850 simulation infrastructure. No production substations, IEDs, merging units, or operational power systems were targeted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acknowledgment
&lt;/h2&gt;

&lt;p&gt;This research was partially informed by historical technical guidance from Bruce Muschlitz, formerly involved in UCA conformance-testing work.&lt;/p&gt;

&lt;p&gt;Bruce provided the historical &lt;code&gt;Template_SV_viewer_rev4p0.xlsm&lt;/code&gt; tool and &lt;code&gt;Sample240.pcap&lt;/code&gt; capture, along with technical context concerning 9-2LE Sampled Values, merging-unit testing, waveform visualization, dataset design, and conformance-test PCAP analysis. The Python parser and visualization workflow used in this research were independently implemented from the supplied material. I am grateful for the opportunity to learn from that history.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>network</category>
      <category>opensource</category>
      <category>architecture</category>
    </item>
    <item>
      <title>IEC 60870-5-104 Security: From APCI State Activation to IEC 62351</title>
      <dc:creator>404Saint</dc:creator>
      <pubDate>Tue, 25 Aug 2026 13:01:44 +0000</pubDate>
      <link>https://dev.to/null_saint/iec-60870-5-104-security-from-apci-state-activation-to-iec-62351-8kn</link>
      <guid>https://dev.to/null_saint/iec-60870-5-104-security-from-apci-state-activation-to-iec-62351-8kn</guid>
      <description>&lt;p&gt;&lt;em&gt;By RUGERO Tesla (&lt;a href="https://github.com/404saint" rel="noopener noreferrer"&gt;@404Saint&lt;/a&gt;).&lt;/em&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A packet-level security study of IEC 60870-5-104, combining protocol architecture, raw APCI/ASDU experimentation, implementation boundary testing, and a controlled comparison against IEC 62351 security mechanisms.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;IEC 60870-5-104 is one of those protocols where understanding the security problem requires going below the usual “port 2404 is exposed” discussion.&lt;/p&gt;

&lt;p&gt;A TCP connection is only the beginning. Before an ASDU is exchanged, the protocol establishes its own link state through APCI control frames. Once data transfer is active, numbered I-frames carry telemetry and commands, while S- and U-frames maintain the session and its state. That structure makes the protocol interesting to study from the wire level.&lt;/p&gt;

&lt;p&gt;For this research, I built an isolated IEC 60870-5-104 laboratory around &lt;code&gt;lib60870-C&lt;/code&gt;, wrote raw Python protocol harnesses rather than relying exclusively on a high-level IEC 104 client, captured the resulting traffic with Wireshark, and then compared the cleartext behavior against a separate TLS/mTLS security laboratory. The objective was not simply to demonstrate that IEC 104 is “insecure.” It was to determine &lt;strong&gt;where the security boundary actually exists at each layer, what the implementation enforces, what it does not enforce, and what changes when security extensions are introduced.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The complete research artifacts, including notes, scripts, PCAPs, and screenshots, are available in the &lt;a href="https://github.com/404saint/industrial-protocol-labs/tree/main/iec104-research" rel="noopener noreferrer"&gt;&lt;code&gt;iec104-research&lt;/code&gt;&lt;/a&gt; directory.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Starting With the Protocol: IEC 104 Before Security
&lt;/h2&gt;

&lt;p&gt;IEC 60870-5-104 extends the IEC 60870-5 telecontrol family over TCP/IP. The protocol architecture separates the TCP transport from the IEC 104 application protocol and its APCI framing. (&lt;a href="https://ecommerce.sist.si/catalog/standards/clc/b6ed7c9e-0feb-4a9f-8a0c-9ce89fb9aa5e/en-60870-5-104-2006" rel="noopener noreferrer"&gt;SIST E-Commerce&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;At the wire level, an IEC 104 APDU begins with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------+--------+-------------------------------+
|  0x68  | Length |       APCI / ASDU             |
+--------+--------+-------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The APCI contains four control octets.&lt;/p&gt;

&lt;p&gt;Those control fields determine whether the frame is an:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;I-format&lt;/strong&gt; frame : application information and ASDU payload&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S-format&lt;/strong&gt; frame : receive acknowledgements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;U-format&lt;/strong&gt; frame : link control such as &lt;code&gt;STARTDT&lt;/code&gt;, &lt;code&gt;STOPDT&lt;/code&gt;, and &lt;code&gt;TESTFR&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The distinction matters because a TCP socket does not automatically mean that IEC 104 application data can be exchanged. The IEC 104 state machine has to be activated first.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Phase 1 : APCI State Activation
&lt;/h2&gt;

&lt;p&gt;The first research phase focused exclusively on the link state.&lt;/p&gt;

&lt;p&gt;The laboratory target was: &lt;code&gt;127.0.0.1:2404&lt;br&gt;
lib60870-C / cs104_server_no_threads&lt;/code&gt;. After establishing TCP connectivity, the session remained in the stopped state.I then transmitted the raw U-format activation frame: &lt;code&gt;68 04 07 00 00 00&lt;/code&gt;, which represents: &lt;code&gt;STARTDT ACT&lt;/code&gt;. The server responded: &lt;code&gt;68 04 0B 00 00 00&lt;/code&gt;, or: &lt;code&gt;STARTDT CON&lt;/code&gt;.  The important observation was not the bytes themselves. It was what &lt;strong&gt;wasn't&lt;/strong&gt; present. There was no identity negotiation, credential exchange, cryptographic challenge, or authentication step between TCP establishment and link activation.&lt;/p&gt;

&lt;p&gt;The transition was effectively:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TCP Connected
      │
      ▼
   STOPPED
      │
      │ STARTDT ACT
      ▼
 STARTDT CON
      │
      ▼
DATA TRANSFER ACTIVE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The base IEC 104 protocol therefore provides the state transition mechanism, but not an authentication boundary.&lt;/p&gt;

&lt;p&gt;The research notes document the complete U-format control set and additional state behavior, including &lt;code&gt;TESTFR&lt;/code&gt; and &lt;code&gt;STOPDT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F97omyudjvadnknt4nx09.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F97omyudjvadnknt4nx09.png" alt="Phase 1 — IEC 104 STARTDT handshake" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. Wire-level observation of the IEC 104 link activation sequence during Phase 1.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This became the baseline for the rest of the investigation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If link activation itself is unauthenticated, what can a client do once the channel reaches the data-transfer state?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3. Phase 2 : Moving From Link State to the ASDU Address Space
&lt;/h2&gt;

&lt;p&gt;Once the link was active, the research moved into the ASDU layer. IEC 104 uses Information Object Addresses (IOAs) together with Common Addresses (CAs) to identify application data. The test harness therefore moved from U-format control frames to I-format ASDUs.&lt;/p&gt;

&lt;p&gt;Several Type IDs were particularly relevant:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type ID&lt;/th&gt;
&lt;th&gt;ASDU&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;11&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;M_ME_NB_1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Measured value, scaled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;45&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;C_SC_NA_1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Single command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;100&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;C_IC_NA_1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;General Interrogation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One of the first things visible in the capture was that the server was not simply responding to individual requests. It was also producing cyclic telemetry.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RX | Type 11 | CA 1 | IOA 110 | 0xcc0300
RX | Type 11 | CA 1 | IOA 110 | 0xcd0300
RX | Type 11 | CA 1 | IOA 110 | 0xce0300
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The payload changed monotonically, providing a simple observable representation of a live counter. The capture also demonstrated why IEC 104 research cannot be implemented as a simplistic &lt;code&gt;send()&lt;/code&gt; → &lt;code&gt;recv()&lt;/code&gt; exchange. Multiple APDUs can exist inside the TCP byte stream.&lt;/p&gt;

&lt;p&gt;A client therefore needs to maintain an application-level stream parser capable of separating:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TCP Stream
    │
    ├── APDU
    ├── APDU
    ├── APDU
    └── APDU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rather than assuming one socket read corresponds to one IEC 104 frame.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc1dws4c6xnhlp5okzqt4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc1dws4c6xnhlp5okzqt4.png" alt="Phase 2 — ASDU and IOA enumeration" width="799" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2. Captured ASDU traffic during telemetry and Information Object Address enumeration.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;An invalid Common Address was also tested. The laboratory server did not terminate the connection when presented with &lt;code&gt;CA: 9999&lt;/code&gt;. Instead, the request produced an IEC 104 response while the transport sequence continued. This was an implementation behavior worth recording because protocol error handling is often just as important as the normal path.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Phase 3 : What Happens When the State Machine Is Abused?
&lt;/h2&gt;

&lt;p&gt;The third phase deliberately moved away from normal protocol operation.&lt;/p&gt;

&lt;p&gt;Three areas were tested:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I-frames before &lt;code&gt;STARTDT&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;I-frames after &lt;code&gt;STOPDT&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Manipulation of &lt;code&gt;N(S)&lt;/code&gt; and &lt;code&gt;N(R)&lt;/code&gt; sequence values&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first result was actually reassuring. An I-format frame sent before link activation caused the server to close the connection. Likewise, after: &lt;code&gt;STOPDT ACT STOPDT CON&lt;/code&gt; attempting to send another I-frame resulted in connection termination rather than continued command processing. So the state machine itself was not completely permissive.&lt;/p&gt;

&lt;p&gt;The implementation distinguished between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STOPPED
    │
    └── I-frame → rejected

STARTED
    │
    └── I-frame → processed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sequence-number behavior was more interesting. IEC 104 uses &lt;code&gt;N(S)&lt;/code&gt; and &lt;code&gt;N(R)&lt;/code&gt; to provide ordered transfer and acknowledgement. The standard specifies sequential progression of these counters. (&lt;a href="https://standards.iteh.ai/catalog/standards/iec/a0f6f64b-a9da-4964-b973-00b7ec19106a/iec-60870-5-104-2006" rel="noopener noreferrer"&gt;iTeh Standards&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;In the laboratory, however, the server accepted experimentally supplied forward jumps such as &lt;code&gt;N(S) = 5000&lt;/code&gt; and &lt;code&gt;N(S) = 32767&lt;/code&gt; without immediately resetting the connection. That does &lt;strong&gt;not&lt;/strong&gt; mean that every IEC 104 implementation will behave this way. It means that this particular &lt;code&gt;lib60870-C&lt;/code&gt; configuration demonstrated tolerance that deserved investigation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The protocol defines sequence semantics. The implementation determines how strictly those semantics are enforced.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Direct Control Execution
&lt;/h3&gt;

&lt;p&gt;The more consequential observation in Phase 3 involved Type ID &lt;code&gt;45&lt;/code&gt;, &lt;code&gt;C_SC_NA_1&lt;/code&gt;. A direct Single Command was transmitted toward: &lt;code&gt;IOA: 5001&lt;/code&gt; with the laboratory actuator configured to represent a breaker-like control point.&lt;/p&gt;

&lt;p&gt;The command was accepted without a preceding Select operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
  │
  │ Type 45 / IOA 5001
  │ Direct Execute
  ▼
RTU
  │
  └── State changed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This demonstrated that the tested configuration did not enforce Select-Before-Operate as a mandatory prerequisite for that control point.&lt;/p&gt;

&lt;p&gt;That finding is configuration and implementation-specific, but its security implication inside the laboratory is straightforward: &lt;strong&gt;network reachability plus knowledge of the addressing scheme was sufficient to reach a control function.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foi94rkezpjpk47399g58.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foi94rkezpjpk47399g58.png" alt="Phase 3 — Attack surface and state testing" width="800" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3. Phase 3 testing of APCI state enforcement, sequence handling, and direct control execution.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Phase 4 : Testing the Parser Boundary
&lt;/h2&gt;

&lt;p&gt;After understanding the normal protocol and control paths, I moved down toward malformed input. The objective was to determine how the implementation behaved when the bytes no longer represented well-formed IEC 104 traffic. Several boundary conditions were tested.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invalid start octet
&lt;/h3&gt;

&lt;p&gt;Instead of: &lt;code&gt;68&lt;/code&gt;, the frame began with: &lt;code&gt;99&lt;/code&gt; . The connection was closed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exaggerated length
&lt;/h3&gt;

&lt;p&gt;A frame advertised a much larger payload than the bytes actually transmitted: &lt;code&gt;68 FF ...&lt;/code&gt;  The server waited for additional stream data until the socket timeout rather than processing an incomplete APDU.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unsupported Type ID
&lt;/h3&gt;

&lt;p&gt;A Type ID of: &lt;code&gt;255&lt;/code&gt; was supplied. The server remained operational and continued transmitting valid telemetry.&lt;/p&gt;

&lt;h3&gt;
  
  
  Truncated ASDU
&lt;/h3&gt;

&lt;p&gt;An incomplete application payload was supplied. Again, the process remained stable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Illegal U-format direction
&lt;/h3&gt;

&lt;p&gt;A client-originated confirmation frame such as &lt;code&gt;STARTDT CON&lt;/code&gt; was tested where an activation request was expected. The server did not treat it as a valid state transition. &lt;/p&gt;

&lt;p&gt;These tests produced an important distinction between &lt;strong&gt;security failure&lt;/strong&gt; and &lt;strong&gt;parser robustness&lt;/strong&gt;.The target did not crash during the malformed-input testing. That is a positive implementation property.&lt;/p&gt;

&lt;p&gt;At the same time, some invalid inputs were silently discarded rather than producing explicit diagnostic responses.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi49ihpiqnutnvx8asqks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi49ihpiqnutnvx8asqks.png" alt="Phase 4 — Boundary testing" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 4. Malformed APCI and ASDU boundary testing against the laboratory implementation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The resulting picture was therefore more nuanced than simply calling the stack “vulnerable.” The implementation showed reasonable structural resilience while simultaneously exposing security weaknesses elsewhere.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. The Security Boundary Was Not at APCI
&lt;/h2&gt;

&lt;p&gt;After four phases, the architecture of the problem became much clearer. The IEC 104 link state machine provided &lt;strong&gt;protocol state&lt;/strong&gt;. It did not provide &lt;strong&gt;identity&lt;/strong&gt;. The ASDU layer provided &lt;strong&gt;application semantics&lt;/strong&gt;. It did not automatically provide &lt;strong&gt;authorization&lt;/strong&gt;. The sequence counters provided &lt;strong&gt;ordering&lt;/strong&gt;. They did not provide &lt;strong&gt;cryptographic authenticity&lt;/strong&gt;. And the parser correctly rejected a number of malformed structures. It still could not distinguish a legitimate master from an unauthorized one based on the base protocol alone. That distinction is critical in OT security.A protocol can be perfectly capable of answering:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Is this frame structurally valid?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;without being capable of answering:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Is this station authorized to send this command?”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  7. Phase 5 : Introducing IEC 62351
&lt;/h2&gt;

&lt;p&gt;The final research phase changed the security model rather than continuing to attack the cleartext implementation. A separate Python laboratory was created around a TLS-wrapped IEC 104 environment.&lt;/p&gt;

&lt;p&gt;The cleartext baseline used: &lt;code&gt;TCP/2404&lt;/code&gt;  while the secure environment used: &lt;code&gt;TCP/19999&lt;/code&gt; with mutual TLS. IEC 62351-3 provides security profiles for TCP/IP-based power-system protocols, including IEC 60870-5-104. IEC 62351-5 specifically addresses security for IEC 60870-5 and derived protocols. (&lt;a href="https://iec61850.dvl.iec.ch/what-is-61850/technical-principles/61850-cybersecurity" rel="noopener noreferrer"&gt;IEC 61850&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The important conceptual change was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CLEAR
------------------------------------------------

TCP
 │
 IEC 104
 │
 APCI
 │
 ASDU
 │
 Control


SECURED
------------------------------------------------

TCP
 │
 TLS / Authentication
 │
 IEC 104
 │
 APCI
 │
 ASDU
 │
 Control
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of trying to make APCI itself become an authentication mechanism, security is introduced around the communication channel and, where applicable, through application-layer security mechanisms. The newer IEC 60870-5-7 technical specification describes the security extensions that apply IEC 62351 mechanisms to IEC 60870-5-101 and IEC 60870-5-104. &lt;/p&gt;




&lt;h2&gt;
  
  
  8. Mutual TLS Changes the First Question
&lt;/h2&gt;

&lt;p&gt;In the cleartext laboratory, the first meaningful exchange was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TCP connection
      ↓
STARTDT ACT
      ↓
STARTDT CON
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the secure laboratory, a client first had to satisfy the TLS authentication boundary.&lt;br&gt;
An unauthenticated client attempting to establish the secure channel received:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[SSL: TLSV13_ALERT_CERTIFICATE_REQUIRED]
tlsv13 alert certificate required
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a fundamentally different failure point. The unauthorized client does not reach IEC 104 link activation. It fails before the application protocol becomes usable. With a valid certificate, the secure channel was established using: &lt;code&gt;TLS_AES_256_GCM_SHA384&lt;/code&gt; and IEC 104 traffic subsequently operated inside that encrypted channel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7ov2avh85oxuf820x15i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7ov2avh85oxuf820x15i.png" alt="Secure IEC 104 client" width="800" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 5. Secure laboratory client validating certificate-based authentication and the protected IEC 104 channel.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdmmrnl3yoezpacvg618l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdmmrnl3yoezpacvg618l.png" alt="Secure IEC 104 server" width="799" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 6. Secure laboratory server enforcing the TLS authentication boundary.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This also changed what passive packet capture could reveal. In the cleartext laboratory, the PCAP contains the actual IEC 104 structures.&lt;/p&gt;

&lt;p&gt;A packet analyst can see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STARTDT
Type ID
CA
IOA
Command values
Telemetry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside TLS, the network observer sees the transport conversation and TLS records rather than the IEC 104 application payload.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Encryption Alone Does Not Solve Authorization
&lt;/h2&gt;

&lt;p&gt;This is where the final phase becomes more interesting. Encryption protects the channel. It does not automatically decide whether an authenticated station should be allowed to operate a particular control point. The secure laboratory therefore added an authorization layer.&lt;/p&gt;

&lt;p&gt;The test client authenticated as: &lt;code&gt;CN=scada-master&lt;/code&gt; and attempted to operate: &lt;code&gt;IOA: 5001&lt;/code&gt;. The request reached the secure server, where the laboratory authorization logic evaluated the role mapping.&lt;/p&gt;

&lt;p&gt;The result was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authenticated Session Established
        │
        ▼
Control Request: IOA 5001
        │
        ▼
Authorization Check
        │
        ├── Not permitted
        ▼
Access Denied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server produced: &lt;code&gt;68 04 47 00 00 00&lt;/code&gt; and the laboratory logged the operation as an RBAC violation. This produced an important contrast with Phase 3.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleartext laboratory
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reach port 2404
      ↓
STARTDT
      ↓
Send Type 45
      ↓
Control executed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Secured laboratory
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Establish TLS
      ↓
Authenticate client
      ↓
Evaluate authorization
      ↓
Control request rejected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two mechanisms therefore address different problems. &lt;strong&gt;Authentication asks who is communicating&lt;/strong&gt; and &lt;strong&gt;Authorization asks what that identity is allowed to do.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;IEC 62351-8 defines the role-based access-control component of the IEC 62351 security family, while IEC 62351-5 provides security mechanisms specifically for IEC 60870-5-derived protocols. (&lt;a href="https://webstore.iec.ch/en/publication/65511" rel="noopener noreferrer"&gt;IEC Webstore&lt;/a&gt;)&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Putting the Research Together
&lt;/h2&gt;

&lt;p&gt;The six research notes can therefore be reduced to a fairly simple security model.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Research Question&lt;/th&gt;
&lt;th&gt;Laboratory Observation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;Can the endpoint be reached?&lt;/td&gt;
&lt;td&gt;TCP/2404 accepted connections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;APCI&lt;/td&gt;
&lt;td&gt;Can the link be activated?&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;STARTDT ACT/CON&lt;/code&gt; without identity verification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ASDU&lt;/td&gt;
&lt;td&gt;What application data exists?&lt;/td&gt;
&lt;td&gt;Telemetry and IOA structures observable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control&lt;/td&gt;
&lt;td&gt;Can commands be issued?&lt;/td&gt;
&lt;td&gt;Direct Type 45 execution observed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;Does the implementation enforce protocol state?&lt;/td&gt;
&lt;td&gt;Yes, for several tested transitions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parser&lt;/td&gt;
&lt;td&gt;Does malformed input crash the process?&lt;/td&gt;
&lt;td&gt;No crashes observed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security extension&lt;/td&gt;
&lt;td&gt;Can communication be authenticated and protected?&lt;/td&gt;
&lt;td&gt;mTLS blocked unauthenticated clients&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authorization&lt;/td&gt;
&lt;td&gt;Can authenticated identities be restricted?&lt;/td&gt;
&lt;td&gt;Laboratory RBAC rejected unauthorized control&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is the part of the research I found most useful. The cleartext implementation was not simply “bad.” It had a functioning protocol state machine. It handled several malformed inputs safely. It maintained sequence counters. It processed telemetry correctly. The problem was that &lt;strong&gt;protocol correctness and security authorization are different properties.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  11. What the Packet Captures Added
&lt;/h2&gt;

&lt;p&gt;The PCAPs were important because the conclusions were not based solely on application output. They provide a second layer of evidence alongside the Python harness output.&lt;/p&gt;

&lt;p&gt;For example, Phase 1 can be reduced to actual bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;68 04 07 00 00 00
68 04 0B 00 00 00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While Phase 3 exposes the I-frame containing the tested command. Phase 5 then provides the contrast: the IEC 104 application payload is no longer directly visible to a passive observer because the communication is encapsulated inside TLS. That progression from raw APCI bytes to encrypted application traffic is really what the entire research series is about.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Building the Laboratory
&lt;/h2&gt;

&lt;p&gt;The cleartext target was built from &lt;code&gt;lib60870-C&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The basic environment was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mz-automation/lib60870.git
&lt;span class="nb"&gt;cd &lt;/span&gt;lib60870/c
&lt;span class="nb"&gt;mkdir &lt;/span&gt;build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;build
cmake ..
make
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server used for the experiments was: &lt;code&gt;cs104_server_no_threads&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A second build layout was also used during the laboratory work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/Desktop/iec104-lab/build
cmake ../lib60870/lib60870-C
make
./examples/cs104_server_no_threads/cs104_server_no_threads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Packet capture was performed directly against the loopback interface.&lt;/p&gt;

&lt;p&gt;For the cleartext target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wireshark &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; lo &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"tcp port 2404"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and for the secure laboratory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wireshark &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; lo &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"tcp port 19999"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The captures were then retained as phase-specific PCAPNG artifacts.&lt;/p&gt;

&lt;p&gt;The secure laboratory also generated its own local CA and server/client certificates, allowing the entire authentication experiment to remain inside the controlled research environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. What I Would Take Away From This Research
&lt;/h2&gt;

&lt;p&gt;There are several conclusions I would carry into an actual IEC 104 security assessment.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Start before the ASDU
&lt;/h3&gt;

&lt;p&gt;Jumping directly into Type IDs and IOAs misses the protocol state machine. &lt;code&gt;STARTDT&lt;/code&gt;, &lt;code&gt;STOPDT&lt;/code&gt;, &lt;code&gt;TESTFR&lt;/code&gt;, and the I/S/U distinction establish the context in which application traffic becomes meaningful.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Separate protocol behavior from implementation behavior
&lt;/h3&gt;

&lt;p&gt;The standard defines the protocol semantics. The implementation decides how strictly those semantics are enforced. The sequence-counter observations and malformed-frame behavior are good examples.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Treat addressing as security-relevant
&lt;/h3&gt;

&lt;p&gt;Once the Common Address and IOA structure becomes known, the application layer becomes considerably easier to reason about. Telemetry enumeration is therefore not merely an informational exercise.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Do not confuse encryption with authorization
&lt;/h3&gt;

&lt;p&gt;TLS prevents passive observers from reading the IEC 104 payload and, with mutual authentication, establishes a cryptographic identity boundary. It does not by itself determine whether an authenticated station should be allowed to operate a particular breaker.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Test the whole path
&lt;/h3&gt;

&lt;p&gt;The useful security boundary was not found in a single packet.&lt;/p&gt;

&lt;p&gt;It emerged across:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TCP
 ↓
APCI
 ↓
ASDU
 ↓
IOA
 ↓
Control Function
 ↓
Authentication
 ↓
Authorization
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is why I structured the research as a sequence rather than a single packet-dissection exercise.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;IEC 60870-5-104 is straightforward to understand once its layers are separated. TCP provides the transport. APCI provides framing, sequencing, acknowledgements, and link-state control. ASDUs provide telemetry and control semantics. But none of those layers, in the baseline laboratory implementation, established the cryptographic identity or authorization boundary required for secure operation.&lt;/p&gt;

&lt;p&gt;That became visible from the first experiment. From there, the research progressed through telemetry enumeration, IOA discovery, direct command execution, sequence manipulation, malformed-frame testing, and finally the introduction of TLS and authorization controls. The result is not a claim that every IEC 104 implementation behaves exactly like the laboratory target. It is a demonstration of &lt;strong&gt;how to interrogate the protocol and its implementation systematically&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Start with the architecture. Move to the wire. Validate the state machine. Map the application layer. Test the boundaries. Then introduce the security controls and repeat the same observations. That helped in distinguishing that IEC 104 uses TCP/2404 and actually understanding &lt;strong&gt;where the protocol's security boundary begins and where it doesn't.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Research Artifacts
&lt;/h2&gt;

&lt;p&gt;The complete laboratory is available in the &lt;a href="https://github.com/404saint/industrial-protocol-labs" rel="noopener noreferrer"&gt;&lt;code&gt;industrial-protocol-labs&lt;/code&gt;&lt;/a&gt; repository.&lt;/p&gt;

&lt;p&gt;The IEC 104 research directory contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iec104-research/
├── certificates/
├── notes/
├── pcaps/
├── screenshots/
└── scripts/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The research notes cover the architecture primer, four cleartext protocol/security investigations, the secure-extension contrast, and the complete laboratory reproduction procedure. All experiments were performed against isolated laboratory systems under my control. The observations above describe the tested &lt;code&gt;lib60870-C&lt;/code&gt; configuration and the accompanying research environment; they should not be interpreted as universal behavior of all IEC 60870-5-104 implementations.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>network</category>
      <category>opensource</category>
      <category>architecture</category>
    </item>
    <item>
      <title>IEC 104 Before the Wire: Understanding Its Architecture, Framing, and Security Boundaries</title>
      <dc:creator>404Saint</dc:creator>
      <pubDate>Thu, 20 Aug 2026 15:57:00 +0000</pubDate>
      <link>https://dev.to/null_saint/iec-104-before-the-wire-understanding-its-architecture-framing-and-security-boundaries-nc5</link>
      <guid>https://dev.to/null_saint/iec-104-before-the-wire-understanding-its-architecture-framing-and-security-boundaries-nc5</guid>
      <description>&lt;p&gt;&lt;em&gt;By RUGERO Tesla (&lt;a href="https://github.com/404saint" rel="noopener noreferrer"&gt;@404Saint&lt;/a&gt;).&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;IEC 60870-5-104 (IEC 104) is the TCP/IP-based member of the IEC 60870-5 telecontrol family. It was designed to carry SCADA telemetry and control information across packet-switched networks, particularly within electrical power systems.&lt;/p&gt;

&lt;p&gt;Before getting into raw packets, it is worth understanding how IEC 104 is structured, how its communication state is maintained, and where its security boundaries actually exist.&lt;/p&gt;

&lt;p&gt;This is the map before we meet the protocol on the wire.&lt;/p&gt;




&lt;h2&gt;
  
  
  Protocol Stack
&lt;/h2&gt;

&lt;p&gt;IEC 104 operates over TCP, commonly using &lt;strong&gt;port 2404&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Two protocol components are particularly important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;APCI&lt;/strong&gt; : Application Protocol Control Information&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ASDU&lt;/strong&gt; : Application Service Data Unit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The APCI handles framing, sequencing, acknowledgments, and connection control.&lt;/p&gt;

&lt;p&gt;The ASDU carries the actual telecontrol information.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------------------------------------------------------------+
| ASDU                                                        |
| Type ID | VSQ | COT | CA | IOA | Information Objects       |
+-------------------------------------------------------------+
| APCI                                                        |
| 0x68 | Length | Control 1 | Control 2 | Control 3 | Ctrl 4 |
+-------------------------------------------------------------+
| TCP / IP                                                    |
+-------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every APDU begins with the &lt;code&gt;0x68&lt;/code&gt; start byte, followed by a length field and four control bytes. The length represents the bytes following the length field, including the four control bytes and, when present, the ASDU. That fixed structure is the starting point for understanding IEC 104 traffic.&lt;/p&gt;




&lt;h2&gt;
  
  
  I, S, and U Formats
&lt;/h2&gt;

&lt;p&gt;IEC 104 defines three APDU formats.&lt;/p&gt;

&lt;h3&gt;
  
  
  I-Format:
&lt;/h3&gt;

&lt;p&gt;→ &lt;strong&gt;I-format&lt;/strong&gt; frames carry application information and therefore contain an ASDU.&lt;/p&gt;

&lt;p&gt;They also carry two sequence numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;N(S)&lt;/code&gt; : send sequence number&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;N(R)&lt;/code&gt; : receive sequence number&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These allow communicating stations to maintain ordered transmission and acknowledgment state.&lt;/p&gt;

&lt;h3&gt;
  
  
  S-Format:
&lt;/h3&gt;

&lt;p&gt;→ &lt;strong&gt;S-format&lt;/strong&gt; frames are supervisory frames.&lt;/p&gt;

&lt;p&gt;They do not carry an ASDU. Their purpose is to communicate receive acknowledgments independently of application data.&lt;/p&gt;

&lt;h3&gt;
  
  
  U-Format:
&lt;/h3&gt;

&lt;p&gt;→ &lt;strong&gt;U-format&lt;/strong&gt; frames handle connection-control functions.&lt;/p&gt;

&lt;p&gt;Three important functions are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STARTDT    Start data transfer
STOPDT     Stop data transfer
TESTFR     Test the communication relationship
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates an important distinction: &lt;strong&gt;A TCP connection is not the same thing as an active IEC 104 data-transfer session.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The TCP connection establishes transport connectivity. IEC 104 then manages its own application-level communication state.&lt;/p&gt;




&lt;h2&gt;
  
  
  Inside the ASDU
&lt;/h2&gt;

&lt;p&gt;When an I-format frame carries application data, the ASDU defines what that data means.&lt;/p&gt;

&lt;p&gt;A simplified ASDU looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------------------------------+
| Type Identification            |
+-------------------------------+
| Variable Structure Qualifier   |
+-------------------------------+
| Cause of Transmission          |
+-------------------------------+
| Common Address                 |
+-------------------------------+
| Information Object             |
|   IOA + Information Elements   |
+-------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✔ Type Identification
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Type ID&lt;/strong&gt; determines the information type being transmitted.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;M_SP_NA_1    Single-point information
C_SC_NA_1    Single command
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✔ Variable Structure Qualifier
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;VSQ&lt;/strong&gt; indicates how many information objects are present and whether their addressing follows a sequential structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✔ Cause of Transmission
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;COT&lt;/strong&gt; provides context for why the ASDU is being transmitted.&lt;/p&gt;

&lt;p&gt;Depending on the message, it can indicate events such as spontaneous transmission, interrogation responses, activation, activation confirmation, or termination.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✔ Common Address and IOA
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Common Address (CA)&lt;/strong&gt; identifies the logical station or ASDU address.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Information Object Address (IOA)&lt;/strong&gt; identifies the individual information object within that address space.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Common Address
      |
      +---- IOA 1
      +---- IOA 2
      +---- IOA 3
      +---- IOA 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Together, these fields provide the addressing model through which IEC 104 identifies telemetry and control points.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where the Security Boundary Actually Sits
&lt;/h2&gt;

&lt;p&gt;The base IEC 104 protocol does not itself provide the cryptographic security properties associated with modern secure application protocols.&lt;/p&gt;

&lt;p&gt;That includes mechanisms such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cryptographic peer authentication&lt;/li&gt;
&lt;li&gt;confidentiality&lt;/li&gt;
&lt;li&gt;cryptographic integrity protection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Historically, this placed significant responsibility on the surrounding network architecture.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             Network Security Boundary

      +---------------------------------------+
      |                                       |
      |       IEC 104 Environment             |
      |                                       |
      | Master &amp;lt;==== TCP/2404 ====&amp;gt; RTU       |
      |                                       |
      +---------------------------------------+
                    ^
                    |
             Firewall / ACL /
             Segmentation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But there is an important qualification here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IEC 104 is not limited to its unsecured base protocol.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The IEC 62351 family defines security mechanisms for power-system communication. &lt;strong&gt;IEC 62351-3:2023&lt;/strong&gt; specifies TLS-based profiles for TCP/IP protocols, including confidentiality, integrity protection, and message-level authentication.&lt;/p&gt;

&lt;p&gt;For protocols based on IEC 60870-5, &lt;strong&gt;IEC 62351-5:2023&lt;/strong&gt; defines an application-profile security mechanism for securing their operation.&lt;/p&gt;

&lt;p&gt;The IEC 60870 family then defines the protocol-specific integration in &lt;strong&gt;IEC TS 60870-5-7:2025&lt;/strong&gt;, which explicitly covers security extensions for IEC 60870-5-101 and &lt;strong&gt;IEC 60870-5-104&lt;/strong&gt;. It describes the message formats and adaptations required to apply IEC 62351 and allows a receiver to verify that an APDU originated from an authorized user and was not modified in transit.&lt;/p&gt;

&lt;p&gt;So the correct security question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What security profile does the deployed implementation support, and is it actually enabled and correctly configured?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Where the Attack Surface Begins
&lt;/h2&gt;

&lt;p&gt;The protocol architecture raises several questions worth investigating.&lt;/p&gt;

&lt;h3&gt;
  
  
  ☐ Session State
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;STARTDT&lt;/code&gt; and &lt;code&gt;STOPDT&lt;/code&gt; control the IEC 104 data-transfer state. That makes session-state handling an obvious area for testing. How does an implementation react to unexpected state-control messages? Does it ignore them, reject them, or terminate the connection?&lt;/p&gt;

&lt;h3&gt;
  
  
  ☐ Sequence State
&lt;/h3&gt;

&lt;p&gt;I-format communication depends on synchronized &lt;code&gt;N(S)&lt;/code&gt; and &lt;code&gt;N(R)&lt;/code&gt; values. That raises questions around duplicate, stale, replayed, or otherwise unexpected sequence information. Again, the interesting part is the implementation's response.&lt;/p&gt;

&lt;h3&gt;
  
  
  ☐ Application Commands
&lt;/h3&gt;

&lt;p&gt;The ASDU structure provides another research surface. If an unauthorized system can reach an IEC 104 endpoint, can it construct a syntactically valid command? Can valid Common Addresses and IOAs be identified? And what controls exist between receiving a command and actually executing it? These are implementation questions.&lt;/p&gt;

&lt;p&gt;The protocol specification tells us where to look but the lab tells us what actually happens.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security Characteristics
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Characteristic&lt;/th&gt;
&lt;th&gt;Security Relevance&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TCP/2404&lt;/td&gt;
&lt;td&gt;Network reachability exposes the protocol endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stateful I/S/U communication&lt;/td&gt;
&lt;td&gt;Session behavior becomes an attack surface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sequence numbers&lt;/td&gt;
&lt;td&gt;Unexpected sequence state warrants investigation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structured ASDUs&lt;/td&gt;
&lt;td&gt;Application messages follow defined semantics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CA / IOA addressing&lt;/td&gt;
&lt;td&gt;Target identification becomes important&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Base IEC 104&lt;/td&gt;
&lt;td&gt;No native cryptographic security in the core protocol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IEC 62351 extensions&lt;/td&gt;
&lt;td&gt;Security mechanisms can be applied to IEC 104 deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last two rows are deliberately separated. A security assessment of IEC 104 should distinguish between the &lt;strong&gt;base protocol&lt;/strong&gt; and the &lt;strong&gt;security mechanisms implemented around it&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  From Architecture to Packets
&lt;/h2&gt;

&lt;p&gt;The basic model is now clear:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TCP
 |
 +-- APCI
 |    |
 |    +-- I-Format
 |    +-- S-Format
 |    +-- U-Format
 |
 +-- ASDU
      |
      +-- Type ID
      +-- VSQ
      +-- COT
      +-- Common Address
      +-- IOA
      +-- Information
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives us the vocabulary needed for the next phase. The specification tells us what the fields and state machines are supposed to mean. The next step is to find out what they actually look like on the wire.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next: IEC 104 packet-level analysis: raw frame construction, state transitions, PCAP dissection, and implementation behavior.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>network</category>
      <category>opensource</category>
      <category>architecture</category>
    </item>
    <item>
      <title>I Started With One OPC UA `GetEndpoints` Request. Then I Found Anonymous Writes.</title>
      <dc:creator>404Saint</dc:creator>
      <pubDate>Tue, 11 Aug 2026 15:54:11 +0000</pubDate>
      <link>https://dev.to/null_saint/i-started-with-one-opc-ua-getendpoints-request-then-i-found-anonymous-writes-1ika</link>
      <guid>https://dev.to/null_saint/i-started-with-one-opc-ua-getendpoints-request-then-i-found-anonymous-writes-1ika</guid>
      <description>&lt;p&gt;&lt;em&gt;By RUGERO Tesla (&lt;a href="https://github.com/404saint" rel="noopener noreferrer"&gt;@404Saint&lt;/a&gt;).&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[*] Target Server: opc.tcp://127.0.0.1:4840
[*] Sending unauthenticated GetEndpointsRequest...
[+] Received 5 exposed endpoints.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five endpoints from a single unauthenticated &lt;code&gt;GetEndpointsRequest&lt;/code&gt;. That was the starting point for this OPC UA laboratory. The response advertised four cryptographically protected configurations and one endpoint using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SecurityMode:   None
SecurityPolicy: None
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server was therefore not enforcing a single security posture at the discovery layer. A client could select the unprotected endpoint even though &lt;code&gt;Sign&lt;/code&gt; and &lt;code&gt;SignAndEncrypt&lt;/code&gt; configurations were available.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp7ve5gbzb7gu4ay7yafe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp7ve5gbzb7gu4ay7yafe.png" alt="OPC UA endpoint discovery" width="783" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Endpoint discovery showing the five configurations advertised by the laboratory server.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That observation alone was not treated as a downgrade vulnerability. No active downgrade attack was performed. The finding was simpler: &lt;strong&gt;an unencrypted endpoint was explicitly available for client selection.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The next step was to use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  From &lt;code&gt;GetEndpoints&lt;/code&gt; to &lt;code&gt;ActivateSession&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;OPC UA establishes application sessions through a defined sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OpenSecureChannel
        ↓
CreateSession
        ↓
ActivateSession
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the &lt;code&gt;SecurityPolicy=None&lt;/code&gt; endpoint, an anonymous identity token was supplied during &lt;code&gt;ActivateSession&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The server accepted the session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[+] SecureChannel established
[+] Session activation succeeded

SecureChannel State: OPEN (SecurityPolicy#None)
Session State:       ACTIVE
Authentication Type: Anonymous
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnxe82xvfkwgwn7e8ohiz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnxe82xvfkwgwn7e8ohiz.png" alt="OPC UA anonymous session handshake" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Successful anonymous session establishment over the &lt;code&gt;SecurityPolicy=None&lt;/code&gt; endpoint.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At this point the scope of the test changed. Endpoint discovery had established insecure exposure. Session establishment demonstrated that the exposed endpoint was actually usable without an authenticated identity. The address space was the next layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Walking the Address Space
&lt;/h2&gt;

&lt;p&gt;OPC UA does not present an industrial control environment as a flat register table.&lt;/p&gt;

&lt;p&gt;Its application model is an address space containing Objects, Variables, Methods, Properties, References, and associated metadata.&lt;/p&gt;

&lt;p&gt;A recursive traversal was therefore used to enumerate the Object graph and inspect the variables reachable through the anonymous session.&lt;/p&gt;

&lt;p&gt;The crawler followed child references and collected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browse Name
NodeId
Node Class
AccessLevel
UserAccessLevel
Current Value
Read Result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Relative browse-path resolution was also tested against the standard Server object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0:Objects
 └── 0:Server
      └── 0:ServerStatus
           └── 0:CurrentTime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which resolved to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ns=0;i=2258
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The traversal itself was not the interesting finding.&lt;/p&gt;

&lt;p&gt;Most of the address space did &lt;strong&gt;not&lt;/strong&gt; provide unrestricted access. Several nodes rejected reads or returned authorization-related errors.&lt;/p&gt;

&lt;p&gt;That distinction became important when evaluating write permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  AccessLevel Wasn't the Final Verdict
&lt;/h2&gt;

&lt;p&gt;The audit did not classify a variable as writable solely because an attribute suggested that it was. Readable variables were tested with actual &lt;code&gt;WriteRequest&lt;/code&gt; operations.&lt;/p&gt;

&lt;p&gt;The mutations were deliberately small and reversible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Boolean
False → True

integer
0 → 1

Double
0.0 → 1.0

Int64
0 → 1

example bytestring
b'test123' → b'test123_probe'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each successful mutation was immediately restored to its original value.&lt;/p&gt;

&lt;p&gt;The result was unambiguous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[CRITICAL VULNERABILITY] Write ACCEPTED on tag Boolean!
Value changed: False -&amp;gt; True

[CRITICAL VULNERABILITY] Write ACCEPTED on tag integer!
Value changed: 0 -&amp;gt; 1

[CRITICAL VULNERABILITY] Write ACCEPTED on tag Double!
Value changed: 0.0 -&amp;gt; 1.0

[CRITICAL VULNERABILITY] Write ACCEPTED on tag Int64!
Value changed: 0 -&amp;gt; 1

[CRITICAL VULNERABILITY] Write ACCEPTED on tag example bytestring!
Value changed: b'test123' -&amp;gt; b'test123_probe'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc2gy1pff8uq7hc0hj18k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc2gy1pff8uq7hc0hj18k.png" alt="OPC UA address-space audit" width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Recursive address-space enumeration and access-level inspection from the anonymous session.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2pxgu5prno9ltimw0y9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2pxgu5prno9ltimw0y9s.png" alt="OPC UA write authorization testing" width="800" height="86"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Successful write mutations against five tested variables. Every mutation was reverted after verification.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is stronger evidence than simply reporting an exposed &lt;code&gt;AccessLevel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The server received the write request, accepted it, value changed and the original value was then restored. The finding is therefore specific to &lt;strong&gt;effective write authorization&lt;/strong&gt;: the anonymous session was able to modify the tested variables without presenting an authenticated user identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Security Boundaries Were Not Universally Broken
&lt;/h2&gt;

&lt;p&gt;The write finding made it important to test the other security controls rather than assume that everything was misconfigured.&lt;/p&gt;

&lt;p&gt;The endpoint configuration exposed &lt;code&gt;SecurityPolicy=None&lt;/code&gt;, but protected configurations were also available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SignAndEncrypt     Aes128_Sha256_RsaOaep
Sign               Aes128_Sha256_RsaOaep
SignAndEncrypt     Basic256Sha256
Sign               Basic256Sha256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An independent X.509 certificate was then generated for the certificate validation test.&lt;/p&gt;

&lt;p&gt;It was deliberately unrelated to the earlier test certificate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Subject:
CN=independent-untrusted-client

Issuer:
CN=independent-untrusted-client

Self-Signed:
True

Application URI:
urn:freeopcua:client:independent-test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The protected connection attempt used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Basic256Sha256
SignAndEncrypt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server rejected the certificate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BadCertificateUriInvalid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsp6l0l4gkxn7vlqkvsxt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsp6l0l4gkxn7vlqkvsxt.png" alt="OPC UA X.509 trust boundary test" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Independent self-signed certificate rejected during the protected OPC UA handshake.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Invalid username/password authentication was also rejected with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BadUserAccessDenied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting security picture was therefore more nuanced than a simple “OPC UA authentication is broken” conclusion.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Security Boundary&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;SecurityPolicy=None&lt;/code&gt; advertised&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Insecure exposure&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anonymous session&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Accepted&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Address-space traversal&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Extensive visibility&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tested anonymous writes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Accepted&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invalid credentials&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Rejected&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Independent X.509 certificate&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Rejected&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protected endpoints&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Available&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The significant finding was the combination of an anonymously accessible session and effective write permissions on the tested variables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeing the Protocol Instead of Just the API
&lt;/h2&gt;

&lt;p&gt;The packet captures were an important part of the investigation.&lt;/p&gt;

&lt;p&gt;For example, the username authentication test produced an &lt;code&gt;ActivateSessionRequest&lt;/code&gt; containing a &lt;code&gt;UserNameIdentityToken&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UserName: admin
Password: ...
EncryptionAlgorithm:
    http://www.w3.org/2001/04/xmlenc#rsa-oaep
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client reported &lt;code&gt;BadUserAccessDenied&lt;/code&gt;, but the PCAP showed how the identity token was actually represented on the wire. That distinction matters in protocol research. A high-level library can tell you that an operation succeeded or failed but a packet capture can show the actual message, security mode, token type, request structure, and server response.&lt;/p&gt;

&lt;p&gt;The laboratory therefore combined both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Python harness
      +
OPC UA server
      +
Wireshark / PCAP
      +
manual protocol inspection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than treating the client library as the protocol itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Research Repository
&lt;/h2&gt;

&lt;p&gt;The complete laboratory is available in the repository:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/404saint/industrial-protocol-labs/tree/main/opcua-research" rel="noopener noreferrer"&gt;industrial-protocol-labs / opcua-research&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It contains the phase-by-phase notes, Python research harnesses, packet captures, screenshots, logs, and reproduction instructions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;opcua-research/
├── captures/
├── logs/
├── notes/
├── screenshots/
└── scripts/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The detailed notes cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OPC UA architecture and security model&lt;/li&gt;
&lt;li&gt;Endpoint discovery&lt;/li&gt;
&lt;li&gt;SecureChannel and session establishment&lt;/li&gt;
&lt;li&gt;Address-space traversal&lt;/li&gt;
&lt;li&gt;Access-level auditing&lt;/li&gt;
&lt;li&gt;Effective write authorization&lt;/li&gt;
&lt;li&gt;X.509 trust-boundary testing&lt;/li&gt;
&lt;li&gt;Laboratory reproduction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interesting part of this research was not any individual API call. It was following the protocol far enough to determine what an apparently ordinary anonymous session could actually do.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GetEndpoints&lt;/code&gt; was only the first request.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ics</category>
      <category>ot</category>
      <category>scada</category>
    </item>
    <item>
      <title>Building BACnet from Raw Packets: Objects, Priority Arrays, BBMDs, and Event Services</title>
      <dc:creator>404Saint</dc:creator>
      <pubDate>Tue, 04 Aug 2026 15:41:40 +0000</pubDate>
      <link>https://dev.to/null_saint/building-bacnet-from-raw-packets-objects-priority-arrays-bbmds-and-event-services-3bml</link>
      <guid>https://dev.to/null_saint/building-bacnet-from-raw-packets-objects-priority-arrays-bbmds-and-event-services-3bml</guid>
      <description>&lt;p&gt;By RUGERO Tesla (&lt;a href="https:/github.com/404saint" rel="noopener noreferrer"&gt;@404saint&lt;/a&gt;) &lt;/p&gt;

&lt;p&gt;Industrial communication protocols reveal their design philosophy through the way they represent operational state.&lt;/p&gt;

&lt;p&gt;Modbus exposes memory through coils and registers.&lt;/p&gt;

&lt;p&gt;EtherNet/IP builds on the Common Industrial Protocol (CIP), organizing devices into object classes and explicit messaging services.&lt;/p&gt;

&lt;p&gt;BACnet approaches automation from an entirely different direction. Rather than treating field devices as collections of registers, it models an automation system as a distributed object database in which sensors, actuators, schedules, alarms, and configuration elements are represented as standardized objects with well-defined properties.&lt;/p&gt;

&lt;p&gt;That architectural decision fundamentally changes how clients interact with a BACnet device. Reading process information no longer involves polling register offsets; instead, application services operate directly against object instances and individual properties. Command execution is governed by priority arbitration, network communication depends on the BACnet Virtual Link Layer (BVLL), and many state changes are distributed through event-driven notifications rather than continuous polling.&lt;/p&gt;

&lt;p&gt;This article documents a packet-level analysis of those mechanisms using an isolated BACnet/IP laboratory built around the open-source &lt;strong&gt;bacnet-stack&lt;/strong&gt; implementation. Instead of relying on high-level BACnet libraries, each experiment manually constructed BVLL, NPDU, and APDU structures in Python to observe how the target implementation processed protocol requests under controlled conditions.&lt;/p&gt;

&lt;p&gt;The research was divided into four phases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Object Model &amp;amp; Property Engine&lt;/li&gt;
&lt;li&gt;Priority Array Arbitration&lt;/li&gt;
&lt;li&gt;Routing, BBMD &amp;amp; Subnet Traversal&lt;/li&gt;
&lt;li&gt;Change-of-Value (COV), Event Services &amp;amp; BACnet/SC&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Research Environment
&lt;/h2&gt;

&lt;p&gt;The target consisted of a locally compiled &lt;strong&gt;bacnet-stack&lt;/strong&gt; server exposing a virtual BACnet/IP device over UDP port &lt;strong&gt;47808 (0xBAC0)&lt;/strong&gt;. All experiments were executed from a separate Python research harness that manually assembled protocol frames before transmitting them to the server.&lt;/p&gt;

&lt;p&gt;Unlike production assessments, the objective was not vulnerability discovery. Instead, the laboratory provided an instrumented environment for observing protocol behavior, validating packet construction, and documenting implementation responses to both expected and unusual requests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 1 — Inspecting the BACnet Object Database
&lt;/h2&gt;

&lt;p&gt;BACnet devices organize operational state into standardized objects. Every physical or logical component of the automation system is represented as an object containing a collection of named properties.&lt;/p&gt;

&lt;p&gt;Rather than beginning with write operations, the first phase focused on understanding the controller's exposed object model.&lt;/p&gt;

&lt;p&gt;The initial experiment queried the &lt;strong&gt;Object_List&lt;/strong&gt; property of the Device object. The response returned 76 object instances, including standard BACnet object classes such as Analog Inputs, Analog Outputs, Binary Inputs, Binary Outputs, and several vendor-defined object types.&lt;/p&gt;

&lt;p&gt;Subsequent experiments exercised additional application services including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ReadProperty&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ReadPropertyMultiple&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CreateObject&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DeleteObject&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Extended property identifiers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One observation recorded during testing was that the target accepted an unauthenticated &lt;code&gt;CreateObject&lt;/code&gt; request while rejecting unsupported property identifiers and protected deletion attempts using standard BACnet Error-PDUs. The implementation remained stable throughout the assessment and returned protocol-compliant error responses instead of terminating the connection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnormy21xtrvranvrgh1q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnormy21xtrvranvrgh1q.png" alt="Figure 1. Phase 1 research harness demonstrating object enumeration, multi-property inspection, dynamic object creation, invalid property handling, and protected object deletion against the laboratory BACnet/IP target." width="687" height="674"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. Phase 1 research harness demonstrating object enumeration, multi-property inspection, dynamic object creation, invalid property handling, and protected object deletion against the laboratory BACnet/IP target.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The experiments established the structure of the target's internal object database before moving to command execution behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 2 — Understanding Priority Array Arbitration
&lt;/h2&gt;

&lt;p&gt;One of BACnet's most distinctive features is the &lt;strong&gt;Priority Array&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Unlike protocols where writing a value immediately replaces the previous state, commandable BACnet objects maintain sixteen independent priority slots. The object's effective value is determined by the highest-priority non-NULL entry within the array.&lt;/p&gt;

&lt;p&gt;To examine this behavior, the research harness issued successive &lt;code&gt;WriteProperty&lt;/code&gt; requests targeting different priority levels before reading back the resulting Priority Array.&lt;/p&gt;

&lt;p&gt;The experiments included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Writing at Schedule priority (Slot 16)&lt;/li&gt;
&lt;li&gt;Writing at Manual Life Safety (Slot 1)&lt;/li&gt;
&lt;li&gt;Writing at Manual Operator (Slot 8)&lt;/li&gt;
&lt;li&gt;Relinquishing control using NULL&lt;/li&gt;
&lt;li&gt;Dumping the complete Priority Array&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server acknowledged each write request with a SimpleACK, while subsequent reads demonstrated how the implementation stored and evaluated priority values.&lt;/p&gt;

&lt;p&gt;During testing, one implementation-specific observation stood out. Although a write targeted Priority Slot 8, the value later appeared in Slot 12 when the Priority Array was retrieved. Because this behavior was observed only within the laboratory implementation, no broader conclusions were drawn regarding BACnet itself. The result is simply documented as an implementation observation requiring additional investigation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffep510y1bxcugjd57rej.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffep510y1bxcugjd57rej.png" alt="Figure 2. Priority Array assessment showing writes across multiple priority levels, NULL relinquishment, and the resulting Priority_Array returned by the laboratory target." width="635" height="719"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2. Priority Array assessment showing writes across multiple priority levels, NULL relinquishment, and the resulting Priority_Array returned by the laboratory target.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Beyond illustrating BACnet's arbitration model, the experiments highlighted how command execution depends on protocol semantics rather than simply accepting the most recent write.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 3 — Routing, BBMDs, and Network Infrastructure
&lt;/h2&gt;

&lt;p&gt;BACnet/IP extends beyond application services by introducing the &lt;strong&gt;BACnet Virtual Link Layer (BVLL)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;BVLL enables communication across routed IP networks through Broadcast Management Devices (BBMDs), Foreign Device Registration, and BACnet network-layer routing services.&lt;/p&gt;

&lt;p&gt;Three infrastructure mechanisms were examined:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Foreign Device Registration&lt;/li&gt;
&lt;li&gt;Who-Is-Router-To-Network&lt;/li&gt;
&lt;li&gt;Global Who-Is broadcasts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The target acknowledged a &lt;code&gt;Register-Foreign-Device&lt;/code&gt; request using a successful BVLL Result response, indicating acceptance of the registration message at the protocol layer.&lt;/p&gt;

&lt;p&gt;Router discovery requests produced no &lt;code&gt;I-Am-Router-To-Network&lt;/code&gt; responses during the observation period, and global broadcast discovery did not identify additional BACnet devices within the laboratory environment.&lt;/p&gt;

&lt;p&gt;These observations are consistent with the isolated topology used during testing and should not be interpreted as representative of production BACnet deployments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fch8w4fveeahqqjkl622f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fch8w4fveeahqqjkl622f.png" alt="Figure 3. BBMD and network infrastructure assessment demonstrating Foreign Device Registration, router discovery, and broadcast-based topology observations." width="658" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3. BBMD and network infrastructure assessment demonstrating Foreign Device Registration, router discovery, and broadcast-based topology observations.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This phase reinforced the distinction between BACnet's application layer and the additional networking infrastructure required to operate across routed environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 4 — Event Services, COV, and BACnet/SC
&lt;/h2&gt;

&lt;p&gt;The final phase examined BACnet's event-driven communication model.&lt;/p&gt;

&lt;p&gt;Unlike protocols that depend exclusively on polling, BACnet allows clients to subscribe for &lt;strong&gt;Change of Value (COV)&lt;/strong&gt; notifications. Once subscribed, the server can transmit state updates whenever monitored values change.&lt;/p&gt;

&lt;p&gt;The research harness manually constructed a &lt;code&gt;SubscribeCOV&lt;/code&gt; request targeting an Analog Input object.&lt;/p&gt;

&lt;p&gt;The target acknowledged the subscription using a SimpleACK before transmitting an &lt;strong&gt;Unconfirmed COV Notification&lt;/strong&gt;, confirming that the subscription had been accepted and event delivery was operational.&lt;/p&gt;

&lt;p&gt;The second experiment focused on the &lt;strong&gt;Event_Enable&lt;/strong&gt; property.&lt;/p&gt;

&lt;p&gt;Rather than issuing a write operation in isolation, the property was read, modified, and read again to verify that the server stored the updated Bit String exactly as transmitted. This confirmed successful property modification within the laboratory implementation.&lt;/p&gt;

&lt;p&gt;The final experiment probed TCP port &lt;strong&gt;47809&lt;/strong&gt;, the standard transport used by BACnet Secure Connect (BACnet/SC), by attempting a WebSocket handshake. The connection was refused, indicating that the laboratory server exposed only traditional BACnet/IP services during testing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fogk2m12lb5ax0jbpj4t2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fogk2m12lb5ax0jbpj4t2.png" alt="Figure 4. Event-service analysis demonstrating SubscribeCOV acknowledgement, Event_Enable verification before and after modification, and BACnet/SC transport probing." width="799" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 4. Event-service analysis demonstrating SubscribeCOV acknowledgement, Event_Enable verification before and after modification, and BACnet/SC transport probing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Together, these experiments completed the examination of BACnet's application services, event mechanisms, and secure transport transition.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Observations
&lt;/h2&gt;

&lt;p&gt;Across the four research phases, several implementation characteristics were consistently observed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The target exposed its object database through standard property services.&lt;/li&gt;
&lt;li&gt;Priority arbitration followed BACnet's multi-slot command model rather than simple overwrite semantics.&lt;/li&gt;
&lt;li&gt;Foreign Device Registration requests were acknowledged at the BVLL layer.&lt;/li&gt;
&lt;li&gt;Router discovery and global broadcast enumeration produced no observable responses within the isolated laboratory.&lt;/li&gt;
&lt;li&gt;SubscribeCOV successfully generated event notifications.&lt;/li&gt;
&lt;li&gt;Event_Enable property modifications were reflected in subsequent reads.&lt;/li&gt;
&lt;li&gt;No BACnet/SC listener was present on the expected secure transport port.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these observations should be generalized beyond the laboratory implementation. They describe the behavior recorded during controlled testing and serve as a foundation for understanding BACnet's protocol mechanics rather than evaluating commercial products.&lt;/p&gt;




&lt;h2&gt;
  
  
  Repository
&lt;/h2&gt;

&lt;p&gt;The complete research repository contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Protocol architecture primer&lt;/li&gt;
&lt;li&gt;Four packet-level research phases&lt;/li&gt;
&lt;li&gt;Laboratory reproduction guide&lt;/li&gt;
&lt;li&gt;Python research harnesses&lt;/li&gt;
&lt;li&gt;Network diagrams&lt;/li&gt;
&lt;li&gt;Terminal captures&lt;/li&gt;
&lt;li&gt;Detailed implementation observations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/404saint/industrial-protocol-labs/tree/main/bacnet-research" rel="noopener noreferrer"&gt;https://github.com/404saint/industrial-protocol-labs/tree/main/bacnet-research&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing Remarks
&lt;/h2&gt;

&lt;p&gt;BACnet is often introduced as "the building automation protocol," but that description overlooks much of what makes it technically interesting. Its object-oriented data model, priority-based command arbitration, network-layer routing infrastructure, and event-driven services distinguish it from many traditional industrial protocols.&lt;/p&gt;

&lt;p&gt;Constructing protocol frames manually made those design decisions significantly clearer than interacting through abstraction libraries alone. More importantly, documenting the observed behavior, whether expected or implementation-specific, provides a reproducible reference for engineers and security researchers studying BACnet at the packet level.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; All experiments described in this article were conducted within an isolated laboratory environment using controlled BACnet/IP implementations. The work is intended solely for protocol education, interoperability research, and defensive security analysis. Do not perform similar testing against systems without explicit authorization.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>security</category>
      <category>cybersecurity</category>
      <category>python</category>
      <category>network</category>
    </item>
    <item>
      <title>Hands-On DNP3 Protocol Analysis: State Machines, Transport Mechanics, and Secure Authentication (SA v5)</title>
      <dc:creator>404Saint</dc:creator>
      <pubDate>Sun, 02 Aug 2026 14:58:13 +0000</pubDate>
      <link>https://dev.to/null_saint/hands-on-dnp3-protocol-analysis-state-machines-transport-mechanics-and-secure-authentication-sa-25pn</link>
      <guid>https://dev.to/null_saint/hands-on-dnp3-protocol-analysis-state-machines-transport-mechanics-and-secure-authentication-sa-25pn</guid>
      <description>&lt;p&gt;By RUGERO Tesla (&lt;a href="https:/github.com/404saint" rel="noopener noreferrer"&gt;@404saint&lt;/a&gt;) &lt;/p&gt;

&lt;p&gt;In my previous article, &lt;strong&gt;Beyond Cyclic Polling: Objects, Classes, and Outstation State in DNP3&lt;/strong&gt;, we explored how DNP3 (IEEE 1815) organizes information through object groups, event classes, and Internal Indications (IIN). That discussion focused on the protocol's architecture and how an outstation exposes operational state to a SCADA master.&lt;/p&gt;

&lt;p&gt;Understanding the specification, however, is only half of the story.&lt;/p&gt;

&lt;p&gt;The more interesting question is what happens &lt;strong&gt;inside an implementation&lt;/strong&gt; when frames begin arriving on the wire. How does an outstation process malformed transport fragments? What state transitions occur when control commands arrive out of sequence? How are administrative function codes handled internally? What actually changes when Secure Authentication (SA v5) is introduced?&lt;/p&gt;

&lt;p&gt;To answer those questions, I built an isolated DNP3 research laboratory entirely in Python. Rather than relying on vendor implementations or third-party protocol libraries, I implemented custom outstation targets, protocol parsers, and master-side test harnesses to observe protocol behavior from the raw socket layer upward. This made it possible to inspect state transitions, reproduce protocol edge cases, and verify implementation behavior under controlled conditions.&lt;/p&gt;

&lt;p&gt;The repository contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A custom DNP3 outstation implementation&lt;/li&gt;
&lt;li&gt;A Secure Authentication (SA v5) enabled outstation&lt;/li&gt;
&lt;li&gt;Individual master-side test suites for each research phase&lt;/li&gt;
&lt;li&gt;Packet captures for every experiment&lt;/li&gt;
&lt;li&gt;Detailed technical notes documenting protocol behavior and implementation observations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can explore the complete project here:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GitHub Repository&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/404saint/industrial-protocol-labs/tree/main/dnp3-research" rel="noopener noreferrer"&gt;https://github.com/404saint/industrial-protocol-labs/tree/main/dnp3-research&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article summarizes five phases of that research:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reconnaissance and protocol fingerprinting&lt;/li&gt;
&lt;li&gt;Control execution and Select-Before-Operate validation&lt;/li&gt;
&lt;li&gt;Administrative state transitions&lt;/li&gt;
&lt;li&gt;Transport-layer behavior and unsolicited messaging&lt;/li&gt;
&lt;li&gt;Secure Authentication (SA v5)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Phase 1 — Reconnaissance &amp;amp; Internal Indication Analysis
&lt;/h2&gt;

&lt;p&gt;Before attempting any control operations, it is useful to understand what information a DNP3 endpoint exposes during normal communication.&lt;/p&gt;

&lt;p&gt;Unlike many industrial protocols that require several exchanges before meaningful information becomes available, a standard Class 0 Integrity Read (&lt;code&gt;FC 0x01&lt;/code&gt;) immediately returns an Application Layer response containing the &lt;strong&gt;Internal Indications (IIN)&lt;/strong&gt; field.&lt;/p&gt;

&lt;p&gt;Those two bytes provide a surprisingly useful snapshot of the outstation's operational state.&lt;/p&gt;

&lt;p&gt;Depending on the implementation, the response may reveal whether:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the device has recently restarted,&lt;/li&gt;
&lt;li&gt;time synchronization is required,&lt;/li&gt;
&lt;li&gt;event buffers contain pending data,&lt;/li&gt;
&lt;li&gt;hardware faults have been detected,&lt;/li&gt;
&lt;li&gt;unsupported function codes were received,&lt;/li&gt;
&lt;li&gt;parameter validation has failed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the IIN field is part of normal protocol behavior, it represents one of the earliest opportunities to understand how an outstation is currently operating.&lt;/p&gt;

&lt;p&gt;The first phase of the lab simply performs a baseline integrity poll and parses those response flags.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffv9v46ke3qvv9fvlwmuq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffv9v46ke3qvv9fvlwmuq.png" alt="Running the reconnaissance suite against the simulated outstation." width="731" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. &lt;code&gt;recon_enum.py&lt;/code&gt; issuing a Class 0 Integrity Read and decoding the returned Internal Indications.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On the opposite side of the connection, the custom outstation processes the incoming FT3 frame, builds the Application Layer response, and returns the corresponding IIN values.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0eutebyol0edf7nmz856.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0eutebyol0edf7nmz856.png" alt="Outstation processing an incoming reconnaissance request." width="639" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2. The custom outstation parsing the request and constructing the response APDU.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One observation from this phase is the persistence of the &lt;strong&gt;Device Restart&lt;/strong&gt; indication (&lt;code&gt;IIN1.7&lt;/code&gt;). Following initialization, this flag remains asserted until the master acknowledges the restart condition. Although simple, this mechanism allows supervisory systems to distinguish between continuous operation and a recently restarted device without relying on external monitoring.&lt;/p&gt;

&lt;p&gt;This phase also establishes an important baseline for later experiments. Once administrative commands, transport anomalies, and authentication mechanisms are introduced, changes in the IIN field become an effective way to observe how internal state evolves throughout the protocol.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 2 — Control Execution &amp;amp; Select-Before-Operate Validation
&lt;/h2&gt;

&lt;p&gt;One of DNP3's defining characteristics is its emphasis on deterministic control execution.&lt;/p&gt;

&lt;p&gt;Rather than immediately actuating field devices whenever a command arrives, IEEE 1815 defines a &lt;strong&gt;Select-Before-Operate (SBO)&lt;/strong&gt; workflow for Control Relay Output Blocks (CROB). The design intentionally separates validation from execution, reducing the likelihood of accidental operations caused by communication errors or malformed traffic.&lt;/p&gt;

&lt;p&gt;Under the SBO model, a control operation normally follows two stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Select (&lt;code&gt;FC 0x03&lt;/code&gt;)&lt;/strong&gt; : The master requests control of a specific output point. The outstation validates the request and temporarily reserves that point.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Operate (&lt;code&gt;FC 0x04&lt;/code&gt;)&lt;/strong&gt; : If the reservation remains valid, the outstation executes the requested operation and clears the reservation state.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The protocol also defines &lt;strong&gt;Direct Operate (&lt;code&gt;FC 0x05&lt;/code&gt;)&lt;/strong&gt;, which bypasses the reservation phase entirely and requests immediate execution.&lt;/p&gt;

&lt;p&gt;To understand how the implementation enforced these different execution paths, I evaluated two scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;issuing a Direct Operate request without a preceding Select, and&lt;/li&gt;
&lt;li&gt;transmitting an Operate request against an unarmed point.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The master-side test suite executed both cases sequentially.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F647i0e7g63w0x8dyyaon.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F647i0e7g63w0x8dyyaon.png" alt="Control execution test suite issuing Direct Operate and Operate commands." width="800" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3. &lt;code&gt;control_attacks.py&lt;/code&gt; exercising multiple CROB execution paths.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The first experiment demonstrated that Direct Operate followed its intended execution path. Because &lt;code&gt;FC 0x05&lt;/code&gt; explicitly bypasses the reservation stage, the simulated outstation accepted the request and executed the control action immediately.&lt;/p&gt;

&lt;p&gt;The second experiment intentionally violated the SBO state machine.&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;Operate&lt;/code&gt; request (&lt;code&gt;FC 0x04&lt;/code&gt;) was transmitted without a matching active &lt;code&gt;Select&lt;/code&gt;. Rather than executing the command, the outstation rejected the request because no reservation existed for the specified control point.&lt;/p&gt;

&lt;p&gt;The internal logs clearly illustrate that transition.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flo3vxaxupg96qw7f40wi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flo3vxaxupg96qw7f40wi.png" alt="Outstation validating Select-Before-Operate state transitions." width="800" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 4. The outstation detecting an invalid Operate sequence and rejecting the request.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Although these experiments were performed against a custom implementation, they demonstrate why protocol state machines matter just as much as packet syntax. A correctly implemented DNP3 endpoint does more than decode bytes... it continuously tracks execution context, validates command ordering, and determines whether a requested action is currently valid.&lt;/p&gt;

&lt;p&gt;That distinction becomes even more important when administrative function codes begin modifying the outstation's runtime state.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 3 — Administrative State Transitions
&lt;/h2&gt;

&lt;p&gt;Control commands are only one part of the DNP3 application layer.&lt;/p&gt;

&lt;p&gt;IEEE 1815 also defines a collection of administrative function codes that influence the lifecycle of an outstation itself. Rather than manipulating individual field points, these commands affect the runtime environment responsible for processing telemetry, maintaining event history, and coordinating system time.&lt;/p&gt;

&lt;p&gt;Among the most significant administrative functions are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function Code&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x0D&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cold Restart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x0E&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Warm Restart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x12&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stop Application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x18&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Write Time&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Unlike CROB operations, these requests alter the state of the outstation rather than the state of an individual control point.&lt;/p&gt;

&lt;p&gt;To observe these transitions, I executed each function sequentially against the simulated outstation while monitoring both protocol responses and internal application state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F31yoi2g1x73okn5wfct6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F31yoi2g1x73okn5wfct6.png" alt="Administrative function test suite executing restart and lifecycle commands." width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 5. &lt;code&gt;system_attacks.py&lt;/code&gt; exercising administrative function codes against the baseline outstation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Several interesting behaviors emerged during testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time Synchronization
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Write Time&lt;/code&gt; function (&lt;code&gt;FC 0x18&lt;/code&gt;) updates the outstation's internal clock. In the lab, submitting a new timestamp immediately modified the runtime clock and cleared the &lt;strong&gt;Need Time&lt;/strong&gt; indication (&lt;code&gt;IIN1.4&lt;/code&gt;), demonstrating how time synchronization directly affects protocol state.&lt;/p&gt;

&lt;p&gt;Although expected behavior, it reinforces how closely event sequencing depends on accurate clocks in industrial systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Warm Restart
&lt;/h3&gt;

&lt;p&gt;Issuing a Warm Restart (&lt;code&gt;FC 0x0E&lt;/code&gt;) temporarily interrupted application processing while preserving much of the runtime context.&lt;/p&gt;

&lt;p&gt;The response included a restart delay object, allowing the master to estimate when normal communications could safely resume.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stop Application
&lt;/h3&gt;

&lt;p&gt;The most interesting behavior occurred after issuing &lt;strong&gt;Stop Application&lt;/strong&gt; (&lt;code&gt;FC 0x12&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Rather than terminating the network connection, the transport and link layers continued accepting frames while the application layer stopped processing operational requests.&lt;/p&gt;

&lt;p&gt;Subsequent read operations were therefore rejected, not because the TCP session had failed, but because the application itself had transitioned into a halted state.&lt;/p&gt;

&lt;p&gt;That distinction is subtle but important.&lt;/p&gt;

&lt;p&gt;From a network perspective, the device still appeared reachable.&lt;/p&gt;

&lt;p&gt;From an application perspective, however, it was no longer servicing requests.&lt;/p&gt;

&lt;p&gt;The outstation logs clearly illustrate these transitions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ob7izuoqbhl4b887drr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ob7izuoqbhl4b887drr.png" alt="Outstation processing administrative state transitions." width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 6. Runtime state changes following administrative commands, including clock updates, application halt, and restart processing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This phase reinforced an important implementation detail: protocol availability is not determined solely by socket state. Internal application state can significantly alter how an endpoint responds, even while lower protocol layers continue operating normally.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 4 — Transport Reassembly &amp;amp; Unsolicited Messaging
&lt;/h2&gt;

&lt;p&gt;The DNP3 Transport Pseudo-Layer is often overlooked because it consists of only a single control byte.&lt;/p&gt;

&lt;p&gt;Despite its simplicity, that byte is responsible for coordinating fragmentation and reassembly across every multi-frame Application Protocol Data Unit (APDU).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bit Position

7      6      5 4 3 2 1 0
+------+------+
| FIR  | FIN  | Sequence |
+------+------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transport header contains three pieces of information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FIR (First Fragment)&lt;/strong&gt; : Indicates the beginning of an APDU stream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FIN (Final Fragment)&lt;/strong&gt; : Marks the final fragment in the stream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequence Number&lt;/strong&gt; : A six-bit counter used to order fragments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most DNP3 communications fit within a single frame, making fragmentation relatively uncommon during normal operation.&lt;/p&gt;

&lt;p&gt;Nevertheless, any implementation supporting larger payloads must correctly maintain fragment state, sequence tracking, and timeout behavior.&lt;/p&gt;

&lt;p&gt;To evaluate that logic, I intentionally exercised several edge cases.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reassembly State Validation
&lt;/h2&gt;

&lt;p&gt;The first experiment transmitted a frame marked as the &lt;strong&gt;final fragment&lt;/strong&gt; without ever establishing an active fragment stream.&lt;/p&gt;

&lt;p&gt;From the parser's perspective, this meant receiving the end of a conversation that had never started.&lt;/p&gt;

&lt;p&gt;The master-side test suite generated the malformed transport header and transmitted it directly to the outstation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ficw627brcsyk2zeqrrhd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ficw627brcsyk2zeqrrhd.png" alt="Transport-layer edge case testing." width="783" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 7. &lt;code&gt;transport_attacks.py&lt;/code&gt; generating malformed transport sequences and unsolicited response traffic.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Rather than attempting to process the payload, the parser detected the invalid state transition and rejected the frame.&lt;/p&gt;

&lt;p&gt;The corresponding runtime logs clearly show the parser identifying the missing fragment context before terminating reassembly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjxdpmbhkxlhfr1uh91rd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjxdpmbhkxlhfr1uh91rd.png" alt="Transport parser rejecting malformed fragment state." width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 8. The transport engine detecting an invalid fragment sequence during APDU reassembly.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The second experiment intentionally opened a fragmented stream without completing it.&lt;/p&gt;

&lt;p&gt;Instead of immediately raising an error, the parser allocated reassembly state and advanced the expected sequence counter.&lt;/p&gt;

&lt;p&gt;This illustrates another important implementation consideration.&lt;/p&gt;

&lt;p&gt;Transport parsers do more than decode individual packets. They maintain state across multiple frames. Any robust implementation must therefore handle incomplete streams, unexpected sequence numbers, retransmissions, and timeout cleanup without exhausting internal resources.&lt;/p&gt;




&lt;h2&gt;
  
  
  Unsolicited Response Processing
&lt;/h2&gt;

&lt;p&gt;The final experiment in this phase focused on &lt;strong&gt;Unsolicited Responses (&lt;code&gt;FC 0x82&lt;/code&gt;)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Unlike conventional request-response communication, unsolicited messages allow an outstation to report significant events without waiting for the master to poll.&lt;/p&gt;

&lt;p&gt;Typical uses include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;binary input changes,&lt;/li&gt;
&lt;li&gt;analog threshold crossings,&lt;/li&gt;
&lt;li&gt;event notifications,&lt;/li&gt;
&lt;li&gt;alarm conditions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Within the laboratory, I generated a synthetic unsolicited response carrying an Analog Input object (&lt;code&gt;Group 30 Variation 1&lt;/code&gt;) to observe how the implementation processed an event arriving outside the normal polling cycle.&lt;/p&gt;

&lt;p&gt;Because the frame matched the expected protocol format, the simulated receiver accepted the message and updated its internal state accordingly.&lt;/p&gt;

&lt;p&gt;The purpose of this experiment was not to demonstrate a universal weakness in DNP3 deployments, but to study how unsolicited messaging is integrated into protocol processing and why implementations must validate message origin, sequencing, and where supported, authentication before committing unsolicited event data.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. DNP3 Secure Authentication (SA v5) Verification
&lt;/h2&gt;

&lt;p&gt;One of the most significant limitations of legacy DNP3 is the absence of built-in authentication for critical control operations. Any device capable of reaching an outstation can potentially issue administrative or control function codes unless additional security controls are deployed.&lt;/p&gt;

&lt;p&gt;To address this, &lt;strong&gt;IEEE 1815-2012&lt;/strong&gt; introduced &lt;strong&gt;DNP3 Secure Authentication Version 5 (SA v5)&lt;/strong&gt;, later aligned with &lt;strong&gt;IEC 62351-5&lt;/strong&gt;. Rather than encrypting protocol traffic, SA v5 protects sensitive operations through a challenge-response mechanism that verifies message authenticity and integrity before control commands are executed.&lt;/p&gt;

&lt;p&gt;To better understand this workflow, I implemented a dedicated Secure Authentication outstation (&lt;code&gt;outstation-sa.py&lt;/code&gt;) alongside a companion master test harness (&lt;code&gt;master_test_runner.py&lt;/code&gt;). Together they reproduce the complete authentication state machine, allowing each stage of the protocol exchange to be observed in isolation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz4qv5cklmty5rkrufoxk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz4qv5cklmty5rkrufoxk.png" alt="Master test runner executing Secure Authentication validation" width="767" height="610"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The master-side test harness executing the complete Secure Authentication validation suite, including challenge negotiation, HMAC verification, session establishment, and authenticated control execution.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Walking Through the Authentication Workflow
&lt;/h3&gt;

&lt;p&gt;The authentication sequence follows a well-defined state machine.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Baseline Read (&lt;code&gt;FC 0x01&lt;/code&gt;)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Standard telemetry reads continue to operate normally. Since these requests are considered non-critical, they do not require cryptographic validation and are processed without additional authentication overhead.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Challenge Negotiation (&lt;code&gt;FC 0x20&lt;/code&gt;, Object 120 Variation 1)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before executing a protected operation, the master initiates the authentication process. The outstation responds with a challenge containing a nonce, sequence information, and additional parameters required to construct a valid authentication response.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Challenge Verification (&lt;code&gt;FC 0x20&lt;/code&gt;, Object 120 Variation 2)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Using the pre-shared session key, the master computes an HMAC over the challenge data and submits the resulting authentication payload. The outstation independently performs the same calculation and compares the received digest against its own result.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Authenticated Session Establishment&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If verification succeeds, the outstation marks the communication session as authenticated and returns its current Secure Authentication status (Object Group 120). Subsequent protected operations are now permitted within the authenticated session.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Protected Control Execution&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With authentication complete, privileged operations, such as &lt;strong&gt;Select-Before-Operate (FC 0x03)&lt;/strong&gt; are processed normally. Requests that would otherwise require authorization can now proceed under the active authenticated session.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffvest080ptxt998m8r0k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffvest080ptxt998m8r0k.png" alt="Dedicated Secure Authentication outstation processing Group 120 objects" width="799" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Secure Authentication outstation validating Group 120 authentication objects, updating session state, and authorizing protected control operations after successful HMAC verification.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Engineering Observations
&lt;/h2&gt;

&lt;p&gt;Building this Secure Authentication implementation reinforced an important distinction that is often overlooked: &lt;strong&gt;Secure Authentication is not encryption.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Application payloads remain visible on the wire, making DNP3 traffic fully inspectable by monitoring platforms and industrial intrusion detection systems. Instead, SA v5 protects the integrity and authenticity of critical operations by ensuring that protected commands originate from an authenticated master and have not been modified or replayed in transit.&lt;/p&gt;

&lt;p&gt;Across the five phases of this research, several observations consistently emerged:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DNP3 is fundamentally a &lt;strong&gt;stateful protocol&lt;/strong&gt;. Correct behavior depends not only on packet contents, but also on transport state, control sequencing, and session context.&lt;/li&gt;
&lt;li&gt;Administrative function codes deserve the same operational scrutiny as relay control commands, as they directly influence device availability and operational visibility.&lt;/li&gt;
&lt;li&gt;Transport-layer correctness is just as important as application-layer validation. Robust fragment handling and sequence tracking are essential for reliable implementations.&lt;/li&gt;
&lt;li&gt;Secure Authentication significantly strengthens DNP3 by introducing integrity verification and replay protection for sensitive operations, while remaining compatible with existing protocol deployments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Perhaps the biggest lesson from this project was methodological.&lt;/p&gt;

&lt;p&gt;Reading the specification explains what the protocol is designed to do. Packet captures show what happened on the network. Building an implementation exposes the internal decisions that connect those two perspectives.&lt;/p&gt;

&lt;p&gt;That shift,, from interacting with industrial protocols to implementing them, has fundamentally changed how I approach protocol research. Understanding parser behavior, state machines, and implementation details provides insights that are difficult to obtain through passive observation alone.&lt;/p&gt;

&lt;p&gt;The complete research laboratory, including the custom outstation implementations, master-side tooling, packet captures, screenshots, and detailed technical notes, is available in the accompanying GitHub repository.&lt;/p&gt;

&lt;p&gt;As with every project in this series, all experiments were performed in an isolated laboratory environment for protocol research, defensive engineering, and implementation analysis. No testing was conducted against production industrial systems.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>ics</category>
      <category>network</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Beyond Cyclic Polling: Objects, Classes, and Outstation State in DNP3</title>
      <dc:creator>404Saint</dc:creator>
      <pubDate>Tue, 28 Jul 2026 16:56:40 +0000</pubDate>
      <link>https://dev.to/null_saint/beyond-cyclic-polling-objects-classes-and-outstation-state-in-dnp3-be9</link>
      <guid>https://dev.to/null_saint/beyond-cyclic-polling-objects-classes-and-outstation-state-in-dnp3-be9</guid>
      <description>&lt;p&gt;By RUGERO Tesla (&lt;a href="https:/github.com/404saint" rel="noopener noreferrer"&gt;@404saint&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;If you approach &lt;strong&gt;DNP3 (Distributed Network Protocol 3)&lt;/strong&gt; from a background in Modbus/TCP or EtherNet/IP (CIP), your mental model of industrial communications will immediately break.&lt;/p&gt;

&lt;p&gt;In Modbus or CIP, state management is largely cyclic and stateless at the application layer. A Master polls a slave’s memory registers (e.g., &lt;em&gt;Read Holding Registers&lt;/em&gt; &lt;code&gt;0x03&lt;/code&gt;), receives a snapshot of current values, and repeats the process on a fixed timer loop. If the network link drops for 10 seconds, every intermediate state change during that window is permanently lost.&lt;/p&gt;

&lt;p&gt;DNP3 was explicitly engineered for wide-area networks (WANs) such as electrical distribution grids and water systems, where telemetry channels like serial radios, cellular and high-latency links are prone to latency, low bandwidth, and intermittent dropouts.&lt;/p&gt;

&lt;p&gt;To survive these harsh environments, DNP3 flips the operational model from &lt;strong&gt;cyclic polling to event-driven, buffered telemetry&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is a deep dive into how DNP3 abstracts memory, categorizes events, and maintains state integrity before we jump into packet-level dissections.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Data Abstraction: Groups, Variations, and Objects
&lt;/h2&gt;

&lt;p&gt;In Modbus, you read raw memory addresses (&lt;code&gt;00001&lt;/code&gt;, &lt;code&gt;40001&lt;/code&gt;). You are responsible for knowing whether a given offset contains a bit, a 16-bit integer, or a split 32-bit float.&lt;/p&gt;

&lt;p&gt;DNP3 abstracts raw memory into &lt;strong&gt;Objects&lt;/strong&gt;, identified by &lt;strong&gt;Group&lt;/strong&gt; and &lt;strong&gt;Variation&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Group:&lt;/strong&gt; Defines the data type or container (e.g., Group 1 = Binary Input, Group 30 = Analog Input, Group 50 = Time and Date).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variation:&lt;/strong&gt; Defines the specific byte structure, resolution, and inclusion of flag/timestamp metadata for that data type.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, if an Outstation needs to transmit an Analog Input:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Group 30, Variation 1:&lt;/strong&gt; 32-bit Analog Input with Flag.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group 30, Variation 2:&lt;/strong&gt; 16-bit Analog Input with Flag.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group 30, Variation 5:&lt;/strong&gt; Single-precision Floating Point with Flag.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcvhv7njyst2dsl7tz1zu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcvhv7njyst2dsl7tz1zu.png" alt="DNP3 Data Hierarchy Structure" width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;DNP3 Data Hierarchy Structure — Group, Variation, and Point Index mapping.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By allowing the Master station to explicitly request specific Variations (or allowing the Outstation to select them dynamically), DNP3 optimizes bandwidth usage over constrained mediums. You don't waste 32 bits on the wire if a 16-bit integer or a single change flag is all that changed.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Event Classes and the Internal State Machine
&lt;/h2&gt;

&lt;p&gt;To prevent constant high-frequency polling across limited radio links, DNP3 separates data into &lt;strong&gt;Static Data&lt;/strong&gt; and &lt;strong&gt;Event Data&lt;/strong&gt;, divided across four internal classes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Class 0 (Static Data):&lt;/strong&gt; Represents the current real-time snapshot of points on the Outstation. Requesting Class 0 is the equivalent of a full system state readout (Integrity Poll).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Class 1 (High Priority Events):&lt;/strong&gt; Critical alarm triggers, trip signals, or high-urgency state transitions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Class 2 (Medium Priority Events):&lt;/strong&gt; Standard operational status changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Class 3 (Low Priority Events):&lt;/strong&gt; Analog deadband breaches, minor diagnostic changes, or slow-moving trend data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Buffer Mechanism
&lt;/h3&gt;

&lt;p&gt;When a physical field value changes (e.g., a breaker trips), the Outstation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Detects&lt;/strong&gt; the state transition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Formats&lt;/strong&gt; the change into an Event Object (e.g., Group 2 for Binary Input Event), appending a microsecond-resolution timestamp.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Appends&lt;/strong&gt; the Event Object into its local Class 1/2/3 Event Buffer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Updates&lt;/strong&gt; its internal Class 0 snapshot value.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv0h4fyovkhhxov8mh2iy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv0h4fyovkhhxov8mh2iy.png" alt="Architectural flow showing field state transitions updating Class 0 snapshot vs. pushing timestamped Event Objects into Class 1/2/3 buffers for Master polls" width="800" height="1673"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Architectural flow showing field state transitions updating Class 0 snapshot vs. pushing timestamped Event Objects into Class 1/2/3 buffers for Master polls.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When the Master polls for Class 1, 2, or 3 Events (an &lt;em&gt;Event Poll&lt;/em&gt;), the Outstation empties its event buffer over the wire. The Master doesn't just learn what the value is right now, it receives the chronological history of every single state transition that occurred since the last poll.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Unsolicited Responses: Breaking Master-Slave Hierarchy
&lt;/h2&gt;

&lt;p&gt;In standard Master-Slave/Client-Server OT architectures, an Outstation never speaks unless spoken to. If a critical transformer overheats right after a poll completes, the Master won't know until the next polling interval.&lt;/p&gt;

&lt;p&gt;DNP3 solves this via &lt;strong&gt;Unsolicited Responses&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An Outstation is configured with threshold conditions (e.g., any Class 1 Event occurring).&lt;/li&gt;
&lt;li&gt;As soon as the event enters the Class 1 buffer, the Outstation spontaneously initiates an Application-Layer response to the Master without waiting for a request.&lt;/li&gt;
&lt;li&gt;The Master accepts the event data and returns an Application-Level Confirmation (&lt;code&gt;ACK&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This fundamentally shifts the latency model: bandwidth usage remains near zero during quiescent operation, but response time for critical alarms becomes near-instantaneous.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Reliability and Integrity: IIN Bytes &amp;amp; Confirmation Loops
&lt;/h2&gt;

&lt;p&gt;Because DNP3 operates over unreliable networks, packet delivery guarantees are handled natively at the Application Layer, rather than relying solely on TCP or lower layers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Internal Indications (IIN)
&lt;/h3&gt;

&lt;p&gt;Every single Application Layer response from a DNP3 Outstation includes a 2-byte header known as the &lt;strong&gt;Internal Indications (IIN)&lt;/strong&gt; flags. These bits act as continuous system diagnostics returned to the Master in every message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmgjh3mqqjukt6jz1t2hm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmgjh3mqqjukt6jz1t2hm.png" alt=" DNP3 16-Bit Internal Indications (IIN) Layout" width="798" height="81"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Caption: Bit-level mapping for Octet 1 (&lt;code&gt;IIN1.0&lt;/code&gt;–&lt;code&gt;IIN1.7&lt;/code&gt;) and Octet 2 (&lt;code&gt;IIN2.0&lt;/code&gt;–&lt;code&gt;IIN2.7&lt;/code&gt;).&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Key Bit Flags Highlighted:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;IIN1.0&lt;/code&gt;: All Stations Message Received&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IIN1.4&lt;/code&gt;: Device Restart&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IIN1.5&lt;/code&gt;: Device Trouble&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IIN1.7&lt;/code&gt;: &lt;strong&gt;Event Buffer Overflow&lt;/strong&gt; &lt;em&gt;(CRITICAL: Last state/events lost)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IIN2.1&lt;/code&gt;: Class 1 Data Available&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IIN2.2&lt;/code&gt;: Class 2 Data Available&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IIN2.3&lt;/code&gt;: Class 3 Data Available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a communications link drops for hours and the Outstation's memory buffer completely fills up, it sets the &lt;code&gt;IIN1.7&lt;/code&gt; (Event Buffer Overflow) bit. When the connection is re-established, the Master reads this bit and immediately knows that time-series continuity was broken, triggering an automatic Class 0 Integrity Poll to re-baseline the system state.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Application Confirmation Loop
&lt;/h3&gt;

&lt;p&gt;To guarantee zero event loss:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Master polls Class 1 Events.&lt;/li&gt;
&lt;li&gt;Outstation transmits Events 1 through 5 in an Application Response (&lt;code&gt;FIN=1&lt;/code&gt;, &lt;code&gt;CON=1&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Outstation retains Events 1 through 5 in its local buffer.&lt;/li&gt;
&lt;li&gt;Master receives the response, parses the events, and transmits an Application Confirm (&lt;code&gt;CONFIRM&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Only upon receiving the explicit Confirm header does the Outstation purge Events 1 through 5 from its memory buffer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the network connection drops before step 4, the Outstation retains the events and re-transmits them on the next successful session.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary &amp;amp; Next Steps
&lt;/h2&gt;

&lt;p&gt;DNP3 trades the simplicity of cyclic register-reading for an event-driven, timestamped state machine designed for high-latency, degraded environments. Understanding these core concepts: &lt;strong&gt;Object Groups/Variations, Event Classes, Unsolicited Responses, and IIN state flags&lt;/strong&gt; is critical before analyzing traffic on the wire.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Part 2&lt;/strong&gt;, we will fire up an active DNP3 Master/Outstation pair in the lab, trigger simulated field alarms, and inspect raw Wireshark PCAPs to dissect the exact byte-level layout of Application Layer headers, Object Requests, and IIN response flags.&lt;/p&gt;

</description>
      <category>ics</category>
      <category>ot</category>
      <category>cybersecurity</category>
      <category>network</category>
    </item>
    <item>
      <title>Why Advanced Industrial Protocols Give Raw Sockets the "Silent Treatment"</title>
      <dc:creator>404Saint</dc:creator>
      <pubDate>Mon, 13 Jul 2026 22:28:35 +0000</pubDate>
      <link>https://dev.to/null_saint/why-advanced-industrial-protocols-give-raw-sockets-the-silent-treatment-33b8</link>
      <guid>https://dev.to/null_saint/why-advanced-industrial-protocols-give-raw-sockets-the-silent-treatment-33b8</guid>
      <description>&lt;p&gt;By RUGERO Tesla (&lt;a href="https:/github.com/404saint" rel="noopener noreferrer"&gt;@404saint&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;When I first ditched high-level libraries like pyModbus to build my own socket-based protocol toolkit, my goal was simple. I just wanted to see the protocols actually talk on the wire.&lt;br&gt;
Using libraries is easy. They abstract away the complexity, but they also blind you to the wire-level mechanisms. If you want to master Operational Technology (OT) security, you have to understand the bytes.&lt;/p&gt;

&lt;p&gt;Building a raw Modbus toolkit worked. But when I tried to take that same mentality and apply it to advanced protocols like &lt;strong&gt;EtherNet/IP&lt;/strong&gt; and &lt;strong&gt;DNP3&lt;/strong&gt;, reality hit hard. Treating EtherNet/IP like Modbus almost broke me.&lt;/p&gt;

&lt;p&gt;Here is the truth about what happens when you abandon abstractions, and why the wire level is a brutal teacher.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Anatomy of a "Silent Timeout"
&lt;/h2&gt;

&lt;p&gt;With basic protocols, formatting is relatively flat. But advanced industrial protocols are nested nightmares. When you are manually crafting binary streams using Python's struct module, perfection isn't an option, it’s the baseline.&lt;/p&gt;

&lt;p&gt;Being off by &lt;strong&gt;even a single byte&lt;/strong&gt; doesn't just trigger an elegant error message. It causes a complete system breakdown:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The PLC refuses to respond.&lt;/li&gt;
&lt;li&gt;Your script hangs and hits a generic timeout.&lt;/li&gt;
&lt;li&gt;The network gives you total silence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you're twisting a raw socket script trying to debug a timeout, the script itself gives you zero useful feedback. To actually find out where the alignment broke, I found myself digging through containerized Docker logs just to see what the simulated PLC environment was choking on.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Trap of Library Assumptions
&lt;/h2&gt;

&lt;p&gt;The frustration doesn't start with raw sockets, it starts when you make assumptions about high-level libraries without knowing how they work under the hood.&lt;br&gt;
If you guess how a library is handling a protocol format and you are one byte off, you enter a loop of endless guessing. You burn two hours trying to tweak the library inputs, only to realize the abstraction layer won't let you bend the packet the way you need to.&lt;/p&gt;

&lt;p&gt;The ironic part? You realize the &lt;em&gt;only&lt;/em&gt; solution left is to craft the entire payload manually from scratch. But when you step up to an advanced protocol like EtherNet/IP, that manual path becomes an uphill battle against complex session registrations and CIP (Common Industrial Protocol) routing headers.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Why Automated Tools Fail in the Field
&lt;/h2&gt;

&lt;p&gt;Relying solely on automated tools or commercial scanners is a massive liability in OT security.&lt;br&gt;
The engineers who write automated tools build them on assumptions about what a "standard" industrial environment looks like. But in the real world, &lt;strong&gt;every industrial environment is shifted a little bit.&lt;/strong&gt;&lt;br&gt;
When a tool encounters a non-standard environment, one of two things happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The industrial device gives the tool the silent treatment.&lt;/li&gt;
&lt;li&gt;The automated tool panics and returns a &lt;strong&gt;confident wrong answer.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Takeaway:
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Meeting Protocols on the Wire.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is exactly why manually wrestling with raw sockets even when it drives you crazy is invaluable.When you meet these protocols directly on the wire, your perspective changes. When you are the one parsing the raw binary data streams, &lt;strong&gt;network anomalies become immediately obvious.&lt;/strong&gt; You stop guessing what a flashing link light or a timed-out script means because you know exactly which byte in the header failed to align.&lt;/p&gt;

&lt;p&gt;Abstractions are great for speed, but deep security research requires you to look at the raw bytes,even when they give you the silent treatment.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>networking</category>
      <category>opensource</category>
      <category>ics</category>
    </item>
    <item>
      <title>Abandoning Abstractions: Manually Crafting EtherNet/IP Packets Almost Broke Me</title>
      <dc:creator>404Saint</dc:creator>
      <pubDate>Mon, 29 Jun 2026 21:13:20 +0000</pubDate>
      <link>https://dev.to/null_saint/abandoning-abstractions-manually-crafting-ethernetip-packets-almost-broke-me-1b2k</link>
      <guid>https://dev.to/null_saint/abandoning-abstractions-manually-crafting-ethernetip-packets-almost-broke-me-1b2k</guid>
      <description>&lt;p&gt;&lt;em&gt;By RUGERO Tesla (&lt;a href="https://github.com/404saint" rel="noopener noreferrer"&gt;@404Saint&lt;/a&gt;).&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;They don’t.&lt;/p&gt;

&lt;p&gt;They hide the architecture.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;cpppo&lt;/code&gt; simulator, and a passive monitoring tool (&lt;code&gt;enip_monitor.py&lt;/code&gt;) capturing traffic in real time.&lt;/p&gt;

&lt;p&gt;It looked clean on paper. Then I reached the application layer.&lt;/p&gt;

&lt;p&gt;And things stopped behaving like theory.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Reality of the “Industrial Abstraction Layer”
&lt;/h2&gt;

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

&lt;p&gt;EtherNet/IP and CIP discard that model entirely.&lt;/p&gt;

&lt;p&gt;Instead, they introduce a structured object system wrapped inside multiple encapsulation layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
+-----------------------------------------------------------+
| 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.)                  |
+-----------------------------------------------------------+

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To communicate with a PLC at the wire level, your code must:&lt;/p&gt;

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

&lt;p&gt;A single mistake in any layer breaks everything silently.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Wall: Fragmentation and State Confusion
&lt;/h2&gt;

&lt;p&gt;Basic tag reads (&lt;code&gt;0x4C&lt;/code&gt;) and writes (&lt;code&gt;0x4D&lt;/code&gt;) against my test dataset (&lt;code&gt;SAINT_DATA&lt;/code&gt;) worked as expected.&lt;/p&gt;

&lt;p&gt;Then I moved into fragmented transfers using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0x52&lt;/code&gt; (Read Fragmented)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0x53&lt;/code&gt; (Write Fragmented)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where things stopped being predictable.&lt;/p&gt;

&lt;p&gt;I was manually tracking offsets, element counts, and buffer boundaries across multiple frames. The client appeared to execute correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
[+] Attribute Write Resolved: write_frag.elements
[+] Attribute Write Resolved: write_frag.offset
[+] Attribute Write Resolved: write_frag.data
[+] Attribute Write Resolved: service

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But nothing changed on the wire.&lt;/p&gt;

&lt;p&gt;No values. No errors. No feedback.&lt;/p&gt;

&lt;p&gt;Just silent failure.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Breakthrough: Observing the Wire Directly
&lt;/h2&gt;

&lt;p&gt;The turning point came from running &lt;code&gt;enip_monitor.py&lt;/code&gt; alongside my client scripts, capturing loopback traffic in real time.&lt;/p&gt;

&lt;p&gt;Instead of trusting the client’s internal state logs, I started trusting the packet stream.&lt;/p&gt;

&lt;p&gt;Then I triggered a deliberate failure by querying a non-existent tag (&lt;code&gt;NON_EXISTENT_TAG&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The monitor immediately exposed the actual system behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
[!] 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

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PLC responded with a clear &lt;strong&gt;General Status Code &lt;code&gt;0x05&lt;/code&gt; (Path Destination Unknown)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That moment mattered.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The system was never broken. The assumptions were.&lt;/p&gt;




&lt;h2&gt;
  
  
  Core Takeaway
&lt;/h2&gt;

&lt;p&gt;This project proved something simple but important:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Abstraction layers do not guarantee understanding. They often remove it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;From a security perspective, this matters because:&lt;/p&gt;

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

&lt;p&gt;Defending these systems requires working at the wire level:&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Closing Thought
&lt;/h2&gt;

&lt;p&gt;If you want to understand OT systems properly, don’t start with tooling.&lt;/p&gt;

&lt;p&gt;Start with bytes.&lt;/p&gt;

&lt;p&gt;Raw sockets are frustrating, slow, and unforgiving but they expose what every abstraction layer tries to hide.&lt;/p&gt;




&lt;h2&gt;
  
  
  Repository
&lt;/h2&gt;

&lt;p&gt;All scripts, validation flows, and 13 supporting technical notes are documented here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/404saint/industrial-protocol-labs/" rel="noopener noreferrer"&gt;https://github.com/404saint/industrial-protocol-labs/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Disclaimer
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Always ensure compliance with applicable laws, operational safety requirements, and organizational policies when working with industrial control systems.&lt;/p&gt;

</description>
      <category>ics</category>
      <category>python</category>
      <category>network</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>The Reality of Raw Sockets: When Your Packet Trace Lies and the PLC Stays Silent</title>
      <dc:creator>404Saint</dc:creator>
      <pubDate>Tue, 23 Jun 2026 15:20:44 +0000</pubDate>
      <link>https://dev.to/null_saint/the-reality-of-raw-sockets-when-your-packet-trace-lies-and-the-plc-stays-silent-5a7k</link>
      <guid>https://dev.to/null_saint/the-reality-of-raw-sockets-when-your-packet-trace-lies-and-the-plc-stays-silent-5a7k</guid>
      <description>&lt;p&gt;&lt;em&gt;By RUGERO Tesla (&lt;a href="https://github.com/404saint" rel="noopener noreferrer"&gt;@404Saint&lt;/a&gt;).&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;We see a lot of clean "wins" on tech blogs. People post beautifully formatted walk-throughs where every exploit chain connects on the first try, every payload returns a root shell, and the terminal looks like a movie script.&lt;/p&gt;

&lt;p&gt;This isn't one of those posts.&lt;/p&gt;

&lt;p&gt;This is the raw, unedited chronology of what happens when you spend hours fighting industrial protocols, getting brick-walled by application layers, and chasing down silent network failures at 2:00 AM.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Trap of Protocol Assumptions (DNP3 vs. Modbus)
&lt;/h2&gt;

&lt;p&gt;The cycle started yesterday. Fresh off building a custom toolkit for Modbus TCP which is a beautifully simple, stateless, and completely predictable protocol, I figured I would easily pivot the framework to handle DNP3.&lt;/p&gt;

&lt;p&gt;I expected a similar flow. I was entirely wrong.&lt;/p&gt;

&lt;p&gt;Five hours of continuous, grueling socket programming later, I was staring at a wall. DNP3 isn’t just a protocol; it’s an intricate architectural ecosystem. It requires dealing with complex outstation internal states, multi-layered packet fragmentations, and strict link-layer confirmations. Treating DNP3 like Modbus isn't just a minor mistake; it’s a fundamental misunderstanding of the wire.&lt;/p&gt;

&lt;p&gt;Realizing I was fighting a losing battle without deep-diving into the complete specification first, I made a tactical decision: I parked DNP3 for a dedicated project later, stepped back, and redirected my raw Python socket architecture toward EtherNet/IP (ENIP) and the Common Industrial Protocol (CIP).&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Illusions of the Network Layer (The TCP ACK Trap)
&lt;/h2&gt;

&lt;p&gt;By late tonight, the initial EtherNet/IP encapsulation code was ready. I fired off the initial Phase 1 registration packets (&lt;code&gt;0x0065&lt;/code&gt;) against my local lab target running an OpenPLC instance. The handshakes were flawless, and the session handles were rotating cleanly in hexadecimal. The transport layer felt bulletproof.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F80xa1m8kuzq12jd13avy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F80xa1m8kuzq12jd13avy.png" alt="Successful ENIP session registration" width="625" height="152"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;&lt;em&gt;Figure 1: Successful ENIP session registration (&lt;code&gt;0x0065&lt;/code&gt;) yielding an active handle.&lt;/em&gt;
  &lt;p&gt;&lt;/p&gt;

&lt;p&gt;Then came Phase 2: Attempting a Symbolic Segment Tag Read (&lt;code&gt;0x4C&lt;/code&gt;) over an unconnected data path (&lt;code&gt;SendRRData&lt;/code&gt;, &lt;code&gt;0x006F&lt;/code&gt;). I hit enter on the execution script and watched &lt;code&gt;tcpdump&lt;/code&gt; on my secondary monitor.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4x06zinemws01xm3cser.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4x06zinemws01xm3cser.png" alt="The network illusion TCP transport layer handshake" width="625" height="152"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;&lt;em&gt;Figure 2: The network illusion— captured a flawless TCP transport-layer handshake followed by absolute application-layer silence.&lt;/em&gt;
  &lt;p&gt;&lt;/p&gt;

&lt;p&gt;On the network level, it looked like a total success:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The script pushed the raw custom ENIP payload to port 44818.&lt;/li&gt;
&lt;li&gt;The target container immediately shot back a TCP &lt;code&gt;. ACK&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But on my main terminal, everything just froze.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[*] Phase 02: Building and delivering Vendor class 0x64 read requests...
[-] Wire execution crashed. Context: timed out

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five seconds of total silence, followed by a socket timeout exception. This is the ultimate illusion of network testing. The host OS kernel swallowed the packet and acknowledged it at the transport layer, but the actual PLC application layer went completely dark. No TCP Reset (&lt;code&gt;RST&lt;/code&gt;). No CIP error frames. Just an empty void.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Demolishing the Target (Inside the OpenPLC Logs)
&lt;/h2&gt;

&lt;p&gt;I spent the next hour questioning my own code. When you're crafting raw binary payloads via Python’s &lt;code&gt;struct.pack()&lt;/code&gt;, a single misaligned byte, a broken word size, or an incorrect item count in the Common Packet Format (CPF) wrapper will cause an industrial parser to drop the ball.&lt;/p&gt;

&lt;p&gt;I tore the script apart, rebuilt the hexadecimal mapping from scratch, and even stripped out the proprietary Rockwell tag-read service down to a completely generic, standard CIP Identity Object discovery path (&lt;code&gt;Class 0x01, Instance 0x01, Service 0x0E&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Re-aligning the CPF wrapper and Encapsulation Header
&lt;/span&gt;&lt;span class="n"&gt;rr_data_packet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;HHII8sI IHHHHHH&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mh"&gt;0x006F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SAINT404&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Encap Header
&lt;/span&gt;    &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x0000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00B2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;          &lt;span class="c1"&gt;# CPF Data
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;cip_data&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran it again. Same result. Another 5-second hang.&lt;/p&gt;

&lt;p&gt;At this point, you have to stop assuming your tool is broken and start looking at the target's internal state. I dropped out of my code editor and pulled the live stdout logs from the target runtime container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker logs openplc-lab

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There, buried in the container timestamps, was the exact smoking gun:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-06-23T14:05:56.165168622Z ENIP: Received unsupported EtherNet/IP Type
2026-06-23T14:05:56.165170130Z Server: Error writing response: -1

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The realization was incredibly clarifying. The script wasn't broken. The packet wasn't misaligned. OpenPLC’s lightweight, modular EtherNet/IP listener simply does not support Rockwell-style proprietary symbolic tag reading &lt;em&gt;or&lt;/em&gt; standard unconnected explicit message routing (UCMM).&lt;/p&gt;

&lt;p&gt;Instead of cleanly replying over the wire with a standard CIP protocol error frame (like a normal PLC would), OpenPLC’s backend code hit an unhandled exception path, threw an internal error (&lt;code&gt;-1&lt;/code&gt;), and dropped the socket completely without writing a single byte back to my network interface.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Upgrading the Arsenal
&lt;/h2&gt;

&lt;p&gt;In offensive security and asset fingerprinting, a silent application-layer drop is incredibly valuable telemetry. It tells you exactly who and what is on the other side of the wire.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A real, production-grade Allen-Bradley controller or a high-fidelity simulator will return either tag data or a structured CIP error frame (&lt;code&gt;Path Segment Error&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;An OpenPLC instance will choke internally, log an unhandled error, and leave the client script hanging until a timeout.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ultimate lesson? Hobbyist software teaches you about hobbyist bugs. Fighting with a stripped-down implementation only maps out its internal software limitations rather than showing you how real-world OT systems respond to packet manipulation.&lt;/p&gt;

&lt;p&gt;To truly reverse-engineer industrial connection managers, &lt;code&gt;Forward_Open&lt;/code&gt; loops, and complex CIP state machines, you need a target that acts like the real thing.&lt;/p&gt;

&lt;p&gt;Tomorrow, the lightweight containers are getting parked. We are upgrading the lab environment to high-fidelity target harnesses.&lt;/p&gt;

</description>
      <category>plc</category>
      <category>ics</category>
      <category>network</category>
      <category>python</category>
    </item>
  </channel>
</rss>
