diff options
Diffstat (limited to '')
-rw-r--r-- | src/app-layer-modbus.c | 1562 |
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 */ +} |