Redes Sociales

martes, 4 de diciembre de 2018

The looking glass service

Many internet agents offer (ISPs, IXPs, etc) what it's called the looking glass service . Only in this web site a full list of 1150 looking glass services are offered: http://www.bgplookingglass.com.

Looking glass services are a great way to understand if a target is up and how to reach it.

It's important to note that the Looking Glass service can be configured on Juniper, Cisco (IOS o IOS-XR), Extreme (NetIron), Quagga, BIRD, OpenBGPd, Vyatta/VyOS/EdgeOS, or FRRouting routers.

OTOH, it's necessary to install a Looking Glass client in a web server in order to be able to use the service. There exist a number of Looking Glass (often called LG) service software packages that can be installed in a web server. . In Ubuntu/Debian we can find:

# apt-cache search looking | grep glass
rancid-cgi - looking glass CGI based on rancid tools

However, the only documentation found about this package is this website, which means it's not widely used.

We al have MRLG (See http://mrlg.op-sec.us/), but it seems quite old...

Nevertheless, the typical LG software used is the standard version of Looking Glass obtained from Version6.net. A good source explaining how to install it in a CentOS distro is: http://amar-linux.blogspot.com/2013/06/setting-up-bgp-looking-glass-centos-6.html. It can also be found in its github reporsitory: https://github.com/version6net/lg.

All of them are services developed in Perl, however we can find other solutions developed in PHP, IE (https://github.com/respawner/looking-glass ...which is fully documented in https://looking-glass.readthedocs.io.

HOW DOES IT WORK

For the normal functioning of the Looking Glass service it's necessary to have access to router's ports 2601 and 2605. The software generally connects to these ports via telnet. Generally credentials for this telnet are stored in a config file and the LG software simply connects via telnet to the router and sends BGP commands, returning the output to the web service. The typical BGP commands are like "sh ip bgp".

Therefore it's necessary to open 2601 and 2605 ports in the firewalls before the LG service to work properly. It will also be necessary to have a working NGINX or Apache Web server. Finally the appropiate libraries will have to be installed (depending on each solution).

On the other hand the router will have to be prepared to wait allow BGP commands remotely. For instance configuration for a Juniper OS (at least for the PHP solution previously mentioned) is the following:https://looking-glass.readthedocs.io/en/latest/juniper/. A restricted user in the router is recommended for the telnet connection and BGP command execution.

Trying to get a Bro Cluster to work

After developing an analyzer as a plugin, I had to develop a number of bro scripts to make my tool work. Finally they were 4 bro scripts. We prepared a Cluster infrastructure with two worker nodes, each with two network interfaces, one of them listening in two different VLANs and the other in the monitoring network. And a manager&proxy node running in the monitoring network only.

The installation was quite smooth. Used a Debian Jessy 64bits and followed instructions found in the Bro website. After installation, both workers and the manager seemed to work fine. Not exactly.

Briefly, in Manager node, bro scripts should be copied in

[1] /usr/local/bro/share/bro/site

...broctl should be configured modifying values of:

[2] /usr/local/bro/etc/broctl.cfg

...logs would be copied to:

[3] /mnt/logs/current

...given the different and deep-named directories I decided to create some symbolic links to them in my /home/bro directory:

root@Manager:/home/bro# ls -l
total 8
drwxr-xr-x 6 bro  bro  4096 Jun  7 13:38 aux_files
drwxr-xr-x 3 bro  bro  4096 May 31 13:30 bro
lrwxrwxrwx 1 root root   29 Jun  8 13:51 broctl.cfg -> /usr/local/bro/etc/broctl.cfg
lrwxrwxrwx 1 root root   17 Jun  8 13:50 logs -> /mnt/logs/current
lrwxrwxrwx 1 root root   29 Jun  8 13:50 site -> /usr/local/bro/share/bro/site

These are the steps to follow when any change have to be introduced in the configuration or bro scripts:

  1. Modify bro scripts located in [1]
  2. Modify (if necessary) broctl options in [2]
  3. launch Bro Control (broctl)
  4. check
  5. deploy

All broctl commands, which can be looked up using the 'help' command, are actually python scripts located in:

[4] /usr/local/bro/lib/broctl/Brocontrol

It's not difficult to understand what they are doing if you know a little bit of Python. For instance, deploy and install commands are run with the control.py python script.

Basically, when a 'deploy' is sent in broctl, the scripts in [1] are copied to:

[5] /home/bro/bro/spoolinstalled-scripts-do-not-touch/site

...both in the manager and all worker nodes.

Why these directories?. Well, this is configured in the broctl.cfg [2] file. In this file there are a number of variables that will allow us to configure directories and bro-scripts like:

  1. PolicyDirSiteInstall: Directory where all bro scripts are copied both in workers and manager nodes. By default is ${SpoolDir}/installed-scripts-do-not-touch/site. In my case is the directory mentioned in [5].
  2. SitePolicyPath: Colon-separated list of Directories to search for site-specific policy (bro) files. For each such directory, all files and subdirectories are copied to PolicyDirSiteInstall during broctl ‘install’ or ‘deploy’. By default is /usr/local/bro/share/bro/site.
  3. SitePolicyWorker: Space-separated list of bro scripts that should be run in EVERY worker node of the cluster. By default its value is local-worker.bro, which is an empty bro script. They will be copied to ALL nodes of the cluster when a 'deploy' or 'install' commands are launched. If we want to run different scripts in both nodes, the process consist in installing the script for one worker (in all nodes) and then, modify nodes.cfg file, modify this variable and send an 'update' command in broctl.
  4. sitepolicyworker=my-prot-worker-main.bro my-prot-aux-functions.bro
    
  5. sitepolicymanager: Space-separated list of bro scripts that should be run in the manager node. In my case this script manages notices generated by workers.
  6. sitepolicymanager=manager_notice_policy.bro
    
  7. LogDir: Location of the log directory where log files will be archived each rotation interval.
    LogDir = /mnt/logs
    

Of course, there are many other interesting options that can be modified for BroControl. Have a llo to the BroControl documentation website.

Using a bro analyzer as a plugin in a Bro Cluster

One of the problem I faced was that all bro scripts running in the worker nodes, monitored a proprietary protocol for which I had developed an analyzer as a plugin. If run standalone both in the manager and worker nodes, the script and the proprietary analyzer worked fine. But if run as a cluster, the scripts seemed not to understand proprietary network traffic. After a couple of checks I found that the plugin was not being 'loaded' by bro in the workers. Then I realized that the problem should be the Plugin directory where Bro was looking for the plugin. Soon I found that there are two different options in broctl.cfg file that could help me:

  • Env_Vars:A comma-separated list of environment variables (e.g. env_vars=VAR1=123, VAR2=456) to set on all nodes immediately before starting Bro. Node-specific values (specified in the node configuration file) override these global values.
  • Env_Vars=BRO_PLUGIN_PATH=/home/bro/aux_files/plugin
    
  • SitePluginPath:Directories to search for custom plugins (i.e., plugins that are not included with broctl), separated by colons.
  • SitePluginPath=/home/bro/aux_files/plugin
    

...but somehow, after deploying, it didn't work.

A little bit of research led to the /usr/local/bro/share/broctl/scripts/run-bro script, where I found that apparently only three environment variables were set, regardless of what I wrote in the broctl.cfg file:

...
echo "PATH=${PATH}" >.env_vars
echo "BROPATH=${BROPATH}" >>.env_vars
echo "CLUSTER_NODE=${CLUSTER_NODE}" >>.env_vars
echo "BRO_PLUGIN_PATH=${BRO_PLUGIN_PATH}" >>.env_vars
...

I just added the last line, et voila!, my analyzer was working fine!... well, not so fine. Some thing didn't work. Somehow some events were not noticed to the manager node, some log file were not created in the manager LogDir, and a config file was not read.



The fact of cluster logging

My first move was to read the stderr.log and stdout.log files stored in the manager node BUT, somehow, even if I filled my scripts with 'print' instructions, the stdout.log was empty!...

...until I found the /usr/local/bro/share/bro/base/frameworks/cluster/nodes directory. Here you can find some redef options for the worker, manager and proxy nodes. And one of these options was:

## Don't do any local logging.
redef Log::enable_local_logging = F;

...setting this option to True and deploying again, made the workers to start logging locally in the following directory of the worker nodes:

[7] /home/bro/bro/spool/worker-n/

There I found the reporter.log, that showed me that there was a mistake in one of my bro scripts. Curiously, the reporter.log file in the manager was empty, which means that logs are NOT COPIED from the workers to the manager node. That's the reason why two of my log files did not appear in the manager node but they were in the workers' log [7] directory.

The fact is, log files correspond to the node where you find them. stdout.log of the worker nodes will show the standard output of the bro scripts running in the workers, while stdout.log in the manager correspond to what the manager policy script is sending to the standard output.

Regarding the protocol log files, we find some network traffic messages in the Manager but these are not all the network traffic messages received in the worker, only about 1 out of 10 messages received in the worker nodes are actually logged in the manager, and this is something we still have to understand.



Opening a config file in the workers

My third problem was related with a config file that should be opened both by the workers and the manager bro scripts. I was trying to open it with something like:

Input::add_event([$source="config.cfg", $reader=Input::READER_RAW, $name="conf", $fields=Val, $ev=config_entry]);

The solution came when I realized that I had to use the full file path, which should be the same both in the worker and the manager nodes:

Input::add_event([$source="/home/bro/bro/spool/installed-scripts-do-not-touch/site/config.cfg", $reader=Input::READER_RAW, $name="conf", $fields=Val, $ev=config_entry]);

This made it work.



The cluster bro processes structure

Now I had to be able to send my local worker log files to the manager. First of all I tried to understand the process structure.

In every worker node there are three bro processes:

root@Mario:/usr/local# ps -ef | grep bro
root      25139      1  0 Jun08 ?        00:00:00 /bin/bash /usr/local/bro/share/broctl/scripts/run-bro -1 -i eth0 -U .status -p broctl -p broctl-live -p local -p worker-1 local.bro broctl base/frameworks/cluster my-prot.bro broctl/auto
root      25145  25139 20 Jun08 ?        03:38:50 /usr/local/bro/bin/bro -i eth0 -U .status -p broctl -p broctl-live -p local -p worker-1 local.bro broctl base/frameworks/cluster my-prot.bro broctl/auto
root      25148  25145  0 Jun08 ?        00:00:09 /usr/local/bro/bin/bro -i eth0 -U .status -p broctl -p broctl-live -p local -p worker-1 local.bro broctl base/frameworks/cluster my-prot.bro broctl/auto

So the first was the parent of one of the bro processes which forked to the other one. Parameters where passed to the run-bro script mainly on run bro as nohup with all the same parameters:

...
mybro=${bro}
...
nohup "$mybro" "$@" &
...

So, the question was, why is the working bro process forking?. lsof command was very useful for that:

root@worker-1:/usr/local# lsof | grep 25145 | egrep -v "\.bro|\.so|pipe" | grep 25186
bro:      25145 25186        root  cwd       DIR                8,1      4096     656821 /home/bro/bro/spool/worker-1
bro:      25145 25186        root  rtd       DIR                8,1      4096          2 /
bro:      25145 25186        root  txt       REG                8,1 134900600    1315052 /usr/local/bro/bin/bro
bro:      25145 25186        root    1w      REG                8,1       188     655382 /home/bro/bro/spool/worker-1/stdout.log
bro:      25145 25186        root    2w      REG                8,1        97     655383 /home/bro/bro/spool/worker-1/stderr.log
bro:      25145 25186        root    4u     IPv4             152650       0t0        UDP 192.168.1.10:41394->192.168.1.5:domain 
bro:      25145 25186        root  202u     pack             152653       0t0        ALL type=SOCK_RAW
bro:      25145 25186        root  260r      REG                8,1       739     657284 /home/bro/bro/spool/installed-scripts-do-not-touch/site/config.cfg
bro:      25145 25186        root  272u     unix 0xffff88007a6abb80       0t0     152656 socket
bro:      25145 25186        root  273w      REG                8,1 283722769     656833 /home/bro/bro/spool/worker-1/my-prot.log
bro:      25145 25186        root  279w      REG                8,1    301214     656838 /home/bro/bro/spool/worker-1/communication.log
bro:      25145 25186        root  280w      REG                8,1     42471     656841 /home/bro/bro/spool/worker-1/weird.log
bro:      25145 25186        root  281w      REG                8,1       447     656844 /home/bro/bro/spool/worker-1/reporter.log



root@worker-1:/usr/local# lsof | grep 25148 | egrep -v "\.bro|\.so|pipe" 
bro       25148              root  cwd       DIR                8,1      4096     656821 /home/bro/bro/spool/worker-1
bro       25148              root  rtd       DIR                8,1      4096          2 /
bro       25148              root  txt       REG                8,1 134900600    1315052 /usr/local/bro/bin/bro
bro       25148              root    0u     IPv4             151842       0t0        TCP 192.168.1.10:35948->192.168.1.20:47762 (ESTABLISHED)
bro       25148              root    4u     IPv4             152650       0t0        UDP 192.168.1.10:41394->192.168.1.5:domain 
bro       25148              root  202u     pack             152653       0t0        ALL type=SOCK_RAW
bro       25148              root  260r      REG                8,1       739     657284 /home/bro/bro/spool/installed-scripts-do-not-touch/site/config.cfg
bro       25148              root  273u     unix 0xffff88007a6ab040       0t0     152657 socket
bro       25148              root  279u     IPv4             151845       0t0        TCP 192.168.1.10:57286->192.168.1.20:47761 (ESTABLISHED)
bro       25148              root  284u     IPv4             151850       0t0        TCP *:47763 (LISTEN)
bro       25148              root  285u     IPv6             151851       0t0        TCP *:47763 (LISTEN)

So, the first bro process (parent), is responsible for listening on the monitoring interface and writing local log files, while the second (child) is responsible for communicating with the manager (two different connections) and listening from the proxy.

In the manager node, there are two processes for the proxy and another two for the manager:

root@Manager:/home/bro/site# ps -ef | grep bro
root      38193      1  0 Jun08 ?        00:00:00 /bin/bash /usr/local/bro/share/broctl/scripts/run-bro -1 -U .status -p broctl -p broctl-live -p local -p manager local.bro broctl base/frameworks/cluster notice_policy.bro broctl/auto
root      38199  38193  3 Jun08 ?        00:37:58 /usr/local/bro/bin/bro -U .status -p broctl -p broctl-live -p local -p manager local.bro broctl base/frameworks/cluster notice_policy.bro broctl/auto
root      38201  38199  0 Jun08 ?        00:00:21 /usr/local/bro/bin/bro -U .status -p broctl -p broctl-live -p local -p manager local.bro broctl base/frameworks/cluster notice_policy.bro broctl/auto


root      38236      1  0 Jun08 ?        00:00:00 /bin/bash /usr/local/bro/share/broctl/scripts/run-bro -1 -U .status -p broctl -p broctl-live -p local -p proxy-1 local.bro broctl base/frameworks/cluster local-proxy broctl/auto
root      38242  38236  3 Jun08 ?        00:40:23 /usr/local/bro/bin/bro -U .status -p broctl -p broctl-live -p local -p proxy-1 local.bro broctl base/frameworks/cluster local-proxy broctl/auto
root      38243  38242  0 Jun08 ?        00:00:03 /usr/local/bro/bin/bro -U .status -p broctl -p broctl-live -p local -p proxy-1 local.bro broctl base/frameworks/cluster local-proxy broctl/auto

Netstat showed the open connections among them and the workers.

root@Manager:/home/bro/site# netstat -tupan | grep bro
tcp        0      0 0.0.0.0:47761           0.0.0.0:*               LISTEN      38201/bro       
tcp        0      0 0.0.0.0:47762           0.0.0.0:*               LISTEN      38243/bro       
tcp        0      0 192.168.1.20:47762         192.168.1.10:35948         ESTABLISHED 38243/bro       
tcp        0      0 192.168.1.20:47761         192.168.1.20:38523         ESTABLISHED 38201/bro       
tcp        0      0 192.168.1.20:38523         192.168.1.20:47761         ESTABLISHED 38243/bro       
tcp        0      0 192.168.1.20:47761         192.168.1.11:57110         ESTABLISHED 38201/bro       
tcp        0      0 192.168.1.20:47762         192.168.1.11:45263         ESTABLISHED 38243/bro       
tcp        0      0 192.168.1.20:47761         192.168.1.10:57286         ESTABLISHED 38201/bro       
tcp6       0      0 :::47761                :::*                    LISTEN      38201/bro       
tcp6       0      0 :::47762                :::*                    LISTEN      38243/bro       
udp        0      0 192.168.1.20:44445         192.168.1.5:53             ESTABLISHED 38242/bro       
udp        0      0 192.168.1.20:35156         192.168.1.5:53             ESTABLISHED 38199/bro  

So, to summarize:

  • The proxy (child process) has one connection to every worker node.
  • The manager (child process) has one connection to every worker node.
  • The proxy and the manager (boy child processes) are connected through a socket (ports 47761 to 38523, in this case)
  • Both proxy and manager parents are more interested in logging processes (they are resposible for writing logs to disk) and DNS resolution

Fine, but we still don't know where are those proprietary logs lost. Installed tcpdump and it quickly showed me that the proprietary protocol logs were being sent from the worker to the manager node and received by the manager child process.

So it seemed the problem should be in my manager policy script. It just implemented the hook of Notice::policy...

hook Notice::policy(not:Notice::Info)

lunes, 29 de octubre de 2018

The APX logic file format

The APX file format

The APX File has a variable size because it stores a lot of different information.

In general terms, the APX file is composed by a first section with general information of the strategy (timestamps, versions, owners, CRCs, the internal strategy password). This section is about 2000 bytes long.

The next section determines the number of Holding registers (MW%), register constants (KW%) and coils (M%) and store all the constant values. This section size depends on the number of constants specified.

The next section stores the error messages that could send the PLC to Unity Pro.

The next section stores network information (IPs, network masks, SNMP configuration, NTP configuration, SMTP configuration, etc.) of all the networking modules associated with the PLC.

The next section stores the strategy in binary format. It also stores chunks of bytes which are ZLIB compressed, when uncompressed they turned to be XML files with configuration information.

So the main structure is:

  1. General information section
  2. Registers and coils
  3. Error messages
  4. Network information
  5. Strategy in binary format


General information section

This section comprises of a number of subsections, separated by a “0E <16-bit CRC> 00 00” separator.

The first subsections are the following:

As we can see, these first blocks, start with 2 or 3 ASCII character (APX subsection, SD subsection, RT subsection…)

At the end of all these blocks, we can see the signature prevoiously mentioned (“E0 16-bit CRC 00 00”).

In the first block, bytes 11 to 15 are 4 byte CRC, in this case “00 C8 2D 0D” that will be repeated in 4 or 5 places of the first section.

We think that 16-bit-CRC values we find all along the APX file validate the starting subsection (they always appear in the beginning of a subsection (or the end of the previous one, which is the same).



Subsections This first subsection is two bytes smaller when it belongs to the TSX premium PLC. The two different bytes seem to be two “00” bytes before the end of the subsection (before “93E0”).

The second subsection is also four bytes smaller when it belongs to the TSX Premium PLC.

Strange enough, the third subsection (corresponding to “RT” ASCII characters) is much longer in the TSX PLC ( 110 vs 64 bytes).

This is because in a TSX PLC APX file, subsections 4 and 5 do not exist and its information is stored (in short) in those extra 56 bytes (prohect date and timestamp, versions, etc.).

This would be the fourth subsection:

This is the fifth subsection:

This subsection stores project information like timestamps, versions, etc, and it's accessed when a message “00 03 00” is sent to the PLC.

And this is the sixth subsection, it also stores some information about the project.

After the project version information come 1024 (0400h) blank bytes intended to store the project description.

And these are the following subsections. Note that Subsection 7 is sent when a “00 03 04” request is done.

However this is not the structure of this section if take into account how the file is read or write in the upload / download process:

The thin blue lines indicate how the bytes are read or write during the strategy upload/download process. It has nothing to do with the separators previously mentioned.

In the red boxes we can see the four-byte-CRC value which is repeated at least 4 time all along the APX file. The small red boxes are the 16-bit CRC values. These 16 bit CRC change on every regeneration of the project, even when the project has NOT changed at all.



Section 2. Registers and coils

In section 2 there are no subsections any more.

Starting in offset AA0h come a bunch of bytes that define the number of bytes stored by holding registers (MW%), constants (KW%, in offset B2 – FF 01 – 1023 bytes – 512 register constants) and coils (M%).

After B00 line come as many bytes as indicated in the KW% constant value (0b02h). In this case they are 01 FF (little endian) bytes. That means that the register constants will be stored in the APX file from 0B0Ch offset to 0D0Bh offset.



Section 3. Error messages

Starting on 0D0Ch, start a block with all error messages:

This block ends with four bytes set to FF (first black box):

It's important to note that both this section and the next start with four bytes “01 00 00 00”, and that subsections seem to start “01 00 A0 00”



Section 4. Network information

Following the end of the error message list, we can see two subsection separators and in the third subsection we can see, in the three coloured boxes the internal IPs of our PLC (in little endian format). In this case “CD 0D 1E 0A” stands for 10.30.13.205, “0A 1E 0D 01” stands for 10.30.13.1 (the gateway IP) and “00 FF FF FF” is the netmask, 255.255.255.0.

After more than 3000 “00” bytes come the next network information.

In offset 2350h (in our case) appear the SNMP configuration, with snmp keys (“public1, public2, public3”), SysLocation (“KKKKKKK...”) and SysContact data (“123456789...”), administration IP and other SNMP information.

Next comes SMTP information with the SMTP server IP address, port, username (“root”), password (“123456789012”). It also stores SMTP From, To and subject information.

This information could allow a malicious attacker to send emails on behalf the PLC.

Then it comes a section of repetitive characters:

In blue we can see the TSX automate APX File and in white the Modicon M340 one. There are some differences but it's a list of “DC053C” values.

From my point of view, this is a way of synchronization.

If the strategy has several cards configured, a “FF FF FF FF” separator comes and the process start again.



Section 5. Binary Strategy code

After the Network information come what we think is the strategy binary code and some blobs of ZLIB bytes.

This section is divided in “PK” subsections:

As we can see after the corresponding “?? E0 CRC16 00 00” signature, comes a “PK” signature an another PK, compressed subsection comes.

This occurs up to 9 times until the end of OUR strategy. Some of these PK files seem to be compressed data, other, with this aspect seem binary data:

The PK signatures appear in our strategy in offsets:

  • 000050F0h
  • 000052B0h
  • 00005C10h
  • 00005CF6h
  • 000064C4h
  • 000066A5h
  • 000066FAh
  • 000069C4h
  • 00006AEDh
  • 000010065h
  • 000010207h

As we can see, their size is small except for one, which is the only one with binary aspect.

Using the binwalk tool it found at least 5 ZLIB blobs, stored in two different PK subsections.

When decompressed these ZLIB blobs found that some of them were compressed XML files, while other stored ST functions:

On the big PK subsection we can even understand the binary code. We tried a simple program and tried to find it in the binary code. And we found it:

Written in our own assembler language we could understand where was it stored and how was it run.



Changes in the APX after a regeneration with no changes at all

We have detected that regenerating a project with no change in it, the APX file generated contains 36 different bytes.

The different bytes are the following:

APX subsection
00000Bh (4 bytes) → CRC32 value

SD Subsection
00003Ch (2 bytes) → SD subsection CRC16 value
000067h (4 bytes) → Copy of the CRC32 value
00006Eh (1 byte) → Regeneration Version number

RT Subsection
000164h (2 bytes) → RT subsection CRC16 value

000284h (2 bytes) → 4th Subsection CRC16 value
0002AFh (3 bytes) → Timestamp
0002B7h (3 bytes) → same timestamp
0002E5h (4 bytes) → Copy of the CRC32 value
0002EDh (4 bytes) → Copy of the CRC32 value
0002F1h (4 bytes) → Copy of the CRC32 value
000301h (1 byte) → Unknown
000355h (1 byte) → Version number
000359h (1 byte) → Unknown


Modification of the file

We were unable to upload an strategy to the PLC with ANY modification. And this must be caused by the CRC checks.

¿Why do we know it's a CRC value?

Running an strings command against the Firmware binary file we find the following function names (related with CRC):

It's clear that CRC16 flags and CRC 32 values are computed all along the APX File.

I tried to calculate the CRC 32 value of entire file, starting from byte 1 to byte 3000. None of them did the trick.

I also tried to calculate 16-bit CRC values of the different subsection. Couldn't get the correct CRC.

As mentioned earlier, we think that 16-bit-CRC values we find all along the APX file validate the starting subsection (they always appear in the beginning of a subsection (or the end of the previous one, which is the same).

sábado, 30 de junio de 2018

Snort rules for modbus and Unity traffic

Snort rules for industrial traffic are important to detect any strange behaviour. DigitalBond did already detect a number of rules which are included at the bottom of this post. However the number of snort rules detected here is much higher.

Taking a look at the Modbus specification and after my research in the Unity/UMAS protocol I have detected a total of 51 snort rules that detect anomalous Modbus and Unity traffic. This is the list of rules:

This is the whole listing:

Modbus protocol rules

RULE 1

Explanation: REQUEST MODBUS 0x01 --> BYTES 4-5 ==> Values should not be above 07D0h -->See Pag 12 modbus specification

alert tcp $EXTERNAL any -> $MODBUS_PLC 502 (flow:from_client, established; content:"|00 00|";
offset:2; depth:2; content:"|01|";offset:7; depth:1; byte_test: 2,>,0x07D0,10;
msg="Modbus TCP 0x01: Too many coils to read requested. Possible DoS";
classtype:attempted-dos; sid:987000001; rev:1; priority:1;)


RULE 2

Explanation: RESPONSE MODBUS 0x81 --> BYTE 2 (EXCEPTION CODE) ==> 0x01 o 0x02 o 0x03 o 0x04 --> See Pag 12 modbus specification

alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|81|"; offset:7; depth:1; content:!"|01|02|03|04|"; offset:8, depth:1;  msg="Modbus TCP 0x01: Response exception code not allowed"; sid:987000002; rev:1; priority:1;)


RULE 3

Explanation: REQUEST MODBUS 0x02 --> BYTES 4-5 ==> Should not be higher than 07D0h -->See Pag 13 modbus specification

alert tcp $EXTERNAL any -> $MODBUS_PLC 502 (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|02|"; offset:7; depth:1; byte_test: 2,>,0x07D0,10; msg="Modbus TCP 0x02: Too many inputs to read requested. Possible DoS"; classtype: attempted-dos; sid:987000003; rev:1; priority:1;)

RULE 4



Explanation --> RESPONSE MODBUS 0x82 --> BYTE 2 (EXCEPTION CODE) ==> 0x01 o 0x02 o 0x03 o 0x04 --> See Pag 13 modbus specification

alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|82|"; offset:7; depth:1; content:!"|01|02|03|04|"; offset:8, depth:1;  msg="Modbus TCP 0x02: Response exception code not allowed"; sid:987000004; rev:1; priority:1;)



RULE 5

Explanation: REQUEST MODBUS 0x03 --> BYTES 4-5 ==> Should not be higher than 0x007D --> See Pag 15 modbus specification

 alert tcp $EXTERNAL any -> $MODBUS_PLC 502 (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|03|"; offset:7; depth:1; -->  BYTE_test: 2,>,0x007D,10; msg="Modbus TCP 0x03: Too many holding registers to read requested. Possible DoS"; classtype: attempted-dos;sid:987000005; rev:1; priority:1;)




RULE 6

Explanation --> RESPONSE MODBUS 0x83 --> BYTE 2 (EXCEPTION CODE) ==> 0x01 o 0x02 o 0x03 o 0x04 --> See Pag 15 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|83|"; offset:7; depth:1; content:!"|01|02|03|04|"; offset:8, depth:1;  msg="Modbus TCP 0x03: Response exception code not allowed"; sid:987000006; rev:1; priority:1;classtype: non-standard-protocol;)




RULE 7

Explanation: REQUEST MODBUS 0x04 --> BYTES 4-5 ==> Should not be higher than 0x007D --> See Pag 16 modbus specification

 alert tcp $EXTERNAL any -> $MODBUS_PLC 502 (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|04|"; offset:7; depth:1; -->  BYTE_test: 2,>,0x007D,10; msg="Modbus TCP 0x04: Too many input registers to read requested. Possible DoS"; classtype: attempted-dos; sid:987000007; rev:1; priority:1;)




RULE 8

Explanation --> RESPONSE MODBUS 0x84 --> BYTE 2 (EXCEPTION CODE) ==> 0x01 o 0x02 o 0x03 o 0x04 --> See Pag 16 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|84|"; offset:7; depth:1; content:!"|01|02|03|04|"; offset:8, depth:1;  msg="Modbus TCP 0x04: Response exception code not allowed"; classtype: non-standard-protocol; sid:987000008; rev:1; priority:3;)




RULE 9

Explanation: REQUEST MODBUS 0x05 --> BYTES 4-5 ==> Allowed values 0x0000 or 0xFF00 --> See Pag 17 modbus specification

 alert tcp $EXTERNAL any -> $MODBUS_PLC 502 (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|05|"; offset:7; depth:1; content:"!|00 00|FF 00|"; offset:10, depth:2;  msg="Modbus TCP 0x05: Coil output value not allowed"; sid:987000009; rev:1; priority:1;)




RULE 10

Explanation --> RESPONSE MODBUS 0x05 --> BYTE 4-5 ==> Allowed values: 0x0000 or 0xFF00 --> See Pag 18 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|05|"; offset:7; depth:1; content:"!|00 00|FF 00|"; offset:10, depth:2;  msg="Modbus TCP 0x05: Coil value incorrect"; sid:987000010; rev:1; priority:1;)




RULE 11

Explanation --> RESPONSE MODBUS 0x85 --> BYTE 2 (EXCEPTION CODE) ==> Allowed values 0x01 or 0x02 or 0x03 or 0x04 --> See Pag 18 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|85|"; offset:7; depth:1; content:!"|01|02|03|04|"; offset:8, depth:1;  msg="Modbus TCP 0x05: Response exception code not allowed"; sid:987000011; rev:1; priority:3;classtype: non-standard-protocol;)




RULE 12

Explanation --> RESPONSE MODBUS 0x86 --> BYTE 2 (EXCEPTION CODE) ==> Allowed values: 1 to 4 --> See Pag 19 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|86|"; offset:7; depth:1; content:!"|01|02|03|04|"; offset:8, depth:1;  msg="Modbus TCP 0x06: Response exception code not allowed"; sid:987000012; rev:1; priority:3;classtype: non-standard-protocol;)




RULE 13

Explanation --> RESPONSE MODBUS 0x87 --> BYTE 2 (EXCEPTION CODE) ==> Allowed values: 01 or 04 --> See Pag 20 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|87|"; offset:7; depth:1; content:!"|01|02|03|04|"; offset:8, depth:1;  msg="Modbus TCP 0x07: Response exception code not allowed"; sid:987000013; rev:1; priority:3;classtype: non-standard-protocol;)




RULE 14

Explanation --> RESPONSE MODBUS 0x88 --> BYTE 2 (EXCEPTION CODE) ==> Allowed values: 01 or 03 or 04 --> See Pag 22 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|88|"; offset:7; depth:1; content:!"|01|03|04|"; offset:8, depth:1;  msg="Modbus TCP 0x08: Response exception code not allowed"; sid:987000014; rev:1; priority:3;classtype: non-standard-protocol;)




RULE 15

Explanation --> RESPONSE MODBUS 0x8B --> BYTE 2 (EXCEPTION CODE) ==> Allowed values: 01 or 04 --> See Pag 25 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|8B|"; offset:7; depth:1; content:!"|01|02|03|04|"; offset:8, depth:1;  msg="Modbus TCP 0x0B: Response exception code not allowed"; sid:987000015; rev:1; priority:1;)




RULE 16

Explanation --> RESPONSE MODBUS 0x8C --> BYTE 2 (EXCEPTION CODE) ==> Allowed values: 01 or 04 --> See Pag 27 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|8C|"; offset:7; depth:1; content:!"|01|02|03|04|"; offset:8, depth:1;  msg="Modbus TCP 0x0C: Response exception code not allowed"; sid:987000016; rev:1; priority:3;classtype: non-standard-protocol;)




RULE 17

Explanation: REQUEST MODBUS 0x0F --> BYTES 4-5 ==> Should not be higher than 0x07B0 --> See Pag 29 modbus specification

 alert tcp $EXTERNAL any -> $MODBUS_PLC 502 (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|0F|"; offset:7; depth:1; -->  BYTE_test: 2,>,0x07B0,10; msg="Modbus TCP 0x0F: Too many coils to write requested"; sid:987000017; rev:1; priority:1;)




RULE 18

Explanation --> RESPONSE MODBUS 0x0F --> BYTES 4-5 ==> Should not be higher than 0x07B0 --> See Pag 29 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|0F|"; offset:7; depth:1; -->  BYTE_test: 2,>,0x07B0,10; msg="Modbus TCP 0x0F: Too many coils responded"; sid:987000018; rev:1; priority:1;)




RULE 19

Explanation --> RESPONSE MODBUS 0x8F --> BYTE 2 (EXCEPTION CODE) ==> Allowed values: 0x01 or 0x02 or 0x03 or 0x04 --> See Pag 29 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|8F|"; offset:7; depth:1; content:!"|01|02|03|04|"; offset:8, depth:1;  msg="Modbus TCP 0x0F: Response exception code not allowed"; sid:987000019; rev:1; priority:3;classtype: non-standard-protocol;)




RULE 20

Explanation: REQUEST MODBUS 0x10 --> BYTES 4-5 ==> Should not be over 0x007B --> See Pag 29 modbus specification

 alert tcp $EXTERNAL any -> $MODBUS_PLC 502 (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|10|"; offset:7; depth:1; -->  BYTE_test: 2,>,0x007B,10; msg="Modbus TCP 0x10: Too many registers to write requested"; sid:987000020; rev:1; priority:1;)




RULE 21

Explanation --> RESPONSE MODBUS 0x10 --> BYTE 4-5 ==> Should not be higher than 0x007B --> See Pag 31 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|0F|"; offset:7; depth:1; -->  BYTE_test: 2,>,0x007B,10; msg="Modbus TCP 0x10: Too many registers responded"; sid:987000021; rev:1; priority:1;)




RULE 22

Explanation --> RESPONSE MODBUS 0x90 --> BYTE 2 (EXCEPTION CODE) ==> Allowed values: 0x01 or 0x02 or 0x03 or 0x04 --> See Pag 31 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|90|"; offset:7; depth:1; content:!"|01|02|03|04|"; offset:8, depth:1;  msg="Modbus TCP 0x10: Response exception code not allowed"; sid:987000022; rev:1; priority:3;classtype: non-standard-protocol;)




RULE 23

Explanation --> RESPONSE MODBUS 0x91 --> BYTE 2 (EXCEPTION CODE) ==> Allowed values: 0x01 or 0x04 --> See Pag 32 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|91|"; offset:7; depth:1; content:!"|01|04|"; offset:8, depth:1;  msg="Modbus TCP 0x11: Response exception code not allowed"; sid:987000023; rev:1; priority:1;)




RULE 24

Explanation: REQUEST MODBUS 0x14 --> BYTE 2 ==> Allowed values: From 0x07 to 0xF5 --> See Pag 33 modbus specification

 alert tcp $EXTERNAL any -> $MODBUS_PLC 502 (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|14|"; offset:7; depth:1; -->  BYTE_test: 1,>,0xF5,8; msg="Modbus TCP Request 0x14: message -->  BYTE count incorrect"; sid:987000124; rev:1; priority:1;)
alert tcp $EXTERNAL any -> $MODBUS_PLC 502 (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|14|"; offset:7; depth:1; -->  BYTE_test: 1,<,0x07,8; msg="Modbus TCP Request 0x14: message -->  BYTE cunt incorrect"; sid:987000224; rev:1; priority:1;)




RULE 28

Explanation --> RESPONSE MODBUS 0x14 --> BYTE 2 ==> Allowed values: 0x07 to 0xF5 --> See Pag 33 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|14|"; offset:7; depth:1; -->  BYTE_test: 1,>,0xF5,8; msg="Modbus TCP Response 0x14: message -->  BYTE count incorrect"; sid:987000128; rev:1; priority:1;)
alert tcp $ $MODBUS_PLC 502 -> EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|14|"; offset:7; depth:1; -->  BYTE_test: 1,<,0x07,8; msg="Modbus TCP Response 0x14: message -->  BYTE cunt incorrect"; sid:987000228; rev:1; priority:1;)




RULE 31

Explanation --> RESPONSE MODBUS 0x94 --> BYTE 2 (EXCEPTION CODE) ==> Allowed values: 0x01 or 0x02 or 0x03 or 0x04 or 0x08 --> See Pag 33 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|94|"; offset:7; depth:1; content:!"|01|02|03|04|"; offset:8, depth:1;  msg="Modbus TCP 0x14: Response exception code not allowed"; sid:987000031; rev:1; priority:3;classtype: non-standard-protocol;)




RULE 32

Explanation: REQUEST MODBUS 0x15 --> BYTE 2 ==> Allowed values: 0x09 to 0xFB --> See Pag 34 modbus specification

 alert tcp $EXTERNAL any -> $MODBUS_PLC 502 (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|15|"; offset:7; depth:1; -->  BYTE_test: 1,>,0xFB,8; msg="Modbus TCP Request 0x15: message -->  BYTE count incorrect"; sid:987000132; rev:1; priority:1;)
alert tcp $EXTERNAL any -> $MODBUS_PLC 502 (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|15|"; offset:7; depth:1; -->  BYTE_test: 1,<,0x09,8; msg="Modbus TCP Request 0x15: message -->  BYTE cunt incorrect"; sid:987000232; rev:1; priority:1;)




RULE 36

Explanation --> RESPONSE MODBUS 0x15 --> BYTE 2 ==> Allowed values: 0x09 to 0xFB --> See Pag 35 modbus specification

 alert tcp $MODBUS_PLC 502 -> EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|15|"; offset:7; depth:1; -->  BYTE_test: 1,>,0xFB,8; msg="Modbus TCP Response 0x15: message -->  BYTE count incorrect"; sid:987000132; rev:1; priority:1;)
alert tcp $ $MODBUS_PLC 502 -> EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|15|"; offset:7; depth:1; -->  BYTE_test: 1,<,0x09,8; msg="Modbus TCP Response 0x15: message -->  BYTE cunt incorrect"; sid:987000232; rev:1; priority:1;)



RULE 40

Explanation --> RESPONSE MODBUS 0x95 --> BYTE 2 (EXCEPTION CODE) ==> Allowed values: 0x01 or 0x02 or 0x03 or 0x04 or 0x08 --> See Pag 35 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|95|"; offset:7; depth:1; content:!"|01|02|03|04|08|"; offset:8, depth:1;  msg="Modbus TCP 0x15: Response exception code not allowed"; sid:987000040; rev:1; priority:3;classtype: non-standard-protocol;)




RULE 41

Explanation --> RESPONSE MODBUS 0x96 --> BYTE 2 (EXCEPTION CODE) ==> Allowed values: 0x01 or 0x02 or 0x03 or 0x04 --> See Pag 37 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|96|"; offset:7; depth:1; content:!"|01|02|03|04|"; offset:8, depth:1;  msg="Modbus TCP 0x16: Response exception code not allowed"; sid:987000041; rev:1; priority:3; classtype: non-standard-protocol;)




RULE 42

Explanation: REQUEST MODBUS 0x17 --> BYTES 4 and 5 ==> Allowed values: 0x0001 to 0x007D --> See Pag 38 modbus specification

 alert tcp $EXTERNAL any -> $MODBUS_PLC 502 (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|17|"; offset:7; depth:1; -->  BYTE_test: 1,>,0x7D,10; msg="Modbus TCP Request 0x17: incorrect register count to read"; sid:987000142; rev:1; priority:1;)
alert tcp $EXTERNAL any -> $MODBUS_PLC 502 (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|17|"; offset:7; depth:1; -->  BYTE_test: 1,<,0x01,10; msg="Modbus TCP Request 0x17: incorrect register count to read"; sid:987000242; rev:1; priority:1;)



RULE 43

Explanation: REQUEST MODBUS 0x17 --> BYTES 8 and 9 ==> Allowed values: 0x0001 a 0x0079 --> See Pag 38 modbus specification

 alert tcp $EXTERNAL any -> $MODBUS_PLC 502 (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|17|"; offset:7; depth:1; -->  BYTE_test: 1,>,0x79,14; msg="Modbus TCP Request 0x17: incorrect register count to write"; sid:987000143; rev:1; priority:1;)
 alert tcp $EXTERNAL any -> $MODBUS_PLC 502 (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|17|"; offset:7; depth:1; -->  BYTE_test: 1,<,0x01,14; msg="Modbus TCP Request 0x17: incorrect register count to write"; sid:987000243; rev:1; priority:1;)




RULE 44

Explanation --> RESPONSE MODBUS 0x97 --> BYTE 2 (EXCEPTION CODE) ==> Allowed values: 0x01 or 0x02 or 0x03 or 0x04 --> See Pag 39 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|97|"; offset:7; depth:1; content:!"|01|02|03|04|"; offset:8, depth:1;  msg="Modbus TCP 0x17: Response exception code not allowed"; sid:987000044; rev:1; priority:3; classtype: non-standard-protocol;)




RULE 45

Explanation --> RESPONSE MODBUS 0x18 --> BYTES 2y3 ==> Allowed values: Should be lower than 0x0041 --> See Pag 41 modbus specification

 alert tcp $MODBUS_PLC 502 -> EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|18|"; offset:7; depth:1; -->  BYTE_test: 1,>,0x41,8; msg="Modbus TCP Response 0x18: message -->  BYTE count incorrect"; sid:987000145; rev:1; priority:1;)




RULE 46

Explanation --> RESPONSE MODBUS 0x18 --> BYTES 4y5 ==> Allowed values: 0x0001 to 0x001F --> See Pag 41 modbus specification

 alert tcp $MODBUS_PLC 502 -> EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|18|"; offset:7; depth:1; -->  BYTE_test: 1,>,0x1F,10; msg="Modbus TCP Response 0x18: message FIFO count incorrect"; sid:987000146; rev:1; priority:1;)
 alert tcp $ $MODBUS_PLC 502 -> EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|18|"; offset:7; depth:1; -->  BYTE_test: 1,<,0x01,10; msg="Modbus TCP Response 0x15: message FIFO count incorrect"; sid:987000246; rev:1; priority:1;)




RULE 47

Explanation --> RESPONSE MODBUS 0x98 --> BYTE 2 (EXCEPTION CODE) ==> Allowed values: 0x01 or 0x02 or 0x03 or 0x04 --> See Pag 41 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|98|"; offset:7; depth:1; content:!"|01|02|03|04|"; offset:8, depth:1;  msg="Modbus TCP 0x18: Response exception code not allowed"; sid:987000047; rev:1; priority:3; classtype: non-standard-protocol;)




RULE 48

Explanation: REQUEST MODBUS 0x2B --> BYTE 2 ==> Allowed values: 0x0D or 0x0B --> See Pag 43 modbus specification

 alert tcp $EXTERNAL any -> $MODBUS_PLC 502 (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|2B|"; offset:7; depth:1; content:"!|0D|0B|"; offset:8, depth:1;  msg="Modbus TCP Request 0x2B: Incorrect MEI Type"; sid:987000048; rev:1; priority:3; classtype: non-standard-protocol;)




RULE 49

Explanation --> RESPONSE MODBUS 0x2B --> BYTE 2 ==> Allowed values: 0x0D or 0x0B --> See Pag 43 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|2B|"; offset:7; depth:1; content:"!|0D|0B|"; offset:8, depth:1;  msg="Modbus TCP Response 0x2B: Incorrect MEI Type"; sid:987000049; rev:1; priority:3; classtype: non-standard-protocol;)




RULE 50

Explanation --> RESPONSE MODBUS 0xAB --> BYTE 2 (EXCEPTION CODE) ==> Allowed values: 0x01 or 0x02 or 0x03 or 0x04 --> See Pag 44 modbus specification

 alert tcp $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|AB|"; offset:7; depth:1; content:!"|01|02|03|04|"; offset:8, depth:1;  msg="Modbus TCP 0x2B: Response exception code not allowed"; sid:987000050; rev:1; priority:3; classtype: non-standard-protocol;)




RULE 51

Explanation: REQUEST MODBUS 0x2B. --> BYTE 2=0x0E --> BYTE 3 ==> Allowed values: 0x01 or 0x02 or 0x03 or 0x04 --> See Pag 44 modbus specification

 alert tcp $EXTERNAL any -> $MODBUS_PLC 502 (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|2B|"; offset:7; depth:1; content:"|0E|"; offset:8; depth:1; content:"!|01|02|03|04|"; offset:9, depth:1;  msg="Modbus TCP Request 0x2B: Incorrect Read Device ID Code"; sid:987000051; rev:1; priority:3; classtype: non-standard-protocol;)




RULE 52

Explanation --> RESPONSE MODBUS 0x2B. --> BYTE 2=0x0E --> BYTE 3 ==> Allowed values: 0x01 or 0x02 or 0x03 or 0x04 --> See Pag 44 modbus specification

 alert tcp  $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|2B|"; offset:7; depth:1; content:"|0E|"; offset:8; depth:1; content:"!|01|02|03|04|"; offset:9, depth:1;  msg="Modbus TCP Response 0x2B: Incorrect Read Device ID Code"; sid:987000052; rev:1; priority:3; classtype: non-standard-protocol;)




RULE 53

Explanation --> RESPONSE MODBUS 0x2B. --> BYTE 2=0x0E --> BYTE 4 ==> Allowed values: 0x01 or 0x02 or 0x03 or 0x81 or 0x82 or 0x83 --> See Pag 44 modbus specification

 alert tcp  $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|2B|"; offset:7; depth:1; content:"|0E|"; offset:8; depth:1; content:"!|01|02|03|81|82|83|"; offset:10, depth:1;  msg="Modbus TCP Response 0x2B: Incorrect Conformity Level"; sid:987000053; rev:1; priority:3; classtype: non-standard-protocol;)


 


RULE 54

Explanation --> RESPONSE MODBUS 0x2B. --> BYTE 2=0x0E --> BYTE 5 ==> Allowed values: 0x00 o 0xFF --> See Pag 44 modbus specification

 alert tcp  $MODBUS_PLC 502 -> $EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; content:"|2B|"; offset:7; depth:1; content:"|0E|"; offset:8; depth:1; content:"!|00|FF|"; offset:11, depth:1;  msg="Modbus TCP Response 0x2B: Incorrect Conformity Level"; sid:987000054; rev:1; priority:3; classtype: non-standard-protocol;)




RULE 55

Explanation --> RESPONSE MODBUS MAYOR QUE 0x80 --> BYTE 2 ==> Allowed values: Should be lower than 0x0B --> See Pag 49 modbus specification

 alert tcp $MODBUS_PLC 502 -> EXTERNAL any (flow:from_client, established; content:"|00 00|"; offset:2; depth:2; -->  BYTE_test: 1,>,0x80,7; -->  BYTE_test: 1,<,0x0B,8; msg="Modbus TCP: Incorrect exception code"; sid:987000155; rev:1; priority:1;)

Unity protocol rules

RULE 56

Explanation: REQUEST MODBUS 0x5A --> BYTE 3=0x23 --> System Bit or Word Modification

 alert tcp any any -> any 502 (msg:"Schneider PLC UMAS Write System bit/word request";content:"|00 00|";offset:2;depth:2;content:"|5a|";offset:7;depth:1;content:"|23|";offset:9;depth:1;sid:987000110;)



RULE 57

Explanation: REQUEST MODBUS 0x5A --> BYTE 3=0x30 --> Strategy upload request

 alert tcp any any -> any 502 (msg:"Schneider PLC UMAS Initialize strategy upload request";content:"|00 00|";offset:2;depth:2;content:"|5a|";offset:7;depth:1;content:"|30|";offset:9;depth:1;sid:987000111;)



RULE 58

Explanation: REQUEST MODBUS 0x5A --> BYTE 3=0x33 --> Strategy upload start

 alert tcp any any -> any 502 (msg:"Schneider PLC UMAS Initialize strategy download request";content:"|00 00|";offset:2;depth:2;content:"|5a|";offset:7;depth:1;content:"|33|";offset:9;depth:1;sid:987000112;)



RULE 59

Explanation: REQUEST MODBUS 0x5A --> BYTE 3=0x36 --> BYTE4=0x02 --> Restore from backup request

 alert tcp any any -> any 502 (msg:"Schneider PLC UMAS strategy restore backup request";content:"|00 00|";offset:2;depth:2;content:"|5a|";offset:7;depth:1;content:"|36 02|";offset:9;depth:2;sid:987000113;)



RULE 60

Explanation: REQUEST MODBUS 0x5A --> BYTE 3=0x36 --> BYTE4=0x04 --> Remove backup request

 alert tcp any any -> any 502 (msg:"Schneider PLC UMAS strategy remove backup request";content:"|00 00|";offset:2;depth:2;content:"|5a|";offset:7;depth:1;content:"|36 04|";offset:9;depth:2;sid:987000114;)



RULE 61

Explanation: REQUEST MODBUS 0x5A. --> BYTE 3=0x40 --> PLC Start request

 alert tcp any any -> any 502 (msg:"Schneider PLC(Quantumn) uses function code 90 for communications the Unity pro software/Set PLC CPU STOP";content:"|00 00|";offset:2;depth:2;content:"|5a|";offset:7;depth:1;content:"|40|";offset:9;depth:1;sid:114;)



RULE 62

Explanation: REQUEST MODBUS 0x5A. --> BYTE 3=0x41 --> PLC Stop request

 alert tcp any any -> any 502 (msg:"Schneider PLC(Quantumn) uses function code 90 for communications the Unity pro software/Set PLC CPU Restart";content:"|00 00|";offset:2;depth:2;content:"|5a|";offset:7;depth:1;content:"|41|";offset:9;depth:1;sid:115;)

miércoles, 27 de junio de 2018

Story of a Linux server malware infection

On May 2016 I was asked to check a Linux Server that seemed to be functioning slowly and because of an alert from the communication services of the University that warned that the server could be serving malware and/or SPAM. This a report of what I found.

FORENSIC ANALYSIS REPORT

  • Date: 27th of May 2016
  • System name: SXXXXXXXa
  • Operative System: Ubuntu 14.04 LTS
  • Installation date: 12th of April, 2016
  • Usage of the System: Server used at the XXXXXXXXX department of the University of XXXXX for their daily operation. It's connected to the internal network of the University and at the same time it's directly accessible from internet through university's subdomain. XXXX.XXXXX.es (IP=193.XXX.XXX.X9). The system has, at the moment of the incident a SSH server accesible from internet. It does not offer any web site nor other available services.
  • Event:The department was informed from the communication services of the University that this server has been detected as the origin of malicious traffic and that probably it has a trojan running. To avoid further problem, the traffic was cut off from and to internet for the server.


Timeline of the problem

After accessing physically to the server the following facts were detected:

  1. The command 'ps' did not show any strange process running. The command netstat did not show any strange connection.
  2. In /root directory of the system, I found a file named XXXX.nano modified on April 21st, 2016 approximately at 12:00 AM in which I could see the line “PermitRootLogin yes”. This is a line for the SSH config file that allows SSH server to be accessed with root user. Checking the SSH config file, I can see it has the same date and time than the .nano file and therefore with that date, the system was allowed to be accessed by root. From my point of view this was not actually part of the attack but a very bad practice used by administrator some days after the system installation and days before the sttack started.
  3. That same day, and approximately same hour, it appears an SSH connection with a normal user (I'll use the fake username 'chris' to refer to it), and 5 minutes later an SSH connection with root user.
  4. Four days later we find that on April 25th, 09:03, an SSH connection with user 'chris' is started, but this time the connection comes from IP 183.3.202.187 (located in China).
  5. In command history of root user we find a total of 13 commands (From 71 to 83 in next image), where we can see that a file is downloaded from a server in China (IP 117.18.4.133), and it's give execution permissions and it's eventually executed... Finally on line 83 the root password is changed.

Unfortunately we cannot know the exact date and time of these commands, but we know that they happened before an SSH connection found on history line 86 (not shown) that happened on April 25th at 12:00 AM. So it seems the attack occurred on April 25th 9:03 and the server was owned a few minutes later.



Post Infection

The system was installed on April 12th, from the day after installation (April 13th, 2016) we can see SSH root Access attempts (which is normal as it had its own public IP address). Actually until the real attack, 42893 SSH connection attempts were found and in the whole life of the server more tan 131000 SSH Access attempts were found:

From IP 183.3.202.187 we can see five failed SSH connection attempts on April 25th at 09:03. The fifth attempt was the access with user 'chris' reported previously on point 3. From that moment on hundreds of connections from that IP were done (As seen in previous image). When asked user 'chris', he/she told me that the password was identical to the username. So the attacker simply tried a number of users with the same passwords and finally he could Access the system. Apparently user chris had sudo permissions and the attacked could easily Access the system as root.

After being owned the server was accessed successfully 61 times from 7 different IP addresses. This is a summary of the connections:

  • April 25th, 09:03. With user root from IP 183.3.202.187. This same day another 16 SSH connections were done with user root from IPs: 183.3.202.187, 185.103.252.14, 110.10.129.201 and 89.248.167.131.
  • The next day, April 26th a total of 15 connections are found from IPs 183.3.202.187 and 110.10.129.201, in these connections the history shows that a package named FakeRoot is installed.
  • The next day, April 27th, another 16 root connections are done from IPs 183.3.202.187, 222.186.21.100 and 110.10.129.201.
  • The next day, April 28th, another 12 root connections from IPs: 183.3.202.187 and 100.43.129.140.

From that day no root connections are done. Actually no new connections are performed from strange IPs.



Location of these IPs

  • 183.3.202.187 ==> Guangzhou, Guangdong, China.
  • 185.103.252.14 ==> Moscu, Russia. .
  • 110.10.129.201 ==> Seoul, South Korea.
  • 89.248.167.131 ==> ¿?, The netherlands.
  • 222.186.21.100 ==> Nanjing, Jiangsu Sheng, China.
  • 100.43.129.140 ==> Orange, California, USA.

All but the last one had been previously reported as malicious IPs.



Actions performed by attacker in the system

  1. In the directory /root the following malicious files were found:
    • 2016ttfacai, ELF executable, 1223123 bytes, with date April, 2nd.
    • heifacai, ELF executable, 1223123 bytes. With date April 3rd, but with the same MD5 hash, so it seems a copy of the former file.
    • conf.n, 73 bytes. Apparently a config file for the previous executables, although it's a binary file.

    All three files were sent to virustotal. The report of the two first executablescan be seen in this link.

  2. In /etc/init.d a script file named DbSecuritySpt was found that actually executed /root/heifacai previously mentioned. This script was executed at every system restart therefore ensuring its presence in memory.
  3. In /etc/init.d another two files kzgbqvo and mowepretrf were found with similar contents.
  4. In /bin at least three different commands 'ps', 'ss' and 'netstat' were found with modification dates between 25th and 28th of April. When running 'ps' no strange process where found and when running 'netstat' no strange connections where found, so it seems that the have the been substituted by a modified copy of the executable. When removed these files, they automatically appeared again:

    After downloading the 'ps' and 'netstat' executables from internet, the malicious heifacai process appeared.

  5. Other malicious files were also found in the /bin directory (ovqbbgzk, kzgbbqvo, kzgbbqvo.sh)


Measures taken

Although eventually the system seemed to be cleaned, a recommendation of formatting and reinstalling the system was given to the owner. A speech of cybersecurity concientization was given to the users and in the newly installed system secure password policies were included.

martes, 26 de junio de 2018

Making softflowd plugin to send correct netflow input_snmp values in a pfsense 2.4.2

As stated in this Netgate fórum post (https://forum.netgate.com/topic/66214/softflowd-source-and-destination-interface-id-as-0/3), the softflowd plugin for pfsense sends netflow packets always in-snmp and out_snmp field with value 0.

Therefore one cannot determine if a flow comes from an interface or the other.

As explained in that post, you should modify the source code of softflowd to fix the problem. As we were working in a production environment, we decided to test this solution and found that it's not exactly how it's explained in that post.

In this post we'll explain how did we prepare a test environment, how did we test the solution given in netgate's forum and how did we apply our solution to our production environment.

1.- Preparing the test environment

I needed a test environment of our pfsense firewall with capabilities to modify source code from the softflowd plugin. I first installed the pfsense in a virtual machine and started trying to install development packages (gcc, make and so on). Soon I realized that it was not so simple to install all necessary development packages to a pfsense environment and decided to start again. Removed this VM and created two different VMs. A first one that had a pfsense environment which had the same OS and plugin versions as in the production environment. A second VM was used to install BSD 11.1-RELEASE-p4, the same version used by our pfsense v2.4.2 (see table in https://www.netgate.com/docs/pfsense/releases/versions-of-pfsense-and-freebsd.html).

After that, the necessary packages for development were installed in this freebsd VM (roughly autoconf, automake, gcc, libtool and m4 packages).

Next step comprised to find out which softflowd version was installed in our production firewall. The pfsense plugin manager showed versión 1.1:

However I could not find version 1.1 anywhere, the last version which I could find the source code was 0.99 ( https://code.google.com/archive/p/softflowd/downloads), so I had to settle for this v0.99, so I downloaded it, compiled it in the freebsd VM and copied it (only the executables softflowd and softflowctl) in my testing environment. They worked fine... still sending netflow packets with the 'in_snmp' field set to zero (as expected).

So it was true, It worked and I had to fix the source code. After barely one hour Reading the code I could understand how does it work. Mainly the problem comes with the plugin expecting a network interface with names like eth:1 or enscp:0 and the number sent in the in_snmp value is that "0" or "1" that goes after the semicolon. This is the original source code that makes it:

OK, but, what if the network interface's name does not include a semicolon?. What if the interface's name does not include a number at all?. The solution given in that fórum Works fine. It calculates a number by adding up the ascii values of network interface name. This is how I left the same source code snipplet:

As it can be seen, al the strsep/strtok staff is out and simply the for loop that calculates a 5 digit number is included. With that done, and after compiling (./configure, make and make install) we tested that the netflow messages sent from the testing envionment included both in_snmp and out_snmp (normal as later an out_snmp=in_snmp is done) with a five digit value:

The final step was to copy the executables (only softflowd and softflowctl) to the production pfsense. It worked fine at the first try.

viernes, 23 de febrero de 2018

The Unity (UMAS) protocol (Part VI)

This is the sixth article of a series of entries in this blog about the Unity protocol, used by Schneider Electric devices for configuration purposes.

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. Specific Modicon Premium function codes

Part VI. Other function codes




In this part we'll talk about a variety of heterogeneous function codes. We actually do not have accurate information about them.



Data Information request(“00 53”)

In a firmware update tool from Schneider Electric when request "DatInf" the following request is done:

00 53 06 00 00 00 00 00 00
  • 00 53: Is the function code
  • 06: Is the length of the following bytes
  • 00 00 00 00 00 00 00: Unknown

The Modicon M340 PLC responds with an error message like:

00 FD 00


M580 request for memory read(“00 07”)

In the communication between Unity and a Modicon M580 a new function code was found.

The request was like:

00 07 00 36 00

The response was:

00 FE 36 00 B5 1A 00 00 00 20 8F E0 70 1C 13 28 4C 12 C7 31 00 00 00 A5 5A 5A 5A 5A

This request was sent every now and then, and the response was always the same. We still don't know what these requests mean.



Unknown request 1 (“00 38”)

Sent from a SCADA software to a M580. Its meaning is unknown.



Unknown request 2 (“00 42”)

It was found once in one communication. Te request is simple:

00 42 00 00

An the response is even simpler:

00 FE


Unknown request 3 (“00 51”)

This function code was found once in a communication. The request was:

00 51 F3 28 01 00

while the response (from the PLC) was:

00 FE C6 F6 02 00 00 00 00 00 00 02 00 11 00 C6 F6 13 00

Its meaning is still unknown.