Support : Knowledge base

Knowledge Base

Welcome to OPAL-RT’s Knowledge Base

OPAL-RT offers a repository of support information for optimal use of its technology.

Loading…

Please note that OPAL-RT knowledge base is not fully optimized for mobile platforms.

For optimal experience, use a desktop computer.

Reference Number: AA-02307// Views: AA-02307// Created: 2024-04-11 12:00:00// Last Updated: 2024-04-12 06:24:21
General Article
[EXata CPS 8.1.3.0] MODP - How To Calculate the Start Byte? (TCP, Modbus, GOOSE)

Question:

When I use the Modify Packet Attacks editor, there is the field called 'Start Byte':


According to the documentation from Keysight, the very first start byte is the first payload byte, but how can I find it / calculate the byte of interest based on that?


Answer:

This is a good question! The payload depends on the type of protocol used. We will present how to calculate the start byte for four different examples.

However, before, for any of the protocols, we have to start by capturing some packets with TShark.

1- Start the co-simulation setup (whether RT-LAB and EXata or HYPERSIM and EXata)

2- Connect to the simulator via SSH

3- Run the command:


tshark -i 'NIC interface' -w 'filename'.pcap


NOTE: Do not forget that TShark makes some system calls that can break the isolation of the cores and thus might or might not create overruns! Only use TShark for debugging purposes.

4- In this example, we are using veth1 and the filename will be the protocol.



5- After a few seconds, press CTRL + C to stop the recording.

6- Using the SSH browser, press refresh, then download and open the file using Wireshark.


EXAMPLE 1 - TCP - 1 float

The first example is the simplest one: TCP. In this example, we are sending 1 float value.

NOTE: The option swap byte order must be checked (available in both RT-LAB and HYPERSIM TCP Driver), otherwise the byte order will be inverted to what EXata is expecting.


The packet in Wireshark looks as follow:



We can see that the data is 4 bytes (1 float uses 4 bytes), and if we decode the value 0x3f800000 using a Python script from an AI chat, we get the float value of 1.



In the case of TCP, the first payload byte is the first byte of data. Therefore, to modify the float value, then we need to set the Start Byte as 1.

Here is an attack example in EXata to multiply the value by 5:


In the screenshot above, note the following:

1- We did not set any filter since there is no other communication going through that port

2- The ACK messages do not have any payload, so they will not be affected


Recording again using TShark (this time on veth2, since veth2 is the data coming out of EXata, i.e. the modified data), we can see that the data has been multiplied:



Using the same Python script, we can confirm that 0x40a00000 is equal to 5. The attack worked as expected!


Example 2 - TCP - Several Data Types

Ok, let's spice things up a little bit. Same example as example 1, but this time, we are sending the following:

2 x float

2 x double

1 x uint8

1 x uint16

Here is the configuration in RT-LAB (it would be the same in HYPERSIM too):



In this example, we want to replace the uint16 from 1989 to 2024. How do we calculate the start byte?

The logic is the same as before, let's start the co-simulation setup and make sure it is working as expected.

Then, let's record using TShark. The Wireshark data looks as follow:



The payload is now 27 bytes. Does it make sense? Knowing that:

1 float is 4 bytes and we have 2 (so 8 bytes in total)

1 double is 8 bytes and we have 2 (so + 16 bytes in total)

1 uint8 is 1 byte and we have 1 (so + 1 byte)

1 uint16 is 2 bytes and we have 1 (so + 2 bytes)

8+16+1+2 = 27 bytes in total, this is as expected!



Therefore, the uint16 is composed of byte 26 and byte 27. We can also validate that 07c5 is indeed 1989 using the Windows calculator in programmer mode: 



Perfect. The last thing is to implement the attack in EXata. In this case, the start byte will then be 26.



Recording again using TShark on veth2, we can indeed see that the uint16 has been replaced by 07E8 (which is 2024 in uint16).



Not that complicated, right?


Example 3 - Modbus

The logic is the same for all other protocols, the only thing that changes is where the payload is starting, so let's use Modbus as an example.

In this example, we are reading one INT32 holding register and one coil every second.


Recording using TShark, we can see the different read queries their responses.



Here, notice that the int32 is actually divided into two uint16 registers. This is normal. The Modbus standard is using only uint16 registers, but the OPAL-RT driver is 'merging' two of those to store an int32.

Taking a look at this, the first thing we need to do is to find a filter, we do not want to do an attack that modifies all the Modbus messages, only a specific one. If we take a look at the column 'Frame length on the wire', we can see that the queries are 78 bytes, the acknowledges are 66 bytes, the response from the holding register is 79 and the response for the coil is 76. Since they are all different sizes, we can use that.

For Modbus (and all other application based protocols), the payload includes the header of the protocol!



Therefore, doing an attack using a start byte = 1 would modify the transaction identifier (and we don't want that) ! What we want is the 'd6', which is the first byte of the holding register.



Therefore, the start byte (in this example) is 10 for the holding register. Let's suppose we want to add 5 to this value. The attack in EXata would look as follow:



Note that the payload is set to 13 in the filter, so that it only attacks the messages from the holding registers responses. In this example, the source and destination IP addresses are optional, since no other messages has a payload of size 13.

Note also that the start byte is 10, not 9. Some may think that we should do 13 (the payload size) minus 4 (1 uint32 is 4 bytes), but we want the first byte of the int32, not the last byte of the previous value.

Recording again with TShark, we can clearly see the +5 done (it is easy to see for uint and int, since it is not some kind of encoding like float or double):



Example 4 - GOOSE message

GOOSE messages are a bit different, since they are working on the MAC layer, i.e. they do not have any IP addresses.

GOOSE messages to work properly with EXata, the recommendations are: 

- Check the option 'Enable fixed-length encoding for GOOSE messages' (this way, the payload will not change through time)

- Uncheck the use of VLAN (makes the decoding easier)



Also, always set the attack on the publisher side (i.e. on the node in EXata mapped to the publisher, not on the node mapped to the subscriber!).


Here is an example of a GOOSE message with one float value in it:



The payload is the complete GOOSE message so the first start byte is 0x00 in the example below (i.e. the 15th byte from the beginning of the message).



The first byte of the float value is all the way down there:



So, we just have to count. In this example, the answer is 136. So, the attack in EXata would look as follow:


Note here that the destination MAC address is optional if there is only one GOOSE being published. The MAC address can be found in the Wireshark packet too or in the IO interface:



This is it! It is not more complicated than that.