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.
suricata/src/detect-cipservice.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 */
}