CIP: Support object-specific services

This commit is contained in:
Dylan Ulis 2022-10-11 07:07:12 +00:00 committed by AndersBroman
parent f7416d7e95
commit 5f9d493640
4 changed files with 225 additions and 11 deletions

View File

@ -44,6 +44,8 @@
// 4. Dissector Table "cip.data_segment.iface" - Unknown. This may be removed in the future
// 5. attribute_info_t: Use this to add handling for an attribute, using a 3 tuple key (Class, Instance, Attribute)
// See 'cip_attribute_vals' for an example.
// 6. cip_service_info_t: Use this to add handling for a service, using a 2 tuple key (Class, Service)
// See 'cip_obj_spec_service_table' for an example.
#include "config.h"
@ -3720,6 +3722,28 @@ static int dissect_port_node_range(packet_info *pinfo _U_, proto_tree *tree, pro
return 4;
}
/// Identity - Services
static int dissect_identity_reset(packet_info *pinfo _U_, proto_tree *tree, proto_item *item _U_, tvbuff_t *tvb, int offset, gboolean request)
{
int parsed_len = 0;
if (request)
{
if (tvb_reported_length_remaining(tvb, offset) > 0)
{
proto_tree_add_item(tree, hf_cip_sc_reset_param, tvb, offset, 1, ENC_LITTLE_ENDIAN);
parsed_len = 1;
}
}
else
{
parsed_len = 0;
}
return parsed_len;
}
static attribute_info_t cip_attribute_vals[] = {
/* Identity Object (class attributes) */
{0x01, TRUE, 1, 0, CLASS_ATTRIBUTE_1_NAME, cip_uint, &hf_attr_class_revision, NULL },
@ -3857,6 +3881,20 @@ static attribute_info_t cip_attribute_vals[] = {
{ 0xF4, FALSE, 11, -1, "Associated Communication Objects", cip_dissector_func, NULL, dissect_port_associated_comm_objects },
};
// Table of CIP services defined by this dissector.
static cip_service_info_t cip_obj_spec_service_table[] = {
{ 0x1, SC_RESET, "Reset", dissect_identity_reset },
};
// Look up a given CIP service from this dissector.
static cip_service_info_t* cip_get_service_cip(guint32 class_id, guint8 service_id)
{
return cip_get_service_one_table(&cip_obj_spec_service_table[0],
sizeof(cip_obj_spec_service_table) / sizeof(cip_service_info_t),
class_id,
service_id);
}
typedef struct attribute_val_array {
size_t size;
attribute_info_t* attrs;
@ -3924,6 +3962,45 @@ attribute_info_t* cip_get_attribute(guint class_id, guint instance, guint attrib
return NULL;
}
// Look up a given CIP service from a table of cip_service_info_t.
cip_service_info_t* cip_get_service_one_table(cip_service_info_t* services, size_t size, guint32 class_id, guint8 service_id)
{
for (guint32 i = 0; i < size; i++)
{
cip_service_info_t* entry = &services[i];
if (entry->class_id == class_id && entry->service_id == (service_id & CIP_SC_MASK))
{
return entry;
}
}
return NULL;
}
// Look through all CIP Service tables from different dissectors, to find a definition for a given CIP service.
static cip_service_info_t* cip_get_service(packet_info *pinfo, guint8 service_id)
{
cip_req_info_t *cip_req_info = (cip_req_info_t*)p_get_proto_data(wmem_file_scope(), pinfo, proto_cip, 0);
if (!cip_req_info || !cip_req_info->ciaData)
{
return NULL;
}
cip_service_info_t* pService = cip_get_service_cip(cip_req_info->ciaData->iClass, service_id);
if (pService)
{
return pService;
}
pService = cip_get_service_enip(cip_req_info->ciaData->iClass, service_id);
if (pService)
{
return pService;
}
return NULL;
}
static const char *
segment_name_format(const char *segment_name, const char *fmt)
G_GNUC_FORMAT(2);
@ -5671,6 +5748,86 @@ int dissect_cip_attribute(packet_info *pinfo, proto_tree *tree, proto_item *item
return consumed;
}
static int dissect_cip_service(packet_info *pinfo, tvbuff_t *tvb, int offset,
proto_item *ti, proto_tree *item_tree, cip_service_info_t *service_entry, guint8 service)
{
int parsed_len = 0;
if (service_entry != NULL && service_entry->pdissect)
{
gboolean request = !(service & CIP_SC_RESPONSE_MASK);
parsed_len = service_entry->pdissect(pinfo, item_tree, ti, tvb, offset, request);
}
return parsed_len;
}
static int dissect_cip_object_specific_service(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, proto_item* msp_item, cip_service_info_t *service_entry)
{
DISSECTOR_ASSERT(service_entry != NULL);
int offset = 0;
guint8 service = tvb_get_guint8(tvb, offset);
guint8 gen_status = 0;
// Skip over the Request/Response header to get to the actual data.
if (service & CIP_SC_RESPONSE_MASK)
{
gen_status = tvb_get_guint8(tvb, offset + 2);
guint16 add_stat_size = tvb_get_guint8(tvb, offset + 3) * 2;
offset = 4 + add_stat_size;
}
else
{
guint16 req_path_size = tvb_get_guint8(tvb, offset + 1) * 2;
offset = 2 + req_path_size;
}
// Display the service name, even if there is no payload data.
if (service_entry->service_name)
{
col_append_str(pinfo->cinfo, COL_INFO, service_entry->service_name);
col_set_fence(pinfo->cinfo, COL_INFO);
proto_item_append_text(msp_item, "%s", service_entry->service_name);
}
// Only dissect responses with specific response statuses.
if ((service & CIP_SC_RESPONSE_MASK)
&& (should_dissect_cip_response(tvb, offset, gen_status) == FALSE))
{
return 0;
}
proto_item *payload_item;
proto_tree *payload_tree = proto_tree_add_subtree(tree, tvb, offset, tvb_reported_length_remaining(tvb, offset), ett_cmd_data, &payload_item, "");
// Add the service info to the tree item.
proto_item_append_text(payload_item, "%s", service_entry->service_name);
if (service & CIP_SC_RESPONSE_MASK)
{
proto_item_append_text(payload_item, " (Response)");
}
else
{
proto_item_append_text(payload_item, " (Request)");
}
// Process any known command-specific data.
offset += dissect_cip_service(pinfo, tvb, offset, payload_item, payload_tree, service_entry, service);
// Add any remaining data.
int len_remain = tvb_reported_length_remaining(tvb, offset);
if (len_remain > 0)
{
proto_tree_add_item(payload_tree, hf_cip_data, tvb, offset, len_remain, ENC_NA);
}
return tvb_reported_length(tvb);
}
/************************************************
*
* Dissector for generic CIP object
@ -6008,14 +6165,6 @@ dissect_cip_generic_service_req(tvbuff_t *tvb, packet_info *pinfo, proto_tree *t
case SC_SET_ATT_LIST:
parsed_len = dissect_cip_set_attribute_list_req(tvb, pinfo, cmd_data_tree, cmd_data_item, offset, req_data);
break;
case SC_RESET:
// Parameter to reset is optional.
if (tvb_reported_length_remaining(tvb, offset) > 0)
{
proto_tree_add_item(cmd_data_tree, hf_cip_sc_reset_param, tvb, offset, 1, ENC_LITTLE_ENDIAN);
parsed_len = 1;
}
break;
case SC_MULT_SERV_PACK:
parsed_len = dissect_cip_multiple_service_packet(tvb, pinfo, cmd_data_tree, cmd_data_item, offset, TRUE);
break;
@ -7067,11 +7216,12 @@ dissect_cip_cm_data( proto_tree *item_tree, tvbuff_t *tvb, int offset, int item_
/* Check to see if service is 'generic' */
try_val_to_str_idx((service & CIP_SC_MASK), cip_sc_vals, &service_index);
cip_service_info_t* service_entry = cip_get_service(pinfo, service);
if ( pembedded_req_info && pembedded_req_info->dissector )
{
call_dissector(pembedded_req_info->dissector, next_tvb, pinfo, item_tree );
}
else if (service_index >= 0)
else if (service_index >= 0 && !service_entry)
{
/* See if object dissector wants to override generic service handling */
if (!dissector_try_heuristic(heur_subdissector_service, tvb, pinfo, item_tree, &hdtbl_entry, NULL))
@ -7079,6 +7229,10 @@ dissect_cip_cm_data( proto_tree *item_tree, tvbuff_t *tvb, int offset, int item_
dissect_cip_generic_service_rsp(tvb, pinfo, item_tree);
}
}
else if (service_entry)
{
dissect_cip_object_specific_service(tvb, pinfo, item_tree, NULL, service_entry);
}
else
{
call_dissector( cip_class_generic_handle, next_tvb, pinfo, item_tree );
@ -8244,6 +8398,8 @@ void dissect_cip_data( proto_tree *item_tree, tvbuff_t *tvb, int offset, packet_
/* Check to see if service is 'generic' */
try_val_to_str_idx((service & CIP_SC_MASK), cip_sc_vals, &service_index);
cip_service_info_t* service_entry = cip_get_service(pinfo, service);
/* If the request set a dissector, then check that first. This ensures
that Unconnected Send responses are properly parsed based on the
embedded request. */
@ -8251,7 +8407,7 @@ void dissect_cip_data( proto_tree *item_tree, tvbuff_t *tvb, int offset, packet_
{
call_dissector(preq_info->dissector, tvb, pinfo, item_tree);
}
else if (service_index >= 0)
else if (service_index >= 0 && !service_entry)
{
/* See if object dissector wants to override generic service handling */
if(!dissector_try_heuristic(heur_subdissector_service, tvb, pinfo, item_tree, &hdtbl_entry, NULL))
@ -8259,6 +8415,10 @@ void dissect_cip_data( proto_tree *item_tree, tvbuff_t *tvb, int offset, packet_
dissect_cip_generic_service_rsp(tvb, pinfo, cip_tree);
}
}
else if (service_entry)
{
dissect_cip_object_specific_service(tvb, pinfo, cip_tree, msp_item, service_entry);
}
else
{
call_dissector( cip_class_generic_handle, tvb, pinfo, item_tree );
@ -8322,7 +8482,9 @@ void dissect_cip_data( proto_tree *item_tree, tvbuff_t *tvb, int offset, packet_
/* Check to see if service is 'generic' */
try_val_to_str_idx(service, cip_sc_vals, &service_index);
if (service_index >= 0)
cip_service_info_t* service_entry = cip_get_service(pinfo, service);
if (service_index >= 0 && !service_entry)
{
/* See if object dissector wants to override generic service handling */
if(!dissector_try_heuristic(heur_subdissector_service, tvb, pinfo, item_tree, &hdtbl_entry, NULL))
@ -8340,6 +8502,10 @@ void dissect_cip_data( proto_tree *item_tree, tvbuff_t *tvb, int offset, packet_
{
call_dissector( dissector, tvb, pinfo, item_tree );
}
else if (service_entry)
{
dissect_cip_object_specific_service(tvb, pinfo, cip_tree, msp_item, service_entry);
}
else
{
call_dissector( cip_class_generic_handle, tvb, pinfo, item_tree );

View File

@ -472,6 +472,17 @@ typedef struct attribute_info {
attribute_dissector_func *pdissect;
} attribute_info_t;
// offset - starts at command specific data.
// returns - size of data that was parsed.
typedef int service_dissector_func(packet_info *pinfo, proto_tree *tree, proto_item *item, tvbuff_t *tvb,
int offset, gboolean request);
typedef struct cip_service_info {
guint32 class_id;
guint8 service_id;
const gchar *service_name;
service_dissector_func *pdissect;
} cip_service_info_t;
// This describes a one-way connection. Each CIP Connection includes 2 of these.
typedef struct cip_connID_info {
// Connection ID from Forward Open Request. This may get updated in the Forward Open Response.
@ -580,6 +591,7 @@ enum cip_elem_data_types {
extern void add_cip_service_to_info_column(packet_info *pinfo, guint8 service, const value_string* service_vals);
extern attribute_info_t* cip_get_attribute(guint class_id, guint instance, guint attribute);
extern cip_service_info_t* cip_get_service_one_table(cip_service_info_t* services, size_t size, guint32 class_id, guint8 service_id);
extern void cip_rpi_api_fmt(gchar *s, guint32 value);
extern int dissect_cip_attribute(packet_info *pinfo, proto_tree *tree, proto_item *item, tvbuff_t *tvb, attribute_info_t* attr, int offset, int total_len);

View File

@ -368,6 +368,7 @@ static int hf_eip_cert_capflags_reserved = -1;
static int hf_eip_cert_capability_flags = -1;
static int hf_eip_cert_num_certs = -1;
static int hf_eip_cert_cert_name = -1;
static int hf_eip_cert_verify_certificate = -1;
static int hf_lldp_subtype = -1;
static int hf_lldp_mac_address = -1;
@ -2185,6 +2186,19 @@ dissect_eip_cert_ca_cert(packet_info *pinfo, proto_tree *tree, proto_item *item,
return path_size + 1;
}
static int dissect_certificate_management_object_verify_certificate(packet_info *pinfo _U_, proto_tree *tree, proto_item *item _U_, tvbuff_t *tvb, int offset, gboolean request)
{
if (request)
{
proto_tree_add_item(tree, hf_eip_cert_verify_certificate, tvb, offset, 2, ENC_LITTLE_ENDIAN);
return 2;
}
else
{
return 0;
}
}
static int dissect_tcpip_port_information(packet_info *pinfo, proto_tree *tree, proto_item *item, tvbuff_t *tvb,
int offset)
{
@ -2366,6 +2380,21 @@ attribute_info_t enip_attribute_vals[] = {
{0x5F, FALSE, 5, 4, "Certificate Encoding", cip_usint, &hf_eip_cert_encoding, NULL },
};
// Table of CIP services defined by this dissector.
static cip_service_info_t enip_obj_spec_service_table[] = {
// Certificate Management
{ 0x5F, 0x4C, "Verify_Certificate", dissect_certificate_management_object_verify_certificate },
};
// Look up a given CIP service from this dissector.
cip_service_info_t* cip_get_service_enip(guint32 class_id, guint8 service_id)
{
return cip_get_service_one_table(&enip_obj_spec_service_table[0],
sizeof(enip_obj_spec_service_table) / sizeof(cip_service_info_t),
class_id,
service_id);
}
static void enip_init_protocol(void)
{
enip_unique_connid = 0;
@ -4675,6 +4704,11 @@ proto_register_enip(void)
FT_STRING, BASE_NONE, NULL, 0,
NULL, HFILL }},
{ &hf_eip_cert_verify_certificate,
{ "Certificate", "cip.eip_cert.verify_certificate",
FT_UINT16, BASE_DEC, NULL, 0,
NULL, HFILL } },
{ &hf_lldp_subtype,
{ "ODVA LLDP Subtype", "cip.lldp.subtype",
FT_UINT8, BASE_DEC, VALS(lldp_cip_subtypes), 0,

View File

@ -109,6 +109,8 @@ void display_fwd_open_connection_path(cip_conn_info_t* conn_info, proto_tree* tr
void enip_close_cip_connection(packet_info *pinfo, const cip_connection_triad_t* triad);
void enip_mark_connection_triad(packet_info *pinfo, const cip_connection_triad_t* triad);
cip_service_info_t* cip_get_service_enip(guint32 class_id, guint8 service_id);
extern int dissect_lldp_cip_tlv(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree);
extern const value_string lldp_cip_subtypes[];