Build1 publisher3 min readPublished
One S7comm function code byte divides a PLC read from a memory write
A hand-built client walked TPKT, COTP and S7comm against a Snap7 server on port 102, negotiated a 480-byte PDU and pulled 16 bytes out of DB3 with function code 0x04. The layers below that byte are the same for a write.
The Engineer · Build desk

What happened
- The lab target was a Snap7 server exposing a Siemens S7-style interface on TCP/102, and the author built the client, constructed the messages, captured the exchanges, then deviated from the expected conversation.
- A manually built COTP Connection Request to 127.0.0.1:102 reported a 22-byte TPKT frame, and the TPDU code went from 0xE0 on the request to 0xD0 on the Connection Confirm.
- A ReadVar request using ROSCTR 0x01 and function 0x04 read 16 bytes from data block 3 at offset 0, and the server answered with return code 0xFF.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint A firewall rule that identifies the protocol and stops there cannot be a read-only rule, because the read and the write share the port, the framing and the session, and differ by one function code in the S7comm payload.
- decision Anyone who wants read-only enforcement on a Siemens cell has to pay for inspection that parses TPKT length, COTP TPDU type and the S7 function byte, or accept that permitting the session permits memory writes.
- exposure On the target under test, the only gate before a working session was the destination TSAP, and it let rack 7, slot 31 through, so session setup did nothing as an access control.
- contradiction The author limits his own tolerance result to Snap7, so operators cannot lift the accepted rack and slot values into a hardware threat model without retesting on a real CPU.
Telling an S7comm read from an S7comm write means walking three headers before you reach the byte that decides it. TPKT gives the frame length, COTP the TPDU type. Then the S7comm header carries the message class and the function. The read that worked in this lab was ROSCTR 0x01, function 0x04, aimed at data block 3, area ID 0x84, offset 0, count 16 [14]. RUGERO Tesla, who publishes as 404Saint, writes that "a single function code can turn a packet from a read request into a write operation" [4]. The layers underneath that byte are the same session in both directions [2]. An allow rule written at TCP/102 therefore permits both [1].
Two size numbers appear in the same session. The COTP connection request carried the parameter C0 01 09, a TPDU size of 512 bytes [8]. Setup Communication then negotiated a maximum S7 PDU of 480 bytes, with one parallel calling job and one parallel called job [9]. The two are 32 bytes apart [10], and Tesla notes they are easy to confuse because they belong to different parts of the communication model [8]. The TPKT header in the same exchange reported a 22-byte frame, which is the 0x16 in the transmitted 03 00 00 16 [6][17].
Session setup is where the target's tolerance showed. Destination TSAP 0x0102 matched the lab's rack 0, slot 2, with source TSAP 0x0100 [5]. The Snap7 target also accepted 0x01FF, which in the lab's Siemens-style reading is rack 7, slot 31 [11]. It accepted 0x0F02 as well [12]. Tesla puts the limit on his own result: "The Snap7 server accepting those values does not establish that a physical Siemens PLC would accept the same combinations." [13]
That caveat splits the finding in two. The layering and the position of the function code are properties of the protocol as captured, so I would expect them to hold wherever an S7comm session is spoken [2][14]. The accepted rack and slot values are properties of one server implementation, the Snap7 target [3]. For the tolerance result to say anything about a plant, a physical CPU would have to skip the same validation.
Tesla built the research client himself, constructed the protocol messages, captured the exchanges, then deliberately deviated from the expected conversation [3]. He wrote: "The earlier projects taught me to establish the architecture before touching the wire, but this time, I wanted to stay on the wire." [18] Reading the capture and accepting what a high-level S7 library reports are two different jobs [19]. S7comm came after Modbus TCP, EtherNet/IP, DNP3, BACnet, OPC UA, IEC 104, IEC 61850 and PROFINET in the same series [16].
The ReadVar came back as an Ack-Data PDU with return code 0xFF and 16 bytes of data [15]. DB3 held sixteen 0x01 bytes, which is what lab data blocks usually hold [15].
What to watch
- Whether the same destination TSAPs, 0x01FF and 0x0F02, are accepted by a physical Siemens CPU rather than a Snap7 server.
- Whether the series follows the ReadVar exchange with a WriteVar capture showing the function-code change on the wire.
- Whether the negotiated 480-byte S7 PDU moves under a different Snap7 configuration or a client asking for more.