Adding SCADA EtherNet/IP and CIP protocol support

Add support for the ENIP/CIP Industrial protocol

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

Implements AFL entry points
pull/2319/head
kwong 9 years ago committed by Victor Julien
parent 5bd906ae9f
commit a3ffebd835

@ -0,0 +1,40 @@
ENIP/CIP Keywords
==============
The enip_command and cip_service keywords can be used for matching on various properties of
ENIP requests.
There are three ways of using this keyword:
* matching on ENIP command with the setting "enip_command";
* matching on CIP Service with the setting "cip_service".
* matching both the ENIP command and the CIP Service with "enip_command" and "cip_service" together
For the ENIP command, we are matching against the command field found in the ENIP encapsulation.
For the CIP Service, we use a maximum of 3 comma seperated values representing the Service, Class and Attribute.
These values are described in the CIP specification. CIP Classes are associated with their Service, and CIP Attributes
are associated with their Service. If you only need to match up until the Service, then only provide the Service value.
If you want to match to the CIP Attribute, then you must provide all 3 values.
Syntax::
enip_command:<value>
cip_service:<value(s)>
enip_command:<value>, cip_service:<value(s)>
Examples::
enip_command:99
cip_service:75
cip_service:16,246,6
enip_command:111, cip_service:5
(cf. http://read.pudn.com/downloads166/ebook/763211/EIP-CIP-V1-1.0.pdf)
Information on the protocol can be found here:
http://literature.rockwellautomation.com/idc/groups/literature/documents/wp/enet-wp001_-en-p.pdf

@ -19,6 +19,8 @@ app-layer-detect-proto.c app-layer-detect-proto.h \
app-layer-dns-common.c app-layer-dns-common.h \
app-layer-dns-tcp.c app-layer-dns-tcp.h \
app-layer-dns-udp.c app-layer-dns-udp.h \
app-layer-enip.c app-layer-enip.h \
app-layer-enip-common.c app-layer-enip-common.h \
app-layer-events.c app-layer-events.h \
app-layer-ftp.c app-layer-ftp.h \
app-layer-htp-body.c app-layer-htp-body.h \
@ -103,6 +105,7 @@ detect-engine-dcepayload.c detect-engine-dcepayload.h \
detect-engine-dns.c detect-engine-dns.h \
detect-engine-tls.c detect-engine-tls.h \
detect-engine-modbus.c detect-engine-modbus.h \
detect-engine-enip.c detect-engine-enip.h \
detect-engine-event.c detect-engine-event.h \
detect-engine-file.c detect-engine-file.h \
detect-engine-filedata-smtp.c detect-engine-filedata-smtp.h \
@ -222,6 +225,7 @@ detect-window.c detect-window.h \
detect-within.c detect-within.h \
detect-modbus.c detect-modbus.h \
detect-xbits.c detect-xbits.h \
detect-cipservice.c detect-cipservice.h \
flow-bit.c flow-bit.h \
flow.c flow.h \
flow-hash.c flow-hash.h \

@ -691,6 +691,8 @@ void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingParser *pp
printf(" alproto: ALPROTO_DNS\n");
else if (pp_pe->alproto == ALPROTO_MODBUS)
printf(" alproto: ALPROTO_MODBUS\n");
else if (pp_pe->alproto == ALPROTO_ENIP)
printf(" alproto: ALPROTO_ENIP\n");
else if (pp_pe->alproto == ALPROTO_TEMPLATE)
printf(" alproto: ALPROTO_TEMPLATE\n");
else
@ -744,6 +746,8 @@ void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingParser *pp
printf(" alproto: ALPROTO_DNS\n");
else if (pp_pe->alproto == ALPROTO_MODBUS)
printf(" alproto: ALPROTO_MODBUS\n");
else if (pp_pe->alproto == ALPROTO_ENIP)
printf(" alproto: ALPROTO_ENIP\n");
else if (pp_pe->alproto == ALPROTO_TEMPLATE)
printf(" alproto: ALPROTO_TEMPLATE\n");
else

@ -0,0 +1,947 @@
/* Copyright (C) 2015 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* \author Kevin Wong <kwong@solananetworks.com>
*
* App-layer parser for ENIP protocol common code
*
*/
#include "suricata-common.h"
#include "util-unittest.h"
#include "util-unittest-helper.h"
#include "detect-parse.h"
#include "detect-engine.h"
#include "util-byte.h"
#include "pkt-var.h"
#include "util-profiling.h"
#include "app-layer-enip-common.h"
/**
* \brief Extract 8 bits and move up the offset
* @param res
* @param input
* @param offset
*/
int ENIPExtractUint8(uint8_t *res, uint8_t *input, uint16_t *offset, uint32_t input_len)
{
if (*offset > (input_len - sizeof(uint8_t)))
{
SCLogDebug("ENIPExtractUint8: Parsing beyond payload length\n");
return 0;
}
*res = *(input + *offset);
*offset += sizeof(uint8_t);
return 1;
}
/**
* \brief Extract 16 bits and move up the offset
* @param res
* @param input
* @param offset
*/
int ENIPExtractUint16(uint16_t *res, uint8_t *input, uint16_t *offset, uint32_t input_len)
{
if (*offset > (input_len - sizeof(uint16_t)))
{
SCLogDebug("ENIPExtractUint16: Parsing beyond payload length\n");
return 0;
}
ByteExtractUint16(res, BYTE_LITTLE_ENDIAN, sizeof(uint16_t),
(const uint8_t *) (input + *offset));
*offset += sizeof(uint16_t);
return 1;
}
/**
* \brief Extract 32 bits and move up the offset
* @param res
* @param input
* @param offset
*/
int ENIPExtractUint32(uint32_t *res, uint8_t *input, uint16_t *offset, uint32_t input_len)
{
if (*offset > (input_len - sizeof(uint32_t)))
{
SCLogDebug("ENIPExtractUint32: Parsing beyond payload length\n");
return 0;
}
ByteExtractUint32(res, BYTE_LITTLE_ENDIAN, sizeof(uint32_t),
(const uint8_t *) (input + *offset));
*offset += sizeof(uint32_t);
return 1;
}
/**
* \brief Extract 64 bits and move up the offset
* @param res
* @param input
* @param offset
*/
int ENIPExtractUint64(uint64_t *res, uint8_t *input, uint16_t *offset, uint32_t input_len)
{
if (*offset > (input_len - sizeof(uint64_t)))
{
SCLogDebug("ENIPExtractUint64: Parsing beyond payload length\n");
return 0;
}
ByteExtractUint64(res, BYTE_LITTLE_ENDIAN, sizeof(uint64_t),
(const uint8_t *) (input + *offset));
*offset += sizeof(uint64_t);
return 1;
}
/**
* \brief Create service entry, add to transaction
* @param tx Transaction
* @return service entry
*/
static CIPServiceEntry *CIPServiceAlloc(ENIPTransaction *tx)
{
CIPServiceEntry *svc = (CIPServiceEntry *) SCCalloc(1,
sizeof(CIPServiceEntry));
if (unlikely(svc == NULL))
return NULL;
memset(svc, 0x00, sizeof(CIPServiceEntry));
TAILQ_INIT(&svc->segment_list);
TAILQ_INIT(&svc->attrib_list);
TAILQ_INSERT_TAIL(&tx->service_list, svc, next);
tx->service_count++;
return svc;
}
/**
* \brief Delete service entry
*/
void CIPServiceFree(void *s)
{
SCEnter();
if (s)
{
CIPServiceEntry *svc = (CIPServiceEntry *) s;
SegmentEntry *seg = NULL;
while ((seg = TAILQ_FIRST(&svc->segment_list)))
{
TAILQ_REMOVE(&svc->segment_list, seg, next);
SCFree(seg);
}
AttributeEntry *attr = NULL;
while ((attr = TAILQ_FIRST(&svc->attrib_list)))
{
TAILQ_REMOVE(&svc->attrib_list, attr, next);
SCFree(attr);
}
SCFree(s);
}
SCReturn;
}
/**
* \brief Decode ENIP Encapsulation Header
* @param input, input_len data stream
* @param enip_data stores data from Packet
* @return 1 Packet ok
* @return 0 Packet has errors
*/
int DecodeENIPPDU(uint8_t *input, uint32_t input_len,
ENIPTransaction *enip_data)
{
int ret = 1;
uint16_t offset = 0; //byte offset
//Decode Encapsulation Header
uint16_t cmd;
uint16_t len;
uint32_t session;
uint32_t status;
uint64_t context;
uint32_t option;
if (ENIPExtractUint16(&cmd, input, &offset, input_len) != 1)
{
return 0;
}
if (ENIPExtractUint16(&len, input, &offset, input_len) != 1)
{
return 0;
}
if (ENIPExtractUint32(&session, input, &offset, input_len) != 1)
{
return 0;
}
if (ENIPExtractUint32(&status, input, &offset, input_len) != 1)
{
return 0;
}
if (ENIPExtractUint64(&context, input, &offset, input_len) != 1)
{
return 0;
}
if (ENIPExtractUint32(&option, input, &offset, input_len) != 1)
{
return 0;
}
enip_data->header.command = cmd;
enip_data->header.length = len;
enip_data->header.session = session;
enip_data->header.status = status;
enip_data->header.context = context;
enip_data->header.option = option;
switch (enip_data->header.command)
{
case NOP:
SCLogDebug("DecodeENIP - NOP\n");
break;
case LIST_SERVICES:
SCLogDebug("DecodeENIP - LIST_SERVICES\n");
break;
case LIST_IDENTITY:
SCLogDebug("DecodeENIP - LIST_IDENTITY\n");
break;
case LIST_INTERFACES:
SCLogDebug("DecodeENIP - LIST_INTERFACES\n");
break;
case REGISTER_SESSION:
SCLogDebug("DecodeENIP - REGISTER_SESSION\n");
break;
case UNREGISTER_SESSION:
SCLogDebug("DecodeENIP - UNREGISTER_SESSION\n");
break;
case SEND_RR_DATA:
SCLogDebug(
"DecodeENIP - SEND_RR_DATA - parse Common Packet Format\n");
ret = DecodeCommonPacketFormatPDU(input, input_len, enip_data,
offset);
break;
case SEND_UNIT_DATA:
SCLogDebug(
"DecodeENIP - SEND UNIT DATA - parse Common Packet Format\n");
ret = DecodeCommonPacketFormatPDU(input, input_len, enip_data,
offset);
break;
case INDICATE_STATUS:
SCLogDebug("DecodeENIP - INDICATE_STATUS\n");
break;
case CANCEL:
SCLogDebug("DecodeENIP - CANCEL\n");
break;
default:
SCLogDebug("DecodeENIP - UNSUPPORTED COMMAND 0x%x\n",
enip_data->header.command);
}
return ret;
}
/**
* \brief Decode Common Packet Format
* @param input, input_len data stream
* @param enip_data stores data from Packet
* @param offset current point in the packet
* @return 1 Packet ok
* @return 0 Packet has errors
*/
int DecodeCommonPacketFormatPDU(uint8_t *input, uint32_t input_len,
ENIPTransaction *enip_data, uint16_t offset)
{
if (enip_data->header.length < sizeof(ENIPEncapDataHdr))
{
SCLogDebug("DecodeCommonPacketFormat: Malformed ENIP packet\n");
return 0;
}
uint32_t handle;
uint16_t timeout;
uint16_t count;
if (ENIPExtractUint32(&handle, input, &offset, input_len) != 1)
{
return 0;
}
if (ENIPExtractUint16(&timeout, input, &offset, input_len) != 1)
{
return 0;
}
if (ENIPExtractUint16(&count, input, &offset, input_len) != 1)
{
return 0;
}
enip_data->encap_data_header.interface_handle = handle;
enip_data->encap_data_header.timeout = timeout;
enip_data->encap_data_header.item_count = count;
uint16_t address_type;
uint16_t address_length; //length of connection id in bytes
uint32_t address_connectionid = 0;
uint32_t address_sequence = 0;
if (ENIPExtractUint16(&address_type, input, &offset, input_len) != 1)
{
return 0;
}
if (ENIPExtractUint16(&address_length, input, &offset, input_len) != 1)
{
return 0;
}
//depending on addr type, get connection id, sequence if needed. Can also use addr length too?
if (address_type == CONNECTION_BASED)
{ //get 4 byte connection id
if (ENIPExtractUint32(&address_connectionid, input, &offset, input_len) != 1)
{
return 0;
}
} else if (address_type == SEQUENCE_ADDR_ITEM)
{ // get 4 byte connection id and 4 byte sequence
if (ENIPExtractUint32(&address_connectionid, input, &offset, input_len) != 1)
{
return 0;
}
if (ENIPExtractUint32(&address_sequence, input, &offset, input_len) != 1)
{
return 0;
}
}
enip_data->encap_addr_item.type = address_type;
enip_data->encap_addr_item.length = address_length;
enip_data->encap_addr_item.conn_id = address_connectionid;
enip_data->encap_addr_item.sequence_num = address_sequence;
uint16_t data_type;
uint16_t data_length; //length of data in bytes
uint16_t data_sequence_count;
if (ENIPExtractUint16(&data_type, input, &offset, input_len) != 1)
{
return 0;
}
if (ENIPExtractUint16(&data_length, input, &offset, input_len) != 1)
{
return 0;
}
enip_data->encap_data_item.type = data_type;
enip_data->encap_data_item.length = data_length;
if (enip_data->encap_data_item.type == CONNECTED_DATA_ITEM)
{ //connected data items have seq number
if (ENIPExtractUint16(&data_sequence_count, input, &offset, input_len) != 1)
{
return 0;
}
enip_data->encap_data_item.sequence_count = data_sequence_count;
}
switch (enip_data->encap_data_item.type)
{
case CONNECTED_DATA_ITEM:
SCLogDebug(
"DecodeCommonPacketFormat - CONNECTED DATA ITEM - parse CIP\n");
DecodeCIPPDU(input, input_len, enip_data, offset);
break;
case UNCONNECTED_DATA_ITEM:
SCLogDebug("DecodeCommonPacketFormat - UNCONNECTED DATA ITEM\n");
DecodeCIPPDU(input, input_len, enip_data, offset);
break;
default:
SCLogDebug("DecodeCommonPacketFormat - UNKNOWN TYPE 0x%x\n\n",
enip_data->encap_data_item.type);
return 0;
}
return 1;
}
/**
* \brief Decode CIP packet
* @param input, input_len data stream
* @param enip_data stores data from Packet
* @param offset current point in the packet
* @return 1 Packet ok
* @return 0 Packet has errors
*/
int DecodeCIPPDU(uint8_t *input, uint32_t input_len,
ENIPTransaction *enip_data, uint16_t offset)
{
int ret = 1;
if (enip_data->encap_data_item.length == 0)
{
SCLogDebug("DecodeCIP: No CIP Data\n");
return 0;
}
if (offset > (input_len - sizeof(uint8_t)))
{
SCLogDebug("DecodeCIP: Parsing beyond payload length\n");
return 0;
}
uint8_t service = 0;
service = *(input + offset);
//SCLogDebug("CIP Service 0x%x\n", service);
//use service code first bit to determine request/response, no need to save or push offset
if (service >> 7)
{
ret = DecodeCIPResponsePDU(input, input_len, enip_data, offset);
} else
{
ret = DecodeCIPRequestPDU(input, input_len, enip_data, offset);
}
return ret;
}
/**
* \brief Decode CIP Request
* @param input, input_len data stream
* @param enip_data stores data from Packet
* @param offset current point in the packet
* @return 1 Packet ok
* @return 0 Packet has errors
*/
int DecodeCIPRequestPDU(uint8_t *input, uint32_t input_len,
ENIPTransaction *enip_data, uint16_t offset)
{
int ret = 1;
if (enip_data->encap_data_item.length < sizeof(CIPReqHdr))
{
SCLogDebug("DecodeCIPRequest - Malformed CIP Data\n");
return 0;
}
uint8_t service; //<-----CIP SERVICE
uint8_t path_size;
if (ENIPExtractUint8(&service, input, &offset, input_len) != 1)
{
return 0;
}
if (ENIPExtractUint8(&path_size, input, &offset, input_len) != 1)
{
return 0;
}
if (service > MAX_CIP_SERVICE)
{ // service codes of value 0x80 or greater are not permitted because in the CIP protocol the highest order bit is used to flag request(0)/response(1)
SCLogDebug("DecodeCIPRequest - INVALID CIP SERVICE 0x%x\n", service);
return 0;
}
//reached maximum number of services
if (enip_data->service_count > 32)
{
SCLogDebug("DecodeCIPRequest: Maximum services reached\n");
return 0;
}
//save CIP data
CIPServiceEntry *node = CIPServiceAlloc(enip_data);
if (node == NULL)
{
SCLogDebug("DecodeCIPRequest: Unable to create CIP service\n");
return 0;
}
node->direction = 0;
node->service = service;
node->request.path_size = path_size;
node->request.path_offset = offset;
// SCLogDebug("DecodeCIPRequestPDU: service 0x%x size %d\n", node->service,
// node->request.path_size);
DecodeCIPRequestPathPDU(input, input_len, node, offset);
offset += path_size * sizeof(uint16_t); //move offset past pathsize
//list of CIP services is large and can be vendor specific, store CIP service anyways and let the rule decide the action
switch (service)
{
case CIP_RESERVED:
SCLogDebug("DecodeCIPRequest - CIP_RESERVED\n");
break;
case CIP_GET_ATTR_ALL:
SCLogDebug("DecodeCIPRequest - CIP_GET_ATTR_ALL\n");
break;
case CIP_GET_ATTR_LIST:
SCLogDebug("DecodeCIPRequest - CIP_GET_ATTR_LIST\n");
break;
case CIP_SET_ATTR_LIST:
SCLogDebug("DecodeCIPRequest - CIP_SET_ATTR_LIST\n");
break;
case CIP_RESET:
SCLogDebug("DecodeCIPRequest - CIP_RESET\n");
break;
case CIP_START:
SCLogDebug("DecodeCIPRequest - CIP_START\n");
break;
case CIP_STOP:
SCLogDebug("DecodeCIPRequest - CIP_STOP\n");
break;
case CIP_CREATE:
SCLogDebug("DecodeCIPRequest - CIP_CREATE\n");
break;
case CIP_DELETE:
SCLogDebug("DecodeCIPRequest - CIP_DELETE\n");
break;
case CIP_MSP:
SCLogDebug("DecodeCIPRequest - CIP_MSP\n");
DecodeCIPRequestMSPPDU(input, input_len, enip_data, offset);
break;
case CIP_APPLY_ATTR:
SCLogDebug("DecodeCIPRequest - CIP_APPLY_ATTR\n");
break;
case CIP_KICK_TIMER:
SCLogDebug("DecodeCIPRequest - CIP_KICK_TIMER\n");
break;
case CIP_OPEN_CONNECTION:
SCLogDebug("DecodeCIPRequest - CIP_OPEN_CONNECTION\n");
break;
case CIP_CHANGE_START:
SCLogDebug("DecodeCIPRequest - CIP_CHANGE_START\n");
break;
case CIP_GET_STATUS:
SCLogDebug("DecodeCIPRequest - CIP_GET_STATUS\n");
break;
default:
SCLogDebug("DecodeCIPRequest - CIP SERVICE 0x%x\n", service);
}
return ret;
}
/**
* \brief Deocde CIP Request Path
* @param input, input_len data stream
* @param enip_data stores data from Packet
* @param offset current point in the packet
* @param cipserviced the cip service rule
* @return 1 Packet matches
* @return 0 Packet not match
*/
int DecodeCIPRequestPathPDU(uint8_t *input, uint32_t input_len,
CIPServiceEntry *node, uint16_t offset)
{
//SCLogDebug("DecodeCIPRequestPath: service 0x%x size %d length %d\n",
// node->service, node->request.path_size, input_len);
if (node->request.path_size < 1)
{
//SCLogDebug("DecodeCIPRequestPath: empty path or CIP Response\n");
return 0;
}
int bytes_remain = node->request.path_size;
uint8_t segment;
uint8_t reserved; //unused byte reserved by ODVA
//8 bit fields
uint8_t req_path_class8;
uint8_t req_path_instance8;
uint8_t req_path_attr8;
//16 bit fields
uint16_t req_path_class16;
uint16_t req_path_instance16;
uint16_t class = 0;
SegmentEntry *seg = NULL;
while (bytes_remain > 0)
{
if (ENIPExtractUint8(&segment, input, &offset, input_len) != 1)
{
return 0;
}
switch (segment)
{ //assume order is class then instance. Can have multiple
case PATH_CLASS_8BIT:
if (ENIPExtractUint8(&req_path_class8, input, &offset, input_len) != 1)
{
return 0;
}
class = (uint16_t) req_path_class8;
SCLogDebug("DecodeCIPRequestPathPDU: 8bit class 0x%x\n", class);
seg = SCMalloc(sizeof(SegmentEntry));
if (unlikely(seg == NULL))
return 0;
seg->segment = segment;
seg->value = class;
TAILQ_INSERT_TAIL(&node->segment_list, seg, next);
bytes_remain--;
break;
case PATH_INSTANCE_8BIT:
if (ENIPExtractUint8(&req_path_instance8, input, &offset, input_len) != 1)
{
return 0;
}
//skip instance, don't need to store
bytes_remain--;
break;
case PATH_ATTR_8BIT: //single attribute
if (ENIPExtractUint8(&req_path_attr8, input, &offset, input_len) != 1)
{
return 0;
}
//uint16_t attrib = (uint16_t) req_path_attr8;
//SCLogDebug("DecodeCIPRequestPath: 8bit attr 0x%x\n", attrib);
seg = SCMalloc(sizeof(SegmentEntry));
if (unlikely(seg == NULL))
return 0;
seg->segment = segment;
seg->value = class;
TAILQ_INSERT_TAIL(&node->segment_list, seg, next);
bytes_remain--;
break;
case PATH_CLASS_16BIT:
if (ENIPExtractUint8(&reserved, input, &offset, input_len) != 1) //skip reserved
{
return 0;
}
if (ENIPExtractUint16(&req_path_class16, input, &offset, input_len) != 1)
{
return 0;
}
class = req_path_class16;
SCLogDebug("DecodeCIPRequestPath: 16bit class 0x%x\n", class);
seg = SCMalloc(sizeof(SegmentEntry));
if (unlikely(seg == NULL))
return 0;
seg->segment = segment;
seg->value = class;
TAILQ_INSERT_TAIL(&node->segment_list, seg, next);
if (bytes_remain >= 2)
{
bytes_remain = bytes_remain - 2;
} else
{
bytes_remain = 0;
}
break;
case PATH_INSTANCE_16BIT:
if (ENIPExtractUint8(&reserved, input, &offset, input_len) != 1) // skip reserved
{
return 0;
}
if (ENIPExtractUint16(&req_path_instance16, input, &offset, input_len) != 1)
{
return 0;
}
//skip instance, don't need to store
if (bytes_remain >= 2)
{
bytes_remain = bytes_remain - 2;
} else
{
bytes_remain = 0;
}
break;
default:
SCLogDebug(
"DecodeCIPRequestPath: UNKNOWN SEGMENT 0x%x service 0x%x\n",
segment, node->service);
return 0;
}
}
if ((node->service == CIP_SET_ATTR_LIST) || (node->service
== CIP_GET_ATTR_LIST))
{
uint16_t attr_list_count;
uint16_t attribute;
//parse get/set attribute list
if (ENIPExtractUint16(&attr_list_count, input, &offset, input_len) != 1)
{
return 0;
}
SCLogDebug("DecodeCIPRequestPathPDU: attribute list count %d\n",
attr_list_count);
for (int i = 0; i < attr_list_count; i++)
{
if (ENIPExtractUint16(&attribute, input, &offset, input_len) != 1)
{
return 0;
}
SCLogDebug("DecodeCIPRequestPathPDU: attribute %d\n", attribute);
//save attrs
AttributeEntry *attr = SCMalloc(sizeof(AttributeEntry));
if (unlikely(attr == NULL))
return 0;
attr->attribute = attribute;
TAILQ_INSERT_TAIL(&node->attrib_list, attr, next);
}
}
return 1;
}
/**
* \brief Decode CIP Response
* @param input, input_len data stream
* @param enip_data stores data from Packet
* @param offset current point in the packet
* @return 1 Packet ok
* @return 0 Packet has errors
*/
int DecodeCIPResponsePDU(uint8_t *input, uint32_t input_len,
ENIPTransaction *enip_data, uint16_t offset)
{
int ret = 1;
if (enip_data->encap_data_item.length < sizeof(CIPRespHdr))
{
SCLogDebug("DecodeCIPResponse - Malformed CIP Data\n");
return 0;
}
uint8_t service; //<----CIP SERVICE
uint8_t reserved; //unused byte reserved by ODVA
uint16_t status;
if (ENIPExtractUint8(&service, input, &offset, input_len) != 1)
{
return 0;
}
if (ENIPExtractUint8(&reserved, input, &offset, input_len) != 1)
{
return 0;
}
if (ENIPExtractUint16(&status, input, &offset, input_len) != 1)
{
return 0;
}
//SCLogDebug("DecodeCIPResponse: service 0x%x\n",service);
service &= 0x7f; //strip off top bit to get service code. Responses have first bit as 1
SCLogDebug("CIP service 0x%x status 0x%x\n", service, status);
//reached maximum number of services
if (enip_data->service_count > 32)
{
SCLogDebug("DecodeCIPRequest: Maximum services reached\n");
return 0;
}
//save CIP data
CIPServiceEntry *node = CIPServiceAlloc(enip_data);
if (node == NULL)
{
SCLogDebug("DecodeCIPRequest: Unable to create CIP service\n");
return 0;
}
node->direction = 1;
node->service = service;
node->response.status = status;
SCLogDebug("DecodeCIPResponsePDU: service 0x%x size %d\n", node->service,
node->request.path_size);
//list of CIP services is large and can be vendor specific, store CIP service anyways and let the rule decide the action
switch (service)
{
case CIP_RESERVED:
SCLogDebug("DecodeCIPResponse - CIP_RESERVED\n");
break;
case CIP_GET_ATTR_ALL:
SCLogDebug("DecodeCIPResponse - CIP_GET_ATTR_ALL\n");
break;
case CIP_GET_ATTR_LIST:
SCLogDebug("DecodeCIPResponse - CIP_GET_ATTR_LIST\n");
break;
case CIP_SET_ATTR_LIST:
SCLogDebug("DecodeCIPResponse - CIP_SET_ATTR_LIST\n");
break;
case CIP_RESET:
SCLogDebug("DecodeCIPResponse - CIP_RESET\n");
break;
case CIP_START:
SCLogDebug("DecodeCIPResponse - CIP_START\n");
break;
case CIP_STOP:
SCLogDebug("DecodeCIPResponse - CIP_STOP\n");
break;
case CIP_CREATE:
SCLogDebug("DecodeCIPResponse - CIP_CREATE\n");
break;
case CIP_DELETE:
SCLogDebug("DecodeCIPResponse - CIP_DELETE\n");
break;
case CIP_MSP:
SCLogDebug("DecodeCIPResponse - CIP_MSP\n");
DecodeCIPResponseMSPPDU(input, input_len, enip_data, offset);
break;
case CIP_APPLY_ATTR:
SCLogDebug("DecodeCIPResponse - CIP_APPLY_ATTR\n");
break;
case CIP_KICK_TIMER:
SCLogDebug("DecodeCIPResponse - CIP_KICK_TIMER\n");
break;
case CIP_OPEN_CONNECTION:
SCLogDebug("DecodeCIPResponse - CIP_OPEN_CONNECTION\n");
break;
case CIP_CHANGE_START:
SCLogDebug("DecodeCIPResponse - CIP_CHANGE_START\n");
break;
case CIP_GET_STATUS:
SCLogDebug("DecodeCIPResponse - CIP_GET_STATUS\n");
break;
default:
SCLogDebug("DecodeCIPResponse - CIP SERVICE 0x%x\n", service);
}
return ret;
}
/**
* \brief Decode CIP Request Multi Service Packet
* @param input, input_len data stream
* @param enip_data stores data from Packet
* @param offset current point in the packet
* @return 1 Packet ok
* @return 0 Packet has errors
*/
int DecodeCIPRequestMSPPDU(uint8_t *input, uint32_t input_len,
ENIPTransaction *enip_data, uint16_t offset)
{
int ret = 1;
if (offset >= (input_len - sizeof(uint16_t)))
{
SCLogDebug("DecodeCIPRequestMSPPDU: Parsing beyond payload length\n");
return 0;
}
//use temp_offset just to grab the service offset, don't want to use and push offset
uint16_t temp_offset = offset;
uint16_t num_services;
ByteExtractUint16(&num_services, BYTE_LITTLE_ENDIAN, sizeof(uint16_t),
(const uint8_t *) (input + temp_offset));
temp_offset += sizeof(uint16_t);
//SCLogDebug("DecodeCIPRequestMSP number of services %d\n",num_services);
for (int svc = 1; svc < num_services + 1; svc++)
{
if (temp_offset >= (input_len - sizeof(uint16_t)))
{
SCLogDebug("DecodeCIPRequestMSPPDU: Parsing beyond payload length\n");
return 0;
}
uint16_t svc_offset; //read set of service offsets
ByteExtractUint16(&svc_offset, BYTE_LITTLE_ENDIAN, sizeof(uint16_t),
(const uint8_t *) (input + temp_offset));
temp_offset += sizeof(uint16_t);
//SCLogDebug("parseCIPRequestMSP service %d offset %d\n",svc, svc_offset);
DecodeCIPPDU(input, input_len, enip_data, offset + svc_offset); //parse CIP at found offset
}
return ret;
}
/**
* \brief Decode CIP Response MultiService Packet.
* @param input, input_len data stream
* @param enip_data stores data from Packet
* @param offset current point in the packet
* @return 1 Packet ok
* @return 0 Packet has errors
*/
int DecodeCIPResponseMSPPDU(uint8_t *input, uint32_t input_len,
ENIPTransaction *enip_data, uint16_t offset)
{
int ret = 1;
if (offset >= (input_len - sizeof(uint16_t)))
{
SCLogDebug("DecodeCIPResponseMSPPDU: Parsing beyond payload length\n");
return 0;
}
//use temp_offset just to grab the service offset, don't want to use and push offset
uint16_t temp_offset = offset;
uint16_t num_services;
ByteExtractUint16(&num_services, BYTE_LITTLE_ENDIAN, sizeof(uint16_t),
(const uint8_t *) (input + temp_offset));
temp_offset += sizeof(uint16_t);
//SCLogDebug("DecodeCIPResponseMSP number of services %d\n", num_services);
for (int svc = 0; svc < num_services; svc++)
{
if (temp_offset >= (input_len - sizeof(uint16_t)))
{
SCLogDebug("DecodeCIPResponseMSP: Parsing beyond payload length\n");
return 0;
}
uint16_t svc_offset; //read set of service offsets
ByteExtractUint16(&svc_offset, BYTE_LITTLE_ENDIAN, sizeof(uint16_t),
(const uint8_t *) (input + temp_offset));
temp_offset += sizeof(uint16_t);
//SCLogDebug("parseCIPResponseMSP service %d offset %d\n", svc, svc_offset);
DecodeCIPPDU(input, input_len, enip_data, offset + svc_offset); //parse CIP at found offset
}
return ret;
}

@ -0,0 +1,250 @@
/* Copyright (C) 2015 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* \author Kevin Wong <kwong@solananetworks.com>
*/
#ifndef __APP_LAYER_ENIP_COMMON_H__
#define __APP_LAYER_ENIP_COMMON_H__
#include "app-layer-protos.h"
#include "app-layer-parser.h"
#include "flow.h"
#include "queue.h"
#define MAX_ENIP_CMD 65535
// EtherNet/IP commands
#define NOP 0x0000
#define LIST_SERVICES 0x0004
#define LIST_IDENTITY 0x0063
#define LIST_INTERFACES 0x0064
#define REGISTER_SESSION 0x0065
#define UNREGISTER_SESSION 0x0066
#define SEND_RR_DATA 0x006F
#define SEND_UNIT_DATA 0x0070
#define INDICATE_STATUS 0x0072
#define CANCEL 0x0073
//Common Packet Format Types
#define NULL_ADDR 0x0000
#define CONNECTION_BASED 0x00a1
#define CONNECTED_DATA_ITEM 0x00b1
#define UNCONNECTED_DATA_ITEM 0x00b2
#define SEQUENCE_ADDR_ITEM 0xB002
//status codes
#define SUCCESS 0x0000
#define INVALID_CMD 0x0001
#define NO_RESOURCES 0x0002
#define INCORRECT_DATA 0x0003
#define INVALID_SESSION 0x0064
#define INVALID_LENGTH 0x0065
#define UNSUPPORTED_PROT_REV 0x0069
#define MAX_CIP_SERVICE 127
#define MAX_CIP_CLASS 65535
#define MAX_CIP_ATTRIBUTE 65535
// CIP service codes
#define CIP_RESERVED 0x00
#define CIP_GET_ATTR_ALL 0x01
#define CIP_GET_ATTR_LIST 0x03
#define CIP_SET_ATTR_LIST 0x04
#define CIP_RESET 0x05
#define CIP_START 0x06
#define CIP_STOP 0x07
#define CIP_CREATE 0x08
#define CIP_DELETE 0x09
#define CIP_MSP 0x0a
#define CIP_APPLY_ATTR 0x0d
#define CIP_GET_ATTR_SINGLE 0x0e
#define CIP_SET_ATTR_SINGLE 0x10
#define CIP_KICK_TIMER 0x4b
#define CIP_OPEN_CONNECTION 0x4c
#define CIP_CHANGE_START 0x4f
#define CIP_GET_STATUS 0x50
//PATH sizing codes
#define PATH_CLASS_8BIT 0x20
#define PATH_CLASS_16BIT 0x21
#define PATH_INSTANCE_8BIT 0x24
#define PATH_INSTANCE_16BIT 0x25
#define PATH_ATTR_8BIT 0x30
#define PATH_ATTR_16BIT 0x31 //possible value
/**
* ENIP encapsulation header
*/
typedef struct ENIPEncapHdr_
{
uint64_t context;
uint32_t session;
uint32_t status;
uint32_t option;
uint16_t command;
uint16_t length;
} ENIPEncapHdr;
/**
* ENIP encapsulation data header
*/
typedef struct ENIPEncapDataHdr_
{
uint32_t interface_handle;
uint16_t timeout;
uint16_t item_count;
} ENIPEncapDataHdr;
/**
* ENIP encapsulation address item
*/
typedef struct ENIPEncapAddresItem_
{
uint16_t type;
uint16_t length;
uint16_t conn_id;
uint16_t sequence_num;
} ENIPEncapAddresItem;
/**
* ENIP encapsulation data item
*/
typedef struct ENIPEncapDataItem_
{
uint16_t type;
uint16_t length;
uint16_t sequence_count;
} ENIPEncapDataItem;
/**
* CIP Request Header
*/
typedef struct CIPReqHdr_
{
uint8_t service;
uint8_t path_size;
} CIPReqHdr;
/**
* CIP Response Header
*/
typedef struct CIPRespHdr_
{
uint8_t service;
uint8_t pad;
uint8_t status;
uint8_t status_size;
} CIPRespHdr;
typedef struct SegmentEntry_
{
uint16_t segment; //segment type
uint16_t value; //segment value (class or attribute)
TAILQ_ENTRY(SegmentEntry_) next;
} SegmentEntry;
typedef struct AttributeEntry_
{
uint16_t attribute; //segment class
TAILQ_ENTRY(AttributeEntry_) next;
} AttributeEntry;
typedef struct CIPServiceEntry_
{
uint8_t service; //cip service
uint8_t direction;
union
{
struct
{
uint8_t path_size; //cip path size
uint16_t path_offset; //offset to cip path
} request;
struct
{
uint8_t status;
} response;
};
TAILQ_HEAD(, SegmentEntry_) segment_list; /**< list for CIP segment */
TAILQ_HEAD(, AttributeEntry_) attrib_list; /**< list for CIP segment */
TAILQ_ENTRY(CIPServiceEntry_) next;
} CIPServiceEntry;
typedef struct ENIPTransaction_
{
struct ENIPState_ *enip;
uint16_t tx_num; /**< internal: id */
uint16_t tx_id; /**< transaction id */
uint16_t service_count; /**< transaction id */
ENIPEncapHdr header; //encapsulation header
ENIPEncapDataHdr encap_data_header; //encapsulation data header
ENIPEncapAddresItem encap_addr_item; //encapsulated address item
ENIPEncapDataItem encap_data_item; //encapsulated data item
TAILQ_HEAD(, CIPServiceEntry_) service_list; /**< list for CIP */
AppLayerDecoderEvents *decoder_events; /**< per tx events */
TAILQ_ENTRY(ENIPTransaction_) next;
DetectEngineState *de_state;
} ENIPTransaction;
/** \brief Per flow ENIP state container */
typedef struct ENIPState_
{
TAILQ_HEAD(, ENIPTransaction_) tx_list; /**< transaction list */
ENIPTransaction *curr; /**< ptr to current tx */
ENIPTransaction *iter;
uint64_t transaction_max;
uint64_t tx_with_detect_state_cnt;
uint16_t events;
uint16_t givenup;
/* used by TCP only */
uint16_t offset;
uint16_t record_len;
uint8_t *buffer;
} ENIPState;
int DecodeENIPPDU(uint8_t *input, uint32_t input_len,
ENIPTransaction *enip_data);
int DecodeCommonPacketFormatPDU(uint8_t *input, uint32_t input_len,
ENIPTransaction *enip_data, uint16_t offset);
int DecodeCIPPDU(uint8_t *input, uint32_t input_len,
ENIPTransaction *enip_data, uint16_t offset);
int DecodeCIPRequestPDU(uint8_t *input, uint32_t input_len,
ENIPTransaction *enip_data, uint16_t offset);
int DecodeCIPResponsePDU(uint8_t *input, uint32_t input_len,
ENIPTransaction *enip_data, uint16_t offset);
int DecodeCIPRequestPathPDU(uint8_t *input, uint32_t input_len,
CIPServiceEntry *node, uint16_t offset);
int DecodeCIPRequestMSPPDU(uint8_t *input, uint32_t input_len,
ENIPTransaction *enip_data, uint16_t offset);
int DecodeCIPResponseMSPPDU(uint8_t *input, uint32_t input_len,
ENIPTransaction *enip_data, uint16_t offset);
#endif /* __APP_LAYER_ENIP_COMMON_H__ */

@ -0,0 +1,605 @@
/* Copyright (C) 2015 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* \author Kevin Wong <kwong@solananetworks.com>
*
* App-layer parser for ENIP protocol
*
*/
#include "suricata-common.h"
#include "util-debug.h"
#include "util-byte.h"
#include "util-enum.h"
#include "util-mem.h"
#include "util-misc.h"
#include "stream.h"
#include "app-layer-protos.h"
#include "app-layer-parser.h"
#include "app-layer-enip.h"
#include "app-layer-enip-common.h"
#include "app-layer-detect-proto.h"
#include "conf.h"
#include "decode.h"
#include "detect-parse.h"
#include "detect-engine.h"
#include "util-byte.h"
#include "util-unittest.h"
#include "util-unittest-helper.h"
#include "pkt-var.h"
#include "util-profiling.h"
SCEnumCharMap enip_decoder_event_table[ ] = {
{ NULL, -1 },
};
/** \brief get value for 'complete' status in ENIP
*
* For ENIP we use a simple bool.
*/
int ENIPGetAlstateProgress(void *tx, uint8_t direction)
{
return 1;
}
/** \brief get value for 'complete' status in ENIP
*
* For ENIP we use a simple bool.
*/
int ENIPGetAlstateProgressCompletionStatus(uint8_t direction)
{
return 1;
}
DetectEngineState *ENIPGetTxDetectState(void *vtx)
{
ENIPTransaction *tx = (ENIPTransaction *)vtx;
return tx->de_state;
}
int ENIPSetTxDetectState(void *state, void *vtx, DetectEngineState *s)
{
ENIPTransaction *tx = (ENIPTransaction *)vtx;
tx->de_state = s;
return 0;
}
void *ENIPGetTx(void *alstate, uint64_t tx_id) {
ENIPState *enip = (ENIPState *) alstate;
ENIPTransaction *tx = NULL;
if (enip->curr && enip->curr->tx_num == tx_id + 1)
return enip->curr;
TAILQ_FOREACH(tx, &enip->tx_list, next) {
if (tx->tx_num != (tx_id+1))
continue;
SCLogDebug("returning tx %p", tx);
return tx;
}
return NULL;
}
uint64_t ENIPGetTxCnt(void *alstate) {
return ((uint64_t) ((ENIPState *) alstate)->transaction_max);
}
AppLayerDecoderEvents *ENIPGetEvents(void *state, uint64_t id) {
ENIPState *enip = (ENIPState *) state;
ENIPTransaction *tx;
if (enip->curr && enip->curr->tx_num == (id + 1))
return enip->curr->decoder_events;
TAILQ_FOREACH(tx, &enip->tx_list, next) {
if (tx->tx_num == (id+1))
return tx->decoder_events;
}
return NULL;
}
int ENIPHasEvents(void *state) {
return (((ENIPState *) state)->events > 0);
}
int ENIPStateGetEventInfo(const char *event_name, int *event_id, AppLayerEventType *event_type) {
*event_id = SCMapEnumNameToValue(event_name, enip_decoder_event_table);
if (*event_id == -1) {
SCLogError(SC_ERR_INVALID_ENUM_MAP, "event \"%s\" not present in "
"enip's enum map table.", event_name);
/* yes this is fatal */
return -1;
}
*event_type = APP_LAYER_EVENT_TYPE_TRANSACTION;
return 0;
}
/** \brief Allocate enip state
*
* return state
*/
void *ENIPStateAlloc(void)
{
SCLogDebug("ENIPStateAlloc \n");
void *s = SCMalloc(sizeof(ENIPState));
if (unlikely(s == NULL))
return NULL;
memset(s, 0, sizeof(ENIPState));
ENIPState *enip_state = (ENIPState *) s;
TAILQ_INIT(&enip_state->tx_list);
return s;
}
/** \internal
* \brief Free a ENIP TX
* \param tx ENIP TX to free */
static void ENIPTransactionFree(ENIPTransaction *tx, ENIPState *state)
{
SCEnter();
SCLogDebug("ENIPTransactionFree \n");
CIPServiceEntry *svc = NULL;
while ((svc = TAILQ_FIRST(&tx->service_list)))
{
TAILQ_REMOVE(&tx->service_list, svc, next);
SegmentEntry *seg = NULL;
while ((seg = TAILQ_FIRST(&svc->segment_list)))
{
TAILQ_REMOVE(&svc->segment_list, seg, next);
SCFree(seg);
}
AttributeEntry *attr = NULL;
while ((attr = TAILQ_FIRST(&svc->attrib_list)))
{
TAILQ_REMOVE(&svc->attrib_list, attr, next);
SCFree(attr);
}
SCFree(svc);
}
AppLayerDecoderEventsFreeEvents(&tx->decoder_events);
if (tx->de_state != NULL)
{
DetectEngineStateFree(tx->de_state);
state->tx_with_detect_state_cnt--;
}
if (state->iter == tx)
state->iter = NULL;
SCFree(tx);
SCReturn;
}
/** \brief Free enip state
*
*/
void ENIPStateFree(void *s)
{
SCEnter();
SCLogDebug("ENIPStateFree \n");
if (s)
{
ENIPState *enip_state = (ENIPState *) s;
ENIPTransaction *tx = NULL;
while ((tx = TAILQ_FIRST(&enip_state->tx_list)))
{
TAILQ_REMOVE(&enip_state->tx_list, tx, next);
ENIPTransactionFree(tx, enip_state);
}
if (enip_state->buffer != NULL)
{
SCFree(enip_state->buffer);
}
SCFree(s);
}
SCReturn;
}
/** \internal
* \brief Allocate a ENIP TX
* \retval tx or NULL */
static ENIPTransaction *ENIPTransactionAlloc(ENIPState *state)
{
SCLogDebug("ENIPStateTransactionAlloc \n");
ENIPTransaction *tx = (ENIPTransaction *) SCCalloc(1,
sizeof(ENIPTransaction));
if (unlikely(tx == NULL))
return NULL;
state->curr = tx;
state->transaction_max++;
memset(tx, 0x00, sizeof(ENIPTransaction));
TAILQ_INIT(&tx->service_list);
tx->enip = state;
tx->tx_num = state->transaction_max;
tx->service_count = 0;
TAILQ_INSERT_TAIL(&state->tx_list, tx, next);
return tx;
}
/**
* \brief enip transaction cleanup callback
*/
void ENIPStateTransactionFree(void *state, uint64_t tx_id)
{
SCEnter();
SCLogDebug("ENIPStateTransactionFree \n");
ENIPState *enip_state = state;
ENIPTransaction *tx = NULL;
TAILQ_FOREACH(tx, &enip_state->tx_list, next)
{
if ((tx_id+1) < tx->tx_num)
break;
else if ((tx_id+1) > tx->tx_num)
continue;
if (tx == enip_state->curr)
enip_state->curr = NULL;
if (tx->decoder_events != NULL)
{
if (tx->decoder_events->cnt <= enip_state->events)
enip_state->events -= tx->decoder_events->cnt;
else
enip_state->events = 0;
}
TAILQ_REMOVE(&enip_state->tx_list, tx, next);
ENIPTransactionFree(tx, state);
break;
}
SCReturn;
}
/** \internal
*
* \brief This function is called to retrieve a ENIP
*
* \param state ENIP state structure for the parser
* \param input Input line of the command
* \param input_len Length of the request
*
* \retval 1 when the command is parsed, 0 otherwise
*/
static int ENIPParse(Flow *f, void *state, AppLayerParserState *pstate,
uint8_t *input, uint32_t input_len, void *local_data)
{
SCEnter();
ENIPState *enip = (ENIPState *) state;
ENIPTransaction *tx;
if (input == NULL && AppLayerParserStateIssetFlag(pstate,
APP_LAYER_PARSER_EOF))
{
SCReturnInt(1);
} else if (input == NULL || input_len == 0)
{
SCReturnInt(-1);
}
while (input_len > 0)
{
tx = ENIPTransactionAlloc(enip);
if (tx == NULL)
SCReturnInt(0);
SCLogDebug("ENIPParse input len %d\n", input_len);
DecodeENIPPDU(input, input_len, tx);
uint32_t pkt_len = tx->header.length + sizeof(ENIPEncapHdr);
SCLogDebug("ENIPParse packet len %d\n", pkt_len);
if (pkt_len > input_len)
{
SCLogDebug("Invalid packet length \n");
break;
}
input += pkt_len;
input_len -= pkt_len;
//SCLogDebug("remaining %d\n", input_len);
if (input_len < sizeof(ENIPEncapHdr))
{
//SCLogDebug("Not enough data\n"); //not enough data for ENIP
break;
}
}
return 1;
}
static uint16_t ENIPProbingParser(uint8_t *input, uint32_t input_len,
uint32_t *offset)
{
// SCLogDebug("ENIPProbingParser %d\n", input_len);
if (input_len < sizeof(ENIPEncapHdr))
{
printf("Length too small to be a ENIP header \n");
return ALPROTO_UNKNOWN;
}
return ALPROTO_ENIP;
}
/**
* \brief Function to register the ENIP protocol parsers and other functions
*/
void RegisterENIPUDPParsers(void)
{
SCEnter();
char *proto_name = "enip";
if (AppLayerProtoDetectConfProtoDetectionEnabled("udp", proto_name))
{
AppLayerProtoDetectRegisterProtocol(ALPROTO_ENIP, proto_name);
if (RunmodeIsUnittests())
{
AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818", ALPROTO_ENIP,
0, sizeof(ENIPEncapHdr), STREAM_TOSERVER, ENIPProbingParser);
AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818", ALPROTO_ENIP,
0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT, ENIPProbingParser);
} else
{
if (!AppLayerProtoDetectPPParseConfPorts("udp", IPPROTO_UDP,
proto_name, ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr),
ENIPProbingParser))
{
SCLogDebug(
"no ENIP UDP config found enabling ENIP detection on port 44818.");
AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818",
ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), STREAM_TOSERVER,
ENIPProbingParser);
AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818",
ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT,
ENIPProbingParser);
}
}
} else
{
printf("Protocol detection and parser disabled for %s protocol.",
proto_name);
return;
}
if (AppLayerParserConfParserEnabled("udp", proto_name))
{
AppLayerParserRegisterParser(IPPROTO_UDP, ALPROTO_ENIP,
STREAM_TOSERVER, ENIPParse);
AppLayerParserRegisterParser(IPPROTO_UDP, ALPROTO_ENIP,
STREAM_TOCLIENT, ENIPParse);
AppLayerParserRegisterStateFuncs(IPPROTO_UDP, ALPROTO_ENIP,
ENIPStateAlloc, ENIPStateFree);
AppLayerParserRegisterGetEventsFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetEvents);
AppLayerParserRegisterHasEventsFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPHasEvents);
AppLayerParserRegisterDetectStateFuncs(IPPROTO_UDP, ALPROTO_ENIP, NULL,
ENIPGetTxDetectState, ENIPSetTxDetectState);
AppLayerParserRegisterGetTx(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTx);
AppLayerParserRegisterGetTxCnt(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTxCnt);
AppLayerParserRegisterTxFreeFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPStateTransactionFree);
AppLayerParserRegisterGetStateProgressFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetAlstateProgress);
AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_ENIP, ENIPGetAlstateProgressCompletionStatus);
AppLayerParserRegisterGetEventInfo(IPPROTO_UDP, ALPROTO_ENIP, ENIPStateGetEventInfo);
AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_UDP,
ALPROTO_ENIP, STREAM_TOSERVER | STREAM_TOCLIENT);
} else
{
SCLogInfo(
"Parsed disabled for %s protocol. Protocol detection" "still on.",
proto_name);
}
#ifdef UNITTESTS
AppLayerParserRegisterProtocolUnittests(IPPROTO_UDP, ALPROTO_ENIP, ENIPParserRegisterTests);
#endif
SCReturn;
}
/**
* \brief Function to register the ENIP protocol parsers and other functions
*/
void RegisterENIPTCPParsers(void)
{
SCEnter();
char *proto_name = "enip";
if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name))
{
AppLayerProtoDetectRegisterProtocol(ALPROTO_ENIP, proto_name);
if (RunmodeIsUnittests())
{
AppLayerProtoDetectPPRegister(IPPROTO_TCP, "44818", ALPROTO_ENIP,
0, sizeof(ENIPEncapHdr), STREAM_TOSERVER, ENIPProbingParser);
AppLayerProtoDetectPPRegister(IPPROTO_TCP, "44818", ALPROTO_ENIP,
0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT, ENIPProbingParser);
} else
{
if (!AppLayerProtoDetectPPParseConfPorts("tcp", IPPROTO_TCP,
proto_name, ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr),
ENIPProbingParser))
{
SCLogDebug(
"no ENIP TCP config found enabling ENIP detection on port 44818.");
AppLayerProtoDetectPPRegister(IPPROTO_TCP, "44818",
ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), STREAM_TOSERVER,
ENIPProbingParser);
AppLayerProtoDetectPPRegister(IPPROTO_TCP, "44818",
ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT,
ENIPProbingParser);
}
}
} else
{
SCLogDebug("Protocol detection and parser disabled for %s protocol.",
proto_name);
return;
}
if (AppLayerParserConfParserEnabled("tcp", proto_name))
{
AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_ENIP,
STREAM_TOSERVER, ENIPParse);
AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_ENIP,
STREAM_TOCLIENT, ENIPParse);
AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_ENIP,
ENIPStateAlloc, ENIPStateFree);
AppLayerParserRegisterGetEventsFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetEvents);
AppLayerParserRegisterHasEventsFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPHasEvents);
AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_ENIP, NULL,
ENIPGetTxDetectState, ENIPSetTxDetectState);
AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTx);
AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTxCnt);
AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPStateTransactionFree);
AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetAlstateProgress);
AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_ENIP, ENIPGetAlstateProgressCompletionStatus);
AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_ENIP, ENIPStateGetEventInfo);
AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP,
ALPROTO_ENIP, STREAM_TOSERVER | STREAM_TOCLIENT);
} else
{
SCLogInfo(
"Parsed disabled for %s protocol. Protocol detection" "still on.",
proto_name);
}
#ifdef UNITTESTS
AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_ENIP, ENIPParserRegisterTests);
#endif
SCReturn;
}
/* UNITTESTS */
#ifdef UNITTESTS
#include "app-layer-parser.h"
#include "detect-parse.h"
#include "detect-engine.h"
#include "flow-util.h"
#include "stream-tcp.h"
#include "util-unittest.h"
#include "util-unittest-helper.h"
static uint8_t listIdentity[] = {/* List ID */ 0x63, 0x00,
/* Length */ 0x00, 0x00,
/* Session */ 0x00, 0x00, 0x00, 0x00,
/* Status */ 0x00, 0x00, 0x00, 0x00,
/* Delay*/ 0x00,
/* Context */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* Quantity of coils */ 0x00, 0x00, 0x00, 0x00, 0x00};
/**
* \brief Test if ENIP Packet matches signature
*/
int ALDecodeENIPTest(void)
{
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
Flow f;
TcpSession ssn;
memset(&f, 0, sizeof(f));
memset(&ssn, 0, sizeof(ssn));
f.protoctx = (void *)&ssn;
f.proto = IPPROTO_TCP;
StreamTcpInitConfig(TRUE);
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_ENIP, STREAM_TOSERVER,
listIdentity, sizeof(listIdentity));
FAIL_IF(r != 0);
ENIPState *enip_state = f.alstate;
FAIL_IF_NULL(enip_state);
ENIPTransaction *tx = ENIPGetTx(enip_state, 0);
FAIL_IF_NULL(tx);
FAIL_IF(tx->header.command != 99);
AppLayerParserThreadCtxFree(alp_tctx);
StreamTcpFreeConfig(TRUE);
FLOW_DESTROY(&f);
PASS;
}
#endif /* UNITTESTS */
void ENIPParserRegisterTests(void)
{
#ifdef UNITTESTS
UtRegisterTest("ALDecodeENIPTest", ALDecodeENIPTest);
#endif /* UNITTESTS */
}

