diff --git a/doc/userguide/rules/modbus-keyword.rst b/doc/userguide/rules/modbus-keyword.rst index a485c71a19..18200b1a1a 100644 --- a/doc/userguide/rules/modbus-keyword.rst +++ b/doc/userguide/rules/modbus-keyword.rst @@ -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 ` 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 `. + +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 `. + +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 `. + +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 `. + +Syntax:: + + modbus.function:[op] + modbus.function:[!] + +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 `. + +Syntax:: + + modbus.subfunction:[op] + modbus.subfunction:[!] + +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 `. + +Syntax:: + + modbus.exception_code:[op] + modbus.exception_code:[!] + +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 `. + +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 `. + +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 `. + +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 `. + +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 `. + +Example:: + + modbus.write.address:500; modbus.write.value:>200;