This is the first of a series of entries explaining the configuration protocol used by Schneider-Electric devices, an mainly PLCs (Programmable Logic Controllers) to get configured. This is a proprietary protocol based on the well-known Modbus Protocol. These findings have been obtained through reverse engineering process and many hours of work.
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. Extra information
Part V. Modicon Premium PLCs specific function codes
INTRODUCTION
Schneider-Electric devices, an mainly PLCs (Programmable Logic Controllers) are able to understand a number of Industrial Protocols (Modbus, Bacnet, Ethernet-IP, Canopen, etc). However, in order to be configured they use a proprietary protocol which is based on the Modbus Protocol. During the last year I have been researching on it, and in this set of articles I will explain all I have found about this protocol.
This research process was performed at the CICLAB Laboratory of the University of Leon and thanks to the support of the SUPPRESS group of this University.
As I have not found this proprietary protocol explained anywhere I think this set of articles can be very useful to understand how the Schneider-Electric PLCs and Schneider-Electric SCADA and engineering software (Unity Pro) communicate.
The protocol (which I will call UMAS based on the name of the DLLs and JAR files used to handle it), is based on the old Xway Unite protocol, used by old Telemechanique PLCs, which is well explained here.
Umas protocol is used to configure and monitor the Schneider-Electric PLCs. It is based on the well-known modbus protocol and uses one of the reserved Function Codes specified in the Annex A of the Modbus Protocol Specification (Function Code 90 or 0x5A in hexadecimal).
Actually Wireshark detects this traffic as Modbus with Function Code 90:
The following is the PDU structure of a Modbus request or response:
When Schneider-Electric PLCs receive a modbus packet, it checks if the Function Code is 0x5A and if so, some specific libraries are used, othewise, the modbus request is treated normally, returning or modifying the specified register(s) or coil(s) of the PLC.
The Function Code of the Modbus protocol indicates the PLC what does the following (in the same packet) data mean, what it is for and how to treat it. The Modbus Protocol Specification explain the meaning of 21 different Function Codes. However, in its annex A, it mentions that a total of 11 new Function Codes are reserved for a future use.
The UMAS protocol uses one of these reserved function codes, Function Code 90 (or 0x5Ah) both for the requests to and replies from the PLC. It is little-endian which is strange as modbus is big-endian and it is based in modbus.
Metodology followed
To reverse engineer this protocol I have had a series of sources. Most of my knowledge has been obtained from real UMAS traffic. I passed many hours researching on UMAS traffic and as someone said... "after a period of time you start finding patterns".
Another source of information was the different PLC Firmware versions that can be downloaded from the Schneider-Electric website. Newest firmware versions are digitally signed but older ones are simply ZIP files which in some cases include JAR files with parts of a UMAS handling library.
Finally a third source of information were the DLL files used by the Unity Pro software, although they were never decompiled or hacked.
The UMAS Packet
The UMAS Packet starts with a 16 bit field that Specify a "UMAS Function Subcode", followed by a variable number of bytes which confirm the UMAS packet payload:
In my research work I have found 28 different "UMAS codes" and in most cases I could identify what was their function. The following is the list of UMAS function codes found:
- UMAS Function Code 0x01 - INIT_COMM: Initialize a UMAS communication
- UMAS Function Code 0x02 - READ_ID: Request a PLC ID
- UMAS Function Code 0x03 - READ_PROJECT_INFO: Read Project Information
- UMAS Function Code 0x04 - READ_PLC_INFO: Get internal PLC Info
- UMAS Function Code 0x06 - READ_CARD_INFO: Get internal PLC SD-Card Info
- UMAS Function Code 0x0A - REPEAT: Sends back data sent to the PLC (used for synchronization)
- UMAS Function Code 0x10 - TAKE_PLC_RESERVATION: Assign an "owner" to the PLC
- UMAS Function Code 0x11 - RELEASE_PLC_RESERVATION: Release the reservation of a PLC
- UMAS Function Code 0x12 - KEEP_ALIVE: Keep alive message (???)
- UMAS Function Code 0x20 - READ_MEMORY_BLOCK: Read a memory block of the PLC
- UMAS Function Code 0x22 - READ_VARIABLES: Read System bits, System Words and Strategy variables
- UMAS Function Code 0x23 - WRITE_VARIABLES: Write System bits, System Words and Strategy variables
- UMAS Function Code 0x24 - READ_COILS_REGISTERS: Read coils and holding registers from PLC
- UMAS Function Code 0x25 - WRITE_COILS_REGISTERS: Write coils and holding registers into PLC
- UMAS Function Code 0x30 - INITIALIZE_UPLOAD: Initialize Strategy upload (copy from engineering PC to PLC)
- UMAS Function Code 0x31 - UPLOAD_BLOCK: Upload (copy from engineering PC to PLC) a strategy block to the PLC
- UMAS Function Code 0x32 - END_STRATEGY_UPLOAD: Finish strategy Upload (copy from engineering PC to PLC)
- UMAS Function Code 0x33 - INITIALIZE_UPLOAD: Initialize Strategy download (copy from PLC to engineering PC)
- UMAS Function Code 0x34 - DOWNLOAD_BLOCK: Download (copy from PLC to engineering PC) a strategy block
- UMAS Function Code 0x35 - END_STRATEGY_DOWNLOAD: Finish strategy Download (copy from PLC to engineering PC)
- UMAS Function Code 0x39 - READ_ETH_MASTER_DATA: Read Ethernet Master Data
- UMAS Function Code 0x40 - START_PLC: Starts the PLC
- UMAS Function Code 0x41 - STOP_PLC: Stops the PLC
- UMAS Function Code 0x50 - MONITOR_PLC: Monitors variables, Systems bits and words
- UMAS Function Code 0x58 - CHECK_PLC: Check PLC Connection status
- UMAS Function Code 0x70 - READ_IO_OBJECT: Read IO Object
- UMAS Function Code 0x71 - WRITE_IO_OBJECT: WriteIO Object
- UMAS Function Code 0x73 - GET_STATUS_MODULE: Get Status Module
Function codes
Requests and responses
First of all we need to keep in mins that this is a request/response protocol. Function Codes are only sent in request message (generally sent from engineering PCs and SCADAs, but that could also been sent by PLCs themselves if working in a master/slave scenario).
Response do no carry the function code and only carry the response code. Therefore the UMAS packet structure would be the following:
[ TCP Packet ] - [ Modbus Header ] - [5A] - [ UMAS CODE (16 bit) ] [ UMAS PAYLOAD (Variable) ]
All UMAS Responses have a similar structure as well:
[ TCP Packet ] - [ Modbus Header ] - [5A] - [ RETURN CODE (16 bit) ] [ UMAS PAYLOAD (Variable) ]
The return code can have two possible values:
0x01 0xFE - Meaning OK
0x01 0xFD - Meaning Error
UMAS Function Code 0x01 - INIT_COMM
Request
The request message sent to the PLC has no payload. Therefore the 0x01 request message is as simple as:
[ TCP Packet ] - [ Modbus Header ] - [5A] - [ 00 01 ]
or
[ TCP Packet ]- [ Modbus Header ] - [5A] - [ 00 01 00]
This is a very simple message (surely the simplest one). It can be seen like this in Wireshark:
If we follow the UNITE protocol documentation, the last "00" could be the "Category Code". However the similarities between both protocols are not frequent, so, this is only a guess.
Note: It's important to note that the UMAS Payload first byte (00 in the previous example), can be different in newer firmware versions. The image example was taken from a Schneider Electric PLC running a 2.10 Firmware version.
This 0x01 message can be sent anytime after the connection is established. It requests some information from the PLC.
Response
The response from the PLC to a 0x01 request depends on wether the PLC is currently "reserved" or not. When a engineer connects to a PLC it automatically "reserves" its use so that no other engineer con connect simultaneoulsy to the PLC. As we will see, this feature can be easily broken.
If the PLC is not currently reserved, the response from the PLC is the following:
As it can be seen, the response informs us about the maximum frame size (bytes "fd 03" - meaning 1022 bytes), the firmware version (bytes "10 02" - meaning firmware version 2.10), followed by 9 bytes that store an internal code and the owner's name size, in this situation, "0".
If the PLC is currently reserved, apart from the whole previous payload, a variable number of bytes is responded, storing the Client_ID Name. The following 0x01 response come from a reserved PLC:
As it can be noted, frame size, firmware version, remains unaltered but the 32-bit internal code has changed and the following fileds have changed. oOners's name has become "OWNED" and owner's name size is now 5 for the 5 letters of "OWNED".
Therefore, the structure of this response message is the following:
[ TCP Packet ] - [ Modbus Header ] - [5A] - [ Error Code (16bit) ] - [ Max frame Size (16bit) ] - [ Firmware Version (16 bit) ]
[ Unknown 1 (32 bit) ] - [ Unknown 2 (32 bit) ] - [ Client name length (8 bit) ] - [ Client Name (variable) ]
Note: The PLC Reservation process will be explained later on Function Codes 0x10 and 0x11. The reason why this internal code has changed is explained there.
UMAS Function Code 0x02 - READ_ID
Request
This request allow Schneiders's Unity Pro to discern what's the device it is connecting to. This request can be sent any time during the connection. It's actually the first request sent by Unity Pro int teh connextion start process (which will be explained later).
The request message sent to the PLC has no payload. Therefore the 0x02 request message is as simple as:
[ TCP Packet ] - [ Modbus Header ] - [5A] - [ 00 02 ]
For this simplicity, no image will be added showing a 0x02 request.
Response
This is an example of response message:
The response from the PLC allows Unity Pro to know the device name (“BMX P34 2020”) in this case, with this text's length (“0C – 12 in this case), the PLC firmware version (2.10), the Ir (8), the HwId (“06 01 03 01”) and the FwLoc (“00 00 00 00”). We know this information because it is also sent during an FTP connection during a DINF command reply.
Therefore the 0x02 command response has the following structure:
[ TCP Packet ] - [ Modbus Header ] - [5A] - [ Response Code (16) ] - [ PLC Family (8) ] - [ PLC Type (8) ] - [ PLC ID (16) ] -
[ PLC Model(16) ] - [ Unknown (16) ] - [ Firmware version (16) ] - [ Patch version (16) ] - [ IR /FWVer (16) ] - [ HwId (16) ] -
[ FwLOC (16) ] - [ Deive name text length (8) ] - [ Device name (variable) ] - [ # of Memory banks ] - [ Memory bank 1 (64)]
...
[Memory bank N (64 bits) ]
Explanation:
- Error Code - As mentioned earlier this 16 bit value can be 0x00FD (Error) or 0x00FE (OK)
- PLC Family (1 byte). In this case value 05 means Modicon. This information was taken from Unity DLL.
- PLC Type (1 byte). In this case value 30 means odicon M340, BMX P342020. I don't have a list of PLC types. This information was taken from Unity DLL.
- PLC ID. (2 bytes). Unknown meaning. This information was taken from Unity DLL.
- PLC Model (2 bytes). In this case "00 00". This information was taken from Unity DLL.
- Unknown (2 bytes). 2 bytes which remain unknown. Further investigation will probably shed some light into these values.
- Firmware Version Internally named "Prod Version". This 16 bit value store in reverse order the firmware version of the PLC 0x1002 means V2.10. Different version values have been checked 0x7002 means v2.70, 0x9002 means v2.90, etc.
- Patch Version. 16 bit field that informs about the patching level inside a firmware version
- The Ir 16 bit value has an unknown meaning for me. I know its name from the DINF response during a FTP connection
- The HwId 32 bit value has an unknown meaning for me. I know its name from the DINF response during a FTP connection
- The FwLoc 32 bit value has an unknown meaning for me. I know its name from the DINF response during a FTP connection. Probably firmware can be store in different locations in PLC's memory.
- Device type text length is the number of bytes coming in the next field. In this case "0c" meaning 12 bytes.
- Next bytes retrieve the PLC type. In this case it's a Schneider Electric's Modicon M340 (internally coded as BMX P342020).
- # Memory banks. 1 byte that informs how many memory banks the PLC has. The SD Card is considered a Memory bank (that's why in this case the value is 2).
- Memory bank 1 (8 bytes).
- Memory bank 2 (8 bytes).
Note: Each memory bank 64 bits fields has the following structure of fields:
[ Bank type (8) ] - [ Folio (8) ] - [ Status (16) ] - [ Size (32) ]
Note: Bank Type can be 01 for PLC memory or 04 for SD Card. Other possible value (not tested) would be for Memory bank slots
UMAS Function Code 0x03 - READ_PROJECT_INFO
Request
The read project info request has a 8 bit subcode value meaning which project information needs to be read. The function 0x03 request message has the following structure:
[ TCP Packet ] - [ Modbus Header ] - [5A] - [ 00 03 ] - [ Subcode ]
Subcode is an 8 bit value and only values 00, 01, 02, 03 and 04 have been seen.
Response
The “00 03 00” request allows Unity to get information about the project that's currently running on the PLC. The response from the PLC is similar to the following:
We still don't know what first 9 bytes mean, but they are sent twice, so this information must be important.
After them, the project creation time and date is sent (again twice), finally the PLC send the version number and the project name with its text's size.
These bytes are also found in the APX file (which will be explained later) in offset 29Ch. You can find a complete explanation of the APX file structure in [this link].
Therefore the response stucture is the following:
[ TCP Packet ] - [ Modbus Header ] - [5A] - [ Response Code (16) ] - [ Unknown (9 bytes) ] - [ Unknown 2 (9 bytes) ] -
[ Modification date (8 bytes) ] - [ Modification date Rep (8 bytes) ] - [ Project Version (16) ] - [ Unknown (16) ] -
[ Project Name length (8) - [ Project name (variable) ]
Note:
The modification date, which is sent twice can be splitted in two fields (modifcation time and modifcation date). Modification time is a reverse translation of values. In the example the 4th (0e) is 14 (or 2 in the afternoon). 24 is 36 in decimal, and 37 y 55 dec. The value 04 is unknown. On regards to the date e0 07 hex is 2016 dec, 02 11 is 11th of february. The date when this logic was uploaded to the PLC
After sending a 00 03 00 request, generally a “00 03 04” request is sent. This request ask for the different Objects stored in the PLC memory. A response like the following is replied:
In this response, after the response code (00FE in this case), come 9 blocks of 12 bytes. with the following structure:
- Response code (16 bit).
- # of blocks (8 bits)
- Block 1 mNb (32 bits). This is the size of the object
- Block 1 mBase (16 bits). This is the memory base
- Block 1 mBlock (16 bits). This is the memory block. 0x2a are System bits, 0x2b are system words, 0x2c, etc.
- Block 1 mOffset (32 bits). This the memory position of the beginning of the block.
- Block 2 mNb, mBase, mBLock and mOffset.
...
- Block N mNb, mBase, mBLock and mOffset.
In the example above 9 objects are stored in the PLC (actually only 5). Object 1 is 128 bytes long, starting ofrom position 0 of memory block 0x2a (System bits or coils and with offset 00004. The other object maintain the same structure.
We tried to send requests with another number after “00 03”. Only three of them replied:
- “00 03 01” → Replied with 60 unknown bytes
- “00 03 02” → Replied with 77 unknown bytes
- “00 03 03” → Replied with 67 unknown bytes. Part of the information replied here is the same when requesting a 0x20 message on block 30
UMAS Function Code 0x04 - READ_PLC_INFO (GETPLCSTATUS)
During a Unity pro connection, the “00 04” request is sent every 0.3 seconds. It helps both Unity Pro and the PLC to keep the connection alive.
This is an example of a 0x04 message response from the PLC:
After the response code, the three first bytes are ignored by the PLC (at least in the implementation I reversed). So in the example above, bytes with value "02 86 80" are ignored. The next byte is the number of 32-bit blocks read (in this case 6 blocks). So, the first block is "48 ce 01 01", the second is "20 6e 00 18" and so on.
For every block, values are resversed (First byte last, and last byte first). A variable named mAppliCheckSum is calculated with the sum of the reverse values of blocks 3 and 4 (in the example this checksum would be 0x18906e20+0x18906e20). This is a checksum value used to check if the PLC is legitimate or not.
The following bytes are mostly unknown. The could probably some system bits and words. We still don't know which ones.
What we know is that the 6th byte from the end let us know if the PLC is running or not. In that byte, the bits 7 and 8 let us know if the PLC is on or off. If the PLC is off bits 7 and 8th are 0-1 and if the PLC is on, the bits are 1-0. Therefore if that byte is 1 (like in the example) the PLC is off and if the byte is 2, the PLC is on and running.
Requests “00 01”, “00 02”, “00 03” and “00 04” allow Unity to fill this following Unity's dialog box:
UMAS Function Code 0x06 - READ_CARD_INFO
A request “00 06 00” allows Unity Pro to know what specific SD Card is inserted in the PLC.
The response from the PLC informs Unity about the specific model of SD card and internal info:
After the error code bytes, there are 6 bytes which still remain unknown. After them next byte is the length of the SD Card name. and the following bytes are a null-terminated string with the SD Card name.
This is the structure of this response:
[ TCP Packet ] - [ Modbus Header ] - [5A] - [ Response Code (16) ] - [ Unknown (6 bytes) ] -
[ SD Card name length (8 bytes) ] - [ SD Card name (variable) ]
UMAS Function Code 0x0A - MIRROR/REPEAT
This request may have any kind of sinchronyzation purpose, but it's not very clear to me. This message is sent during the initialization process (which will be explained later) and the PLC just replies with the same payload it was sent in the request.
The following is a UMAS MIRROR request:
...and this is the response received from the PLC.
As it can be seen, the payload is sent back in the next reply from the PLC. The UNITE protocol has a very similar message (named MIRROR, but with a different Function Code). For UNITE, this message purpose is stated to be "to test the correct routing of data between two communicating
devices. It is also used for carrying out performance measurements."
UMAS Function Code 0x10 - TAKE_PLC_RESERVATION
This message enables a client (Unity Pro - or an attacker) to partially or completely reserve the functions of a PLC. After this message is sent, if other client (Unity Pro) tries to connect to the PLC (by sending, among others, this message), the PLC responds with an error message.
The following is an example of this message:
The structure of this message is the following:
[ TCP Packet ] - [ Modbus Header ] - [5A] - [ 00 (by now) ] - [ 10 ] - [ Unknown 1 (32 bit) ] -
[ Client name length (8 bit) ] - [ Client Name (variable) ]
Once a client reserves a PLC giving a client name, next 0x01 request will respond with the client name given during the reservation process, as can be seen in the following image:
The first message is a 0x01 response message received from the PLC, when it has not been reserved yet. Then a 0x10 TAKE_PLC_RESERVATION message is sent. When another 0x01 message is sent, the PLC responds with the third message in this image. It can be stated, the unknown value passed in the TAKE_PLC_RESERVATION is returned in the 0x01 ID message.
UMAS Function Code 0x11 - RELEASE_PLC_RESERVATION
This request is as simple (“00 11”) as its reply (“00 FE”), which is an OK.
It's used by a client to release the PLC reservation. After this message any other client will be able to reserve the PLC again.
UMAS Function Code 0x12 - KEEP_ALIVE
This request is as simple (“00 12”) as its reply (“00 FE”), which is an OK.
We think it is a simple “I'm still here”, and that it's sent when a period of milliseconds expire with no message sent by Unity.
In other words, it's a keep-alive message.
This is all for today. In part II we'll talk about other interesting Function Codes that can be used to read and write values directly to/from the PLC.