@ -0,0 +1,35 @@
/* Copyright (C) 2015 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* \author Kevin Wong <kwong@solananetworks.com>
*/
#ifndef __APP_LAYER_ENIP_H__
#define __APP_LAYER_ENIP_H__
#include "decode.h"
#include "detect-engine-state.h"
#include "queue.h"
void RegisterENIPUDPParsers(void);
void RegisterENIPTCPParsers(void);
void ENIPParserRegisterTests(void);
#endif /* __APP_LAYER_ENIP_H__ */

@ -58,6 +58,7 @@
#include "app-layer-dns-udp.h"
#include "app-layer-dns-tcp.h"
#include "app-layer-modbus.h"
#include "app-layer-enip.h"
#include "app-layer-template.h"
#include "conf.h"
@ -1191,6 +1192,8 @@ void AppLayerParserRegisterProtocolParsers(void)
RegisterDNSUDPParsers();
RegisterDNSTCPParsers();
RegisterModbusParsers();
RegisterENIPUDPParsers();
RegisterENIPTCPParsers();
RegisterTemplateParsers();
/** IMAP */

@ -75,6 +75,9 @@ const char *AppProtoToString(AppProto alproto)
case ALPROTO_MODBUS:
proto_name = "modbus";
break;
case ALPROTO_ENIP:
proto_name = "enip";
break;
case ALPROTO_TEMPLATE:
proto_name = "template";
break;

