diff --git a/doc/userguide/rules/enip-keyword.rst b/doc/userguide/rules/enip-keyword.rst new file mode 100644 index 0000000000..d37f26859f --- /dev/null +++ b/doc/userguide/rules/enip-keyword.rst @@ -0,0 +1,40 @@ +ENIP/CIP Keywords +============== + +The enip_command and cip_service keywords can be used for matching on various properties of +ENIP requests. + +There are three ways of using this keyword: + +* matching on ENIP command with the setting "enip_command"; +* matching on CIP Service with the setting "cip_service". +* matching both the ENIP command and the CIP Service with "enip_command" and "cip_service" together + + +For the ENIP command, we are matching against the command field found in the ENIP encapsulation. + +For the CIP Service, we use a maximum of 3 comma seperated values representing the Service, Class and Attribute. +These values are described in the CIP specification. CIP Classes are associated with their Service, and CIP Attributes +are associated with their Service. If you only need to match up until the Service, then only provide the Service value. +If you want to match to the CIP Attribute, then you must provide all 3 values. + + +Syntax:: + + enip_command: + cip_service: + enip_command:, cip_service: + + +Examples:: + + enip_command:99 + cip_service:75 + cip_service:16,246,6 + enip_command:111, cip_service:5 + + +(cf. http://read.pudn.com/downloads166/ebook/763211/EIP-CIP-V1-1.0.pdf) + +Information on the protocol can be found here: +http://literature.rockwellautomation.com/idc/groups/literature/documents/wp/enet-wp001_-en-p.pdf diff --git a/src/Makefile.am b/src/Makefile.am index 92b9472e1a..d636765cff 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -19,6 +19,8 @@ app-layer-detect-proto.c app-layer-detect-proto.h \ app-layer-dns-common.c app-layer-dns-common.h \ app-layer-dns-tcp.c app-layer-dns-tcp.h \ app-layer-dns-udp.c app-layer-dns-udp.h \ +app-layer-enip.c app-layer-enip.h \ +app-layer-enip-common.c app-layer-enip-common.h \ app-layer-events.c app-layer-events.h \ app-layer-ftp.c app-layer-ftp.h \ app-layer-htp-body.c app-layer-htp-body.h \ @@ -103,6 +105,7 @@ detect-engine-dcepayload.c detect-engine-dcepayload.h \ detect-engine-dns.c detect-engine-dns.h \ detect-engine-tls.c detect-engine-tls.h \ detect-engine-modbus.c detect-engine-modbus.h \ +detect-engine-enip.c detect-engine-enip.h \ detect-engine-event.c detect-engine-event.h \ detect-engine-file.c detect-engine-file.h \ detect-engine-filedata-smtp.c detect-engine-filedata-smtp.h \ @@ -222,6 +225,7 @@ detect-window.c detect-window.h \ detect-within.c detect-within.h \ detect-modbus.c detect-modbus.h \ detect-xbits.c detect-xbits.h \ +detect-cipservice.c detect-cipservice.h \ flow-bit.c flow-bit.h \ flow.c flow.h \ flow-hash.c flow-hash.h \ diff --git a/src/app-layer-detect-proto.c b/src/app-layer-detect-proto.c index cca1ae7a8f..74e22d4ae0 100644 --- a/src/app-layer-detect-proto.c +++ b/src/app-layer-detect-proto.c @@ -691,6 +691,8 @@ void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingParser *pp printf(" alproto: ALPROTO_DNS\n"); else if (pp_pe->alproto == ALPROTO_MODBUS) printf(" alproto: ALPROTO_MODBUS\n"); + else if (pp_pe->alproto == ALPROTO_ENIP) + printf(" alproto: ALPROTO_ENIP\n"); else if (pp_pe->alproto == ALPROTO_TEMPLATE) printf(" alproto: ALPROTO_TEMPLATE\n"); else @@ -744,6 +746,8 @@ void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingParser *pp printf(" alproto: ALPROTO_DNS\n"); else if (pp_pe->alproto == ALPROTO_MODBUS) printf(" alproto: ALPROTO_MODBUS\n"); + else if (pp_pe->alproto == ALPROTO_ENIP) + printf(" alproto: ALPROTO_ENIP\n"); else if (pp_pe->alproto == ALPROTO_TEMPLATE) printf(" alproto: ALPROTO_TEMPLATE\n"); else diff --git a/src/app-layer-enip-common.c b/src/app-layer-enip-common.c new file mode 100644 index 0000000000..72ebc00be2 --- /dev/null +++ b/src/app-layer-enip-common.c @@ -0,0 +1,947 @@ +/* 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 common code + * + */ + +#include "suricata-common.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "util-byte.h" +#include "pkt-var.h" +#include "util-profiling.h" + +#include "app-layer-enip-common.h" + +/** + * \brief Extract 8 bits and move up the offset + * @param res + * @param input + * @param offset + */ +int ENIPExtractUint8(uint8_t *res, uint8_t *input, uint16_t *offset, uint32_t input_len) +{ + + if (*offset > (input_len - sizeof(uint8_t))) + { + SCLogDebug("ENIPExtractUint8: Parsing beyond payload length\n"); + return 0; + } + + *res = *(input + *offset); + *offset += sizeof(uint8_t); + return 1; +} + +/** + * \brief Extract 16 bits and move up the offset + * @param res + * @param input + * @param offset + */ +int ENIPExtractUint16(uint16_t *res, uint8_t *input, uint16_t *offset, uint32_t input_len) +{ + + if (*offset > (input_len - sizeof(uint16_t))) + { + SCLogDebug("ENIPExtractUint16: Parsing beyond payload length\n"); + return 0; + } + + ByteExtractUint16(res, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), + (const uint8_t *) (input + *offset)); + *offset += sizeof(uint16_t); + return 1; +} + +/** + * \brief Extract 32 bits and move up the offset + * @param res + * @param input + * @param offset + */ +int ENIPExtractUint32(uint32_t *res, uint8_t *input, uint16_t *offset, uint32_t input_len) +{ + + if (*offset > (input_len - sizeof(uint32_t))) + { + SCLogDebug("ENIPExtractUint32: Parsing beyond payload length\n"); + return 0; + } + + ByteExtractUint32(res, BYTE_LITTLE_ENDIAN, sizeof(uint32_t), + (const uint8_t *) (input + *offset)); + *offset += sizeof(uint32_t); + return 1; +} + +/** + * \brief Extract 64 bits and move up the offset + * @param res + * @param input + * @param offset + */ +int ENIPExtractUint64(uint64_t *res, uint8_t *input, uint16_t *offset, uint32_t input_len) +{ + + if (*offset > (input_len - sizeof(uint64_t))) + { + SCLogDebug("ENIPExtractUint64: Parsing beyond payload length\n"); + return 0; + } + + ByteExtractUint64(res, BYTE_LITTLE_ENDIAN, sizeof(uint64_t), + (const uint8_t *) (input + *offset)); + *offset += sizeof(uint64_t); + return 1; +} + + +/** + * \brief Create service entry, add to transaction + * @param tx Transaction + * @return service entry + */ +static CIPServiceEntry *CIPServiceAlloc(ENIPTransaction *tx) +{ + + CIPServiceEntry *svc = (CIPServiceEntry *) SCCalloc(1, + sizeof(CIPServiceEntry)); + if (unlikely(svc == NULL)) + return NULL; + + memset(svc, 0x00, sizeof(CIPServiceEntry)); + + TAILQ_INIT(&svc->segment_list); + TAILQ_INIT(&svc->attrib_list); + + TAILQ_INSERT_TAIL(&tx->service_list, svc, next); + tx->service_count++; + return svc; + +} + + +/** + * \brief Delete service entry + */ + +void CIPServiceFree(void *s) +{ + SCEnter(); + if (s) + { + CIPServiceEntry *svc = (CIPServiceEntry *) s; + + 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(s); + } + SCReturn; +} + + + +/** + * \brief Decode ENIP Encapsulation Header + * @param input, input_len data stream + * @param enip_data stores data from Packet + * @return 1 Packet ok + * @return 0 Packet has errors + */ +int DecodeENIPPDU(uint8_t *input, uint32_t input_len, + ENIPTransaction *enip_data) +{ + int ret = 1; + + uint16_t offset = 0; //byte offset + + //Decode Encapsulation Header + uint16_t cmd; + uint16_t len; + uint32_t session; + uint32_t status; + uint64_t context; + uint32_t option; + if (ENIPExtractUint16(&cmd, input, &offset, input_len) != 1) + { + return 0; + } + if (ENIPExtractUint16(&len, input, &offset, input_len) != 1) + { + return 0; + } + if (ENIPExtractUint32(&session, input, &offset, input_len) != 1) + { + return 0; + } + if (ENIPExtractUint32(&status, input, &offset, input_len) != 1) + { + return 0; + } + if (ENIPExtractUint64(&context, input, &offset, input_len) != 1) + { + return 0; + } + if (ENIPExtractUint32(&option, input, &offset, input_len) != 1) + { + return 0; + } + + enip_data->header.command = cmd; + enip_data->header.length = len; + enip_data->header.session = session; + enip_data->header.status = status; + enip_data->header.context = context; + enip_data->header.option = option; + + switch (enip_data->header.command) + { + case NOP: + SCLogDebug("DecodeENIP - NOP\n"); + break; + case LIST_SERVICES: + SCLogDebug("DecodeENIP - LIST_SERVICES\n"); + break; + case LIST_IDENTITY: + SCLogDebug("DecodeENIP - LIST_IDENTITY\n"); + break; + case LIST_INTERFACES: + SCLogDebug("DecodeENIP - LIST_INTERFACES\n"); + break; + case REGISTER_SESSION: + SCLogDebug("DecodeENIP - REGISTER_SESSION\n"); + break; + case UNREGISTER_SESSION: + SCLogDebug("DecodeENIP - UNREGISTER_SESSION\n"); + break; + case SEND_RR_DATA: + SCLogDebug( + "DecodeENIP - SEND_RR_DATA - parse Common Packet Format\n"); + ret = DecodeCommonPacketFormatPDU(input, input_len, enip_data, + offset); + break; + case SEND_UNIT_DATA: + SCLogDebug( + "DecodeENIP - SEND UNIT DATA - parse Common Packet Format\n"); + ret = DecodeCommonPacketFormatPDU(input, input_len, enip_data, + offset); + break; + case INDICATE_STATUS: + SCLogDebug("DecodeENIP - INDICATE_STATUS\n"); + break; + case CANCEL: + SCLogDebug("DecodeENIP - CANCEL\n"); + break; + default: + SCLogDebug("DecodeENIP - UNSUPPORTED COMMAND 0x%x\n", + enip_data->header.command); + } + + return ret; +} + + +/** + * \brief Decode Common Packet Format + * @param input, input_len data stream + * @param enip_data stores data from Packet + * @param offset current point in the packet + * @return 1 Packet ok + * @return 0 Packet has errors + */ +int DecodeCommonPacketFormatPDU(uint8_t *input, uint32_t input_len, + ENIPTransaction *enip_data, uint16_t offset) +{ + + if (enip_data->header.length < sizeof(ENIPEncapDataHdr)) + { + SCLogDebug("DecodeCommonPacketFormat: Malformed ENIP packet\n"); + return 0; + } + + uint32_t handle; + uint16_t timeout; + uint16_t count; + if (ENIPExtractUint32(&handle, input, &offset, input_len) != 1) + { + return 0; + } + if (ENIPExtractUint16(&timeout, input, &offset, input_len) != 1) + { + return 0; + } + if (ENIPExtractUint16(&count, input, &offset, input_len) != 1) + { + return 0; + } + enip_data->encap_data_header.interface_handle = handle; + enip_data->encap_data_header.timeout = timeout; + enip_data->encap_data_header.item_count = count; + + uint16_t address_type; + uint16_t address_length; //length of connection id in bytes + uint32_t address_connectionid = 0; + uint32_t address_sequence = 0; + + if (ENIPExtractUint16(&address_type, input, &offset, input_len) != 1) + { + return 0; + } + if (ENIPExtractUint16(&address_length, input, &offset, input_len) != 1) + { + return 0; + } + + //depending on addr type, get connection id, sequence if needed. Can also use addr length too? + if (address_type == CONNECTION_BASED) + { //get 4 byte connection id + if (ENIPExtractUint32(&address_connectionid, input, &offset, input_len) != 1) + { + return 0; + } + } else if (address_type == SEQUENCE_ADDR_ITEM) + { // get 4 byte connection id and 4 byte sequence + if (ENIPExtractUint32(&address_connectionid, input, &offset, input_len) != 1) + { + return 0; + } + if (ENIPExtractUint32(&address_sequence, input, &offset, input_len) != 1) + { + return 0; + } + } + + enip_data->encap_addr_item.type = address_type; + enip_data->encap_addr_item.length = address_length; + enip_data->encap_addr_item.conn_id = address_connectionid; + enip_data->encap_addr_item.sequence_num = address_sequence; + + uint16_t data_type; + uint16_t data_length; //length of data in bytes + uint16_t data_sequence_count; + + if (ENIPExtractUint16(&data_type, input, &offset, input_len) != 1) + { + return 0; + } + if (ENIPExtractUint16(&data_length, input, &offset, input_len) != 1) + { + return 0; + } + + enip_data->encap_data_item.type = data_type; + enip_data->encap_data_item.length = data_length; + + if (enip_data->encap_data_item.type == CONNECTED_DATA_ITEM) + { //connected data items have seq number + if (ENIPExtractUint16(&data_sequence_count, input, &offset, input_len) != 1) + { + return 0; + } + enip_data->encap_data_item.sequence_count = data_sequence_count; + } + + switch (enip_data->encap_data_item.type) + { + case CONNECTED_DATA_ITEM: + SCLogDebug( + "DecodeCommonPacketFormat - CONNECTED DATA ITEM - parse CIP\n"); + DecodeCIPPDU(input, input_len, enip_data, offset); + break; + case UNCONNECTED_DATA_ITEM: + SCLogDebug("DecodeCommonPacketFormat - UNCONNECTED DATA ITEM\n"); + DecodeCIPPDU(input, input_len, enip_data, offset); + break; + default: + SCLogDebug("DecodeCommonPacketFormat - UNKNOWN TYPE 0x%x\n\n", + enip_data->encap_data_item.type); + return 0; + } + + return 1; +} + +/** + * \brief Decode CIP packet + * @param input, input_len data stream + * @param enip_data stores data from Packet + * @param offset current point in the packet + * @return 1 Packet ok + * @return 0 Packet has errors + */ + +int DecodeCIPPDU(uint8_t *input, uint32_t input_len, + ENIPTransaction *enip_data, uint16_t offset) +{ + int ret = 1; + + if (enip_data->encap_data_item.length == 0) + { + SCLogDebug("DecodeCIP: No CIP Data\n"); + return 0; + } + + if (offset > (input_len - sizeof(uint8_t))) + { + SCLogDebug("DecodeCIP: Parsing beyond payload length\n"); + return 0; + } + + uint8_t service = 0; + service = *(input + offset); + + //SCLogDebug("CIP Service 0x%x\n", service); + + //use service code first bit to determine request/response, no need to save or push offset + if (service >> 7) + { + ret = DecodeCIPResponsePDU(input, input_len, enip_data, offset); + } else + { + ret = DecodeCIPRequestPDU(input, input_len, enip_data, offset); + } + + return ret; +} + + + +/** + * \brief Decode CIP Request + * @param input, input_len data stream + * @param enip_data stores data from Packet + * @param offset current point in the packet + * @return 1 Packet ok + * @return 0 Packet has errors + */ +int DecodeCIPRequestPDU(uint8_t *input, uint32_t input_len, + ENIPTransaction *enip_data, uint16_t offset) +{ + int ret = 1; + + if (enip_data->encap_data_item.length < sizeof(CIPReqHdr)) + { + SCLogDebug("DecodeCIPRequest - Malformed CIP Data\n"); + return 0; + } + + uint8_t service; //<-----CIP SERVICE + uint8_t path_size; + + if (ENIPExtractUint8(&service, input, &offset, input_len) != 1) + { + return 0; + } + if (ENIPExtractUint8(&path_size, input, &offset, input_len) != 1) + { + return 0; + } + + if (service > MAX_CIP_SERVICE) + { // service codes of value 0x80 or greater are not permitted because in the CIP protocol the highest order bit is used to flag request(0)/response(1) + SCLogDebug("DecodeCIPRequest - INVALID CIP SERVICE 0x%x\n", service); + return 0; + } + + //reached maximum number of services + if (enip_data->service_count > 32) + { + SCLogDebug("DecodeCIPRequest: Maximum services reached\n"); + return 0; + } + + //save CIP data + CIPServiceEntry *node = CIPServiceAlloc(enip_data); + if (node == NULL) + { + SCLogDebug("DecodeCIPRequest: Unable to create CIP service\n"); + return 0; + } + node->direction = 0; + node->service = service; + node->request.path_size = path_size; + node->request.path_offset = offset; + // SCLogDebug("DecodeCIPRequestPDU: service 0x%x size %d\n", node->service, + // node->request.path_size); + + DecodeCIPRequestPathPDU(input, input_len, node, offset); + + offset += path_size * sizeof(uint16_t); //move offset past pathsize + + //list of CIP services is large and can be vendor specific, store CIP service anyways and let the rule decide the action + switch (service) + { + case CIP_RESERVED: + SCLogDebug("DecodeCIPRequest - CIP_RESERVED\n"); + break; + case CIP_GET_ATTR_ALL: + SCLogDebug("DecodeCIPRequest - CIP_GET_ATTR_ALL\n"); + break; + case CIP_GET_ATTR_LIST: + SCLogDebug("DecodeCIPRequest - CIP_GET_ATTR_LIST\n"); + break; + case CIP_SET_ATTR_LIST: + SCLogDebug("DecodeCIPRequest - CIP_SET_ATTR_LIST\n"); + break; + case CIP_RESET: + SCLogDebug("DecodeCIPRequest - CIP_RESET\n"); + break; + case CIP_START: + SCLogDebug("DecodeCIPRequest - CIP_START\n"); + break; + case CIP_STOP: + SCLogDebug("DecodeCIPRequest - CIP_STOP\n"); + break; + case CIP_CREATE: + SCLogDebug("DecodeCIPRequest - CIP_CREATE\n"); + break; + case CIP_DELETE: + SCLogDebug("DecodeCIPRequest - CIP_DELETE\n"); + break; + case CIP_MSP: + SCLogDebug("DecodeCIPRequest - CIP_MSP\n"); + DecodeCIPRequestMSPPDU(input, input_len, enip_data, offset); + break; + case CIP_APPLY_ATTR: + SCLogDebug("DecodeCIPRequest - CIP_APPLY_ATTR\n"); + break; + case CIP_KICK_TIMER: + SCLogDebug("DecodeCIPRequest - CIP_KICK_TIMER\n"); + break; + case CIP_OPEN_CONNECTION: + SCLogDebug("DecodeCIPRequest - CIP_OPEN_CONNECTION\n"); + break; + case CIP_CHANGE_START: + SCLogDebug("DecodeCIPRequest - CIP_CHANGE_START\n"); + break; + case CIP_GET_STATUS: + SCLogDebug("DecodeCIPRequest - CIP_GET_STATUS\n"); + break; + default: + SCLogDebug("DecodeCIPRequest - CIP SERVICE 0x%x\n", service); + } + + return ret; +} + + +/** + * \brief Deocde CIP Request Path + * @param input, input_len data stream + * @param enip_data stores data from Packet + * @param offset current point in the packet + * @param cipserviced the cip service rule + * @return 1 Packet matches + * @return 0 Packet not match + */ +int DecodeCIPRequestPathPDU(uint8_t *input, uint32_t input_len, + CIPServiceEntry *node, uint16_t offset) +{ + //SCLogDebug("DecodeCIPRequestPath: service 0x%x size %d length %d\n", + // node->service, node->request.path_size, input_len); + + if (node->request.path_size < 1) + { + //SCLogDebug("DecodeCIPRequestPath: empty path or CIP Response\n"); + return 0; + } + + int bytes_remain = node->request.path_size; + + uint8_t segment; + uint8_t reserved; //unused byte reserved by ODVA + + //8 bit fields + uint8_t req_path_class8; + uint8_t req_path_instance8; + uint8_t req_path_attr8; + + //16 bit fields + uint16_t req_path_class16; + uint16_t req_path_instance16; + + uint16_t class = 0; + + SegmentEntry *seg = NULL; + + while (bytes_remain > 0) + { + if (ENIPExtractUint8(&segment, input, &offset, input_len) != 1) + { + return 0; + } + switch (segment) + { //assume order is class then instance. Can have multiple + case PATH_CLASS_8BIT: + if (ENIPExtractUint8(&req_path_class8, input, &offset, input_len) != 1) + { + return 0; + } + class = (uint16_t) req_path_class8; + SCLogDebug("DecodeCIPRequestPathPDU: 8bit class 0x%x\n", class); + + seg = SCMalloc(sizeof(SegmentEntry)); + if (unlikely(seg == NULL)) + return 0; + seg->segment = segment; + seg->value = class; + TAILQ_INSERT_TAIL(&node->segment_list, seg, next); + + bytes_remain--; + break; + case PATH_INSTANCE_8BIT: + if (ENIPExtractUint8(&req_path_instance8, input, &offset, input_len) != 1) + { + return 0; + } + //skip instance, don't need to store + bytes_remain--; + break; + case PATH_ATTR_8BIT: //single attribute + if (ENIPExtractUint8(&req_path_attr8, input, &offset, input_len) != 1) + { + return 0; + } + //uint16_t attrib = (uint16_t) req_path_attr8; + //SCLogDebug("DecodeCIPRequestPath: 8bit attr 0x%x\n", attrib); + + seg = SCMalloc(sizeof(SegmentEntry)); + if (unlikely(seg == NULL)) + return 0; + seg->segment = segment; + seg->value = class; + TAILQ_INSERT_TAIL(&node->segment_list, seg, next); + + bytes_remain--; + break; + case PATH_CLASS_16BIT: + if (ENIPExtractUint8(&reserved, input, &offset, input_len) != 1) //skip reserved + { + return 0; + } + if (ENIPExtractUint16(&req_path_class16, input, &offset, input_len) != 1) + { + return 0; + } + class = req_path_class16; + SCLogDebug("DecodeCIPRequestPath: 16bit class 0x%x\n", class); + + seg = SCMalloc(sizeof(SegmentEntry)); + if (unlikely(seg == NULL)) + return 0; + seg->segment = segment; + seg->value = class; + TAILQ_INSERT_TAIL(&node->segment_list, seg, next); + if (bytes_remain >= 2) + { + bytes_remain = bytes_remain - 2; + } else + { + bytes_remain = 0; + } + break; + case PATH_INSTANCE_16BIT: + if (ENIPExtractUint8(&reserved, input, &offset, input_len) != 1) // skip reserved + { + return 0; + } + if (ENIPExtractUint16(&req_path_instance16, input, &offset, input_len) != 1) + { + return 0; + } + //skip instance, don't need to store + if (bytes_remain >= 2) + { + bytes_remain = bytes_remain - 2; + } else + { + bytes_remain = 0; + } + break; + default: + SCLogDebug( + "DecodeCIPRequestPath: UNKNOWN SEGMENT 0x%x service 0x%x\n", + segment, node->service); + return 0; + } + } + + if ((node->service == CIP_SET_ATTR_LIST) || (node->service + == CIP_GET_ATTR_LIST)) + { + uint16_t attr_list_count; + uint16_t attribute; + //parse get/set attribute list + + if (ENIPExtractUint16(&attr_list_count, input, &offset, input_len) != 1) + { + return 0; + } + SCLogDebug("DecodeCIPRequestPathPDU: attribute list count %d\n", + attr_list_count); + for (int i = 0; i < attr_list_count; i++) + { + if (ENIPExtractUint16(&attribute, input, &offset, input_len) != 1) + { + return 0; + } + SCLogDebug("DecodeCIPRequestPathPDU: attribute %d\n", attribute); + //save attrs + AttributeEntry *attr = SCMalloc(sizeof(AttributeEntry)); + if (unlikely(attr == NULL)) + return 0; + attr->attribute = attribute; + TAILQ_INSERT_TAIL(&node->attrib_list, attr, next); + + } + } + + return 1; +} + +/** + * \brief Decode CIP Response + * @param input, input_len data stream + * @param enip_data stores data from Packet + * @param offset current point in the packet + * @return 1 Packet ok + * @return 0 Packet has errors + */ +int DecodeCIPResponsePDU(uint8_t *input, uint32_t input_len, + ENIPTransaction *enip_data, uint16_t offset) +{ + int ret = 1; + + if (enip_data->encap_data_item.length < sizeof(CIPRespHdr)) + { + SCLogDebug("DecodeCIPResponse - Malformed CIP Data\n"); + return 0; + } + + uint8_t service; //<----CIP SERVICE + uint8_t reserved; //unused byte reserved by ODVA + uint16_t status; + + if (ENIPExtractUint8(&service, input, &offset, input_len) != 1) + { + return 0; + } + if (ENIPExtractUint8(&reserved, input, &offset, input_len) != 1) + { + return 0; + } + if (ENIPExtractUint16(&status, input, &offset, input_len) != 1) + { + return 0; + } + + //SCLogDebug("DecodeCIPResponse: service 0x%x\n",service); + service &= 0x7f; //strip off top bit to get service code. Responses have first bit as 1 + + SCLogDebug("CIP service 0x%x status 0x%x\n", service, status); + + //reached maximum number of services + if (enip_data->service_count > 32) + { + SCLogDebug("DecodeCIPRequest: Maximum services reached\n"); + return 0; + } + + //save CIP data + CIPServiceEntry *node = CIPServiceAlloc(enip_data); + if (node == NULL) + { + SCLogDebug("DecodeCIPRequest: Unable to create CIP service\n"); + return 0; + } + node->direction = 1; + node->service = service; + node->response.status = status; + + SCLogDebug("DecodeCIPResponsePDU: service 0x%x size %d\n", node->service, + node->request.path_size); + + //list of CIP services is large and can be vendor specific, store CIP service anyways and let the rule decide the action + switch (service) + { + case CIP_RESERVED: + SCLogDebug("DecodeCIPResponse - CIP_RESERVED\n"); + break; + case CIP_GET_ATTR_ALL: + SCLogDebug("DecodeCIPResponse - CIP_GET_ATTR_ALL\n"); + break; + case CIP_GET_ATTR_LIST: + SCLogDebug("DecodeCIPResponse - CIP_GET_ATTR_LIST\n"); + break; + case CIP_SET_ATTR_LIST: + SCLogDebug("DecodeCIPResponse - CIP_SET_ATTR_LIST\n"); + break; + case CIP_RESET: + SCLogDebug("DecodeCIPResponse - CIP_RESET\n"); + break; + case CIP_START: + SCLogDebug("DecodeCIPResponse - CIP_START\n"); + break; + case CIP_STOP: + SCLogDebug("DecodeCIPResponse - CIP_STOP\n"); + break; + case CIP_CREATE: + SCLogDebug("DecodeCIPResponse - CIP_CREATE\n"); + break; + case CIP_DELETE: + SCLogDebug("DecodeCIPResponse - CIP_DELETE\n"); + break; + case CIP_MSP: + SCLogDebug("DecodeCIPResponse - CIP_MSP\n"); + DecodeCIPResponseMSPPDU(input, input_len, enip_data, offset); + break; + case CIP_APPLY_ATTR: + SCLogDebug("DecodeCIPResponse - CIP_APPLY_ATTR\n"); + break; + case CIP_KICK_TIMER: + SCLogDebug("DecodeCIPResponse - CIP_KICK_TIMER\n"); + break; + case CIP_OPEN_CONNECTION: + SCLogDebug("DecodeCIPResponse - CIP_OPEN_CONNECTION\n"); + break; + case CIP_CHANGE_START: + SCLogDebug("DecodeCIPResponse - CIP_CHANGE_START\n"); + break; + case CIP_GET_STATUS: + SCLogDebug("DecodeCIPResponse - CIP_GET_STATUS\n"); + break; + default: + SCLogDebug("DecodeCIPResponse - CIP SERVICE 0x%x\n", service); + } + + return ret; +} + + +/** + * \brief Decode CIP Request Multi Service Packet + * @param input, input_len data stream + * @param enip_data stores data from Packet + * @param offset current point in the packet + * @return 1 Packet ok + * @return 0 Packet has errors + */ +int DecodeCIPRequestMSPPDU(uint8_t *input, uint32_t input_len, + ENIPTransaction *enip_data, uint16_t offset) +{ + int ret = 1; + if (offset >= (input_len - sizeof(uint16_t))) + { + SCLogDebug("DecodeCIPRequestMSPPDU: Parsing beyond payload length\n"); + return 0; + } + //use temp_offset just to grab the service offset, don't want to use and push offset + uint16_t temp_offset = offset; + uint16_t num_services; + ByteExtractUint16(&num_services, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), + (const uint8_t *) (input + temp_offset)); + temp_offset += sizeof(uint16_t); + //SCLogDebug("DecodeCIPRequestMSP number of services %d\n",num_services); + + for (int svc = 1; svc < num_services + 1; svc++) + { + if (temp_offset >= (input_len - sizeof(uint16_t))) + { + SCLogDebug("DecodeCIPRequestMSPPDU: Parsing beyond payload length\n"); + return 0; + } + + uint16_t svc_offset; //read set of service offsets + ByteExtractUint16(&svc_offset, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), + (const uint8_t *) (input + temp_offset)); + temp_offset += sizeof(uint16_t); + //SCLogDebug("parseCIPRequestMSP service %d offset %d\n",svc, svc_offset); + + DecodeCIPPDU(input, input_len, enip_data, offset + svc_offset); //parse CIP at found offset + } + + return ret; +} + + + +/** + * \brief Decode CIP Response MultiService Packet. + * @param input, input_len data stream + * @param enip_data stores data from Packet + * @param offset current point in the packet + * @return 1 Packet ok + * @return 0 Packet has errors + */ +int DecodeCIPResponseMSPPDU(uint8_t *input, uint32_t input_len, + ENIPTransaction *enip_data, uint16_t offset) +{ + int ret = 1; + + if (offset >= (input_len - sizeof(uint16_t))) + { + SCLogDebug("DecodeCIPResponseMSPPDU: Parsing beyond payload length\n"); + return 0; + } + //use temp_offset just to grab the service offset, don't want to use and push offset + uint16_t temp_offset = offset; + uint16_t num_services; + ByteExtractUint16(&num_services, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), + (const uint8_t *) (input + temp_offset)); + temp_offset += sizeof(uint16_t); + //SCLogDebug("DecodeCIPResponseMSP number of services %d\n", num_services); + + for (int svc = 0; svc < num_services; svc++) + { + if (temp_offset >= (input_len - sizeof(uint16_t))) + { + SCLogDebug("DecodeCIPResponseMSP: Parsing beyond payload length\n"); + return 0; + } + + uint16_t svc_offset; //read set of service offsets + ByteExtractUint16(&svc_offset, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), + (const uint8_t *) (input + temp_offset)); + temp_offset += sizeof(uint16_t); + //SCLogDebug("parseCIPResponseMSP service %d offset %d\n", svc, svc_offset); + + DecodeCIPPDU(input, input_len, enip_data, offset + svc_offset); //parse CIP at found offset + } + + return ret; +} diff --git a/src/app-layer-enip-common.h b/src/app-layer-enip-common.h new file mode 100644 index 0000000000..77777c8837 --- /dev/null +++ b/src/app-layer-enip-common.h @@ -0,0 +1,250 @@ +/* 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 + */ + +#ifndef __APP_LAYER_ENIP_COMMON_H__ +#define __APP_LAYER_ENIP_COMMON_H__ + +#include "app-layer-protos.h" +#include "app-layer-parser.h" +#include "flow.h" +#include "queue.h" + +#define MAX_ENIP_CMD 65535 + +// EtherNet/IP commands +#define NOP 0x0000 +#define LIST_SERVICES 0x0004 +#define LIST_IDENTITY 0x0063 +#define LIST_INTERFACES 0x0064 +#define REGISTER_SESSION 0x0065 +#define UNREGISTER_SESSION 0x0066 +#define SEND_RR_DATA 0x006F +#define SEND_UNIT_DATA 0x0070 +#define INDICATE_STATUS 0x0072 +#define CANCEL 0x0073 + +//Common Packet Format Types +#define NULL_ADDR 0x0000 +#define CONNECTION_BASED 0x00a1 +#define CONNECTED_DATA_ITEM 0x00b1 +#define UNCONNECTED_DATA_ITEM 0x00b2 +#define SEQUENCE_ADDR_ITEM 0xB002 + +//status codes +#define SUCCESS 0x0000 +#define INVALID_CMD 0x0001 +#define NO_RESOURCES 0x0002 +#define INCORRECT_DATA 0x0003 +#define INVALID_SESSION 0x0064 +#define INVALID_LENGTH 0x0065 +#define UNSUPPORTED_PROT_REV 0x0069 + +#define MAX_CIP_SERVICE 127 +#define MAX_CIP_CLASS 65535 +#define MAX_CIP_ATTRIBUTE 65535 + +// CIP service codes +#define CIP_RESERVED 0x00 +#define CIP_GET_ATTR_ALL 0x01 +#define CIP_GET_ATTR_LIST 0x03 +#define CIP_SET_ATTR_LIST 0x04 +#define CIP_RESET 0x05 +#define CIP_START 0x06 +#define CIP_STOP 0x07 +#define CIP_CREATE 0x08 +#define CIP_DELETE 0x09 +#define CIP_MSP 0x0a +#define CIP_APPLY_ATTR 0x0d +#define CIP_GET_ATTR_SINGLE 0x0e +#define CIP_SET_ATTR_SINGLE 0x10 +#define CIP_KICK_TIMER 0x4b +#define CIP_OPEN_CONNECTION 0x4c +#define CIP_CHANGE_START 0x4f +#define CIP_GET_STATUS 0x50 + +//PATH sizing codes +#define PATH_CLASS_8BIT 0x20 +#define PATH_CLASS_16BIT 0x21 +#define PATH_INSTANCE_8BIT 0x24 +#define PATH_INSTANCE_16BIT 0x25 +#define PATH_ATTR_8BIT 0x30 +#define PATH_ATTR_16BIT 0x31 //possible value + +/** + * ENIP encapsulation header + */ +typedef struct ENIPEncapHdr_ +{ + uint64_t context; + uint32_t session; + uint32_t status; + uint32_t option; + uint16_t command; + uint16_t length; +} ENIPEncapHdr; + +/** + * ENIP encapsulation data header + */ +typedef struct ENIPEncapDataHdr_ +{ + uint32_t interface_handle; + uint16_t timeout; + uint16_t item_count; +} ENIPEncapDataHdr; + +/** + * ENIP encapsulation address item + */ +typedef struct ENIPEncapAddresItem_ +{ + uint16_t type; + uint16_t length; + uint16_t conn_id; + uint16_t sequence_num; +} ENIPEncapAddresItem; + +/** + * ENIP encapsulation data item + */ +typedef struct ENIPEncapDataItem_ +{ + uint16_t type; + uint16_t length; + uint16_t sequence_count; +} ENIPEncapDataItem; + +/** + * CIP Request Header + */ +typedef struct CIPReqHdr_ +{ + uint8_t service; + uint8_t path_size; +} CIPReqHdr; + +/** + * CIP Response Header + */ +typedef struct CIPRespHdr_ +{ + uint8_t service; + uint8_t pad; + uint8_t status; + uint8_t status_size; +} CIPRespHdr; + +typedef struct SegmentEntry_ +{ + uint16_t segment; //segment type + uint16_t value; //segment value (class or attribute) + +TAILQ_ENTRY(SegmentEntry_) next; +} SegmentEntry; + +typedef struct AttributeEntry_ +{ + uint16_t attribute; //segment class + +TAILQ_ENTRY(AttributeEntry_) next; +} AttributeEntry; + +typedef struct CIPServiceEntry_ +{ + uint8_t service; //cip service + uint8_t direction; + union + { + struct + { + uint8_t path_size; //cip path size + uint16_t path_offset; //offset to cip path + } request; + struct + { + uint8_t status; + } response; + }; + + TAILQ_HEAD(, SegmentEntry_) segment_list; /**< list for CIP segment */ + TAILQ_HEAD(, AttributeEntry_) attrib_list; /**< list for CIP segment */ + + TAILQ_ENTRY(CIPServiceEntry_) next; +} CIPServiceEntry; + +typedef struct ENIPTransaction_ +{ + struct ENIPState_ *enip; + uint16_t tx_num; /**< internal: id */ + uint16_t tx_id; /**< transaction id */ + uint16_t service_count; /**< transaction id */ + + ENIPEncapHdr header; //encapsulation header + ENIPEncapDataHdr encap_data_header; //encapsulation data header + ENIPEncapAddresItem encap_addr_item; //encapsulated address item + ENIPEncapDataItem encap_data_item; //encapsulated data item + + TAILQ_HEAD(, CIPServiceEntry_) service_list; /**< list for CIP */ + + AppLayerDecoderEvents *decoder_events; /**< per tx events */ + + TAILQ_ENTRY(ENIPTransaction_) next; + DetectEngineState *de_state; +} ENIPTransaction; + +/** \brief Per flow ENIP state container */ +typedef struct ENIPState_ +{ + TAILQ_HEAD(, ENIPTransaction_) tx_list; /**< transaction list */ + ENIPTransaction *curr; /**< ptr to current tx */ + ENIPTransaction *iter; + uint64_t transaction_max; + uint64_t tx_with_detect_state_cnt; + + uint16_t events; + uint16_t givenup; + + /* used by TCP only */ + uint16_t offset; + uint16_t record_len; + uint8_t *buffer; +} ENIPState; + +int DecodeENIPPDU(uint8_t *input, uint32_t input_len, + ENIPTransaction *enip_data); +int DecodeCommonPacketFormatPDU(uint8_t *input, uint32_t input_len, + ENIPTransaction *enip_data, uint16_t offset); +int DecodeCIPPDU(uint8_t *input, uint32_t input_len, + ENIPTransaction *enip_data, uint16_t offset); +int DecodeCIPRequestPDU(uint8_t *input, uint32_t input_len, + ENIPTransaction *enip_data, uint16_t offset); +int DecodeCIPResponsePDU(uint8_t *input, uint32_t input_len, + ENIPTransaction *enip_data, uint16_t offset); +int DecodeCIPRequestPathPDU(uint8_t *input, uint32_t input_len, + CIPServiceEntry *node, uint16_t offset); +int DecodeCIPRequestMSPPDU(uint8_t *input, uint32_t input_len, + ENIPTransaction *enip_data, uint16_t offset); +int DecodeCIPResponseMSPPDU(uint8_t *input, uint32_t input_len, + ENIPTransaction *enip_data, uint16_t offset); + +#endif /* __APP_LAYER_ENIP_COMMON_H__ */ diff --git a/src/app-layer-enip.c b/src/app-layer-enip.c new file mode 100644 index 0000000000..db50221a4c --- /dev/null +++ b/src/app-layer-enip.c @@ -0,0 +1,605 @@ +/* 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 "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-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-byte.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. + */ +int ENIPGetAlstateProgress(void *tx, uint8_t direction) +{ + return 1; +} + +/** \brief get value for 'complete' status in ENIP + * + * For ENIP we use a simple bool. + */ +int ENIPGetAlstateProgressCompletionStatus(uint8_t direction) +{ + return 1; +} + +DetectEngineState *ENIPGetTxDetectState(void *vtx) +{ + ENIPTransaction *tx = (ENIPTransaction *)vtx; + return tx->de_state; +} + +int ENIPSetTxDetectState(void *state, void *vtx, DetectEngineState *s) +{ + ENIPTransaction *tx = (ENIPTransaction *)vtx; + tx->de_state = s; + return 0; +} + +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; +} + +uint64_t ENIPGetTxCnt(void *alstate) { + return ((uint64_t) ((ENIPState *) alstate)->transaction_max); +} + +AppLayerDecoderEvents *ENIPGetEvents(void *state, uint64_t id) { + ENIPState *enip = (ENIPState *) state; + ENIPTransaction *tx; + + if (enip->curr && enip->curr->tx_num == (id + 1)) + return enip->curr->decoder_events; + + TAILQ_FOREACH(tx, &enip->tx_list, next) { + if (tx->tx_num == (id+1)) + return tx->decoder_events; + } + + return NULL; +} + +int ENIPHasEvents(void *state) { + return (((ENIPState *) state)->events > 0); +} + +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(SC_ERR_INVALID_ENUM_MAP, "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; +} + +/** \brief Allocate enip state + * + * return state + */ +void *ENIPStateAlloc(void) +{ + SCLogDebug("ENIPStateAlloc \n"); + 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 \n"); + 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->decoder_events); + + if (tx->de_state != NULL) + { + DetectEngineStateFree(tx->de_state); + + state->tx_with_detect_state_cnt--; + } + + if (state->iter == tx) + state->iter = NULL; + + SCFree(tx); + SCReturn; +} + +/** \brief Free enip state + * + */ +void ENIPStateFree(void *s) +{ + SCEnter(); + SCLogDebug("ENIPStateFree \n"); + 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 \n"); + 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 + */ +void ENIPStateTransactionFree(void *state, uint64_t tx_id) +{ + SCEnter(); + SCLogDebug("ENIPStateTransactionFree \n"); + 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->decoder_events != NULL) + { + if (tx->decoder_events->cnt <= enip_state->events) + enip_state->events -= tx->decoder_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 int ENIPParse(Flow *f, void *state, AppLayerParserState *pstate, + uint8_t *input, uint32_t input_len, void *local_data) +{ + SCEnter(); + ENIPState *enip = (ENIPState *) state; + ENIPTransaction *tx; + + if (input == NULL && AppLayerParserStateIssetFlag(pstate, + APP_LAYER_PARSER_EOF)) + { + SCReturnInt(1); + } else if (input == NULL || input_len == 0) + { + SCReturnInt(-1); + } + + while (input_len > 0) + { + tx = ENIPTransactionAlloc(enip); + if (tx == NULL) + SCReturnInt(0); + + SCLogDebug("ENIPParse input len %d\n", input_len); + DecodeENIPPDU(input, input_len, tx); + uint32_t pkt_len = tx->header.length + sizeof(ENIPEncapHdr); + SCLogDebug("ENIPParse packet len %d\n", pkt_len); + if (pkt_len > input_len) + { + SCLogDebug("Invalid packet length \n"); + break; + } + + input += pkt_len; + input_len -= pkt_len; + //SCLogDebug("remaining %d\n", input_len); + + if (input_len < sizeof(ENIPEncapHdr)) + { + //SCLogDebug("Not enough data\n"); //not enough data for ENIP + break; + } + } + + return 1; +} + + + +static uint16_t ENIPProbingParser(uint8_t *input, uint32_t input_len, + uint32_t *offset) +{ + // SCLogDebug("ENIPProbingParser %d\n", input_len); + if (input_len < sizeof(ENIPEncapHdr)) + { + printf("Length too small to be a ENIP header \n"); + return ALPROTO_UNKNOWN; + } + + return ALPROTO_ENIP; +} + +/** + * \brief Function to register the ENIP protocol parsers and other functions + */ +void RegisterENIPUDPParsers(void) +{ + SCEnter(); + char *proto_name = "enip"; + + if (AppLayerProtoDetectConfProtoDetectionEnabled("udp", proto_name)) + { + AppLayerProtoDetectRegisterProtocol(ALPROTO_ENIP, proto_name); + + if (RunmodeIsUnittests()) + { + AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818", ALPROTO_ENIP, + 0, sizeof(ENIPEncapHdr), STREAM_TOSERVER, ENIPProbingParser); + + AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818", ALPROTO_ENIP, + 0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT, ENIPProbingParser); + + } else + { + if (!AppLayerProtoDetectPPParseConfPorts("udp", IPPROTO_UDP, + proto_name, ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), + 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); + + AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818", + ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT, + ENIPProbingParser); + } + } + + } else + { + printf("Protocol detection and parser disabled for %s protocol.", + proto_name); + return; + } + + if (AppLayerParserConfParserEnabled("udp", proto_name)) + { + AppLayerParserRegisterParser(IPPROTO_UDP, ALPROTO_ENIP, + STREAM_TOSERVER, ENIPParse); + AppLayerParserRegisterParser(IPPROTO_UDP, ALPROTO_ENIP, + STREAM_TOCLIENT, ENIPParse); + + AppLayerParserRegisterStateFuncs(IPPROTO_UDP, ALPROTO_ENIP, + ENIPStateAlloc, ENIPStateFree); + + AppLayerParserRegisterGetEventsFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetEvents); + AppLayerParserRegisterHasEventsFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPHasEvents); + + AppLayerParserRegisterDetectStateFuncs(IPPROTO_UDP, ALPROTO_ENIP, NULL, + ENIPGetTxDetectState, ENIPSetTxDetectState); + + AppLayerParserRegisterGetTx(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTx); + AppLayerParserRegisterGetTxCnt(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTxCnt); + AppLayerParserRegisterTxFreeFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPStateTransactionFree); + + AppLayerParserRegisterGetStateProgressFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetAlstateProgress); + AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_ENIP, ENIPGetAlstateProgressCompletionStatus); + + AppLayerParserRegisterGetEventInfo(IPPROTO_UDP, ALPROTO_ENIP, ENIPStateGetEventInfo); + + 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(); + char *proto_name = "enip"; + + if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) + { + AppLayerProtoDetectRegisterProtocol(ALPROTO_ENIP, proto_name); + + if (RunmodeIsUnittests()) + { + AppLayerProtoDetectPPRegister(IPPROTO_TCP, "44818", ALPROTO_ENIP, + 0, sizeof(ENIPEncapHdr), STREAM_TOSERVER, ENIPProbingParser); + + AppLayerProtoDetectPPRegister(IPPROTO_TCP, "44818", ALPROTO_ENIP, + 0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT, ENIPProbingParser); + + } else + { + if (!AppLayerProtoDetectPPParseConfPorts("tcp", IPPROTO_TCP, + proto_name, ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), + ENIPProbingParser)) + { + SCLogDebug( + "no ENIP TCP config found enabling ENIP detection on port 44818."); + + AppLayerProtoDetectPPRegister(IPPROTO_TCP, "44818", + ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), STREAM_TOSERVER, + ENIPProbingParser); + AppLayerProtoDetectPPRegister(IPPROTO_TCP, "44818", + ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT, + ENIPProbingParser); + } + } + + } 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, ENIPParse); + AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_ENIP, + STREAM_TOCLIENT, ENIPParse); + AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_ENIP, + ENIPStateAlloc, ENIPStateFree); + + AppLayerParserRegisterGetEventsFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetEvents); + AppLayerParserRegisterHasEventsFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPHasEvents); + + AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_ENIP, NULL, + ENIPGetTxDetectState, ENIPSetTxDetectState); + + AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTx); + AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTxCnt); + AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPStateTransactionFree); + + AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetAlstateProgress); + AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_ENIP, ENIPGetAlstateProgressCompletionStatus); + + AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_ENIP, ENIPStateGetEventInfo); + + AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, + ALPROTO_ENIP, STREAM_TOSERVER | STREAM_TOCLIENT); + } else + { + SCLogInfo( + "Parsed disabled for %s protocol. Protocol detection" "still on.", + proto_name); + } + +#ifdef UNITTESTS + AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_ENIP, ENIPParserRegisterTests); +#endif + + SCReturn; +} + +/* UNITTESTS */ +#ifdef UNITTESTS +#include "app-layer-parser.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "flow-util.h" +#include "stream-tcp.h" +#include "util-unittest.h" +#include "util-unittest-helper.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 + */ +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; + + 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 */ +} diff --git a/src/app-layer-enip.h b/src/app-layer-enip.h new file mode 100644 index 0000000000..d31cfb3aad --- /dev/null +++ b/src/app-layer-enip.h @@ -0,0 +1,35 @@ +/* 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 + */ + +#ifndef __APP_LAYER_ENIP_H__ +#define __APP_LAYER_ENIP_H__ + +#include "decode.h" +#include "detect-engine-state.h" +#include "queue.h" + +void RegisterENIPUDPParsers(void); +void RegisterENIPTCPParsers(void); +void ENIPParserRegisterTests(void); + +#endif /* __APP_LAYER_ENIP_H__ */ diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index 36a2792f5b..410e1adfeb 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -58,6 +58,7 @@ #include "app-layer-dns-udp.h" #include "app-layer-dns-tcp.h" #include "app-layer-modbus.h" +#include "app-layer-enip.h" #include "app-layer-template.h" #include "conf.h" @@ -1191,6 +1192,8 @@ void AppLayerParserRegisterProtocolParsers(void) RegisterDNSUDPParsers(); RegisterDNSTCPParsers(); RegisterModbusParsers(); + RegisterENIPUDPParsers(); + RegisterENIPTCPParsers(); RegisterTemplateParsers(); /** IMAP */ diff --git a/src/app-layer-protos.c b/src/app-layer-protos.c index e8875643f8..50d1342c49 100644 --- a/src/app-layer-protos.c +++ b/src/app-layer-protos.c @@ -75,6 +75,9 @@ const char *AppProtoToString(AppProto alproto) case ALPROTO_MODBUS: proto_name = "modbus"; break; + case ALPROTO_ENIP: + proto_name = "enip"; + break; case ALPROTO_TEMPLATE: proto_name = "template"; break; diff --git a/src/app-layer-protos.h b/src/app-layer-protos.h index aff90e9bba..907bd8629b 100644 --- a/src/app-layer-protos.h +++ b/src/app-layer-protos.h @@ -42,6 +42,7 @@ enum AppProtoEnum { ALPROTO_DNS, ALPROTO_MODBUS, + ALPROTO_ENIP, ALPROTO_TEMPLATE, /* used by the probing parser when alproto detection fails diff --git a/src/detect-cipservice.c b/src/detect-cipservice.c new file mode 100644 index 0000000000..1e738e6b57 --- /dev/null +++ b/src/detect-cipservice.c @@ -0,0 +1,474 @@ +/* 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 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" + + +/* + * CIP SERVICE CODE + */ + +/** + * \brief CIP Service Detect Prototypes + */ +static int DetectCipServiceSetup(DetectEngineCtx *, Signature *, char *); +static void DetectCipServiceFree(void *); +static void DetectCipServiceRegisterTests(void); + +/** + * \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].AppLayerMatch = NULL; + sigmatch_table[DETECT_CIPSERVICE].Setup = DetectCipServiceSetup; + sigmatch_table[DETECT_CIPSERVICE].Free = DetectCipServiceFree; + sigmatch_table[DETECT_CIPSERVICE].RegisterTests + = DetectCipServiceRegisterTests; + + 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 + */ +DetectCipServiceData *DetectCipServiceParse(char *rulestr) +{ + const char delims[] = ","; + DetectCipServiceData *cipserviced = NULL; + + //SCLogDebug("DetectCipServiceParse - rule string %s\n", rulestr); + + 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]; + int i = 0; + + token = strtok_r(rulestr, delims, &save); + while (token != NULL) + { + if (i > 2) //for now only need 3 parameters + { + printf("DetectEnipCommandParse: Too many parameters\n"); + goto error; + } + + if (i < 2) //if on service or class + { + if (!isdigit((int) *token)) + { + printf("DetectCipServiceParse - Parameter Error %s\n", token); + goto error; + } + } else //if on attribute + { + + if (token[0] == '!') + { + cipserviced->matchattribute = 0; + token++; + } + + if (!isdigit((int) *token)) + { + printf("DetectCipServiceParse - Attribute Error %s\n", token); + goto error; + } + + } + + unsigned long num = atol(token); + if ((num > MAX_CIP_SERVICE) && (i == 0))//if service greater than 7 bit + { + printf("DetectEnipCommandParse: Invalid CIP service %lu\n", num); + goto error; + } else if ((num > MAX_CIP_CLASS) && (i == 1))//if service greater than 16 bit + { + printf("DetectEnipCommandParse: Invalid CIP class %lu\n", num); + goto error; + } else if ((num > MAX_CIP_ATTRIBUTE) && (i == 2))//if service greater than 16 bit + { + printf("DetectEnipCommandParse: Invalid CIP attribute %lu\n", num); + goto error; + } + + sscanf(token, "%d", &var); + input[i++] = var; + + token = strtok_r(NULL, delims, &save); + } + + cipserviced->cipservice = input[0]; + cipserviced->cipclass = input[1]; + cipserviced->cipattribute = input[2]; + cipserviced->tokens = i; + + SCLogDebug("DetectCipServiceParse - tokens %d\n", cipserviced->tokens); + SCLogDebug("DetectCipServiceParse - service %d\n", cipserviced->cipservice); + SCLogDebug("DetectCipServiceParse - class %d\n", cipserviced->cipclass); + SCLogDebug("DetectCipServiceParse - match attribute %d\n", + cipserviced->matchattribute); + SCLogDebug("DetectCipServiceParse - attribute %d\n", + cipserviced->cipattribute); + + SCReturnPtr(cipserviced, "DetectENIPFunction"); + +error: + if (cipserviced) + SCFree(cipserviced); + printf("DetectCipServiceParse - Error Parsing Parameters\n"); + 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, + char *rulestr) +{ + SCEnter(); + + DetectCipServiceData *cipserviced = NULL; + SigMatch *sm = NULL; + + if (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_ENIP) + { + SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, + "rule contains conflicting keywords."); + goto error; + } + + cipserviced = DetectCipServiceParse(rulestr); + if (cipserviced == NULL) + goto error; + + sm = SigMatchAlloc(); + if (sm == NULL) + goto error; + + sm->type = DETECT_CIPSERVICE; + sm->ctx = (void *) cipserviced; + + s->alproto = ALPROTO_ENIP; + + SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_CIP_MATCH); + + SCReturnInt(0); + +error: + if (cipserviced != NULL) + DetectCipServiceFree(cipserviced); + if (sm != NULL) + SCFree(sm); + printf("DetectCipServiceSetup - Error\n"); + + SCReturnInt(-1); +} + +/** + * \brief this function will free memory associated with DetectCipServiceData + * + * \param ptr pointer to DetectCipServiceData + */ +void DetectCipServiceFree(void *ptr) +{ + DetectCipServiceData *cipserviced = (DetectCipServiceData *) ptr; + SCFree(cipserviced); +} + +#ifdef UNITTESTS + +/** + * \test Test CIP Command parameter parsing + */ +static int DetectCipServiceParseTest01 (void) +{ + uint8_t res = 1; + + /*DetectCipServiceData *cipserviced = NULL; + cipserviced = DetectCipServiceParse("1"); + if (cipserviced != NULL) + { + if (cipserviced->cipservice == 1) + { + res = 1; + } + + DetectCipServiceFree(cipserviced); + } + */ + return res; +} + +/** + * \test Test CIP Service signature + */ +static int DetectCipServiceSignatureTest01 (void) +{ + uint8_t res = 0; + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) + goto end; + + Signature *sig = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any (cip_service:1; sid:1; rev:1;)"); + if (sig == NULL) + { + printf("parsing signature failed: "); + goto end; + } + + /* if we get here, all conditions pass */ + res = 1; +end: + if (de_ctx != NULL) + DetectEngineCtxFree(de_ctx); + return res; +} + +#endif /* UNITTESTS */ + +/** + * \brief this function registers unit tests for DetectCipService + */ +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 *, char *); +static void DetectEnipCommandFree(void *); +static void DetectEnipCommandRegisterTests(void); + +/** + * \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].AppLayerMatch = NULL; + sigmatch_table[DETECT_ENIPCOMMAND].Setup = DetectEnipCommandSetup; + sigmatch_table[DETECT_ENIPCOMMAND].Free = DetectEnipCommandFree; + sigmatch_table[DETECT_ENIPCOMMAND].RegisterTests + = DetectEnipCommandRegisterTests; + +} + +/** + * \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 + */ +DetectEnipCommandData *DetectEnipCommandParse(char *rulestr) +{ + DetectEnipCommandData *enipcmdd = NULL; + + enipcmdd = SCMalloc(sizeof(DetectEnipCommandData)); + if (unlikely(enipcmdd == NULL)) + goto error; + + if (isdigit((int) *rulestr)) + { + unsigned long cmd = atol(rulestr); + if (cmd > MAX_ENIP_CMD) //if command greater than 16 bit + { + //printf("DetectEnipCommandParse: Invalid ENIP command %lu\n", cmd); + goto error; + } + + enipcmdd->enipcommand = (uint16_t) atoi(rulestr); + + } else + { + goto error; + } + + return enipcmdd; + +error: + if (enipcmdd) + SCFree(enipcmdd); + //printf("DetectEnipCommandParse - Error Parsing Parameters\n"); + 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, + char *rulestr) +{ + DetectEnipCommandData *enipcmdd = NULL; + SigMatch *sm = NULL; + + if (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_ENIP) + { + SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, + "rule contains conflicting keywords."); + goto error; + } + + enipcmdd = DetectEnipCommandParse(rulestr); + if (enipcmdd == NULL) + goto error; + + sm = SigMatchAlloc(); + if (sm == NULL) + goto error; + + sm->type = DETECT_ENIPCOMMAND; + sm->ctx = (void *) enipcmdd; + + s->alproto = ALPROTO_ENIP; + SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_ENIP_MATCH); + + SCReturnInt(0); + +error: + if (enipcmdd != NULL) + DetectEnipCommandFree(enipcmdd); + if (sm != NULL) + SCFree(sm); + printf("DetectEnipCommandSetup - Error\n"); + SCReturnInt(-1); +} + +/** + * \brief this function will free memory associated with DetectEnipCommandData + * + * \param ptr pointer to DetectEnipCommandData + */ +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 + */ +void DetectEnipCommandRegisterTests(void) +{ +#ifdef UNITTESTS + UtRegisterTest("DetectEnipCommandParseTest01", + DetectEnipCommandParseTest01); + UtRegisterTest("DetectEnipCommandSignatureTest01", + DetectEnipCommandSignatureTest01); +#endif /* UNITTESTS */ +} diff --git a/src/detect-cipservice.h b/src/detect-cipservice.h new file mode 100644 index 0000000000..50bd9de6f1 --- /dev/null +++ b/src/detect-cipservice.h @@ -0,0 +1,100 @@ +/* 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 + */ + +#ifndef _DETECT_CIPSERVICE_H +#define _DETECT_CIPSERVICE_H + +#include "app-layer-protos.h" +#include "app-layer-parser.h" +#include "flow.h" +#include "queue.h" +#include "app-layer-enip-common.h" + +#define ENIP_PORT 44818 //standard EtherNet/IP port + +/** + * CIP Service rule data structure + */ +typedef struct DetectCipServiceData_ +{ + uint8_t cipservice; /* cip service type */ + uint16_t cipclass; + uint16_t cipattribute; + uint8_t matchattribute; /* whether to match on attribute*/ + uint8_t tokens; /* number of parameters*/ +} DetectCipServiceData; + +/** + * ENIP Command rule data structure + */ +typedef struct DetectEnipCommandData_ +{ + uint16_t enipcommand; /* enip command */ +} DetectEnipCommandData; + +void DetectCipServiceRegister(void); +void DetectEnipCommandRegister(void); + +/** + * link list node for storing CIP service data + */ +typedef struct CIPServiceData_ +{ + uint8_t service; //cip service + union + { + struct + { + uint8_t path_size; //cip path size + uint16_t path_offset; //offset to cip path + } request; + struct + { + uint8_t status; + } response; + }; + struct CIPServiceData* next; +} CIPServiceData; + +/** + * ENIP data structure + */ +typedef struct ENIPData_ +{ + int direction; + ENIPEncapHdr header; //encapsulation header + ENIPEncapDataHdr encap_data_header; //encapsulation data header + ENIPEncapAddresItem encap_addr_item; //encapsulated address item + ENIPEncapDataItem encap_data_item; //encapsulated data item + + CIPServiceData* service_head; //head of cip service data list + CIPServiceData* service_tail; //tail of cip service data list + +} ENIPData; + +/** + * Add new CIPServiceData node to link list + */ +CIPServiceData *CreateCIPServiceData(ENIPData *enip_data); + +#endif /* _DETECT_CIPSERVICE_H */ diff --git a/src/detect-engine-enip.c b/src/detect-engine-enip.c new file mode 100644 index 0000000000..4efcb49fd2 --- /dev/null +++ b/src/detect-engine-enip.c @@ -0,0 +1,375 @@ +/* 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 + * + * Based on detect-engine-modbus.c + */ + +#include "suricata-common.h" + +#include "app-layer.h" + +#include "detect.h" +#include "detect-cipservice.h" +#include "detect-engine-enip.h" + +#include "flow.h" + +#include "util-debug.h" + +/** + * \brief Print fields from ENIP Packet + * @param enip_data + */ +void PrintENIPAL(ENIPTransaction *enip_data) +{ + SCLogDebug("============================================\n"); + SCLogDebug("ENCAP HEADER cmd 0x%x, length %d, session 0x%x, status 0x%x\n", + enip_data->header.command, enip_data->header.length, + enip_data->header.session, enip_data->header.status); + //SCLogDebug("context 0x%x option 0x%x\n", enip_data->header.context, enip_data->header.option); + SCLogDebug("ENCAP DATA HEADER handle 0x%x, timeout %d, count %d\n", + 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 \n", + enip_data->encap_addr_item.type, enip_data->encap_addr_item.length); + SCLogDebug("ENCAP DATA ITEM type 0x%x, length %d sequence 0x%x\n", + 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\n", count, svc->service); + count++; + } +} + +/** + * \brief Matches the rule to the CIP segment in ENIP Packet + * @param svc - the CIP service entry + * * @param cipserviced - the CIP service rule + */ +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 transation + * * @param cipserviced - the CIP service rule + */ + +int CIPServiceMatch(ENIPTransaction *enip_data, + DetectCipServiceData *cipserviced) +{ + int count = 1; + CIPServiceEntry *svc = NULL; + //SCLogDebug("CIPServiceMatchAL\n"); + TAILQ_FOREACH(svc, &enip_data->service_list, next) + { + SCLogDebug("CIPServiceMatchAL service #%d : 0x%x dir %d \n", count, svc->service, svc->direction); + + if (cipserviced->cipservice == svc->service) + { // compare service + //SCLogDebug("Rule Match for cip service %d\n",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\n"); + return 1; + } + } + count++; + } + 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 + */ +int DetectEngineInspectCIP(ThreadVars *tv, DetectEngineCtx *de_ctx, + DetectEngineThreadCtx *det_ctx, Signature *s, Flow *f, uint8_t flags, + void *alstate, void *txv, uint64_t tx_id) +{ + SCEnter(); + + + ENIPTransaction *tx = (ENIPTransaction *) txv; + SigMatch *sm = s->sm_lists[DETECT_SM_LIST_CIP_MATCH]; + DetectCipServiceData *cipserviced = (DetectCipServiceData *) sm->ctx; + + if (cipserviced == NULL) + { + SCLogDebug("no cipservice state, no match"); + SCReturnInt(0); + } + // SCLogDebug("DetectEngineInspectCIP %d\n", cipserviced->cipservice); + + if (CIPServiceMatch(tx, cipserviced) == 1) + { + // SCLogDebug("DetectCIPServiceMatchAL found\n"); + 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 + */ + +int DetectEngineInspectENIP(ThreadVars *tv, DetectEngineCtx *de_ctx, + DetectEngineThreadCtx *det_ctx, Signature *s, Flow *f, uint8_t flags, + void *alstate, void *txv, uint64_t tx_id) +{ + SCEnter(); + + ENIPTransaction *tx = (ENIPTransaction *) txv; + SigMatch *sm = s->sm_lists[DETECT_SM_LIST_ENIP_MATCH]; + DetectEnipCommandData *enipcmdd = (DetectEnipCommandData *) sm->ctx; + + if (enipcmdd == NULL) + { + SCLogDebug("no enipcommand state, no match"); + SCReturnInt(0); + } + + //SCLogDebug("DetectEngineInspectENIP %d, %d\n", enipcmdd->enipcommand, tx->header.command); + + if (enipcmdd->enipcommand == tx->header.command) + { + // SCLogDebug("DetectENIPCommandMatchAL found!\n"); + SCReturnInt(1); + } + + SCReturnInt(0); +} + +#ifdef UNITTESTS /* UNITTESTS */ +#include "app-layer-parser.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "flow-util.h" +#include "stream-tcp.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" + +static uint8_t listIdentity[] = {/* List ID */ 0x00, 0x63, + /* 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,}; + +/** \test Test code function. */ +static int DetectEngineInspectENIPTest01(void) +{ + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + DetectEngineThreadCtx *det_ctx = NULL; + DetectEngineCtx *de_ctx = NULL; + Flow f; + Packet *p = NULL; + Signature *s = NULL; + TcpSession ssn; + ThreadVars tv; + + memset(&tv, 0, sizeof(ThreadVars)); + memset(&f, 0, sizeof(Flow)); + memset(&ssn, 0, sizeof(TcpSession)); + + p = UTHBuildPacket(listIdentity, sizeof(listIdentity), IPPROTO_TCP); + FAIL_IF_NULL(p); + + FLOW_INITIALIZE(&f); + f.alproto = ALPROTO_ENIP; + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + f.flags |= FLOW_IPV4; + + p->flow = &f; + p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST; + p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED; + + StreamTcpInitConfig(TRUE); + + de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + + de_ctx->flags |= DE_QUIET; + s = de_ctx->sig_list = SigInit(de_ctx, "alert enip any any -> any any " + "(msg:\"Testing enip command\"; " + "enipcommand:99 ; sid:1;)"); + FAIL_IF_NULL(s); + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx); + + 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); + + /* do detect */ + SigMatchSignatures(&tv, de_ctx, det_ctx, p); + + FAIL_IF(!(PacketAlertCheck(p, 1))); + + AppLayerParserThreadCtxFree(alp_tctx); + DetectEngineThreadCtxDeinit(&tv, det_ctx); + DetectEngineCtxFree(de_ctx); + + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + UTHFreePacket(p); + + PASS; +} + +#endif /* UNITTESTS */ + +void DetectEngineInspectENIPRegisterTests(void) +{ +#ifdef UNITTESTS + UtRegisterTest("DetectEngineInspectENIPTest01", DetectEngineInspectENIPTest01); +#endif /* UNITTESTS */ + return; +} diff --git a/src/detect-engine-enip.h b/src/detect-engine-enip.h new file mode 100644 index 0000000000..1d701d414d --- /dev/null +++ b/src/detect-engine-enip.h @@ -0,0 +1,35 @@ +/* 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 + */ + +#ifndef __DETECT_ENGINE_ENIP_H__ +#define __DETECT_ENGINE_ENIP_H__ + +int DetectEngineInspectCIP(ThreadVars *, DetectEngineCtx *de_ctx, + DetectEngineThreadCtx *, Signature *, + Flow *, uint8_t, void *, void *, uint64_t); + +int DetectEngineInspectENIP(ThreadVars *, DetectEngineCtx *de_ctx, + DetectEngineThreadCtx *, Signature *, + Flow *, uint8_t, void *, void *, uint64_t); + +void DetectEngineInspectENIPRegisterTests(void); +#endif /* __DETECT_ENGINE_ENIP_H__ */ diff --git a/src/detect-engine.c b/src/detect-engine.c index 0a74031e2b..52bee574fe 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -2533,6 +2533,11 @@ const char *DetectSigmatchListEnumToString(enum DetectSigmatchListEnum type) case DETECT_SM_LIST_MODBUS_MATCH: return "modbus"; + case DETECT_SM_LIST_CIP_MATCH: + return "cip"; + case DETECT_SM_LIST_ENIP_MATCH: + return "enip"; + case DETECT_SM_LIST_BASE64_DATA: return "base64_data"; diff --git a/src/detect-parse.c b/src/detect-parse.c index f71d7166b8..5a476d10b9 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -1630,6 +1630,11 @@ static Signature *SigInitHelper(DetectEngineCtx *de_ctx, char *sigstr, if (sig->sm_lists[DETECT_SM_LIST_APP_EVENT]) sig->flags |= SIG_FLAG_STATE_MATCH; + if (sig->sm_lists[DETECT_SM_LIST_CIP_MATCH]) + sig->flags |= SIG_FLAG_STATE_MATCH; + if (sig->sm_lists[DETECT_SM_LIST_ENIP_MATCH]) + sig->flags |= SIG_FLAG_STATE_MATCH; + if (!(sig->init_flags & SIG_FLAG_INIT_FLOW)) { sig->flags |= SIG_FLAG_TOSERVER; sig->flags |= SIG_FLAG_TOCLIENT; diff --git a/src/detect.c b/src/detect.c index 900f824386..8dfd63f506 100644 --- a/src/detect.c +++ b/src/detect.c @@ -186,6 +186,7 @@ #include "detect-ssl-version.h" #include "detect-ssl-state.h" #include "detect-modbus.h" +#include "detect-cipservice.h" #include "action-globals.h" #include "tm-threads.h" @@ -2186,6 +2187,10 @@ PacketCreateMask(Packet *p, SignatureMask *mask, AppProto alproto, int has_state SCLogDebug("packet/flow has smtp state"); (*mask) |= SIG_MASK_REQUIRE_SMTP_STATE; break; + case ALPROTO_ENIP: + SCLogDebug("packet/flow has enip state"); + (*mask) |= SIG_MASK_REQUIRE_ENIP_STATE; + break; case ALPROTO_TEMPLATE: SCLogDebug("packet/flow has template state"); (*mask) |= SIG_MASK_REQUIRE_TEMPLATE_STATE; @@ -2431,6 +2436,10 @@ static int SignatureCreateMask(Signature *s) s->mask |= SIG_MASK_REQUIRE_SMTP_STATE; SCLogDebug("sig requires smtp state"); } + if (s->alproto == ALPROTO_ENIP) { + s->mask |= SIG_MASK_REQUIRE_ENIP_STATE; + SCLogDebug("sig requires enip state"); + } if (s->alproto == ALPROTO_TEMPLATE) { s->mask |= SIG_MASK_REQUIRE_TEMPLATE_STATE; SCLogDebug("sig requires template state"); @@ -2442,6 +2451,7 @@ static int SignatureCreateMask(Signature *s) (s->mask & SIG_MASK_REQUIRE_DNS_STATE) || (s->mask & SIG_MASK_REQUIRE_FTP_STATE) || (s->mask & SIG_MASK_REQUIRE_SMTP_STATE) || + (s->mask & SIG_MASK_REQUIRE_ENIP_STATE) || (s->mask & SIG_MASK_REQUIRE_TEMPLATE_STATE) || (s->mask & SIG_MASK_REQUIRE_TLS_STATE)) { @@ -4138,6 +4148,8 @@ void SigTableSetup(void) DetectDnsQueryRegister(); DetectModbusRegister(); + DetectCipServiceRegister(); + DetectEnipCommandRegister(); DetectTlsSniRegister(); DetectTlsIssuerRegister(); diff --git a/src/detect.h b/src/detect.h index b82c9bf53c..e71f722165 100644 --- a/src/detect.h +++ b/src/detect.h @@ -136,6 +136,9 @@ enum DetectSigmatchListEnum { DETECT_SM_LIST_MODBUS_MATCH, + DETECT_SM_LIST_CIP_MATCH, + DETECT_SM_LIST_ENIP_MATCH, + DETECT_SM_LIST_BASE64_DATA, DETECT_SM_LIST_TEMPLATE_BUFFER_MATCH, @@ -305,6 +308,7 @@ typedef struct DetectPort_ { #define SIG_MASK_REQUIRE_FTP_STATE (1<<11) #define SIG_MASK_REQUIRE_SMTP_STATE (1<<12) #define SIG_MASK_REQUIRE_TEMPLATE_STATE (1<<13) +#define SIG_MASK_REQUIRE_ENIP_STATE (1<<14) /* for now a uint8_t is enough */ #define SignatureMask uint16_t @@ -1319,6 +1323,8 @@ enum { DETECT_AL_TLS_CERT_ISSUER, DETECT_AL_TLS_CERT_SUBJECT, DETECT_AL_MODBUS, + DETECT_CIPSERVICE, + DETECT_ENIPCOMMAND, DETECT_XBITS, DETECT_BASE64_DECODE, diff --git a/src/suricata.c b/src/suricata.c index 596544902a..2376decb15 100644 --- a/src/suricata.c +++ b/src/suricata.c @@ -122,6 +122,7 @@ #include "app-layer-smtp.h" #include "app-layer-smb.h" #include "app-layer-modbus.h" +#include "app-layer-enip.h" #include "util-decode-der.h" #include "util-radix-tree.h" @@ -1158,6 +1159,8 @@ static TmEcode ParseCommandLine(int argc, char** argv, SCInstance *suri) {"afl-smb", required_argument, 0 , 0}, {"afl-modbus-request", required_argument, 0 , 0}, {"afl-modbus", required_argument, 0 , 0}, + {"afl-enip-request", required_argument, 0 , 0}, + {"afl-enip", required_argument, 0 , 0}, {"afl-mime", required_argument, 0 , 0}, {"afl-decoder-ppp", required_argument, 0 , 0}, @@ -1428,6 +1431,16 @@ static TmEcode ParseCommandLine(int argc, char** argv, SCInstance *suri) AppLayerParserSetup(); RegisterModbusParsers(); exit(AppLayerParserFromFile(ALPROTO_MODBUS, optarg)); + } else if(strcmp((long_opts[option_index]).name, "afl-enip-request") == 0) { + //printf("arg: //%s\n", optarg); + AppLayerParserSetup(); + RegisterENIPTCPParsers(); + exit(AppLayerParserRequestFromFile(ALPROTO_ENIP, optarg)); + } else if(strcmp((long_opts[option_index]).name, "afl-enip") == 0) { + //printf("arg: //%s\n", optarg); + AppLayerParserSetup(); + RegisterENIPTCPParsers(); + exit(AppLayerParserFromFile(ALPROTO_ENIP, optarg)); #endif #ifdef AFLFUZZ_MIME } else if(strcmp((long_opts[option_index]).name, "afl-mime") == 0) { diff --git a/src/util-error.c b/src/util-error.c index 7e13f67d8a..82c820a335 100644 --- a/src/util-error.c +++ b/src/util-error.c @@ -297,6 +297,7 @@ const char * SCErrorToString(SCError err) CASE_CODE (SC_ERR_DNS_CONFIG); CASE_CODE (SC_ERR_MODBUS_CONFIG); CASE_CODE (SC_ERR_CONF_YAML_ERROR); + CASE_CODE (SC_ERR_ENIP_CONFIG); CASE_CODE (SC_ERR_CONF_NAME_TOO_LONG); CASE_CODE (SC_ERR_APP_LAYER_PROTOCOL_DETECTION); CASE_CODE (SC_ERR_PCIE_INIT_FAILED); diff --git a/src/util-error.h b/src/util-error.h index 6aef68cbb9..1b63e43554 100644 --- a/src/util-error.h +++ b/src/util-error.h @@ -319,6 +319,7 @@ typedef enum { SC_ERR_INVALID_HASH, SC_ERR_NO_SHA1_SUPPORT, SC_ERR_NO_SHA256_SUPPORT, + SC_ERR_ENIP_CONFIG, } SCError; const char *SCErrorToString(SCError); diff --git a/suricata.yaml.in b/suricata.yaml.in index a0a78eeb08..a682da02dd 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -831,6 +831,8 @@ app-layer: # response-body-limit: 4096 # double-decode-path: no # double-decode-query: no + enip: + enabled: no # Limit for the maximum number of asn1 frames to decode (default 256) asn1-max-frames: 256