]> git.ipfire.org Git - people/ms/suricata.git/commitdiff
Adding SCADA EtherNet/IP and CIP protocol support
authorkwong <kwong@solananetworks.com>
Thu, 29 Sep 2016 17:15:54 +0000 (13:15 -0400)
committerVictor Julien <victor@inliniac.net>
Fri, 30 Sep 2016 05:58:30 +0000 (07:58 +0200)
Add support for the ENIP/CIP Industrial protocol

This is an app layer implementation which uses the "enip" protocol
and "cip_service" and "enip_command" keywords

Implements AFL entry points

22 files changed:
doc/userguide/rules/enip-keyword.rst [new file with mode: 0644]
src/Makefile.am
src/app-layer-detect-proto.c
src/app-layer-enip-common.c [new file with mode: 0644]
src/app-layer-enip-common.h [new file with mode: 0644]
src/app-layer-enip.c [new file with mode: 0644]
src/app-layer-enip.h [new file with mode: 0644]
src/app-layer-parser.c
src/app-layer-protos.c
src/app-layer-protos.h
src/detect-cipservice.c [new file with mode: 0644]
src/detect-cipservice.h [new file with mode: 0644]
src/detect-engine-enip.c [new file with mode: 0644]
src/detect-engine-enip.h [new file with mode: 0644]
src/detect-engine.c
src/detect-parse.c
src/detect.c
src/detect.h
src/suricata.c
src/util-error.c
src/util-error.h
suricata.yaml.in

diff --git a/doc/userguide/rules/enip-keyword.rst b/doc/userguide/rules/enip-keyword.rst
new file mode 100644 (file)
index 0000000..d37f268
--- /dev/null
@@ -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:<value>
+  cip_service:<value(s)>
+  enip_command:<value>, cip_service:<value(s)>
+
+
+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
index 92b9472e1afc3a20c7467089de1fc161631989a8..d636765cff2f65f5134757c84b9aa3b8d8724b18 100644 (file)
@@ -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 \
index cca1ae7a8f55b2a6a99df90e01e4f5c48b914f2a..74e22d4ae08aac735f4ee05029ccbfe1b1b8835e 100644 (file)
@@ -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 (file)
index 0000000..72ebc00
--- /dev/null
@@ -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 <kwong@solananetworks.com>
+ *
+ * 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 (file)
index 0000000..77777c8
--- /dev/null
@@ -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 <kwong@solananetworks.com>
+ */
+
+#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 (file)
index 0000000..db50221
--- /dev/null
@@ -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 <kwong@solananetworks.com>
+ *
+ * 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 (file)
index 0000000..d31cfb3
--- /dev/null
@@ -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 <kwong@solananetworks.com>
+ */
+
+#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__ */
index 36a2792f5b2b2b474eb44585ab94f216b1bf2ec2..410e1adfebdeb7c4685e4462ee8e1fb0d14d3f07 100644 (file)
@@ -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 */
index e8875643f8793ae4b28a9bc6dd2df382314f4faa..50d1342c492394e68be8b14c80753b457bfeebc1 100644 (file)
@@ -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;
index aff90e9bbaf74e45c245d26d01e8d1b35a75e511..907bd8629b0e0f9b596db71de399a176b20b9692 100644 (file)
@@ -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 (file)
index 0000000..1e738e6
--- /dev/null
@@ -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 <kwong@solananetworks.com>
+ *
+ * Set up ENIP Commnad and CIP Service rule parsing and entry point for matching
+ */
+
+#include "suricata-common.h"
+#include "util-unittest.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "util-byte.h"
+
+#include "detect-cipservice.h"
+
+
+/*
+ * 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 (file)
index 0000000..50bd9de
--- /dev/null
@@ -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 <kwong@solananetworks.com>
+ */
+
+#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 (file)
index 0000000..4efcb49
--- /dev/null
@@ -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 <kwong@solananetworks.com>
+ *
+ *  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 (file)
index 0000000..1d701d4
--- /dev/null
@@ -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 <kwong@solananetworks.com>
+ */
+
+#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__ */
index 0a74031e2b76323c441c8eb252263ebc628471fc..52bee574fea1f4171e2af9f62b309d1129ce99e4 100644 (file)
@@ -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";
 
index f71d7166b8be998d1cc913ab95877cbddac4c953..5a476d10b93ad7fce2a7f5b18f4ad68359a56cd9 100644 (file)
@@ -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;
index 900f824386198f03241bed3043ca220c43bb4eaf..8dfd63f506f4cef301eba34e67d0b65dec4bd0b1 100644 (file)
 #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();
index b82c9bf53c8f8210524a7a902590a8a6470ffa91..e71f722165025a759c79ac39c9b421e9c0e61b1d 100644 (file)
@@ -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,
index 596544902a84c6222bf431cfbe1adcb31575c911..2376decb158f81b6b4c3ea3cfe72018f03cb2943 100644 (file)
 #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) {
index 7e13f67d8a6ec443e0370a279f703aa6fe22a4d2..82c820a335aa936114c3982f10451fe718979476 100644 (file)
@@ -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);
index 6aef68cbb9bca5f21a774f31edabacece932b424..1b63e43554e20d1e26159a88eef62452c390461d 100644 (file)
@@ -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);
index a0a78eeb08438d0084bd2c54927bca5122bb5c95..a682da02dd35b2bf9731a8eccb2ff3a2118e8efa 100644 (file)
@@ -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