@ -42,6 +42,7 @@ enum AppProtoEnum {
ALPROTO_DNS,
ALPROTO_MODBUS,
ALPROTO_ENIP,
ALPROTO_TEMPLATE,
/* used by the probing parser when alproto detection fails

@ -0,0 +1,474 @@
/* Copyright (C) 2015 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* \author Kevin Wong <kwong@solananetworks.com>
*
* Set up ENIP Commnad and CIP Service rule parsing and entry point for matching
*/
#include "suricata-common.h"
#include "util-unittest.h"
#include "detect-parse.h"
#include "detect-engine.h"
#include "util-byte.h"
#include "detect-cipservice.h"
/*
* CIP SERVICE CODE
*/
/**
* \brief CIP Service Detect Prototypes
*/
static int DetectCipServiceSetup(DetectEngineCtx *, Signature *, char *);
static void DetectCipServiceFree(void *);
static void DetectCipServiceRegisterTests(void);
/**
* \brief Registration function for cip_service: keyword
*/
void DetectCipServiceRegister(void)
{
SCEnter();
sigmatch_table[DETECT_CIPSERVICE].name = "cip_service"; //rule keyword
sigmatch_table[DETECT_CIPSERVICE].desc = "Rules for detecting CIP Service ";
sigmatch_table[DETECT_CIPSERVICE].Match = NULL;
sigmatch_table[DETECT_CIPSERVICE].AppLayerMatch = NULL;
sigmatch_table[DETECT_CIPSERVICE].Setup = DetectCipServiceSetup;
sigmatch_table[DETECT_CIPSERVICE].Free = DetectCipServiceFree;
sigmatch_table[DETECT_CIPSERVICE].RegisterTests
= DetectCipServiceRegisterTests;
SCReturn;
}
/**
* \brief This function is used to parse cip_service options passed via cip_service: keyword
*
* \param rulestr Pointer to the user provided rulestr options
* Takes comma seperated string with numeric tokens. Only first 3 are used
*
* \retval cipserviced pointer to DetectCipServiceData on success
* \retval NULL on failure
*/
DetectCipServiceData *DetectCipServiceParse(char *rulestr)
{
const char delims[] = ",";
DetectCipServiceData *cipserviced = NULL;
//SCLogDebug("DetectCipServiceParse - rule string %s\n", rulestr);
cipserviced = SCMalloc(sizeof(DetectCipServiceData));
if (unlikely(cipserviced == NULL))
goto error;
cipserviced->cipservice = 0;
cipserviced->cipclass = 0;
cipserviced->matchattribute = 1;
cipserviced->cipattribute = 0;
char* token;
char *save;
int var;
int input[3];
int i = 0;
token = strtok_r(rulestr, delims, &save);
while (token != NULL)
{
if (i > 2) //for now only need 3 parameters
{
printf("DetectEnipCommandParse: Too many parameters\n");
goto error;
}
if (i < 2) //if on service or class
{
if (!isdigit((int) *token))
{
printf("DetectCipServiceParse - Parameter Error %s\n", token);
goto error;
}
} else //if on attribute
{
if (token[0] == '!')
{
cipserviced->matchattribute = 0;
token++;
}
if (!isdigit((int) *token))
{
printf("DetectCipServiceParse - Attribute Error %s\n", token);
goto error;
}
}
unsigned long num = atol(token);
if ((num > MAX_CIP_SERVICE) && (i == 0))//if service greater than 7 bit
{
printf("DetectEnipCommandParse: Invalid CIP service %lu\n", num);
goto error;
} else if ((num > MAX_CIP_CLASS) && (i == 1))//if service greater than 16 bit
{
printf("DetectEnipCommandParse: Invalid CIP class %lu\n", num);
goto error;
} else if ((num > MAX_CIP_ATTRIBUTE) && (i == 2))//if service greater than 16 bit
{
printf("DetectEnipCommandParse: Invalid CIP attribute %lu\n", num);
goto error;
}
sscanf(token, "%d", &var);
input[i++] = var;
token = strtok_r(NULL, delims, &save);
}
cipserviced->cipservice = input[0];
cipserviced->cipclass = input[1];
cipserviced->cipattribute = input[2];
cipserviced->tokens = i;
SCLogDebug("DetectCipServiceParse - tokens %d\n", cipserviced->tokens);
SCLogDebug("DetectCipServiceParse - service %d\n", cipserviced->cipservice);
SCLogDebug("DetectCipServiceParse - class %d\n", cipserviced->cipclass);
SCLogDebug("DetectCipServiceParse - match attribute %d\n",
cipserviced->matchattribute);
SCLogDebug("DetectCipServiceParse - attribute %d\n",
cipserviced->cipattribute);
SCReturnPtr(cipserviced, "DetectENIPFunction");
error:
if (cipserviced)
SCFree(cipserviced);
printf("DetectCipServiceParse - Error Parsing Parameters\n");
SCReturnPtr(NULL, "DetectENIP");
}
/**
* \brief this function is used to a cipserviced the parsed cip_service data into the current signature
*
* \param de_ctx pointer to the Detection Engine Context
* \param s pointer to the Current Signature
* \param rulestr pointer to the user provided cip_service options
*
* \retval 0 on Success
* \retval -1 on Failure
*/
static int DetectCipServiceSetup(DetectEngineCtx *de_ctx, Signature *s,
char *rulestr)
{
SCEnter();
DetectCipServiceData *cipserviced = NULL;
SigMatch *sm = NULL;
if (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_ENIP)
{
SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS,
"rule contains conflicting keywords.");
goto error;
}
cipserviced = DetectCipServiceParse(rulestr);
if (cipserviced == NULL)
goto error;
sm = SigMatchAlloc();
if (sm == NULL)
goto error;
sm->type = DETECT_CIPSERVICE;
sm->ctx = (void *) cipserviced;
s->alproto = ALPROTO_ENIP;
SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_CIP_MATCH);
SCReturnInt(0);
error:
if (cipserviced != NULL)
DetectCipServiceFree(cipserviced);
if (sm != NULL)
SCFree(sm);
printf("DetectCipServiceSetup - Error\n");
SCReturnInt(-1);
}
/**
* \brief this function will free memory associated with DetectCipServiceData
*
* \param ptr pointer to DetectCipServiceData
*/
void DetectCipServiceFree(void *ptr)
{
DetectCipServiceData *cipserviced = (DetectCipServiceData *) ptr;
SCFree(cipserviced);
}
#ifdef UNITTESTS
/**
* \test Test CIP Command parameter parsing
*/
static int DetectCipServiceParseTest01 (void)
{
uint8_t res = 1;
/*DetectCipServiceData *cipserviced = NULL;
cipserviced = DetectCipServiceParse("1");
if (cipserviced != NULL)
{
if (cipserviced->cipservice == 1)
{
res = 1;
}
DetectCipServiceFree(cipserviced);
}
*/
return res;
}
/**
* \test Test CIP Service signature
*/
static int DetectCipServiceSignatureTest01 (void)
{
uint8_t res = 0;
DetectEngineCtx *de_ctx = DetectEngineCtxInit();
if (de_ctx == NULL)
goto end;
Signature *sig = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any (cip_service:1; sid:1; rev:1;)");
if (sig == NULL)
{
printf("parsing signature failed: ");
goto end;
}
/* if we get here, all conditions pass */
res = 1;
end:
if (de_ctx != NULL)
DetectEngineCtxFree(de_ctx);
return res;
}
#endif /* UNITTESTS */
/**
* \brief this function registers unit tests for DetectCipService
*/
void DetectCipServiceRegisterTests(void)
{
#ifdef UNITTESTS
UtRegisterTest("DetectCipServiceParseTest01",
DetectCipServiceParseTest01);
UtRegisterTest("DetectCipServiceSignatureTest01",
DetectCipServiceSignatureTest01);
#endif /* UNITTESTS */
}
/*
* ENIP COMMAND CODE
*/
/**
* \brief ENIP Commond Detect Prototypes
*/
static int DetectEnipCommandSetup(DetectEngineCtx *, Signature *, char *);
static void DetectEnipCommandFree(void *);
static void DetectEnipCommandRegisterTests(void);
/**
* \brief Registration function for enip_command: keyword
*/
void DetectEnipCommandRegister(void)
{
sigmatch_table[DETECT_ENIPCOMMAND].name = "enip_command"; //rule keyword
sigmatch_table[DETECT_ENIPCOMMAND].desc
= "Rules for detecting EtherNet/IP command";
sigmatch_table[DETECT_ENIPCOMMAND].Match = NULL;
sigmatch_table[DETECT_ENIPCOMMAND].AppLayerMatch = NULL;
sigmatch_table[DETECT_ENIPCOMMAND].Setup = DetectEnipCommandSetup;
sigmatch_table[DETECT_ENIPCOMMAND].Free = DetectEnipCommandFree;
sigmatch_table[DETECT_ENIPCOMMAND].RegisterTests
= DetectEnipCommandRegisterTests;
}
/**
* \brief This function is used to parse cip_service options passed via enip_command: keyword
*
* \param rulestr Pointer to the user provided rulestr options
* Takes single single numeric value
*
* \retval enipcmdd pointer to DetectCipServiceData on success
* \retval NULL on failure
*/
DetectEnipCommandData *DetectEnipCommandParse(char *rulestr)
{
DetectEnipCommandData *enipcmdd = NULL;
enipcmdd = SCMalloc(sizeof(DetectEnipCommandData));
if (unlikely(enipcmdd == NULL))
goto error;
if (isdigit((int) *rulestr))
{
unsigned long cmd = atol(rulestr);
if (cmd > MAX_ENIP_CMD) //if command greater than 16 bit
{
//printf("DetectEnipCommandParse: Invalid ENIP command %lu\n", cmd);
goto error;
}
enipcmdd->enipcommand = (uint16_t) atoi(rulestr);
} else
{
goto error;
}
return enipcmdd;
error:
if (enipcmdd)
SCFree(enipcmdd);
//printf("DetectEnipCommandParse - Error Parsing Parameters\n");
return NULL;
}
/**
* \brief this function is used by enipcmdd to parse enip_command data into the current signature
*
* \param de_ctx pointer to the Detection Engine Context
* \param s pointer to the Current Signature
* \param rulestr pointer to the user provided enip command options
*
* \retval 0 on Success
* \retval -1 on Failure
*/
static int DetectEnipCommandSetup(DetectEngineCtx *de_ctx, Signature *s,
char *rulestr)
{
DetectEnipCommandData *enipcmdd = NULL;
SigMatch *sm = NULL;
if (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_ENIP)
{
SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS,
"rule contains conflicting keywords.");
goto error;
}
enipcmdd = DetectEnipCommandParse(rulestr);
if (enipcmdd == NULL)
goto error;
sm = SigMatchAlloc();
if (sm == NULL)
goto error;
sm->type = DETECT_ENIPCOMMAND;
sm->ctx = (void *) enipcmdd;
s->alproto = ALPROTO_ENIP;
SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_ENIP_MATCH);
SCReturnInt(0);
error:
if (enipcmdd != NULL)
DetectEnipCommandFree(enipcmdd);
if (sm != NULL)
SCFree(sm);
printf("DetectEnipCommandSetup - Error\n");
SCReturnInt(-1);
}
/**
* \brief this function will free memory associated with DetectEnipCommandData
*
* \param ptr pointer to DetectEnipCommandData
*/
void DetectEnipCommandFree(void *ptr)
{
DetectEnipCommandData *enipcmdd = (DetectEnipCommandData *) ptr;
SCFree(enipcmdd);
}
#ifdef UNITTESTS
/**
* \test ENIP parameter test
*/
static int DetectEnipCommandParseTest01 (void)
{
DetectEnipCommandData *enipcmdd = NULL;
enipcmdd = DetectEnipCommandParse("1");
FAIL_IF_NULL(enipcmdd);
FAIL_IF_NOT(enipcmdd->enipcommand == 1);
DetectEnipCommandFree(enipcmdd);
PASS;
}
/**
* \test ENIP Command signature test
*/
static int DetectEnipCommandSignatureTest01 (void)
{
DetectEngineCtx *de_ctx = DetectEngineCtxInit();
FAIL_IF_NULL(de_ctx);
Signature *sig = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any (enip_command:1; sid:1; rev:1;)");
FAIL_IF_NULL(sig);
DetectEngineCtxFree(de_ctx);
PASS;
}
#endif /* UNITTESTS */
/**
* \brief this function registers unit tests for DetectEnipCommand
*/
void DetectEnipCommandRegisterTests(void)
{
#ifdef UNITTESTS
UtRegisterTest("DetectEnipCommandParseTest01",
DetectEnipCommandParseTest01);
UtRegisterTest("DetectEnipCommandSignatureTest01",
DetectEnipCommandSignatureTest01);
#endif /* UNITTESTS */
}

