--- /dev/null
+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
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 \
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 \
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 \
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
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
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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__ */
--- /dev/null
+/* 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 */
+}
--- /dev/null
+/* 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__ */
#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"
RegisterDNSUDPParsers();
RegisterDNSTCPParsers();
RegisterModbusParsers();
+ RegisterENIPUDPParsers();
+ RegisterENIPTCPParsers();
RegisterTemplateParsers();
/** IMAP */
case ALPROTO_MODBUS:
proto_name = "modbus";
break;
+ case ALPROTO_ENIP:
+ proto_name = "enip";
+ break;
case ALPROTO_TEMPLATE:
proto_name = "template";
break;
ALPROTO_DNS,
ALPROTO_MODBUS,
+ ALPROTO_ENIP,
ALPROTO_TEMPLATE,
/* used by the probing parser when alproto detection fails
--- /dev/null
+/* 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 */
+}
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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__ */
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";
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;
#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"
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;
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");
(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))
{
DetectDnsQueryRegister();
DetectModbusRegister();
+ DetectCipServiceRegister();
+ DetectEnipCommandRegister();
DetectTlsSniRegister();
DetectTlsIssuerRegister();
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,
#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
DETECT_AL_TLS_CERT_ISSUER,
DETECT_AL_TLS_CERT_SUBJECT,
DETECT_AL_MODBUS,
+ DETECT_CIPSERVICE,
+ DETECT_ENIPCOMMAND,
DETECT_XBITS,
DETECT_BASE64_DECODE,
#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"
{"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},
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) {
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);
SC_ERR_INVALID_HASH,
SC_ERR_NO_SHA1_SUPPORT,
SC_ERR_NO_SHA256_SUPPORT,
+ SC_ERR_ENIP_CONFIG,
} SCError;
const char *SCErrorToString(SCError);
# 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