pull/16445/merge
Philip Akekudaga 12 hours ago committed by GitHub
commit d54dc65b08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -129,3 +129,290 @@ V1.0b, the MODBUS slave device addresses on serial line are assigned from 1 to
Paper and presentation (in french) on Modbus support are available :
http://www.ssi.gouv.fr/agence/publication/detection-dintrusion-dans-les-systemes-industriels-suricata-et-le-cas-modbus/
Per-value keywords
------------------
The following keywords match on single values of a Modbus transaction,
taken from the same fields the EVE ``modbus`` event logs. They all use
:ref:`unsigned integers <rules-integer-keywords>` and support ranges and
negation. In to-server direction they match on the request; in to-client
direction they match on the response, for the values a response carries.
Read requests are only matched in to-server direction, as a read response
carries data rather than an address and a quantity.
modbus.unit_id
~~~~~~~~~~~~~~
Match on the Modbus unit identifier, the address of a slave device
connected on a sub-network behind a bridge or gateway.
modbus.unit_id uses an :ref:`unsigned 8-bits integer <rules-integer-keywords>`.
Examples::
modbus.unit_id:10;
modbus.unit_id:>247;
modbus.transaction_id
~~~~~~~~~~~~~~~~~~~~~
Match on the Modbus/TCP transaction identifier used to pair requests
and responses.
modbus.transaction_id uses an
:ref:`unsigned 16-bits integer <rules-integer-keywords>`.
Example::
modbus.transaction_id:1;
modbus.protocol_id
~~~~~~~~~~~~~~~~~~
Match on the Modbus/TCP protocol identifier. The specification requires
this field to be 0, so a non-zero value is an anomaly.
modbus.protocol_id uses an
:ref:`unsigned 16-bits integer <rules-integer-keywords>`.
Example::
modbus.protocol_id:!0;
modbus.function
~~~~~~~~~~~~~~~
Match on the Modbus function code, as it is logged in ``function_raw``.
It can be specified as the integer value or as the function code name
logged in ``function_code``. Names are case insensitive and can be
negated with ``!``.
.. note::
Error responses carry the request function code with the highest bit
set (e.g. 136 for a failed function 8), so ``modbus.function:>127;``
matches any error response. ``function_code`` is logged without that
bit, so a name matches a request, and a response that is not an error,
but never an error response. Match those by value, with
``modbus.function:>127;`` or the raised code.
``Unknown`` is logged in ``function_code`` for every code that has no
name, so it does not identify one value and is not accepted here. The
codes it covers are still matched by value.
modbus.function uses an :ref:`unsigned 8-bits integer <rules-integer-keywords>`.
Syntax::
modbus.function:[op]<number>
modbus.function:[!]<name>
See ``suricata --list-keywords=json | jq '."modbus.function".enum_values'``
Where name is one of:
* RdCoils
* RdDiscreteInputs
* RdHoldRegs
* RdInputRegs
* WrSingleCoil
* WrSingleReg
* RdExcStatus
* Diagnostic
* Program484
* Poll484
* GetCommEventCtr
* GetCommEventLog
* ProgramController
* PollController
* WrMultCoils
* WrMultRegs
* ReportServerID
* Program884
* ResetCommLink
* RdFileRec
* WrFileRec
* MaskWrReg
* RdWrMultRegs
* RdFIFOQueue
* MEI
Examples::
modbus.function:8;
modbus.function:Diagnostic; # same as above
modbus.function:!RdCoils;
modbus.function:>127;
modbus.subfunction
~~~~~~~~~~~~~~~~~~
Match on the subfunction code of Modbus Diagnostics (function 8)
messages. It can be specified as the integer value or as the
subfunction name logged in ``diagnostic.code``. Names are case
insensitive and can be negated with ``!``.
.. note::
``Reserved`` is logged in ``diagnostic.code`` for every subfunction
that has no name, so it does not identify one value and is not
accepted here. Those subfunctions are still matched by value.
modbus.subfunction uses an
:ref:`unsigned 16-bits integer <rules-integer-keywords>`.
Syntax::
modbus.subfunction:[op]<number>
modbus.subfunction:[!]<name>
See ``suricata --list-keywords=json | jq '."modbus.subfunction".enum_values'``
Where name is one of:
* RetQueryData
* RestartCommOpt
* RetDiagReg
* ChangeInputDelimiter
* ForceListenOnlyMode
* ClearCtrDiagReg
* RetBusMsgCount
* RetBusCommErrCount
* RetBusExcErrCount
* RetServerMsgCount
* RetServerNoRespCount
* RetServerNAKCount
* RetServerBusyCount
* RetBusCharOverrunCount
* RetOverrunErrCount
* ClearOverrunCounterFlag
* GetClearPlusStats
Examples::
modbus.function:8; modbus.subfunction:4;
modbus.function:Diagnostic; modbus.subfunction:ForceListenOnlyMode;
modbus.exception_code
~~~~~~~~~~~~~~~~~~~~~
Match on the exception code of Modbus error responses. It can be
specified as the integer value or as the exception name logged in
``exception.code``. Names are case insensitive and can be negated with
``!``.
.. note::
``Unknown`` is logged in ``exception.code`` for every exception code
that has no name, so it does not identify one value and is not
accepted here. Those codes are still matched by value.
modbus.exception_code uses an
:ref:`unsigned 8-bits integer <rules-integer-keywords>`.
Syntax::
modbus.exception_code:[op]<number>
modbus.exception_code:[!]<name>
See ``suricata --list-keywords=json | jq '."modbus.exception_code".enum_values'``
Where name is one of:
* IllegalFunction
* IllegalDataAddr
* IllegalDataValue
* ServerDeviceFail
* Ack
* ServerDeviceBusy
* NegAck
* MemParityErr
* GatewayPathUnavailable
* GatewayTargetFailToResp
Examples::
modbus.exception_code:11;
modbus.exception_code:GatewayTargetFailToResp; # same as above
modbus.read.address
~~~~~~~~~~~~~~~~~~~
Match on the starting address of Modbus read requests.
Unlike the ``address`` option of the ``modbus`` keyword above, this
matches the raw protocol value as it is logged, without the +1 offset,
and matches only the starting address of an access rather than the
whole accessed range.
modbus.read.address uses an
:ref:`unsigned 16-bits integer <rules-integer-keywords>`.
Example::
modbus.read.address:1000;
modbus.read.quantity
~~~~~~~~~~~~~~~~~~~~
Match on the quantity of items in Modbus read requests.
modbus.read.quantity uses an
:ref:`unsigned 16-bits integer <rules-integer-keywords>`.
Example::
modbus.read.quantity:>1000;
modbus.write.address
~~~~~~~~~~~~~~~~~~~~
Match on the starting address of Modbus write accesses. In to-client
direction this matches the address echoed in write responses.
Unlike the ``address`` option of the ``modbus`` keyword above, this
matches the raw protocol value as it is logged, without the +1 offset,
and matches only the starting address of an access rather than the
whole accessed range.
modbus.write.address uses an
:ref:`unsigned 16-bits integer <rules-integer-keywords>`.
Example::
modbus.write.address:500;
modbus.write.quantity
~~~~~~~~~~~~~~~~~~~~~
Match on the quantity of items in Modbus multiple-write requests, as it
is logged in ``write.quantity``. In to-client direction this matches the
quantity echoed in multiple-write responses, which the protocol carries
in the value field and the log reports as ``write.data``.
modbus.write.quantity uses an
:ref:`unsigned 16-bits integer <rules-integer-keywords>`.
Example::
modbus.write.quantity:>100;
modbus.write.value
~~~~~~~~~~~~~~~~~~
Match on the value of Modbus single-write accesses, as it is logged in
``write.data``. In to-client direction this matches the value echoed in
single-write responses.
.. note::
Multiple-write responses are not matched, as they echo a quantity
rather than a value (see ``modbus.write.quantity``).
modbus.write.value uses an
:ref:`unsigned 16-bits integer <rules-integer-keywords>`.
Example::
modbus.write.address:500; modbus.write.value:>200;