@ -0,0 +1,100 @@
/* Copyright (C) 2015 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* \author Kevin Wong <kwong@solananetworks.com>
*/
#ifndef _DETECT_CIPSERVICE_H
#define _DETECT_CIPSERVICE_H
#include "app-layer-protos.h"
#include "app-layer-parser.h"
#include "flow.h"
#include "queue.h"
#include "app-layer-enip-common.h"
#define ENIP_PORT 44818 //standard EtherNet/IP port
/**
* CIP Service rule data structure
*/
typedef struct DetectCipServiceData_
{
uint8_t cipservice; /* cip service type */
uint16_t cipclass;
uint16_t cipattribute;
uint8_t matchattribute; /* whether to match on attribute*/
uint8_t tokens; /* number of parameters*/
} DetectCipServiceData;
/**
* ENIP Command rule data structure
*/
typedef struct DetectEnipCommandData_
{
uint16_t enipcommand; /* enip command */
} DetectEnipCommandData;
void DetectCipServiceRegister(void);
void DetectEnipCommandRegister(void);
/**
* link list node for storing CIP service data
*/
typedef struct CIPServiceData_
{
uint8_t service; //cip service
union
{
struct
{
uint8_t path_size; //cip path size
uint16_t path_offset; //offset to cip path
} request;
struct
{
uint8_t status;
} response;
};
struct CIPServiceData* next;
} CIPServiceData;
/**
* ENIP data structure
*/
typedef struct ENIPData_
{
int direction;
ENIPEncapHdr header; //encapsulation header
ENIPEncapDataHdr encap_data_header; //encapsulation data header
ENIPEncapAddresItem encap_addr_item; //encapsulated address item
ENIPEncapDataItem encap_data_item; //encapsulated data item
CIPServiceData* service_head; //head of cip service data list
CIPServiceData* service_tail; //tail of cip service data list
} ENIPData;
/**
* Add new CIPServiceData node to link list
*/
CIPServiceData *CreateCIPServiceData(ENIPData *enip_data);
#endif /* _DETECT_CIPSERVICE_H */

