mirror of https://github.com/OISF/suricata
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 pointspull/2319/head
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
|
@ -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__ */
|
@ -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__ */
|
Loading…
Reference in New Issue