/* Copyright (C) 2015 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free * Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ /** * \file * * \author Kevin Wong * * Set up ENIP Command and CIP Service rule parsing and entry point for matching */ #include "suricata-common.h" #include "util-unittest.h" #include "detect-parse.h" #include "detect-engine.h" #include "util-byte.h" #include "app-layer-enip-common.h" #include "detect-cipservice.h" #include "detect-engine-enip.h" /* * CIP SERVICE CODE */ /** * \brief CIP Service Detect Prototypes */ static int DetectCipServiceSetup(DetectEngineCtx *, Signature *, const char *); static void DetectCipServiceFree(DetectEngineCtx *, void *); #ifdef UNITTESTS static void DetectCipServiceRegisterTests(void); #endif static int g_cip_buffer_id = 0; /** * \brief Registration function for cip_service: keyword */ void DetectCipServiceRegister(void) { SCEnter(); sigmatch_table[DETECT_CIPSERVICE].name = "cip_service"; //rule keyword sigmatch_table[DETECT_CIPSERVICE].desc = "match on CIP Service"; sigmatch_table[DETECT_CIPSERVICE].url = "/rules/enip-keyword.html#enip-cip-keywords"; sigmatch_table[DETECT_CIPSERVICE].Match = NULL; sigmatch_table[DETECT_CIPSERVICE].Setup = DetectCipServiceSetup; sigmatch_table[DETECT_CIPSERVICE].Free = DetectCipServiceFree; #ifdef UNITTESTS sigmatch_table[DETECT_CIPSERVICE].RegisterTests = DetectCipServiceRegisterTests; #endif DetectAppLayerInspectEngineRegister2( "cip", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, DetectEngineInspectCIP, NULL); DetectAppLayerInspectEngineRegister2( "cip", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectCIP, NULL); g_cip_buffer_id = DetectBufferTypeGetByName("cip"); SCReturn; } /** * \brief This function is used to parse cip_service options passed via cip_service: keyword * * \param rulestr Pointer to the user provided rulestr options * Takes comma seperated string with numeric tokens. Only first 3 are used * * \retval cipserviced pointer to DetectCipServiceData on success * \retval NULL on failure */ static DetectCipServiceData *DetectCipServiceParse(const char *rulestrc) { const char delims[] = ","; DetectCipServiceData *cipserviced = NULL; //SCLogDebug("DetectCipServiceParse - rule string %s", rulestr); /* strtok_r modifies the string so work with a copy */ char *rulestr = SCStrdup(rulestrc); if (unlikely(rulestr == NULL)) goto error; cipserviced = SCMalloc(sizeof(DetectCipServiceData)); if (unlikely(cipserviced == NULL)) goto error; cipserviced->cipservice = 0; cipserviced->cipclass = 0; cipserviced->matchattribute = 1; cipserviced->cipattribute = 0; char* token; char *save; uint8_t var; uint8_t input[3] = { 0, 0, 0 }; uint8_t i = 0; token = strtok_r(rulestr, delims, &save); while (token != NULL) { if (i > 2) //for now only need 3 parameters { SCLogError("too many parameters"); goto error; } if (i < 2) //if on service or class { if (!isdigit((int) *token)) { SCLogError("parameter error %s", token); goto error; } } else //if on attribute { if (token[0] == '!') { cipserviced->matchattribute = 0; token++; } if (!isdigit((int) *token)) { SCLogError("attribute error %s", token); goto error; } } unsigned long num = atol(token); if ((num > MAX_CIP_SERVICE) && (i == 0))//if service greater than 7 bit { SCLogError("invalid CIP service %lu", num); goto error; } else if ((num > MAX_CIP_CLASS) && (i == 1))//if service greater than 16 bit { SCLogError("invalid CIP class %lu", num); goto error; } else if ((num > MAX_CIP_ATTRIBUTE) && (i == 2))//if service greater than 16 bit { SCLogError("invalid CIP attribute %lu", num); goto error; } sscanf(token, "%2" SCNu8, &var); input[i++] = var; token = strtok_r(NULL, delims, &save); } if (i == 0) { SCLogError("no tokens found"); goto error; } cipserviced->cipservice = input[0]; cipserviced->cipclass = input[1]; cipserviced->cipattribute = input[2]; cipserviced->tokens = i; SCLogDebug("DetectCipServiceParse - tokens %d", cipserviced->tokens); SCLogDebug("DetectCipServiceParse - service %d", cipserviced->cipservice); SCLogDebug("DetectCipServiceParse - class %d", cipserviced->cipclass); SCLogDebug("DetectCipServiceParse - match attribute %d", cipserviced->matchattribute); SCLogDebug("DetectCipServiceParse - attribute %d", cipserviced->cipattribute); SCFree(rulestr); SCReturnPtr(cipserviced, "DetectENIPFunction"); error: if (cipserviced) SCFree(cipserviced); if (rulestr) SCFree(rulestr); SCReturnPtr(NULL, "DetectENIP"); } /** * \brief this function is used to a cipserviced the parsed cip_service data into the current signature * * \param de_ctx pointer to the Detection Engine Context * \param s pointer to the Current Signature * \param rulestr pointer to the user provided cip_service options * * \retval 0 on Success * \retval -1 on Failure */ static int DetectCipServiceSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) { SCEnter(); DetectCipServiceData *cipserviced = NULL; SigMatch *sm = NULL; if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) return -1; cipserviced = DetectCipServiceParse(rulestr); if (cipserviced == NULL) goto error; sm = SigMatchAlloc(); if (sm == NULL) goto error; sm->type = DETECT_CIPSERVICE; sm->ctx = (void *) cipserviced; SigMatchAppendSMToList(s, sm, g_cip_buffer_id); SCReturnInt(0); error: if (cipserviced != NULL) DetectCipServiceFree(de_ctx, cipserviced); if (sm != NULL) SCFree(sm); SCReturnInt(-1); } /** * \brief this function will free memory associated with DetectCipServiceData * * \param ptr pointer to DetectCipServiceData */ static void DetectCipServiceFree(DetectEngineCtx *de_ctx, void *ptr) { DetectCipServiceData *cipserviced = (DetectCipServiceData *) ptr; SCFree(cipserviced); } #ifdef UNITTESTS /** * \test Test CIP Command parameter parsing */ static int DetectCipServiceParseTest01 (void) { DetectCipServiceData *cipserviced = NULL; cipserviced = DetectCipServiceParse("7"); FAIL_IF_NULL(cipserviced); FAIL_IF(cipserviced->cipservice != 7); DetectCipServiceFree(NULL, cipserviced); PASS; } /** * \test Test CIP Service signature */ static int DetectCipServiceSignatureTest01 (void) { DetectEngineCtx *de_ctx = DetectEngineCtxInit(); FAIL_IF_NULL(de_ctx); Signature *sig = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any (cip_service:1; sid:1; rev:1;)"); FAIL_IF_NULL(sig); DetectEngineCtxFree(de_ctx); PASS; } /** * \brief this function registers unit tests for DetectCipService */ static void DetectCipServiceRegisterTests(void) { UtRegisterTest("DetectCipServiceParseTest01", DetectCipServiceParseTest01); UtRegisterTest("DetectCipServiceSignatureTest01", DetectCipServiceSignatureTest01); } #endif /* UNITTESTS */ /* * ENIP COMMAND CODE */ /** * \brief ENIP Command Detect Prototypes */ static int DetectEnipCommandSetup(DetectEngineCtx *, Signature *, const char *); static void DetectEnipCommandFree(DetectEngineCtx *, void *); #ifdef UNITTESTS static void DetectEnipCommandRegisterTests(void); #endif static int g_enip_buffer_id = 0; /** * \brief Registration function for enip_command: keyword */ void DetectEnipCommandRegister(void) { sigmatch_table[DETECT_ENIPCOMMAND].name = "enip_command"; //rule keyword sigmatch_table[DETECT_ENIPCOMMAND].desc = "rules for detecting EtherNet/IP command"; sigmatch_table[DETECT_ENIPCOMMAND].url = "/rules/enip-keyword.html#enip-cip-keywords"; sigmatch_table[DETECT_ENIPCOMMAND].Match = NULL; sigmatch_table[DETECT_ENIPCOMMAND].Setup = DetectEnipCommandSetup; sigmatch_table[DETECT_ENIPCOMMAND].Free = DetectEnipCommandFree; #ifdef UNITTESTS sigmatch_table[DETECT_ENIPCOMMAND].RegisterTests = DetectEnipCommandRegisterTests; #endif DetectAppLayerInspectEngineRegister2( "enip", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, DetectEngineInspectENIP, NULL); DetectAppLayerInspectEngineRegister2( "enip", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectENIP, NULL); g_enip_buffer_id = DetectBufferTypeGetByName("enip"); } /** * \brief This function is used to parse cip_service options passed via enip_command: keyword * * \param rulestr Pointer to the user provided rulestr options * Takes single numeric value * * \retval enipcmdd pointer to DetectCipServiceData on success * \retval NULL on failure */ static DetectEnipCommandData *DetectEnipCommandParse(const char *rulestr) { DetectEnipCommandData *enipcmdd = NULL; enipcmdd = SCMalloc(sizeof(DetectEnipCommandData)); if (unlikely(enipcmdd == NULL)) goto error; if (!(isdigit((int) *rulestr))) { SCLogError("invalid ENIP command %s", rulestr); goto error; } uint16_t cmd; if (StringParseUint16(&cmd, 10, 0, rulestr) < 0) { SCLogError("invalid ENIP command" ": \"%s\"", rulestr); goto error; } enipcmdd->enipcommand = cmd; return enipcmdd; error: if (enipcmdd) SCFree(enipcmdd); return NULL; } /** * \brief this function is used by enipcmdd to parse enip_command data into the current signature * * \param de_ctx pointer to the Detection Engine Context * \param s pointer to the Current Signature * \param rulestr pointer to the user provided enip command options * * \retval 0 on Success * \retval -1 on Failure */ static int DetectEnipCommandSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) { DetectEnipCommandData *enipcmdd = NULL; SigMatch *sm = NULL; if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) return -1; enipcmdd = DetectEnipCommandParse(rulestr); if (enipcmdd == NULL) goto error; sm = SigMatchAlloc(); if (sm == NULL) goto error; sm->type = DETECT_ENIPCOMMAND; sm->ctx = (void *) enipcmdd; SigMatchAppendSMToList(s, sm, g_enip_buffer_id); SCReturnInt(0); error: if (enipcmdd != NULL) DetectEnipCommandFree(de_ctx, enipcmdd); if (sm != NULL) SCFree(sm); SCReturnInt(-1); } /** * \brief this function will free memory associated with DetectEnipCommandData * * \param ptr pointer to DetectEnipCommandData */ static void DetectEnipCommandFree(DetectEngineCtx *de_ctx, void *ptr) { DetectEnipCommandData *enipcmdd = (DetectEnipCommandData *) ptr; SCFree(enipcmdd); } #ifdef UNITTESTS /** * \test ENIP parameter test */ static int DetectEnipCommandParseTest01 (void) { DetectEnipCommandData *enipcmdd = NULL; enipcmdd = DetectEnipCommandParse("1"); FAIL_IF_NULL(enipcmdd); FAIL_IF_NOT(enipcmdd->enipcommand == 1); DetectEnipCommandFree(NULL, enipcmdd); PASS; } /** * \test ENIP Command signature test */ static int DetectEnipCommandSignatureTest01 (void) { DetectEngineCtx *de_ctx = DetectEngineCtxInit(); FAIL_IF_NULL(de_ctx); Signature *sig = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any (enip_command:1; sid:1; rev:1;)"); FAIL_IF_NULL(sig); DetectEngineCtxFree(de_ctx); PASS; } /** * \brief this function registers unit tests for DetectEnipCommand */ static void DetectEnipCommandRegisterTests(void) { UtRegisterTest("DetectEnipCommandParseTest01", DetectEnipCommandParseTest01); UtRegisterTest("DetectEnipCommandSignatureTest01", DetectEnipCommandSignatureTest01); } #endif /* UNITTESTS */