|
|
|
|
@ -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;
|
|
|
|
|
|