/* * 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 * * 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 */ }