@ -4110,7 +4110,12 @@
"type": "string"
},
"raw": {
"type": "integer"
"type": "integer",
"suricata": {
"keywords": [
"modbus.subfunction"
]
}
}
}
},
@ -4121,7 +4126,12 @@
"type": "string"
},
"function_raw": {
"type": "integer"
"type": "integer",
"suricata": {
"keywords": [
"modbus.function"
]
}
},
"mei": {
"type": "object",
@ -4139,35 +4149,84 @@
}
},
"protocol_id": {
"type": "integer"
"type": "integer",
"suricata": {
"keywords": [
"modbus.protocol_id"
]
}
},
"read": {
"type": "object",
"additionalProperties": false,
"properties": {
"address": {
"type": "integer"
"type": "integer",
"suricata": {
"keywords": [
"modbus.read.address"
]
}
},
"quantity": {
"type": "integer"
"type": "integer",
"suricata": {
"keywords": [
"modbus.read.quantity"
]
}
}
}
},
"transaction_id": {
"type": "integer"
"type": "integer",
"suricata": {
"keywords": [
"modbus.transaction_id"
]
}
},
"unit_id": {
"type": "integer"
"type": "integer",
"suricata": {
"keywords": [
"modbus.unit_id"
]
}
},
"write": {
"type": "object",
"additionalProperties": false,
"properties": {
"address": {
"type": "integer",
"suricata": {
"keywords": [
"modbus.write.address"
]
}
},
"and_mask": {
"type": "integer"
},
"data": {
"type": ["string", "number"],
"suricata": {
"keywords": [
"modbus.write.value"
]
}
},
"or_mask": {
"type": "integer"
},
"quantity": {
"type": "integer",
"suricata": {
"keywords": [
"modbus.write.quantity"
]
}
}
}
}
@ -4197,7 +4256,12 @@
"type": "string"
},
"raw": {
"type": "integer"
"type": "integer",
"suricata": {
"keywords": [
"modbus.subfunction"
]
}
}
}
},
@ -4212,7 +4276,12 @@
"type": "string"
},
"raw": {
"type": "integer"
"type": "integer",
"suricata": {
"keywords": [
"modbus.exception_code"
]
}
}
}
},
@ -4220,10 +4289,20 @@
"type": "string"
},
"function_raw": {
"type": "integer"
"type": "integer",
"suricata": {
"keywords": [
"modbus.function"
]
}
},
"protocol_id": {
"type": "integer"
"type": "integer",
"suricata": {
"keywords": [
"modbus.protocol_id"
]
}
},
"read": {
"type": "object",
@ -4235,19 +4314,46 @@
}
},
"transaction_id": {
"type": "integer"
"type": "integer",
"suricata": {
"keywords": [
"modbus.transaction_id"
]
}
},
"unit_id": {
"type": "integer"
"type": "integer",
"suricata": {
"keywords": [
"modbus.unit_id"
]
}
},
"write": {
"type": "object",
"additionalProperties": false,
"properties": {
"address": {
"type": "integer",
"suricata": {
"keywords": [
"modbus.write.address"
]
}
},
"and_mask": {
"type": "integer"
},
"data": {
"type": "integer",
"suricata": {
"keywords": [
"modbus.write.value",
"modbus.write.quantity"
]
}
},
"or_mask": {
"type": "integer"
}
}

@ -64,7 +64,7 @@ pub fn derive_enum_string_u8(input: TokenStream) -> TokenStream {
stringenum::derive_enum_string::<u8>(input, "u8")
}
#[proc_macro_derive(EnumStringU16, attributes(name))]
#[proc_macro_derive(EnumStringU16, attributes(name, suricata))]
pub fn derive_enum_string_u16(input: TokenStream) -> TokenStream {
stringenum::derive_enum_string::<u16>(input, "u16")
}

@ -48,9 +48,13 @@ The enum can have a suricata attribute, which is a list of named values
- enum_string_style can be:
- UPPERCASE : uppercase is used for logging and parsing
- LOG_UPPERCASE : uppercase is only used for logging, snake_case for parsing
- PascalCase : the variant name is used as is for logging, parsing is case insensitive
With UPPERCASE
ExampleEnum::BestValueEver.to_str() is "BESTVALUEEVER" (without underscores)
With PascalCase
ExampleEnum::BestValueEver.to_str() is "BestValueEver"
*/
extern crate proc_macro;
@ -108,6 +112,11 @@ where
names_upper.push(v.ident.to_string().to_ascii_uppercase());
names_lower.push(v.ident.to_string().to_ascii_lowercase());
}
"PascalCase" => {
names.push(v.ident.to_string());
names_upper.push(v.ident.to_string().to_ascii_uppercase());
names_lower.push(v.ident.to_string().to_ascii_lowercase());
}
"LOG_UPPERCASE" => {
names.push(v.ident.to_string().to_ascii_uppercase());
let fname = transform_name(&v.ident.to_string());

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save