Summary
The final post on this subject will cover some of the differences in the ICS preprocessors/inspectors supported by snort2.x, vs. those supported by Snort3, as well as difficulties and differences in rule options and their syntax that I encountered while trying to convert various snort 2.x ICS rules for use with Snort3.
About scada_special.rules…
As part of this procress of ensuring that our rules were able to be “translated” via snort2lua
, we went through our ruleset, and identified rules that were not able to be processed correctly through an output file that snort2lua generates called “snort.rej
”. At first we had somewhere in the neighborhood of 200 rules that required manual review.
Initially, the thought never crossed my mine to look at rules that are still in the ET ruleset, but are disabled by default. One sed -i s/^#alert/alert/g et_all.rules
later, and we had all of the rules enabled, and about another 100 or so rules that needed tending to.
Whenever we disable rules in the ETOPEN/ETPRO rulesets that are not in the disabled category, we usually do so for rules that we believe are extremely useful, but are computationally expensive, or may be prone to false positives, etc… So we leave it as an exercise to the user community and their rule management tools to decide what rules they want to enable. But don’t take my word for it, check out our FAQ.
For an example of this, in the ETPRO HUNTING category, we have a series of rules to identify java deserialization/RCE gadgets (Common Java RCE Gadgets Observed), but we leave them disabled by default because currently, there is no way to use content matches that are decoded using base64_decode
and base64_data
in a fast_pattern match, so we don’t have a strong fast_pattern candidate for these rules, and so they end up being “checked” in traffic frequently, which eats CPU time, and causes performance problems if they are enabled. But if you host java web applications on your network, (which, who doesn’t these days) you might see the value of enabling them.
Back to the topic at hand, a significant number of these disabled by default rules were from the ETPRO scada_special
rule category, as well as some ETPRO rules in the scada
category. Aside from the IP address and Port variable problems that we solved in the previous post, some of the disabled rules also used specific preprocessor options and modifiers for their rules. Additionally, these rules didn’t really have any strong fast_pattern
candidates, either. Which is likely a contributing factor towards most of them being disabled by default.
Snort2lua didn’t like a vast portion of these rules, because the syntax for these options have changed a bit, and in some cases, some of the options have disappeared entirely, or have been renamed to something else.
I’ve gone through the process of analyzing the rules manually updating them for use with the new syntax and options Snort3 uses for its ICS preprocessors, however:
- They are all ETPRO rules
- As of right now, we do not have an official pipeline for distributing Snort3 rules. But we are addressing this problem, a pipeline for this process is being built, and gears are turning behind the scenes.
So instead, if ICS networks are your concern, and you need scada_special
rules, or need to know how to write your own ICS rules for Snort3, here is a little primer on the changes I encountered, and problems to be aware of for writing (or updating) your own ICS rules.
Snort2.x, Snort3, and their ICS Preprocessors and features
Snort 2.x has both a modbus and DNP3 preprocessor. By default, the modbus preprocessor analyzes traffic on port 502/tcp, while DNP3 defaults to port 20000.
Modbus
Snort 2.x provides a read me file for their preprocessors, and the rule options they enable. In this case, README.modbus.
For the most part, we had no problems processing MODBUS rules in the ET/ETPRO ruleset. Our rules don’t really make use of the MODBUS rule options Which are:
-
modbus_func
: accepts a modbus function code between 0 and 255, or accepts a string that respresents the function of a modbus function code. For example:
modbus_func:read_coils;
modbus_func:1;
Mean the same thing, we’re looking at modbus traffic over port 502/tcp, for the modbus function code that shows a read coils request.
-
modbus_unit
: a number between 0 and 255 that represents the modbus unit field in the modbus protocol header.
modbus_unit:137;
-
modbus_data
: according to the documentation, this option sets the detection pointer to the modbus data portion of a modbus request/response. According to the syntax, this looks like it may be a sticky buffer.
modbus_data; content:"ayylmao";
would look for the string ayylmao
in the data portion of a modbus request/response.
For snort3, it looks like this preprocessor hasn’t changed much if at all. Something I will make note of is that Cisco has two different pages for information regarding their preprocessors/“inspectors”.
One reference is in the Snort3 rule writing guide. While the other is a Cisco webpage titled “Snort3 Inspector Reference”. Whats interesting is that the Snort3 Rule writing guide informs users that the modbus_func
keyword accepts strings in place of some of the function codes (just like with snort2.x), but then tells readers “go look in the source code if you want to know what they are”, while the Inspector Reference page gives readers a nicely formatted table that shows what string maps to what modbus function code. This won’t be the last time you see me cite both of these resources. My personal advice is to take advantage of both of these documentation sources.
As another aside, as previously mentioned, Snort3 has automatic application layer protocol detection now. Based on their Wizard/Binder system. By default, Snort3 is configured to detect modbus traffic. What this means is that users can write rules using the modbus rule header option:
alert modbus any any -> any any (msg:"ayy"; sid:1000000; rev:1;)
DNP3
Just like with modbus, snort 2.x has a README.dnp3 file for specific DNP3 preprocessor features. The readme defines 4 DNP3 specific rule options:
-
dnp3_func
: not unlike modbus, this option allows users to define either a string or a number between 0 and 255 that corresponds to a DNP3 function.
dnp3_func:read;
dnp3_func:1;
refer to the same function.
-
dnp3_ind
: This option allows users to specify Internal Indicator flags present in a DNP3 Application Response Header. This option takes strings only.
dnp3_ind:need_time;
-
dnp3_obj
: Refers to DNP3 object headers in requests/responses. This option appears to take two numbers between 0 and 255, separated by a comma. The first number is a group id number, while the second is a var id number.
dnp3_obj:50,1;
The example above (in the README.dnp3) refers to a DNP3 Date and Time object.
-
dnp3_data
: The DNP3 protocol’s application data includes CRCs every 16 bytes. this rule content option (likemodbus_data
) allows users to write rules looking in the DNP3 application data portion of network traffic, without having to be cognizant of the CRC calculations every 16 bytes. It appears to be a sticky buffer.
dnp3_data; content:"ayy";
It appears in the distant past, when the DNP3 rules in the ET ruleset were written, some of these rule options were different, and some no longer exist. For example, instead of dnp3_func
there was a similar rule option called dnp3_cmd_fc
which was similar in nature. I also spotted a few rules with the options dnp3_cmd_ot
and dnp3_resp_ot
. These rule options no longer exist, and they do not map to any of the supported rule options for the Snort3 DNP3 inspector.
Aside from those problems, Just like with the modbus preprocessor, the Snort3 rule writing guide has some information about the various DNP3 preprocessor options, but then tells readers to go root around in the snort3 source files to see what strings are supported for the dnp3_func
, and dnp3_ind
rule options, while the Snort 3 Inspector Reference spells out all of the supported strings up front.
The most important difference between Snort 2.x and Snort3 is that the dnp3_obj
rule option syntax has changed. Snort3 expects the following syntax:
dnp3_obj:group 50, var 1;
Snort 3 expects the keywords group
and var
to be placed before the obj group and var numbers respectively. The syntax presented in the Snort3 Inspector Reference is NOT correct.
ENIP/CIP
This is the last preprocessor I’m gonna be covering in this post, as it was the last one to give me trouble, while also being the most interesting. I’m relatively new member to the Emerging Threats team, so sometime looking at parts of the ruleset is like doing archeology. This was one of those times. In the not-so-distant past, Digital Bond led an effort to write an ENIP/CIP preprocessor for CIP (Common Industrial Protocol). Over time, updates to snort broke the preprocessor. For a while the preprocessor was continually maintained, but was eventually scrapped. Community members ported ENIP/CIP rules over to Suricata some time in 2017.
For some period of time, these ENIP/CIP rules that used the ENIP/CIP preprocessor options remained in the Emerging Threats ruleset, disabled by default, long after the preprocessor was gone. However with Snort3, the CIP Inspector/Preprocessor exists. Interestingly, the Snort 3 Rule writing guide states that the CIP inspector is disabled by default, but the stock snort.lua
that ships with the snort3 source has both the correct service inspector and binder configuration options present to where it is enabled.
Once again, there are some discrepancies between the Snort 3 Inspector Reference, and the Snort 3 Rule writing guide regarding the features of the CIP Inspector. The Inspector Reference lists 8 rule options for the CIP Inspector, while the rule writing guide states that there are 11 – the 8 in the Inspector reference plus three more rule options specifically for enip – enip_command
, enip_req
and enip_resp
.
The one option that gave me the most trouble, seeing as how it was present in the most snort 2.x rules I converted was the cip_service
option.
In the old snort 2.x CIP preprocessor, the cip_service
option expected a single numeric value between 0 and 127:
cip_service:78;
However with snort3, they’ve enabled the ability to use comparison logic with service numbers. Snort2lua failed to validate rules looking for a single cip_service
number unless there was an =
next to it like so:
cip_service: =78;
“We’re only looking for values where the CIP service is set to 78.”
However, using calling cip_service with no comparison symbols next to it will validate perfectly fine with snort3’s -T option, assuming the rest of the rule is syntactically correct. With the rules being able to use comparison logic, you can do things like this, if there are multiple service codes that mean roughly the same thing:
cip_service: 77<=>78
“We’re only looking for values where the CIP service is less than or equal to 78, and greater than or equal to 77.”
The Snort 3 rule writing guide entry for the CIP Inspector provides a whole list of range/comparison operations that this rule option supports.
A lot of the other CIP Inspector rule options, such as cip_attribute
, cip_class
, cip_conn_path_class
, cip_status
, and enip_command
. The only major difference between these various options are the maximum number ranges they support:
cip_attribute
: 0-65535
cip_class
: 0-65535
cip_conn_path_class
: 0-65535
cip_instance
: 0-4294967295
cip_service
: 0-127
cip_status
: 0-255
enip_command
: 0-65535
The remaining options for the inspector are:
cip_req
: match on CIP request packets.
cip_rsp
: match on CIP response packets.
enip_req
: match on ENIP request packets.
enip_rsp
: match on ENIP response packets.
None of the options above take any arguments.
Conclusion
That about covers it so far. Bear in mind, this wasn’t meant to be a comprehensive guide to Snort2.x’s or Snort3’s ICS protocol preprocessors, but to cover some of the issues I ran into specifically with ICS rules during the process of making our snort 2.9.x ruleset compatible with snort3 via snort2lua.
You might be interesting in reviewing some of the different Inspector options and/or preprocessor alerts, as well as taking a look at the IEC 104 and S7comm Inspectors as well. Until next time, happy hunting!