modbus: Support Unit Identifier

When destination IP address does not suffice to uniquely identify
the Modbus/TCP device.

Some Modbus/TCP devices act as gateways to other Modbus/TCP devices
that are behind this gateways.
pull/3288/head
David DIALLO 8 years ago committed by Victor Julien
parent 71742ed52b
commit c2236ea2b3

@ -4,10 +4,11 @@ Modbus Keyword
The modbus keyword can be used for matching on various properties of The modbus keyword can be used for matching on various properties of
Modbus requests. Modbus requests.
There are two ways of using this keyword: There are three ways of using this keyword:
* matching on functions properties with the setting "function"; * matching on functions properties with the setting "function";
* matching on directly on data access with the setting "access". * matching on directly on data access with the setting "access";
* matching on unit identifier with the setting "unit" only or with the previous setting "function" or "access".
With the setting **function**, you can match on: With the setting **function**, you can match on:
@ -72,6 +73,43 @@ Examples::
modbus: access read discretes, address <100 # Read access at address smaller than 100 of Discretes Input table modbus: access read discretes, address <100 # Read access at address smaller than 100 of Discretes Input table
modbus: access write holding, address 500, value >200 # Write value greather than 200 at address 500 of Holding Registers table modbus: access write holding, address 500, value >200 # Write value greather than 200 at address 500 of Holding Registers table
With the setting **unit**, you can match on:
* a MODBUS slave address of a remote device connected on the sub-network behind a bridge or a gateway. The destination IP address identifies the bridge itself and the bridge uses the MODBUS unit identifier to forward the request to the right slave device.
Syntax::
modbus: unit <value>
modbus: unit <value>, function <value>
modbus: unit <value>, function <value>, subfunction <value>
modbus: unit <value>, function [!] <assigned | unassigned | public | user | reserved | all>
modbus: unit <value>, access <read | write>
modbus: unit <value>, access read <discretes | coils | input | holding>
modbus: unit <value>, access read <discretes | coils | input | holding>, address <value>
modbus: unit <value>, access write < coils | holding>
modbus: unit <value>, access write < coils | holding>, address <value>
modbus: unit <value>, access write < coils | holding>, address <value>, value <value>
With _<value>_ setting matches on the address or value as it is being
accessed or written as follows::
unit 10 # exactly unit identifier 10
unit 10<>20 # greater than unit identifier 10 and smaller than unit identifier 20
unit >10 # greater than unit identifier 10
unit <10 # smaller than unit identifier 10
Examples::
modbus: unit 10 # Unit identifier 10
modbus: unit 10, function 21 # Unit identifier 10 and write File record function
modbus: unit 10, function 4, subfunction 4 # Unit identifier 10 and force Listen Only Mode (Diagnostics) function
modbus: unit 10, function assigned # Unit identifier 10 and assigned function
modbus: unit 10, function !reserved # Unit identifier 10 and every function but reserved function
modbus: unit 10, access read # Unit identifier 10 and Read access
modbus: unit 10, access write coils # Unit identifier 10 and Write access to Coils table
modbus: unit >10, access read discretes, address <100 # Greater than unit identifier 10 and Read access at address smaller than 100 of Discretes Input table
modbus: unit 10<>20, access write holding, address 500, value >200 # Greater than unit identifier 10 and smaller than unit identifier 20 and Write value greather than 200 at address 500 of Holding Registers table
(cf. http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf) (cf. http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf)
**Note:** Address of read and write are starting at 1. So if your system **Note:** Address of read and write are starting at 1. So if your system
@ -83,6 +121,10 @@ remote device and not to open and close it for each MODBUS/TCP
transaction. In that case, it is important to set the depth of the transaction. In that case, it is important to set the depth of the
stream reassembling as unlimited (stream.reassembly.depth: 0) stream reassembling as unlimited (stream.reassembly.depth: 0)
**Note:** According to MODBUS Messaging on TCP/IP Implementation Guide
V1.0b, the MODBUS slave device addresses on serial line are assigned from 1 to
247 (decimal). Address 0 is used as broadcast address.
(cf. http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf) (cf. http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf)
Paper and presentation (in french) on Modbus support are available : Paper and presentation (in french) on Modbus support are available :

