This is the second part of a series of entries that explain the configuration protocol used by Schneider-Electric devices, an mainly PLCs (Programmable Logic Controllers) in order to be configured. This is a proprietary protocol based on the well-known Modbus Protocol.
INDEX
Part I. Introduction, initialization phase, functions codes used in the initialization phase
Part II. Function codes used to read and write memory values from/to memory
Part III. Function codes used to deal with logic programs, and work with the PLC
Part IV. Other extra function codes
Part V. Modicon Premium PLCs specific function codes
UMAS Function Code 0x20 - MEMORY BLOCK READ
This is a very important Function Code. It allows a client to read a specific offset from a memory block of the PLC device.
The request format is the following:
The PDU starts with the UMAS function code 00 20, the third byte meaning is unknown, sometimes its value is “00”, sometimes is “01”.
Next 16-bit value reference the block number, in this case “00 13”. Little Endian. Next 16 bits are the starting offset. In the first request is “00 00”, in the following is the sum of the previous request offset plus the previous request' requested bytes. Little Endian.Next 16 bits are unknown but they are always “00 00”. Always.
The final field is the number of bytes to read. It's little endian format. This value depends on the maximum frame size, negotiated in the “00 01” message. It also depends on the size of the memory block.
This is an example of a MEMORY READ request:
In this case we are requesting 0x64 (100) bytes from the 0013 block, starting from the offset 0.
The response from the PLC is like the following:
In this case, first two bytes (00 FE) say "OK", followed by "00" (unknown meaning). Next two bytes say "you requested 100 bytes, here they are", followed by the 100 requested bytes.
It's perfectly possible to read the whole PLC memory by sending requests like these. I performed a short investigation on this and found that:
Different memory block values were tried from the Modicon M340 PLC. The following values replied with information:
“01 01”, “01 11” to “01 18”, “01 21” to “01 2e”, “01 32”
- Blocks “01 01” to “01 09” replied with the same information, a total of 23Kb.
- Block 0x11h replied with a total of 2048 bytes.
- Block 0x12h replied with 22Kb of information.
- Blocks 0x13 to 0x32 replied with 65Kb of information.
The information returned by the PLC is mostly unintelligible but it seems to be the VxWorks internal OS memory as the first possible memory block starts with “VxWorks 6.4“ (in a Modicon M340).
We known (from a Unity Pro request) that block "01 2E" is asking for "instances of unlocated variables” and it can be seen that unlocated variables are in block 2E and are 03 F7 bytes long.
On the other hand when in Unity Pro we requested for the instances of function blocks, the request sent was for block "01 32" with the same length "F7 03"
UMAS Function Code 0x21
It seems that there is no UMAS Function Code 0x21. I never saw a 0x21 request in any traffic analysis performed by Unity Pro against a Schneider PLC. However, it seems strange that if 0x20, 0x22 and 0x24 function codes read different memory values from the PLC and 0x23 and 0x25 function codes allow to write values, there should be a 0x21 function code as well.
Some tests were performed and all of them returned with error. Possibly PLCs I tested were somehow memory protected and was unable to detect it. If this function code actually exist should have a similar packet structure as the 0x20 request.
UMAS Function Codes 0x22 & 0x23 - READ & WRITE SYSTEM BITS & BLOCKS
Internally Schneider electric source code call these requests UMAS_READ_BOL and UMAS_WRITE_BOL. I don't know exactly what does "BOL" mean but these requests can be used to read and write system bits and words.
There are two ways to read and write system bits and blocks. One of them is using messages “00 22” and “00 23”, the other one is using message “01 50”.
By far the simplest is the first one, but to send it we need first to do some previous work. A CRC value is needed in order this request can return valid values. To obtain this CRC a “00 04” request message needs to be sent and the return CRC32 value needs to be captured.
To read system bits or words we need to use "00 22" request. To write a system bit or word, we need to use "00 23" request.
Reading System bits and words
The packet structure of a 00 22 request is the following, but first let's have a look to a real example:
- After the UMAS code (“00 22”), four bytes are sent. With every strategy (logic) uploaded to the PLC these four bytes seem to be different. After a little bit of research, they are the CRC request in message “00 04” but shifted left one bit, that means, multiplied by 2 (with little-endian format). I will explain how to obtain this "shifted CRC" later.
- After these 4 bytes, the next byte shows the number of System words that are going to be read. In this only 1 (“01”) system word will be read.
- Next byte ("02") is the data type to be read. There is an internal mapping from "02" to "Word" (16 bits)
- Next two bytes (in little endian) "2B 00" indicate the memory block number were these value(s) will be read from. "2B 00" (43 dec) is the "System Words" block.
- Next byte is always 1.
- Next two bytes indicate the base offset of the memory block read. It's codified in little endian.
- The final byte is the relative offset. To calculate it follow these instructions:
If we are requesting a System Bit, we have to add 4 to the number of the system bit. For instance if we are request System bit 50, the last byte of the request will be 50 + 4 = 54d ==> 36h
If we are requesting a System Word, we have to add 80 to the number of the system Word multiplied by 2 (because is a word value). For instance if we are request System Word 50, the last byte of the request will be 50 x 2 = 100 + 80 = 180 ==> b4h
In this second case if the System Word number is higher than 88, the base offset is increased in 1. For instance, let's check System Word 90:
90 * 2 = 180 + 80 = 260 → 01 04h
Therefore the Base offset will be 01h and the relative offset 04h. The last three bytes of the request will be:
01 00 04h
An example with 3 system bits and words would be:
00 22 -- 04 e9 0d 02 – 03 -- 02 - 2b 00 - 01 - 00 00 - b8 -- 01 - 2A 00 - 01 - 00 00 - 06 –- 02 - 2b 00 - 01 - 00 00 - 58
We would be reading two system words (2B 00) and one system bit (2A 00). System words would be “00 B8”, which in decimal is 184 – 80 = 104 / 2 = 52 ==> SW%52 ...and 58h → 88d → 88 -80 = 8 / 2 = 4 → SW%4
The system bit would be 6 - 4 = S%2
Writing System Bits and Words
In this case we'll use UMAS Function Code "00 23". The structure is very similar, that's why we do not include any image on it:
- After the UMAS code comes the same “shifted CRC” 4 bytes mentioned previously that will be explained later.
- After these 4 bytes comes one single byte that is always "01".
- Next byte indicates the data type (01 for bit, 2 for word, 3 for dword, etc).
- Next two bytes indicate the memory block number, if it's a System bit or System Word ("2A 00" means System bit, "2B 00" means System Word).
- Next two bytes (in little endian) indicate the number of system bit o system word (The offset). This number is codified as previously explained:
System bit → N + 4 System word → (2 * N) + 80
- The last four bytes are the system bit or word value, in big endian. If it's a System bit, the values can be “00 00 00 00” or “00 00 00 01”.
So, for instance, the following UMAS message:
00 23 - C4 48 C4 37 - 01 - 02 - 2B 00 - B4 00 - 00 00 3A 30
...will be used to write value 00003A30h in System Word #SW50.
The response in this case will be “00 FE 01 00 00”, as only one value was written correctly. Otherwise, if any error happened, “00 FD 01 00 00” will be returned.
How to calculate the 4-byte “shifted CRC” value
- In our example, the “00 04” message was replied with the following packet:
As it can be seen, in this case the CRC32 read was "82 F4 06 01".
- It's little endian, so the real value is "01 06 F4 82". Now let's shift left one bit. The result is "02 0D E9 04"
- Now put it back to little-endian: "04 E9 0D 02"
This is the value we will use when creating "00 22" requests.
There is another way to read and write system bits and blocks. It uses function code 0x0150. It will be explained in a future section of this document.
UMAS Function Codes 0x24 & 0x25 - READ & WRITE SYSTEM COILS & HOLDING REGISTERS
"00 24" request can be used to read coils or holding registers.
"00 25" request can be used to write coils or holding registers.
The request structure of the "00 24" request, is very similar to the previously used by the “00 20” requests:
- The third byte meaning is the number of coils or registers to read. It is usually "01".
- Next byte is always "00"
- Next byte is the Object Type (02 for Coil, 03 for Holding register...)
- Next 32 bits are the starting offset. Little Endian.
- Last 16 bits are the number of coils or holding registers to be read. Little endian.
The "00 25" request structure is a little bit different. At the end of the request we have to add the coils/registers values we are going to write into the PLC:
Let's see an example. In this case we are going to write values 2323h and 5632h to holding registers 1 and 2 using a UMAS request:
"00 25 - 01 - 00 - 03 - 01 00 00 00 - 02 00 - 23 23 56 32"
UMAS Function Codes 0x26 - ENABLE/DISABLE DATA DICTIONARY
I have never seen this request from Unity Pro traffic. However in the piece of internal code I was able to obtain, I found this function code described. I don't really understand what the Data Dictionary is, but I could find that this request structure is the following:
After the function code there's only 1 byte with possible values 0 and 1. Value 0 disables Data Dictionary. Value 1 encables Data Dictionary.
Return codes are very simple as well, 0xFE means OK and 0xFD followed by an unkown bunch of bytes means error.
And this is all for today. In the following part we'll talk about function codes used to upload and download the programming logic of the PLC.
Hello,
ResponderEliminarthe code 0x21 exist, it is WriteMemoryBlock
the request is
type: 0 ebool / 1 Byte
Address: Block (Word) Offset (Dword)
Length: number of bytes in the following data
Data: array of Length bytes