From: DIALLO David Date: Thu, 14 Aug 2014 14:53:30 +0000 (+0200) Subject: Detect: Add Modbus keyword management X-Git-Tag: suricata-2.1beta3~143 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b3bf2f99394158285caae51e9773f519318b54ad;p=thirdparty%2Fsuricata.git Detect: Add Modbus keyword management Add the modbus.function and subfunction) keywords for public function match in rules (Modbus layer). Matching based on code function, and if necessary, sub-function code or based on category (assigned, unassigned, public, user or reserved) and negation is permitted. Add the modbus.access keyword for read/write Modbus function match in rules (Modbus layer). Matching based on access type (read or write), and/or function type (discretes, coils, input or holding) and, if necessary, read or write address access, and, if necessary, value to write. For address and value matching, "<", ">" and "<>" is permitted. Based on TLS source code and file size source code (address and value matching). Signed-off-by: David DIALLO --- diff --git a/src/Makefile.am b/src/Makefile.am index 5b2b3cc887..407018119a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -194,6 +194,7 @@ detect-uricontent.c detect-uricontent.h \ detect-urilen.c detect-urilen.h \ detect-window.c detect-window.h \ detect-within.c detect-within.h \ +detect-modbus.c detect-modbus.h \ flow-bit.c flow-bit.h \ flow.c flow.h \ flow-hash.c flow-hash.h \ diff --git a/src/detect-engine.c b/src/detect-engine.c index c393439792..d9655762aa 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -1596,6 +1596,9 @@ const char *DetectSigmatchListEnumToString(enum DetectSigmatchListEnum type) case DETECT_SM_LIST_DNSQUERY_MATCH: return "dns query"; + case DETECT_SM_LIST_MODBUS_MATCH: + return "modbus"; + case DETECT_SM_LIST_POSTMATCH: return "post-match"; diff --git a/src/detect-modbus.c b/src/detect-modbus.c new file mode 100644 index 0000000000..4c23e34810 --- /dev/null +++ b/src/detect-modbus.c @@ -0,0 +1,895 @@ +/* + * 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 + * + * Implements the Modbus function and access keywords + * You can specify a: + * - concrete function like Modbus: + * function 8, subfunction 4 (diagnostic: Force Listen Only Mode) + * - data (in primary table) register access (r/w) like Modbus: + * access read coils, address 1000 (.i.e Read coils: at address 1000) + * - write data value at specific address Modbus: + * access write, address 1500<>2000, value >2000 (Write multiple coils/register: + * at address between 1500 and 2000 value greater than 2000) + */ + +#include "suricata-common.h" + +#include "detect.h" +#include "detect-parse.h" + +#include "detect-modbus.h" + +#include "util-debug.h" + +#include "app-layer-modbus.h" + +#include "stream-tcp.h" + +/** + * \brief Regex for parsing the Modbus function string + */ +#define PARSE_REGEX_FUNCTION "^\\s*\"?\\s*function\\s*(!?[A-z0-9]+)(,\\s*subfunction\\s+(\\d+))?\\s*\"?\\s*$" +static pcre *function_parse_regex; +static pcre_extra *function_parse_regex_study; + +/** + * \brief Regex for parsing the Modbus access string + */ +#define PARSE_REGEX_ACCESS "^\\s*\"?\\s*access\\s*(read|write)\\s*(discretes|coils|input|holding)?(,\\s*address\\s+([<>]?\\d+)(<>\\d+)?(,\\s*value\\s+([<>]?\\d+)(<>\\d+)?)?)?\\s*\"?\\s*$" +static pcre *access_parse_regex; +static pcre_extra *access_parse_regex_study; + +#define MAX_SUBSTRINGS 30 + +void DetectModbusRegisterTests(void); + +/** \internal + * + * \brief this function will free memory associated with DetectModbus + * + * \param ptr pointer to DetectModbus + */ +static void DetectModbusFree(void *ptr) { + SCEnter(); + DetectModbus *modbus = (DetectModbus *) ptr; + + if(modbus) { + if (modbus->subfunction) + SCFree(modbus->subfunction); + + if (modbus->address) + SCFree(modbus->address); + + if (modbus->data) + SCFree(modbus->data); + + SCFree(modbus); + } +} + +/** \internal + * + * \brief This function is used to parse Modbus parameters in access mode + * + * \param str Pointer to the user provided id option + * + * \retval Pointer to DetectModbusData on success or NULL on failure + */ +static DetectModbus *DetectModbusAccessParse(char *str) +{ + SCEnter(); + DetectModbus *modbus = NULL; + + char arg[MAX_SUBSTRINGS]; + int ov[MAX_SUBSTRINGS], ret, res; + + ret = pcre_exec(access_parse_regex, access_parse_regex_study, str, strlen(str), 0, 0, ov, MAX_SUBSTRINGS); + + if (ret < 1) + goto error; + + res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 1, arg, MAX_SUBSTRINGS); + if (res < 0) { + SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); + goto error; + } + + /* We have a correct Modbus option */ + modbus = (DetectModbus *) SCCalloc(1, sizeof(DetectModbus)); + if (unlikely(modbus == NULL)) + goto error; + + if (strcmp(arg, "read") == 0) + modbus->type = MODBUS_TYP_READ; + else if (strcmp(arg, "write") == 0) + modbus->type = MODBUS_TYP_WRITE; + else + goto error; + + if (ret > 2) { + res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 2, arg, MAX_SUBSTRINGS); + if (res < 0) { + SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); + goto error; + } + + if (*arg != '\0') { + if (strcmp(arg, "discretes") == 0) { + if (modbus->type == MODBUS_TYP_WRITE) + /* Discrete access is only read access. */ + goto error; + + modbus->type |= MODBUS_TYP_DISCRETES; + } + else if (strcmp(arg, "coils") == 0) { + modbus->type |= MODBUS_TYP_COILS; + } + else if (strcmp(arg, "input") == 0) { + if (modbus->type == MODBUS_TYP_WRITE) { + /* Input access is only read access. */ + goto error; + } + + modbus->type |= MODBUS_TYP_INPUT; + } + else if (strcmp(arg, "holding") == 0) { + modbus->type |= MODBUS_TYP_HOLDING; + } + else + goto error; + } + + if (ret > 4) { + res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 4, arg, MAX_SUBSTRINGS); + if (res < 0) { + SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); + goto error; + } + + /* We have a correct address option */ + modbus->address = (DetectModbusValue *) SCCalloc(1, sizeof(DetectModbusValue)); + if (unlikely(modbus->address == NULL)) + goto error; + + if (arg[0] == '>') { + modbus->address->min = atoi((const char*) (arg+1)); + modbus->address->mode = DETECT_MODBUS_GT; + } else if (arg[0] == '<') { + modbus->address->min = atoi((const char*) (arg+1)); + modbus->address->mode = DETECT_MODBUS_LT; + } else { + modbus->address->min = atoi((const char*) arg); + } + SCLogDebug("and min/equal address %d", modbus->address->min); + + if (ret > 5) { + res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 5, arg, MAX_SUBSTRINGS); + if (res < 0) { + SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); + goto error; + } + + if (*arg != '\0') { + modbus->address->max = atoi((const char*) (arg+2)); + modbus->address->mode = DETECT_MODBUS_RA; + SCLogDebug("and max address %d", modbus->address->max); + } + + if (ret > 7) { + res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 7, arg, MAX_SUBSTRINGS); + if (res < 0) { + SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); + goto error; + } + + if (modbus->address->mode != DETECT_MODBUS_EQ) { + SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords (address range and value)."); + goto error; + } + + /* We have a correct address option */ + modbus->data = (DetectModbusValue *) SCCalloc(1, sizeof(DetectModbusValue)); + if (unlikely(modbus->data == NULL)) + goto error; + + if (arg[0] == '>') { + modbus->data->min = atoi((const char*) (arg+1)); + modbus->data->mode = DETECT_MODBUS_GT; + } else if (arg[0] == '<') { + modbus->data->min = atoi((const char*) (arg+1)); + modbus->data->mode = DETECT_MODBUS_LT; + } else { + modbus->data->min = atoi((const char*) arg); + } + SCLogDebug("and min/equal value %d", modbus->data->min); + + if (ret > 8) { + res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 8, arg, MAX_SUBSTRINGS); + if (res < 0) { + SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); + goto error; + } + + if (*arg != '\0') { + modbus->data->max = atoi((const char*) (arg+2)); + modbus->data->mode = DETECT_MODBUS_RA; + SCLogDebug("and max value %d", modbus->data->max); + } + } + } + } + } + } + + SCReturnPtr(modbus, "DetectModbusAccess"); + +error: + if (modbus != NULL) + DetectModbusFree(modbus); + + SCReturnPtr(NULL, "DetectModbus"); +} + +/** \internal + * + * \brief This function is used to parse Modbus parameters in function mode + * + * \param str Pointer to the user provided id option + * + * \retval id_d pointer to DetectModbusData on success + * \retval NULL on failure + */ +static DetectModbus *DetectModbusFunctionParse(char *str) +{ + SCEnter(); + DetectModbus *modbus = NULL; + + char arg[MAX_SUBSTRINGS], *ptr = arg; + int ov[MAX_SUBSTRINGS], res, ret; + + ret = pcre_exec(function_parse_regex, function_parse_regex_study, str, strlen(str), 0, 0, ov, MAX_SUBSTRINGS); + + if (ret < 1) + goto error; + + res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 1, arg, MAX_SUBSTRINGS); + if (res < 0) { + SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); + goto error; + } + + /* We have a correct Modbus function option */ + modbus = (DetectModbus *) SCCalloc(1, sizeof(DetectModbus)); + if (unlikely(modbus == NULL)) + goto error; + + if (isdigit(ptr[0])) { + modbus->function = atoi((const char*) ptr); + SCLogDebug("will look for modbus function %d", modbus->function); + + if (ret > 2) { + res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 3, arg, MAX_SUBSTRINGS); + if (res < 0) { + SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); + goto error; + } + + /* We have a correct address option */ + modbus->subfunction =(uint16_t *) SCCalloc(1, sizeof(uint16_t)); + if (modbus->subfunction == NULL) + goto error; + + *(modbus->subfunction) = atoi((const char*) arg); + SCLogDebug("and subfunction %d", *(modbus->subfunction)); + } + } else { + uint8_t neg = 0; + + if (ptr[0] == '!') { + neg = 1; + ptr++; + } + + if (strcmp("assigned", ptr) == 0) + modbus->category = MODBUS_CAT_PUBLIC_ASSIGNED; + else if (strcmp("unassigned", ptr) == 0) + modbus->category = MODBUS_CAT_PUBLIC_UNASSIGNED; + else if (strcmp("public", ptr) == 0) + modbus->category = MODBUS_CAT_PUBLIC_ASSIGNED | MODBUS_CAT_PUBLIC_UNASSIGNED; + else if (strcmp("user", ptr) == 0) + modbus->category = MODBUS_CAT_USER_DEFINED; + else if (strcmp("reserved", ptr) == 0) + modbus->category = MODBUS_CAT_RESERVED; + else if (strcmp("all", ptr) == 0) + modbus->category = MODBUS_CAT_ALL; + + if (neg) + modbus->category = ~modbus->category; + SCLogDebug("will look for modbus category function %d", modbus->category); + } + + SCReturnPtr(modbus, "DetectModbusFunction"); + +error: + if (modbus != NULL) + DetectModbusFree(modbus); + + SCReturnPtr(NULL, "DetectModbus"); +} + +/** \internal + * + * \brief this function is used to add the parsed "id" option into the current signature + * + * \param de_ctx Pointer to the Detection Engine Context + * \param s Pointer to the Current Signature + * \param str Pointer to the user provided "id" option + * + * \retval 0 on Success or -1 on Failure + */ +static int DetectModbusSetup(DetectEngineCtx *de_ctx, Signature *s, char *str) +{ + SCEnter(); + DetectModbus *modbus = NULL; + SigMatch *sm = NULL; + + if (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_MODBUS) { + SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords."); + goto error; + } + + if ((modbus = DetectModbusFunctionParse(str)) == NULL) { + if ((modbus = DetectModbusAccessParse(str)) == NULL) { + SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option"); + goto error; + } + } + + /* Okay so far so good, lets get this into a SigMatch and put it in the Signature. */ + sm = SigMatchAlloc(); + if (sm == NULL) + goto error; + + sm->type = DETECT_AL_MODBUS; + sm->ctx = (void *) modbus; + + SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_MODBUS_MATCH); + s->alproto = ALPROTO_MODBUS; + + SCReturnInt(0); + +error: + if (modbus != NULL) + DetectModbusFree(modbus); + if (sm != NULL) + SCFree(sm); + SCReturnInt(-1); +} + +/** + * \brief Registration function for Modbus keyword + */ +void DetectModbusRegister(void) { + SCEnter(); + sigmatch_table[DETECT_AL_MODBUS].name = "modbus"; + sigmatch_table[DETECT_AL_MODBUS].Match = NULL; + sigmatch_table[DETECT_AL_MODBUS].AppLayerMatch = NULL; + sigmatch_table[DETECT_AL_MODBUS].alproto = ALPROTO_MODBUS; + sigmatch_table[DETECT_AL_MODBUS].Setup = DetectModbusSetup; + sigmatch_table[DETECT_AL_MODBUS].Free = DetectModbusFree; + sigmatch_table[DETECT_AL_MODBUS].RegisterTests = DetectModbusRegisterTests; + + const char *eb; + int eo, opts = 0; + + SCLogDebug("registering modbus rule option"); + + /* Function PARSE_REGEX */ + function_parse_regex = pcre_compile(PARSE_REGEX_FUNCTION, opts, &eb, &eo, NULL); + if (function_parse_regex == NULL) { + SCLogError(SC_ERR_PCRE_COMPILE, "Compile of \"%s\" failed at offset %" PRId32 ": %s", + PARSE_REGEX_FUNCTION, eo, eb); + goto error; + } + + function_parse_regex_study = pcre_study(function_parse_regex, 0, &eb); + if (eb != NULL) { + SCLogError(SC_ERR_PCRE_STUDY, "pcre study failed: %s", eb); + goto error; + } + + /* Access PARSE_REGEX */ + access_parse_regex = pcre_compile(PARSE_REGEX_ACCESS, opts, &eb, &eo, NULL); + if (access_parse_regex == NULL) { + SCLogError(SC_ERR_PCRE_COMPILE, "Compile of \"%s\" failed at offset %" PRId32 ": %s", + PARSE_REGEX_ACCESS, eo, eb); + goto error; + } + + access_parse_regex_study = pcre_study(access_parse_regex, 0, &eb); + if (eb != NULL) { + SCLogError(SC_ERR_PCRE_STUDY, "pcre study failed: %s", eb); + goto error; + } + +error: + SCReturn; +} + +#ifdef UNITTESTS /* UNITTESTS */ +#include "detect-engine.h" + +#include "util-unittest.h" + +/** \test Signature containing a function. */ +static int DetectModbusTest01(void) +{ + DetectEngineCtx *de_ctx = NULL; + DetectModbus *modbus = NULL; + + int result = 0; + + de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) + goto end; + + de_ctx->flags |= DE_QUIET; + + de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any " + "(msg:\"Testing modbus function\"; " + "modbus: function 1; sid:1;)"); + + if (de_ctx->sig_list == NULL) + goto end; + + if ((de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH] == NULL) || + (de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH]->ctx == NULL)) { + printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: "); + goto end; + } + + modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH]->ctx; + + if (modbus->function != 1) { + printf("expected function %" PRIu8 ", got %" PRIu8 ": ", 1, modbus->function); + goto end; + } + + result = 1; + + end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + DetectEngineCtxFree(de_ctx); + + return result; +} + +/** \test Signature containing a function and a subfunction. */ +static int DetectModbusTest02(void) +{ + DetectEngineCtx *de_ctx = NULL; + DetectModbus *modbus = NULL; + + int result = 0; + + de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) + goto end; + + de_ctx->flags |= DE_QUIET; + + de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any " + "(msg:\"Testing modbus function and subfunction\"; " + "modbus: function 8, subfunction 4; sid:1;)"); + + if (de_ctx->sig_list == NULL) + goto end; + + if ((de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH] == NULL) || + (de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH]->ctx == NULL)) { + printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: "); + goto end; + } + + modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH]->ctx; + + if ((modbus->function != 8) || (*modbus->subfunction != 4)) { + printf("expected function %" PRIu8 ", got %" PRIu8 ": ", 1, modbus->function); + printf("expected subfunction %" PRIu8 ", got %" PRIu16 ": ", 4, *modbus->subfunction); + goto end; + } + + result = 1; + + end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + DetectEngineCtxFree(de_ctx); + + return result; +} + +/** \test Signature containing a function category. */ +static int DetectModbusTest03(void) +{ + DetectEngineCtx *de_ctx = NULL; + DetectModbus *modbus = NULL; + + int result = 0; + + de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) + goto end; + + de_ctx->flags |= DE_QUIET; + + de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any " + "(msg:\"Testing modbus.function\"; " + "modbus: function reserved; sid:1;)"); + + if (de_ctx->sig_list == NULL) + goto end; + + if ((de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH] == NULL) || + (de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH]->ctx == NULL)) { + printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: "); + goto end; + } + + modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH]->ctx; + + if (modbus->category != MODBUS_CAT_RESERVED) { + printf("expected function %" PRIu8 ", got %" PRIu8 ": ", MODBUS_CAT_RESERVED, modbus->category); + goto end; + } + + result = 1; + + end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + DetectEngineCtxFree(de_ctx); + + return result; +} + +/** \test Signature containing a negative function category. */ +static int DetectModbusTest04(void) +{ + DetectEngineCtx *de_ctx = NULL; + DetectModbus *modbus = NULL; + + uint8_t category = ~MODBUS_CAT_PUBLIC_ASSIGNED; + + int result = 0; + + de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) + goto end; + + de_ctx->flags |= DE_QUIET; + + de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any " + "(msg:\"Testing modbus function\"; " + "modbus: function !assigned; sid:1;)"); + + if (de_ctx->sig_list == NULL) + goto end; + + if ((de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH] == NULL) || + (de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH]->ctx == NULL)) { + printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: "); + goto end; + } + + modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH]->ctx; + + if (modbus->category != category) { + printf("expected function %" PRIu8 ", got %" PRIu8 ": ", ~MODBUS_CAT_PUBLIC_ASSIGNED, modbus->category); + goto end; + } + + result = 1; + + end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + DetectEngineCtxFree(de_ctx); + + return result; +} + +/** \test Signature containing a access type. */ +static int DetectModbusTest05(void) +{ + DetectEngineCtx *de_ctx = NULL; + DetectModbus *modbus = NULL; + + int result = 0; + + de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) + goto end; + + de_ctx->flags |= DE_QUIET; + + de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any " + "(msg:\"Testing modbus.access\"; " + "modbus: access read; sid:1;)"); + + if (de_ctx->sig_list == NULL) + goto end; + + if ((de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH] == NULL) || + (de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH]->ctx == NULL)) { + printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: "); + goto end; + } + + modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH]->ctx; + + if (modbus->type != MODBUS_TYP_READ) { + printf("expected function %" PRIu8 ", got %" PRIu8 ": ", MODBUS_TYP_READ, modbus->type); + goto end; + } + + result = 1; + + end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + DetectEngineCtxFree(de_ctx); + + return result; +} + +/** \test Signature containing a access function. */ +static int DetectModbusTest06(void) +{ + DetectEngineCtx *de_ctx = NULL; + DetectModbus *modbus = NULL; + + uint8_t type = (MODBUS_TYP_READ | MODBUS_TYP_DISCRETES); + + int result = 0; + + de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) + goto end; + + de_ctx->flags |= DE_QUIET; + + de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any " + "(msg:\"Testing modbus.access\"; " + "modbus: access read discretes; sid:1;)"); + + if (de_ctx->sig_list == NULL) + goto end; + + if ((de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH] == NULL) || + (de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH]->ctx == NULL)) { + printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: "); + goto end; + } + + modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH]->ctx; + + if (modbus->type != type) { + printf("expected function %" PRIu8 ", got %" PRIu8 ": ", type, modbus->type); + goto end; + } + + result = 1; + + end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + DetectEngineCtxFree(de_ctx); + + return result; +} + +/** \test Signature containing a read access at an address. */ +static int DetectModbusTest07(void) +{ + DetectEngineCtx *de_ctx = NULL; + DetectModbus *modbus = NULL; + DetectModbusMode mode = DETECT_MODBUS_EQ; + + uint8_t type = MODBUS_TYP_READ; + int result = 0; + + de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) + goto end; + + de_ctx->flags |= DE_QUIET; + + de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any " + "(msg:\"Testing modbus.access\"; " + "modbus: access read, address 1000; sid:1;)"); + + if (de_ctx->sig_list == NULL) + goto end; + + if ((de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH] == NULL) || + (de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH]->ctx == NULL)) { + printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: "); + goto end; + } + + modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH]->ctx; + + if ((modbus->type != type) || + ((*modbus->address).mode != mode) || + ((*modbus->address).min != 1000)) { + printf("expected function %" PRIu8 ", got %" PRIu8 ": ", type, modbus->type); + printf("expected mode %" PRIu8 ", got %" PRIu16 ": ", mode, (*modbus->address).mode); + printf("expected address %" PRIu8 ", got %" PRIu16 ": ", 1000, (*modbus->address).min); + goto end; + } + + result = 1; + + end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + DetectEngineCtxFree(de_ctx); + + return result; +} + +/** \test Signature containing a write access at a range of address. */ +static int DetectModbusTest08(void) +{ + DetectEngineCtx *de_ctx = NULL; + DetectModbus *modbus = NULL; + DetectModbusMode mode = DETECT_MODBUS_GT; + + uint8_t type = (MODBUS_TYP_WRITE | MODBUS_TYP_COILS); + int result = 0; + + de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) + goto end; + + de_ctx->flags |= DE_QUIET; + + de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any " + "(msg:\"Testing modbus.access\"; " + "modbus: access write coils, address >500; sid:1;)"); + + if (de_ctx->sig_list == NULL) + goto end; + + if ((de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH] == NULL) || + (de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH]->ctx == NULL)) { + printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: "); + goto end; + } + + modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH]->ctx; + + if ((modbus->type != type) || + ((*modbus->address).mode != mode) || + ((*modbus->address).min != 500)) { + printf("expected function %" PRIu8 ", got %" PRIu8 ": ", type, modbus->type); + printf("expected mode %" PRIu8 ", got %" PRIu16 ": ", mode, (*modbus->address).mode); + printf("expected address %" PRIu8 ", got %" PRIu16 ": ", 500, (*modbus->address).min); + goto end; + } + + result = 1; + + end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + DetectEngineCtxFree(de_ctx); + + return result; +} + +/** \test Signature containing a write access at a address a range of value. */ +static int DetectModbusTest09(void) +{ + DetectEngineCtx *de_ctx = NULL; + DetectModbus *modbus = NULL; + DetectModbusMode addressMode = DETECT_MODBUS_EQ; + DetectModbusMode valueMode = DETECT_MODBUS_RA; + + uint8_t type = (MODBUS_TYP_WRITE | MODBUS_TYP_HOLDING); + int result = 0; + + de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) + goto end; + + de_ctx->flags |= DE_QUIET; + + de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any " + "(msg:\"Testing modbus.access\"; " + "modbus: access write holding, address 100, value 500<>1000; sid:1;)"); + + if (de_ctx->sig_list == NULL) + goto end; + + if ((de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH] == NULL) || + (de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH]->ctx == NULL)) { + printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: "); + goto end; + } + + modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[DETECT_SM_LIST_MODBUS_MATCH]->ctx; + + if ((modbus->type != type) || + ((*modbus->address).mode != addressMode) || + ((*modbus->address).min != 100) || + ((*modbus->data).mode != valueMode) || + ((*modbus->data).min != 500) || + ((*modbus->data).max != 1000)) { + printf("expected function %" PRIu8 ", got %" PRIu8 ": ", type, modbus->type); + printf("expected address mode %" PRIu8 ", got %" PRIu16 ": ", addressMode, (*modbus->address).mode); + printf("expected address %" PRIu8 ", got %" PRIu16 ": ", 500, (*modbus->address).min); + printf("expected value mode %" PRIu8 ", got %" PRIu16 ": ", valueMode, (*modbus->data).mode); + printf("expected min value %" PRIu8 ", got %" PRIu16 ": ", 500, (*modbus->data).min); + printf("expected max value %" PRIu8 ", got %" PRIu16 ": ", 1000, (*modbus->data).max); + goto end; + } + + result = 1; + + end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + DetectEngineCtxFree(de_ctx); + + return result; +} +#endif /* UNITTESTS */ + +/** + * \brief this function registers unit tests for DetectModbus + */ +void DetectModbusRegisterTests(void) { +#ifdef UNITTESTS /* UNITTESTS */ + UtRegisterTest("DetectModbusTest01 - Testing function", DetectModbusTest01, 1); + UtRegisterTest("DetectModbusTest02 - Testing function and subfunction", DetectModbusTest02, 1); + UtRegisterTest("DetectModbusTest03 - Testing category function", DetectModbusTest03, 1); + UtRegisterTest("DetectModbusTest04 - Testing category function in negative", DetectModbusTest04, 1); + UtRegisterTest("DetectModbusTest05 - Testing access type", DetectModbusTest05, 1); + UtRegisterTest("DetectModbusTest06 - Testing access function", DetectModbusTest06, 1); + UtRegisterTest("DetectModbusTest07 - Testing access at address", DetectModbusTest07, 1); + UtRegisterTest("DetectModbusTest08 - Testing a range of address", DetectModbusTest08, 1); + UtRegisterTest("DetectModbusTest09 - Testing write a range of value", DetectModbusTest09, 1); +#endif /* UNITTESTS */ +} diff --git a/src/detect-modbus.h b/src/detect-modbus.h new file mode 100644 index 0000000000..6675cde8b2 --- /dev/null +++ b/src/detect-modbus.h @@ -0,0 +1,64 @@ +/* + * 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 + */ + +#ifndef __DETECT_MODBUS_H__ +#define __DETECT_MODBUS_H__ + +#include "app-layer-modbus.h" + +typedef enum { + DETECT_MODBUS_EQ = 0, /** < EQual operator */ + DETECT_MODBUS_LT, /** < "Less Than" operator */ + DETECT_MODBUS_GT, /** < "Greater Than" operator */ + DETECT_MODBUS_RA, /** < RAnge operator */ +} DetectModbusMode; + +typedef struct DetectModbusValue_ { + uint16_t min; /** < Modbus minimum [range] or equal value to match */ + uint16_t max; /** < Modbus maximum value [range] to match */ + DetectModbusMode mode; /** < Modbus operator used in the address/data signature */ +} DetectModbusValue; + +typedef struct DetectModbus_ { + uint8_t category; /** < Modbus function code category to match */ + uint8_t function; /** < Modbus function code to match */ + uint16_t *subfunction; /** < Modbus subfunction to match */ + uint8_t type; /** < Modbus access type to match */ + DetectModbusValue *address; /** < Modbus address to match */ + DetectModbusValue *data; /** < Modbus data to match */ +} DetectModbus; + +/* prototypes */ +void DetectModbusRegister (void); + +#endif /* __DETECT_MODBUS_H__ */ diff --git a/src/detect-parse.c b/src/detect-parse.c index b19c2b7e36..8145b2c1ab 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -1461,6 +1461,8 @@ static Signature *SigInitHelper(DetectEngineCtx *de_ctx, char *sigstr, sig->flags |= SIG_FLAG_STATE_MATCH; if (sig->sm_lists[DETECT_SM_LIST_DNSQUERY_MATCH]) sig->flags |= SIG_FLAG_STATE_MATCH; + if (sig->sm_lists[DETECT_SM_LIST_MODBUS_MATCH]) + sig->flags |= SIG_FLAG_STATE_MATCH; if (sig->sm_lists[DETECT_SM_LIST_APP_EVENT]) sig->flags |= SIG_FLAG_STATE_MATCH; diff --git a/src/detect.c b/src/detect.c index f4f0d54e3d..737bb3409c 100644 --- a/src/detect.c +++ b/src/detect.c @@ -165,6 +165,7 @@ #include "detect-http-stat-code.h" #include "detect-ssl-version.h" #include "detect-ssl-state.h" +#include "detect-modbus.h" #include "action-globals.h" #include "tm-threads.h" @@ -4813,6 +4814,7 @@ void SigTableSetup(void) DetectLuaRegister(); DetectIPRepRegister(); DetectDnsQueryRegister(); + DetectModbusRegister(); DetectAppLayerProtocolRegister(); } diff --git a/src/detect.h b/src/detect.h index cc23fe3028..df2690d290 100644 --- a/src/detect.h +++ b/src/detect.h @@ -121,6 +121,7 @@ enum DetectSigmatchListEnum { DETECT_SM_LIST_FILEMATCH, DETECT_SM_LIST_DNSQUERY_MATCH, + DETECT_SM_LIST_MODBUS_MATCH, /* list for post match actions: flowbit set, flowint increment, etc */ DETECT_SM_LIST_POSTMATCH, @@ -1149,6 +1150,7 @@ enum { DETECT_IPREP, DETECT_AL_DNS_QUERY, + DETECT_AL_MODBUS, /* make sure this stays last */ DETECT_TBLSIZE,