/* 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 * * App-layer parser for ENIP protocol * */ #include "suricata-common.h" #include "suricata.h" #include "util-debug.h" #include "util-byte.h" #include "util-enum.h" #include "util-mem.h" #include "util-misc.h" #include "stream.h" #include "app-layer.h" #include "app-layer-protos.h" #include "app-layer-parser.h" #include "app-layer-enip.h" #include "app-layer-enip-common.h" #include "app-layer-detect-proto.h" #include "conf.h" #include "decode.h" #include "detect-parse.h" #include "detect-engine.h" #include "util-unittest.h" #include "util-unittest-helper.h" #include "pkt-var.h" #include "util-profiling.h" SCEnumCharMap enip_decoder_event_table[ ] = { { NULL, -1 }, }; /** \brief get value for 'complete' status in ENIP * * For ENIP we use a simple bool. */ static int ENIPGetAlstateProgress(void *tx, uint8_t direction) { return 1; } static AppLayerTxData *ENIPGetTxData(void *vtx) { ENIPTransaction *tx = (ENIPTransaction *)vtx; return &tx->tx_data; } static AppLayerStateData *ENIPGetStateData(void *vstate) { ENIPState *state = (ENIPState *)vstate; return &state->state_data; } static void *ENIPGetTx(void *alstate, uint64_t tx_id) { ENIPState *enip = (ENIPState *) alstate; ENIPTransaction *tx = NULL; if (enip->curr && enip->curr->tx_num == tx_id + 1) return enip->curr; TAILQ_FOREACH(tx, &enip->tx_list, next) { if (tx->tx_num != (tx_id+1)) continue; SCLogDebug("returning tx %p", tx); return tx; } return NULL; } static uint64_t ENIPGetTxCnt(void *alstate) { return ((ENIPState *)alstate)->transaction_max; } static int ENIPStateGetEventInfo(const char *event_name, int *event_id, AppLayerEventType *event_type) { *event_id = SCMapEnumNameToValue(event_name, enip_decoder_event_table); if (*event_id == -1) { SCLogError("event \"%s\" not present in " "enip's enum map table.", event_name); /* yes this is fatal */ return -1; } *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION; return 0; } static int ENIPStateGetEventInfoById(int event_id, const char **event_name, AppLayerEventType *event_type) { *event_name = SCMapEnumValueToName(event_id, enip_decoder_event_table); if (*event_name == NULL) { SCLogError("event \"%d\" not present in " "enip's enum map table.", event_id); /* yes this is fatal */ return -1; } *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION; return 0; } /** \brief Allocate enip state * * return state */ static void *ENIPStateAlloc(void *orig_state, AppProto proto_orig) { SCLogDebug("ENIPStateAlloc"); void *s = SCMalloc(sizeof(ENIPState)); if (unlikely(s == NULL)) return NULL; memset(s, 0, sizeof(ENIPState)); ENIPState *enip_state = (ENIPState *) s; TAILQ_INIT(&enip_state->tx_list); return s; } /** \internal * \brief Free a ENIP TX * \param tx ENIP TX to free */ static void ENIPTransactionFree(ENIPTransaction *tx, ENIPState *state) { SCEnter(); SCLogDebug("ENIPTransactionFree"); CIPServiceEntry *svc = NULL; while ((svc = TAILQ_FIRST(&tx->service_list))) { TAILQ_REMOVE(&tx->service_list, svc, next); SegmentEntry *seg = NULL; while ((seg = TAILQ_FIRST(&svc->segment_list))) { TAILQ_REMOVE(&svc->segment_list, seg, next); SCFree(seg); } AttributeEntry *attr = NULL; while ((attr = TAILQ_FIRST(&svc->attrib_list))) { TAILQ_REMOVE(&svc->attrib_list, attr, next); SCFree(attr); } SCFree(svc); } AppLayerDecoderEventsFreeEvents(&tx->tx_data.events); if (tx->tx_data.de_state != NULL) { DetectEngineStateFree(tx->tx_data.de_state); state->tx_with_detect_state_cnt--; } if (state->iter == tx) state->iter = NULL; SCFree(tx); SCReturn; } /** \brief Free enip state * */ static void ENIPStateFree(void *s) { SCEnter(); SCLogDebug("ENIPStateFree"); if (s) { ENIPState *enip_state = (ENIPState *) s; ENIPTransaction *tx = NULL; while ((tx = TAILQ_FIRST(&enip_state->tx_list))) { TAILQ_REMOVE(&enip_state->tx_list, tx, next); ENIPTransactionFree(tx, enip_state); } if (enip_state->buffer != NULL) { SCFree(enip_state->buffer); } SCFree(s); } SCReturn; } /** \internal * \brief Allocate a ENIP TX * \retval tx or NULL */ static ENIPTransaction *ENIPTransactionAlloc(ENIPState *state) { SCLogDebug("ENIPStateTransactionAlloc"); ENIPTransaction *tx = (ENIPTransaction *) SCCalloc(1, sizeof(ENIPTransaction)); if (unlikely(tx == NULL)) return NULL; state->curr = tx; state->transaction_max++; memset(tx, 0x00, sizeof(ENIPTransaction)); TAILQ_INIT(&tx->service_list); tx->enip = state; tx->tx_num = state->transaction_max; tx->service_count = 0; TAILQ_INSERT_TAIL(&state->tx_list, tx, next); return tx; } /** * \brief enip transaction cleanup callback */ static void ENIPStateTransactionFree(void *state, uint64_t tx_id) { SCEnter(); SCLogDebug("ENIPStateTransactionFree"); ENIPState *enip_state = state; ENIPTransaction *tx = NULL; TAILQ_FOREACH(tx, &enip_state->tx_list, next) { if ((tx_id+1) < tx->tx_num) break; else if ((tx_id+1) > tx->tx_num) continue; if (tx == enip_state->curr) enip_state->curr = NULL; if (tx->tx_data.events != NULL) { if (tx->tx_data.events->cnt <= enip_state->events) enip_state->events -= tx->tx_data.events->cnt; else enip_state->events = 0; } TAILQ_REMOVE(&enip_state->tx_list, tx, next); ENIPTransactionFree(tx, state); break; } SCReturn; } /** \internal * * \brief This function is called to retrieve a ENIP * * \param state ENIP state structure for the parser * \param input Input line of the command * \param input_len Length of the request * * \retval 1 when the command is parsed, 0 otherwise */ static AppLayerResult ENIPParse(Flow *f, void *state, AppLayerParserState *pstate, StreamSlice stream_slice, void *local_data, uint8_t direction) { SCEnter(); ENIPState *enip = (ENIPState *) state; ENIPTransaction *tx; const uint8_t *input = StreamSliceGetData(&stream_slice); uint32_t input_len = StreamSliceGetDataLen(&stream_slice); if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS|APP_LAYER_PARSER_EOF_TC)) { SCReturnStruct(APP_LAYER_OK); } else if (input == NULL && input_len != 0) { // GAP SCReturnStruct(APP_LAYER_OK); } else if (input == NULL || input_len == 0) { SCReturnStruct(APP_LAYER_ERROR); } while (input_len > 0) { tx = ENIPTransactionAlloc(enip); if (tx == NULL) SCReturnStruct(APP_LAYER_OK); if (direction == STREAM_TOCLIENT) tx->tx_data.detect_flags_ts |= APP_LAYER_TX_SKIP_INSPECT_FLAG; else tx->tx_data.detect_flags_tc |= APP_LAYER_TX_SKIP_INSPECT_FLAG; SCLogDebug("ENIPParse input len %d", input_len); DecodeENIPPDU(input, input_len, tx); uint32_t pkt_len = tx->header.length + sizeof(ENIPEncapHdr); SCLogDebug("ENIPParse packet len %d", pkt_len); if (pkt_len > input_len) { SCLogDebug("Invalid packet length"); break; } input += pkt_len; input_len -= pkt_len; //SCLogDebug("remaining %d", input_len); if (input_len < sizeof(ENIPEncapHdr)) { //SCLogDebug("Not enough data"); //not enough data for ENIP break; } } SCReturnStruct(APP_LAYER_OK); } static AppLayerResult ENIPParseRequest(Flow *f, void *state, AppLayerParserState *pstate, StreamSlice stream_slice, void *local_data) { return ENIPParse(f, state, pstate, stream_slice, local_data, STREAM_TOSERVER); } static AppLayerResult ENIPParseResponse(Flow *f, void *state, AppLayerParserState *pstate, StreamSlice stream_slice, void *local_data) { return ENIPParse(f, state, pstate, stream_slice, local_data, STREAM_TOCLIENT); } #define ENIP_LEN_REGISTER_SESSION 4 // protocol u16, options u16 static uint16_t ENIPProbingParser(Flow *f, uint8_t direction, const uint8_t *input, uint32_t input_len, uint8_t *rdir) { // SCLogDebug("ENIPProbingParser %d", input_len); if (input_len < sizeof(ENIPEncapHdr)) { SCLogDebug("length too small to be a ENIP header"); return ALPROTO_UNKNOWN; } uint16_t cmd; uint16_t enip_len; uint32_t status; uint32_t option; uint16_t nbitems; int ret = ByteExtractUint32( &status, BYTE_LITTLE_ENDIAN, sizeof(uint32_t), (const uint8_t *)(input + 8)); if (ret < 0) { return ALPROTO_FAILED; } switch (status) { case SUCCESS: case INVALID_CMD: case NO_RESOURCES: case INCORRECT_DATA: case INVALID_SESSION: case INVALID_LENGTH: case UNSUPPORTED_PROT_REV: case ENCAP_HEADER_ERROR: break; default: return ALPROTO_FAILED; } ret = ByteExtractUint16(&cmd, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input)); if(ret < 0) { return ALPROTO_FAILED; } ret = ByteExtractUint32( &option, BYTE_LITTLE_ENDIAN, sizeof(uint32_t), (const uint8_t *)(input + 20)); if (ret < 0) { return ALPROTO_FAILED; } ret = ByteExtractUint16( &enip_len, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input + 2)); if (ret < 0) { return ALPROTO_FAILED; } //ok for all the known commands switch(cmd) { case NOP: if (option != 0) { return ALPROTO_FAILED; } break; case REGISTER_SESSION: if (enip_len != ENIP_LEN_REGISTER_SESSION) { return ALPROTO_FAILED; } break; case UNREGISTER_SESSION: if (enip_len != ENIP_LEN_REGISTER_SESSION && enip_len != 0) { // 0 for request and 4 for response return ALPROTO_FAILED; } break; case LIST_SERVICES: case LIST_IDENTITY: case SEND_RR_DATA: case SEND_UNIT_DATA: case INDICATE_STATUS: case CANCEL: break; case LIST_INTERFACES: if (input_len < sizeof(ENIPEncapHdr) + 2) { SCLogDebug("length too small to be a ENIP LIST_INTERFACES"); return ALPROTO_UNKNOWN; } ret = ByteExtractUint16( &nbitems, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input)); if(ret < 0) { return ALPROTO_FAILED; } if (enip_len < sizeof(ENIPEncapHdr) + 2 * (size_t)nbitems) { return ALPROTO_FAILED; } break; default: return ALPROTO_FAILED; } return ALPROTO_ENIP; } static AppLayerGetTxIterTuple ENIPGetTxIterator(const uint8_t ipproto, const AppProto alproto, void *alstate, uint64_t min_tx_id, uint64_t max_tx_id, AppLayerGetTxIterState *state) { ENIPState *enip_state = (ENIPState *)alstate; AppLayerGetTxIterTuple no_tuple = { NULL, 0, false }; if (enip_state) { ENIPTransaction *tx_ptr; if (state->un.ptr == NULL) { tx_ptr = TAILQ_FIRST(&enip_state->tx_list); } else { tx_ptr = (ENIPTransaction *)state->un.ptr; } if (tx_ptr) { while (tx_ptr->tx_num < min_tx_id + 1) { tx_ptr = TAILQ_NEXT(tx_ptr, next); if (!tx_ptr) { return no_tuple; } } if (tx_ptr->tx_num >= max_tx_id + 1) { return no_tuple; } state->un.ptr = TAILQ_NEXT(tx_ptr, next); AppLayerGetTxIterTuple tuple = { .tx_ptr = tx_ptr, .tx_id = tx_ptr->tx_num - 1, .has_next = (state->un.ptr != NULL), }; return tuple; } } return no_tuple; } /** * \brief Function to register the ENIP protocol parsers and other functions */ void RegisterENIPUDPParsers(void) { SCEnter(); const char *proto_name = "enip"; if (AppLayerProtoDetectConfProtoDetectionEnabledDefault("udp", proto_name, false)) { AppLayerProtoDetectRegisterProtocol(ALPROTO_ENIP, proto_name); if (RunmodeIsUnittests()) { AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818", ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), STREAM_TOSERVER, ENIPProbingParser, NULL); AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818", ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT, ENIPProbingParser, NULL); } else { if (!AppLayerProtoDetectPPParseConfPorts("udp", IPPROTO_UDP, proto_name, ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), ENIPProbingParser, ENIPProbingParser)) { SCLogDebug( "no ENIP UDP config found enabling ENIP detection on port 44818."); AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818", ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), STREAM_TOSERVER, ENIPProbingParser, NULL); AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818", ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT, ENIPProbingParser, NULL); } } } else { SCLogConfig("Protocol detection and parser disabled for %s protocol.", proto_name); return; } if (AppLayerParserConfParserEnabled("udp", proto_name)) { AppLayerParserRegisterParser(IPPROTO_UDP, ALPROTO_ENIP, STREAM_TOSERVER, ENIPParseRequest); AppLayerParserRegisterParser(IPPROTO_UDP, ALPROTO_ENIP, STREAM_TOCLIENT, ENIPParseResponse); AppLayerParserRegisterStateFuncs(IPPROTO_UDP, ALPROTO_ENIP, ENIPStateAlloc, ENIPStateFree); AppLayerParserRegisterGetTx(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTx); AppLayerParserRegisterGetTxIterator(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTxIterator); AppLayerParserRegisterTxDataFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTxData); AppLayerParserRegisterStateDataFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetStateData); AppLayerParserRegisterGetTxCnt(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTxCnt); AppLayerParserRegisterTxFreeFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPStateTransactionFree); AppLayerParserRegisterGetStateProgressFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetAlstateProgress); AppLayerParserRegisterStateProgressCompletionStatus(ALPROTO_ENIP, 1, 1); AppLayerParserRegisterGetEventInfo(IPPROTO_UDP, ALPROTO_ENIP, ENIPStateGetEventInfo); AppLayerParserRegisterGetEventInfoById(IPPROTO_UDP, ALPROTO_ENIP, ENIPStateGetEventInfoById); AppLayerParserRegisterParserAcceptableDataDirection( IPPROTO_UDP, ALPROTO_ENIP, STREAM_TOSERVER | STREAM_TOCLIENT); } else { SCLogInfo( "Parsed disabled for %s protocol. Protocol detection" "still on.", proto_name); } #ifdef UNITTESTS AppLayerParserRegisterProtocolUnittests(IPPROTO_UDP, ALPROTO_ENIP, ENIPParserRegisterTests); #endif SCReturn; } /** * \brief Function to register the ENIP protocol parsers and other functions */ void RegisterENIPTCPParsers(void) { SCEnter(); const char *proto_name = "enip"; if (AppLayerProtoDetectConfProtoDetectionEnabledDefault("tcp", proto_name, false)) { AppLayerProtoDetectRegisterProtocol(ALPROTO_ENIP, proto_name); if (RunmodeIsUnittests()) { AppLayerProtoDetectPPRegister(IPPROTO_TCP, "44818", ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), STREAM_TOSERVER, ENIPProbingParser, NULL); AppLayerProtoDetectPPRegister(IPPROTO_TCP, "44818", ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT, ENIPProbingParser, NULL); } else { if (!AppLayerProtoDetectPPParseConfPorts("tcp", IPPROTO_TCP, proto_name, ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), ENIPProbingParser, ENIPProbingParser)) { return; } } } else { SCLogDebug("Protocol detection and parser disabled for %s protocol.", proto_name); return; } if (AppLayerParserConfParserEnabled("tcp", proto_name)) { AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_ENIP, STREAM_TOSERVER, ENIPParseRequest); AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_ENIP, STREAM_TOCLIENT, ENIPParseResponse); AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_ENIP, ENIPStateAlloc, ENIPStateFree); AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTx); AppLayerParserRegisterGetTxIterator(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTxIterator); AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTxData); AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetStateData); AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTxCnt); AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPStateTransactionFree); AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetAlstateProgress); AppLayerParserRegisterStateProgressCompletionStatus(ALPROTO_ENIP, 1, 1); AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_ENIP, ENIPStateGetEventInfo); AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_ENIP, STREAM_TOSERVER | STREAM_TOCLIENT); /* This parser accepts gaps. */ AppLayerParserRegisterOptionFlags(IPPROTO_TCP, ALPROTO_ENIP, APP_LAYER_PARSER_OPT_ACCEPT_GAPS); AppLayerParserRegisterOptionFlags(IPPROTO_TCP, ALPROTO_ENIP, 0); } else { SCLogConfig("Parser disabled for %s protocol. Protocol detection still on.", proto_name); } #ifdef UNITTESTS AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_ENIP, ENIPParserRegisterTests); #endif SCReturn; } /* UNITTESTS */ #ifdef UNITTESTS #include "flow-util.h" #include "stream-tcp.h" static uint8_t listIdentity[] = {/* List ID */ 0x63, 0x00, /* Length */ 0x00, 0x00, /* Session */ 0x00, 0x00, 0x00, 0x00, /* Status */ 0x00, 0x00, 0x00, 0x00, /* Delay*/ 0x00, /* Context */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Quantity of coils */ 0x00, 0x00, 0x00, 0x00, 0x00}; /** * \brief Test if ENIP Packet matches signature */ static int ALDecodeENIPTest(void) { AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); Flow f; TcpSession ssn; memset(&f, 0, sizeof(f)); memset(&ssn, 0, sizeof(ssn)); f.protoctx = (void *)&ssn; f.proto = IPPROTO_TCP; f.alproto = ALPROTO_ENIP; StreamTcpInitConfig(true); int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_ENIP, STREAM_TOSERVER, listIdentity, sizeof(listIdentity)); FAIL_IF(r != 0); ENIPState *enip_state = f.alstate; FAIL_IF_NULL(enip_state); ENIPTransaction *tx = ENIPGetTx(enip_state, 0); FAIL_IF_NULL(tx); FAIL_IF(tx->header.command != 99); AppLayerParserThreadCtxFree(alp_tctx); StreamTcpFreeConfig(true); FLOW_DESTROY(&f); PASS; } #endif /* UNITTESTS */ void ENIPParserRegisterTests(void) { #ifdef UNITTESTS UtRegisterTest("ALDecodeENIPTest", ALDecodeENIPTest); #endif /* UNITTESTS */ }