summaryrefslogtreecommitdiffstats
path: root/src/app-layer-enip-common.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/app-layer-enip-common.c')
-rw-r--r--src/app-layer-enip-common.c960
1 files changed, 960 insertions, 0 deletions
diff --git a/src/app-layer-enip-common.c b/src/app-layer-enip-common.c
new file mode 100644
index 0000000..305eb83
--- /dev/null
+++ b/src/app-layer-enip-common.c
@@ -0,0 +1,960 @@
+/* Copyright (C) 2015-2022 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Kevin Wong <kwong@solananetworks.com>
+ *
+ * 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
+ */
+static int ENIPExtractUint8(uint8_t *res, const uint8_t *input, uint16_t *offset, uint32_t input_len)
+{
+
+ if (input_len < sizeof(uint8_t) || *offset > (input_len - sizeof(uint8_t)))
+ {
+ SCLogDebug("ENIPExtractUint8: Parsing beyond payload length");
+ 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
+ */
+static int ENIPExtractUint16(uint16_t *res, const uint8_t *input, uint16_t *offset, uint32_t input_len)
+{
+
+ if (input_len < sizeof(uint16_t) || *offset > (input_len - sizeof(uint16_t))) {
+ SCLogDebug("ENIPExtractUint16: Parsing beyond payload length");
+ return 0;
+ }
+
+ if (ByteExtractUint16(res, BYTE_LITTLE_ENDIAN, sizeof(uint16_t),
+ (const uint8_t *)(input + *offset)) == -1) {
+ return 0;
+ }
+
+ *offset += sizeof(uint16_t);
+ return 1;
+}
+
+/**
+ * \brief Extract 32 bits and move up the offset
+ * @param res
+ * @param input
+ * @param offset
+ */
+static int ENIPExtractUint32(uint32_t *res, const uint8_t *input, uint16_t *offset, uint32_t input_len)
+{
+
+ if (input_len < sizeof(uint32_t) || *offset > (input_len - sizeof(uint32_t)))
+ {
+ SCLogDebug("ENIPExtractUint32: Parsing beyond payload length");
+ return 0;
+ }
+
+ if (ByteExtractUint32(res, BYTE_LITTLE_ENDIAN, sizeof(uint32_t),
+ (const uint8_t *)(input + *offset)) == -1) {
+ return 0;
+ }
+
+ *offset += sizeof(uint32_t);
+ return 1;
+}
+
+/**
+ * \brief Extract 64 bits and move up the offset
+ * @param res
+ * @param input
+ * @param offset
+ */
+static int ENIPExtractUint64(uint64_t *res, const uint8_t *input, uint16_t *offset, uint32_t input_len)
+{
+
+ if (input_len < sizeof(uint64_t) || *offset > (input_len - sizeof(uint64_t)))
+ {
+ SCLogDebug("ENIPExtractUint64: Parsing beyond payload length");
+ return 0;
+ }
+
+ if (ByteExtractUint64(res, BYTE_LITTLE_ENDIAN, sizeof(uint64_t),
+ (const uint8_t *)(input + *offset)) == -1) {
+ return 0;
+ }
+
+ *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;
+
+}
+
+#if 0
+/**
+ * \brief Delete service entry
+ */
+
+static 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;
+}
+#endif
+
+/**
+ * \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(const 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");
+ break;
+ case LIST_SERVICES:
+ SCLogDebug("DecodeENIP - LIST_SERVICES");
+ break;
+ case LIST_IDENTITY:
+ SCLogDebug("DecodeENIP - LIST_IDENTITY");
+ break;
+ case LIST_INTERFACES:
+ SCLogDebug("DecodeENIP - LIST_INTERFACES");
+ break;
+ case REGISTER_SESSION:
+ SCLogDebug("DecodeENIP - REGISTER_SESSION");
+ break;
+ case UNREGISTER_SESSION:
+ SCLogDebug("DecodeENIP - UNREGISTER_SESSION");
+ break;
+ case SEND_RR_DATA:
+ SCLogDebug(
+ "DecodeENIP - SEND_RR_DATA - parse Common Packet Format");
+ ret = DecodeCommonPacketFormatPDU(input, input_len, enip_data,
+ offset);
+ break;
+ case SEND_UNIT_DATA:
+ SCLogDebug(
+ "DecodeENIP - SEND UNIT DATA - parse Common Packet Format");
+ ret = DecodeCommonPacketFormatPDU(input, input_len, enip_data,
+ offset);
+ break;
+ case INDICATE_STATUS:
+ SCLogDebug("DecodeENIP - INDICATE_STATUS");
+ break;
+ case CANCEL:
+ SCLogDebug("DecodeENIP - CANCEL");
+ break;
+ default:
+ SCLogDebug("DecodeENIP - UNSUPPORTED COMMAND 0x%x",
+ 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(const 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");
+ 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");
+ DecodeCIPPDU(input, input_len, enip_data, offset);
+ break;
+ case UNCONNECTED_DATA_ITEM:
+ SCLogDebug("DecodeCommonPacketFormat - UNCONNECTED DATA ITEM");
+ DecodeCIPPDU(input, input_len, enip_data, offset);
+ break;
+ default:
+ SCLogDebug("DecodeCommonPacketFormat - UNKNOWN TYPE 0x%x",
+ 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(const 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");
+ return 0;
+ }
+
+ if (offset > (input_len - sizeof(uint8_t)))
+ {
+ SCLogDebug("DecodeCIP: Parsing beyond payload length");
+ return 0;
+ }
+
+ uint8_t service = 0;
+ service = *(input + offset);
+
+ //SCLogDebug("CIP Service 0x%x", 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(const 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");
+ return 0;
+ }
+
+ uint8_t service = 0; //<-----CIP SERVICE
+ uint8_t path_size = 0;
+
+ 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", service);
+ return 0;
+ }
+
+ //reached maximum number of services
+ if (enip_data->service_count > 32)
+ {
+ SCLogDebug("DecodeCIPRequest: Maximum services reached");
+ return 0;
+ }
+
+ //save CIP data
+ CIPServiceEntry *node = CIPServiceAlloc(enip_data);
+ if (node == NULL)
+ {
+ SCLogDebug("DecodeCIPRequest: Unable to create CIP service");
+ 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", 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");
+ break;
+ case CIP_GET_ATTR_ALL:
+ SCLogDebug("DecodeCIPRequest - CIP_GET_ATTR_ALL");
+ break;
+ case CIP_GET_ATTR_LIST:
+ SCLogDebug("DecodeCIPRequest - CIP_GET_ATTR_LIST");
+ break;
+ case CIP_SET_ATTR_LIST:
+ SCLogDebug("DecodeCIPRequest - CIP_SET_ATTR_LIST");
+ break;
+ case CIP_RESET:
+ SCLogDebug("DecodeCIPRequest - CIP_RESET");
+ break;
+ case CIP_START:
+ SCLogDebug("DecodeCIPRequest - CIP_START");
+ break;
+ case CIP_STOP:
+ SCLogDebug("DecodeCIPRequest - CIP_STOP");
+ break;
+ case CIP_CREATE:
+ SCLogDebug("DecodeCIPRequest - CIP_CREATE");
+ break;
+ case CIP_DELETE:
+ SCLogDebug("DecodeCIPRequest - CIP_DELETE");
+ break;
+ case CIP_MSP:
+ SCLogDebug("DecodeCIPRequest - CIP_MSP");
+ DecodeCIPRequestMSPPDU(input, input_len, enip_data, offset);
+ break;
+ case CIP_APPLY_ATTR:
+ SCLogDebug("DecodeCIPRequest - CIP_APPLY_ATTR");
+ break;
+ case CIP_KICK_TIMER:
+ SCLogDebug("DecodeCIPRequest - CIP_KICK_TIMER");
+ break;
+ case CIP_OPEN_CONNECTION:
+ SCLogDebug("DecodeCIPRequest - CIP_OPEN_CONNECTION");
+ break;
+ case CIP_CHANGE_START:
+ SCLogDebug("DecodeCIPRequest - CIP_CHANGE_START");
+ break;
+ case CIP_GET_STATUS:
+ SCLogDebug("DecodeCIPRequest - CIP_GET_STATUS");
+ break;
+ default:
+ SCLogDebug("DecodeCIPRequest - CIP SERVICE 0x%x", service);
+ }
+
+ return ret;
+}
+
+/**
+ * \brief Decode 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(const uint8_t *input, uint32_t input_len,
+ CIPServiceEntry *node, uint16_t offset)
+{
+ //SCLogDebug("DecodeCIPRequestPath: service 0x%x size %d length %d",
+ // node->service, node->request.path_size, input_len);
+
+ if (node->request.path_size < 1)
+ {
+ //SCLogDebug("DecodeCIPRequestPath: empty path or CIP Response");
+ return 0;
+ }
+
+ int bytes_remain = node->request.path_size;
+
+ uint8_t reserved; //unused byte reserved by ODVA
+
+ //8 bit fields
+ 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)
+ {
+ uint8_t segment = 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: {
+ uint8_t req_path_class8 = 0;
+ if (ENIPExtractUint8(&req_path_class8, input, &offset, input_len) != 1) {
+ return 0;
+ }
+ class = (uint16_t) req_path_class8;
+ SCLogDebug("DecodeCIPRequestPathPDU: 8bit class 0x%x", 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", 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", 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",
+ 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",
+ 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", 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(const 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");
+ return 0;
+ }
+
+ uint8_t service = 0; //<----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",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", service, status);
+
+ //reached maximum number of services
+ if (enip_data->service_count > 32)
+ {
+ SCLogDebug("DecodeCIPRequest: Maximum services reached");
+ return 0;
+ }
+
+ //save CIP data
+ CIPServiceEntry *node = CIPServiceAlloc(enip_data);
+ if (node == NULL)
+ {
+ SCLogDebug("DecodeCIPRequest: Unable to create CIP service");
+ return 0;
+ }
+ node->direction = 1;
+ node->service = service;
+ node->response.status = status;
+
+ SCLogDebug("DecodeCIPResponsePDU: service 0x%x size %d", 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");
+ break;
+ case CIP_GET_ATTR_ALL:
+ SCLogDebug("DecodeCIPResponse - CIP_GET_ATTR_ALL");
+ break;
+ case CIP_GET_ATTR_LIST:
+ SCLogDebug("DecodeCIPResponse - CIP_GET_ATTR_LIST");
+ break;
+ case CIP_SET_ATTR_LIST:
+ SCLogDebug("DecodeCIPResponse - CIP_SET_ATTR_LIST");
+ break;
+ case CIP_RESET:
+ SCLogDebug("DecodeCIPResponse - CIP_RESET");
+ break;
+ case CIP_START:
+ SCLogDebug("DecodeCIPResponse - CIP_START");
+ break;
+ case CIP_STOP:
+ SCLogDebug("DecodeCIPResponse - CIP_STOP");
+ break;
+ case CIP_CREATE:
+ SCLogDebug("DecodeCIPResponse - CIP_CREATE");
+ break;
+ case CIP_DELETE:
+ SCLogDebug("DecodeCIPResponse - CIP_DELETE");
+ break;
+ case CIP_MSP:
+ SCLogDebug("DecodeCIPResponse - CIP_MSP");
+ DecodeCIPResponseMSPPDU(input, input_len, enip_data, offset);
+ break;
+ case CIP_APPLY_ATTR:
+ SCLogDebug("DecodeCIPResponse - CIP_APPLY_ATTR");
+ break;
+ case CIP_KICK_TIMER:
+ SCLogDebug("DecodeCIPResponse - CIP_KICK_TIMER");
+ break;
+ case CIP_OPEN_CONNECTION:
+ SCLogDebug("DecodeCIPResponse - CIP_OPEN_CONNECTION");
+ break;
+ case CIP_CHANGE_START:
+ SCLogDebug("DecodeCIPResponse - CIP_CHANGE_START");
+ break;
+ case CIP_GET_STATUS:
+ SCLogDebug("DecodeCIPResponse - CIP_GET_STATUS");
+ break;
+ default:
+ SCLogDebug("DecodeCIPResponse - CIP SERVICE 0x%x", 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(const 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");
+ 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;
+ if (ByteExtractUint16(&num_services, BYTE_LITTLE_ENDIAN, sizeof(uint16_t),
+ (const uint8_t *)(input + temp_offset)) == -1) {
+ return 0;
+ }
+
+ temp_offset += sizeof(uint16_t);
+ //SCLogDebug("DecodeCIPRequestMSP number of services %d",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");
+ return 0;
+ }
+
+ uint16_t svc_offset; //read set of service offsets
+ if (ByteExtractUint16(&svc_offset, BYTE_LITTLE_ENDIAN, sizeof(uint16_t),
+ (const uint8_t *)(input + temp_offset)) == -1) {
+ return 0;
+ }
+ temp_offset += sizeof(uint16_t);
+ //SCLogDebug("parseCIPRequestMSP service %d offset %d",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(const 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");
+ 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;
+ if (ByteExtractUint16(&num_services, BYTE_LITTLE_ENDIAN, sizeof(uint16_t),
+ (const uint8_t *)(input + temp_offset)) == -1) {
+ return 0;
+ }
+ temp_offset += sizeof(uint16_t);
+ //SCLogDebug("DecodeCIPResponseMSP number of services %d", num_services);
+
+ for (int svc = 0; svc < num_services; svc++) {
+ if (temp_offset >= (input_len - sizeof(uint16_t)))
+ {
+ SCLogDebug("DecodeCIPResponseMSP: Parsing beyond payload length");
+ return 0;
+ }
+
+ uint16_t svc_offset; //read set of service offsets
+ if (ByteExtractUint16(&svc_offset, BYTE_LITTLE_ENDIAN, sizeof(uint16_t),
+ (const uint8_t *)(input + temp_offset)) == -1) {
+ return 0;
+ }
+ temp_offset += sizeof(uint16_t);
+ //SCLogDebug("parseCIPResponseMSP service %d offset %d", svc, svc_offset);
+
+ DecodeCIPPDU(input, input_len, enip_data, offset + svc_offset); //parse CIP at found offset
+ }
+
+ return ret;
+}