@ -0,0 +1,375 @@
/* Copyright (C) 2015 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/** \file
*
* \author Kevin Wong <kwong@solananetworks.com>
*
* Based on detect-engine-modbus.c
*/
#include "suricata-common.h"
#include "app-layer.h"
#include "detect.h"
#include "detect-cipservice.h"
#include "detect-engine-enip.h"
#include "flow.h"
#include "util-debug.h"
/**
* \brief Print fields from ENIP Packet
* @param enip_data
*/
void PrintENIPAL(ENIPTransaction *enip_data)
{
SCLogDebug("============================================\n");
SCLogDebug("ENCAP HEADER cmd 0x%x, length %d, session 0x%x, status 0x%x\n",
enip_data->header.command, enip_data->header.length,
enip_data->header.session, enip_data->header.status);
//SCLogDebug("context 0x%x option 0x%x\n", enip_data->header.context, enip_data->header.option);
SCLogDebug("ENCAP DATA HEADER handle 0x%x, timeout %d, count %d\n",
enip_data->encap_data_header.interface_handle,
enip_data->encap_data_header.timeout,
enip_data->encap_data_header.item_count);
SCLogDebug("ENCAP ADDR ITEM type 0x%x, length %d \n",
enip_data->encap_addr_item.type, enip_data->encap_addr_item.length);
SCLogDebug("ENCAP DATA ITEM type 0x%x, length %d sequence 0x%x\n",
enip_data->encap_data_item.type, enip_data->encap_data_item.length,
enip_data->encap_data_item.sequence_count);
CIPServiceEntry *svc = NULL;
int count = 0;
TAILQ_FOREACH(svc, &enip_data->service_list, next)
{
//SCLogDebug("CIP Service #%d : 0x%x\n", count, svc->service);
count++;
}
}
/**
* \brief Matches the rule to the CIP segment in ENIP Packet
* @param svc - the CIP service entry
* * @param cipserviced - the CIP service rule
*/
int CIPPathMatch(CIPServiceEntry *svc, DetectCipServiceData *cipserviced)
{
uint16_t class = 0;
uint16_t attrib = 0;
int found_class = 0;
SegmentEntry *seg = NULL;
TAILQ_FOREACH(seg, &svc->segment_list, next)
{
switch(seg->segment)
{
case PATH_CLASS_8BIT:
class = seg->value;
if (cipserviced->cipclass == class)
{
if (cipserviced->tokens == 2)
{// if rule only has class
return 1;
} else
{
found_class = 1;
}
}
break;
case PATH_INSTANCE_8BIT:
break;
case PATH_ATTR_8BIT: //single attribute
attrib = seg->value;
if ((cipserviced->tokens == 3) &&
(cipserviced->cipclass == class) &&
(cipserviced->cipattribute == attrib) &&
(cipserviced->matchattribute == 1))
{ // if rule has class & attribute, matched all here
return 1;
}
if ((cipserviced->tokens == 3) &&
(cipserviced->cipclass == class) &&
(cipserviced->matchattribute == 0))
{ // for negation rule on attribute
return 1;
}
break;
case PATH_CLASS_16BIT:
class = seg->value;
if (cipserviced->cipclass == class)
{
if (cipserviced->tokens == 2)
{// if rule only has class
return 1;
} else
{
found_class = 1;
}
}
break;
case PATH_INSTANCE_16BIT:
break;
default:
return 0;
}
}
if (found_class == 0)
{ // if haven't matched class yet, no need to check attribute
return 0;
}
if ((svc->service == CIP_SET_ATTR_LIST) ||
(svc->service == CIP_GET_ATTR_LIST))
{
AttributeEntry *attr = NULL;
TAILQ_FOREACH (attr, &svc->attrib_list, next)
{
if (cipserviced->cipattribute == attr->attribute)
{
return 1;
}
}
}
return 0;
}
/**
* \brief Matches the rule to the ENIP Transaction
* @param enip_data - the ENIP transation
* * @param cipserviced - the CIP service rule
*/
int CIPServiceMatch(ENIPTransaction *enip_data,
DetectCipServiceData *cipserviced)
{
int count = 1;
CIPServiceEntry *svc = NULL;
//SCLogDebug("CIPServiceMatchAL\n");
TAILQ_FOREACH(svc, &enip_data->service_list, next)
{
SCLogDebug("CIPServiceMatchAL service #%d : 0x%x dir %d \n", count, svc->service, svc->direction);
if (cipserviced->cipservice == svc->service)
{ // compare service
//SCLogDebug("Rule Match for cip service %d\n",cipserviced->cipservice );
if (cipserviced->tokens > 1)
{ //if rule params have class and attribute
if ((svc->service == CIP_SET_ATTR_LIST) || (svc->service
== CIP_SET_ATTR_SINGLE) || (svc->service
== CIP_GET_ATTR_LIST) || (svc->service
== CIP_GET_ATTR_SINGLE))
{ //decode path
if (CIPPathMatch(svc, cipserviced) == 1)
{
if (svc->direction == 1) return 0; //don't match responses
return 1;
}
}
} else
{
if (svc->direction == 1) return 0; //don't match responses
// SCLogDebug("CIPServiceMatchAL found\n");
return 1;
}
}
count++;
}
return 0;
}
/** \brief Do the content inspection & validation for a signature
*
* \param de_ctx Detection engine context
* \param det_ctx Detection engine thread context
* \param s Signature to inspect ( and sm: SigMatch to inspect)
* \param f Flow
* \param flags App layer flags
* \param alstate App layer state
* \param txv Pointer to ENIP Transaction structure
*
* \retval 0 no match or 1 match
*/
int DetectEngineInspectCIP(ThreadVars *tv, DetectEngineCtx *de_ctx,
DetectEngineThreadCtx *det_ctx, Signature *s, Flow *f, uint8_t flags,
void *alstate, void *txv, uint64_t tx_id)
{
SCEnter();
ENIPTransaction *tx = (ENIPTransaction *) txv;
SigMatch *sm = s->sm_lists[DETECT_SM_LIST_CIP_MATCH];
DetectCipServiceData *cipserviced = (DetectCipServiceData *) sm->ctx;
if (cipserviced == NULL)
{
SCLogDebug("no cipservice state, no match");
SCReturnInt(0);
}
// SCLogDebug("DetectEngineInspectCIP %d\n", cipserviced->cipservice);
if (CIPServiceMatch(tx, cipserviced) == 1)
{
// SCLogDebug("DetectCIPServiceMatchAL found\n");
SCReturnInt(1);
}
SCReturnInt(0);
}
/** \brief Do the content inspection & validation for a signature
*
* \param de_ctx Detection engine context
* \param det_ctx Detection engine thread context
* \param s Signature to inspect ( and sm: SigMatch to inspect)
* \param f Flow
* \param flags App layer flags
* \param alstate App layer state
* \param txv Pointer to ENIP Transaction structure
*
* \retval 0 no match or 1 match
*/
int DetectEngineInspectENIP(ThreadVars *tv, DetectEngineCtx *de_ctx,
DetectEngineThreadCtx *det_ctx, Signature *s, Flow *f, uint8_t flags,
void *alstate, void *txv, uint64_t tx_id)
{
SCEnter();
ENIPTransaction *tx = (ENIPTransaction *) txv;
SigMatch *sm = s->sm_lists[DETECT_SM_LIST_ENIP_MATCH];
DetectEnipCommandData *enipcmdd = (DetectEnipCommandData *) sm->ctx;
if (enipcmdd == NULL)
{
SCLogDebug("no enipcommand state, no match");
SCReturnInt(0);
}
//SCLogDebug("DetectEngineInspectENIP %d, %d\n", enipcmdd->enipcommand, tx->header.command);
if (enipcmdd->enipcommand == tx->header.command)
{
// SCLogDebug("DetectENIPCommandMatchAL found!\n");
SCReturnInt(1);
}
SCReturnInt(0);
}
#ifdef UNITTESTS /* UNITTESTS */
#include "app-layer-parser.h"
#include "detect-parse.h"
#include "detect-engine.h"
#include "flow-util.h"
#include "stream-tcp.h"
#include "util-unittest.h"
#include "util-unittest-helper.h"
static uint8_t listIdentity[] = {/* List ID */ 0x00, 0x63,
/* Length */ 0x00, 0x00,
/* Session */ 0x00, 0x00, 0x00, 0x00,
/* Status */ 0x00, 0x00, 0x00, 0x00,
/* Delay*/ 0x00,
/* Context */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* Quantity of coils */ 0x00, 0x00, 0x00, 0x00,};
/** \test Test code function. */
static int DetectEngineInspectENIPTest01(void)
{
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
DetectEngineThreadCtx *det_ctx = NULL;
DetectEngineCtx *de_ctx = NULL;
Flow f;
Packet *p = NULL;
Signature *s = NULL;
TcpSession ssn;
ThreadVars tv;
memset(&tv, 0, sizeof(ThreadVars));
memset(&f, 0, sizeof(Flow));
memset(&ssn, 0, sizeof(TcpSession));
p = UTHBuildPacket(listIdentity, sizeof(listIdentity), IPPROTO_TCP);
FAIL_IF_NULL(p);
FLOW_INITIALIZE(&f);
f.alproto = ALPROTO_ENIP;
f.protoctx = (void *)&ssn;
f.proto = IPPROTO_TCP;
f.flags |= FLOW_IPV4;
p->flow = &f;
p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST;
p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED;
StreamTcpInitConfig(TRUE);
de_ctx = DetectEngineCtxInit();
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
s = de_ctx->sig_list = SigInit(de_ctx, "alert enip any any -> any any "
"(msg:\"Testing enip command\"; "
"enipcommand:99 ; sid:1;)");
FAIL_IF_NULL(s);
SigGroupBuild(de_ctx);
DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_ENIP, STREAM_TOSERVER,
listIdentity, sizeof(listIdentity));
FAIL_IF(r != 0);
ENIPState *enip_state = f.alstate;
FAIL_IF_NULL(enip_state);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
FAIL_IF(!(PacketAlertCheck(p, 1)));
AppLayerParserThreadCtxFree(alp_tctx);
DetectEngineThreadCtxDeinit(&tv, det_ctx);
DetectEngineCtxFree(de_ctx);
StreamTcpFreeConfig(TRUE);
FLOW_DESTROY(&f);
UTHFreePacket(p);
PASS;
}
#endif /* UNITTESTS */
void DetectEngineInspectENIPRegisterTests(void)
{
#ifdef UNITTESTS
UtRegisterTest("DetectEngineInspectENIPTest01", DetectEngineInspectENIPTest01);
#endif /* UNITTESTS */
return;
}

