diff options
Diffstat (limited to 'src/detect-engine-enip.c')
-rw-r--r-- | src/detect-engine-enip.c | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/src/detect-engine-enip.c b/src/detect-engine-enip.c new file mode 100644 index 0000000..0c5fb0a --- /dev/null +++ b/src/detect-engine-enip.c @@ -0,0 +1,288 @@ +/* Copyright (C) 2015-2022 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> + * + * Based on detect-engine-modbus.c + */ + +#include "suricata-common.h" + +#include "app-layer.h" +#include "app-layer-enip-common.h" + +#include "detect.h" +#include "detect-cipservice.h" +#include "detect-engine-enip.h" + +#include "flow.h" + +#include "util-debug.h" + +#if 0 +/** + * \brief Print fields from ENIP Packet + * @param enip_data + */ +void PrintENIPAL(ENIPTransaction *enip_data) +{ + SCLogDebug("============================================"); + SCLogDebug("ENCAP HEADER cmd 0x%x, length %d, session 0x%x, status 0x%x", + enip_data->header.command, enip_data->header.length, + enip_data->header.session, enip_data->header.status); + //SCLogDebug("context 0x%x option 0x%x", enip_data->header.context, enip_data->header.option); + SCLogDebug("ENCAP DATA HEADER handle 0x%x, timeout %d, count %d", + enip_data->encap_data_header.interface_handle, + enip_data->encap_data_header.timeout, + enip_data->encap_data_header.item_count); + SCLogDebug("ENCAP ADDR ITEM type 0x%x, length %d", + enip_data->encap_addr_item.type, enip_data->encap_addr_item.length); + SCLogDebug("ENCAP DATA ITEM type 0x%x, length %d sequence 0x%x", + enip_data->encap_data_item.type, enip_data->encap_data_item.length, + enip_data->encap_data_item.sequence_count); + + CIPServiceEntry *svc = NULL; + + int count = 0; + TAILQ_FOREACH(svc, &enip_data->service_list, next) + { + //SCLogDebug("CIP Service #%d : 0x%x", count, svc->service); + count++; + } +} +#endif + +/** + * \brief Matches the rule to the CIP segment in ENIP Packet + * @param svc - the CIP service entry + * * @param cipserviced - the CIP service rule + */ +static int CIPPathMatch(CIPServiceEntry *svc, DetectCipServiceData *cipserviced) +{ + uint16_t class = 0; + uint16_t attrib = 0; + int found_class = 0; + + SegmentEntry *seg = NULL; + TAILQ_FOREACH(seg, &svc->segment_list, next) + { + switch(seg->segment) + { + case PATH_CLASS_8BIT: + class = seg->value; + if (cipserviced->cipclass == class) + { + if (cipserviced->tokens == 2) + {// if rule only has class + return 1; + } else + { + found_class = 1; + } + } + break; + case PATH_INSTANCE_8BIT: + break; + case PATH_ATTR_8BIT: //single attribute + attrib = seg->value; + if ((cipserviced->tokens == 3) && + (cipserviced->cipclass == class) && + (cipserviced->cipattribute == attrib) && + (cipserviced->matchattribute == 1)) + { // if rule has class & attribute, matched all here + return 1; + } + if ((cipserviced->tokens == 3) && + (cipserviced->cipclass == class) && + (cipserviced->matchattribute == 0)) + { // for negation rule on attribute + return 1; + } + break; + case PATH_CLASS_16BIT: + class = seg->value; + if (cipserviced->cipclass == class) + { + if (cipserviced->tokens == 2) + {// if rule only has class + return 1; + } else + { + found_class = 1; + } + } + break; + case PATH_INSTANCE_16BIT: + break; + default: + return 0; + } + } + + if (found_class == 0) + { // if haven't matched class yet, no need to check attribute + return 0; + } + + if ((svc->service == CIP_SET_ATTR_LIST) || + (svc->service == CIP_GET_ATTR_LIST)) + { + AttributeEntry *attr = NULL; + TAILQ_FOREACH (attr, &svc->attrib_list, next) + { + if (cipserviced->cipattribute == attr->attribute) + { + return 1; + } + } + } + + return 0; +} + +/** + * \brief Matches the rule to the ENIP Transaction + * @param enip_data - the ENIP transaction + * * @param cipserviced - the CIP service rule + */ + +static int CIPServiceMatch(ENIPTransaction *enip_data, + DetectCipServiceData *cipserviced) +{ +#ifdef DEBUG + int count = 1; +#endif + CIPServiceEntry *svc = NULL; + //SCLogDebug("CIPServiceMatchAL"); + TAILQ_FOREACH(svc, &enip_data->service_list, next) + { + SCLogDebug("CIPServiceMatchAL service #%d : 0x%x dir %d", + count, svc->service, svc->direction); + + if (cipserviced->cipservice == svc->service) + { // compare service + //SCLogDebug("Rule Match for cip service %d",cipserviced->cipservice ); + + if (cipserviced->tokens > 1) + { //if rule params have class and attribute + + + if ((svc->service == CIP_SET_ATTR_LIST) || (svc->service + == CIP_SET_ATTR_SINGLE) || (svc->service + == CIP_GET_ATTR_LIST) || (svc->service + == CIP_GET_ATTR_SINGLE)) + { //decode path + if (CIPPathMatch(svc, cipserviced) == 1) + { + if (svc->direction == 1) return 0; //don't match responses + + return 1; + } + } + } else + { + if (svc->direction == 1) return 0; //don't match responses + + // SCLogDebug("CIPServiceMatchAL found"); + return 1; + } + } +#ifdef DEBUG + count++; +#endif + } + return 0; +} + +/** \brief Do the content inspection & validation for a signature + * + * \param de_ctx Detection engine context + * \param det_ctx Detection engine thread context + * \param s Signature to inspect ( and sm: SigMatch to inspect) + * \param f Flow + * \param flags App layer flags + * \param alstate App layer state + * \param txv Pointer to ENIP Transaction structure + * + * \retval 0 no match or 1 match + */ +uint8_t DetectEngineInspectCIP(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f, + uint8_t flags, void *alstate, void *txv, uint64_t tx_id) +{ + SCEnter(); + + + ENIPTransaction *tx = (ENIPTransaction *) txv; + DetectCipServiceData *cipserviced = (DetectCipServiceData *)engine->smd->ctx; + + if (cipserviced == NULL) + { + SCLogDebug("no cipservice state, no match"); + SCReturnInt(0); + } + //SCLogDebug("DetectEngineInspectCIP %d", cipserviced->cipservice); + + if (CIPServiceMatch(tx, cipserviced) == 1) + { + //SCLogDebug("DetectCIPServiceMatchAL found"); + SCReturnInt(1); + } + + SCReturnInt(0); +} + +/** \brief Do the content inspection & validation for a signature + * + * \param de_ctx Detection engine context + * \param det_ctx Detection engine thread context + * \param s Signature to inspect ( and sm: SigMatch to inspect) + * \param f Flow + * \param flags App layer flags + * \param alstate App layer state + * \param txv Pointer to ENIP Transaction structure + * + * \retval 0 no match or 1 match + */ + +uint8_t DetectEngineInspectENIP(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f, + uint8_t flags, void *alstate, void *txv, uint64_t tx_id) +{ + SCEnter(); + + ENIPTransaction *tx = (ENIPTransaction *) txv; + DetectEnipCommandData *enipcmdd = (DetectEnipCommandData *)engine->smd->ctx; + + if (enipcmdd == NULL) + { + SCLogDebug("no enipcommand state, no match"); + SCReturnInt(0); + } + + //SCLogDebug("DetectEngineInspectENIP %d, %d", enipcmdd->enipcommand, tx->header.command); + + if (enipcmdd->enipcommand == tx->header.command) + { + // SCLogDebug("DetectENIPCommandMatchAL found!"); + SCReturnInt(1); + } + + SCReturnInt(0); +} |