mirror of https://github.com/OISF/suricata
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
463 lines
13 KiB
C
463 lines
13 KiB
C
/* 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 <kwong@solananetworks.com>
|
|
*
|
|
* Set up ENIP Commnad 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 "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(void *);
|
|
static void DetectCipServiceRegisterTests(void);
|
|
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 = "Rules for detecting CIP Service ";
|
|
sigmatch_table[DETECT_CIPSERVICE].Match = NULL;
|
|
sigmatch_table[DETECT_CIPSERVICE].Setup = DetectCipServiceSetup;
|
|
sigmatch_table[DETECT_CIPSERVICE].Free = DetectCipServiceFree;
|
|
sigmatch_table[DETECT_CIPSERVICE].RegisterTests
|
|
= DetectCipServiceRegisterTests;
|
|
|
|
DetectAppLayerInspectEngineRegister("cip",
|
|
ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0,
|
|
DetectEngineInspectCIP);
|
|
DetectAppLayerInspectEngineRegister("cip",
|
|
ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0,
|
|
DetectEngineInspectCIP);
|
|
|
|
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;
|
|
int var;
|
|
int input[3] = { 0, 0, 0 };
|
|
int i = 0;
|
|
|
|
token = strtok_r(rulestr, delims, &save);
|
|
while (token != NULL)
|
|
{
|
|
if (i > 2) //for now only need 3 parameters
|
|
{
|
|
SCLogError(SC_ERR_INVALID_SIGNATURE, "too many parameters");
|
|
goto error;
|
|
}
|
|
|
|
if (i < 2) //if on service or class
|
|
{
|
|
if (!isdigit((int) *token))
|
|
{
|
|
SCLogError(SC_ERR_INVALID_SIGNATURE, "parameter error %s", token);
|
|
goto error;
|
|
}
|
|
} else //if on attribute
|
|
{
|
|
|
|
if (token[0] == '!')
|
|
{
|
|
cipserviced->matchattribute = 0;
|
|
token++;
|
|
}
|
|
|
|
if (!isdigit((int) *token))
|
|
{
|
|
SCLogError(SC_ERR_INVALID_SIGNATURE, "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(SC_ERR_INVALID_SIGNATURE, "invalid CIP service %lu", num);
|
|
goto error;
|
|
} else if ((num > MAX_CIP_CLASS) && (i == 1))//if service greater than 16 bit
|
|
{
|
|
SCLogError(SC_ERR_INVALID_SIGNATURE, "invalid CIP class %lu", num);
|
|
goto error;
|
|
} else if ((num > MAX_CIP_ATTRIBUTE) && (i == 2))//if service greater than 16 bit
|
|
{
|
|
SCLogError(SC_ERR_INVALID_SIGNATURE, "invalid CIP attribute %lu", num);
|
|
goto error;
|
|
}
|
|
|
|
sscanf(token, "%d", &var);
|
|
input[i++] = var;
|
|
|
|
token = strtok_r(NULL, delims, &save);
|
|
}
|
|
|
|
if (i == 0) {
|
|
SCLogError(SC_ERR_INVALID_SIGNATURE, "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(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(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(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;
|
|
}
|
|
|
|
#endif /* UNITTESTS */
|
|
|
|
/**
|
|
* \brief this function registers unit tests for DetectCipService
|
|
*/
|
|
static void DetectCipServiceRegisterTests(void)
|
|
{
|
|
#ifdef UNITTESTS
|
|
UtRegisterTest("DetectCipServiceParseTest01",
|
|
DetectCipServiceParseTest01);
|
|
UtRegisterTest("DetectCipServiceSignatureTest01",
|
|
DetectCipServiceSignatureTest01);
|
|
#endif /* UNITTESTS */
|
|
}
|
|
|
|
/*
|
|
* ENIP COMMAND CODE
|
|
*/
|
|
|
|
/**
|
|
* \brief ENIP Commond Detect Prototypes
|
|
*/
|
|
static int DetectEnipCommandSetup(DetectEngineCtx *, Signature *, const char *);
|
|
static void DetectEnipCommandFree(void *);
|
|
static void DetectEnipCommandRegisterTests(void);
|
|
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].Match = NULL;
|
|
sigmatch_table[DETECT_ENIPCOMMAND].Setup = DetectEnipCommandSetup;
|
|
sigmatch_table[DETECT_ENIPCOMMAND].Free = DetectEnipCommandFree;
|
|
sigmatch_table[DETECT_ENIPCOMMAND].RegisterTests
|
|
= DetectEnipCommandRegisterTests;
|
|
|
|
DetectAppLayerInspectEngineRegister("enip",
|
|
ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0,
|
|
DetectEngineInspectENIP);
|
|
DetectAppLayerInspectEngineRegister("enip",
|
|
ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0,
|
|
DetectEngineInspectENIP);
|
|
|
|
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 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(SC_ERR_INVALID_SIGNATURE, "invalid ENIP command %s", rulestr);
|
|
goto error;
|
|
}
|
|
|
|
unsigned long cmd = atol(rulestr);
|
|
if (cmd > MAX_ENIP_CMD) //if command greater than 16 bit
|
|
{
|
|
SCLogError(SC_ERR_INVALID_SIGNATURE, "invalid ENIP command %lu", cmd);
|
|
goto error;
|
|
}
|
|
|
|
enipcmdd->enipcommand = (uint16_t) atoi(rulestr);
|
|
|
|
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(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(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(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;
|
|
}
|
|
|
|
#endif /* UNITTESTS */
|
|
|
|
/**
|
|
* \brief this function registers unit tests for DetectEnipCommand
|
|
*/
|
|
static void DetectEnipCommandRegisterTests(void)
|
|
{
|
|
#ifdef UNITTESTS
|
|
UtRegisterTest("DetectEnipCommandParseTest01",
|
|
DetectEnipCommandParseTest01);
|
|
UtRegisterTest("DetectEnipCommandSignatureTest01",
|
|
DetectEnipCommandSignatureTest01);
|
|
#endif /* UNITTESTS */
|
|
}
|