File diff suppressed because it is too large Load Diff

@ -78,6 +78,9 @@ enum {
#define MODBUS_TYP_WRITE_MULTIPLE (MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE) #define MODBUS_TYP_WRITE_MULTIPLE (MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE)
#define MODBUS_TYP_READ_WRITE_MULTIPLE (MODBUS_TYP_READ | MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE) #define MODBUS_TYP_READ_WRITE_MULTIPLE (MODBUS_TYP_READ | MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE)
/* Modbus Function Code. */
#define MODBUS_FUNC_NONE 0x00
/* Modbus Transaction Structure, request/response. */ /* Modbus Transaction Structure, request/response. */
typedef struct ModbusTransaction_ { typedef struct ModbusTransaction_ {
struct ModbusState_ *modbus; struct ModbusState_ *modbus;
@ -86,6 +89,7 @@ typedef struct ModbusTransaction_ {
uint32_t logged; /**< flags indicating which loggers have logged */ uint32_t logged; /**< flags indicating which loggers have logged */
uint16_t transactionId; uint16_t transactionId;
uint16_t length; uint16_t length;
uint8_t unit_id;
uint8_t function; uint8_t function;
uint8_t category; uint8_t category;
uint8_t type; uint8_t type;

File diff suppressed because it is too large Load Diff

@ -56,6 +56,13 @@
#include "stream-tcp.h" #include "stream-tcp.h"
/**
* \brief Regex for parsing the Modbus unit id string
*/
#define PARSE_REGEX_UNIT_ID "^\\s*\"?\\s*unit\\s+([<>]?\\d+)(<>\\d+)?(,\\s*(.*))?\\s*\"?\\s*$"
static pcre *unit_id_parse_regex;
static pcre_extra *unit_id_parse_regex_study;
/** /**
* \brief Regex for parsing the Modbus function string * \brief Regex for parsing the Modbus function string
*/ */
@ -90,6 +97,9 @@ static void DetectModbusFree(void *ptr) {
if (modbus->subfunction) if (modbus->subfunction)
SCFree(modbus->subfunction); SCFree(modbus->subfunction);
if (modbus->unit_id)
SCFree(modbus->unit_id);
if (modbus->address) if (modbus->address)
SCFree(modbus->address); SCFree(modbus->address);
@ -302,6 +312,14 @@ static DetectModbus *DetectModbusFunctionParse(const char *str)
if (isdigit((unsigned char)ptr[0])) { if (isdigit((unsigned char)ptr[0])) {
modbus->function = atoi((const char*) ptr); modbus->function = atoi((const char*) ptr);
/* Function code 0 is managed by decoder_event INVALID_FUNCTION_CODE */
if (modbus->function == MODBUS_FUNC_NONE) {
SCLogError(SC_ERR_INVALID_SIGNATURE,
"Invalid argument \"%d\" supplied to modbus function keyword.",
modbus->function);
goto error;
}
SCLogDebug("will look for modbus function %d", modbus->function); SCLogDebug("will look for modbus function %d", modbus->function);
if (ret > 2) { if (ret > 2) {
@ -354,6 +372,96 @@ error:
SCReturnPtr(NULL, "DetectModbus"); SCReturnPtr(NULL, "DetectModbus");
} }
/** \internal
*
* \brief This function is used to parse Modbus parameters in unit id mode
*
* \param str Pointer to the user provided id option
*
* \retval Pointer to DetectModbusUnit on success or NULL on failure
*/
static DetectModbus *DetectModbusUnitIdParse(const char *str)
{
SCEnter();
DetectModbus *modbus = NULL;
char arg[MAX_SUBSTRINGS];
int ov[MAX_SUBSTRINGS], ret, res;
ret = pcre_exec(unit_id_parse_regex, unit_id_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;
}
if (ret > 3) {
/* We have more Modbus option */
const char *str_ptr;
res = pcre_get_substring((char *)str, ov, MAX_SUBSTRINGS, 4, &str_ptr);
if (res < 0) {
SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
goto error;
}
if ((modbus = DetectModbusFunctionParse(str_ptr)) == NULL) {
if ((modbus = DetectModbusAccessParse(str_ptr)) == NULL) {
SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option");
goto error;
}
}
} else {
/* We have only unit id Modbus option */
modbus = (DetectModbus *) SCCalloc(1, sizeof(DetectModbus));
if (unlikely(modbus == NULL))
goto error;
}
/* We have a correct unit id option */
modbus->unit_id = (DetectModbusValue *) SCCalloc(1, sizeof(DetectModbusValue));
if (unlikely(modbus->unit_id == NULL))
goto error;
if (arg[0] == '>') {
modbus->unit_id->min = atoi((const char*) (arg+1));
modbus->unit_id->mode = DETECT_MODBUS_GT;
} else if (arg[0] == '<') {
modbus->unit_id->min = atoi((const char*) (arg+1));
modbus->unit_id->mode = DETECT_MODBUS_LT;
} else {
modbus->unit_id->min = atoi((const char*) arg);
}
SCLogDebug("and min/equal unit id %d", modbus->unit_id->min);
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') {
modbus->unit_id->max = atoi((const char*) (arg+2));
modbus->unit_id->mode = DETECT_MODBUS_RA;
SCLogDebug("and max unit id %d", modbus->unit_id->max);
}
}
SCReturnPtr(modbus, "DetectModbusUnitId");
error:
if (modbus != NULL)
DetectModbusFree(modbus);
SCReturnPtr(NULL, "DetectModbus");
}
/** \internal /** \internal
* *
* \brief this function is used to add the parsed "id" option into the current signature * \brief this function is used to add the parsed "id" option into the current signature
@ -373,12 +481,14 @@ static int DetectModbusSetup(DetectEngineCtx *de_ctx, Signature *s, const char *
if (DetectSignatureSetAppProto(s, ALPROTO_MODBUS) != 0) if (DetectSignatureSetAppProto(s, ALPROTO_MODBUS) != 0)
return -1; return -1;
if ((modbus = DetectModbusUnitIdParse(str)) == NULL) {
if ((modbus = DetectModbusFunctionParse(str)) == NULL) { if ((modbus = DetectModbusFunctionParse(str)) == NULL) {
if ((modbus = DetectModbusAccessParse(str)) == NULL) { if ((modbus = DetectModbusAccessParse(str)) == NULL) {
SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option"); SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option");
goto error; goto error;
} }
} }
}
/* Okay so far so good, lets get this into a SigMatch and put it in the Signature. */ /* Okay so far so good, lets get this into a SigMatch and put it in the Signature. */
sm = SigMatchAlloc(); sm = SigMatchAlloc();
@ -412,6 +522,8 @@ void DetectModbusRegister(void)
sigmatch_table[DETECT_AL_MODBUS].Free = DetectModbusFree; sigmatch_table[DETECT_AL_MODBUS].Free = DetectModbusFree;
sigmatch_table[DETECT_AL_MODBUS].RegisterTests = DetectModbusRegisterTests; sigmatch_table[DETECT_AL_MODBUS].RegisterTests = DetectModbusRegisterTests;
DetectSetupParseRegexes(PARSE_REGEX_UNIT_ID,
&unit_id_parse_regex, &unit_id_parse_regex_study);
DetectSetupParseRegexes(PARSE_REGEX_FUNCTION, DetectSetupParseRegexes(PARSE_REGEX_FUNCTION,
&function_parse_regex, &function_parse_regex_study); &function_parse_regex, &function_parse_regex_study);
DetectSetupParseRegexes(PARSE_REGEX_ACCESS, DetectSetupParseRegexes(PARSE_REGEX_ACCESS,
@ -433,42 +545,26 @@ static int DetectModbusTest01(void)
DetectEngineCtx *de_ctx = NULL; DetectEngineCtx *de_ctx = NULL;
DetectModbus *modbus = NULL; DetectModbus *modbus = NULL;
int result = 0;
de_ctx = DetectEngineCtxInit(); de_ctx = DetectEngineCtxInit();
if (de_ctx == NULL) FAIL_IF_NULL(de_ctx);
goto end;
de_ctx->flags |= DE_QUIET; de_ctx->flags |= DE_QUIET;
de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any " de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
"(msg:\"Testing modbus function\"; " "(msg:\"Testing modbus function\"; "
"modbus: function 1; sid:1;)"); "modbus: function 1; sid:1;)");
FAIL_IF_NULL(de_ctx->sig_list);
if (de_ctx->sig_list == NULL) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
goto end; FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
if ((de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id] == NULL) ||
(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->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[g_modbus_buffer_id]->ctx; modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
if (modbus->function != 1) { FAIL_IF_NOT(modbus->function == 1);
printf("expected function %d, got %" PRIu8 ": ", 1, modbus->function);
goto end;
}
result = 1;
end:
SigGroupCleanup(de_ctx); SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx); SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx); DetectEngineCtxFree(de_ctx);
PASS;
return result;
} }
/** \test Signature containing a function and a subfunction. */ /** \test Signature containing a function and a subfunction. */
@ -477,43 +573,27 @@ static int DetectModbusTest02(void)
DetectEngineCtx *de_ctx = NULL; DetectEngineCtx *de_ctx = NULL;
DetectModbus *modbus = NULL; DetectModbus *modbus = NULL;
int result = 0;
de_ctx = DetectEngineCtxInit(); de_ctx = DetectEngineCtxInit();
if (de_ctx == NULL) FAIL_IF_NULL(de_ctx);
goto end;
de_ctx->flags |= DE_QUIET; de_ctx->flags |= DE_QUIET;
de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any " de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
"(msg:\"Testing modbus function and subfunction\"; " "(msg:\"Testing modbus function and subfunction\"; "
"modbus: function 8, subfunction 4; sid:1;)"); "modbus: function 8, subfunction 4; sid:1;)");
FAIL_IF_NULL(de_ctx->sig_list);
if (de_ctx->sig_list == NULL) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
goto end; FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
if ((de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id] == NULL) ||
(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->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[g_modbus_buffer_id]->ctx; modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
if ((modbus->function != 8) || (*modbus->subfunction != 4)) { FAIL_IF_NOT(modbus->function == 8);
printf("expected function %d, got %" PRIu8 ": ", 1, modbus->function); FAIL_IF_NOT(*modbus->subfunction == 4);
printf("expected subfunction %d, got %" PRIu16 ": ", 4, *modbus->subfunction);
goto end;
}
result = 1;
end:
SigGroupCleanup(de_ctx); SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx); SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx); DetectEngineCtxFree(de_ctx);
PASS;
return result;
} }
/** \test Signature containing a function category. */ /** \test Signature containing a function category. */
@ -522,42 +602,26 @@ static int DetectModbusTest03(void)
DetectEngineCtx *de_ctx = NULL; DetectEngineCtx *de_ctx = NULL;
DetectModbus *modbus = NULL; DetectModbus *modbus = NULL;
int result = 0;
de_ctx = DetectEngineCtxInit(); de_ctx = DetectEngineCtxInit();
if (de_ctx == NULL) FAIL_IF_NULL(de_ctx);
goto end;
de_ctx->flags |= DE_QUIET; de_ctx->flags |= DE_QUIET;
de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any " de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
"(msg:\"Testing modbus.function\"; " "(msg:\"Testing modbus.function\"; "
"modbus: function reserved; sid:1;)"); "modbus: function reserved; sid:1;)");
FAIL_IF_NULL(de_ctx->sig_list);
if (de_ctx->sig_list == NULL) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
goto end; FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
if ((de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id] == NULL) ||
(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->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[g_modbus_buffer_id]->ctx; modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
if (modbus->category != MODBUS_CAT_RESERVED) { FAIL_IF_NOT(modbus->category == MODBUS_CAT_RESERVED);
printf("expected function %d, got %" PRIu8 ": ", MODBUS_CAT_RESERVED, modbus->category);
goto end;
}
result = 1;
end:
SigGroupCleanup(de_ctx); SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx); SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx); DetectEngineCtxFree(de_ctx);
PASS;
return result;
} }
/** \test Signature containing a negative function category. */ /** \test Signature containing a negative function category. */
@ -568,42 +632,26 @@ static int DetectModbusTest04(void)
uint8_t category = ~MODBUS_CAT_PUBLIC_ASSIGNED; uint8_t category = ~MODBUS_CAT_PUBLIC_ASSIGNED;
int result = 0;
de_ctx = DetectEngineCtxInit(); de_ctx = DetectEngineCtxInit();
if (de_ctx == NULL) FAIL_IF_NULL(de_ctx);
goto end;
de_ctx->flags |= DE_QUIET; de_ctx->flags |= DE_QUIET;
de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any " de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
"(msg:\"Testing modbus function\"; " "(msg:\"Testing modbus function\"; "
"modbus: function !assigned; sid:1;)"); "modbus: function !assigned; sid:1;)");
FAIL_IF_NULL(de_ctx->sig_list);
if (de_ctx->sig_list == NULL) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
goto end; FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
if ((de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id] == NULL) ||
(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->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[g_modbus_buffer_id]->ctx; modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
if (modbus->category != category) { FAIL_IF_NOT(modbus->category == category);
printf("expected function %u, got %" PRIu8 ": ", ~MODBUS_CAT_PUBLIC_ASSIGNED, modbus->category);
goto end;
}
result = 1;
end:
SigGroupCleanup(de_ctx); SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx); SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx); DetectEngineCtxFree(de_ctx);
PASS;
return result;
} }
/** \test Signature containing a access type. */ /** \test Signature containing a access type. */
@ -612,42 +660,26 @@ static int DetectModbusTest05(void)
DetectEngineCtx *de_ctx = NULL; DetectEngineCtx *de_ctx = NULL;
DetectModbus *modbus = NULL; DetectModbus *modbus = NULL;
int result = 0;
de_ctx = DetectEngineCtxInit(); de_ctx = DetectEngineCtxInit();
if (de_ctx == NULL) FAIL_IF_NULL(de_ctx);
goto end;
de_ctx->flags |= DE_QUIET; de_ctx->flags |= DE_QUIET;
de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any " de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
"(msg:\"Testing modbus.access\"; " "(msg:\"Testing modbus.access\"; "
"modbus: access read; sid:1;)"); "modbus: access read; sid:1;)");
FAIL_IF_NULL(de_ctx->sig_list);
if (de_ctx->sig_list == NULL) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
goto end; FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
if ((de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id] == NULL) ||
(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->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[g_modbus_buffer_id]->ctx; modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
if (modbus->type != MODBUS_TYP_READ) { FAIL_IF_NOT(modbus->type == MODBUS_TYP_READ);
printf("expected function %d, got %" PRIu8 ": ", MODBUS_TYP_READ, modbus->type);
goto end;
}
result = 1;
end:
SigGroupCleanup(de_ctx); SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx); SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx); DetectEngineCtxFree(de_ctx);
PASS;
return result;
} }
/** \test Signature containing a access function. */ /** \test Signature containing a access function. */
@ -658,42 +690,26 @@ static int DetectModbusTest06(void)
uint8_t type = (MODBUS_TYP_READ | MODBUS_TYP_DISCRETES); uint8_t type = (MODBUS_TYP_READ | MODBUS_TYP_DISCRETES);
int result = 0;
de_ctx = DetectEngineCtxInit(); de_ctx = DetectEngineCtxInit();
if (de_ctx == NULL) FAIL_IF_NULL(de_ctx);
goto end;
de_ctx->flags |= DE_QUIET; de_ctx->flags |= DE_QUIET;
de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any " de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
"(msg:\"Testing modbus.access\"; " "(msg:\"Testing modbus.access\"; "
"modbus: access read discretes; sid:1;)"); "modbus: access read discretes; sid:1;)");
FAIL_IF_NULL(de_ctx->sig_list);
if (de_ctx->sig_list == NULL) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
goto end; FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
if ((de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id] == NULL) ||
(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->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[g_modbus_buffer_id]->ctx; modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
if (modbus->type != type) { FAIL_IF_NOT(modbus->type == type);
printf("expected function %" PRIu8 ", got %" PRIu8 ": ", type, modbus->type);
goto end;
}
result = 1;
end:
SigGroupCleanup(de_ctx); SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx); SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx); DetectEngineCtxFree(de_ctx);
PASS;
return result;
} }
/** \test Signature containing a read access at an address. */ /** \test Signature containing a read access at an address. */
@ -704,46 +720,29 @@ static int DetectModbusTest07(void)
DetectModbusMode mode = DETECT_MODBUS_EQ; DetectModbusMode mode = DETECT_MODBUS_EQ;
uint8_t type = MODBUS_TYP_READ; uint8_t type = MODBUS_TYP_READ;
int result = 0;
de_ctx = DetectEngineCtxInit(); de_ctx = DetectEngineCtxInit();
if (de_ctx == NULL) FAIL_IF_NULL(de_ctx);
goto end;
de_ctx->flags |= DE_QUIET; de_ctx->flags |= DE_QUIET;
de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any " de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
"(msg:\"Testing modbus.access\"; " "(msg:\"Testing modbus.access\"; "
"modbus: access read, address 1000; sid:1;)"); "modbus: access read, address 1000; sid:1;)");
FAIL_IF_NULL(de_ctx->sig_list);
if (de_ctx->sig_list == NULL) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
goto end; FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
if ((de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id] == NULL) ||
(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->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[g_modbus_buffer_id]->ctx; modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
if ((modbus->type != type) || FAIL_IF_NOT(modbus->type == type);
((*modbus->address).mode != mode) || FAIL_IF_NOT((*modbus->address).mode == mode);
((*modbus->address).min != 1000)) { FAIL_IF_NOT((*modbus->address).min == 1000);
printf("expected function %" PRIu8 ", got %" PRIu8 ": ", type, modbus->type);
printf("expected mode %u, got %u: ", mode, (*modbus->address).mode);
printf("expected address %d, got %" PRIu16 ": ", 1000, (*modbus->address).min);
goto end;
}
result = 1;
end:
SigGroupCleanup(de_ctx); SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx); SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx); DetectEngineCtxFree(de_ctx);
PASS;
return result;
} }
/** \test Signature containing a write access at a range of address. */ /** \test Signature containing a write access at a range of address. */
@ -754,46 +753,29 @@ static int DetectModbusTest08(void)
DetectModbusMode mode = DETECT_MODBUS_GT; DetectModbusMode mode = DETECT_MODBUS_GT;
uint8_t type = (MODBUS_TYP_WRITE | MODBUS_TYP_COILS); uint8_t type = (MODBUS_TYP_WRITE | MODBUS_TYP_COILS);
int result = 0;
de_ctx = DetectEngineCtxInit(); de_ctx = DetectEngineCtxInit();
if (de_ctx == NULL) FAIL_IF_NULL(de_ctx);
goto end;
de_ctx->flags |= DE_QUIET; de_ctx->flags |= DE_QUIET;
de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any " de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
"(msg:\"Testing modbus.access\"; " "(msg:\"Testing modbus.access\"; "
"modbus: access write coils, address >500; sid:1;)"); "modbus: access write coils, address >500; sid:1;)");
FAIL_IF_NULL(de_ctx->sig_list);
if (de_ctx->sig_list == NULL) FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
goto end; FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
if ((de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id] == NULL) ||
(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->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[g_modbus_buffer_id]->ctx; modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
if ((modbus->type != type) || FAIL_IF_NOT(modbus->type == type);
((*modbus->address).mode != mode) || FAIL_IF_NOT((*modbus->address).mode == mode);
((*modbus->address).min != 500)) { FAIL_IF_NOT((*modbus->address).min == 500);
printf("expected function %" PRIu8 ", got %" PRIu8 ": ", type, modbus->type);
printf("expected mode %d, got %u: ", mode, (*modbus->address).mode);
printf("expected address %u, got %" PRIu16 ": ", 500, (*modbus->address).min);
goto end;
}
result = 1;
end:
SigGroupCleanup(de_ctx); SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx); SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx); DetectEngineCtxFree(de_ctx);
PASS;
return result;
} }
/** \test Signature containing a write access at a address a range of value. */ /** \test Signature containing a write access at a address a range of value. */
@ -805,52 +787,160 @@ static int DetectModbusTest09(void)
DetectModbusMode valueMode = DETECT_MODBUS_RA; DetectModbusMode valueMode = DETECT_MODBUS_RA;
uint8_t type = (MODBUS_TYP_WRITE | MODBUS_TYP_HOLDING); uint8_t type = (MODBUS_TYP_WRITE | MODBUS_TYP_HOLDING);
int result = 0;
de_ctx = DetectEngineCtxInit(); de_ctx = DetectEngineCtxInit();
if (de_ctx == NULL) FAIL_IF_NULL(de_ctx);
goto end;
de_ctx->flags |= DE_QUIET; de_ctx->flags |= DE_QUIET;
de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any " de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
"(msg:\"Testing modbus.access\"; " "(msg:\"Testing modbus.access\"; "
"modbus: access write holding, address 100, value 500<>1000; sid:1;)"); "modbus: access write holding, address 100, value 500<>1000; sid:1;)");
FAIL_IF_NULL(de_ctx->sig_list);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
if (de_ctx->sig_list == NULL) FAIL_IF_NOT(modbus->type == type);
goto end; FAIL_IF_NOT((*modbus->address).mode == addressMode);
FAIL_IF_NOT((*modbus->address).min == 100);
FAIL_IF_NOT((*modbus->data).mode == valueMode);
FAIL_IF_NOT((*modbus->data).min == 500);
FAIL_IF_NOT((*modbus->data).max == 1000);
if ((de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id] == NULL) || SigGroupCleanup(de_ctx);
(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx == NULL)) { SigCleanSignatures(de_ctx);
printf("de_ctx->pmatch_tail == NULL && de_ctx->pmatch_tail->ctx == NULL: "); DetectEngineCtxFree(de_ctx);
goto end; PASS;
} }
/** \test Signature containing a unit_id. */
static int DetectModbusTest10(void)
{
DetectEngineCtx *de_ctx = NULL;
DetectModbus *modbus = NULL;
DetectModbusMode mode = DETECT_MODBUS_EQ;
de_ctx = DetectEngineCtxInit();
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
"(msg:\"Testing modbus unit_id\"; "
"modbus: unit 10; sid:1;)");
FAIL_IF_NULL(de_ctx->sig_list);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx; modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
if ((modbus->type != type) || FAIL_IF_NOT((*modbus->unit_id).min == 10);
((*modbus->address).mode != addressMode) || FAIL_IF_NOT((*modbus->unit_id).mode == mode);
((*modbus->address).min != 100) ||
((*modbus->data).mode != valueMode) || SigGroupCleanup(de_ctx);
((*modbus->data).min != 500) || SigCleanSignatures(de_ctx);
((*modbus->data).max != 1000)) { DetectEngineCtxFree(de_ctx);
printf("expected function %" PRIu8 ", got %" PRIu8 ": ", type, modbus->type); PASS;
printf("expected address mode %u, got %u: ", addressMode, (*modbus->address).mode);
printf("expected address %d, got %" PRIu16 ": ", 500, (*modbus->address).min);
printf("expected value mode %u, got %u: ", valueMode, (*modbus->data).mode);
printf("expected min value %d, got %" PRIu16 ": ", 500, (*modbus->data).min);
printf("expected max value %d, got %" PRIu16 ": ", 1000, (*modbus->data).max);
goto end;
} }
result = 1; /** \test Signature containing a unit_id, a function and a subfunction. */
static int DetectModbusTest11(void)
{
DetectEngineCtx *de_ctx = NULL;
DetectModbus *modbus = NULL;
DetectModbusMode mode = DETECT_MODBUS_EQ;
de_ctx = DetectEngineCtxInit();
FAIL_IF_NULL(de_ctx);
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: unit 10, function 8, subfunction 4; sid:1;)");
FAIL_IF_NULL(de_ctx->sig_list);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
FAIL_IF_NOT((*modbus->unit_id).min == 10);
FAIL_IF_NOT((*modbus->unit_id).mode == mode);
FAIL_IF_NOT(modbus->function == 8);
FAIL_IF_NOT((*modbus->subfunction) == 4);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
PASS;
}
/** \test Signature containing an unit_id and a read access at an address. */
static int DetectModbusTest12(void)
{
DetectEngineCtx *de_ctx = NULL;
DetectModbus *modbus = NULL;
DetectModbusMode mode = DETECT_MODBUS_EQ;
uint8_t type = MODBUS_TYP_READ;
de_ctx = DetectEngineCtxInit();
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
"(msg:\"Testing modbus.access\"; "
"modbus: unit 10, access read, address 1000; sid:1;)");
FAIL_IF_NULL(de_ctx->sig_list);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
FAIL_IF_NOT((*modbus->unit_id).min == 10);
FAIL_IF_NOT((*modbus->unit_id).mode == mode);
FAIL_IF_NOT(modbus->type == type);
FAIL_IF_NOT((*modbus->address).mode == mode);
FAIL_IF_NOT((*modbus->address).min == 1000);
end:
SigGroupCleanup(de_ctx); SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx); SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx); DetectEngineCtxFree(de_ctx);
PASS;
}
/** \test Signature containing a range of unit_id. */
static int DetectModbusTest13(void)
{
DetectEngineCtx *de_ctx = NULL;
DetectModbus *modbus = NULL;
DetectModbusMode mode = DETECT_MODBUS_RA;
return result; de_ctx = DetectEngineCtxInit();
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
de_ctx->sig_list = SigInit(de_ctx, "alert modbus any any -> any any "
"(msg:\"Testing modbus.access\"; "
"modbus: unit 10<>500; sid:1;)");
FAIL_IF_NULL(de_ctx->sig_list);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
FAIL_IF_NOT((*modbus->unit_id).min == 10);
FAIL_IF_NOT((*modbus->unit_id).max == 500);
FAIL_IF_NOT((*modbus->unit_id).mode == mode);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
PASS;
} }
#endif /* UNITTESTS */ #endif /* UNITTESTS */
@ -878,5 +968,13 @@ void DetectModbusRegisterTests(void)
DetectModbusTest08); DetectModbusTest08);
UtRegisterTest("DetectModbusTest09 - Testing write a range of value", UtRegisterTest("DetectModbusTest09 - Testing write a range of value",
DetectModbusTest09); DetectModbusTest09);
UtRegisterTest("DetectModbusTest10 - Testing unit_id",
DetectModbusTest10);
UtRegisterTest("DetectModbusTest11 - Testing unit_id, function and subfunction",
DetectModbusTest11);
UtRegisterTest("DetectModbusTest12 - Testing unit_id and access at address",
DetectModbusTest12);
UtRegisterTest("DetectModbusTest13 - Testing a range of unit_id",
DetectModbusTest13);
#endif /* UNITTESTS */ #endif /* UNITTESTS */
} }

@ -54,6 +54,7 @@ typedef struct DetectModbus_ {
uint8_t function; /** < Modbus function code to match */ uint8_t function; /** < Modbus function code to match */
uint16_t *subfunction; /** < Modbus subfunction to match */ uint16_t *subfunction; /** < Modbus subfunction to match */
uint8_t type; /** < Modbus access type to match */ uint8_t type; /** < Modbus access type to match */
DetectModbusValue *unit_id; /** < Modbus unit id to match */
DetectModbusValue *address; /** < Modbus address to match */ DetectModbusValue *address; /** < Modbus address to match */
DetectModbusValue *data; /** < Modbus data to match */ DetectModbusValue *data; /** < Modbus data to match */
} DetectModbus; } DetectModbus;

Loading…
Cancel
Save