summaryrefslogtreecommitdiffstats
path: root/src/app-layer-modbus.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/app-layer-modbus.c1562
1 files changed, 1562 insertions, 0 deletions
diff --git a/src/app-layer-modbus.c b/src/app-layer-modbus.c
new file mode 100644
index 0000000..c1edbf2
--- /dev/null
+++ b/src/app-layer-modbus.c
@@ -0,0 +1,1562 @@
+/*
+ * Copyright (C) 2014 ANSSI
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * \author David DIALLO <diallo@et.esiea.fr>
+ *
+ * App-layer parser for Modbus protocol
+ *
+ */
+
+#include "suricata-common.h"
+
+#include "util-debug.h"
+
+#include "app-layer-parser.h"
+#include "app-layer-modbus.h"
+
+void ModbusParserRegisterTests(void);
+
+/**
+ * \brief Function to register the Modbus protocol parser
+ */
+void RegisterModbusParsers(void)
+{
+ rs_modbus_register_parser();
+#ifdef UNITTESTS
+ AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_MODBUS, ModbusParserRegisterTests);
+#endif
+
+ SCReturn;
+}
+
+/* UNITTESTS */
+#ifdef UNITTESTS
+#include "detect.h"
+#include "detect-engine.h"
+#include "detect-parse.h"
+#include "detect-engine-build.h"
+#include "detect-engine-alert.h"
+
+#include "flow-util.h"
+
+#include "util-unittest.h"
+#include "util-unittest-helper.h"
+
+#include "stream-tcp.h"
+#include "stream-tcp-private.h"
+
+#include "rust.h"
+
+/* Modbus default stream reassembly depth */
+#define MODBUS_CONFIG_DEFAULT_STREAM_DEPTH 0
+
+/* Modbus Application Protocol Specification V1.1b3 6.1: Read Coils */
+static uint8_t invalidFunctionCode[] = {
+ /* Transaction ID */ 0x00, 0x00,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x02,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x00
+};
+
+/* Modbus Application Protocol Specification V1.1b3 6.1: Read Coils */
+/* Example of a request to read discrete outputs 20-38 */
+static uint8_t readCoilsReq[] = {/* Transaction ID */ 0x00, 0x00,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x06,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x01,
+ /* Starting Address */ 0x78, 0x90,
+ /* Quantity of coils */ 0x00, 0x13 };
+
+static uint8_t readCoilsRsp[] = {/* Transaction ID */ 0x00, 0x00,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x06,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x01,
+ /* Byte count */ 0x03,
+ /* Coil Status */ 0xCD, 0x6B, 0x05 };
+
+static uint8_t readCoilsErrorRsp[] = {
+ /* Transaction ID */ 0x00, 0x00,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x03,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x81,
+ /* Invalid Exception code: should trigger the InvalidExceptionCode ModbusEvent */
+ 0xFF
+};
+
+/* Modbus Application Protocol Specification V1.1b3 6.6: Write Single register */
+/* Example of a request to write register 2 to 00 03 hex */
+static uint8_t writeSingleRegisterReq[] = {/* Transaction ID */ 0x00, 0x0A,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x06,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x06,
+ /* Register Address */ 0x00, 0x01,
+ /* Register Value */ 0x00, 0x03};
+
+static uint8_t invalidWriteSingleRegisterReq[] = {/* Transaction ID */ 0x00, 0x0A,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x04,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x06,
+ /* Register Address */ 0x00, 0x01};
+
+static uint8_t writeSingleRegisterRsp[] = {/* Transaction ID */ 0x00, 0x0A,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x06,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x06,
+ /* Register Address */ 0x00, 0x01,
+ /* Register Value */ 0x00, 0x03};
+
+/* Modbus Application Protocol Specification V1.1b3 6.12: Write Multiple registers */
+/* Example of a request to write two registers starting at 2 to 00 0A and 01 02 hex */
+static uint8_t writeMultipleRegistersReq[] = {/* Transaction ID */ 0x00, 0x0A,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x0B,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x10,
+ /* Starting Address */ 0x00, 0x01,
+ /* Quantity of Registers */ 0x00, 0x02,
+ /* Byte count */ 0x04,
+ /* Registers Value */ 0x00, 0x0A,
+ 0x01, 0x02};
+
+static uint8_t writeMultipleRegistersRsp[] = {/* Transaction ID */ 0x00, 0x0A,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x06,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x10,
+ /* Starting Address */ 0x00, 0x01,
+ /* Quantity of Registers */ 0x00, 0x02};
+
+/* Modbus Application Protocol Specification V1.1b3 6.16: Mask Write Register */
+/* Example of a request to mask write to register 5 */
+static uint8_t maskWriteRegisterReq[] = {/* Transaction ID */ 0x00, 0x0A,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x08,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x16,
+ /* Reference Address */ 0x00, 0x04,
+ /* And_Mask */ 0x00, 0xF2,
+ /* Or_Mask */ 0x00, 0x25};
+
+static uint8_t invalidMaskWriteRegisterReq[] = {/* Transaction ID */ 0x00, 0x0A,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x06,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x16,
+ /* Reference Address */ 0x00, 0x04,
+ /* And_Mask */ 0x00, 0xF2};
+
+static uint8_t maskWriteRegisterRsp[] = {/* Transaction ID */ 0x00, 0x0A,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x08,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x16,
+ /* Reference Address */ 0x00, 0x04,
+ /* And_Mask */ 0x00, 0xF2,
+ /* Or_Mask */ 0x00, 0x25};
+
+/* Modbus Application Protocol Specification V1.1b3 6.17: Read/Write Multiple registers */
+/* Example of a request to read six registers starting at register 4, */
+/* and to write three registers starting at register 15 */
+static uint8_t readWriteMultipleRegistersReq[] = {/* Transaction ID */ 0x12, 0x34,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x11,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x17,
+ /* Read Starting Address */ 0x00, 0x03,
+ /* Quantity to Read */ 0x00, 0x06,
+ /* Write Starting Address */ 0x00, 0x0E,
+ /* Quantity to Write */ 0x00, 0x03,
+ /* Write Byte count */ 0x06,
+ /* Write Registers Value */ 0x12, 0x34,
+ 0x56, 0x78,
+ 0x9A, 0xBC};
+
+/* Mismatch value in Byte count 0x0B instead of 0x0C */
+static uint8_t readWriteMultipleRegistersRsp[] = {/* Transaction ID */ 0x12, 0x34,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x0E,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x17,
+ /* Byte count */ 0x0B,
+ /* Read Registers Value */ 0x00, 0xFE,
+ 0x0A, 0xCD,
+ 0x00, 0x01,
+ 0x00, 0x03,
+ 0x00, 0x0D,
+ 0x00};
+
+/* Modbus Application Protocol Specification V1.1b3 6.8.1: 04 Force Listen Only Mode */
+/* Example of a request to to remote device to its Listen Only Mode for Modbus Communications. */
+static uint8_t forceListenOnlyMode[] = {/* Transaction ID */ 0x0A, 0x00,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x06,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x08,
+ /* Sub-function code */ 0x00, 0x04,
+ /* Data */ 0x00, 0x00};
+
+static uint8_t invalidProtocolIdReq[] = {/* Transaction ID */ 0x00, 0x00,
+ /* Protocol ID */ 0x00, 0x01,
+ /* Length */ 0x00, 0x06,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x01,
+ /* Starting Address */ 0x78, 0x90,
+ /* Quantity of coils */ 0x00, 0x13 };
+
+static uint8_t invalidLengthWriteMultipleRegistersReq[] = {
+ /* Transaction ID */ 0x00, 0x0A,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x09,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x10,
+ /* Starting Address */ 0x00, 0x01,
+ /* Quantity of Registers */ 0x00, 0x02,
+ /* Byte count */ 0x04,
+ /* Registers Value */ 0x00, 0x0A,
+ 0x01, 0x02};
+
+static uint8_t exceededLengthWriteMultipleRegistersReq[] = {
+ /* Transaction ID */ 0x00, 0x0A,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0xff, 0xfa,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x10,
+ /* Starting Address */ 0x00, 0x01,
+ /* Quantity of Registers */ 0x7f, 0xf9,
+ /* Byte count */ 0xff};
+
+static uint8_t invalidLengthPDUWriteMultipleRegistersReq[] = {
+ /* Transaction ID */ 0x00, 0x0A,
+ /* Protocol ID */ 0x00, 0x00,
+ /* Length */ 0x00, 0x02,
+ /* Unit ID */ 0x00,
+ /* Function code */ 0x10};
+
+/** \test Send Modbus Read Coils request/response. */
+static int ModbusParserTest01(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ Flow f;
+ TcpSession ssn;
+
+ FAIL_IF_NULL(alp_tctx);
+
+ memset(&f, 0, sizeof(f));
+ memset(&ssn, 0, sizeof(ssn));
+
+ FLOW_INITIALIZE(&f);
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+
+ StreamTcpInitConfig(true);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOSERVER, readCoilsReq,
+ sizeof(readCoilsReq));
+ FAIL_IF_NOT(r == 0);
+
+ ModbusState *modbus_state = f.alstate;
+ FAIL_IF_NULL(modbus_state);
+
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 1);
+ FAIL_IF_NOT(rs_modbus_message_get_read_request_address(&request) == 0x7890);
+ FAIL_IF_NOT(rs_modbus_message_get_read_request_quantity(&request) == 19);
+
+ r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOCLIENT, readCoilsRsp,
+ sizeof(readCoilsRsp));
+ FAIL_IF_NOT(r == 0);
+
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ PASS;
+}
+
+/** \test Send Modbus Write Multiple registers request/response. */
+static int ModbusParserTest02(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ Flow f;
+ TcpSession ssn;
+
+ FAIL_IF_NULL(alp_tctx);
+
+ memset(&f, 0, sizeof(f));
+ memset(&ssn, 0, sizeof(ssn));
+
+ FLOW_INITIALIZE(&f);
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+
+ StreamTcpInitConfig(true);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOSERVER, writeMultipleRegistersReq,
+ sizeof(writeMultipleRegistersReq));
+ FAIL_IF_NOT(r == 0);
+
+ ModbusState *modbus_state = f.alstate;
+ FAIL_IF_NULL(modbus_state);
+
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 16);
+ FAIL_IF_NOT(rs_modbus_message_get_write_multreq_address(&request) == 0x01);
+ FAIL_IF_NOT(rs_modbus_message_get_write_multreq_quantity(&request) == 2);
+
+ size_t data_len;
+ const uint8_t *data = rs_modbus_message_get_write_multreq_data(&request, &data_len);
+ FAIL_IF_NOT(data_len == 4);
+ FAIL_IF_NOT(data[0] == 0x00);
+ FAIL_IF_NOT(data[1] == 0x0A);
+ FAIL_IF_NOT(data[2] == 0x01);
+ FAIL_IF_NOT(data[3] == 0x02);
+
+ r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOCLIENT, writeMultipleRegistersRsp,
+ sizeof(writeMultipleRegistersRsp));
+ FAIL_IF_NOT(r == 0);
+
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ PASS;
+}
+
+/** \test Send Modbus Read/Write Multiple registers request/response with mismatch value. */
+static int ModbusParserTest03(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ DetectEngineThreadCtx *det_ctx = NULL;
+ Flow f;
+ Packet *p = NULL;
+ Signature *s = NULL;
+ TcpSession ssn;
+ ThreadVars tv;
+
+ FAIL_IF_NULL(alp_tctx);
+
+ memset(&tv, 0, sizeof(ThreadVars));
+ memset(&f, 0, sizeof(Flow));
+ memset(&ssn, 0, sizeof(TcpSession));
+
+ p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
+
+ FLOW_INITIALIZE(&f);
+ f.alproto = ALPROTO_MODBUS;
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+ f.flags |= FLOW_IPV4;
+
+ p->flow = &f;
+ p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
+ p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
+
+ StreamTcpInitConfig(true);
+
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ FAIL_IF_NULL(de_ctx);
+
+ de_ctx->flags |= DE_QUIET;
+ s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+ "(msg:\"Modbus Data mismatch\"; "
+ "app-layer-event: "
+ "modbus.value_mismatch; "
+ "sid:1;)");
+ FAIL_IF_NULL(s);
+
+ SigGroupBuild(de_ctx);
+ DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOSERVER,
+ readWriteMultipleRegistersReq,
+ sizeof(readWriteMultipleRegistersReq));
+ FAIL_IF_NOT(r == 0);
+
+ ModbusState *modbus_state = f.alstate;
+ FAIL_IF_NULL(modbus_state);
+
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
+
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 23);
+ FAIL_IF_NOT(rs_modbus_message_get_rw_multreq_read_address(&request) == 0x03);
+ FAIL_IF_NOT(rs_modbus_message_get_rw_multreq_read_quantity(&request) == 6);
+ FAIL_IF_NOT(rs_modbus_message_get_rw_multreq_write_address(&request) == 0x0E);
+ FAIL_IF_NOT(rs_modbus_message_get_rw_multreq_write_quantity(&request) == 3);
+
+ size_t data_len;
+ uint8_t const *data = rs_modbus_message_get_rw_multreq_write_data(&request, &data_len);
+ FAIL_IF_NOT(data_len == 6);
+ FAIL_IF_NOT(data[0] == 0x12);
+ FAIL_IF_NOT(data[1] == 0x34);
+ FAIL_IF_NOT(data[2] == 0x56);
+ FAIL_IF_NOT(data[3] == 0x78);
+ FAIL_IF_NOT(data[4] == 0x9A);
+ FAIL_IF_NOT(data[5] == 0xBC);
+
+ r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOCLIENT, readWriteMultipleRegistersRsp,
+ sizeof(readWriteMultipleRegistersRsp));
+ FAIL_IF_NOT(r == 0);
+
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
+
+ /* do detect */
+ SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+ FAIL_IF_NOT(PacketAlertCheck(p, 1));
+
+ SigGroupCleanup(de_ctx);
+ SigCleanSignatures(de_ctx);
+
+ DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
+ DetectEngineCtxFree(de_ctx);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ UTHFreePackets(&p, 1);
+ PASS;
+}
+
+/** \test Send Modbus Force Listen Only Mode request. */
+static int ModbusParserTest04(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ Flow f;
+ TcpSession ssn;
+
+ FAIL_IF_NULL(alp_tctx);
+
+ memset(&f, 0, sizeof(f));
+ memset(&ssn, 0, sizeof(ssn));
+
+ FLOW_INITIALIZE(&f);
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+
+ StreamTcpInitConfig(true);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOSERVER, forceListenOnlyMode,
+ sizeof(forceListenOnlyMode));
+ FAIL_IF_NOT(r == 0);
+
+ ModbusState *modbus_state = f.alstate;
+ FAIL_IF_NULL(modbus_state);
+
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
+
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 8);
+ FAIL_IF_NOT(rs_modbus_message_get_subfunction(&request) == 4);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ PASS;
+}
+
+/** \test Send Modbus invalid Protocol version in request. */
+static int ModbusParserTest05(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ DetectEngineThreadCtx *det_ctx = NULL;
+ Flow f;
+ Packet *p = NULL;
+ Signature *s = NULL;
+ TcpSession ssn;
+ ThreadVars tv;
+
+ FAIL_IF_NULL(alp_tctx);
+
+ memset(&tv, 0, sizeof(ThreadVars));
+ memset(&f, 0, sizeof(Flow));
+ memset(&ssn, 0, sizeof(TcpSession));
+
+ p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
+
+ FLOW_INITIALIZE(&f);
+ f.alproto = ALPROTO_MODBUS;
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+ f.flags |= FLOW_IPV4;
+
+ p->flow = &f;
+ p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
+ p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
+
+ StreamTcpInitConfig(true);
+
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ FAIL_IF_NULL(de_ctx);
+
+ de_ctx->flags |= DE_QUIET;
+ s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+ "(msg:\"Modbus invalid Protocol version\"; "
+ "app-layer-event: "
+ "modbus.invalid_protocol_id; "
+ "sid:1;)");
+ FAIL_IF_NULL(s);
+
+ SigGroupBuild(de_ctx);
+ DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOSERVER, invalidProtocolIdReq,
+ sizeof(invalidProtocolIdReq));
+ FAIL_IF_NOT(r == 0);
+
+ ModbusState *modbus_state = f.alstate;
+ FAIL_IF_NULL(modbus_state);
+
+ /* do detect */
+ SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+ FAIL_IF_NOT(PacketAlertCheck(p, 1));
+
+ SigGroupCleanup(de_ctx);
+ SigCleanSignatures(de_ctx);
+
+ DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
+ DetectEngineCtxFree(de_ctx);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ UTHFreePackets(&p, 1);
+ PASS;
+}
+
+/** \test Send Modbus unsolicited response. */
+static int ModbusParserTest06(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ DetectEngineThreadCtx *det_ctx = NULL;
+ Flow f;
+ Packet *p = NULL;
+ Signature *s = NULL;
+ TcpSession ssn;
+ ThreadVars tv;
+
+ FAIL_IF_NULL(alp_tctx);
+
+ memset(&tv, 0, sizeof(ThreadVars));
+ memset(&f, 0, sizeof(Flow));
+ memset(&ssn, 0, sizeof(TcpSession));
+
+ p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
+
+ FLOW_INITIALIZE(&f);
+ f.alproto = ALPROTO_MODBUS;
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+ f.flags |= FLOW_IPV4;
+
+ p->flow = &f;
+ p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
+ p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
+
+ StreamTcpInitConfig(true);
+
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ FAIL_IF_NULL(de_ctx);
+
+ de_ctx->flags |= DE_QUIET;
+ s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+ "(msg:\"Modbus unsolicited response\"; "
+ "app-layer-event: "
+ "modbus.unsolicited_response; "
+ "sid:1;)");
+ FAIL_IF_NULL(s);
+
+ SigGroupBuild(de_ctx);
+ DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOCLIENT, readCoilsRsp,
+ sizeof(readCoilsRsp));
+ FAIL_IF_NOT(r == 0);
+
+ ModbusState *modbus_state = f.alstate;
+ FAIL_IF_NULL(modbus_state);
+
+ /* do detect */
+ SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+ FAIL_IF_NOT(PacketAlertCheck(p, 1));
+
+ SigGroupCleanup(de_ctx);
+ SigCleanSignatures(de_ctx);
+
+ DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
+ DetectEngineCtxFree(de_ctx);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ UTHFreePackets(&p, 1);
+ PASS;
+}
+
+/** \test Send Modbus invalid Length request. */
+static int ModbusParserTest07(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ DetectEngineThreadCtx *det_ctx = NULL;
+ Flow f;
+ Packet *p = NULL;
+ Signature *s = NULL;
+ TcpSession ssn;
+ ThreadVars tv;
+
+ FAIL_IF_NULL(alp_tctx);
+
+ memset(&tv, 0, sizeof(ThreadVars));
+ memset(&f, 0, sizeof(Flow));
+ memset(&ssn, 0, sizeof(TcpSession));
+
+ p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
+
+ FLOW_INITIALIZE(&f);
+ f.alproto = ALPROTO_MODBUS;
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+ f.flags |= FLOW_IPV4;
+
+ p->flow = &f;
+ p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
+ p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
+
+ StreamTcpInitConfig(true);
+
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ FAIL_IF_NULL(de_ctx);
+
+ de_ctx->flags |= DE_QUIET;
+ s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+ "(msg:\"Modbus invalid Length\"; "
+ "app-layer-event: "
+ "modbus.invalid_length; "
+ "sid:1;)");
+ FAIL_IF_NULL(s);
+
+ SigGroupBuild(de_ctx);
+ DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOSERVER,
+ invalidLengthWriteMultipleRegistersReq,
+ sizeof(invalidLengthWriteMultipleRegistersReq));
+ FAIL_IF_NOT(r == 1);
+
+ ModbusState *modbus_state = f.alstate;
+ FAIL_IF_NULL(modbus_state);
+
+ /* do detect */
+ SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+ FAIL_IF_NOT(PacketAlertCheck(p, 1));
+
+ SigGroupCleanup(de_ctx);
+ SigCleanSignatures(de_ctx);
+
+ DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
+ DetectEngineCtxFree(de_ctx);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ UTHFreePackets(&p, 1);
+ PASS;
+}
+
+/** \test Send Modbus Read Coils request and error response with Exception code invalid. */
+static int ModbusParserTest08(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ DetectEngineThreadCtx *det_ctx = NULL;
+ Flow f;
+ Packet *p = NULL;
+ Signature *s = NULL;
+ TcpSession ssn;
+ ThreadVars tv;
+
+ FAIL_IF_NULL(alp_tctx);
+
+ memset(&tv, 0, sizeof(ThreadVars));
+ memset(&f, 0, sizeof(Flow));
+ memset(&ssn, 0, sizeof(TcpSession));
+
+ p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
+
+ FLOW_INITIALIZE(&f);
+ f.alproto = ALPROTO_MODBUS;
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+ f.flags |= FLOW_IPV4;
+
+ p->flow = &f;
+ p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
+ p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
+
+ StreamTcpInitConfig(true);
+
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ FAIL_IF_NULL(de_ctx);
+
+ de_ctx->flags |= DE_QUIET;
+ s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+ "(msg:\"Modbus Exception code invalid\"; "
+ "app-layer-event: "
+ "modbus.invalid_exception_code; "
+ "sid:1;)");
+ FAIL_IF_NULL(s);
+
+ SigGroupBuild(de_ctx);
+ DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOSERVER, readCoilsReq,
+ sizeof(readCoilsReq));
+ FAIL_IF_NOT(r == 0);
+
+ ModbusState *modbus_state = f.alstate;
+ FAIL_IF_NULL(modbus_state);
+
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
+
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 1);
+ FAIL_IF_NOT(rs_modbus_message_get_read_request_address(&request) == 0x7890);
+ FAIL_IF_NOT(rs_modbus_message_get_read_request_quantity(&request) == 19);
+
+ r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOCLIENT, readCoilsErrorRsp,
+ sizeof(readCoilsErrorRsp));
+ FAIL_IF_NOT(r == 0);
+
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
+
+ /* do detect */
+ SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+ FAIL_IF_NOT(PacketAlertCheck(p, 1));
+
+ SigGroupCleanup(de_ctx);
+ SigCleanSignatures(de_ctx);
+
+ DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
+ DetectEngineCtxFree(de_ctx);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ UTHFreePackets(&p, 1);
+ PASS;
+}
+
+/** \test Modbus fragmentation - 1 ADU over 2 TCP packets. */
+static int ModbusParserTest09(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ Flow f;
+ TcpSession ssn;
+
+ uint32_t input_len = sizeof(readCoilsReq), part2_len = 3;
+ uint8_t *input = readCoilsReq;
+
+ FAIL_IF_NULL(alp_tctx);
+
+ memset(&f, 0, sizeof(f));
+ memset(&ssn, 0, sizeof(ssn));
+
+ FLOW_INITIALIZE(&f);
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+
+ StreamTcpInitConfig(true);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOSERVER, input, input_len - part2_len);
+ FAIL_IF_NOT(r == 1);
+
+ r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOSERVER, input, input_len);
+ FAIL_IF_NOT(r == 0);
+
+ ModbusState *modbus_state = f.alstate;
+ FAIL_IF_NULL(modbus_state);
+
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
+
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 1);
+ FAIL_IF_NOT(rs_modbus_message_get_read_request_address(&request) == 0x7890);
+ FAIL_IF_NOT(rs_modbus_message_get_read_request_quantity(&request) == 19);
+
+ input_len = sizeof(readCoilsRsp);
+ part2_len = 10;
+ input = readCoilsRsp;
+
+ r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOCLIENT, input, input_len - part2_len);
+ FAIL_IF_NOT(r == 1);
+
+ r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOCLIENT, input, input_len);
+ FAIL_IF_NOT(r == 0);
+
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ PASS;
+}
+
+/** \test Modbus fragmentation - 2 ADU in 1 TCP packet. */
+static int ModbusParserTest10(void) {
+ uint32_t input_len = sizeof(readCoilsReq) + sizeof(writeMultipleRegistersReq);
+ uint8_t *input, *ptr;
+
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ Flow f;
+ TcpSession ssn;
+
+ FAIL_IF_NULL(alp_tctx);
+
+ input = (uint8_t *) SCMalloc (input_len * sizeof(uint8_t));
+ FAIL_IF_NULL(input);
+
+ memcpy(input, readCoilsReq, sizeof(readCoilsReq));
+ memcpy(input + sizeof(readCoilsReq), writeMultipleRegistersReq, sizeof(writeMultipleRegistersReq));
+
+ memset(&f, 0, sizeof(f));
+ memset(&ssn, 0, sizeof(ssn));
+
+ FLOW_INITIALIZE(&f);
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+
+ StreamTcpInitConfig(true);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOSERVER, input, input_len);
+ FAIL_IF_NOT(r == 0);
+
+ ModbusState *modbus_state = f.alstate;
+ FAIL_IF_NULL(modbus_state);
+
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 2);
+
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 1);
+ FAIL_IF_NULL(request._0);
+
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 16);
+ FAIL_IF_NOT(rs_modbus_message_get_write_multreq_address(&request) == 0x01);
+ FAIL_IF_NOT(rs_modbus_message_get_write_multreq_quantity(&request) == 2);
+
+ size_t data_len;
+ uint8_t const *data = rs_modbus_message_get_write_multreq_data(&request, &data_len);
+ FAIL_IF_NOT(data_len == 4);
+ FAIL_IF_NOT(data[0] == 0x00);
+ FAIL_IF_NOT(data[1] == 0x0A);
+ FAIL_IF_NOT(data[2] == 0x01);
+ FAIL_IF_NOT(data[3] == 0x02);
+
+ input_len = sizeof(readCoilsRsp) + sizeof(writeMultipleRegistersRsp);
+
+ ptr = (uint8_t *) SCRealloc (input, input_len * sizeof(uint8_t));
+ FAIL_IF_NULL(ptr);
+ input = ptr;
+
+ memcpy(input, readCoilsRsp, sizeof(readCoilsRsp));
+ memcpy(input + sizeof(readCoilsRsp), writeMultipleRegistersRsp, sizeof(writeMultipleRegistersRsp));
+
+ r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT, input, input_len);
+ FAIL_IF_NOT(r == 0);
+
+ SCFree(input);
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ PASS;
+}
+
+/** \test Send Modbus exceed Length request. */
+static int ModbusParserTest11(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ DetectEngineThreadCtx *det_ctx = NULL;
+ Flow f;
+ Packet *p = NULL;
+ Signature *s = NULL;
+ TcpSession ssn;
+ ThreadVars tv;
+
+ size_t input_len = 65536;
+ uint8_t *input = SCCalloc(1, input_len);
+
+ FAIL_IF(input == NULL);
+
+ memcpy(input, exceededLengthWriteMultipleRegistersReq,
+ sizeof(exceededLengthWriteMultipleRegistersReq));
+
+ FAIL_IF(alp_tctx == NULL);
+
+ memset(&tv, 0, sizeof(ThreadVars));
+ memset(&f, 0, sizeof(Flow));
+ memset(&ssn, 0, sizeof(TcpSession));
+
+ p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
+
+ FLOW_INITIALIZE(&f);
+ f.alproto = ALPROTO_MODBUS;
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+ f.flags |= FLOW_IPV4;
+
+ p->flow = &f;
+ p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
+ p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
+
+ StreamTcpInitConfig(true);
+
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ FAIL_IF_NULL(de_ctx);
+
+ de_ctx->flags |= DE_QUIET;
+ s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+ "(msg:\"Modbus invalid Length\"; "
+ "app-layer-event: "
+ "modbus.invalid_length; "
+ "sid:1;)");
+ FAIL_IF_NULL(s);
+
+ SigGroupBuild(de_ctx);
+ DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+ int r = AppLayerParserParse(
+ NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER, input, input_len);
+ FAIL_IF_NOT(r == 0);
+
+ ModbusState *modbus_state = f.alstate;
+ FAIL_IF_NULL(modbus_state);
+
+ /* do detect */
+ SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+ FAIL_IF_NOT(PacketAlertCheck(p, 1));
+
+ SigGroupCleanup(de_ctx);
+ SigCleanSignatures(de_ctx);
+
+ DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
+ DetectEngineCtxFree(de_ctx);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ UTHFreePackets(&p, 1);
+ PASS;
+}
+
+/** \test Send Modbus invalid PDU Length. */
+static int ModbusParserTest12(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ DetectEngineThreadCtx *det_ctx = NULL;
+ Flow f;
+ Packet *p = NULL;
+ Signature *s = NULL;
+ TcpSession ssn;
+ ThreadVars tv;
+
+ FAIL_IF_NULL(alp_tctx);
+
+ memset(&tv, 0, sizeof(ThreadVars));
+ memset(&f, 0, sizeof(Flow));
+ memset(&ssn, 0, sizeof(TcpSession));
+
+ p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
+
+ FLOW_INITIALIZE(&f);
+ f.alproto = ALPROTO_MODBUS;
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+ f.flags |= FLOW_IPV4;
+
+ p->flow = &f;
+ p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
+ p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
+
+ StreamTcpInitConfig(true);
+
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ FAIL_IF_NULL(de_ctx);
+
+ de_ctx->flags |= DE_QUIET;
+ s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+ "(msg:\"Modbus invalid Length\"; "
+ "app-layer-event: "
+ "modbus.invalid_length; "
+ "sid:1;)");
+ FAIL_IF_NULL(s);
+
+ SigGroupBuild(de_ctx);
+ DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOSERVER,
+ invalidLengthPDUWriteMultipleRegistersReq,
+ sizeof(invalidLengthPDUWriteMultipleRegistersReq));
+ FAIL_IF_NOT(r == 0);
+
+ ModbusState *modbus_state = f.alstate;
+ FAIL_IF_NULL(modbus_state);
+
+ /* do detect */
+ SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+ FAIL_IF_NOT(PacketAlertCheck(p, 1));
+
+ SigGroupCleanup(de_ctx);
+ SigCleanSignatures(de_ctx);
+
+ DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
+ DetectEngineCtxFree(de_ctx);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ UTHFreePackets(&p, 1);
+ PASS;
+}
+
+/** \test Send Modbus Mask Write register request/response. */
+static int ModbusParserTest13(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ Flow f;
+ TcpSession ssn;
+
+ FAIL_IF_NULL(alp_tctx);
+
+ memset(&f, 0, sizeof(f));
+ memset(&ssn, 0, sizeof(ssn));
+
+ FLOW_INITIALIZE(&f);
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+
+ StreamTcpInitConfig(true);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOSERVER, maskWriteRegisterReq,
+ sizeof(maskWriteRegisterReq));
+ FAIL_IF_NOT(r == 0);
+
+ ModbusState *modbus_state = f.alstate;
+ FAIL_IF_NULL(modbus_state);
+
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
+
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 22);
+ FAIL_IF_NOT(rs_modbus_message_get_and_mask(&request) == 0x00F2);
+ FAIL_IF_NOT(rs_modbus_message_get_or_mask(&request) == 0x0025);
+
+ r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOCLIENT, maskWriteRegisterRsp,
+ sizeof(maskWriteRegisterRsp));
+ FAIL_IF_NOT(r == 0);
+
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ PASS;
+}
+
+/** \test Send Modbus Write single register request/response. */
+static int ModbusParserTest14(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ Flow f;
+ TcpSession ssn;
+
+ FAIL_IF_NULL(alp_tctx);
+
+ memset(&f, 0, sizeof(f));
+ memset(&ssn, 0, sizeof(ssn));
+
+ FLOW_INITIALIZE(&f);
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+
+ StreamTcpInitConfig(true);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOSERVER, writeSingleRegisterReq,
+ sizeof(writeSingleRegisterReq));
+ FAIL_IF_NOT(r == 0);
+
+ ModbusState *modbus_state = f.alstate;
+ FAIL_IF_NULL(modbus_state);
+
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
+
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 6);
+ FAIL_IF_NOT(rs_modbus_message_get_write_address(&request) == 0x0001);
+ FAIL_IF_NOT(rs_modbus_message_get_write_data(&request) == 0x0003);
+
+ r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOCLIENT, writeSingleRegisterRsp,
+ sizeof(writeSingleRegisterRsp));
+ FAIL_IF_NOT(r == 0);
+
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ PASS;
+}
+
+/** \test Send invalid Modbus Mask Write register request. */
+static int ModbusParserTest15(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ DetectEngineThreadCtx *det_ctx = NULL;
+ Flow f;
+ Packet *p = NULL;
+ Signature *s = NULL;
+ TcpSession ssn;
+ ThreadVars tv;
+
+ FAIL_IF_NULL(alp_tctx);
+
+ memset(&tv, 0, sizeof(ThreadVars));
+ memset(&f, 0, sizeof(f));
+ memset(&ssn, 0, sizeof(ssn));
+
+ p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
+
+ FLOW_INITIALIZE(&f);
+ f.alproto = ALPROTO_MODBUS;
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+ f.flags |= FLOW_IPV4;
+
+ p->flow = &f;
+ p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
+ p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
+
+ StreamTcpInitConfig(true);
+
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ FAIL_IF_NULL(de_ctx);
+
+ de_ctx->flags |= DE_QUIET;
+ s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+ "(msg:\"Modbus invalid Length\"; "
+ "app-layer-event: "
+ "modbus.invalid_length; "
+ "sid:1;)");
+ FAIL_IF_NULL(s);
+
+ SigGroupBuild(de_ctx);
+ DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOSERVER, invalidMaskWriteRegisterReq,
+ sizeof(invalidMaskWriteRegisterReq));
+ FAIL_IF_NOT(r == 0);
+
+ ModbusState *modbus_state = f.alstate;
+ FAIL_IF_NULL(modbus_state);
+
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
+
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 22);
+
+ /* do detect */
+ SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+ FAIL_IF_NOT(PacketAlertCheck(p, 1));
+
+ r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOCLIENT, maskWriteRegisterRsp,
+ sizeof(maskWriteRegisterRsp));
+ FAIL_IF_NOT(r == 0);
+
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
+ ModbusMessage response = rs_modbus_state_get_tx_response(modbus_state, 0);
+ FAIL_IF_NULL(response._0);
+
+ FAIL_IF_NOT(rs_modbus_message_get_function(&response) == 22);
+
+ SigGroupCleanup(de_ctx);
+ SigCleanSignatures(de_ctx);
+
+ DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
+ DetectEngineCtxFree(de_ctx);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ UTHFreePackets(&p, 1);
+ PASS;
+}
+
+/** \test Send invalid Modbus Mask Write register request. */
+static int ModbusParserTest16(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ DetectEngineThreadCtx *det_ctx = NULL;
+ Flow f;
+ Packet *p = NULL;
+ Signature *s = NULL;
+ TcpSession ssn;
+ ThreadVars tv;
+
+ FAIL_IF_NULL(alp_tctx);
+
+ memset(&tv, 0, sizeof(ThreadVars));
+ memset(&f, 0, sizeof(f));
+ memset(&ssn, 0, sizeof(ssn));
+
+ p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
+
+ FLOW_INITIALIZE(&f);
+ f.alproto = ALPROTO_MODBUS;
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+ f.flags |= FLOW_IPV4;
+
+ p->flow = &f;
+ p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
+ p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
+
+ StreamTcpInitConfig(true);
+
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ FAIL_IF_NULL(de_ctx);
+
+ de_ctx->flags |= DE_QUIET;
+ s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+ "(msg:\"Modbus invalid Length\"; "
+ "app-layer-event: "
+ "modbus.invalid_length; "
+ "sid:1;)");
+ FAIL_IF_NULL(s);
+
+ SigGroupBuild(de_ctx);
+ DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOSERVER,
+ invalidWriteSingleRegisterReq,
+ sizeof(invalidWriteSingleRegisterReq));
+ FAIL_IF_NOT(r == 0);
+
+ ModbusState *modbus_state = f.alstate;
+ FAIL_IF_NULL(modbus_state);
+
+ ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+ FAIL_IF_NULL(request._0);
+
+ FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 6);
+ size_t data_len;
+ const uint8_t *data = rs_modbus_message_get_bytevec_data(&request, &data_len);
+ FAIL_IF_NOT(data_len == 2);
+ FAIL_IF_NOT(data[0] == 0x00);
+ FAIL_IF_NOT(data[1] == 0x01);
+
+ /* do detect */
+ SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+ FAIL_IF_NOT(PacketAlertCheck(p, 1));
+
+ r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOCLIENT, writeSingleRegisterRsp,
+ sizeof(writeSingleRegisterRsp));
+ FAIL_IF_NOT(r == 0);
+
+ FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
+ ModbusMessage response = rs_modbus_state_get_tx_response(modbus_state, 0);
+ FAIL_IF_NULL(response._0);
+
+ FAIL_IF_NOT(rs_modbus_message_get_function(&response) == 6);
+ FAIL_IF_NOT(rs_modbus_message_get_write_address(&response) == 0x0001);
+
+ SigGroupCleanup(de_ctx);
+ SigCleanSignatures(de_ctx);
+
+ DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
+ DetectEngineCtxFree(de_ctx);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ UTHFreePackets(&p, 1);
+ PASS;}
+
+/** \test Checks if stream_depth is correct */
+static int ModbusParserTest17(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ Flow f;
+ TcpSession ssn;
+
+ FAIL_IF_NULL(alp_tctx);
+
+ memset(&f, 0, sizeof(f));
+ memset(&ssn, 0, sizeof(ssn));
+
+ FLOW_INITIALIZE(&f);
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+
+ StreamTcpInitConfig(true);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
+ readCoilsReq, sizeof(readCoilsReq));
+ FAIL_IF(r != 0);
+
+ FAIL_IF(f.alstate == NULL);
+
+ FAIL_IF(((TcpSession *)(f.protoctx))->reassembly_depth != MODBUS_CONFIG_DEFAULT_STREAM_DEPTH);
+
+ r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT,
+ readCoilsRsp, sizeof(readCoilsRsp));
+ FAIL_IF(r != 0);
+
+ FAIL_IF(((TcpSession *)(f.protoctx))->reassembly_depth != MODBUS_CONFIG_DEFAULT_STREAM_DEPTH);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ PASS;
+}
+
+/*/ \test Checks if stream depth is correct over 2 TCP packets */
+static int ModbusParserTest18(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ Flow f;
+ TcpSession ssn;
+
+ uint32_t input_len = sizeof(readCoilsReq), part2_len = 3;
+ uint8_t *input = readCoilsReq;
+
+ FAIL_IF_NULL(alp_tctx);
+
+ memset(&f, 0, sizeof(f));
+ memset(&ssn, 0, sizeof(ssn));
+
+ FLOW_INITIALIZE(&f);
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+
+ StreamTcpInitConfig(true);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
+ input, input_len - part2_len);
+ FAIL_IF(r != 1);
+
+ FAIL_IF(((TcpSession *)(f.protoctx))->reassembly_depth != MODBUS_CONFIG_DEFAULT_STREAM_DEPTH);
+
+ r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
+ input, input_len);
+ FAIL_IF(r != 0);
+
+ FAIL_IF(((TcpSession *)(f.protoctx))->reassembly_depth != MODBUS_CONFIG_DEFAULT_STREAM_DEPTH);
+
+ FAIL_IF(f.alstate == NULL);
+
+ input_len = sizeof(readCoilsRsp);
+ part2_len = 10;
+ input = readCoilsRsp;
+
+ r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT,
+ input, input_len - part2_len);
+ FAIL_IF(r != 1);
+
+ FAIL_IF(((TcpSession *)(f.protoctx))->reassembly_depth != MODBUS_CONFIG_DEFAULT_STREAM_DEPTH);
+
+ r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT,
+ input, input_len);
+ FAIL_IF(r != 0);
+
+ FAIL_IF(((TcpSession *)(f.protoctx))->reassembly_depth != MODBUS_CONFIG_DEFAULT_STREAM_DEPTH);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ PASS;
+}
+
+/** \test Send Modbus invalid function. */
+static int ModbusParserTest19(void) {
+ AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+ DetectEngineThreadCtx *det_ctx = NULL;
+ Flow f;
+ Packet *p = NULL;
+ Signature *s = NULL;
+ TcpSession ssn;
+ ThreadVars tv;
+
+ FAIL_IF_NULL(alp_tctx);
+
+ memset(&tv, 0, sizeof(ThreadVars));
+ memset(&f, 0, sizeof(Flow));
+ memset(&ssn, 0, sizeof(TcpSession));
+
+ p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
+
+ FLOW_INITIALIZE(&f);
+ f.alproto = ALPROTO_MODBUS;
+ f.protoctx = (void *)&ssn;
+ f.proto = IPPROTO_TCP;
+ f.alproto = ALPROTO_MODBUS;
+ f.flags |= FLOW_IPV4;
+
+ p->flow = &f;
+ p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
+ p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
+
+ StreamTcpInitConfig(true);
+
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ FAIL_IF_NULL(de_ctx);
+
+ de_ctx->flags |= DE_QUIET;
+ s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any "
+ "(msg:\"Modbus invalid Function code\"; "
+ "app-layer-event: "
+ "modbus.invalid_function_code; "
+ "sid:1;)");
+ FAIL_IF_NULL(s);
+
+ SigGroupBuild(de_ctx);
+ DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+
+ int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
+ STREAM_TOSERVER,
+ invalidFunctionCode,
+ sizeof(invalidFunctionCode));
+ FAIL_IF_NOT(r == 0);
+
+ ModbusState *modbus_state = f.alstate;
+ FAIL_IF_NULL(modbus_state);
+
+ /* do detect */
+ SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+ FAIL_IF_NOT(PacketAlertCheck(p, 1));
+
+ SigGroupCleanup(de_ctx);
+ SigCleanSignatures(de_ctx);
+
+ DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx);
+ DetectEngineCtxFree(de_ctx);
+
+ AppLayerParserThreadCtxFree(alp_tctx);
+ StreamTcpFreeConfig(true);
+ FLOW_DESTROY(&f);
+ UTHFreePackets(&p, 1);
+ PASS;
+}
+#endif /* UNITTESTS */
+
+void ModbusParserRegisterTests(void) {
+#ifdef UNITTESTS
+ UtRegisterTest("ModbusParserTest01 - Modbus Read Coils request",
+ ModbusParserTest01);
+ UtRegisterTest("ModbusParserTest02 - Modbus Write Multiple registers request",
+ ModbusParserTest02);
+ UtRegisterTest("ModbusParserTest03 - Modbus Read/Write Multiple registers request",
+ ModbusParserTest03);
+ UtRegisterTest("ModbusParserTest04 - Modbus Force Listen Only Mode request",
+ ModbusParserTest04);
+ UtRegisterTest("ModbusParserTest05 - Modbus invalid Protocol version",
+ ModbusParserTest05);
+ UtRegisterTest("ModbusParserTest06 - Modbus unsolicited response",
+ ModbusParserTest06);
+ UtRegisterTest("ModbusParserTest07 - Modbus invalid Length request",
+ ModbusParserTest07);
+ UtRegisterTest("ModbusParserTest08 - Modbus Exception code invalid",
+ ModbusParserTest08);
+ UtRegisterTest("ModbusParserTest09 - Modbus fragmentation - 1 ADU in 2 TCP packets",
+ ModbusParserTest09);
+ UtRegisterTest("ModbusParserTest10 - Modbus fragmentation - 2 ADU in 1 TCP packet",
+ ModbusParserTest10);
+ UtRegisterTest("ModbusParserTest11 - Modbus exceeded Length request",
+ ModbusParserTest11);
+ UtRegisterTest("ModbusParserTest12 - Modbus invalid PDU Length",
+ ModbusParserTest12);
+ UtRegisterTest("ModbusParserTest13 - Modbus Mask Write register request",
+ ModbusParserTest13);
+ UtRegisterTest("ModbusParserTest14 - Modbus Write single register request",
+ ModbusParserTest14);
+ UtRegisterTest("ModbusParserTest15 - Modbus invalid Mask Write register request",
+ ModbusParserTest15);
+ UtRegisterTest("ModbusParserTest16 - Modbus invalid Write single register request",
+ ModbusParserTest16);
+ UtRegisterTest("ModbusParserTest17 - Modbus stream depth",
+ ModbusParserTest17);
+ UtRegisterTest("ModbusParserTest18 - Modbus stream depth in 2 TCP packets",
+ ModbusParserTest18);
+ UtRegisterTest("ModbusParserTest19 - Modbus invalid Function code",
+ ModbusParserTest19);
+#endif /* UNITTESTS */
+}