@ -0,0 +1,35 @@
/* Copyright (C) 2015 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/** \file
*
* \author Kevin Wong <kwong@solananetworks.com>
*/
#ifndef __DETECT_ENGINE_ENIP_H__
#define __DETECT_ENGINE_ENIP_H__
int DetectEngineInspectCIP(ThreadVars *, DetectEngineCtx *de_ctx,
DetectEngineThreadCtx *, Signature *,
Flow *, uint8_t, void *, void *, uint64_t);
int DetectEngineInspectENIP(ThreadVars *, DetectEngineCtx *de_ctx,
DetectEngineThreadCtx *, Signature *,
Flow *, uint8_t, void *, void *, uint64_t);
void DetectEngineInspectENIPRegisterTests(void);
#endif /* __DETECT_ENGINE_ENIP_H__ */

@ -2533,6 +2533,11 @@ const char *DetectSigmatchListEnumToString(enum DetectSigmatchListEnum type)
case DETECT_SM_LIST_MODBUS_MATCH:
return "modbus";
case DETECT_SM_LIST_CIP_MATCH:
return "cip";
case DETECT_SM_LIST_ENIP_MATCH:
return "enip";
case DETECT_SM_LIST_BASE64_DATA:
return "base64_data";

@ -1630,6 +1630,11 @@ static Signature *SigInitHelper(DetectEngineCtx *de_ctx, char *sigstr,
if (sig->sm_lists[DETECT_SM_LIST_APP_EVENT])
sig->flags |= SIG_FLAG_STATE_MATCH;
if (sig->sm_lists[DETECT_SM_LIST_CIP_MATCH])
sig->flags |= SIG_FLAG_STATE_MATCH;
if (sig->sm_lists[DETECT_SM_LIST_ENIP_MATCH])
sig->flags |= SIG_FLAG_STATE_MATCH;
if (!(sig->init_flags & SIG_FLAG_INIT_FLOW)) {
sig->flags |= SIG_FLAG_TOSERVER;
sig->flags |= SIG_FLAG_TOCLIENT;

@ -186,6 +186,7 @@
#include "detect-ssl-version.h"
#include "detect-ssl-state.h"
#include "detect-modbus.h"
#include "detect-cipservice.h"
#include "action-globals.h"
#include "tm-threads.h"
@ -2186,6 +2187,10 @@ PacketCreateMask(Packet *p, SignatureMask *mask, AppProto alproto, int has_state
SCLogDebug("packet/flow has smtp state");
(*mask) |= SIG_MASK_REQUIRE_SMTP_STATE;
break;
case ALPROTO_ENIP:
SCLogDebug("packet/flow has enip state");
(*mask) |= SIG_MASK_REQUIRE_ENIP_STATE;
break;
case ALPROTO_TEMPLATE:
SCLogDebug("packet/flow has template state");
(*mask) |= SIG_MASK_REQUIRE_TEMPLATE_STATE;
@ -2431,6 +2436,10 @@ static int SignatureCreateMask(Signature *s)
s->mask |= SIG_MASK_REQUIRE_SMTP_STATE;
SCLogDebug("sig requires smtp state");
}
if (s->alproto == ALPROTO_ENIP) {
s->mask |= SIG_MASK_REQUIRE_ENIP_STATE;
SCLogDebug("sig requires enip state");
}
if (s->alproto == ALPROTO_TEMPLATE) {
s->mask |= SIG_MASK_REQUIRE_TEMPLATE_STATE;
SCLogDebug("sig requires template state");
@ -2442,6 +2451,7 @@ static int SignatureCreateMask(Signature *s)
(s->mask & SIG_MASK_REQUIRE_DNS_STATE) ||
(s->mask & SIG_MASK_REQUIRE_FTP_STATE) ||
(s->mask & SIG_MASK_REQUIRE_SMTP_STATE) ||
(s->mask & SIG_MASK_REQUIRE_ENIP_STATE) ||
(s->mask & SIG_MASK_REQUIRE_TEMPLATE_STATE) ||
(s->mask & SIG_MASK_REQUIRE_TLS_STATE))
{
@ -4138,6 +4148,8 @@ void SigTableSetup(void)
DetectDnsQueryRegister();
DetectModbusRegister();
DetectCipServiceRegister();
DetectEnipCommandRegister();
DetectTlsSniRegister();
DetectTlsIssuerRegister();

@ -136,6 +136,9 @@ enum DetectSigmatchListEnum {
DETECT_SM_LIST_MODBUS_MATCH,
DETECT_SM_LIST_CIP_MATCH,
DETECT_SM_LIST_ENIP_MATCH,
DETECT_SM_LIST_BASE64_DATA,
DETECT_SM_LIST_TEMPLATE_BUFFER_MATCH,
@ -305,6 +308,7 @@ typedef struct DetectPort_ {
#define SIG_MASK_REQUIRE_FTP_STATE (1<<11)
#define SIG_MASK_REQUIRE_SMTP_STATE (1<<12)
#define SIG_MASK_REQUIRE_TEMPLATE_STATE (1<<13)
#define SIG_MASK_REQUIRE_ENIP_STATE (1<<14)
/* for now a uint8_t is enough */
#define SignatureMask uint16_t
@ -1319,6 +1323,8 @@ enum {
DETECT_AL_TLS_CERT_ISSUER,
DETECT_AL_TLS_CERT_SUBJECT,
DETECT_AL_MODBUS,
DETECT_CIPSERVICE,
DETECT_ENIPCOMMAND,
DETECT_XBITS,
DETECT_BASE64_DECODE,

@ -122,6 +122,7 @@
#include "app-layer-smtp.h"
#include "app-layer-smb.h"
#include "app-layer-modbus.h"
#include "app-layer-enip.h"
#include "util-decode-der.h"
#include "util-radix-tree.h"
@ -1158,6 +1159,8 @@ static TmEcode ParseCommandLine(int argc, char** argv, SCInstance *suri)
{"afl-smb", required_argument, 0 , 0},
{"afl-modbus-request", required_argument, 0 , 0},
{"afl-modbus", required_argument, 0 , 0},
{"afl-enip-request", required_argument, 0 , 0},
{"afl-enip", required_argument, 0 , 0},
{"afl-mime", required_argument, 0 , 0},
{"afl-decoder-ppp", required_argument, 0 , 0},
@ -1428,6 +1431,16 @@ static TmEcode ParseCommandLine(int argc, char** argv, SCInstance *suri)
AppLayerParserSetup();
RegisterModbusParsers();
exit(AppLayerParserFromFile(ALPROTO_MODBUS, optarg));
} else if(strcmp((long_opts[option_index]).name, "afl-enip-request") == 0) {
//printf("arg: //%s\n", optarg);
AppLayerParserSetup();
RegisterENIPTCPParsers();
exit(AppLayerParserRequestFromFile(ALPROTO_ENIP, optarg));
} else if(strcmp((long_opts[option_index]).name, "afl-enip") == 0) {
//printf("arg: //%s\n", optarg);
AppLayerParserSetup();
RegisterENIPTCPParsers();
exit(AppLayerParserFromFile(ALPROTO_ENIP, optarg));
#endif
#ifdef AFLFUZZ_MIME
} else if(strcmp((long_opts[option_index]).name, "afl-mime") == 0) {

@ -297,6 +297,7 @@ const char * SCErrorToString(SCError err)
CASE_CODE (SC_ERR_DNS_CONFIG);
CASE_CODE (SC_ERR_MODBUS_CONFIG);
CASE_CODE (SC_ERR_CONF_YAML_ERROR);
CASE_CODE (SC_ERR_ENIP_CONFIG);
CASE_CODE (SC_ERR_CONF_NAME_TOO_LONG);
CASE_CODE (SC_ERR_APP_LAYER_PROTOCOL_DETECTION);
CASE_CODE (SC_ERR_PCIE_INIT_FAILED);

@ -319,6 +319,7 @@ typedef enum {
SC_ERR_INVALID_HASH,
SC_ERR_NO_SHA1_SUPPORT,
SC_ERR_NO_SHA256_SUPPORT,
SC_ERR_ENIP_CONFIG,
} SCError;
const char *SCErrorToString(SCError);

@ -831,6 +831,8 @@ app-layer:
# response-body-limit: 4096
# double-decode-path: no
# double-decode-query: no
enip:
enabled: no
# Limit for the maximum number of asn1 frames to decode (default 256)
asn1-max-frames: 256

Loading…
Cancel
Save