diff --git a/doc/userguide/configuration/suricata-yaml.rst b/doc/userguide/configuration/suricata-yaml.rst index 0b39705d896b..4bf870f7366f 100644 --- a/doc/userguide/configuration/suricata-yaml.rst +++ b/doc/userguide/configuration/suricata-yaml.rst @@ -1761,7 +1761,8 @@ incompatible with ``decode-mime``. If both are enabled, Maximum transactions ~~~~~~~~~~~~~~~~~~~~ -MQTT, FTP, PostgreSQL, SMB, DCERPC and NFS have each a `max-tx` parameter that can be customized. +MQTT, FTP, PostgreSQL, SMB, DCERPC, ENIP and NFS have each a `max-tx` +parameter that can be customized. `max-tx` refers to the maximum number of live transactions for each flow. An app-layer event `protocol.too_many_transactions` is triggered when this value is reached. The point of this parameter is to find a balance between the completeness of analysis diff --git a/doc/userguide/rules/enip-keyword.rst b/doc/userguide/rules/enip-keyword.rst index 5899ca47883a..82beaccf6cca 100644 --- a/doc/userguide/rules/enip-keyword.rst +++ b/doc/userguide/rules/enip-keyword.rst @@ -1,40 +1,225 @@ ENIP/CIP Keywords ================= -The enip_command and cip_service keywords can be used for matching on various properties of -ENIP requests. +enip_command +------------ -There are three ways of using this keyword: +For the ENIP command, we are matching against the command field found in the ENIP encapsulation. + +Examples:: -* 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 + enip_command:99; + enip_command:ListIdentity; -For the ENIP command, we are matching against the command field found in the ENIP encapsulation. +cip_service +----------- For the CIP Service, we use a maximum of 3 comma separated 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: - cip_service: - enip_command:, cip_service: - - 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: ``_ + +enip.status +----------- + +For the ENIP status, we are matching against the status field found in the ENIP encapsulation. +It uses a 32-bit unsigned integer as value. + +Examples:: + + enip.status:100; + enip.status:>106; + +enip.protocol_version +--------------------- + +Match on the protocol version in different messages. +It uses a 16-bit unsigned integer as value. + +Examples:: + + enip.protocol_version:1; + enip.protocol_version:>1; + +enip.cip_attribute +------------------ + +Match on the cip attribute in different messages. +It uses a 32-bit unsigned integer as value. + +This allows to match without needing to match on cip.service. + +Examples:: + + enip.cip_attribute:1; + enip.cip_attribute:>1; + +enip.cip_instance +----------------- + +Match on the cip instance in CIP request path. +It uses a 32-bit unsigned integer as value. + +Examples:: + + enip.cip_instance:1; + enip.cip_instance:>1; + +enip.cip_class +-------------- + +Match on the cip class in CIP request path. +It uses a 32-bit unsigned integer as value. + +This allows to match without needing to match on cip.service. + +Examples:: + + enip.cip_class:1; + enip.cip_class:>1; + +enip.cip_extendedstatus +----------------------- + +Match on the cip extended status if any (one of them in case of multiple service packet). +It uses a 16-bit unsigned integer as value. + +Examples:: + + enip.cip_extendedstatus:1; + enip.cip_extendedstatus:>1; + +enip.revision +--------------------- + +Match on the revision in identity message. +It uses a 16-bit unsigned integer as value. + +Examples:: + + enip.revision:1; + enip.revision:>1; + +enip.identity_status +-------------------- + +Match on the status in identity message (not in ENIP header). +It uses a 16-bit unsigned integer as value. + +Examples:: + + enip.identity_status:1; + enip.identity_status:>1; + +enip.state +---------- + +Match on the state in identity message. +It uses a 8-bit unsigned integer as value. + +Examples:: + + enip.state:1; + enip.state:>1; + +enip.serial +----------- + +Match on the serial in identity message. +It uses a 32-bit unsigned integer as value. + +Examples:: + + enip.serial:1; + enip.serial:>1; + +enip.product_code +----------------- + +Match on the product code in identity message. +It uses a 16-bit unsigned integer as value. + +Examples:: + + enip.product_code:1; + enip.product_code:>1; + +enip.device_type +---------------- + +Match on the device type in identity message. +It uses a 16-bit unsigned integer as value. + +Examples:: + + enip.device_type:1; + enip.device_type:>1; + +enip.vendor_id +-------------- + +Match on the vendor id in identity message. +It uses a 16-bit unsigned integer as value. + +Examples:: + + enip.vendor_id:1; + enip.vendor_id:>1; + +enip.product_name +----------------- + +Match on the product name in identity message. + +Examples:: + + enip.product_name; pcre:"/^123[0-9]*/"; + enip.product_name; content:"swordfish"; + +``enip.product_name`` is a 'sticky buffer' and can be used as ``fast_pattern``. + +enip.service_name +----------------- + +Match on the service name in list services message. + +Examples:: + + enip.service_name; pcre:"/^123[0-9]*/"; + enip.service_name; content:"swordfish"; + +``enip.service_name`` is a 'sticky buffer' and can be used as ``fast_pattern``. + +enip.capabilities +----------------- + +Match on the capabilities in list services message. +It uses a 16-bit unsigned integer as value. + +Examples:: + + enip.capabilities:1; + enip.capabilities:>1; + +enip.cip_status +--------------------- + +Match on the cip status (one of them in case of multiple service packet). +It uses a 8-bit unsigned integer as value. + +Examples:: + + enip.cip_status:1; + enip.cip_status:>1; \ No newline at end of file diff --git a/etc/schema.json b/etc/schema.json index c194017ddf6f..177a986006df 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -1399,6 +1399,208 @@ }, "additionalProperties": false }, + "enip": { + "type": "object", + "properties": { + "request": { + "type": "object", + "properties": { + "command": { + "type": "string" + }, + "status": { + "type": "string" + }, + "register_session": { + "type": "object", + "properties": { + "protocol_version": { + "type": "integer" + }, + "options": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "cip": { + "type": "object", + "properties": { + "service": { + "type": "string" + }, + "path": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "segment_type": { + "type": "string" + }, + "value": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "class_name": { + "type": "string" + }, + "multiple" : { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "service": { + "type": "string" + }, + "path": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "segment_type": { + "type": "string" + }, + "value": { + "type": "integer" + } + }, + "additionalProperties": false + } + }, + "class_name": { + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "response": { + "type": "object", + "properties": { + "command": { + "type": "string" + }, + "status": { + "type": "string" + }, + "register_session": { + "type": "object", + "properties": { + "protocol_version": { + "type": "integer" + }, + "options": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "list_services": { + "type": "object", + "properties": { + "protocol_version": { + "type": "integer" + }, + "capabilities": { + "type": "integer" + }, + "service_name": { + "type": "string" + } + }, + "additionalProperties": false + }, + "identity": { + "type": "object", + "properties": { + "protocol_version": { + "type": "integer" + }, + "revision": { + "type": "string" + }, + "vendor_id": { + "type": "string" + }, + "device_type": { + "type": "string" + }, + "product_code": { + "type": "integer" + }, + "status": { + "type": "integer" + }, + "serial": { + "type": "integer" + }, + "product_name": { + "type": "string" + }, + "state": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "cip": { + "type": "object", + "properties": { + "service": { + "type": "string" + }, + "status": { + "type": "string" + }, + "status_extended": { + "type": "string" + }, + "status_extended_meaning": { + "type": "string" + }, + "multiple" : { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "service": { + "type": "string" + }, + "status": { + "type": "string" + }, + "status_extended": { + "type": "string" + }, + "status_extended_meaning": { + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, "ether": { "type": "object", "properties": { diff --git a/rules/Makefile.am b/rules/Makefile.am index d0ea6eda622f..710f0b06af4b 100644 --- a/rules/Makefile.am +++ b/rules/Makefile.am @@ -6,6 +6,7 @@ decoder-events.rules \ dhcp-events.rules \ dnp3-events.rules \ dns-events.rules \ +enip-events.rules \ files.rules \ ftp-events.rules \ http-events.rules \ diff --git a/rules/enip-events.rules b/rules/enip-events.rules new file mode 100644 index 000000000000..6c7efd8e3c67 --- /dev/null +++ b/rules/enip-events.rules @@ -0,0 +1,8 @@ +# ENIP app layer event rules +# +# SID's fall in the 2223000+ range. See https://redmine.openinfosecfoundation.org/projects/suricata/wiki/AppLayer +# +# These sigs fire at most once per connection. +# +alert enip any any -> any any (msg:"SURICATA ENIP too many transactions"; app-layer-event:enip.too_many_transactions; classtype:protocol-command-decode; sid:2234000; rev:1;) +alert enip any any -> any any (msg:"SURICATA ENIP invalid PDU"; app-layer-event:enip.invalid_pdu; classtype:protocol-command-decode; sid:2234001; rev:1;) diff --git a/rust/src/applayer.rs b/rust/src/applayer.rs index 97db321e2249..86cf190a5dd1 100644 --- a/rust/src/applayer.rs +++ b/rust/src/applayer.rs @@ -487,6 +487,7 @@ extern { pub fn AppLayerParserStateIssetFlag(state: *mut c_void, flag: u16) -> u16; pub fn AppLayerParserSetStreamDepth(ipproto: u8, alproto: AppProto, stream_depth: u32); pub fn AppLayerParserConfParserEnabled(ipproto: *const c_char, proto: *const c_char) -> c_int; + pub fn AppLayerParserRegisterParserAcceptableDataDirection(ipproto: u8, alproto: AppProto, dir: u8); } #[repr(C)] diff --git a/rust/src/enip/detect.rs b/rust/src/enip/detect.rs new file mode 100644 index 000000000000..1af152ce218a --- /dev/null +++ b/rust/src/enip/detect.rs @@ -0,0 +1,808 @@ +/* Copyright (C) 2023 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. + */ + +use nom7::branch::alt; +use nom7::character::complete::{char, digit1, space0}; +use nom7::combinator::{map_opt, opt, verify}; +use nom7::error::{make_error, ErrorKind}; +use nom7::IResult; + +use std::ffi::c_void; + +use crate::enip::enip::EnipTransaction; +use crate::enip::parser::{ + CipData, CipDir, EnipCipRequestPayload, EnipCipResponsePayload, EnipItemPayload, EnipPayload, + CIP_MULTIPLE_SERVICE, ENIP_CMD_CANCEL, ENIP_CMD_INDICATE_STATUS, ENIP_CMD_LIST_IDENTITY, + ENIP_CMD_LIST_INTERFACES, ENIP_CMD_LIST_SERVICES, ENIP_CMD_NOP, ENIP_CMD_REGISTER_SESSION, + ENIP_CMD_SEND_RRDATA, ENIP_CMD_SEND_UNIT_DATA, ENIP_CMD_UNREGISTER_SESSION, +}; + +use crate::detect::uint::{detect_match_uint, DetectUintData}; + +use crate::core::Direction; + +use std::ffi::CStr; + +fn enip_detect_parse_u16(i: &str) -> IResult<&str, u16> { + let (i, r) = map_opt(digit1, |s: &str| s.parse::().ok())(i)?; + return Ok((i, r)); +} + +fn enip_parse_command_string(i: &str) -> IResult<&str, u16> { + let su = i.to_uppercase(); + let su_slice: &str = &su; + match su_slice { + "NOP" => Ok((i, ENIP_CMD_NOP)), + "LISTSERVICES" => Ok((i, ENIP_CMD_LIST_SERVICES)), + "LISTIDENTITY" => Ok((i, ENIP_CMD_LIST_IDENTITY)), + "LISTINTERFACES" => Ok((i, ENIP_CMD_LIST_INTERFACES)), + "REGISTERSESSION" => Ok((i, ENIP_CMD_REGISTER_SESSION)), + "UNREGISTERSESSION" => Ok((i, ENIP_CMD_UNREGISTER_SESSION)), + "SENDRRDATA" => Ok((i, ENIP_CMD_SEND_RRDATA)), + "SENDUNITDATA" => Ok((i, ENIP_CMD_SEND_UNIT_DATA)), + "INDICATESTATUS" => Ok((i, ENIP_CMD_INDICATE_STATUS)), + "CANCEL" => Ok((i, ENIP_CMD_CANCEL)), + _ => Err(nom7::Err::Error(nom7::error::make_error( + i, + nom7::error::ErrorKind::MapOpt, + ))), + } +} + +fn enip_parse_command(i: &str) -> IResult<&str, u16> { + let (i, v) = alt((enip_detect_parse_u16, enip_parse_command_string))(i)?; + return Ok((i, v)); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_parse_command( + raw: *const std::os::raw::c_char, value: *mut u16, +) -> bool { + let raw2: &CStr = CStr::from_ptr(raw); //unsafe + if let Ok(s) = raw2.to_str() { + if let Ok((_, v)) = enip_parse_command(s) { + *value = v; + return true; + } + } + return false; +} + +fn enip_tx_is_cmd( + tx: &mut EnipTransaction, direction: Direction, value: u16, +) -> std::os::raw::c_int { + if direction == Direction::ToServer { + if let Some(req) = &tx.request { + if req.header.cmd == value { + return 1; + } + } + } else if let Some(resp) = &tx.response { + if resp.header.cmd == value { + return 1; + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_tx_is_cmd( + tx: *mut std::os::raw::c_void, direction: u8, value: u16, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, EnipTransaction); + return enip_tx_is_cmd(tx, direction.into(), value); +} + +#[derive(Clone, Debug, Default)] +pub struct DetectCipServiceData { + pub service: u8, + pub class: Option, + pub attribute: Option, +} + +fn enip_parse_cip_service(i: &str) -> IResult<&str, DetectCipServiceData> { + let (i, _) = space0(i)?; + let (i, service) = verify(map_opt(digit1, |s: &str| s.parse::().ok()), |&v| { + v < 0x80 + })(i)?; + let mut class = None; + let mut attribute = None; + let (i, _) = space0(i)?; + let (i, comma) = opt(char(','))(i)?; + let mut input = i; + if comma.is_some() { + let (i, _) = space0(i)?; + let (i, class1) = map_opt(digit1, |s: &str| s.parse::().ok())(i)?; + class = Some(class1); + let (i, _) = space0(i)?; + let (i, comma) = opt(char(','))(i)?; + input = i; + if comma.is_some() { + let (i, _) = space0(i)?; + let (i, negation) = opt(char('!'))(i)?; + let (i, attr1) = map_opt(digit1, |s: &str| s.parse::().ok())(i)?; + if negation.is_none() { + attribute = Some(attr1); + } + input = i; + } + } + let (i, _) = space0(input)?; + if !i.is_empty() { + return Err(nom7::Err::Error(make_error(i, ErrorKind::NonEmpty))); + } + return Ok(( + i, + DetectCipServiceData { + service, + class, + attribute, + }, + )); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_parse_cip_service( + raw: *const std::os::raw::c_char, +) -> *mut c_void { + let raw2: &CStr = CStr::from_ptr(raw); //unsafe + if let Ok(s) = raw2.to_str() { + if let Ok((_, ctx)) = enip_parse_cip_service(s) { + let boxed = Box::new(ctx); + return Box::into_raw(boxed) as *mut _; + } + } + return std::ptr::null_mut(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_cip_service_free(ctx: *mut c_void) { + std::mem::drop(Box::from_raw(ctx as *mut DetectCipServiceData)); +} + +fn enip_cip_has_attribute(cipdir: &CipDir, attr: u32) -> std::os::raw::c_int { + if let CipDir::Request(req) = cipdir { + for seg in req.path.iter() { + if seg.segment_type >> 2 == 12 && seg.value == attr { + return 1; + } + } + match &req.payload { + EnipCipRequestPayload::GetAttributeList(ga) => { + for attrg in ga.attr_list.iter() { + if attr == (*attrg).into() { + return 1; + } + } + } + EnipCipRequestPayload::SetAttributeList(sa) => { + if let Some(val) = sa.first_attr { + if attr == val.into() { + return 1; + } + } + } + _ => {} + } + } + return 0; +} + +fn enip_cip_has_class(cipdir: &CipDir, class: u32) -> bool { + if let CipDir::Request(req) = cipdir { + for seg in req.path.iter() { + if seg.segment_type >> 2 == 8 && seg.value == class { + return true; + } + } + } + return false; +} + +fn enip_cip_match_service(d: &CipData, ctx: &DetectCipServiceData) -> std::os::raw::c_int { + if d.service == ctx.service { + if let Some(class) = ctx.class { + if enip_cip_has_class(&d.cipdir, class) { + if let Some(attr) = ctx.attribute { + return enip_cip_has_attribute(&d.cipdir, attr); + } //else + return 1; + } //else + return 0; + } //else + return 1; + } else if d.service == CIP_MULTIPLE_SERVICE { + match &d.cipdir { + CipDir::Request(req) => { + if let EnipCipRequestPayload::Multiple(m) = &req.payload { + for p in m.packet_list.iter() { + if enip_cip_match_service(p, ctx) == 1 { + return 1; + } + } + } + } + CipDir::Response(resp) => { + if let EnipCipResponsePayload::Multiple(m) = &resp.payload { + for p in m.packet_list.iter() { + if enip_cip_match_service(p, ctx) == 1 { + return 1; + } + } + } + } + _ => {} + } + } + return 0; +} + +fn enip_tx_has_cip_service( + tx: &mut EnipTransaction, direction: Direction, ctx: &DetectCipServiceData, +) -> std::os::raw::c_int { + let pduo = if direction == Direction::ToServer { + &tx.request + } else { + &tx.response + }; + if let Some(pdu) = pduo { + if let EnipPayload::Cip(c) = &pdu.payload { + for item in c.items.iter() { + if let EnipItemPayload::Data(d) = &item.payload { + return enip_cip_match_service(&d.cip, ctx); + } + } + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_tx_has_cip_service( + tx: *mut std::os::raw::c_void, direction: u8, ctx: *const c_void, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, EnipTransaction); + let ctx = cast_pointer!(ctx, DetectCipServiceData); + return enip_tx_has_cip_service(tx, direction.into(), ctx); +} + +fn enip_cip_match_status(d: &CipData, ctx: &DetectUintData) -> std::os::raw::c_int { + if let CipDir::Response(resp) = &d.cipdir { + if detect_match_uint(ctx, resp.status) { + return 1; + } + if let EnipCipResponsePayload::Multiple(m) = &resp.payload { + for p in m.packet_list.iter() { + if enip_cip_match_status(p, ctx) == 1 { + return 1; + } + } + } + } + return 0; +} + +fn enip_tx_has_cip_status( + tx: &mut EnipTransaction, ctx: &DetectUintData, +) -> std::os::raw::c_int { + if let Some(pdu) = &tx.response { + if let EnipPayload::Cip(c) = &pdu.payload { + for item in c.items.iter() { + if let EnipItemPayload::Data(d) = &item.payload { + return enip_cip_match_status(&d.cip, ctx); + } + } + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_tx_has_cip_status( + tx: *mut std::os::raw::c_void, ctx: *const c_void, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, EnipTransaction); + let ctx = cast_pointer!(ctx, DetectUintData); + return enip_tx_has_cip_status(tx, ctx); +} + +fn enip_cip_match_extendedstatus(d: &CipData, ctx: &DetectUintData) -> std::os::raw::c_int { + if let CipDir::Response(resp) = &d.cipdir { + if resp.status_extended.len() == 2 { + let val = ((resp.status_extended[1] as u16) << 8) | (resp.status_extended[0] as u16); + if detect_match_uint(ctx, val) { + return 1; + } + } + if let EnipCipResponsePayload::Multiple(m) = &resp.payload { + for p in m.packet_list.iter() { + if enip_cip_match_extendedstatus(p, ctx) == 1 { + return 1; + } + } + } + } + return 0; +} + +fn enip_tx_has_cip_extendedstatus( + tx: &mut EnipTransaction, ctx: &DetectUintData, +) -> std::os::raw::c_int { + if let Some(pdu) = &tx.response { + if let EnipPayload::Cip(c) = &pdu.payload { + for item in c.items.iter() { + if let EnipItemPayload::Data(d) = &item.payload { + return enip_cip_match_extendedstatus(&d.cip, ctx); + } + } + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_tx_has_cip_extendedstatus( + tx: *mut std::os::raw::c_void, ctx: *const c_void, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, EnipTransaction); + let ctx = cast_pointer!(ctx, DetectUintData); + return enip_tx_has_cip_extendedstatus(tx, ctx); +} + +fn enip_get_status(tx: &mut EnipTransaction, direction: Direction) -> Option { + if direction == Direction::ToServer { + if let Some(req) = &tx.request { + return Some(req.header.status); + } + } else if let Some(resp) = &tx.response { + return Some(resp.header.status); + } + return None; +} + +fn enip_cip_match_segment( + d: &CipData, ctx: &DetectUintData, segment_type: u8, +) -> std::os::raw::c_int { + if let CipDir::Request(req) = &d.cipdir { + for seg in req.path.iter() { + if seg.segment_type >> 2 == segment_type && detect_match_uint(ctx, seg.value) { + return 1; + } + } + if let EnipCipRequestPayload::Multiple(m) = &req.payload { + for p in m.packet_list.iter() { + if enip_cip_match_segment(p, ctx, segment_type) == 1 { + return 1; + } + } + } + } + return 0; +} + +fn enip_tx_has_cip_segment( + tx: &mut EnipTransaction, ctx: &DetectUintData, segment_type: u8, +) -> std::os::raw::c_int { + if let Some(pdu) = &tx.request { + if let EnipPayload::Cip(c) = &pdu.payload { + for item in c.items.iter() { + if let EnipItemPayload::Data(d) = &item.payload { + return enip_cip_match_segment(&d.cip, ctx, segment_type); + } + } + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_tx_has_cip_class( + tx: *mut std::os::raw::c_void, ctx: *const c_void, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, EnipTransaction); + let ctx = cast_pointer!(ctx, DetectUintData); + return enip_tx_has_cip_segment(tx, ctx, 8); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_tx_has_cip_instance( + tx: *mut std::os::raw::c_void, ctx: *const c_void, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, EnipTransaction); + let ctx = cast_pointer!(ctx, DetectUintData); + return enip_tx_has_cip_segment(tx, ctx, 9); +} + +fn enip_cip_match_attribute(d: &CipData, ctx: &DetectUintData) -> std::os::raw::c_int { + if let CipDir::Request(req) = &d.cipdir { + for seg in req.path.iter() { + if seg.segment_type >> 2 == 12 && detect_match_uint(ctx, seg.value) { + return 1; + } + } + match &req.payload { + EnipCipRequestPayload::GetAttributeList(ga) => { + for attrg in ga.attr_list.iter() { + if detect_match_uint(ctx, (*attrg).into()) { + return 1; + } + } + } + EnipCipRequestPayload::SetAttributeList(sa) => { + if let Some(val) = sa.first_attr { + if detect_match_uint(ctx, val.into()) { + return 1; + } + } + } + EnipCipRequestPayload::Multiple(m) => { + for p in m.packet_list.iter() { + if enip_cip_match_attribute(p, ctx) == 1 { + return 1; + } + } + } + _ => {} + } + } + return 0; +} + +fn enip_tx_has_cip_attribute( + tx: &mut EnipTransaction, ctx: &DetectUintData, +) -> std::os::raw::c_int { + if let Some(pdu) = &tx.request { + if let EnipPayload::Cip(c) = &pdu.payload { + for item in c.items.iter() { + if let EnipItemPayload::Data(d) = &item.payload { + return enip_cip_match_attribute(&d.cip, ctx); + } + } + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_tx_has_cip_attribute( + tx: *mut std::os::raw::c_void, ctx: *const c_void, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, EnipTransaction); + let ctx = cast_pointer!(ctx, DetectUintData); + return enip_tx_has_cip_attribute(tx, ctx); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_get_status( + tx: *mut std::os::raw::c_void, direction: u8, value: *mut u32, +) -> bool { + let tx = cast_pointer!(tx, EnipTransaction); + if let Some(x) = enip_get_status(tx, direction.into()) { + *value = x; + return true; + } + return false; +} + +fn enip_tx_get_protocol_version(tx: &mut EnipTransaction, direction: Direction) -> Option { + if direction == Direction::ToServer { + if let Some(req) = &tx.request { + if let EnipPayload::RegisterSession(rs) = &req.payload { + return Some(rs.protocol_version); + } + } + } else if let Some(resp) = &tx.response { + match &resp.payload { + EnipPayload::RegisterSession(rs) => { + return Some(rs.protocol_version); + } + EnipPayload::ListServices(lsp) if !lsp.is_empty() => { + if let EnipItemPayload::Services(ls) = &lsp[0].payload { + return Some(ls.protocol_version); + } + } + EnipPayload::ListIdentity(lip) if !lip.is_empty() => { + if let EnipItemPayload::Identity(li) = &lip[0].payload { + return Some(li.protocol_version); + } + } + _ => {} + } + } + return None; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_get_protocol_version( + tx: *mut std::os::raw::c_void, direction: u8, value: *mut u16, +) -> bool { + let tx = cast_pointer!(tx, EnipTransaction); + if let Some(val) = enip_tx_get_protocol_version(tx, direction.into()) { + *value = val; + return true; + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_get_capabilities( + tx: *mut std::os::raw::c_void, value: *mut u16, +) -> bool { + let tx = cast_pointer!(tx, EnipTransaction); + if let Some(ref response) = tx.response { + if let EnipPayload::ListServices(lsp) = &response.payload { + if !lsp.is_empty() { + if let EnipItemPayload::Services(ls) = &lsp[0].payload { + *value = ls.capabilities; + return true; + } + } + } + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_get_revision( + tx: *mut std::os::raw::c_void, value: *mut u16, +) -> bool { + let tx = cast_pointer!(tx, EnipTransaction); + if let Some(ref response) = tx.response { + if let EnipPayload::ListIdentity(lip) = &response.payload { + if !lip.is_empty() { + if let EnipItemPayload::Identity(li) = &lip[0].payload { + *value = ((li.revision_major as u16) << 8) | (li.revision_minor as u16); + return true; + } + } + } + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_get_identity_status( + tx: *mut std::os::raw::c_void, value: *mut u16, +) -> bool { + let tx = cast_pointer!(tx, EnipTransaction); + if let Some(ref response) = tx.response { + if let EnipPayload::ListIdentity(lip) = &response.payload { + if !lip.is_empty() { + if let EnipItemPayload::Identity(li) = &lip[0].payload { + *value = li.status; + return true; + } + } + } + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_get_state(tx: *mut std::os::raw::c_void, value: *mut u8) -> bool { + let tx = cast_pointer!(tx, EnipTransaction); + if let Some(ref response) = tx.response { + if let EnipPayload::ListIdentity(lip) = &response.payload { + if !lip.is_empty() { + if let EnipItemPayload::Identity(li) = &lip[0].payload { + *value = li.state; + return true; + } + } + } + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_get_serial( + tx: *mut std::os::raw::c_void, value: *mut u32, +) -> bool { + let tx = cast_pointer!(tx, EnipTransaction); + if let Some(ref response) = tx.response { + if let EnipPayload::ListIdentity(lip) = &response.payload { + if !lip.is_empty() { + if let EnipItemPayload::Identity(li) = &lip[0].payload { + *value = li.serial; + return true; + } + } + } + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_get_product_code( + tx: *mut std::os::raw::c_void, value: *mut u16, +) -> bool { + let tx = cast_pointer!(tx, EnipTransaction); + if let Some(ref response) = tx.response { + if let EnipPayload::ListIdentity(lip) = &response.payload { + if !lip.is_empty() { + if let EnipItemPayload::Identity(li) = &lip[0].payload { + *value = li.product_code; + return true; + } + } + } + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_get_device_type( + tx: *mut std::os::raw::c_void, value: *mut u16, +) -> bool { + let tx = cast_pointer!(tx, EnipTransaction); + if let Some(ref response) = tx.response { + if let EnipPayload::ListIdentity(lip) = &response.payload { + if !lip.is_empty() { + if let EnipItemPayload::Identity(li) = &lip[0].payload { + *value = li.device_type; + return true; + } + } + } + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_get_vendor_id( + tx: *mut std::os::raw::c_void, value: *mut u16, +) -> bool { + let tx = cast_pointer!(tx, EnipTransaction); + if let Some(ref response) = tx.response { + if let EnipPayload::ListIdentity(lip) = &response.payload { + if !lip.is_empty() { + if let EnipItemPayload::Identity(li) = &lip[0].payload { + *value = li.vendor_id; + return true; + } + } + } + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_tx_get_product_name( + tx: &EnipTransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if let Some(ref response) = tx.response { + if let EnipPayload::ListIdentity(lip) = &response.payload { + if !lip.is_empty() { + if let EnipItemPayload::Identity(li) = &lip[0].payload { + *buffer = li.product_name.as_ptr(); + *buffer_len = li.product_name.len() as u32; + return 1; + } + } + } + } + + *buffer = std::ptr::null(); + *buffer_len = 0; + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_tx_get_service_name( + tx: &EnipTransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if let Some(ref response) = tx.response { + if let EnipPayload::ListServices(lsp) = &response.payload { + if !lsp.is_empty() { + if let EnipItemPayload::Services(ls) = &lsp[0].payload { + *buffer = ls.service_name.as_ptr(); + *buffer_len = ls.service_name.len() as u32; + return 1; + } + } + } + } + + *buffer = std::ptr::null(); + *buffer_len = 0; + return 0; +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Simple test of some valid data. + #[test] + fn test_enip_parse_cip_service() { + let buf1 = "12"; + let r1 = enip_parse_cip_service(buf1); + match r1 { + Ok((remainder, csd)) => { + // Check the first message. + assert_eq!(csd.service, 12); + assert_eq!(csd.class, None); + assert_eq!(remainder.len(), 0); + } + Err(_) => { + panic!("Result should not be an error."); + } + } + + // with spaces and all values + let buf2 = "12 , 123 , 45678"; + let r2 = enip_parse_cip_service(buf2); + match r2 { + Ok((remainder, csd)) => { + // Check the first message. + assert_eq!(csd.service, 12); + assert_eq!(csd.class, Some(123)); + assert_eq!(csd.attribute, Some(45678)); + assert_eq!(remainder.len(), 0); + } + Err(_) => { + panic!("Result should not be an error."); + } + } + + // too big for service + let buf3 = "202"; + let r3 = enip_parse_cip_service(buf3); + match r3 { + Ok((_, _)) => { + panic!("Result should be an error."); + } + Err(_) => {} + } + + // non numerical after comma + let buf4 = "123,toto"; + let r4 = enip_parse_cip_service(buf4); + match r4 { + Ok((_, _)) => { + panic!("Result should be an error."); + } + Err(_) => {} + } + + // too many commas/values + let buf5 = "1,2,3,4"; + let r5 = enip_parse_cip_service(buf5); + match r5 { + Ok((_, _)) => { + panic!("Result should be an error."); + } + Err(_) => {} + } + + // too many commas/values + let buf6 = "1,2,!3"; + let r6 = enip_parse_cip_service(buf6); + match r6 { + Ok((remainder, csd)) => { + // Check the first message. + assert_eq!(csd.service, 1); + assert_eq!(csd.class, Some(2)); + assert_eq!(csd.attribute, None); + assert_eq!(remainder.len(), 0); + } + Err(_) => { + panic!("Result should not be an error."); + } + } + } +} diff --git a/rust/src/enip/enip.rs b/rust/src/enip/enip.rs new file mode 100644 index 000000000000..d9ab218f0db4 --- /dev/null +++ b/rust/src/enip/enip.rs @@ -0,0 +1,669 @@ +/* Copyright (C) 2023 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. + */ + +use super::parser; +use crate::applayer::{self, *}; +use crate::conf::conf_get; +use crate::core::{ + AppProto, Direction, Flow, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP, IPPROTO_UDP, + STREAM_TOCLIENT, STREAM_TOSERVER, +}; +use crate::frames::Frame; +use nom7 as nom; +use std; +use std::collections::VecDeque; +use std::ffi::CString; +use std::os::raw::{c_char, c_int, c_void}; + +static mut ALPROTO_ENIP: AppProto = ALPROTO_UNKNOWN; + +static mut ENIP_MAX_TX: usize = 1024; + +#[derive(AppLayerEvent)] +enum EnipEvent { + TooManyTransactions, + InvalidPdu, +} + +#[derive(Default)] +pub struct EnipTransaction { + tx_id: u64, + pub request: Option, + pub response: Option, + pub done: bool, + + tx_data: AppLayerTxData, +} + +impl Transaction for EnipTransaction { + fn id(&self) -> u64 { + self.tx_id + } +} + +#[derive(Default)] +pub struct EnipState { + state_data: AppLayerStateData, + tx_id: u64, + transactions: VecDeque, + request_gap: bool, + response_gap: bool, +} + +impl State for EnipState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&EnipTransaction> { + self.transactions.get(index) + } +} + +fn process_frames( + pdu: &parser::EnipPdu, stream_slice: &StreamSlice, flow: *const Flow, input: &[u8], +) { + let _pdu = Frame::new( + flow, + stream_slice, + input, + ENIP_HEADER_LEN as i64, + EnipFrameType::EnipHeader as u8, + ); + let _pdu = Frame::new( + flow, + stream_slice, + &input[ENIP_HEADER_LEN as usize..], + pdu.header.pdulen as i64, + EnipFrameType::EnipPayload as u8, + ); + let items = parser::enip_pdu_get_items(pdu); + for item in items.iter() { + let _pdu = Frame::new( + flow, + stream_slice, + &input[item.start..], + 4 + item.item_length as i64, + EnipFrameType::EnipItem as u8, + ); + } + if let parser::EnipPayload::Cip(c) = &pdu.payload { + for item in c.items.iter() { + if let parser::EnipItemPayload::Data(d) = &item.payload { + let _pdu = Frame::new( + flow, + stream_slice, + &input[item.cip_offset..], + item.item_length as i64, + EnipFrameType::Cip as u8, + ); + match &d.cip.cipdir { + parser::CipDir::Request(req) => { + if let parser::EnipCipRequestPayload::Multiple(m) = &req.payload { + for i in 0..m.packet_list.len() { + let _pdu = Frame::new( + flow, + stream_slice, + &input[item.cip_offset + + m.offset_from_cip + + (m.offset_list[i] as usize)..], + m.size_list[i] as i64, + EnipFrameType::Cip as u8, + ); + } + } + } + parser::CipDir::Response(resp) => { + if let parser::EnipCipResponsePayload::Multiple(m) = &resp.payload { + for i in 0..m.packet_list.len() { + let _pdu = Frame::new( + flow, + stream_slice, + &input[item.cip_offset + + m.offset_from_cip + + (m.offset_list[i] as usize)..], + m.size_list[i] as i64, + EnipFrameType::Cip as u8, + ); + } + } + } + _ => {} + } + } + } + } +} + +impl EnipState { + pub fn new() -> Self { + Default::default() + } + + // Free a transaction by ID. + fn free_tx(&mut self, tx_id: u64) { + let len = self.transactions.len(); + let mut found = false; + let mut index = 0; + for i in 0..len { + let tx = &self.transactions[i]; + if tx.tx_id == tx_id + 1 { + found = true; + index = i; + break; + } + } + if found { + self.transactions.remove(index); + } + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&EnipTransaction> { + self.transactions.iter().find(|tx| tx.tx_id == tx_id + 1) + } + + fn new_tx(&mut self) -> EnipTransaction { + let mut tx = EnipTransaction::default(); + self.tx_id += 1; + tx.tx_id = self.tx_id; + return tx; + } + + fn purge_tx_flood(&mut self) { + let mut event_set = false; + for tx in self.transactions.iter_mut() { + tx.done = true; + if !event_set { + tx.tx_data.set_event(EnipEvent::TooManyTransactions as u8); + event_set = true; + } + } + } + + fn find_request(&mut self, pdu: &parser::EnipPdu) -> Option<&mut EnipTransaction> { + for tx in self.transactions.iter_mut() { + if let Some(req) = &tx.request { + if tx.response.is_none() { + tx.done = true; + if response_matches_request(req, pdu) { + return Some(tx); + } + } + } + } + None + } + + fn parse_udp( + &mut self, stream_slice: StreamSlice, request: bool, flow: *const Flow, + ) -> AppLayerResult { + let input = stream_slice.as_slice(); + match parser::parse_enip_pdu(input) { + Ok((_, pdu)) => { + process_frames(&pdu, &stream_slice, flow, input); + if request { + if self.transactions.len() < unsafe { ENIP_MAX_TX } { + let mut tx = self.new_tx(); + if pdu.invalid { + tx.tx_data.set_event(EnipEvent::InvalidPdu as u8); + } + tx.request = Some(pdu); + self.transactions.push_back(tx); + } else { + self.purge_tx_flood(); + } + } else if let Some(tx) = self.find_request(&pdu) { + if pdu.invalid { + tx.tx_data.set_event(EnipEvent::InvalidPdu as u8); + } + tx.response = Some(pdu); + } else { + let mut tx = self.new_tx(); + if pdu.invalid { + tx.tx_data.set_event(EnipEvent::InvalidPdu as u8); + } + tx.response = Some(pdu); + tx.done = true; + self.transactions.push_back(tx); + } + return AppLayerResult::ok(); + } + Err(_) => { + return AppLayerResult::err(); + } + } + } + fn parse_tcp( + &mut self, stream_slice: StreamSlice, request: bool, flow: *const Flow, + ) -> AppLayerResult { + let input = stream_slice.as_slice(); + if request { + if self.request_gap { + if !probe(input) { + return AppLayerResult::ok(); + } + self.request_gap = false; + } + } else if self.response_gap { + if !probe(input) { + return AppLayerResult::ok(); + } + self.response_gap = false; + } + let mut start = input; + while !start.is_empty() { + match parser::parse_enip_pdu(start) { + Ok((rem, pdu)) => { + process_frames(&pdu, &stream_slice, flow, start); + start = rem; + if request { + if self.transactions.len() < unsafe { ENIP_MAX_TX } { + let mut tx = self.new_tx(); + if pdu.invalid { + tx.tx_data.set_event(EnipEvent::InvalidPdu as u8); + } + tx.request = Some(pdu); + self.transactions.push_back(tx); + } else { + self.purge_tx_flood(); + } + } else if let Some(tx) = self.find_request(&pdu) { + if pdu.invalid { + tx.tx_data.set_event(EnipEvent::InvalidPdu as u8); + } + tx.response = Some(pdu); + } else { + let mut tx = self.new_tx(); + if pdu.invalid { + tx.tx_data.set_event(EnipEvent::InvalidPdu as u8); + } + tx.response = Some(pdu); + tx.done = true; + self.transactions.push_back(tx); + } + } + Err(nom::Err::Incomplete(_)) => { + let consumed = input.len() - start.len(); + let needed = start.len() + 1; + return AppLayerResult::incomplete(consumed as u32, needed as u32); + } + Err(_) => { + return AppLayerResult::err(); + } + } + } + + // All input was fully consumed. + return AppLayerResult::ok(); + } + + fn on_request_gap(&mut self, _size: u32) { + self.request_gap = true; + } + + fn on_response_gap(&mut self, _size: u32) { + self.response_gap = true; + } +} + +fn response_matches_request(req: &parser::EnipPdu, resp: &parser::EnipPdu) -> bool { + if req.header.cmd != resp.header.cmd { + return false; + } + if req.header.session != resp.header.session + && req.header.cmd != parser::ENIP_CMD_REGISTER_SESSION + { + // register session response has session hanbdle when request has 0 + return false; + } + if let parser::EnipPayload::Cip(c1) = &req.payload { + if let parser::EnipPayload::Cip(c2) = &resp.payload { + // connection ids are different in each direction + // and need to see beginning of stream to catch it + if c1.items.len() >= 2 + && c2.items.len() >= 2 + && c1.items[1].item_type == parser::ENIP_ITEM_TYPE_CONNECTED_DATA + && c2.items[1].item_type == parser::ENIP_ITEM_TYPE_CONNECTED_DATA + { + if let parser::EnipItemPayload::Data(d1) = &c1.items[1].payload { + if let parser::EnipItemPayload::Data(d2) = &c2.items[1].payload { + if d1.seq_num.is_some() && d1.seq_num == d2.seq_num { + return true; + } + } + } + // sequences number did not match even if they were present + return false; + } + // we do not have CIP sequence numbers + return true; + } // else default to false + } else { + if let parser::EnipPayload::Cip(_c2) = &resp.payload { + // request has no cip but response has it + return false; + } + // no cip in either + return true; + } + return false; +} + +/// Probe for a valid header. +/// +/// As this enip protocol uses messages prefixed with the size +/// as a string followed by a ':', we look at up to the first 10 +/// characters for that pattern. +fn probe(input: &[u8]) -> bool { + match parser::parse_enip_header(input) { + Ok((rem, header)) => { + match header.status { + parser::ENIP_STATUS_SUCCESS + | parser::ENIP_STATUS_INVALID_CMD + | parser::ENIP_STATUS_NO_RESOURCES + | parser::ENIP_STATUS_INCORRECT_DATA + | parser::ENIP_STATUS_INVALID_SESSION + | parser::ENIP_STATUS_INVALID_LENGTH + | parser::ENIP_STATUS_UNSUPPORTED_PROT_REV + | parser::ENIP_STATUS_ENCAP_HEADER_ERROR => {} // Ok so far, continue + _ => { + return false; + } + } + + match header.cmd { + parser::ENIP_CMD_NOP => { + if header.options != 0 { + return false; + } + } + parser::ENIP_CMD_REGISTER_SESSION => { + if header.pdulen != 4 { + return false; + } + } + parser::ENIP_CMD_UNREGISTER_SESSION => { + if header.pdulen != 4 && header.pdulen != 0 { + return false; + } + } + parser::ENIP_CMD_LIST_INTERFACES => { + if parser::parse_enip_list_interfaces(rem).is_err() { + return false; + } + } + parser::ENIP_CMD_LIST_SERVICES + | parser::ENIP_CMD_LIST_IDENTITY + | parser::ENIP_CMD_SEND_RRDATA + | parser::ENIP_CMD_SEND_UNIT_DATA + | parser::ENIP_CMD_INDICATE_STATUS + | parser::ENIP_CMD_CANCEL => {} // Ok so far, continue + _ => { + return false; + } + } + return true; + } + _ => { + return false; + } + } +} + +// C exports. + +unsafe extern "C" fn enip_probing_parser_udp( + _flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8, +) -> AppProto { + // Need at least 24 bytes. + if input_len >= ENIP_HEADER_LEN && !input.is_null() { + let slice = build_slice!(input, input_len as usize); + if probe(slice) { + return ALPROTO_ENIP; + } + } + return ALPROTO_FAILED; +} + +const ENIP_HEADER_LEN: u32 = 24; + +unsafe extern "C" fn enip_probing_parser_tcp( + _flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8, +) -> AppProto { + // Need at least 24 bytes. + if input.is_null() { + return ALPROTO_FAILED; + } + if input_len < ENIP_HEADER_LEN { + return ALPROTO_UNKNOWN; + } + let slice = build_slice!(input, input_len as usize); + if probe(slice) { + return ALPROTO_ENIP; + } + return ALPROTO_FAILED; +} + +extern "C" fn rs_enip_state_new(_orig_state: *mut c_void, _orig_proto: AppProto) -> *mut c_void { + let state = EnipState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut c_void; +} + +unsafe extern "C" fn enip_state_free(state: *mut c_void) { + std::mem::drop(Box::from_raw(state as *mut EnipState)); +} + +unsafe extern "C" fn enip_state_tx_free(state: *mut c_void, tx_id: u64) { + let state = cast_pointer!(state, EnipState); + state.free_tx(tx_id); +} + +unsafe extern "C" fn enip_parse_request_udp( + flow: *const Flow, state: *mut c_void, _pstate: *mut c_void, stream_slice: StreamSlice, + _data: *const c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, EnipState); + state.parse_udp(stream_slice, true, flow) +} + +unsafe extern "C" fn enip_parse_response_udp( + flow: *const Flow, state: *mut c_void, _pstate: *mut c_void, stream_slice: StreamSlice, + _data: *const c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, EnipState); + state.parse_udp(stream_slice, false, flow) +} + +unsafe extern "C" fn enip_parse_request_tcp( + flow: *const Flow, state: *mut c_void, pstate: *mut c_void, stream_slice: StreamSlice, + _data: *const c_void, +) -> AppLayerResult { + let eof = AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) > 0; + if eof { + return AppLayerResult::ok(); + } + + let state = cast_pointer!(state, EnipState); + if stream_slice.is_gap() { + state.on_request_gap(stream_slice.gap_size()); + AppLayerResult::ok() + } else { + debug_validate_bug_on!(stream_slice.is_empty()); + state.parse_tcp(stream_slice, true, flow) + } +} + +unsafe extern "C" fn enip_parse_response_tcp( + flow: *const Flow, state: *mut c_void, pstate: *mut c_void, stream_slice: StreamSlice, + _data: *const c_void, +) -> AppLayerResult { + let eof = AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) > 0; + if eof { + return AppLayerResult::ok(); + } + + let state = cast_pointer!(state, EnipState); + if stream_slice.is_gap() { + state.on_response_gap(stream_slice.gap_size()); + AppLayerResult::ok() + } else { + debug_validate_bug_on!(stream_slice.is_empty()); + state.parse_tcp(stream_slice, false, flow) + } +} + +unsafe extern "C" fn rs_enip_state_get_tx(state: *mut c_void, tx_id: u64) -> *mut c_void { + let state = cast_pointer!(state, EnipState); + match state.get_tx(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +unsafe extern "C" fn rs_enip_state_get_tx_count(state: *mut c_void) -> u64 { + let state = cast_pointer!(state, EnipState); + return state.tx_id; +} + +unsafe extern "C" fn rs_enip_tx_get_alstate_progress(tx: *mut c_void, direction: u8) -> c_int { + let tx = cast_pointer!(tx, EnipTransaction); + + // Transaction is done if we have a response. + if tx.done { + return 1; + } + let dir: Direction = direction.into(); + if dir == Direction::ToServer { + if tx.request.is_some() { + return 1; + } + } else if tx.response.is_some() { + return 1; + } + return 0; +} + +// app-layer-frame-documentation tag start: FrameType enum +#[derive(AppLayerFrameType)] +pub enum EnipFrameType { + EnipHeader, + EnipPayload, + Cip, + EnipItem, +} + +export_tx_data_get!(rs_enip_get_tx_data, EnipTransaction); +export_state_data_get!(rs_enip_get_state_data, EnipState); + +// Parser name as a C style string. +const PARSER_NAME: &[u8] = b"enip\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_register_parsers() { + let default_port = CString::new("[44818]").unwrap(); + let mut parser = RustParser { + name: PARSER_NAME.as_ptr() as *const c_char, + default_port: default_port.as_ptr(), + ipproto: IPPROTO_UDP, + probe_ts: Some(enip_probing_parser_udp), + probe_tc: Some(enip_probing_parser_udp), + min_depth: 0, + max_depth: 16, + state_new: rs_enip_state_new, + state_free: enip_state_free, + tx_free: enip_state_tx_free, + parse_ts: enip_parse_request_udp, + parse_tc: enip_parse_response_udp, + get_tx_count: rs_enip_state_get_tx_count, + get_tx: rs_enip_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_enip_tx_get_alstate_progress, + get_eventinfo: Some(EnipEvent::get_event_info), + get_eventinfo_byid: Some(EnipEvent::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some(applayer::state_get_tx_iterator::), + get_tx_data: rs_enip_get_tx_data, + get_state_data: rs_enip_get_state_data, + apply_tx_config: None, + flags: 0, + truncate: None, + get_frame_id_by_name: Some(EnipFrameType::ffi_id_from_name), + get_frame_name_by_id: Some(EnipFrameType::ffi_name_from_id), + }; + + let ip_proto_str = CString::new("udp").unwrap(); + + if let Some(val) = conf_get("app-layer.protocols.enip.max-tx") { + if let Ok(v) = val.parse::() { + ENIP_MAX_TX = v; + } else { + SCLogError!("Invalid value for enip.max-tx"); + } + } + + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_ENIP = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + SCLogDebug!("Rust enip parser registered for UDP."); + unsafe { + AppLayerParserRegisterParserAcceptableDataDirection( + IPPROTO_UDP, + ALPROTO_ENIP, + STREAM_TOSERVER | STREAM_TOCLIENT, + ); + } + } else { + SCLogDebug!("Protocol detector and parser disabled for ENIP on UDP."); + } + + parser.ipproto = IPPROTO_TCP; + parser.probe_ts = Some(enip_probing_parser_tcp); + parser.probe_tc = Some(enip_probing_parser_tcp); + parser.parse_ts = enip_parse_request_tcp; + parser.parse_tc = enip_parse_response_tcp; + parser.flags = APP_LAYER_PARSER_OPT_ACCEPT_GAPS; + + let ip_proto_str = CString::new("tcp").unwrap(); + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_ENIP = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + SCLogDebug!("Rust enip parser registered for TCP."); + unsafe { + AppLayerParserRegisterParserAcceptableDataDirection( + IPPROTO_TCP, + ALPROTO_ENIP, + STREAM_TOSERVER | STREAM_TOCLIENT, + ); + } + } else { + SCLogDebug!("Protocol detector and parser disabled for ENIP on TCP."); + } +} diff --git a/rust/src/enip/logger.rs b/rust/src/enip/logger.rs new file mode 100644 index 000000000000..ce1a947cd73e --- /dev/null +++ b/rust/src/enip/logger.rs @@ -0,0 +1,1905 @@ +/* Copyright (C) 2023 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. + */ + +use super::enip::EnipTransaction; +use super::parser::{ + cip_segment_type_string, enip_command_string, enip_status_string, CipData, CipDir, EnipCIP, + EnipCipPathSegment, EnipCipRequestPayload, EnipCipResponsePayload, EnipHeader, EnipItemPayload, + EnipPayload, +}; +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use std; + +fn log_enip_header(h: &EnipHeader, js: &mut JsonBuilder) -> Result<(), JsonError> { + match enip_command_string(h.cmd) { + Some(val) => { + js.set_string("command", val)?; + } + None => { + js.set_string("command", &format!("unknown-{}", h.cmd))?; + } + } + match enip_status_string(h.status) { + Some(val) => { + js.set_string("status", val)?; + } + None => { + js.set_string("status", &format!("unknown-{}", h.status))?; + } + } + if h.options != 0 { + js.set_uint("options", h.options.into())?; + } + Ok(()) +} + +fn enip_vendorid_string(p: u16) -> Option<&'static str> { + match p { + 1 => Some("Rockwell Automation/Allen-Bradley"), + 2 => Some("Namco Controls Corp."), + 3 => Some("Honeywell Inc."), + 4 => Some("Parker Hannifin Corp. (Veriflo Division)"), + 5 => Some("Rockwell Automation/Reliance Elec."), + 7 => Some("SMC Corporation"), + 8 => Some("Molex Incorporated"), + 9 => Some("Western Reserve Controls Corp."), + 10 => Some("Advanced Micro Controls Inc. (AMCI)"), + 11 => Some("ASCO Pneumatic Controls"), + 12 => Some("Banner Engineering Corp."), + 13 => Some("Belden Wire & Cable Company"), + 14 => Some("Cooper Interconnect"), + 16 => Some("Daniel Woodhead Co. (Woodhead Connectivity)"), + 17 => Some("Dearborn Group Inc."), + 19 => Some("Helm Instrument Company"), + 20 => Some("Huron Net Works"), + 21 => Some("Lumberg Inc."), + 22 => Some("Online Development Inc.(Automation Value)"), + 23 => Some("Vorne Industries Inc."), + 24 => Some("ODVA Special Reserve"), + 26 => Some("Festo Corporation"), + 30 => Some("Unico Inc."), + 31 => Some("Ross Controls"), + 34 => Some("Hohner Corp."), + 35 => Some("Micro Mo Electronics Inc."), + 36 => Some("MKS Instruments Inc."), + 37 => Some("Yaskawa Electric America (formerly Magnetek Drives)"), + 39 => Some("AVG Automation (Uticor)"), + 40 => Some("Wago Corporation"), + 41 => Some("Kinetics (Unit Instruments)"), + 42 => Some("IMI Norgren Limited"), + 43 => Some("BALLUFF Inc."), + 44 => Some("Yaskawa Electric America Inc."), + 45 => Some("Eurotherm Controls Inc"), + 46 => Some("ABB Industrial Systems"), + 47 => Some("Omron Corporation"), + 48 => Some("Turck Inc."), + 49 => Some("Grayhill Inc."), + 50 => Some("Real Time Automation (C&ID)"), + 52 => Some("Numatics Inc."), + 53 => Some("Lutze Inc."), + 56 => Some("Softing GmbH"), + 57 => Some("Pepperl + Fuchs"), + 58 => Some("Spectrum Controls Inc."), + 59 => Some("D.I.P. Inc. MKS Inst."), + 60 => Some("Applied Motion Products Inc."), + 61 => Some("Sencon Inc."), + 62 => Some("High Country Tek"), + 63 => Some("SWAC Automation Consult GmbH"), + 64 => Some("Clippard Instrument Laboratory"), + 68 => Some("Eaton Electrical"), + 71 => Some("Toshiba International Corp."), + 72 => Some("Control Technology Incorporated"), + 73 => Some("TCS (NZ) Ltd."), + 74 => Some("Hitachi Ltd."), + 75 => Some("ABB Robotics Products AB"), + 76 => Some("NKE Corporation"), + 77 => Some("Rockwell Software Inc."), + 78 => Some("Escort Memory Systems (A Datalogic Group Co.)"), + 79 => Some("Berk-Tek"), + 80 => Some("Industrial Devices Corporation"), + 81 => Some("IXXAT Automation GmbH"), + 82 => Some("Mitsubishi Electric Automation Inc."), + 83 => Some("OPTO-22"), + 86 => Some("Horner Electric"), + 87 => Some("Burkert Werke GmbH & Co. KG"), + 88 => Some("Industrial Indexing Systems, Inc."), + 89 => Some("Industrial Indexing Systems Inc."), + 90 => Some("HMS Industrial Networks AB"), + 91 => Some("Robicon"), + 92 => Some("Helix Technology (Granville-Phillips)"), + 93 => Some("Arlington Laboratory"), + 94 => Some("Advantech Co. Ltd."), + 95 => Some("Square D Company"), + 96 => Some("Digital Electronics Corp."), + 97 => Some("Danfoss"), + 100 => Some("Bosch Rexroth Corporation, Pneumatics"), + 101 => Some("Applied Materials Inc."), + 102 => Some("Showa Electric Wire & Cable Co."), + 103 => Some("Pacific Scientific (API Controls Inc.)"), + 104 => Some("Sharp Manufacturing Systems Corp."), + 105 => Some("Olflex Wire & Cable Inc."), + 107 => Some("Unitrode"), + 108 => Some("Beckhoff Automation GmbH"), + 109 => Some("National Instruments"), + 110 => Some("Mykrolis Corporations (Millipore)"), + 111 => Some("International Motion Controls Corp."), + 113 => Some("SEG Kempen GmbH"), + 116 => Some("MTS Systems Corp."), + 117 => Some("Krones Inc"), + 119 => Some("EXOR Electronic R&D"), + 120 => Some("SIEI S.p.A."), + 121 => Some("KUKA Roboter GmbH"), + 123 => Some("SEC (Samsung Electronics Co. Ltd.)"), + 124 => Some("Binary Electronics Ltd"), + 125 => Some("Flexible Machine Controls"), + 127 => Some("ABB Inc. (Entrelec)"), + 128 => Some("MAC Valves Inc."), + 129 => Some("Auma Actuators Inc."), + 130 => Some("Toyoda Machine Works Ltd."), + 133 => Some("Balogh T.A.G. Corporation"), + 134 => Some("TR Systemtechnik GmbH"), + 135 => Some("UNIPULSE Corporation"), + 138 => Some("Conxall Corporation Inc."), + 141 => Some("Kuramo Electric Co. Ltd."), + 142 => Some("Creative Micro Designs"), + 143 => Some("GE Industrial Systems"), + 144 => Some("Leybold Vacuum GmbH"), + 145 => Some("Siemens Energy & Automation/Drives"), + 146 => Some("Kodensha Ltd."), + 147 => Some("Motion Engineering Inc."), + 148 => Some("Honda Engineering Co. Ltd."), + 149 => Some("EIM Valve Controls"), + 150 => Some("Melec Inc."), + 151 => Some("Sony Manufacturing Systems Corporation"), + 152 => Some("North American Mfg."), + 153 => Some("WATLOW"), + 154 => Some("Japan Radio Co. Ltd."), + 155 => Some("NADEX Co. Ltd."), + 156 => Some("Ametek Automation & Process Technologies"), + 157 => Some("FACTS, Inc."), + 158 => Some("KVASER AB"), + 159 => Some("IDEC IZUMI Corporation"), + 160 => Some("Mitsubishi Heavy Industries Ltd."), + 161 => Some("Mitsubishi Electric Corporation"), + 162 => Some("Horiba-STEC Inc."), + 163 => Some("ESD Electronic System Design GmbH"), + 164 => Some("DAIHEN Corporation"), + 165 => Some("Tyco Valves & Controls/Keystone"), + 166 => Some("EBARA Corporation"), + 169 => Some("Hokuyo Electric Co. Ltd."), + 170 => Some("Pyramid Solutions Inc."), + 171 => Some("Denso Wave Incorporated"), + 172 => Some("HLS Hard-Line Solutions Inc."), + 173 => Some("Caterpillar Inc."), + 174 => Some("PDL Electronics Ltd."), + 176 => Some("Red Lion Controls"), + 177 => Some("ANELVA Corporation"), + 178 => Some("Toyo Denki Seizo KK"), + 179 => Some("Sanyo Denki Co. Ltd."), + 180 => Some("Advanced Energy Japan K.K. (Aera Japan)"), + 181 => Some("Pilz GmbH & Co"), + 182 => Some("Marsh Bellofram-Bellofram PCD Division"), + 184 => Some("M-SYSTEM Co. Ltd."), + 185 => Some("Nissin Electric Co. Ltd."), + 186 => Some("Hitachi Metals Ltd."), + 187 => Some("Oriental Motor Company"), + 188 => Some("A&D Co. Ltd."), + 189 => Some("Phasetronics Inc."), + 190 => Some("Cummins Engine Company"), + 191 => Some("Deltron Inc."), + 192 => Some("Geneer Corporation"), + 193 => Some("Anatol Automation Inc."), + 196 => Some("Medar Inc."), + 197 => Some("Comdel Inc."), + 198 => Some("Advanced Energy Industries Inc."), + 200 => Some("DAIDEN Co. Ltd."), + 201 => Some("CKD Corporation"), + 202 => Some("Toyo Electric Corporation"), + 204 => Some("AuCom Electronics Ltd."), + 205 => Some("Shinko Electric Co. Ltd."), + 206 => Some("Vector Informatik GmbH"), + 208 => Some("Moog Inc."), + 209 => Some("Contemporary Controls"), + 210 => Some("Tokyo Sokki Kenkyujo Co. Ltd."), + 211 => Some("Schenck-AccuRate Inc."), + 212 => Some("The Oilgear Company"), + 214 => Some("ASM Japan K.K."), + 215 => Some("HIRATA Corp."), + 216 => Some("SUNX Limited"), + 217 => Some("Meidensha Corp."), + 218 => Some("NIDEC SANKYO CORPORATION (Sankyo Seiki Mfg. Co. Ltd.)"), + 219 => Some("KAMRO Corp."), + 220 => Some("Nippon System Development Co. Ltd."), + 221 => Some("EBARA Technologies Inc."), + 224 => Some("SG Co. Ltd."), + 225 => Some("Vaasa Institute of Technology"), + 226 => Some("MKS Instruments (ENI Technology)"), + 227 => Some("Tateyama System Laboratory Co. Ltd."), + 228 => Some("QLOG Corporation"), + 229 => Some("Matric Limited Inc."), + 230 => Some("NSD Corporation"), + 232 => Some("Sumitomo Wiring Systems Ltd."), + 233 => Some("Group 3 Technology Ltd."), + 234 => Some("CTI Cryogenics"), + 235 => Some("POLSYS CORP"), + 236 => Some("Ampere Inc."), + 238 => Some("Simplatroll Ltd."), + 241 => Some("Leading Edge Design"), + 242 => Some("Humphrey Products"), + 243 => Some("Schneider Automation Inc."), + 244 => Some("Westlock Controls Corp."), + 245 => Some("Nihon Weidmuller Co. Ltd."), + 246 => Some("Brooks Instrument (Div. of Emerson)"), + 248 => Some("Moeller GmbH"), + 249 => Some("Varian Vacuum Products"), + 250 => Some("Yokogawa Electric Corporation"), + 251 => Some("Electrical Design Daiyu Co. Ltd."), + 252 => Some("Omron Software Co. Ltd."), + 253 => Some("BOC Edwards"), + 254 => Some("Control Technology Corporation"), + 255 => Some("Bosch Rexroth"), + 256 => Some("Turck"), + 257 => Some("Control Techniques PLC"), + 258 => Some("Hardy Instruments Inc."), + 259 => Some("LS Industrial Systems"), + 260 => Some("E.O.A. Systems Inc."), + 262 => Some("New Cosmos Electric Co. Ltd."), + 263 => Some("Sense Eletronica LTDA"), + 264 => Some("Xycom Inc."), + 265 => Some("Baldor Electric"), + 267 => Some("Patlite Corporation"), + 269 => Some("Mogami Wire & Cable Corporation"), + 270 => Some("Welding Technology Corporation (WTC)"), + 272 => Some("Deutschmann Automation GmbH"), + 273 => Some("ICP Panel-Tec Inc."), + 274 => Some("Bray Controls USA"), + 276 => Some("Status Technologies"), + 277 => Some("Trio Motion Technology Ltd."), + 278 => Some("Sherrex Systems Ltd."), + 279 => Some("Adept Technology Inc."), + 280 => Some("Spang Power Electronics"), + 282 => Some("Acrosser Technology Co. Ltd."), + 283 => Some("Hilscher GmbH"), + 284 => Some("IMAX Corporation"), + 285 => Some("Electronic Innovation Inc. (Falter Engineering)"), + 286 => Some("Netlogic Inc."), + 287 => Some("Bosch Rexroth Corporation, Indramat"), + 290 => Some("Murata Machinery Ltd."), + 291 => Some("MTT Company Ltd."), + 292 => Some("Kanematsu Semiconductor Corp."), + 293 => Some("Takebishi Electric Sales Co."), + 294 => Some("Tokyo Electron Device Ltd."), + 295 => Some("PFU Limited"), + 296 => Some("Hakko Automation Co. Ltd."), + 297 => Some("Advanet Inc."), + 298 => Some("Tokyo Electron Software Technologies Ltd."), + 300 => Some("Shinagawa Electric Wire Co. Ltd."), + 301 => Some("Yokogawa M&C Corporation"), + 302 => Some("KONAN Electric Co. Ltd."), + 303 => Some("Binar Elektronik AB"), + 304 => Some("Furukawa Electric Co."), + 305 => Some("Cooper Energy Services"), + 306 => Some("Schleicher GmbH & Co."), + 307 => Some("Hirose Electric Co. Ltd."), + 308 => Some("Western Servo Design Inc."), + 309 => Some("Prosoft Technology"), + 311 => Some("Towa Shoko Co. Ltd."), + 312 => Some("Kyopal Co. Ltd."), + 313 => Some("Extron Co."), + 314 => Some("Wieland Electric GmbH"), + 315 => Some("SEW Eurodrive GmbH"), + 316 => Some("Aera Corporation"), + 317 => Some("STA Reutlingen"), + 319 => Some("Fuji Electric Co. Ltd."), + 322 => Some("ifm efector, inc."), + 324 => Some("IDEACOD-Hohner Automation S.A."), + 325 => Some("CommScope Inc."), + 326 => Some("GE Fanuc Automation North America Inc."), + 327 => Some("Matsushita Electric Industrial Co. Ltd."), + 328 => Some("Okaya Electronics Corporation"), + 329 => Some("KASHIYAMA Industries Ltd."), + 330 => Some("JVC"), + 331 => Some("Interface Corporation"), + 332 => Some("Grape Systems Inc."), + 334 => Some("KEBA AG"), + 335 => Some("Toshiba IT & Control Systems Corporation"), + 336 => Some("Sanyo Machine Works Ltd."), + 337 => Some("Vansco Electronics Ltd."), + 338 => Some("Dart Container Corp."), + 339 => Some("Livingston & Co. Inc."), + 340 => Some("Alfa Laval LKM A/S"), + 341 => Some("BF ENTRON Ltd. (British Federal)"), + 342 => Some("Bekaert Engineering NV"), + 343 => Some("Ferran Scientific Inc."), + 344 => Some("KEBA AG"), + 345 => Some("Endress + Hauser"), + 346 => Some("Lincoln Electric Company"), + 347 => Some("ABB ALSTOM Power UK Ltd. (EGT)"), + 348 => Some("Berger Lahr GmbH"), + 350 => Some("Federal Signal Corp."), + 351 => Some("Kawasaki Robotics (USA) Inc."), + 352 => Some("Bently Nevada Corporation"), + 354 => Some("FRABA Posital GmbH"), + 355 => Some("Elsag Bailey Inc."), + 356 => Some("Fanuc Robotics America"), + 358 => Some("Surface Combustion Inc."), + 360 => Some("AILES Electronics Ind. Co. Ltd."), + 361 => Some("Wonderware Corporation"), + 362 => Some("Particle Measuring Systems Inc."), + 365 => Some("BITS Co. Ltd."), + 366 => Some("Japan Aviation Electronics Industry Ltd."), + 367 => Some("Keyence Corporation"), + 368 => Some("Kuroda Precision Industries Ltd."), + 369 => Some("Mitsubishi Electric Semiconductor Application"), + 370 => Some("Nippon Seisen Cable Ltd."), + 371 => Some("Omron ASO Co. Ltd."), + 372 => Some("Seiko Seiki Co. Ltd."), + 373 => Some("Sumitomo Heavy IndustriesLtd."), + 374 => Some("Tango Computer Service Corporation"), + 375 => Some("Technology Service Inc."), + 376 => Some("Toshiba Information Systems (Japan) Corporation"), + 377 => Some("TOSHIBA Schneider Inverter Corporation"), + 378 => Some("Toyooki Kogyo Co. Ltd."), + 379 => Some("XEBEC"), + 380 => Some("Madison Cable Corporation"), + 381 => Some("Hitati Engineering & Services Co. Ltd."), + 382 => Some("TEM-TECH Lab Co. Ltd."), + 383 => Some("International Laboratory Corporation"), + 384 => Some("Dyadic Systems Co. Ltd."), + 385 => Some("SETO Electronics Industry Co. Ltd."), + 386 => Some("Tokyo Electron Kyushu Limited"), + 387 => Some("KEI System Co. Ltd."), + 389 => Some("Asahi Engineering Co. Ltd."), + 390 => Some("Contrex Inc."), + 391 => Some("Paradigm Controls Ltd."), + 393 => Some("Ohm Electric Co. Ltd."), + 394 => Some("RKC Instrument Inc."), + 395 => Some("Suzuki Motor Corporation"), + 396 => Some("Custom Servo Motors Inc."), + 397 => Some("PACE Control Systems"), + 398 => Some("Selectron Systems AG"), + 400 => Some("LINTEC Co. Ltd."), + 401 => Some("Hitachi Cable Ltd."), + 402 => Some("BUSWARE Direct"), + 403 => Some("Eaton Electric B.V. (formerly Holec Holland N.V.)"), + 404 => Some("VAT Vakuumventile AG"), + 405 => Some("Scientific Technologies Incorporated"), + 406 => Some("Alfa Instrumentos Eletronicos Ltda."), + 407 => Some("TWK Elektronik GmbH"), + 408 => Some("ABB Welding Systems AB"), + 409 => Some("BYSTRONIC Maschinen AG"), + 410 => Some("Kimura Electric Co. Ltd."), + 411 => Some("Nissei Plastic Industrial Co. Ltd."), + 413 => Some("Kistler-Morse Corporation"), + 414 => Some("Proteous Industries Inc."), + 415 => Some("IDC Corporation"), + 416 => Some("Nordson Corporation"), + 417 => Some("Rapistan Systems"), + 418 => Some("LP-Elektronik GmbH"), + 419 => Some("GERBI & FASE S.p.A. (Fase Saldatura)"), + 420 => Some("Phoenix Digital Corporation"), + 421 => Some("Z-World Engineering"), + 422 => Some("Honda R&D Co. Ltd."), + 423 => Some("Bionics Instrument Co. Ltd."), + 424 => Some("Teknic Inc."), + 425 => Some("R.Stahl Inc."), + 427 => Some("Ryco Graphic Manufacturing Inc."), + 428 => Some("Giddings & Lewis Inc."), + 429 => Some("Koganei Corporation"), + 431 => Some("Nichigoh Communication Electric Wire Co. Ltd."), + 433 => Some("Fujikura Ltd."), + 434 => Some("AD Link Technology Inc."), + 435 => Some("StoneL Corporation"), + 436 => Some("Computer Optical Products Inc."), + 437 => Some("CONOS Inc."), + 438 => Some("Erhardt + Leimer GmbH"), + 439 => Some("UNIQUE Co. Ltd."), + 440 => Some("Roboticsware Inc."), + 441 => Some("Nachi Fujikoshi Corporation"), + 442 => Some("Hengstler GmbH"), + 443 => Some("Vacon Plc"), + 444 => Some("SUNNY GIKEN Inc."), + 445 => Some("Lenze Drive Systems GmbH"), + 446 => Some("CD Systems B.V."), + 447 => Some("FMT/Aircraft Gate Support Systems AB"), + 448 => Some("Axiomatic Technologies Corp"), + 449 => Some("Embedded System Products Inc."), + 451 => Some("Mencom Corporation"), + 452 => Some("Kollmorgen"), + 453 => Some("Matsushita Welding Systems Co. Ltd."), + 454 => Some("Dengensha Mfg. Co. Ltd."), + 455 => Some("Quinn Systems Ltd."), + 456 => Some("Tellima Technology Ltd."), + 457 => Some("MDT Software"), + 458 => Some("Taiwan Keiso Co. Ltd."), + 459 => Some("Pinnacle Systems"), + 460 => Some("Ascom Hasler Mailing Sys."), + 461 => Some("INSTRUMAR Limited"), + 463 => Some("Navistar International Transportation Corp."), + 464 => Some("Huettinger Elektronik GmbH + Co. KG"), + 465 => Some("OCM Technology Inc."), + 466 => Some("Professional Supply Inc."), + 467 => Some("Control Solutions"), + 468 => Some("Baumer IVO GmbH & Co. KG"), + 469 => Some("Worcester Controls Corporation"), + 470 => Some("Pyramid Technical Consultants Inc."), + 471 => Some("Eilersen Electric A/S"), + 472 => Some("Apollo Fire Detectors Limited"), + 473 => Some("Avtron Manufacturing Inc."), + 475 => Some("Tokyo Keiso Co. Ltd."), + 476 => Some("Daishowa Swiki Co. Ltd."), + 477 => Some("Kojima Instruments Inc."), + 478 => Some("Shimadzu Corporation"), + 479 => Some("Tatsuta Electric Wire & Cable Co. Ltd."), + 480 => Some("MECS Corporation"), + 481 => Some("Tahara Electric"), + 482 => Some("Koyo Electronics"), + 483 => Some("Clever Devices"), + 484 => Some("GCD Hardware & Software GmbH"), + 486 => Some("Miller Electric Mfg. Co."), + 487 => Some("GEA Tuchenhagen GmbH"), + 488 => Some("Riken Keiki Co., Ltd."), + 489 => Some("Keisokugiken Corporation"), + 490 => Some("Fuji Machine Mfg. Co. Ltd."), + 492 => Some("Nidec-Shimpo Corp."), + 493 => Some("UTEC Corporation"), + 494 => Some("Sanyo Electric Co. Ltd."), + 497 => Some("Okano Electric Wire Co. Ltd."), + 498 => Some("Shimaden Co. Ltd."), + 499 => Some("Teddington Controls Ltd"), + 501 => Some("VIPA GmbH"), + 502 => Some("Warwick Manufacturing Group"), + 503 => Some("Danaher Controls"), + 506 => Some("American Science & Engineering"), + 507 => Some("Accutron Controls International Inc."), + 508 => Some("Norcott Technologies Ltd."), + 509 => Some("TB Woods Inc."), + 510 => Some("Proportion-Air Inc."), + 511 => Some("SICK Stegmann GmbH"), + 513 => Some("Edwards Signaling"), + 514 => Some("Sumitomo Metal Industries Ltd."), + 515 => Some("Cosmo Instruments Co. Ltd."), + 516 => Some("Denshosha Co. Ltd."), + 517 => Some("Kaijo Corp."), + 518 => Some("Michiproducts Co. Ltd."), + 519 => Some("Miura Corporation"), + 520 => Some("TG Information Network Co. Ltd."), + 521 => Some("Fujikin Inc."), + 522 => Some("Estic Corp."), + 523 => Some("GS Hydraulic Sales"), + 524 => Some("Leuze Electronic GmbH & Co. KG"), + 525 => Some("MTE Limited"), + 526 => Some("Hyde Park Electronics Inc."), + 527 => Some("Pfeiffer Vacuum GmbH"), + 528 => Some("Cyberlogic Technologies"), + 529 => Some("OKUMA Corporation FA Systems Division"), + 531 => Some("Hitachi Kokusai Electric Co. Ltd."), + 532 => Some("SHINKO TECHNOS Co. Ltd."), + 533 => Some("Itoh Electric Co. Ltd."), + 534 => Some("Colorado Flow Tech Inc."), + 535 => Some("Love Controls Division/Dwyer Inst."), + 536 => Some("Alstom Drives and Controls"), + 537 => Some("The Foxboro Company"), + 538 => Some("Tescom Corporation"), + 540 => Some("Atlas Copco Controls UK"), + 542 => Some("Autojet Technologies"), + 543 => Some("Prima Electronics S.p.A."), + 544 => Some("PMA GmbH"), + 545 => Some("Shimafuji Electric Co. Ltd."), + 546 => Some("Oki Electric Industry Co. Ltd."), + 547 => Some("Kyushu Matsushita Electric Co. Ltd."), + 548 => Some("Nihon Electric Wire & Cable Co. Ltd."), + 549 => Some("Tsuken Electric Ind Co. Ltd."), + 550 => Some("Tamadic Co."), + 551 => Some("MAATEL SA"), + 552 => Some("OKUMA America"), + 553 => Some("Control Techniques PLC-NA"), + 554 => Some("TPC Wire & Cable"), + 555 => Some("ATI Industrial Automation"), + 556 => Some("Microcontrol (Australia) Pty Ltd."), + 557 => Some("Serra Soldadura, S.A."), + 558 => Some("Southwest Research Institute"), + 559 => Some("Cabinplant International"), + 560 => Some("Sartorius Mechatronics T&H GmbH"), + 561 => Some("Comau S.p.A. Robotics & Final Assembly Division"), + 562 => Some("Phoenix Contact"), + 563 => Some("Yokogawa MAT Corporation"), + 564 => Some("Asahi Sangyo Co., Ltd."), + 566 => Some("Akita Myotoku Ltd."), + 567 => Some("OBARA Corp."), + 568 => Some("Suetron Electronic GmbH"), + 570 => Some("Serck Controls Limited"), + 571 => Some("Fairchild Industrial Products Company"), + 572 => Some("ARO S.A."), + 573 => Some("M2C GmbH"), + 574 => Some("Shin Caterpillar Mitsubishi Ltd."), + 575 => Some("Santest Co. Ltd."), + 576 => Some("Cosmotechs Co. Ltd."), + 577 => Some("Hitachi Electric Systems"), + 578 => Some("Smartscan Ltd."), + 579 => Some("Woodhead Software & Electronics France"), + 580 => Some("Athena Controls Inc."), + 581 => Some("Syron Engineering & Manufacturing Inc."), + 582 => Some("Asahi Optical Co. Ltd."), + 583 => Some("Sansha Electric Mfg. Co. Ltd."), + 584 => Some("Nikki Denso Co. Ltd."), + 585 => Some("Star Micronics => Some( Co. Ltd."), + 586 => Some("Ecotecnia Socirtat Corp."), + 587 => Some("AC Technology Corp."), + 588 => Some("West Instruments Limited"), + 589 => Some("NTI Limited"), + 590 => Some("Delta Computer Systems Inc."), + 591 => Some("FANUC Ltd."), + 592 => Some("Hearn-Gu Lee"), + 593 => Some("ABB Automation Products"), + 594 => Some("Orion Machinery Co. Ltd."), + 596 => Some("Wire-Pro Inc."), + 597 => Some("Beijing Huakong Technology Co. Ltd."), + 598 => Some("Yokoyama Shokai Co. Ltd."), + 599 => Some("Toyogiken Co. Ltd."), + 600 => Some("Coester Equipamentos Eletronicos Ltda."), + 601 => Some("Kawasaki Heavy Industries, Ltd."), + 602 => Some("Electroplating Engineers of Japan Ltd."), + 603 => Some("ROBOX S.p.A."), + 604 => Some("Spraying Systems Company"), + 605 => Some("Benshaw Inc."), + 606 => Some("ZPA-DP A.S."), + 607 => Some("Wired Rite Systems"), + 608 => Some("Tandis Research Inc."), + 609 => Some("SSD Drives GmbH"), + 610 => Some("ULVAC Japan Ltd."), + 611 => Some("DYNAX Corporation"), + 612 => Some("Nor-Cal Products Inc."), + 613 => Some("Aros Electronics AB"), + 614 => Some("Jun-Tech Co. Ltd."), + 615 => Some("HAN-MI Co. Ltd."), + 616 => Some("uniNtech (formerly SungGi Internet)"), + 617 => Some("Hae Pyung Electronics Research Institute"), + 618 => Some("Milwaukee Electronics"), + 619 => Some("OBERG Industries"), + 620 => Some("Parker Hannifin/Compumotor Division"), + 621 => Some("TECHNO DIGITAL CORPORATION"), + 622 => Some("Network Supply Co. Ltd."), + 623 => Some("Union Electronics Co. Ltd."), + 624 => Some("Tritronics Services PM Ltd."), + 625 => Some("Rockwell Automation-Sprecher+Schuh"), + 626 => Some("Matsushita Electric Industrial Co. Ltd. Motor Co."), + 627 => Some("Rolls-Royce Energy Systems Inc."), + 628 => Some("Jeongil Intercom Co., Ltd."), + 629 => Some("Interroll Corp."), + 630 => Some("Hubbell Wiring Device-Kellems (Delaware)"), + 631 => Some("Intelligent Motion Systems"), + 633 => Some("INFICON AG"), + 634 => Some("Hirschmann Inc."), + 635 => Some("The Siemon Company"), + 636 => Some("YAMAHA Motor Co. Ltd."), + 637 => Some("aska corporation"), + 638 => Some("Woodhead Connectivity"), + 639 => Some("Trimble AB"), + 640 => Some("Murrelektronik GmbH"), + 641 => Some("Creatrix Labs Inc."), + 642 => Some("TopWorx"), + 643 => Some("Kumho Industrial Co. Ltd."), + 644 => Some("Wind River Systems Inc."), + 645 => Some("Bihl & Wiedemann GmbH"), + 646 => Some("Harmonic Drive Systems Inc."), + 647 => Some("Rikei Corporation"), + 648 => Some("BL Autotec Ltd."), + 649 => Some("Hana Information & Technology Co. Ltd."), + 650 => Some("Seoil Electric Co. Ltd."), + 651 => Some("Fife Corporation"), + 652 => Some("Shanghai Electrical Apparatus Research Institute"), + 653 => Some("Detector Electronics"), + 654 => Some("Parasense Development Centre"), + 657 => Some("Six Tau S.p.A."), + 658 => Some("Aucos GmbH"), + 659 => Some("Rotork Controls"), + 660 => Some("Automationdirect.com"), + 661 => Some("Thermo BLH"), + 662 => Some("System Controls Ltd."), + 663 => Some("Univer S.p.A."), + 664 => Some("MKS-Tenta Technology"), + 665 => Some("Lika Electronic SNC"), + 666 => Some("Mettler-Toledo Inc."), + 667 => Some("DXL USA Inc."), + 668 => Some("Rockwell Automation/Entek IRD Intl."), + 669 => Some("Nippon Otis Elevator Company"), + 670 => Some("Sinano Electric => Some( Co. Ltd."), + 671 => Some("Sony Manufacturing Systems"), + 673 => Some("Contec Co. Ltd."), + 674 => Some("Automated Solutions"), + 675 => Some("Controlweigh"), + 677 => Some("Fincor Electronics"), + 678 => Some("Cognex Corporation"), + 679 => Some("Qualiflow"), + 680 => Some("Weidmuller Inc."), + 681 => Some("Morinaga Milk Industry Co. Ltd."), + 682 => Some("Takagi Industrial Co. Ltd."), + 683 => Some("Wittenstein AG"), + 684 => Some("Sena Technologies Inc."), + 686 => Some("APV Products Unna"), + 687 => Some("Creator Teknisk Utvedkling AB"), + 689 => Some("Mibu Denki Industrial Co. Ltd."), + 690 => Some("Takamastsu Machineer Section"), + 691 => Some("Startco Engineering Ltd."), + 693 => Some("Holjeron"), + 694 => Some("ALCATEL High Vacuum Technology"), + 695 => Some("Taesan LCD Co. Ltd."), + 696 => Some("POSCON"), + 697 => Some("VMIC"), + 698 => Some("Matsushita Electric Works Ltd."), + 699 => Some("IAI Corporation"), + 700 => Some("Horst GmbH"), + 701 => Some("MicroControl GmbH & Co."), + 702 => Some("Leine & Linde AB"), + 704 => Some("EC Elettronica Srl"), + 705 => Some("VIT Software HB"), + 706 => Some("Bronkhorst High-Tech B.V."), + 707 => Some("Optex Co. Ltd."), + 708 => Some("Yosio Electronic Co."), + 709 => Some("Terasaki Electric Co. Ltd."), + 710 => Some("Sodick Co. Ltd."), + 711 => Some("MTS Systems Corporation-Automation Division"), + 712 => Some("Mesa Systemtechnik"), + 713 => Some("SHIN HO SYSTEM Co. Ltd."), + 714 => Some("Goyo Electronics Co. Ltd."), + 715 => Some("Loreme"), + 716 => Some("SAB Brockskes GmbH & Co. KG"), + 717 => Some("Trumpf Laser GmbH + Co. KG"), + 718 => Some("Niigata Electronic Instruments Co. Ltd."), + 719 => Some("Yokogawa Digital Computer Corporation"), + 720 => Some("O.N. Electronic Co. Ltd."), + 721 => Some("Industrial Control Communication Inc."), + 722 => Some("ABB Inc."), + 723 => Some("ElectroWave USA Inc."), + 724 => Some("Industrial Network Controls, LC"), + 725 => Some("KDT Systems Co. Ltd."), + 726 => Some("SEFA Technology Inc."), + 727 => Some("Nippon POP Rivets and Fasteners Ltd."), + 728 => Some("Yamato Scale Co. Ltd."), + 729 => Some("Zener Electric"), + 730 => Some("GSE Scale Systems"), + 731 => Some("ISAS (Integrated Switchgear & Sys. Pty Ltd.)"), + 732 => Some("Beta LaserMike Limited"), + 733 => Some("TOEI Electric Co. Ltd."), + 734 => Some("Hakko Electronics Co. Ltd"), + 736 => Some("RFID Inc."), + 737 => Some("Adwin Corporation"), + 738 => Some("Osaka Vacuum Ltd."), + 739 => Some("A-Kyung Motion Inc."), + 740 => Some("Camozzi S.P.A."), + 741 => Some("Crevis Co., Ltd."), + 742 => Some("Rice Lake Weighing Systems"), + 743 => Some("Linux Network Services"), + 744 => Some("KEB Antriebstechnik GmbH"), + 745 => Some("Hagiwara Electric Co. Ltd."), + 746 => Some("Glass Inc. International"), + 748 => Some("DVT Corporation"), + 749 => Some("Woodward Governor"), + 750 => Some("Mosaic Systems Inc."), + 751 => Some("Laserline GmbH"), + 752 => Some("COM-TEC Inc."), + 753 => Some("Weed Instrument"), + 754 => Some("Prof-face European Technology Center"), + 755 => Some("Fuji Automation Co. Ltd."), + 756 => Some("Matsutame Co. Ltd."), + 757 => Some("Hitachi Via Mechanics Ltd."), + 758 => Some("Dainippon Screen Mfg. Co. Ltd."), + 759 => Some("FLS Automation A/S"), + 760 => Some("ABB Stotz Kontakt GmbH"), + 761 => Some("Technical Marine Service"), + 762 => Some("Advanced Automation Associates Inc."), + 763 => Some("Baumer Ident GmbH"), + 764 => Some("Tsubakimoto Chain Co."), + 766 => Some("Furukawa Co. Ltd."), + 767 => Some("Active Power"), + 768 => Some("CSIRO Mining Automation"), + 769 => Some("Matrix Integrated Systems"), + 770 => Some("Digitronic Automationsanlagen GmbH"), + 771 => Some("SICK STEGMANN Inc."), + 772 => Some("TAE-Antriebstechnik GmbH"), + 773 => Some("Electronic Solutions"), + 774 => Some("Rocon L.L.C."), + 775 => Some("Dijitized Communications Inc."), + 776 => Some("Asahi Organic Chemicals Industry Co. Ltd."), + 777 => Some("Hodensha"), + 778 => Some("Harting Inc. NA"), + 779 => Some("Kubler GmbH"), + 780 => Some("Yamatake Corporation"), + 781 => Some("JEOL"), + 782 => Some("Yamatake Industrial Systems Co.Ltd."), + 783 => Some("HAEHNE Elektronische Messgerate GmbH"), + 784 => Some("Ci Technologies Pty Ltd (for Pelamos Industries)"), + 785 => Some("N. SCHLUMBERGER & CIE"), + 786 => Some("Teijin Seiki Co. Ltd."), + 787 => Some("DAIKIN Industries Ltd."), + 788 => Some("RyuSyo Industrial Co. Ltd."), + 789 => Some("Saginomiya Seisakusho, Inc."), + 790 => Some("Seishin Engineering Co. Ltd."), + 791 => Some("Japan Support System Ltd."), + 792 => Some("Decsys"), + 793 => Some("Metronix Messgerate u. Elektronik GmbH"), + 794 => Some("ROPEX Industrie - Elektronik GmbH"), + 795 => Some("Vaccon Company Inc."), + 796 => Some("Siemens Energy & Automation Inc."), + 797 => Some("Ten X Technology Inc."), + 798 => Some("Tyco Electronics"), + 799 => Some("Delta Power Electronics Center"), + 800 => Some("Denker"), + 801 => Some("Autonics Corporation"), + 802 => Some("JFE Electronic Engineering Pty. Ltd."), + 804 => Some("Electro-Sensors Inc."), + 805 => Some("Digi International Inc."), + 806 => Some("Texas Instruments"), + 807 => Some("ADTEC Plasma Technology Co. Ltd."), + 808 => Some("SICK AG"), + 809 => Some("Ethernet Peripherals Inc."), + 810 => Some("Animatics Corporation"), + 812 => Some("Process Control Corporation"), + 813 => Some("SystemV. Inc."), + 814 => Some("Danaher Motion SRL"), + 815 => Some("SHINKAWA Sensor Technology Inc."), + 816 => Some("Tesch GmbH & Co. KG"), + 818 => Some("Trend Controls Systems Ltd."), + 819 => Some("Guangzhou ZHIYUAN Electronic Co. Ltd."), + 820 => Some("Mykrolis Corporation"), + 821 => Some("Bethlehem Steel Corporation"), + 822 => Some("KK ICP"), + 823 => Some("Takemoto Denki Corporation"), + 824 => Some("The Montalvo Corporation"), + 826 => Some("LEONI Special Cables GmbH"), + 828 => Some("ONO SOKKI CO., LTD."), + 829 => Some("Rockwell Samsung Automation"), + 830 => Some("SHINDENGEN ELECTRIC MFG. CO. LTD"), + 831 => Some("Origin Electric Co. Ltd."), + 832 => Some("Quest Technical Solutions Inc."), + 833 => Some("LS CableLtd."), + 834 => Some("Enercon-Nord Electronic GmbH"), + 835 => Some("Northwire Inc."), + 836 => Some("Engel Elektroantriebe GmbH"), + 837 => Some("The Stanley Works"), + 838 => Some("Celesco Transducer Products Inc."), + 839 => Some("Chugoku Electric Wire and Cable Co."), + 840 => Some("Kongsberg Simrad AS"), + 841 => Some("Panduit Corporation"), + 842 => Some("Spellman High Voltage Electronics Corp."), + 843 => Some("Kokusai Electric Alpha Co. Ltd."), + 844 => Some("Brooks Automation Inc."), + 845 => Some("ANYWIRE CORPORATION"), + 846 => Some("Honda Electronics Co. Ltd"), + 847 => Some("REO Elektronik AG"), + 848 => Some("Fusion UV Systems Inc."), + 849 => Some("ASI Advanced Semiconductor Instruments GmbH"), + 850 => Some("Datalogic Inc."), + 851 => Some("SoftPLC Corporation"), + 852 => Some("Dynisco Instruments LLC"), + 853 => Some("WEG Industrias SA"), + 854 => Some("Frontline Test Equipment Inc."), + 855 => Some("Tamagawa Seiki Co. Ltd."), + 856 => Some("Multi Computing Co. Ltd."), + 857 => Some("RVSI"), + 858 => Some("Commercial Timesharing Inc."), + 859 => Some("Tennessee Rand Automation LLC"), + 860 => Some("Wacogiken Co. Ltd"), + 861 => Some("Reflex Integration Inc."), + 862 => Some("Siemens AG, A&D PI Flow Instruments"), + 863 => Some("G. Bachmann Electronic GmbH"), + 864 => Some("NT International"), + 865 => Some("Schweitzer Engineering Laboratories"), + 866 => Some("ATR Industrie-Elektronik GmbH Co."), + 867 => Some("PLASMATECH Co. Ltd."), + 869 => Some("GEMU GmbH & Co. KG"), + 870 => Some("Alcorn McBride Inc."), + 871 => Some("Mori Seiki Co., Ltd."), + 872 => Some("NodeTech Systems Ltd."), + 873 => Some("Emhart Teknologies"), + 874 => Some("Cervis Inc."), + 875 => Some("FieldServer Technologies (Div Sierra Monitor Corp)"), + 876 => Some("NEDAP Power Supplies"), + 877 => Some("Nippon Sanso Corporation"), + 878 => Some("Mitomi Giken Co. Ltd."), + 879 => Some("PULS GmbH"), + 881 => Some("Japan Control Engineering Ltd."), + 882 => Some("Embedded Systems Korea (Former Zues Emtek Co Ltd.)"), + 883 => Some("Automa SRL"), + 884 => Some("Harms+Wende GmbH & Co KG"), + 885 => Some("SAE-STAHL GmbH"), + 886 => Some("Microwave Data Systems"), + 887 => Some("Bernecker + Rainer Industrie-Elektronik GmbH"), + 888 => Some("Hiprom Technologies"), + 890 => Some("Nitta Corporation"), + 891 => Some("Kontron Modular Computers GmbH"), + 892 => Some("Marlin Controls"), + 893 => Some("ELCIS s.r.l."), + 894 => Some("Acromag Inc."), + 895 => Some("Avery Weigh-Tronix"), + 899 => Some("Practicon Ltd"), + 900 => Some("Schunk GmbH & Co. KG"), + 901 => Some("MYNAH Technologies"), + 902 => Some("Defontaine Groupe"), + 903 => Some("Emerson Process Management Power & Water Solutions"), + 904 => Some("F.A. Elec"), + 905 => Some("Hottinger Baldwin Messtechnik GmbH"), + 906 => Some("Coreco Imaging Inc."), + 907 => Some("London Electronics Ltd."), + 908 => Some("HSD SpA"), + 909 => Some("Comtrol Corporation"), + 910 => Some("TEAM => Some( S.A. (Tecnica Electronica de Automatismo Y Medida)"), + 911 => Some("MAN B&W Diesel Ltd. Regulateurs Europa"), + 914 => Some("Micro Motion Inc."), + 915 => Some("Eckelmann AG"), + 916 => Some("Hanyoung Nux"), + 917 => Some("Ransburg Industrial Finishing KK"), + 918 => Some("Kun Hung Electric Co. Ltd."), + 919 => Some("Brimos wegbebakening b.v."), + 920 => Some("Nitto Seiki Co. Ltd."), + 921 => Some("PPT Vision Inc."), + 922 => Some("Yamazaki Machinery Works"), + 923 => Some("SCHMIDT Technology GmbH"), + 924 => Some("Parker Hannifin SpA (SBC Division)"), + 925 => Some("HIMA Paul Hildebrandt GmbH"), + 926 => Some("RivaTek Inc."), + 927 => Some("Misumi Corporation"), + 928 => Some("GE Multilin"), + 929 => Some("Measurement Computing Corporation"), + 930 => Some("Jetter AG"), + 931 => Some("Tokyo Electronics Systems Corporation"), + 932 => Some("Togami Electric Mfg. Co. Ltd."), + 933 => Some("HK Systems"), + 934 => Some("CDA Systems Ltd."), + 935 => Some("Aerotech Inc."), + 936 => Some("JVL Industrie Elektronik A/S"), + 937 => Some("NovaTech Process Solutions LLC"), + 939 => Some("Cisco Systems"), + 940 => Some("Grid Connect"), + 941 => Some("ITW Automotive Finishing"), + 942 => Some("HanYang System"), + 943 => Some("ABB K.K. Technical Center"), + 944 => Some("Taiyo Electric Wire & Cable Co.Ltd."), + 946 => Some("SEREN IPS INC"), + 947 => Some("Belden CDT Electronics Division"), + 948 => Some("ControlNet International"), + 949 => Some("Gefran S.P.A."), + 950 => Some("Jokab Safety AB"), + 951 => Some("SUMITA OPTICAL GLASS => Some( INC."), + 952 => Some("Biffi Italia srl"), + 953 => Some("Beck IPC GmbH"), + 954 => Some("Copley Controls Corporation"), + 955 => Some("Fagor Automation S. Coop."), + 956 => Some("DARCOM"), + 957 => Some("Frick Controls (div. of York International)"), + 958 => Some("SymCom Inc."), + 959 => Some("Infranor"), + 960 => Some("Kyosan CableLtd."), + 961 => Some("Varian Vacuum Technologies"), + 962 => Some("Messung Systems"), + 963 => Some("Xantrex Technology Inc."), + 964 => Some("StarThis Inc."), + 965 => Some("Chiyoda Co.Ltd."), + 966 => Some("Flowserve Corporation"), + 967 => Some("Spyder Controls Corp."), + 968 => Some("IBA AG"), + 969 => Some("SHIMOHIRA ELECTRIC MFG.CO.,LTD"), + 971 => Some("Siemens L&A"), + 972 => Some("Micro Innovations AG"), + 973 => Some("Switchgear & Instrumentation"), + 974 => Some("PRE-TECH CO. => Some( LTD."), + 975 => Some("National Semiconductor"), + 976 => Some("Invensys Process Systems"), + 977 => Some("Ametek HDR Power Systems"), + 979 => Some("TETRA-K Corporation"), + 980 => Some("C & M Corporation"), + 981 => Some("Siempelkamp Maschinen"), + 983 => Some("Daifuku America Corporation"), + 984 => Some("Electro-Matic Products Inc."), + 985 => Some("BUSSAN MICROELECTRONICS CORP."), + 986 => Some("ELAU AG"), + 987 => Some("Hetronic USA"), + 988 => Some("NIIGATA POWER SYSTEMS Co.Ltd."), + 989 => Some("Software Horizons Inc."), + 990 => Some("B3 Systems Inc."), + 991 => Some("Moxa Networking Co.Ltd."), + 993 => Some("S4 Integration"), + 994 => Some("Elettro Stemi S.R.L."), + 995 => Some("AquaSensors"), + 996 => Some("Ifak System GmbH"), + 997 => Some("SANKEI MANUFACTURING Co.,LTD."), + 998 => Some("Emerson Network Power Co.Ltd."), + 999 => Some("Fairmount Automation Inc."), + 1000 => Some("Bird Electronic Corporation"), + 1001 => Some("Nabtesco Corporation"), + 1002 => Some("AGM Electronics Inc."), + 1003 => Some("ARCX Inc."), + 1004 => Some("DELTA I/O Co."), + 1005 => Some("Chun IL Electric Ind. Co."), + 1006 => Some("N-Tron"), + 1007 => Some("Nippon Pneumatics/Fludics System CO., LTD."), + 1008 => Some("DDK Ltd."), + 1009 => Some("Seiko Epson Corporation"), + 1010 => Some("Halstrup-Walcher GmbH"), + 1011 => Some("ITT"), + 1012 => Some("Ground Fault Systems bv"), + 1013 => Some("Scolari Engineering S.p.A."), + 1014 => Some("Vialis Traffic bv"), + 1015 => Some("Weidmueller Interface GmbH & Co. KG"), + 1016 => Some("Shanghai Sibotech Automation Co. Ltd."), + 1017 => Some("AEG Power Supply Systems GmbH"), + 1018 => Some("Komatsu Electronics Inc."), + 1019 => Some("Souriau"), + 1020 => Some("Baumuller Chicago Corp."), + 1021 => Some("J. Schmalz GmbH"), + 1022 => Some("SEN Corporation"), + 1023 => Some("Korenix Technology Co. Ltd"), + 1024 => Some("Cooper Power Tools"), + 1025 => Some("INNOBIS"), + 1026 => Some("Shinho System"), + 1027 => Some("Xm Services Ltd."), + 1028 => Some("KVC Co.Ltd."), + 1029 => Some("Sanyu Seiki Co.Ltd."), + 1030 => Some("TuxPLC"), + 1031 => Some("Northern Network Solutions"), + 1032 => Some("Converteam GmbH"), + 1033 => Some("Symbol Technologies"), + 1034 => Some("S-TEAM Lab"), + 1035 => Some("Maguire Products Inc."), + 1036 => Some("AC&T"), + 1037 => Some("MITSUBISHI HEAVY INDUSTRIES, LTD. KOBE SHIPYARD & MACHINERY WORKS"), + 1038 => Some("Hurletron Inc."), + 1039 => Some("Chunichi Denshi Co.Ltd"), + 1040 => Some("Cardinal Scale Mfg. Co."), + 1041 => Some("BTR NETCOM via RIA Connect Inc."), + 1042 => Some("Base2"), + 1043 => Some("ASRC Aerospace"), + 1044 => Some("Beijing Stone Automation"), + 1045 => Some("Changshu Switchgear Manufacture Ltd."), + 1046 => Some("METRONIX Corp."), + 1047 => Some("WIT"), + 1048 => Some("ORMEC Systems Corp."), + 1049 => Some("ASATech (China) Inc."), + 1050 => Some("Controlled Systems Limited"), + 1051 => Some("Mitsubishi Heavy Ind. Digital System Co.Ltd. (M.H.I.)"), + 1052 => Some("Electrogrip"), + 1053 => Some("TDS Automation"), + 1054 => Some("T&C Power Conversion Inc."), + 1055 => Some("Robostar Co.Ltd"), + 1056 => Some("Scancon A/S"), + 1057 => Some("Haas Automation Inc."), + 1058 => Some("Eshed Technology"), + 1059 => Some("Delta Electronic Inc."), + 1060 => Some("Innovasic Semiconductor"), + 1061 => Some("SoftDEL Systems Limited"), + 1062 => Some("FiberFin Inc."), + 1063 => Some("Nicollet Technologies Corp."), + 1064 => Some("B.F. Systems"), + 1065 => Some("Empire Wire and Supply LLC"), + 1066 => Some("ENDO KOGYO CO., LTD"), + 1067 => Some("Elmo Motion Control LTD"), + 1069 => Some("Asahi Keiki Co. Ltd."), + 1070 => Some("Joy Mining Machinery"), + 1071 => Some("MPM Engineering Ltd."), + 1072 => Some("Wolke Inks & Printers GmbH"), + 1073 => Some("Mitsubishi Electric Engineering Co. Ltd."), + 1074 => Some("COMET AG"), + 1075 => Some("Real Time Objects & Systems, LLC"), + 1076 => Some("MISCO Refractometer"), + 1077 => Some("JT Engineering Inc."), + 1078 => Some("Automated Packing Systems"), + 1079 => Some("Niobrara R&D Corp."), + 1080 => Some("Garmin Ltd."), + 1081 => Some("Japan Mobile Platform Co. Ltd"), + 1082 => Some("Advosol Inc."), + 1083 => Some("ABB Global Services Limited"), + 1084 => Some("Sciemetric Instruments Inc."), + 1085 => Some("Tata Elxsi Ltd."), + 1086 => Some("TPC Mechatronics => Some( Co. Ltd."), + 1087 => Some("Cooper Bussmann"), + 1088 => Some("Trinite Automatisering B.V."), + 1089 => Some("Peek Traffic B.V."), + 1090 => Some("Acrison Inc."), + 1091 => Some("Applied Robotics Inc."), + 1092 => Some("FireBus Systems Inc."), + 1093 => Some("Beijing Sevenstar Huachuang Electronics"), + 1094 => Some("Magnetek"), + 1095 => Some("Microscan"), + 1096 => Some("Air Water Inc."), + 1097 => Some("Sensopart Industriesensorik GmbH"), + 1098 => Some("Tiefenbach Control Systems GmbH"), + 1099 => Some("INOXPA S.A"), + 1100 => Some("Zurich University of Applied Sciences"), + 1101 => Some("Ethernet Direct"), + 1102 => Some("GSI-Micro-E Systems"), + 1103 => Some("S-Net Automation Co.Ltd."), + 1104 => Some("Power Electronics S.L."), + 1105 => Some("Renesas Technology Corp."), + 1106 => Some("NSWCCD-SSES"), + 1107 => Some("Porter Engineering Ltd."), + 1108 => Some("Meggitt Airdynamics Inc."), + 1109 => Some("Inductive Automation"), + 1110 => Some("Neural ID"), + 1111 => Some("EEPod LLC"), + 1112 => Some("Hitachi Industrial Equipment Systems Co. Ltd."), + 1113 => Some("Salem Automation"), + 1114 => Some("port GmbH"), + 1115 => Some("B & PLUS"), + 1116 => Some("Graco Inc."), + 1117 => Some("Altera Corporation"), + 1118 => Some("Technology Brewing Corporation"), + 1121 => Some("CSE Servelec"), + 1124 => Some("Fluke Networks"), + 1125 => Some("Tetra Pak Packaging Solutions SPA"), + 1126 => Some("Racine Federated Inc."), + 1127 => Some("Pureron Japan Co. Ltd."), + 1130 => Some("Brother Industries Ltd."), + 1132 => Some("Leroy Automation"), + 1134 => Some("THK CO., LTD."), + 1137 => Some("TR-Electronic GmbH"), + 1138 => Some("ASCON S.p.A."), + 1139 => Some("Toledo do Brasil Industria de Balancas Ltda."), + 1140 => Some("Bucyrus DBT Europe GmbH"), + 1141 => Some("Emerson Process Management Valve Automation"), + 1142 => Some("Alstom Transport"), + 1144 => Some("Matrox Electronic Systems"), + 1145 => Some("Littelfuse"), + 1146 => Some("PLASMART Inc."), + 1147 => Some("Miyachi Corporation"), + 1150 => Some("Promess Incorporated"), + 1151 => Some("COPA-DATA GmbH"), + 1152 => Some("Precision Engine Controls Corporation"), + 1153 => Some("Alga Automacao e controle LTDA"), + 1154 => Some("U.I. Lapp GmbH"), + 1155 => Some("ICES"), + 1156 => Some("Philips Lighting bv"), + 1157 => Some("Aseptomag AG"), + 1158 => Some("ARC Informatique"), + 1159 => Some("Hesmor GmbH"), + 1160 => Some("Kobe SteelLtd."), + 1161 => Some("FLIR Systems"), + 1162 => Some("Simcon A/S"), + 1163 => Some("COPALP"), + 1164 => Some("Zypcom Inc."), + 1165 => Some("Swagelok"), + 1166 => Some("Elspec"), + 1167 => Some("ITT Water & Wastewater AB"), + 1168 => Some("Kunbus GmbH Industrial Communication"), + 1170 => Some("Performance Controls Inc."), + 1171 => Some("ACS Motion ControlLtd."), + 1173 => Some("IStar Technology Limited"), + 1174 => Some("Alicat Scientific Inc."), + 1176 => Some("ADFweb.com SRL"), + 1177 => Some("Tata Consultancy Services Limited"), + 1178 => Some("CXR Ltd."), + 1179 => Some("Vishay Nobel AB"), + 1181 => Some("SolaHD"), + 1182 => Some("Endress+Hauser"), + 1183 => Some("Bartec GmbH"), + 1185 => Some("AccuSentry Inc."), + 1186 => Some("Exlar Corporation"), + 1187 => Some("ILS Technology"), + 1188 => Some("Control Concepts Inc."), + 1190 => Some("Procon Engineering Limited"), + 1191 => Some("Hermary Opto Electronics Inc."), + 1192 => Some("Q-Lambda"), + 1194 => Some("VAMP Ltd"), + 1195 => Some("FlexLink"), + 1196 => Some("Office FA.com Co. Ltd."), + 1197 => Some("SPMC (Changzhou) Co. Ltd."), + 1198 => Some("Anton Paar GmbH"), + 1199 => Some("Zhuzhou CSR Times Electric Co. Ltd."), + 1200 => Some("DeStaCo"), + 1201 => Some("Synrad Inc"), + 1202 => Some("Bonfiglioli Vectron GmbH"), + 1203 => Some("Pivotal Systems"), + 1204 => Some("TKSCT"), + 1205 => Some("Randy Nuernberger"), + 1206 => Some("CENTRALP"), + 1207 => Some("Tengen Group"), + 1208 => Some("OES Inc."), + 1209 => Some("Actel Corporation"), + 1210 => Some("Monaghan Engineering Inc."), + 1211 => Some("wenglor sensoric gmbh"), + 1212 => Some("HSA Systems"), + 1213 => Some("MK Precision Co.Ltd."), + 1214 => Some("Tappan Wire and Cable"), + 1215 => Some("Heinzmann GmbH & Co. KG"), + 1216 => Some("Process Automation International Ltd."), + 1217 => Some("Secure Crossing"), + 1218 => Some("SMA Railway Technology GmbH"), + 1219 => Some("FMS Force Measuring Systems AG"), + 1220 => Some("ABT Endustri Enerji Sistemleri Sanayi Tic. Ltd. Sti."), + 1221 => Some("MagneMotion Inc."), + 1222 => Some("STS Co.Ltd."), + 1223 => Some("MERAK SIC => Some( SA"), + 1224 => Some("ABOUNDI Inc."), + 1225 => Some("Rosemount Inc."), + 1226 => Some("GEA FES Inc."), + 1227 => Some("TMG Technologie und Engineering GmbH"), + 1228 => Some("embeX GmbH"), + 1229 => Some("GH Electrotermia, S.A."), + 1230 => Some("Tolomatic"), + 1231 => Some("Dukane"), + 1232 => Some("Elco (Tian Jin) Electronics Co.Ltd."), + 1233 => Some("Jacobs Automation"), + 1234 => Some("Noda Radio Frequency Technologies Co.Ltd."), + 1235 => Some("MSC Tuttlingen GmbH"), + 1236 => Some("Hitachi Cable Manchester"), + 1237 => Some("ACOREL SAS"), + 1238 => Some("Global Engineering Solutions Co.Ltd."), + 1239 => Some("ALTE Transportation, S.L."), + 1240 => Some("Penko Engineering B.V."), + 1241 => Some("Z-Tec Automation Systems Inc."), + 1242 => Some("ENTRON Controls LLC"), + 1243 => Some("Johannes Huebner Fabrik Elektrischer Maschinen GmbH"), + 1244 => Some("RF IDeas, Inc."), + 1245 => Some("Pentronic AB"), + 1246 => Some("SCA Schucker GmbH & Co. KG"), + 1247 => Some("TDK-Lambda"), + 1250 => Some("Altronic LLC"), + 1251 => Some("Siemens AG"), + 1252 => Some("Liebherr Transportation Systems GmbH & Co KG"), + 1254 => Some("SKF USA Inc."), + 1256 => Some("LMI Technologies"), + 1259 => Some("EN Technologies Inc."), + 1261 => Some("CEPHALOS Automatisierung mbH"), + 1262 => Some("Atronix Engineering, Inc."), + 1263 => Some("Monode Marking Products, Inc."), + 1265 => Some("Quabbin Wire & Cable Co., Inc."), + 1266 => Some("GPSat Systems Australia"), + 1269 => Some("Tri-Tronics Co., Inc."), + 1270 => Some("Rovema GmbH"), + 1272 => Some("IEP GmbH"), + 1277 => Some("Control Chief Corporation"), + 1280 => Some("Jacktek Systems Inc."), + 1282 => Some("PRIMES GmbH"), + 1283 => Some("Branson Ultrasonics"), + 1284 => Some("DEIF A/S"), + 1285 => Some("3S-Smart Software Solutions GmbH"), + 1287 => Some("Smarteye Corporation"), + 1288 => Some("Toshiba Machine"), + 1289 => Some("eWON"), + 1290 => Some("OFS"), + 1291 => Some("KROHNE"), + 1293 => Some("General Cable Industries, Inc."), + 1295 => Some("Kistler Instrumente AG"), + 1296 => Some("YJS Co., Ltd."), + 1301 => Some("Xylem Analytics Germany GmbH"), + 1302 => Some("Lenord, Bauer & Co. GmbH"), + 1303 => Some("Carlo Gavazzi Controls"), + 1304 => Some("Faiveley Transport"), + 1306 => Some("vMonitor"), + 1307 => Some("Kepware Technologies"), + 1308 => Some("duagon AG"), + 1310 => Some("Xylem Water Solutions"), + 1311 => Some("Automation Professionals, LLC"), + 1313 => Some("CEIA SpA"), + 1314 => Some("Marine Technologies LLC"), + 1315 => Some("Alphagate Automatisierungstechnik GmbH"), + 1316 => Some("Mecco Partners, LLC"), + 1317 => Some("LAP GmbH Laser Applikationen"), + 1318 => Some("ABB S.p.A. - SACE Division"), + 1319 => Some("ABB S.p.A. - SACE Division"), + 1322 => Some("Thermo Ramsey Inc."), + 1323 => Some("Helmholz GmbH & Co. KG"), + 1324 => Some("EUCHNER GmbH + Co. KG"), + 1325 => Some("AMK GmbH & Co. KG"), + 1326 => Some("Badger Meter"), + 1328 => Some("Fisher-Rosemount Systems, Inc."), + 1329 => Some("LJU Automatisierungstechnik GmbH"), + 1330 => Some("Fairbanks Scales, Inc."), + 1331 => Some("Imperx, Inc."), + 1332 => Some("FRONIUS International GmbH"), + 1333 => Some("Hoffman Enclosures"), + 1334 => Some("Elecsys Corporation"), + 1335 => Some("Bedrock Automation"), + 1336 => Some("RACO Manufacturing and Engineering"), + 1337 => Some("Hein Lanz Industrial Tech."), + 1338 => Some("Synopsys, Inc. (formerly Codenomicon)"), + 1341 => Some("Sensirion AG"), + 1342 => Some("SIKO GmbH"), + 1344 => Some("GRUNDFOS"), + 1346 => Some("Beijer Electronics Products AB"), + 1348 => Some("AIMCO"), + 1350 => Some("Coval Vacuum Managers"), + 1351 => Some("Powell Industries"), + 1353 => Some("IPDisplays"), + 1354 => Some("SCAIME SAS"), + 1355 => Some("Metal Work SpA"), + 1356 => Some("Telsonic AG"), + 1358 => Some("Hauch & Bach ApS"), + 1359 => Some("Pago AG"), + 1360 => Some("ULTIMATE Europe Transportation Equipment GmbH"), + 1362 => Some("Enovation Controls"), + 1363 => Some("Lake Cable LLC"), + 1367 => Some("Laird"), + 1368 => Some("Nanotec Electronic GmbH & Co. KG"), + 1369 => Some("SAMWON ACT Co., Ltd."), + 1370 => Some("Aparian Inc."), + 1371 => Some("Cosys Inc."), + 1372 => Some("Insight Automation Inc."), + 1374 => Some("FASTECH"), + 1375 => Some("K.A. Schmersal GmbH & Co. KG"), + 1377 => Some("Chromalox"), + 1378 => Some("SEIDENSHA ELECTRONICS CO., LTD"), + 1380 => Some("Don Electronics Ltd"), + 1381 => Some("burster gmbh & co kg"), + 1382 => Some("Unitronics (1989) (RG) LTD"), + 1383 => Some("OEM Technology Solutions"), + 1384 => Some("Allied Motion"), + 1385 => Some("Mitron Oy"), + 1386 => Some("Dengensha TOA"), + 1387 => Some("Systec Systemtechnik und Industrieautomation GmbH"), + 1389 => Some("Jenny Science AG"), + 1390 => Some("Baumer Optronic GmbH"), + 1391 => Some("Invertek Drives Ltd"), + 1392 => Some("High Grade Controls Corporation"), + 1394 => Some("Ishida Europe Limited"), + 1396 => Some("Actia Systems"), + 1398 => Some("Beijing Tiandi-Marco Electro-Hydraulic Control System Co., Ltd."), + 1399 => Some("Universal Robots A/S"), + 1401 => Some("Dialight"), + 1402 => Some("E-T-A Elektrotechnische Apparate GmbH"), + 1403 => Some("Kemppi Oy"), + 1404 => Some("Tianjin Geneuo Technology Co., Ltd."), + 1405 => Some("ORing Industrial Networking Corp."), + 1406 => Some("Benchmark Electronics"), + 1408 => Some("ELAP S.R.L."), + 1409 => Some("Applied Mining Technologies"), + 1410 => Some("KITZ SCT Corporation"), + 1411 => Some("VTEX Corporation"), + 1412 => Some("ESYSE GmbH Embedded Systems Engineering"), + 1413 => Some("Automation Controls"), + 1415 => Some("Cincinnati Test Systems"), + 1417 => Some("Zumbach Electronics Corp."), + 1418 => Some("Emerson Process Management"), + 1419 => Some("CCS Inc."), + 1420 => Some("Videojet, Inc."), + 1421 => Some("Zebra Technologies"), + 1422 => Some("Anritsu Infivis"), + 1423 => Some("Dimetix AG"), + 1424 => Some("General Measure (China)"), + 1425 => Some("Fortress Interlocks"), + 1427 => Some("Task Force Tips"), + 1428 => Some("SERVO-ROBOT INC."), + 1429 => Some("Flow Devices and Systems, Inc."), + 1430 => Some("nLIGHT, Inc."), + 1431 => Some("Microchip Technology Inc."), + 1432 => Some("DENT Instruments"), + 1433 => Some("CMC Industrial Electronics Ltd."), + 1434 => Some("Accutron Instruments Inc."), + 1435 => Some("Kaeser Kompressoren SE"), + 1436 => Some("Optoelectronics"), + 1437 => Some("Coherix, Inc."), + 1438 => Some("FLSmidth A/S"), + 1439 => Some("Kyland Corporation"), + 1440 => Some("Cole-Parmer Instrument Company"), + 1441 => Some("Wachendorff Automation GmbH & Co., KG"), + 1442 => Some("SMAC Moving Coil Actuators"), + 1444 => Some("PushCorp, Inc."), + 1445 => Some("Fluke Process Instruments GmbH"), + 1446 => Some("Mini Motor srl"), + 1447 => Some("I-CON Industry Tech."), + 1448 => Some("Grace Engineered Products, Inc."), + 1449 => Some("Zaxis Inc."), + 1450 => Some("Lumasense Technologies"), + 1451 => Some("Domino Printing"), + 1452 => Some("LightMachinery Inc"), + 1453 => Some("DEUTA-WERKE GmbH"), + 1454 => Some("Altus Sistemas de Automação S.A."), + 1455 => Some("Criterion NDT"), + 1456 => Some("InterTech Development Company"), + 1457 => Some("Action Labs, Incorporated"), + 1458 => Some("Perle Systems Limited"), + 1459 => Some("Utthunga Technologies Pvt Ltd."), + 1460 => Some("Dong IL Vision, Co., Ltd."), + 1461 => Some("Wipotec Wiege-und Positioniersysteme GmbH"), + 1462 => Some("Atos spa"), + 1463 => Some("Solartron Metrology LTD"), + 1464 => Some("Willowglen Systems Inc."), + 1465 => Some("Analog Devices"), + 1466 => Some("Power Electronics International, Inc."), + 1468 => Some("Campbell Wrapper Corporatio"), + 1469 => Some("Herkules-Resotec Elektronik GmbH"), + 1470 => Some("aignep spa"), + 1471 => Some("SHANGHAI CARGOA M.&E.EQUIPMENT CO. LTD"), + 1472 => Some("PMV Automation AB"), + 1473 => Some("K-Patents Oy"), + 1474 => Some("Dynatronix"), + 1475 => Some("Atop Technologies"), + 1476 => Some("Bitronics, LLC."), + 1477 => Some("Delta Tau Data Systems"), + 1478 => Some("WITZ Corporation"), + 1479 => Some("AUTOSOL"), + 1480 => Some("ADB Safegate"), + 1481 => Some("VersaBuilt, Inc"), + 1482 => Some("Visual Technologies, Inc."), + 1483 => Some("Artis GmbH"), + 1484 => Some("Reliance Electric Limited"), + 1485 => Some("Vanderlande"), + 1486 => Some("Packet Power"), + 1487 => Some("ima-tec gmbh"), + 1488 => Some("Vision Automation A/S"), + 1489 => Some("PROCENTEC BV"), + 1490 => Some("HETRONIK GmbH"), + 1491 => Some("Lanmark Controls Inc."), + 1492 => Some("profichip GmbH"), + 1493 => Some("flexlog GmbH"), + 1494 => Some("YUCHANGTECH"), + 1495 => Some("Dynapower Company"), + 1496 => Some("TAKIKAWA ENGINEERING"), + 1497 => Some("Ingersoll Rand"), + 1498 => Some("ASA-RT s.r.l"), + 1499 => Some("Trumpf Laser- und Systemtectechnik Gmbh"), + 1500 => Some("SYNTEC TECHNOLOGY CORPORATION COMPANY"), + 1501 => Some("Rinstrum"), + 1502 => Some("Symbotic LLC"), + 1503 => Some("GE Healthcare Life Sciences"), + 1504 => Some("BlueBotics SA"), + 1505 => Some("Dynapar Corporation"), + 1506 => Some("Blum-Novotest"), + 1507 => Some("CIMON"), + 1508 => Some("Dalian SeaSky Automation Co., Ltd."), + 1509 => Some("Rethink Robotics, Inc."), + 1510 => Some("Ingeteam"), + 1511 => Some("TOSEI ENGINEERING CORP."), + 1512 => Some("SAMSON AG"), + 1513 => Some("TGW Mechanics GmbH"), + _ => None, + } +} + +fn enip_devicetype_string(p: u16) -> Option<&'static str> { + match p { + 0 => Some("Generic Device (deprecated)"), + 1 => Some("Control Station (deprecated)"), + 2 => Some("AC Drive Device"), + 3 => Some("Motor Overload"), + 4 => Some("Limit Switch"), + 5 => Some("Inductive Proximity Switch"), + 6 => Some("Photoelectric Sensor"), + 7 => Some("General Purpose Discrete I/O"), + 8 => Some("Encoder (deprecated)"), + 9 => Some("Resolver"), + 10 => Some("General Purpose Analog I/O (deprecated)"), + 12 => Some("Communications Adapter"), + 13 => Some("Barcode Scanner (deprecated)"), + 14 => Some("Programmable Logic Controller"), + 16 => Some("Position Controller"), + 17 => Some("Weigh Scale (deprecated)"), + 18 => Some("Message Display (deprecated)"), + 19 => Some("DC Drive"), + 20 => Some("Servo Drives (deprecated)"), + 21 => Some("Contactor"), + 22 => Some("Motor Starter"), + 23 => Some("Softstart Starter"), + 24 => Some("Human-Machine Interface"), + 25 => Some("Pneumatic Valve(s) (deprecated)"), + 26 => Some("Mass Flow Controller"), + 27 => Some("Pneumatic Valve(s)"), + 28 => Some("Vacuum Pressure Gauge"), + 29 => Some("Process Control Value"), + 30 => Some("Residual Gas Analyzer"), + 31 => Some("DC Power Generator"), + 32 => Some("RF Power Generator"), + 33 => Some("Turbomolecular Vacuum Pump"), + 34 => Some("Encoder"), + 35 => Some("Safety Discrete I/O Device"), + 36 => Some("Fluid Flow Controller"), + 37 => Some("CIP Motion Drive"), + 38 => Some("CompoNet Repeater"), + 39 => Some("Mass Flow Controller Enhanced"), + 40 => Some("CIP Modbus Device"), + 41 => Some("CIP Modbus Translator"), + 42 => Some("Safety Analog I/O Device"), + 43 => Some("Generic Device (keyable)"), + 44 => Some("Managed Ethernet Switch"), + 45 => Some("CIP Motion Safety Drive Device"), + 46 => Some("Safety Drive Device"), + 47 => Some("CIP Motion Encoder"), + 48 => Some("CIP Motion Converter"), + 49 => Some("CIP Motion I/O"), + 50 => Some("ControlNet Physical Layer Component"), + 100 => Some("In-Sight 2000 Series"), + 200 => Some("Embedded Component"), + 150 => Some("PowerFlex 525"), + 773 => Some("DataMan Series Reader"), + _ => None, + } +} + +fn cip_service_string(p: u8) -> Option<&'static str> { + match p { + 0x01 => Some("Get Attributes All"), + 0x02 => Some("Set Attributes All"), + 0x03 => Some("Get Attribute List"), + 0x04 => Some("Set Attribute List"), + 0x05 => Some("Reset"), + 0x06 => Some("Start"), + 0x07 => Some("Stop"), + 0x08 => Some("Create"), + 0x09 => Some("Delete"), + 0x0A => Some("Multiple Service Packet"), + 0x0D => Some("Apply Attributes"), + 0x0E => Some("Get Attributes Single"), + 0x10 => Some("Set Attributes Single"), + 0x11 => Some("Find Next Object Instance"), + 0x15 => Some("Restore"), + 0x16 => Some("Save"), + 0x17 => Some("No Forward Open"), + 0x18 => Some("Get Member"), + 0x19 => Some("Set Member"), + 0x1A => Some("Insert Member"), + 0x1B => Some("Remove Member"), + 0x1C => Some("Group Sync"), + 0x1D => Some("Get Connection Point Member List"), + // these next ones are class-specific + 0x4B => Some("Execute PCCC Service"), + 0x4C => Some("Read Data"), + 0x4D => Some("Write Data"), + 0x4E => Some("Read Write Modify Data"), + 0x52 => Some("Read Data Fragmented"), + 0x53 => Some("Write Data Fragmented"), + 0x54 => Some("Forward Open"), + _ => None, + } +} + +fn cip_status_string(p: u8) -> Option<&'static str> { + match p { + 0x00 => Some("Success"), + 0x01 => Some("Connection Failure"), + 0x02 => Some("Resource Unavailable"), + 0x03 => Some("Invalid Parameter Value"), + 0x04 => Some("Path Segment Error"), + 0x05 => Some("Path Destination Unknown"), + 0x06 => Some("Partial Transfer"), + 0x07 => Some("Connection Lost"), + 0x08 => Some("Service Not Supported"), + 0x09 => Some("Invalid Attribute Value"), + 0x0A => Some("Attribute List Error"), + 0x0B => Some("Already In Requested Mode/State"), + 0x0C => Some("Object State Conflict"), + 0x0D => Some("Object Already Exists"), + 0x0E => Some("Attribute Not Settable"), + 0x0F => Some("Privilege Violation"), + 0x10 => Some("Device State Conflict"), + 0x11 => Some("Reply Data Too Large"), + 0x12 => Some("Fragmentation Of A Primitive Value"), + 0x13 => Some("Not Enough Data"), + 0x14 => Some("Attribute Not Supported"), + 0x15 => Some("Too Much Data"), + 0x16 => Some("Object Does Not Exist"), + 0x17 => Some("Service Fragmentation Sequence Not In Progress"), + 0x18 => Some("No Stored Attribute Data"), + 0x19 => Some("Store Operation Failure"), + 0x1A => Some("Routing Failure, Request Packet Too Large"), + 0x1B => Some("Routing Failure, Response Packet Too Large"), + 0x1C => Some("Missing Attribute List Entry Data"), + 0x1D => Some("Invalid Attribute Value List"), + 0x1E => Some("Embedded Service Error"), + 0x1F => Some("Vendor Specific Error"), + 0x20 => Some("Invalid Parameter"), + 0x21 => Some("Write-Once Value Or Medium Already Written"), + 0x22 => Some("Invalid Reply Received"), + 0x23 => Some("Buffer Overflow"), + 0x24 => Some("Invalid Message Format"), + 0x25 => Some("Key Failure In Path"), + 0x26 => Some("Path Size Invalid"), + 0x27 => Some("Unexpected Attribute In List"), + 0x28 => Some("Invalid Member ID"), + 0x29 => Some("Member Not Settable"), + 0x2A => Some("Group 2 Only Server General Failure"), + 0x2B => Some("Unknown Modbus Error"), + 0x2C => Some("Attribute Not Gettable"), + 0xFF => Some("Still Processing"), + _ => None, + } +} + +fn cip_status_extended_string(p: &[u8]) -> Option<&'static str> { + if p.len() == 2 { + let val = ((p[1] as u16) << 8) | (p[0] as u16); + return match val { + 0x100 => Some( "Connection in use or duplicate Forward Open"), + 0x103 => Some( "Transport class and trigger combination not supported"), + 0x106 => Some( "Ownership conflict"), + 0x107 => Some( "Target connection not found"), + 0x108 => Some( "Invalid network connection parameter"), + 0x109 => Some( "Invalid connection size"), + 0x110 => Some( "Target for connection not configured"), + 0x111 => Some( "RPI not supported"), + 0x112 => Some( "RPI value(s) not acceptable"), + 0x113 => Some( "Out of connections"), + 0x114 => Some( "Vendor ID or product code mismatch"), + 0x115 => Some( "Device type mismatch"), + 0x116 => Some( "Revision mismatch"), + 0x117 => Some( "Invalid produced or consumed application path"), + 0x118 => Some( "Invalid or inconsistent configuration application path"), + 0x119 => Some( "Non-listen only connection not opened"), + 0x11A => Some( "Target object out of connections"), + 0x11B => Some( "RPI is smaller than the production inhibit time"), + 0x11C => Some( "Transport class not supported"), + 0x11D => Some( "Production trigger not supported"), + 0x11E => Some( "Direction not supported"), + 0x11F => Some( "Invalid O->T Fixed/Variable"), + 0x120 => Some( "Invalid T->O Fixed/Variable"), + 0x121 => Some( "Invalid O->T Priority"), + 0x122 => Some( "Invalid T->O Priority"), + 0x123 => Some( "Invalid O->T connection type"), + 0x124 => Some( "Invalid T->O connection type"), + 0x125 => Some( "Invalid O->T redundant owner"), + 0x126 => Some( "Invalid configuration size"), + 0x127 => Some( "Invalid O->T size"), + 0x128 => Some( "Invalid T->O size"), + 0x129 => Some( "Invalid configuration application path"), + 0x12A => Some( "Invalid consuming application path"), + 0x12B => Some( "Invalid producing application path"), + 0x12C => Some( "Configuration symbol does not exist"), + 0x12D => Some( "Consuming symbol does not exist"), + 0x12E => Some( "Producing symbol does not exist"), + 0x12F => Some( "Inconsistent application path combination"), + 0x130 => Some( "Inconsistent consume data format"), + 0x131 => Some( "Inconsistent produce data format"), + 0x132 => Some( "NULL ForwardOpen not supported"), + 0x203 => Some( "Connection timed out"), + 0x204 => Some( "Unconnected request timed out"), + 0x205 => Some( "Parameter error in unconnected request"), + 0x206 => Some( "Message too large for UnconnectedSend"), + 0x207 => Some( "Unconnected acknowledged without reply"), + 0x301 => Some( "No buffer memory available"), + 0x302 => Some( "Network bandwidth not available for data"), + 0x303 => Some( "No consumed connection ID filter available"), + 0x304 => Some( "Not configured to send scheduled priority data"), + 0x305 => Some( "Schedule signature mismatch"), + 0x306 => Some( "Schedule signature validation not possible"), + 0x311 => Some( "Port not available"), + 0x312 => Some( "Link address not valid"), + 0x315 => Some( "Invalid segment in connection path"), + 0x316 => Some( "ForwardClose connection path mismatch"), + 0x317 => Some( "Scheduling not specified"), + 0x318 => Some( "Link address to self invalid"), + 0x319 => Some( "Secondary resources unavailable"), + 0x31A => Some( "Rack connection already established"), + 0x31B => Some( "Module connection already established"), + 0x31C => Some( "Miscellaneous"), + 0x31D => Some( "Redundant connection mismatch"), + 0x31E => Some( "No more user configurable link consumer resources available in the producing module"), + 0x31F => Some( "No more user configurable link consumer resources configured in the producing module"), + 0x800 => Some( "Network link offline"), + 0x801 => Some( "Incompatible Multicast RPI"), + 0x802 => Some( "Invalid Safety Connection Size"), + 0x803 => Some( "Invalid Safety Connection Format"), + 0x804 => Some( "Invalid Time Correction Connection Parameters"), + 0x805 => Some( "Invalid Ping Interval EPI Multiplier"), + 0x806 => Some( "Time Coordination Msg Min Multiplier"), + 0x807 => Some( "Network Time Expectation Multiplier"), + 0x808 => Some( "Timeout Multiplier"), + 0x809 => Some( "Invalid Max Consumer Number"), + 0x80A => Some( "Invalid CPCRC"), + 0x80B => Some( "Time Correction Connection ID Invalid"), + 0x80C => Some( "SCID Mismatch"), + 0x80D => Some( "TUNID not set"), + 0x80E => Some( "TUNID Mismatch"), + 0x80F => Some( "Configuration operation not allowed"), + 0x810 => Some( "No target application data available"), + 0x811 => Some( "No originator application data available"), + 0x812 => Some( "Node address has changed since the network was scheduled"), + 0x813 => Some( "Not configured for off-subnet multicast"), + 0x814 => Some( "Invalid produce/consume data format"), + _ => None, + }; + } + None +} + +fn cip_class_string(p: u32) -> Option<&'static str> { + match p { + 0x00 => Some(""), + 0x01 => Some("Identity"), + 0x02 => Some("Message Router"), + 0x03 => Some("DeviceNet"), + 0x04 => Some("Assembly"), + 0x05 => Some("Connection"), + 0x06 => Some("Connection Manager"), + 0x07 => Some("Register"), + 0x08 => Some("Discrete Input Point"), + 0x09 => Some("Discrete Output Point"), + 0x0A => Some("Analog Input Point"), + 0x0B => Some("Analog Output Point"), + 0x0E => Some("Presence Sensing"), + 0x0F => Some("Parameter"), + 0x10 => Some("Parameter Group"), + 0x12 => Some("Group"), + 0x1D => Some("Discrete Input Group"), + 0x1E => Some("Discrete Output Group"), + 0x1F => Some("Discrete Group"), + 0x20 => Some("Analog Input Group"), + 0x21 => Some("Analog Output Group"), + 0x22 => Some("Analog Group"), + 0x23 => Some("Position Sensor"), + 0x24 => Some("Position Controller Supervisor"), + 0x25 => Some("Position Controller"), + 0x26 => Some("Block Sequencer"), + 0x27 => Some("Command Block"), + 0x28 => Some("Motor Data"), + 0x29 => Some("Control Supervisor"), + 0x2A => Some("AC/DC Drive"), + 0x2B => Some("Acknowledge Handler"), + 0x2C => Some("Overload"), + 0x2D => Some("Softstart"), + 0x2E => Some("Selection"), + 0x30 => Some("S-Device Supervisor"), + 0x31 => Some("S-Analog Sensor"), + 0x32 => Some("S-Analog Actuator"), + 0x33 => Some("S-Single Stage Controller"), + 0x34 => Some("S-Gas Calibration"), + 0x35 => Some("Trip Point"), + 0x37 => Some("File"), + 0x38 => Some("S-Partial Pressure"), + 0x39 => Some("Safety Supervisor"), + 0x3A => Some("Safety Validator"), + 0x3B => Some("Safety Discrete Output Point"), + 0x3C => Some("Safety Discrete Output Group"), + 0x3D => Some("Safety Discrete Input Point"), + 0x3E => Some("Safety Discrete Input Group"), + 0x3F => Some("Safety Dual Channel Output"), + 0x40 => Some("S-Sensor Calibration"), + 0x41 => Some("Event Log"), + 0x42 => Some("Motion Device Axis"), + 0x43 => Some("Time Sync"), + 0x44 => Some("Modbus"), + 0x45 => Some("Originator Connection List"), + 0x46 => Some("Modbus Serial Link"), + 0x47 => Some("Device Level Ring (DLR)"), + 0x48 => Some("QoS"), + 0x49 => Some("Safety Analog Input Point"), + 0x4A => Some("Safety Analog Input Group"), + 0x4B => Some("Safety Dual Channel Analog Input"), + 0x4C => Some("SERCOS III Link"), + 0x4D => Some("Target Connection List"), + 0x4E => Some("Base Energy"), + 0x4F => Some("Electrical Energy"), + 0x50 => Some("Non-Electrical Energy"), + 0x51 => Some("Base Switch"), + 0x52 => Some("SNMP"), + 0x53 => Some("Power Management"), + 0x54 => Some("RSTP Bridge"), + 0x55 => Some("RSTP Port"), + 0x56 => Some("PRP/HSR Protocol"), + 0x57 => Some("PRP/HSR Nodes Table"), + 0x58 => Some("Safety Feedback"), + 0x59 => Some("Safety Dual Channel Feedback"), + 0x5A => Some("Safety Stop Functions"), + 0x5B => Some("Safety Limit Functions"), + 0x5C => Some("Power Curtailment"), + 0x5D => Some("CIP Security"), + 0x5E => Some("EtherNet/IP Security"), + 0x5F => Some("Certificate Management"), + 0x67 => Some("PCCC Class"), + 0xF0 => Some("ControlNet"), + 0xF1 => Some("ControlNet Keeper"), + 0xF2 => Some("ControlNet Scheduling"), + 0xF3 => Some("Connection Configuration"), + 0xF4 => Some("Port"), + 0xF5 => Some("TCP/IP Interface"), + 0xF6 => Some("Ethernet Link"), + 0xF7 => Some("CompoNet"), + 0xF8 => Some("CompoNet Repeater"), + _ => None, + } +} + +fn log_cip_path_segment(c: &EnipCipPathSegment) -> Result { + let mut js = JsonBuilder::try_new_object()?; + match cip_segment_type_string(c.segment_type) { + Some(val) => { + js.set_string("segment_type", val)?; + } + None => { + js.set_string("segment_type", &format!("unknown-{}", c.segment_type))?; + } + } + js.set_uint("value", c.value.into())?; + js.close()?; + Ok(js) +} + +fn log_cip(c: &EnipCIP, js: &mut JsonBuilder) -> Result<(), JsonError> { + for item in c.items.iter() { + if let EnipItemPayload::Data(d) = &item.payload { + js.open_object("cip")?; + log_cip_data(&d.cip, js)?; + js.close()?; + return Ok(()); + } + } + return Ok(()); +} + +fn log_cip_data(d: &CipData, js: &mut JsonBuilder) -> Result<(), JsonError> { + match cip_service_string(d.service) { + Some(val) => { + js.set_string("service", val)?; + } + None => { + js.set_string("service", &format!("unknown-{}", d.service))?; + } + } + match &d.cipdir { + CipDir::Response(resp) => { + match cip_status_string(resp.status) { + Some(val) => { + js.set_string("status", val)?; + } + None => { + js.set_string("status", &format!("unknown-{}", resp.status))?; + } + } + if !resp.status_extended.is_empty() { + js.set_hex("status_extended", &resp.status_extended)?; + if let Some(val) = cip_status_extended_string(&resp.status_extended) { + js.set_string("status_extended_meaning", val)?; + } + } + if let EnipCipResponsePayload::Multiple(m) = &resp.payload { + if !m.packet_list.is_empty() { + js.open_array("multiple")?; + for p in m.packet_list.iter() { + let mut js2 = JsonBuilder::try_new_object()?; + // parsing has already limited recursion + log_cip_data(p, &mut js2)?; + js2.close()?; + js.append_object(&js2)?; + } + js.close()?; + } + } + } + CipDir::Request(req) => { + if !req.path.is_empty() { + js.open_array("path")?; + for seg in req.path.iter() { + js.append_object(&log_cip_path_segment(seg)?)?; + } + js.close()?; + for i in 0..req.path.len() { + //take last class + let idx = req.path.len() - 1 - i; + if (req.path[idx].segment_type & 0xFC) == 0x20 { + match cip_class_string(req.path[idx].value) { + Some(val) => { + js.set_string("class_name", val)?; + } + None => { + js.set_string( + "class_name", + &format!("unknown-{}", req.path[idx].value), + )?; + } + } + break; + } + } + } + if let EnipCipRequestPayload::Multiple(m) = &req.payload { + if !m.packet_list.is_empty() { + js.open_array("multiple")?; + for p in m.packet_list.iter() { + let mut js2 = JsonBuilder::try_new_object()?; + // parsing has already limited recursion + log_cip_data(p, &mut js2)?; + js2.close()?; + js.append_object(&js2)?; + } + js.close()?; + } + } + } + CipDir::None => {} + } + Ok(()) +} + +fn log_enip(tx: &EnipTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> { + js.open_object("enip")?; + if let Some(ref request) = tx.request { + js.open_object("request")?; + log_enip_header(&request.header, js)?; + match &request.payload { + EnipPayload::Cip(cip) => { + log_cip(cip, js)?; + } + EnipPayload::RegisterSession(rs) => { + js.open_object("register_session")?; + js.set_uint("protocol_version", rs.protocol_version.into())?; + js.set_uint("options", rs.options.into())?; + js.close()?; + } + _ => {} + } + js.close()?; + } + if let Some(ref response) = tx.response { + js.open_object("response")?; + log_enip_header(&response.header, js)?; + match &response.payload { + EnipPayload::RegisterSession(rs) => { + js.open_object("register_session")?; + js.set_uint("protocol_version", rs.protocol_version.into())?; + js.set_uint("options", rs.options.into())?; + js.close()?; + } + EnipPayload::Cip(cip) => { + log_cip(cip, js)?; + } + EnipPayload::ListServices(lsp) if !lsp.is_empty() => { + if let EnipItemPayload::Services(ls) = &lsp[0].payload { + js.open_object("list_services")?; + js.set_uint("protocol_version", ls.protocol_version.into())?; + js.set_uint("capabilities", ls.capabilities.into())?; + js.set_string("service_name", &String::from_utf8_lossy(&ls.service_name))?; + js.close()?; + } + } + EnipPayload::ListIdentity(lip) if !lip.is_empty() => { + if let EnipItemPayload::Identity(li) = &lip[0].payload { + js.open_object("identity")?; + js.set_uint("protocol_version", li.protocol_version.into())?; + js.set_string( + "revision", + &format!("{}.{}", li.revision_major, li.revision_minor), + )?; + match enip_vendorid_string(li.vendor_id) { + Some(val) => { + js.set_string("vendor_id", val)?; + } + None => { + js.set_string("vendor_id", &format!("unknown-{}", li.vendor_id))?; + } + } + match enip_devicetype_string(li.device_type) { + Some(val) => { + js.set_string("device_type", val)?; + } + None => { + js.set_string("device_type", &format!("unknown-{}", li.device_type))?; + } + } + js.set_uint("product_code", li.product_code.into())?; + js.set_uint("status", li.status.into())?; + js.set_uint("serial", li.serial.into())?; + js.set_string("product_name", &String::from_utf8_lossy(&li.product_name))?; + js.set_uint("state", li.state.into())?; + js.close()?; + } + } + _ => {} + } + js.close()?; + } + js.close()?; + Ok(()) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_enip_logger_log( + tx: *mut std::os::raw::c_void, js: &mut JsonBuilder, +) -> bool { + let tx = cast_pointer!(tx, EnipTransaction); + if tx.request.is_none() && tx.response.is_none() { + return false; + } + log_enip(tx, js).is_ok() +} diff --git a/rust/src/enip/mod.rs b/rust/src/enip/mod.rs new file mode 100644 index 000000000000..35470cb29976 --- /dev/null +++ b/rust/src/enip/mod.rs @@ -0,0 +1,23 @@ +/* Copyright (C) 2023 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. + */ + +//! Application layer enip parser and logger module. + +pub mod detect; +pub mod enip; +pub mod logger; +mod parser; diff --git a/rust/src/enip/parser.rs b/rust/src/enip/parser.rs new file mode 100644 index 000000000000..717a3bb8720b --- /dev/null +++ b/rust/src/enip/parser.rs @@ -0,0 +1,691 @@ +/* Copyright (C) 2023 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. + */ + +use nom7::bytes::streaming::take; +use nom7::error::{make_error, ErrorKind}; +use nom7::multi::count; +use nom7::number::streaming::{le_u16, le_u32, le_u64, le_u8}; +use nom7::IResult; + +pub const ENIP_STATUS_SUCCESS: u32 = 0; +pub const ENIP_STATUS_INVALID_CMD: u32 = 1; +pub const ENIP_STATUS_NO_RESOURCES: u32 = 2; +pub const ENIP_STATUS_INCORRECT_DATA: u32 = 3; +pub const ENIP_STATUS_INVALID_SESSION: u32 = 0x64; +pub const ENIP_STATUS_INVALID_LENGTH: u32 = 0x65; +pub const ENIP_STATUS_UNSUPPORTED_PROT_REV: u32 = 0x69; +//Found in wireshark +pub const ENIP_STATUS_ENCAP_HEADER_ERROR: u32 = 0x6A; + +pub fn enip_status_string(v: u32) -> Option<&'static str> { + match v { + ENIP_STATUS_SUCCESS => Some("Success"), + ENIP_STATUS_INVALID_CMD => Some("InvalidCmd"), + ENIP_STATUS_NO_RESOURCES => Some("NoResources"), + ENIP_STATUS_INCORRECT_DATA => Some("IncorrectData"), + ENIP_STATUS_INVALID_SESSION => Some("InvalidSession"), + ENIP_STATUS_INVALID_LENGTH => Some("InvalidLength"), + ENIP_STATUS_UNSUPPORTED_PROT_REV => Some("UnsupportedProtRev"), + ENIP_STATUS_ENCAP_HEADER_ERROR => Some("EncapHeaderError"), + _ => None, + } +} + +pub const ENIP_CMD_NOP: u16 = 0; +pub const ENIP_CMD_LIST_SERVICES: u16 = 4; +pub const ENIP_CMD_LIST_IDENTITY: u16 = 0x63; +pub const ENIP_CMD_LIST_INTERFACES: u16 = 0x64; +pub const ENIP_CMD_REGISTER_SESSION: u16 = 0x65; +pub const ENIP_CMD_UNREGISTER_SESSION: u16 = 0x66; +pub const ENIP_CMD_SEND_RRDATA: u16 = 0x6F; +pub const ENIP_CMD_SEND_UNIT_DATA: u16 = 0x70; +pub const ENIP_CMD_INDICATE_STATUS: u16 = 0x72; +pub const ENIP_CMD_CANCEL: u16 = 0x73; + +pub fn enip_command_string(v: u16) -> Option<&'static str> { + match v { + ENIP_CMD_NOP => Some("Nop"), + ENIP_CMD_LIST_SERVICES => Some("ListServices"), + ENIP_CMD_LIST_IDENTITY => Some("ListIdentity"), + ENIP_CMD_LIST_INTERFACES => Some("ListInterfaces"), + ENIP_CMD_REGISTER_SESSION => Some("RegisterSession"), + ENIP_CMD_UNREGISTER_SESSION => Some("UnregisterSession"), + ENIP_CMD_SEND_RRDATA => Some("SendRRData"), + ENIP_CMD_SEND_UNIT_DATA => Some("SendUnitData"), + ENIP_CMD_INDICATE_STATUS => Some("IndicateStatus"), + ENIP_CMD_CANCEL => Some("Cancel"), + _ => None, + } +} + +#[derive(Clone, Debug, Default)] +pub struct EnipHeader { + pub cmd: u16, + pub pdulen: u16, + pub session: u32, + pub status: u32, + pub context: u64, + pub options: u32, +} + +pub fn parse_enip_header(i: &[u8]) -> IResult<&[u8], EnipHeader> { + let (i, cmd) = le_u16(i)?; + let (i, pdulen) = le_u16(i)?; + let (i, session) = le_u32(i)?; + let (i, status) = le_u32(i)?; + let (i, context) = le_u64(i)?; + let (i, options) = le_u32(i)?; + Ok(( + i, + EnipHeader { + cmd, + pdulen, + session, + status, + context, + options, + }, + )) +} + +pub fn parse_enip_list_interfaces(i: &[u8]) -> IResult<&[u8], Vec> { + let (i, nb) = le_u16(i)?; + let (i, r) = count(le_u16, nb.into())(i)?; + Ok((i, r)) +} + +#[derive(Clone, Debug, Default)] +pub enum EnipPayload { + #[default] + Unparsed, + Cip(EnipCIP), + ListIdentity(Vec), + ListServices(Vec), + ListInterfaces(Vec), + RegisterSession(EnipRegisterSession), +} + +#[derive(Clone, Debug, Default)] +pub struct EnipRegisterSession { + pub protocol_version: u16, + pub options: u16, +} + +#[derive(Clone, Debug, Default)] +pub struct EnipCipPathSegment { + pub segment_type: u8, + pub value: u32, +} + +#[derive(Clone, Debug, Default)] +pub struct EnipCipRequestGetAttributeList { + pub attr_list: Vec, +} + +#[derive(Clone, Debug, Default)] +pub struct EnipCipRequestSetAttributeList { + pub first_attr: Option, +} + +#[derive(Clone, Debug, Default)] +pub struct EnipCipReqRespMultipleService { + pub offset_from_cip: usize, + pub offset_list: Vec, + pub packet_list: Vec, + pub size_list: Vec, +} + +#[derive(Clone, Debug, Default)] +pub enum EnipCipRequestPayload { + #[default] + Unhandled, + GetAttributeList(EnipCipRequestGetAttributeList), + SetAttributeList(EnipCipRequestSetAttributeList), + Multiple(EnipCipReqRespMultipleService), +} + +#[derive(Clone, Debug, Default)] +pub struct EnipCipRequest { + pub path: Vec, + pub payload: EnipCipRequestPayload, +} + +#[derive(Clone, Debug, Default)] +pub enum EnipCipResponsePayload { + #[default] + Unhandled, + Multiple(EnipCipReqRespMultipleService), +} + +#[derive(Clone, Debug, Default)] +pub struct EnipCipResponse { + pub status: u8, + pub status_extended: Vec, + pub payload: EnipCipResponsePayload, +} + +#[derive(Clone, Debug, Default)] +pub enum CipDir { + #[default] + None, + Request(EnipCipRequest), + Response(EnipCipResponse), +} + +#[derive(Clone, Debug, Default)] +pub struct CipData { + pub service: u8, + pub cipdir: CipDir, +} + +pub fn cip_segment_type_string(p: u8) -> Option<&'static str> { + match p >> 2 { + 8 => Some("class"), + 9 => Some("instance"), + 12 => Some("attribute"), + _ => None, + } +} + +pub fn parse_cip_path_segment(i: &[u8]) -> IResult<&[u8], EnipCipPathSegment> { + let (i, segment_type) = le_u8(i)?; + if segment_type >> 5 != 1 { + // we only handle logical segment + return Err(nom7::Err::Error(make_error(i, ErrorKind::Verify))); + } + let (i, value) = match segment_type & 3 { + 0 => { + let (i, v) = le_u8(i)?; + Ok((i, v as u32)) + } + 1 => { + let (i, _pad) = le_u8(i)?; + let (i, v) = le_u16(i)?; + Ok((i, v as u32)) + } + 2 => { + let (i, _pad) = le_u8(i)?; + le_u32(i) + } + // There may be more cases to handle + _ => Err(nom7::Err::Error(make_error(i, ErrorKind::Switch))), + }?; + return Ok(( + i, + EnipCipPathSegment { + segment_type, + value, + }, + )); +} + +pub fn parse_cip_path(i: &[u8]) -> IResult<&[u8], (Vec, usize)> { + let (i, nb) = le_u8(i)?; + let (i, data) = take(2 * (nb as usize))(i)?; + let consumed = 1 + 2 * (nb as usize); + let mut rem = data; + let mut segments = Vec::new(); + while !rem.is_empty() { + let (rem2, seg) = parse_cip_path_segment(rem)?; + segments.push(seg); + rem = rem2; + } + return Ok((i, (segments, consumed))); +} + +pub const CIP_GET_ATTR_LIST: u8 = 3; +pub const CIP_SET_ATTR_LIST: u8 = 4; +pub const CIP_MULTIPLE_SERVICE: u8 = 0xa; + +pub fn parse_cip_request_get_attr_list(i: &[u8]) -> IResult<&[u8], EnipCipRequestGetAttributeList> { + let (i, nb) = le_u16(i)?; + let (i, attr_list) = count(le_u16, nb.into())(i)?; + Ok((i, EnipCipRequestGetAttributeList { attr_list })) +} + +pub fn parse_cip_request_set_attr_list(i: &[u8]) -> IResult<&[u8], EnipCipRequestSetAttributeList> { + let (i, nb) = le_u16(i)?; + if nb > 0 { + let (i, first_attr) = le_u16(i)?; + // do not parse further because attribute data is class specific + return Ok(( + i, + EnipCipRequestSetAttributeList { + first_attr: Some(first_attr), + }, + )); + } + return Ok((i, EnipCipRequestSetAttributeList { first_attr: None })); +} + +pub fn parse_cip_reqresp_multiple( + i: &[u8], offset_from_cip: usize, +) -> IResult<&[u8], EnipCipReqRespMultipleService> { + let start = i; + let (i, nb) = le_u16(i)?; + let (i, offset_list) = count(le_u16, nb.into())(i)?; + let mut packet_list = Vec::new(); + let mut size_list = Vec::new(); + let mut rem = i; + for j in 0..nb as usize { + if (offset_list[j] as usize) < start.len() { + let (rem2, packet) = parse_cip_multi(&start[offset_list[j] as usize..])?; + packet_list.push(packet); + size_list.push(start[offset_list[j] as usize..].len() - rem2.len()); + rem = rem2; + } + } + Ok(( + rem, + EnipCipReqRespMultipleService { + offset_from_cip, + offset_list, + packet_list, + size_list, + }, + )) +} + +pub fn parse_cip_request(i: &[u8], service: u8, multi: bool) -> IResult<&[u8], EnipCipRequest> { + let (i, (path, offset_from_cip)) = parse_cip_path(i)?; + let (i, payload) = match service { + CIP_GET_ATTR_LIST => { + let (i, ga) = parse_cip_request_get_attr_list(i)?; + Ok((i, EnipCipRequestPayload::GetAttributeList(ga))) + } + CIP_SET_ATTR_LIST => { + let (i, sa) = parse_cip_request_set_attr_list(i)?; + Ok((i, EnipCipRequestPayload::SetAttributeList(sa))) + } + CIP_MULTIPLE_SERVICE if multi => { + // adding one byte for the cip service + let (i, m) = parse_cip_reqresp_multiple(i, offset_from_cip + 1)?; + Ok((i, EnipCipRequestPayload::Multiple(m))) + } + _ => Ok((i, EnipCipRequestPayload::Unhandled)), + }?; + return Ok((i, EnipCipRequest { path, payload })); +} + +pub fn parse_cip_response(i: &[u8], service: u8, multi: bool) -> IResult<&[u8], EnipCipResponse> { + let (i, _reserved) = le_u8(i)?; + let (i, status) = le_u8(i)?; + let (i, status_ext_nb) = le_u8(i)?; + let (i, status_extended) = take(status_ext_nb as usize)(i)?; + let offset_from_cip = 4 + (status_ext_nb as usize); + let (i, payload) = match service { + // CIP_GET_ATTR_LIST : need to parse attribute value variant, based on cip class + CIP_MULTIPLE_SERVICE if multi => { + let (i, m) = parse_cip_reqresp_multiple(i, offset_from_cip)?; + Ok((i, EnipCipResponsePayload::Multiple(m))) + } + _ => Ok((i, EnipCipResponsePayload::Unhandled)), + }?; + + return Ok(( + i, + EnipCipResponse { + status, + payload, + status_extended: status_extended.to_vec(), + }, + )); +} + +pub fn parse_cip_base(i: &[u8]) -> IResult<&[u8], CipData> { + parse_cip(i, true) +} + +pub fn parse_cip_multi(i: &[u8]) -> IResult<&[u8], CipData> { + // have only one level of recursion + parse_cip(i, false) +} + +pub fn parse_cip(i: &[u8], multi: bool) -> IResult<&[u8], CipData> { + let (i, service) = le_u8(i)?; + let (i, cipdir) = if service & 0x80 == 0 { + let (i, req) = parse_cip_request(i, service, multi)?; + Ok((i, CipDir::Request(req))) + } else { + let (i, resp) = parse_cip_response(i, service & 0x7F, multi)?; + Ok((i, CipDir::Response(resp))) + }?; + return Ok(( + i, + CipData { + service: service & 0x7F, + cipdir, + }, + )); +} + +#[derive(Clone, Debug, Default)] +pub struct EnipItemData { + pub seq_num: Option, + pub cip: CipData, +} + +#[derive(Clone, Debug, Default)] +pub struct EnipItemIdentity { + pub protocol_version: u16, + pub vendor_id: u16, + pub device_type: u16, + pub product_code: u16, + pub revision_major: u8, + pub revision_minor: u8, + pub status: u16, + pub serial: u32, + pub product_name: Vec, + pub state: u8, +} + +#[derive(Clone, Debug, Default)] +pub enum EnipItemPayload { + #[default] + Unparsed, + Data(EnipItemData), + Identity(EnipItemIdentity), + Services(EnipItemServices), +} + +#[derive(Clone, Debug, Default)] +pub struct EnipCipItem { + pub item_type: u16, + pub item_length: u16, + pub cip_offset: usize, + pub start: usize, + pub payload: EnipItemPayload, +} + +pub const ENIP_ITEM_TYPE_CONNECTED_DATA: u16 = 0xb1; +pub const ENIP_ITEM_TYPE_UNCONNECTED_DATA: u16 = 0xb2; +pub const ENIP_ITEM_TYPE_IDENTITY: u16 = 0xc; +pub const ENIP_ITEM_TYPE_SERVICES: u16 = 0x100; + +pub fn parse_cip_identity(i: &[u8]) -> IResult<&[u8], EnipItemIdentity> { + let (i, protocol_version) = le_u16(i)?; + let (i, _sock_addr) = take(16_usize)(i)?; + let (i, vendor_id) = le_u16(i)?; + let (i, device_type) = le_u16(i)?; + let (i, product_code) = le_u16(i)?; + let (i, revision_major) = le_u8(i)?; + let (i, revision_minor) = le_u8(i)?; + let (i, status) = le_u16(i)?; + let (i, serial) = le_u32(i)?; + let (i, prod_name_len) = le_u8(i)?; + let (i, product_name) = take(prod_name_len as usize)(i)?; + let (i, state) = le_u8(i)?; + + return Ok(( + i, + EnipItemIdentity { + protocol_version, + vendor_id, + device_type, + product_code, + revision_major, + revision_minor, + status, + serial, + product_name: product_name.to_vec(), + state, + }, + )); +} + +#[derive(Clone, Debug, Default)] +pub struct EnipItemServices { + pub protocol_version: u16, + pub capabilities: u16, + pub service_name: Vec, +} + +pub fn parse_enip_services(i: &[u8]) -> IResult<&[u8], EnipItemServices> { + let (i, protocol_version) = le_u16(i)?; + let (i, capabilities) = le_u16(i)?; + let (i, service_name) = take(16_usize)(i)?; + return Ok(( + i, + EnipItemServices { + protocol_version, + capabilities, + service_name: service_name.to_vec(), + }, + )); +} + +pub fn parse_enip_cip_item(i: &[u8], start: usize) -> IResult<&[u8], EnipCipItem> { + let (i, item_type) = le_u16(i)?; + let (i, item_length) = le_u16(i)?; + let mut cip_offset = start + 4; + let (i, data) = take(item_length as usize)(i)?; + let (_, payload) = match item_type { + ENIP_ITEM_TYPE_IDENTITY => { + let (_, li) = parse_cip_identity(data)?; + Ok((data, EnipItemPayload::Identity(li))) + } + ENIP_ITEM_TYPE_SERVICES => { + let (_, ls) = parse_enip_services(data)?; + Ok((data, EnipItemPayload::Services(ls))) + } + ENIP_ITEM_TYPE_CONNECTED_DATA => { + let (data, seq_num) = le_u16(data)?; + cip_offset += 2; + let (_, cip) = parse_cip_base(data)?; + Ok(( + data, + EnipItemPayload::Data(EnipItemData { + seq_num: Some(seq_num), + cip, + }), + )) + } + ENIP_ITEM_TYPE_UNCONNECTED_DATA => { + let (_, cip) = parse_cip_base(data)?; + Ok(( + data, + EnipItemPayload::Data(EnipItemData { seq_num: None, cip }), + )) + } + _ => Ok((data, EnipItemPayload::Unparsed)), + }?; + Ok(( + i, + EnipCipItem { + item_type, + item_length, + cip_offset, + start, + payload, + }, + )) +} + +#[derive(Clone, Debug, Default)] +pub struct EnipCIP { + pub handle: u32, + pub timeout: u16, + pub items: Vec, +} + +pub fn parse_enip_cip_items(i: &[u8]) -> IResult<&[u8], Vec> { + let (i, nb) = le_u16(i)?; + let mut start = 26; // ENIP_HEADER_LEN + fields parsed + let mut items = Vec::new(); + let mut rem = i; + for _j in 0..nb { + let (rem2, item) = parse_enip_cip_item(rem, start)?; + items.push(item); + start += rem.len() - rem2.len(); + rem = rem2; + } + Ok((i, items)) +} + +pub fn parse_enip_cip(i: &[u8]) -> IResult<&[u8], EnipCIP> { + let (i, handle) = le_u32(i)?; + let (i, timeout) = le_u16(i)?; + let (i, nb) = le_u16(i)?; + let mut start = 32; // ENIP_HEADER_LEN + fields parsed + let mut items = Vec::new(); + let mut rem = i; + for _j in 0..nb { + let (rem2, item) = parse_enip_cip_item(rem, start)?; + items.push(item); + start += rem.len() - rem2.len(); + rem = rem2; + } + Ok(( + i, + EnipCIP { + handle, + timeout, + items, + }, + )) +} + +pub fn parse_enip_register_session(i: &[u8]) -> IResult<&[u8], EnipRegisterSession> { + let (i, protocol_version) = le_u16(i)?; + let (i, options) = le_u16(i)?; + Ok(( + i, + EnipRegisterSession { + protocol_version, + options, + }, + )) +} + +#[derive(Clone, Debug, Default)] +pub struct EnipPdu { + pub header: EnipHeader, + pub payload: EnipPayload, + pub invalid: bool, +} + +pub fn parse_enip_pdu(i: &[u8]) -> IResult<&[u8], EnipPdu> { + let (i, header) = parse_enip_header(i)?; + let (i, data) = take(header.pdulen as usize)(i)?; + let mut invalid = false; + match header.cmd { + ENIP_CMD_REGISTER_SESSION => { + if let Ok((_, rs)) = parse_enip_register_session(data) { + return Ok(( + i, + EnipPdu { + header, + payload: EnipPayload::RegisterSession(rs), + invalid, + }, + )); + } else { + //used to set event + invalid = true; + } + } + ENIP_CMD_LIST_SERVICES if header.pdulen > 0 => { + // request is empty, response has data + if let Ok((_, li)) = parse_enip_cip_items(data) { + return Ok(( + i, + EnipPdu { + header, + payload: EnipPayload::ListServices(li), + invalid, + }, + )); + } else { + invalid = true; + } + } + ENIP_CMD_LIST_INTERFACES if header.pdulen > 0 => { + // request is empty, response has data + if let Ok((_, li)) = parse_enip_cip_items(data) { + return Ok(( + i, + EnipPdu { + header, + payload: EnipPayload::ListInterfaces(li), + invalid, + }, + )); + } else { + invalid = true; + } + } + ENIP_CMD_LIST_IDENTITY if header.pdulen > 0 => { + // request is empty, response has data + if let Ok((_, li)) = parse_enip_cip_items(data) { + return Ok(( + i, + EnipPdu { + header, + payload: EnipPayload::ListIdentity(li), + invalid, + }, + )); + } else { + invalid = true; + } + } + ENIP_CMD_SEND_RRDATA | ENIP_CMD_SEND_UNIT_DATA => { + if let Ok((_, cip)) = parse_enip_cip(data) { + return Ok(( + i, + EnipPdu { + header, + payload: EnipPayload::Cip(cip), + invalid, + }, + )); + } else { + //used to set event + invalid = true; + } + } + _ => {} + } + Ok(( + i, + EnipPdu { + header, + payload: EnipPayload::Unparsed, + invalid, + }, + )) +} + +pub fn enip_pdu_get_items(pdu: &EnipPdu) -> &[EnipCipItem] { + match &pdu.payload { + EnipPayload::ListIdentity(li) => { + return li; + } + EnipPayload::ListInterfaces(li) => { + return li; + } + EnipPayload::ListServices(ls) => { + return ls; + } + EnipPayload::Cip(cip) => { + return &cip.items; + } + _ => { + return &[]; + } + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index da2859637783..a1f860dd0a90 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -103,6 +103,7 @@ pub mod rfb; pub mod mqtt; pub mod pgsql; pub mod telnet; +pub mod enip; pub mod applayertemplate; pub mod rdp; pub mod x509; diff --git a/src/Makefile.am b/src/Makefile.am index 21e1dfe5fbeb..c22792522069 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -17,8 +17,6 @@ noinst_HEADERS = \ app-layer-detect-proto.h \ app-layer-dnp3.h \ app-layer-dnp3-objects.h \ - app-layer-enip-common.h \ - app-layer-enip.h \ app-layer-events.h \ app-layer-expectation.h \ app-layer-frames.h \ @@ -132,7 +130,6 @@ noinst_HEADERS = \ detect-engine-build.h \ detect-engine-content-inspection.h \ detect-engine-dcepayload.h \ - detect-engine-enip.h \ detect-engine-event.h \ detect-engine-file.h \ detect-engine-frame.h \ @@ -153,6 +150,24 @@ noinst_HEADERS = \ detect-engine-tag.h \ detect-engine-threshold.h \ detect-engine-uint.h \ + detect-enip-command.h \ + detect-enip-status.h \ + detect-enip-product-name.h \ + detect-enip-protocol-version.h \ + detect-enip-cip-attribute.h \ + detect-enip-cip-instance.h \ + detect-enip-cip-class.h \ + detect-enip-cip-extendedstatus.h \ + detect-enip-cip-status.h \ + detect-enip-service-name.h \ + detect-enip-capabilities.h \ + detect-enip-revision.h \ + detect-enip-identity-status.h \ + detect-enip-state.h \ + detect-enip-serial.h \ + detect-enip-product-code.h \ + detect-enip-device-type.h \ + detect-enip-vendor-id.h \ detect-fast-pattern.h \ detect-file-data.h \ detect-file-hash-common.h \ @@ -405,6 +420,7 @@ noinst_HEADERS = \ output-json-dns.h \ output-json-drop.h \ output-json-email-common.h \ + output-json-enip.h \ output-json-file.h \ output-json-flow.h \ output-json-frame.h \ @@ -635,8 +651,6 @@ libsuricata_c_a_SOURCES = \ app-layer-detect-proto.c \ app-layer-dnp3.c \ app-layer-dnp3-objects.c \ - app-layer-enip.c \ - app-layer-enip-common.c \ app-layer-events.c \ app-layer-expectation.c \ app-layer-ftp.c \ @@ -749,7 +763,6 @@ libsuricata_c_a_SOURCES = \ detect-engine.c \ detect-engine-content-inspection.c \ detect-engine-dcepayload.c \ - detect-engine-enip.c \ detect-engine-event.c \ detect-engine-file.c \ detect-engine-frame.c \ @@ -769,6 +782,24 @@ libsuricata_c_a_SOURCES = \ detect-engine-tag.c \ detect-engine-threshold.c \ detect-engine-uint.c \ + detect-enip-command.c \ + detect-enip-status.c \ + detect-enip-product-name.c \ + detect-enip-protocol-version.c \ + detect-enip-cip-attribute.c \ + detect-enip-cip-instance.c \ + detect-enip-cip-class.c \ + detect-enip-cip-extendedstatus.c \ + detect-enip-cip-status.c \ + detect-enip-service-name.c \ + detect-enip-capabilities.c \ + detect-enip-revision.c \ + detect-enip-identity-status.c \ + detect-enip-state.c \ + detect-enip-serial.c \ + detect-enip-product-code.c \ + detect-enip-device-type.c \ + detect-enip-vendor-id.c \ detect-fast-pattern.c \ detect-file-data.c \ detect-file-hash-common.c \ @@ -1019,6 +1050,7 @@ libsuricata_c_a_SOURCES = \ output-json-dns.c \ output-json-drop.c \ output-json-email-common.c \ + output-json-enip.c \ output-json-file.c \ output-json-flow.c \ output-json-frame.c \ diff --git a/src/app-layer-enip-common.c b/src/app-layer-enip-common.c deleted file mode 100644 index 0608080e21f1..000000000000 --- a/src/app-layer-enip-common.c +++ /dev/null @@ -1,958 +0,0 @@ -/* Copyright (C) 2015-2022 Open Information Security Foundation - * - * You can copy, redistribute or modify this Program under the terms of - * the GNU General Public License version 2 as published by the Free - * Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * version 2 along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ - -/** - * \file - * - * \author Kevin Wong - * - * App-layer parser for ENIP protocol common code - * - */ - -#include "suricata-common.h" -#include "util-unittest.h" -#include "util-unittest-helper.h" -#include "detect-parse.h" -#include "detect-engine.h" -#include "util-byte.h" -#include "pkt-var.h" -#include "util-profiling.h" - -#include "app-layer-enip-common.h" - -/** - * \brief Extract 8 bits and move up the offset - * @param res - * @param input - * @param offset - */ -static int ENIPExtractUint8(uint8_t *res, const uint8_t *input, uint16_t *offset, uint32_t input_len) -{ - - if (input_len < sizeof(uint8_t) || *offset > (input_len - sizeof(uint8_t))) - { - SCLogDebug("ENIPExtractUint8: Parsing beyond payload length"); - return 0; - } - - *res = *(input + *offset); - *offset += sizeof(uint8_t); - return 1; -} - -/** - * \brief Extract 16 bits and move up the offset - * @param res - * @param input - * @param offset - */ -static int ENIPExtractUint16(uint16_t *res, const uint8_t *input, uint16_t *offset, uint32_t input_len) -{ - - if (input_len < sizeof(uint16_t) || *offset > (input_len - sizeof(uint16_t))) { - SCLogDebug("ENIPExtractUint16: Parsing beyond payload length"); - return 0; - } - - if (ByteExtractUint16(res, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), - (const uint8_t *)(input + *offset)) == -1) { - return 0; - } - - *offset += sizeof(uint16_t); - return 1; -} - -/** - * \brief Extract 32 bits and move up the offset - * @param res - * @param input - * @param offset - */ -static int ENIPExtractUint32(uint32_t *res, const uint8_t *input, uint16_t *offset, uint32_t input_len) -{ - - if (input_len < sizeof(uint32_t) || *offset > (input_len - sizeof(uint32_t))) - { - SCLogDebug("ENIPExtractUint32: Parsing beyond payload length"); - return 0; - } - - if (ByteExtractUint32(res, BYTE_LITTLE_ENDIAN, sizeof(uint32_t), - (const uint8_t *)(input + *offset)) == -1) { - return 0; - } - - *offset += sizeof(uint32_t); - return 1; -} - -/** - * \brief Extract 64 bits and move up the offset - * @param res - * @param input - * @param offset - */ -static int ENIPExtractUint64(uint64_t *res, const uint8_t *input, uint16_t *offset, uint32_t input_len) -{ - - if (input_len < sizeof(uint64_t) || *offset > (input_len - sizeof(uint64_t))) - { - SCLogDebug("ENIPExtractUint64: Parsing beyond payload length"); - return 0; - } - - if (ByteExtractUint64(res, BYTE_LITTLE_ENDIAN, sizeof(uint64_t), - (const uint8_t *)(input + *offset)) == -1) { - return 0; - } - - *offset += sizeof(uint64_t); - return 1; -} - - -/** - * \brief Create service entry, add to transaction - * @param tx Transaction - * @return service entry - */ -static CIPServiceEntry *CIPServiceAlloc(ENIPTransaction *tx) -{ - - CIPServiceEntry *svc = (CIPServiceEntry *) SCCalloc(1, - sizeof(CIPServiceEntry)); - if (unlikely(svc == NULL)) - return NULL; - - TAILQ_INIT(&svc->segment_list); - TAILQ_INIT(&svc->attrib_list); - - TAILQ_INSERT_TAIL(&tx->service_list, svc, next); - tx->service_count++; - return svc; - -} - -#if 0 -/** - * \brief Delete service entry - */ - -static void CIPServiceFree(void *s) -{ - SCEnter(); - if (s) - { - CIPServiceEntry *svc = (CIPServiceEntry *) s; - - SegmentEntry *seg = NULL; - while ((seg = TAILQ_FIRST(&svc->segment_list))) - { - TAILQ_REMOVE(&svc->segment_list, seg, next); - SCFree(seg); - } - - AttributeEntry *attr = NULL; - while ((attr = TAILQ_FIRST(&svc->attrib_list))) - { - TAILQ_REMOVE(&svc->attrib_list, attr, next); - SCFree(attr); - } - - SCFree(s); - } - SCReturn; -} -#endif - -/** - * \brief Decode ENIP Encapsulation Header - * @param input, input_len data stream - * @param enip_data stores data from Packet - * @return 1 Packet ok - * @return 0 Packet has errors - */ -int DecodeENIPPDU(const uint8_t *input, uint32_t input_len, - ENIPTransaction *enip_data) -{ - int ret = 1; - - uint16_t offset = 0; //byte offset - - //Decode Encapsulation Header - uint16_t cmd; - uint16_t len; - uint32_t session; - uint32_t status; - uint64_t context; - uint32_t option; - if (ENIPExtractUint16(&cmd, input, &offset, input_len) != 1) - { - return 0; - } - if (ENIPExtractUint16(&len, input, &offset, input_len) != 1) - { - return 0; - } - if (ENIPExtractUint32(&session, input, &offset, input_len) != 1) - { - return 0; - } - if (ENIPExtractUint32(&status, input, &offset, input_len) != 1) - { - return 0; - } - if (ENIPExtractUint64(&context, input, &offset, input_len) != 1) - { - return 0; - } - if (ENIPExtractUint32(&option, input, &offset, input_len) != 1) - { - return 0; - } - - enip_data->header.command = cmd; - enip_data->header.length = len; - enip_data->header.session = session; - enip_data->header.status = status; - enip_data->header.context = context; - enip_data->header.option = option; - - switch (enip_data->header.command) - { - case NOP: - SCLogDebug("DecodeENIP - NOP"); - break; - case LIST_SERVICES: - SCLogDebug("DecodeENIP - LIST_SERVICES"); - break; - case LIST_IDENTITY: - SCLogDebug("DecodeENIP - LIST_IDENTITY"); - break; - case LIST_INTERFACES: - SCLogDebug("DecodeENIP - LIST_INTERFACES"); - break; - case REGISTER_SESSION: - SCLogDebug("DecodeENIP - REGISTER_SESSION"); - break; - case UNREGISTER_SESSION: - SCLogDebug("DecodeENIP - UNREGISTER_SESSION"); - break; - case SEND_RR_DATA: - SCLogDebug( - "DecodeENIP - SEND_RR_DATA - parse Common Packet Format"); - ret = DecodeCommonPacketFormatPDU(input, input_len, enip_data, - offset); - break; - case SEND_UNIT_DATA: - SCLogDebug( - "DecodeENIP - SEND UNIT DATA - parse Common Packet Format"); - ret = DecodeCommonPacketFormatPDU(input, input_len, enip_data, - offset); - break; - case INDICATE_STATUS: - SCLogDebug("DecodeENIP - INDICATE_STATUS"); - break; - case CANCEL: - SCLogDebug("DecodeENIP - CANCEL"); - break; - default: - SCLogDebug("DecodeENIP - UNSUPPORTED COMMAND 0x%x", - enip_data->header.command); - } - - return ret; -} - - -/** - * \brief Decode Common Packet Format - * @param input, input_len data stream - * @param enip_data stores data from Packet - * @param offset current point in the packet - * @return 1 Packet ok - * @return 0 Packet has errors - */ -int DecodeCommonPacketFormatPDU(const uint8_t *input, uint32_t input_len, - ENIPTransaction *enip_data, uint16_t offset) -{ - - if (enip_data->header.length < sizeof(ENIPEncapDataHdr)) - { - SCLogDebug("DecodeCommonPacketFormat: Malformed ENIP packet"); - return 0; - } - - uint32_t handle; - uint16_t timeout; - uint16_t count; - if (ENIPExtractUint32(&handle, input, &offset, input_len) != 1) - { - return 0; - } - if (ENIPExtractUint16(&timeout, input, &offset, input_len) != 1) - { - return 0; - } - if (ENIPExtractUint16(&count, input, &offset, input_len) != 1) - { - return 0; - } - enip_data->encap_data_header.interface_handle = handle; - enip_data->encap_data_header.timeout = timeout; - enip_data->encap_data_header.item_count = count; - - uint16_t address_type; - uint16_t address_length; //length of connection id in bytes - uint32_t address_connectionid = 0; - uint32_t address_sequence = 0; - - if (ENIPExtractUint16(&address_type, input, &offset, input_len) != 1) - { - return 0; - } - if (ENIPExtractUint16(&address_length, input, &offset, input_len) != 1) - { - return 0; - } - - //depending on addr type, get connection id, sequence if needed. Can also use addr length too? - if (address_type == CONNECTION_BASED) - { //get 4 byte connection id - if (ENIPExtractUint32(&address_connectionid, input, &offset, input_len) != 1) - { - return 0; - } - } else if (address_type == SEQUENCE_ADDR_ITEM) - { // get 4 byte connection id and 4 byte sequence - if (ENIPExtractUint32(&address_connectionid, input, &offset, input_len) != 1) - { - return 0; - } - if (ENIPExtractUint32(&address_sequence, input, &offset, input_len) != 1) - { - return 0; - } - } - - enip_data->encap_addr_item.type = address_type; - enip_data->encap_addr_item.length = address_length; - enip_data->encap_addr_item.conn_id = address_connectionid; - enip_data->encap_addr_item.sequence_num = address_sequence; - - uint16_t data_type; - uint16_t data_length; //length of data in bytes - uint16_t data_sequence_count; - - if (ENIPExtractUint16(&data_type, input, &offset, input_len) != 1) - { - return 0; - } - if (ENIPExtractUint16(&data_length, input, &offset, input_len) != 1) - { - return 0; - } - - enip_data->encap_data_item.type = data_type; - enip_data->encap_data_item.length = data_length; - - if (enip_data->encap_data_item.type == CONNECTED_DATA_ITEM) - { //connected data items have seq number - if (ENIPExtractUint16(&data_sequence_count, input, &offset, input_len) != 1) - { - return 0; - } - enip_data->encap_data_item.sequence_count = data_sequence_count; - } - - switch (enip_data->encap_data_item.type) { - case CONNECTED_DATA_ITEM: - SCLogDebug( - "DecodeCommonPacketFormat - CONNECTED DATA ITEM - parse CIP"); - DecodeCIPPDU(input, input_len, enip_data, offset); - break; - case UNCONNECTED_DATA_ITEM: - SCLogDebug("DecodeCommonPacketFormat - UNCONNECTED DATA ITEM"); - DecodeCIPPDU(input, input_len, enip_data, offset); - break; - default: - SCLogDebug("DecodeCommonPacketFormat - UNKNOWN TYPE 0x%x", - enip_data->encap_data_item.type); - return 0; - } - - return 1; -} - -/** - * \brief Decode CIP packet - * @param input, input_len data stream - * @param enip_data stores data from Packet - * @param offset current point in the packet - * @return 1 Packet ok - * @return 0 Packet has errors - */ - -int DecodeCIPPDU(const uint8_t *input, uint32_t input_len, - ENIPTransaction *enip_data, uint16_t offset) -{ - int ret = 1; - - if (enip_data->encap_data_item.length == 0) - { - SCLogDebug("DecodeCIP: No CIP Data"); - return 0; - } - - if (offset > (input_len - sizeof(uint8_t))) - { - SCLogDebug("DecodeCIP: Parsing beyond payload length"); - return 0; - } - - uint8_t service = 0; - service = *(input + offset); - - //SCLogDebug("CIP Service 0x%x", service); - - //use service code first bit to determine request/response, no need to save or push offset - if (service >> 7) - { - ret = DecodeCIPResponsePDU(input, input_len, enip_data, offset); - } else - { - ret = DecodeCIPRequestPDU(input, input_len, enip_data, offset); - } - - return ret; -} - - - -/** - * \brief Decode CIP Request - * @param input, input_len data stream - * @param enip_data stores data from Packet - * @param offset current point in the packet - * @return 1 Packet ok - * @return 0 Packet has errors - */ -int DecodeCIPRequestPDU(const uint8_t *input, uint32_t input_len, - ENIPTransaction *enip_data, uint16_t offset) -{ - int ret = 1; - - if (enip_data->encap_data_item.length < sizeof(CIPReqHdr)) - { - SCLogDebug("DecodeCIPRequest - Malformed CIP Data"); - return 0; - } - - uint8_t service = 0; //<-----CIP SERVICE - uint8_t path_size = 0; - - if (ENIPExtractUint8(&service, input, &offset, input_len) != 1) - { - return 0; - } - if (ENIPExtractUint8(&path_size, input, &offset, input_len) != 1) - { - return 0; - } - - if (service > MAX_CIP_SERVICE) - { // service codes of value 0x80 or greater are not permitted because in the CIP protocol the highest order bit is used to flag request(0)/response(1) - SCLogDebug("DecodeCIPRequest - INVALID CIP SERVICE 0x%x", service); - return 0; - } - - //reached maximum number of services - if (enip_data->service_count > 32) - { - SCLogDebug("DecodeCIPRequest: Maximum services reached"); - return 0; - } - - //save CIP data - CIPServiceEntry *node = CIPServiceAlloc(enip_data); - if (node == NULL) - { - SCLogDebug("DecodeCIPRequest: Unable to create CIP service"); - return 0; - } - node->direction = 0; - node->service = service; - node->request.path_size = path_size; - node->request.path_offset = offset; - // SCLogDebug("DecodeCIPRequestPDU: service 0x%x size %d", node->service, - // node->request.path_size); - - DecodeCIPRequestPathPDU(input, input_len, node, offset); - - offset += path_size * sizeof(uint16_t); //move offset past pathsize - - //list of CIP services is large and can be vendor specific, store CIP service anyways and let the rule decide the action - switch (service) - { - case CIP_RESERVED: - SCLogDebug("DecodeCIPRequest - CIP_RESERVED"); - break; - case CIP_GET_ATTR_ALL: - SCLogDebug("DecodeCIPRequest - CIP_GET_ATTR_ALL"); - break; - case CIP_GET_ATTR_LIST: - SCLogDebug("DecodeCIPRequest - CIP_GET_ATTR_LIST"); - break; - case CIP_SET_ATTR_LIST: - SCLogDebug("DecodeCIPRequest - CIP_SET_ATTR_LIST"); - break; - case CIP_RESET: - SCLogDebug("DecodeCIPRequest - CIP_RESET"); - break; - case CIP_START: - SCLogDebug("DecodeCIPRequest - CIP_START"); - break; - case CIP_STOP: - SCLogDebug("DecodeCIPRequest - CIP_STOP"); - break; - case CIP_CREATE: - SCLogDebug("DecodeCIPRequest - CIP_CREATE"); - break; - case CIP_DELETE: - SCLogDebug("DecodeCIPRequest - CIP_DELETE"); - break; - case CIP_MSP: - SCLogDebug("DecodeCIPRequest - CIP_MSP"); - DecodeCIPRequestMSPPDU(input, input_len, enip_data, offset); - break; - case CIP_APPLY_ATTR: - SCLogDebug("DecodeCIPRequest - CIP_APPLY_ATTR"); - break; - case CIP_KICK_TIMER: - SCLogDebug("DecodeCIPRequest - CIP_KICK_TIMER"); - break; - case CIP_OPEN_CONNECTION: - SCLogDebug("DecodeCIPRequest - CIP_OPEN_CONNECTION"); - break; - case CIP_CHANGE_START: - SCLogDebug("DecodeCIPRequest - CIP_CHANGE_START"); - break; - case CIP_GET_STATUS: - SCLogDebug("DecodeCIPRequest - CIP_GET_STATUS"); - break; - default: - SCLogDebug("DecodeCIPRequest - CIP SERVICE 0x%x", service); - } - - return ret; -} - -/** - * \brief Decode CIP Request Path - * @param input, input_len data stream - * @param enip_data stores data from Packet - * @param offset current point in the packet - * @param cipserviced the cip service rule - * @return 1 Packet matches - * @return 0 Packet not match - */ -int DecodeCIPRequestPathPDU(const uint8_t *input, uint32_t input_len, - CIPServiceEntry *node, uint16_t offset) -{ - //SCLogDebug("DecodeCIPRequestPath: service 0x%x size %d length %d", - // node->service, node->request.path_size, input_len); - - if (node->request.path_size < 1) - { - //SCLogDebug("DecodeCIPRequestPath: empty path or CIP Response"); - return 0; - } - - int bytes_remain = node->request.path_size; - - uint8_t reserved; //unused byte reserved by ODVA - - //8 bit fields - uint8_t req_path_instance8; - uint8_t req_path_attr8; - - //16 bit fields - uint16_t req_path_class16; - uint16_t req_path_instance16; - - uint16_t class = 0; - - SegmentEntry *seg = NULL; - - while (bytes_remain > 0) - { - uint8_t segment = 0; - if (ENIPExtractUint8(&segment, input, &offset, input_len) != 1) - { - return 0; - } - switch (segment) - { //assume order is class then instance. Can have multiple - case PATH_CLASS_8BIT: { - uint8_t req_path_class8 = 0; - if (ENIPExtractUint8(&req_path_class8, input, &offset, input_len) != 1) { - return 0; - } - class = (uint16_t) req_path_class8; - SCLogDebug("DecodeCIPRequestPathPDU: 8bit class 0x%x", class); - - seg = SCMalloc(sizeof(SegmentEntry)); - if (unlikely(seg == NULL)) - return 0; - seg->segment = segment; - seg->value = class; - TAILQ_INSERT_TAIL(&node->segment_list, seg, next); - - bytes_remain--; - break; - } - case PATH_INSTANCE_8BIT: - if (ENIPExtractUint8(&req_path_instance8, input, &offset, input_len) != 1) - { - return 0; - } - //skip instance, don't need to store - bytes_remain--; - break; - case PATH_ATTR_8BIT: //single attribute - if (ENIPExtractUint8(&req_path_attr8, input, &offset, input_len) != 1) - { - return 0; - } - //uint16_t attrib = (uint16_t) req_path_attr8; - //SCLogDebug("DecodeCIPRequestPath: 8bit attr 0x%x", attrib); - - seg = SCMalloc(sizeof(SegmentEntry)); - if (unlikely(seg == NULL)) - return 0; - seg->segment = segment; - seg->value = class; - TAILQ_INSERT_TAIL(&node->segment_list, seg, next); - - bytes_remain--; - break; - case PATH_CLASS_16BIT: - if (ENIPExtractUint8(&reserved, input, &offset, input_len) != 1) //skip reserved - { - return 0; - } - if (ENIPExtractUint16(&req_path_class16, input, &offset, input_len) != 1) - { - return 0; - } - class = req_path_class16; - SCLogDebug("DecodeCIPRequestPath: 16bit class 0x%x", class); - - seg = SCMalloc(sizeof(SegmentEntry)); - if (unlikely(seg == NULL)) - return 0; - seg->segment = segment; - seg->value = class; - TAILQ_INSERT_TAIL(&node->segment_list, seg, next); - if (bytes_remain >= 2) - { - bytes_remain = bytes_remain - 2; - } else - { - bytes_remain = 0; - } - break; - case PATH_INSTANCE_16BIT: - if (ENIPExtractUint8(&reserved, input, &offset, input_len) != 1) // skip reserved - { - return 0; - } - if (ENIPExtractUint16(&req_path_instance16, input, &offset, input_len) != 1) - { - return 0; - } - //skip instance, don't need to store - if (bytes_remain >= 2) - { - bytes_remain = bytes_remain - 2; - } else - { - bytes_remain = 0; - } - break; - default: - SCLogDebug( - "DecodeCIPRequestPath: UNKNOWN SEGMENT 0x%x service 0x%x", - segment, node->service); - return 0; - } - } - - if ((node->service == CIP_SET_ATTR_LIST) || (node->service - == CIP_GET_ATTR_LIST)) - { - uint16_t attr_list_count; - uint16_t attribute; - //parse get/set attribute list - - if (ENIPExtractUint16(&attr_list_count, input, &offset, input_len) != 1) - { - return 0; - } - SCLogDebug("DecodeCIPRequestPathPDU: attribute list count %d", - attr_list_count); - for (int i = 0; i < attr_list_count; i++) - { - if (ENIPExtractUint16(&attribute, input, &offset, input_len) != 1) - { - return 0; - } - SCLogDebug("DecodeCIPRequestPathPDU: attribute %d", attribute); - //save attrs - AttributeEntry *attr = SCMalloc(sizeof(AttributeEntry)); - if (unlikely(attr == NULL)) - return 0; - attr->attribute = attribute; - TAILQ_INSERT_TAIL(&node->attrib_list, attr, next); - - } - } - - return 1; -} - -/** - * \brief Decode CIP Response - * @param input, input_len data stream - * @param enip_data stores data from Packet - * @param offset current point in the packet - * @return 1 Packet ok - * @return 0 Packet has errors - */ -int DecodeCIPResponsePDU(const uint8_t *input, uint32_t input_len, - ENIPTransaction *enip_data, uint16_t offset) -{ - int ret = 1; - - if (enip_data->encap_data_item.length < sizeof(CIPRespHdr)) - { - SCLogDebug("DecodeCIPResponse - Malformed CIP Data"); - return 0; - } - - uint8_t service = 0; //<----CIP SERVICE - uint8_t reserved; //unused byte reserved by ODVA - uint16_t status; - - if (ENIPExtractUint8(&service, input, &offset, input_len) != 1) - { - return 0; - } - if (ENIPExtractUint8(&reserved, input, &offset, input_len) != 1) - { - return 0; - } - if (ENIPExtractUint16(&status, input, &offset, input_len) != 1) - { - return 0; - } - - //SCLogDebug("DecodeCIPResponse: service 0x%x",service); - service &= 0x7f; //strip off top bit to get service code. Responses have first bit as 1 - - SCLogDebug("CIP service 0x%x status 0x%x", service, status); - - //reached maximum number of services - if (enip_data->service_count > 32) - { - SCLogDebug("DecodeCIPRequest: Maximum services reached"); - return 0; - } - - //save CIP data - CIPServiceEntry *node = CIPServiceAlloc(enip_data); - if (node == NULL) - { - SCLogDebug("DecodeCIPRequest: Unable to create CIP service"); - return 0; - } - node->direction = 1; - node->service = service; - node->response.status = status; - - SCLogDebug("DecodeCIPResponsePDU: service 0x%x size %d", node->service, - node->request.path_size); - - //list of CIP services is large and can be vendor specific, store CIP service anyways and let the rule decide the action - switch (service) - { - case CIP_RESERVED: - SCLogDebug("DecodeCIPResponse - CIP_RESERVED"); - break; - case CIP_GET_ATTR_ALL: - SCLogDebug("DecodeCIPResponse - CIP_GET_ATTR_ALL"); - break; - case CIP_GET_ATTR_LIST: - SCLogDebug("DecodeCIPResponse - CIP_GET_ATTR_LIST"); - break; - case CIP_SET_ATTR_LIST: - SCLogDebug("DecodeCIPResponse - CIP_SET_ATTR_LIST"); - break; - case CIP_RESET: - SCLogDebug("DecodeCIPResponse - CIP_RESET"); - break; - case CIP_START: - SCLogDebug("DecodeCIPResponse - CIP_START"); - break; - case CIP_STOP: - SCLogDebug("DecodeCIPResponse - CIP_STOP"); - break; - case CIP_CREATE: - SCLogDebug("DecodeCIPResponse - CIP_CREATE"); - break; - case CIP_DELETE: - SCLogDebug("DecodeCIPResponse - CIP_DELETE"); - break; - case CIP_MSP: - SCLogDebug("DecodeCIPResponse - CIP_MSP"); - DecodeCIPResponseMSPPDU(input, input_len, enip_data, offset); - break; - case CIP_APPLY_ATTR: - SCLogDebug("DecodeCIPResponse - CIP_APPLY_ATTR"); - break; - case CIP_KICK_TIMER: - SCLogDebug("DecodeCIPResponse - CIP_KICK_TIMER"); - break; - case CIP_OPEN_CONNECTION: - SCLogDebug("DecodeCIPResponse - CIP_OPEN_CONNECTION"); - break; - case CIP_CHANGE_START: - SCLogDebug("DecodeCIPResponse - CIP_CHANGE_START"); - break; - case CIP_GET_STATUS: - SCLogDebug("DecodeCIPResponse - CIP_GET_STATUS"); - break; - default: - SCLogDebug("DecodeCIPResponse - CIP SERVICE 0x%x", service); - } - - return ret; -} - - -/** - * \brief Decode CIP Request Multi Service Packet - * @param input, input_len data stream - * @param enip_data stores data from Packet - * @param offset current point in the packet - * @return 1 Packet ok - * @return 0 Packet has errors - */ -int DecodeCIPRequestMSPPDU(const uint8_t *input, uint32_t input_len, - ENIPTransaction *enip_data, uint16_t offset) -{ - int ret = 1; - if (offset >= (input_len - sizeof(uint16_t))) - { - SCLogDebug("DecodeCIPRequestMSPPDU: Parsing beyond payload length"); - return 0; - } - //use temp_offset just to grab the service offset, don't want to use and push offset - uint16_t temp_offset = offset; - uint16_t num_services; - if (ByteExtractUint16(&num_services, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), - (const uint8_t *)(input + temp_offset)) == -1) { - return 0; - } - - temp_offset += sizeof(uint16_t); - //SCLogDebug("DecodeCIPRequestMSP number of services %d",num_services); - - for (int svc = 1; svc < num_services + 1; svc++) - { - if (temp_offset >= (input_len - sizeof(uint16_t))) - { - SCLogDebug("DecodeCIPRequestMSPPDU: Parsing beyond payload length"); - return 0; - } - - uint16_t svc_offset; //read set of service offsets - if (ByteExtractUint16(&svc_offset, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), - (const uint8_t *)(input + temp_offset)) == -1) { - return 0; - } - temp_offset += sizeof(uint16_t); - //SCLogDebug("parseCIPRequestMSP service %d offset %d",svc, svc_offset); - - DecodeCIPPDU(input, input_len, enip_data, offset + svc_offset); //parse CIP at found offset - } - - return ret; -} - - - -/** - * \brief Decode CIP Response MultiService Packet. - * @param input, input_len data stream - * @param enip_data stores data from Packet - * @param offset current point in the packet - * @return 1 Packet ok - * @return 0 Packet has errors - */ -int DecodeCIPResponseMSPPDU(const uint8_t *input, uint32_t input_len, - ENIPTransaction *enip_data, uint16_t offset) -{ - int ret = 1; - - if (offset >= (input_len - sizeof(uint16_t))) - { - SCLogDebug("DecodeCIPResponseMSPPDU: Parsing beyond payload length"); - return 0; - } - //use temp_offset just to grab the service offset, don't want to use and push offset - uint16_t temp_offset = offset; - uint16_t num_services; - if (ByteExtractUint16(&num_services, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), - (const uint8_t *)(input + temp_offset)) == -1) { - return 0; - } - temp_offset += sizeof(uint16_t); - //SCLogDebug("DecodeCIPResponseMSP number of services %d", num_services); - - for (int svc = 0; svc < num_services; svc++) { - if (temp_offset >= (input_len - sizeof(uint16_t))) - { - SCLogDebug("DecodeCIPResponseMSP: Parsing beyond payload length"); - return 0; - } - - uint16_t svc_offset; //read set of service offsets - if (ByteExtractUint16(&svc_offset, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), - (const uint8_t *)(input + temp_offset)) == -1) { - return 0; - } - temp_offset += sizeof(uint16_t); - //SCLogDebug("parseCIPResponseMSP service %d offset %d", svc, svc_offset); - - DecodeCIPPDU(input, input_len, enip_data, offset + svc_offset); //parse CIP at found offset - } - - return ret; -} diff --git a/src/app-layer-enip-common.h b/src/app-layer-enip-common.h deleted file mode 100644 index 1578343a69eb..000000000000 --- a/src/app-layer-enip-common.h +++ /dev/null @@ -1,245 +0,0 @@ -/* 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 - */ - -#ifndef __APP_LAYER_ENIP_COMMON_H__ -#define __APP_LAYER_ENIP_COMMON_H__ - -#include "rust.h" - -// 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 -//Found in wireshark -#define ENCAP_HEADER_ERROR 0x006A - -#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 ENIPEncapAddressItem_ { - uint16_t type; - uint16_t length; - uint32_t conn_id; - uint32_t sequence_num; -} ENIPEncapAddressItem; - -/** - * 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 - { - uint16_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; - uint64_t tx_num; /**< internal: id */ - uint16_t tx_id; /**< transaction id */ - uint16_t service_count; - - ENIPEncapHdr header; /**< encapsulation header */ - ENIPEncapDataHdr encap_data_header; /**< encapsulation data header */ - ENIPEncapAddressItem encap_addr_item; /**< encapsulated address item */ - ENIPEncapDataItem encap_data_item; /**< encapsulated data item */ - - TAILQ_HEAD(, CIPServiceEntry_) service_list; /**< list for CIP */ - - TAILQ_ENTRY(ENIPTransaction_) next; - AppLayerTxData tx_data; -} ENIPTransaction; - -/** \brief Per flow ENIP state container */ -typedef struct ENIPState_ -{ - AppLayerStateData state_data; - 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(const uint8_t *input, uint32_t input_len, - ENIPTransaction *enip_data); -int DecodeCommonPacketFormatPDU(const uint8_t *input, uint32_t input_len, - ENIPTransaction *enip_data, uint16_t offset); -int DecodeCIPPDU(const uint8_t *input, uint32_t input_len, - ENIPTransaction *enip_data, uint16_t offset); -int DecodeCIPRequestPDU(const uint8_t *input, uint32_t input_len, - ENIPTransaction *enip_data, uint16_t offset); -int DecodeCIPResponsePDU(const uint8_t *input, uint32_t input_len, - ENIPTransaction *enip_data, uint16_t offset); -int DecodeCIPRequestPathPDU(const uint8_t *input, uint32_t input_len, - CIPServiceEntry *node, uint16_t offset); -int DecodeCIPRequestMSPPDU(const uint8_t *input, uint32_t input_len, - ENIPTransaction *enip_data, uint16_t offset); -int DecodeCIPResponseMSPPDU(const uint8_t *input, uint32_t input_len, - ENIPTransaction *enip_data, uint16_t offset); - -#endif /* __APP_LAYER_ENIP_COMMON_H__ */ diff --git a/src/app-layer-enip.c b/src/app-layer-enip.c deleted file mode 100644 index f059774b6da5..000000000000 --- a/src/app-layer-enip.c +++ /dev/null @@ -1,709 +0,0 @@ -/* 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 - * - * App-layer parser for ENIP protocol - * - */ - -#include "suricata-common.h" -#include "suricata.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.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-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. - */ -static int ENIPGetAlstateProgress(void *tx, uint8_t direction) -{ - return 1; -} - -static AppLayerTxData *ENIPGetTxData(void *vtx) -{ - ENIPTransaction *tx = (ENIPTransaction *)vtx; - return &tx->tx_data; -} - -static AppLayerStateData *ENIPGetStateData(void *vstate) -{ - ENIPState *state = (ENIPState *)vstate; - return &state->state_data; -} - -static 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; -} - -static uint64_t ENIPGetTxCnt(void *alstate) -{ - return ((ENIPState *)alstate)->transaction_max; -} - -static 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("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; -} - -static int ENIPStateGetEventInfoById(int event_id, const char **event_name, - AppLayerEventType *event_type) -{ - *event_name = SCMapEnumValueToName(event_id, enip_decoder_event_table); - if (*event_name == NULL) { - SCLogError("event \"%d\" not present in " - "enip's enum map table.", - event_id); - /* yes this is fatal */ - return -1; - } - - *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION; - - return 0; -} - -/** \brief Allocate enip state - * - * return state - */ -static void *ENIPStateAlloc(void *orig_state, AppProto proto_orig) -{ - SCLogDebug("ENIPStateAlloc"); - void *s = SCCalloc(1, sizeof(ENIPState)); - if (unlikely(s == NULL)) - return NULL; - - 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"); - 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->tx_data.events); - - if (tx->tx_data.de_state != NULL) { - DetectEngineStateFree(tx->tx_data.de_state); - - state->tx_with_detect_state_cnt--; - } - - if (state->iter == tx) - state->iter = NULL; - - SCFree(tx); - SCReturn; -} - -/** \brief Free enip state - * - */ -static void ENIPStateFree(void *s) -{ - SCEnter(); - SCLogDebug("ENIPStateFree"); - 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"); - ENIPTransaction *tx = (ENIPTransaction *) SCCalloc(1, - sizeof(ENIPTransaction)); - if (unlikely(tx == NULL)) - return NULL; - - state->curr = tx; - state->transaction_max++; - - 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 - */ -static void ENIPStateTransactionFree(void *state, uint64_t tx_id) -{ - SCEnter(); - SCLogDebug("ENIPStateTransactionFree"); - 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->tx_data.events != NULL) { - if (tx->tx_data.events->cnt <= enip_state->events) - enip_state->events -= tx->tx_data.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 AppLayerResult ENIPParse(Flow *f, void *state, AppLayerParserState *pstate, - StreamSlice stream_slice, void *local_data, uint8_t direction) -{ - SCEnter(); - ENIPState *enip = (ENIPState *) state; - ENIPTransaction *tx; - - const uint8_t *input = StreamSliceGetData(&stream_slice); - uint32_t input_len = StreamSliceGetDataLen(&stream_slice); - - if (input == NULL && AppLayerParserStateIssetFlag(pstate, - APP_LAYER_PARSER_EOF_TS|APP_LAYER_PARSER_EOF_TC)) - { - SCReturnStruct(APP_LAYER_OK); - } else if (input == NULL && input_len != 0) { - // GAP - SCReturnStruct(APP_LAYER_OK); - } else if (input == NULL || input_len == 0) - { - SCReturnStruct(APP_LAYER_ERROR); - } - - while (input_len > 0) - { - tx = ENIPTransactionAlloc(enip); - if (tx == NULL) - SCReturnStruct(APP_LAYER_OK); - - if (direction == STREAM_TOCLIENT) - tx->tx_data.detect_flags_ts |= APP_LAYER_TX_SKIP_INSPECT_FLAG; - else - tx->tx_data.detect_flags_tc |= APP_LAYER_TX_SKIP_INSPECT_FLAG; - - SCLogDebug("ENIPParse input len %d", input_len); - DecodeENIPPDU(input, input_len, tx); - uint32_t pkt_len = tx->header.length + sizeof(ENIPEncapHdr); - SCLogDebug("ENIPParse packet len %d", pkt_len); - if (pkt_len > input_len) - { - SCLogDebug("Invalid packet length"); - break; - } - - input += pkt_len; - input_len -= pkt_len; - //SCLogDebug("remaining %d", input_len); - - if (input_len < sizeof(ENIPEncapHdr)) - { - //SCLogDebug("Not enough data"); //not enough data for ENIP - break; - } - } - - SCReturnStruct(APP_LAYER_OK); -} - -static AppLayerResult ENIPParseRequest(Flow *f, void *state, AppLayerParserState *pstate, - StreamSlice stream_slice, void *local_data) -{ - return ENIPParse(f, state, pstate, stream_slice, local_data, STREAM_TOSERVER); -} - -static AppLayerResult ENIPParseResponse(Flow *f, void *state, AppLayerParserState *pstate, - StreamSlice stream_slice, void *local_data) -{ - return ENIPParse(f, state, pstate, stream_slice, local_data, STREAM_TOCLIENT); -} - -#define ENIP_LEN_REGISTER_SESSION 4 // protocol u16, options u16 - -static uint16_t ENIPProbingParser(Flow *f, uint8_t direction, - const uint8_t *input, uint32_t input_len, uint8_t *rdir) -{ - // SCLogDebug("ENIPProbingParser %d", input_len); - if (input_len < sizeof(ENIPEncapHdr)) - { - SCLogDebug("length too small to be a ENIP header"); - return ALPROTO_UNKNOWN; - } - uint16_t cmd; - uint16_t enip_len; - uint32_t status; - uint32_t option; - uint16_t nbitems; - - int ret = ByteExtractUint32( - &status, BYTE_LITTLE_ENDIAN, sizeof(uint32_t), (const uint8_t *)(input + 8)); - if (ret < 0) { - return ALPROTO_FAILED; - } - switch (status) { - case SUCCESS: - case INVALID_CMD: - case NO_RESOURCES: - case INCORRECT_DATA: - case INVALID_SESSION: - case INVALID_LENGTH: - case UNSUPPORTED_PROT_REV: - case ENCAP_HEADER_ERROR: - break; - default: - return ALPROTO_FAILED; - } - ret = ByteExtractUint16(&cmd, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input)); - if(ret < 0) { - return ALPROTO_FAILED; - } - ret = ByteExtractUint32( - &option, BYTE_LITTLE_ENDIAN, sizeof(uint32_t), (const uint8_t *)(input + 20)); - if (ret < 0) { - return ALPROTO_FAILED; - } - ret = ByteExtractUint16( - &enip_len, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input + 2)); - if (ret < 0) { - return ALPROTO_FAILED; - } - - //ok for all the known commands - switch(cmd) { - case NOP: - if (option != 0) { - return ALPROTO_FAILED; - } - break; - case REGISTER_SESSION: - if (enip_len != ENIP_LEN_REGISTER_SESSION) { - return ALPROTO_FAILED; - } - break; - case UNREGISTER_SESSION: - if (enip_len != ENIP_LEN_REGISTER_SESSION && enip_len != 0) { - // 0 for request and 4 for response - return ALPROTO_FAILED; - } - break; - case LIST_SERVICES: - case LIST_IDENTITY: - case SEND_RR_DATA: - case SEND_UNIT_DATA: - case INDICATE_STATUS: - case CANCEL: - break; - case LIST_INTERFACES: - if (input_len < sizeof(ENIPEncapHdr) + 2) { - SCLogDebug("length too small to be a ENIP LIST_INTERFACES"); - return ALPROTO_UNKNOWN; - } - ret = ByteExtractUint16( - &nbitems, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input)); - if(ret < 0) { - return ALPROTO_FAILED; - } - if (enip_len < sizeof(ENIPEncapHdr) + 2 * (size_t)nbitems) { - return ALPROTO_FAILED; - } - break; - default: - return ALPROTO_FAILED; - } - return ALPROTO_ENIP; -} - -static AppLayerGetTxIterTuple ENIPGetTxIterator(const uint8_t ipproto, const AppProto alproto, - void *alstate, uint64_t min_tx_id, uint64_t max_tx_id, AppLayerGetTxIterState *state) -{ - ENIPState *enip_state = (ENIPState *)alstate; - AppLayerGetTxIterTuple no_tuple = { NULL, 0, false }; - if (enip_state) { - ENIPTransaction *tx_ptr; - if (state->un.ptr == NULL) { - tx_ptr = TAILQ_FIRST(&enip_state->tx_list); - } else { - tx_ptr = (ENIPTransaction *)state->un.ptr; - } - if (tx_ptr) { - while (tx_ptr->tx_num < min_tx_id + 1) { - tx_ptr = TAILQ_NEXT(tx_ptr, next); - if (!tx_ptr) { - return no_tuple; - } - } - if (tx_ptr->tx_num >= max_tx_id + 1) { - return no_tuple; - } - state->un.ptr = TAILQ_NEXT(tx_ptr, next); - AppLayerGetTxIterTuple tuple = { - .tx_ptr = tx_ptr, - .tx_id = tx_ptr->tx_num - 1, - .has_next = (state->un.ptr != NULL), - }; - return tuple; - } - } - return no_tuple; -} - -/** - * \brief Function to register the ENIP protocol parsers and other functions - */ -void RegisterENIPUDPParsers(void) -{ - SCEnter(); - const char *proto_name = "enip"; - - if (AppLayerProtoDetectConfProtoDetectionEnabledDefault("udp", proto_name, false)) { - AppLayerProtoDetectRegisterProtocol(ALPROTO_ENIP, proto_name); - - if (RunmodeIsUnittests()) - { - AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818", ALPROTO_ENIP, - 0, sizeof(ENIPEncapHdr), STREAM_TOSERVER, ENIPProbingParser, NULL); - - AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818", ALPROTO_ENIP, - 0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT, ENIPProbingParser, NULL); - - } else - { - if (!AppLayerProtoDetectPPParseConfPorts("udp", IPPROTO_UDP, - proto_name, ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), - ENIPProbingParser, 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, NULL); - - AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818", - ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT, - ENIPProbingParser, NULL); - } - } - - } else { - SCLogConfig("Protocol detection and parser disabled for %s protocol.", - proto_name); - return; - } - - if (AppLayerParserConfParserEnabled("udp", proto_name)) - { - AppLayerParserRegisterParser(IPPROTO_UDP, ALPROTO_ENIP, STREAM_TOSERVER, ENIPParseRequest); - AppLayerParserRegisterParser(IPPROTO_UDP, ALPROTO_ENIP, STREAM_TOCLIENT, ENIPParseResponse); - - AppLayerParserRegisterStateFuncs(IPPROTO_UDP, ALPROTO_ENIP, - ENIPStateAlloc, ENIPStateFree); - - AppLayerParserRegisterGetTx(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTx); - AppLayerParserRegisterGetTxIterator(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTxIterator); - AppLayerParserRegisterTxDataFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTxData); - AppLayerParserRegisterStateDataFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetStateData); - AppLayerParserRegisterGetTxCnt(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTxCnt); - AppLayerParserRegisterTxFreeFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPStateTransactionFree); - - AppLayerParserRegisterGetStateProgressFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetAlstateProgress); - AppLayerParserRegisterStateProgressCompletionStatus(ALPROTO_ENIP, 1, 1); - - AppLayerParserRegisterGetEventInfo(IPPROTO_UDP, ALPROTO_ENIP, ENIPStateGetEventInfo); - AppLayerParserRegisterGetEventInfoById(IPPROTO_UDP, ALPROTO_ENIP, ENIPStateGetEventInfoById); - - 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(); - const char *proto_name = "enip"; - - if (AppLayerProtoDetectConfProtoDetectionEnabledDefault("tcp", proto_name, false)) { - AppLayerProtoDetectRegisterProtocol(ALPROTO_ENIP, proto_name); - - if (RunmodeIsUnittests()) - { - AppLayerProtoDetectPPRegister(IPPROTO_TCP, "44818", ALPROTO_ENIP, - 0, sizeof(ENIPEncapHdr), STREAM_TOSERVER, ENIPProbingParser, NULL); - - AppLayerProtoDetectPPRegister(IPPROTO_TCP, "44818", ALPROTO_ENIP, - 0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT, ENIPProbingParser, NULL); - - } else - { - if (!AppLayerProtoDetectPPParseConfPorts("tcp", IPPROTO_TCP, - proto_name, ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), - ENIPProbingParser, ENIPProbingParser)) - { - return; - } - } - - } 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, ENIPParseRequest); - AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_ENIP, STREAM_TOCLIENT, ENIPParseResponse); - AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_ENIP, - ENIPStateAlloc, ENIPStateFree); - - AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTx); - AppLayerParserRegisterGetTxIterator(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTxIterator); - AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTxData); - AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetStateData); - AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTxCnt); - AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPStateTransactionFree); - - AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetAlstateProgress); - AppLayerParserRegisterStateProgressCompletionStatus(ALPROTO_ENIP, 1, 1); - - AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_ENIP, ENIPStateGetEventInfo); - - AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, - ALPROTO_ENIP, STREAM_TOSERVER | STREAM_TOCLIENT); - - /* This parser accepts gaps. */ - AppLayerParserRegisterOptionFlags(IPPROTO_TCP, ALPROTO_ENIP, - APP_LAYER_PARSER_OPT_ACCEPT_GAPS); - - AppLayerParserRegisterOptionFlags(IPPROTO_TCP, ALPROTO_ENIP, 0); - } else - { - SCLogConfig("Parser disabled for %s protocol. Protocol detection still on.", - proto_name); - } - -#ifdef UNITTESTS - AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_ENIP, ENIPParserRegisterTests); -#endif - - SCReturn; -} - -/* UNITTESTS */ -#ifdef UNITTESTS -#include "flow-util.h" -#include "stream-tcp.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 - */ -static 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; - f.alproto = ALPROTO_ENIP; - - 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 */ -} diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index 1f6066471757..a6404a20cee0 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -47,7 +47,6 @@ #include "app-layer-ssl.h" #include "app-layer-ssh.h" #include "app-layer-modbus.h" -#include "app-layer-enip.h" #include "app-layer-dnp3.h" #include "app-layer-nfs-tcp.h" #include "app-layer-nfs-udp.h" @@ -1751,8 +1750,7 @@ void AppLayerParserRegisterProtocolParsers(void) rs_dns_tcp_register_parser(); rs_bittorrent_dht_udp_register_parser(); RegisterModbusParsers(); - RegisterENIPUDPParsers(); - RegisterENIPTCPParsers(); + rs_enip_register_parsers(); RegisterDNP3Parsers(); RegisterNFSTCPParsers(); RegisterNFSUDPParsers(); diff --git a/src/detect-cipservice.c b/src/detect-cipservice.c index 494e1e17520f..3897d8d83677 100644 --- a/src/detect-cipservice.c +++ b/src/detect-cipservice.c @@ -24,172 +24,26 @@ */ #include "suricata-common.h" -#include "util-unittest.h" #include "detect-parse.h" #include "detect-engine.h" -#include "util-byte.h" +#include "rust.h" -#include "app-layer-enip-common.h" #include "detect-cipservice.h" -#include "detect-engine-enip.h" /* * CIP SERVICE CODE */ -/** - * \brief CIP Service Detect Prototypes - */ -static int DetectCipServiceSetup(DetectEngineCtx *, Signature *, const char *); -static void DetectCipServiceFree(DetectEngineCtx *, void *); -#ifdef UNITTESTS -static void DetectCipServiceRegisterTests(void); -#endif static int g_cip_buffer_id = 0; /** - * \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 = "match on CIP Service"; - sigmatch_table[DETECT_CIPSERVICE].url = "/rules/enip-keyword.html#enip-cip-keywords"; - sigmatch_table[DETECT_CIPSERVICE].Match = NULL; - sigmatch_table[DETECT_CIPSERVICE].Setup = DetectCipServiceSetup; - sigmatch_table[DETECT_CIPSERVICE].Free = DetectCipServiceFree; -#ifdef UNITTESTS - sigmatch_table[DETECT_CIPSERVICE].RegisterTests - = DetectCipServiceRegisterTests; -#endif - DetectAppLayerInspectEngineRegister2( - "cip", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, DetectEngineInspectCIP, NULL); - DetectAppLayerInspectEngineRegister2( - "cip", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectCIP, NULL); - - g_cip_buffer_id = DetectBufferTypeGetByName("cip"); - - 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 + * \brief this function will free memory associated with DetectCipServiceData * - * \retval cipserviced pointer to DetectCipServiceData on success - * \retval NULL on failure + * \param ptr pointer to DetectCipServiceData */ -static DetectCipServiceData *DetectCipServiceParse(const char *rulestrc) +static void DetectCipServiceFree(DetectEngineCtx *de_ctx, void *ptr) { - const char delims[] = ","; - DetectCipServiceData *cipserviced = NULL; - - //SCLogDebug("DetectCipServiceParse - rule string %s", rulestr); - - /* strtok_r modifies the string so work with a copy */ - char *rulestr = SCStrdup(rulestrc); - if (unlikely(rulestr == NULL)) - goto error; - - 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; - uint8_t var; - uint8_t input[3] = { 0, 0, 0 }; - uint8_t i = 0; - - token = strtok_r(rulestr, delims, &save); - while (token != NULL) - { - if (i > 2) //for now only need 3 parameters - { - SCLogError("too many parameters"); - goto error; - } - - if (i < 2) //if on service or class - { - if (!isdigit((int) *token)) - { - SCLogError("parameter error %s", token); - goto error; - } - } else //if on attribute - { - - if (token[0] == '!') - { - cipserviced->matchattribute = 0; - token++; - } - - if (!isdigit((int) *token)) - { - SCLogError("attribute error %s", token); - goto error; - } - - } - - unsigned long num = atol(token); - if ((num > MAX_CIP_SERVICE) && (i == 0))//if service greater than 7 bit - { - SCLogError("invalid CIP service %lu", num); - goto error; - } else if ((num > MAX_CIP_CLASS) && (i == 1))//if service greater than 16 bit - { - SCLogError("invalid CIP class %lu", num); - goto error; - } else if ((num > MAX_CIP_ATTRIBUTE) && (i == 2))//if service greater than 16 bit - { - SCLogError("invalid CIP attribute %lu", num); - goto error; - } - - sscanf(token, "%2" SCNu8, &var); - input[i++] = var; - - token = strtok_r(NULL, delims, &save); - } - - if (i == 0) { - SCLogError("no tokens found"); - goto error; - } - - cipserviced->cipservice = input[0]; - cipserviced->cipclass = input[1]; - cipserviced->cipattribute = input[2]; - cipserviced->tokens = i; - - SCLogDebug("DetectCipServiceParse - tokens %d", cipserviced->tokens); - SCLogDebug("DetectCipServiceParse - service %d", cipserviced->cipservice); - SCLogDebug("DetectCipServiceParse - class %d", cipserviced->cipclass); - SCLogDebug("DetectCipServiceParse - match attribute %d", - cipserviced->matchattribute); - SCLogDebug("DetectCipServiceParse - attribute %d", - cipserviced->cipattribute); - - SCFree(rulestr); - SCReturnPtr(cipserviced, "DetectENIPFunction"); - -error: - if (cipserviced) - SCFree(cipserviced); - if (rulestr) - SCFree(rulestr); - SCReturnPtr(NULL, "DetectENIP"); + rs_enip_cip_service_free(ptr); } /** @@ -207,242 +61,56 @@ static int DetectCipServiceSetup(DetectEngineCtx *de_ctx, Signature *s, { SCEnter(); - DetectCipServiceData *cipserviced = NULL; - if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) return -1; - cipserviced = DetectCipServiceParse(rulestr); + void *cipserviced = rs_enip_parse_cip_service(rulestr); if (cipserviced == NULL) - goto error; + return -1; if (SigMatchAppendSMToList(de_ctx, s, DETECT_CIPSERVICE, (SigMatchCtx *)cipserviced, g_cip_buffer_id) == NULL) { - goto error; + DetectCipServiceFree(de_ctx, cipserviced); + SCReturnInt(-1); } SCReturnInt(0); - -error: - if (cipserviced != NULL) - DetectCipServiceFree(de_ctx, cipserviced); - SCReturnInt(-1); } /** - * \brief this function will free memory associated with DetectCipServiceData + * \brief This function is used to match enip command type rule option on a transaction with those + * passed via enip_command: * - * \param ptr pointer to DetectCipServiceData + * \retval 0 no match + * \retval 1 match */ -static void DetectCipServiceFree(DetectEngineCtx *de_ctx, void *ptr) -{ - DetectCipServiceData *cipserviced = (DetectCipServiceData *) ptr; - SCFree(cipserviced); -} +static int DetectCipServiceMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) -#ifdef UNITTESTS - -/** - * \test Test CIP Command parameter parsing - */ -static int DetectCipServiceParseTest01 (void) { - DetectCipServiceData *cipserviced = NULL; - cipserviced = DetectCipServiceParse("7"); - FAIL_IF_NULL(cipserviced); - FAIL_IF(cipserviced->cipservice != 7); - DetectCipServiceFree(NULL, cipserviced); - PASS; + return rs_enip_tx_has_cip_service(txv, flags, ctx); } /** - * \test Test CIP Service signature - */ -static int DetectCipServiceSignatureTest01 (void) -{ - DetectEngineCtx *de_ctx = DetectEngineCtxInit(); - FAIL_IF_NULL(de_ctx); - Signature *sig = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any (cip_service:1; sid:1; rev:1;)"); - FAIL_IF_NULL(sig); - DetectEngineCtxFree(de_ctx); - PASS; -} - -/** - * \brief this function registers unit tests for DetectCipService + * \brief Registration function for cip_service: keyword */ -static void DetectCipServiceRegisterTests(void) +void DetectCipServiceRegister(void) { - UtRegisterTest("DetectCipServiceParseTest01", - DetectCipServiceParseTest01); - UtRegisterTest("DetectCipServiceSignatureTest01", - DetectCipServiceSignatureTest01); -} -#endif /* UNITTESTS */ - -/* - * ENIP COMMAND CODE - */ - -/** - * \brief ENIP Command Detect Prototypes - */ -static int DetectEnipCommandSetup(DetectEngineCtx *, Signature *, const char *); -static void DetectEnipCommandFree(DetectEngineCtx *, void *); -#ifdef UNITTESTS -static void DetectEnipCommandRegisterTests(void); -#endif -static int g_enip_buffer_id = 0; + SCEnter(); + sigmatch_table[DETECT_CIPSERVICE].name = "cip_service"; // rule keyword + sigmatch_table[DETECT_CIPSERVICE].desc = + "match on CIP Service, and optionnally class and attribute"; + sigmatch_table[DETECT_CIPSERVICE].url = "/rules/enip-keyword.html#cip_service"; + sigmatch_table[DETECT_CIPSERVICE].Match = NULL; + sigmatch_table[DETECT_CIPSERVICE].AppLayerTxMatch = DetectCipServiceMatch; + sigmatch_table[DETECT_CIPSERVICE].Setup = DetectCipServiceSetup; + sigmatch_table[DETECT_CIPSERVICE].Free = DetectCipServiceFree; -/** - * \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].url = "/rules/enip-keyword.html#enip-cip-keywords"; - sigmatch_table[DETECT_ENIPCOMMAND].Match = NULL; - sigmatch_table[DETECT_ENIPCOMMAND].Setup = DetectEnipCommandSetup; - sigmatch_table[DETECT_ENIPCOMMAND].Free = DetectEnipCommandFree; -#ifdef UNITTESTS - sigmatch_table[DETECT_ENIPCOMMAND].RegisterTests - = DetectEnipCommandRegisterTests; -#endif DetectAppLayerInspectEngineRegister2( - "enip", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, DetectEngineInspectENIP, NULL); + "cip", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, DetectEngineInspectGenericList, NULL); DetectAppLayerInspectEngineRegister2( - "enip", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectENIP, NULL); - - g_enip_buffer_id = DetectBufferTypeGetByName("enip"); -} - -/** - * \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 numeric value - * - * \retval enipcmdd pointer to DetectCipServiceData on success - * \retval NULL on failure - */ -static DetectEnipCommandData *DetectEnipCommandParse(const char *rulestr) -{ - DetectEnipCommandData *enipcmdd = NULL; - - enipcmdd = SCMalloc(sizeof(DetectEnipCommandData)); - if (unlikely(enipcmdd == NULL)) - goto error; - - if (!(isdigit((int) *rulestr))) { - SCLogError("invalid ENIP command %s", rulestr); - goto error; - } - - uint16_t cmd; - if (StringParseUint16(&cmd, 10, 0, rulestr) < 0) { - SCLogError("invalid ENIP command" - ": \"%s\"", - rulestr); - goto error; - } - - enipcmdd->enipcommand = cmd; - - return enipcmdd; - -error: - if (enipcmdd) - SCFree(enipcmdd); - 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, - const char *rulestr) -{ - DetectEnipCommandData *enipcmdd = NULL; + "cip", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectGenericList, NULL); - if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) - return -1; - - enipcmdd = DetectEnipCommandParse(rulestr); - if (enipcmdd == NULL) - goto error; - - if (SigMatchAppendSMToList( - de_ctx, s, DETECT_ENIPCOMMAND, (SigMatchCtx *)enipcmdd, g_enip_buffer_id) == NULL) { - goto error; - } - SCReturnInt(0); - -error: - if (enipcmdd != NULL) - DetectEnipCommandFree(de_ctx, enipcmdd); - SCReturnInt(-1); -} - -/** - * \brief this function will free memory associated with DetectEnipCommandData - * - * \param ptr pointer to DetectEnipCommandData - */ -static void DetectEnipCommandFree(DetectEngineCtx *de_ctx, 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(NULL, 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; -} + g_cip_buffer_id = DetectBufferTypeGetByName("cip"); -/** - * \brief this function registers unit tests for DetectEnipCommand - */ -static void DetectEnipCommandRegisterTests(void) -{ - UtRegisterTest("DetectEnipCommandParseTest01", - DetectEnipCommandParseTest01); - UtRegisterTest("DetectEnipCommandSignatureTest01", - DetectEnipCommandSignatureTest01); + SCReturn; } -#endif /* UNITTESTS */ diff --git a/src/detect-cipservice.h b/src/detect-cipservice.h index 6a9c500cc9fa..9cc5fcb03f61 100644 --- a/src/detect-cipservice.h +++ b/src/detect-cipservice.h @@ -24,48 +24,6 @@ #ifndef _DETECT_CIPSERVICE_H #define _DETECT_CIPSERVICE_H -/** - * 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; #endif /* _DETECT_CIPSERVICE_H */ diff --git a/src/detect-engine-enip.c b/src/detect-engine-enip.c deleted file mode 100644 index 0c5fb0a81cbe..000000000000 --- a/src/detect-engine-enip.c +++ /dev/null @@ -1,288 +0,0 @@ -/* Copyright (C) 2015-2022 Open Information Security Foundation - * - * You can copy, redistribute or modify this Program under the terms of - * the GNU General Public License version 2 as published by the Free - * Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * version 2 along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ - -/** \file - * - * \author Kevin Wong - * - * Based on detect-engine-modbus.c - */ - -#include "suricata-common.h" - -#include "app-layer.h" -#include "app-layer-enip-common.h" - -#include "detect.h" -#include "detect-cipservice.h" -#include "detect-engine-enip.h" - -#include "flow.h" - -#include "util-debug.h" - -#if 0 -/** - * \brief Print fields from ENIP Packet - * @param enip_data - */ -void PrintENIPAL(ENIPTransaction *enip_data) -{ - SCLogDebug("============================================"); - SCLogDebug("ENCAP HEADER cmd 0x%x, length %d, session 0x%x, status 0x%x", - enip_data->header.command, enip_data->header.length, - enip_data->header.session, enip_data->header.status); - //SCLogDebug("context 0x%x option 0x%x", enip_data->header.context, enip_data->header.option); - SCLogDebug("ENCAP DATA HEADER handle 0x%x, timeout %d, count %d", - 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", - enip_data->encap_addr_item.type, enip_data->encap_addr_item.length); - SCLogDebug("ENCAP DATA ITEM type 0x%x, length %d sequence 0x%x", - 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", count, svc->service); - count++; - } -} -#endif - -/** - * \brief Matches the rule to the CIP segment in ENIP Packet - * @param svc - the CIP service entry - * * @param cipserviced - the CIP service rule - */ -static 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 transaction - * * @param cipserviced - the CIP service rule - */ - -static int CIPServiceMatch(ENIPTransaction *enip_data, - DetectCipServiceData *cipserviced) -{ -#ifdef DEBUG - int count = 1; -#endif - CIPServiceEntry *svc = NULL; - //SCLogDebug("CIPServiceMatchAL"); - TAILQ_FOREACH(svc, &enip_data->service_list, next) - { - SCLogDebug("CIPServiceMatchAL service #%d : 0x%x dir %d", - count, svc->service, svc->direction); - - if (cipserviced->cipservice == svc->service) - { // compare service - //SCLogDebug("Rule Match for cip service %d",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"); - return 1; - } - } -#ifdef DEBUG - count++; -#endif - } - 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 - */ -uint8_t DetectEngineInspectCIP(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, - const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f, - uint8_t flags, void *alstate, void *txv, uint64_t tx_id) -{ - SCEnter(); - - - ENIPTransaction *tx = (ENIPTransaction *) txv; - DetectCipServiceData *cipserviced = (DetectCipServiceData *)engine->smd->ctx; - - if (cipserviced == NULL) - { - SCLogDebug("no cipservice state, no match"); - SCReturnInt(0); - } - //SCLogDebug("DetectEngineInspectCIP %d", cipserviced->cipservice); - - if (CIPServiceMatch(tx, cipserviced) == 1) - { - //SCLogDebug("DetectCIPServiceMatchAL found"); - 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 - */ - -uint8_t DetectEngineInspectENIP(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, - const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f, - uint8_t flags, void *alstate, void *txv, uint64_t tx_id) -{ - SCEnter(); - - ENIPTransaction *tx = (ENIPTransaction *) txv; - DetectEnipCommandData *enipcmdd = (DetectEnipCommandData *)engine->smd->ctx; - - if (enipcmdd == NULL) - { - SCLogDebug("no enipcommand state, no match"); - SCReturnInt(0); - } - - //SCLogDebug("DetectEngineInspectENIP %d, %d", enipcmdd->enipcommand, tx->header.command); - - if (enipcmdd->enipcommand == tx->header.command) - { - // SCLogDebug("DetectENIPCommandMatchAL found!"); - SCReturnInt(1); - } - - SCReturnInt(0); -} diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 0f459eccb67b..26bfc6ec1d58 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -272,6 +272,24 @@ #include "detect-ssl-state.h" #include "detect-modbus.h" #include "detect-cipservice.h" +#include "detect-enip-command.h" +#include "detect-enip-status.h" +#include "detect-enip-product-name.h" +#include "detect-enip-protocol-version.h" +#include "detect-enip-cip-attribute.h" +#include "detect-enip-cip-instance.h" +#include "detect-enip-cip-class.h" +#include "detect-enip-cip-extendedstatus.h" +#include "detect-enip-cip-status.h" +#include "detect-enip-service-name.h" +#include "detect-enip-capabilities.h" +#include "detect-enip-revision.h" +#include "detect-enip-identity-status.h" +#include "detect-enip-state.h" +#include "detect-enip-serial.h" +#include "detect-enip-product-code.h" +#include "detect-enip-device-type.h" +#include "detect-enip-vendor-id.h" #include "detect-dnp3.h" #include "detect-ike-exch-type.h" #include "detect-ike-spi.h" @@ -518,6 +536,23 @@ void SigTableSetup(void) DetectModbusRegister(); DetectCipServiceRegister(); DetectEnipCommandRegister(); + DetectEnipStatusRegister(); + DetectEnipProductNameRegister(); + DetectEnipProtocolVersionRegister(); + DetectEnipCipAttributeRegister(); + DetectEnipCipInstanceRegister(); + DetectEnipCipClassRegister(); + DetectEnipCipExtendedstatusRegister(); + DetectEnipCipStatusRegister(); + DetectEnipServiceNameRegister(); + DetectEnipCapabilitiesRegister(); + DetectEnipRevisionRegister(); + DetectEnipIdentityStatusRegister(); + DetectEnipStateRegister(); + DetectEnipSerialRegister(); + DetectEnipProductCodeRegister(); + DetectEnipDeviceTypeRegister(); + DetectEnipVendorIdRegister(); DetectDNP3Register(); DetectIkeExchTypeRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index 273aa10d7c9b..3317b57c8f04 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -245,6 +245,23 @@ enum DetectKeywordId { DETECT_AL_MODBUS, DETECT_CIPSERVICE, DETECT_ENIPCOMMAND, + DETECT_ENIPSTATUS, + DETECT_ENIP_PRODUCTNAME, + DETECT_ENIP_PROTOCOLVERSION, + DETECT_ENIP_CIPATTRIBUTE, + DETECT_ENIP_CIPINSTANCE, + DETECT_ENIP_CIPCLASS, + DETECT_ENIP_CIPEXTENDEDSTATUS, + DETECT_ENIP_CIPSTATUS, + DETECT_ENIP_SERVICENAME, + DETECT_ENIP_CAPABILITIES, + DETECT_ENIP_REVISION, + DETECT_ENIP_IDENTITYSTATUS, + DETECT_ENIP_STATE, + DETECT_ENIP_SERIAL, + DETECT_ENIP_PRODUCTCODE, + DETECT_ENIP_DEVICETYPE, + DETECT_ENIP_VENDORID, DETECT_AL_DNP3DATA, DETECT_AL_DNP3FUNC, diff --git a/src/detect-enip-capabilities.c b/src/detect-enip-capabilities.c new file mode 100644 index 000000000000..c3c1c1c67287 --- /dev/null +++ b/src/detect-enip-capabilities.c @@ -0,0 +1,106 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Set up ENIP capabilities keyword + */ + +#include "suricata-common.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-uint.h" +#include "rust.h" + +#include "detect-enip-capabilities.h" + +static int g_enip_capabilities_id = 0; + +static void DetectEnipCapabilitiesFree(DetectEngineCtx *de_ctx, void *ptr) +{ + rs_detect_u16_free(ptr); +} + +/** + * \brief this function is used to parse enip_capabilities 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 capabilities options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectEnipCapabilitiesSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) + return -1; + + DetectU16Data *du16 = DetectU16Parse(rulestr); + if (du16 == NULL) { + return -1; + } + + if (SigMatchAppendSMToList(de_ctx, s, DETECT_ENIP_CAPABILITIES, (SigMatchCtx *)du16, + g_enip_capabilities_id) == NULL) { + DetectEnipCapabilitiesFree(de_ctx, du16); + SCReturnInt(-1); + } + SCReturnInt(0); +} + +/** + * \brief This function is used to match enip capabilities type rule option on a transaction + * with those passed via enip_capabilities: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectEnipCapabilitiesMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) + +{ + uint16_t value; + if (!rs_enip_get_capabilities(txv, &value)) + SCReturnInt(0); + const DetectU16Data *du16 = (const DetectU16Data *)ctx; + return DetectU16Match(value, du16); +} + +/** + * \brief Registration function for enip_capabilities: keyword + */ +void DetectEnipCapabilitiesRegister(void) +{ + sigmatch_table[DETECT_ENIP_CAPABILITIES].name = "enip.capabilities"; // rule keyword + sigmatch_table[DETECT_ENIP_CAPABILITIES].desc = "rules for detecting EtherNet/IP capabilities"; + sigmatch_table[DETECT_ENIP_CAPABILITIES].url = "/rules/enip-keyword.html#enip-capabilities"; + sigmatch_table[DETECT_ENIP_CAPABILITIES].Match = NULL; + sigmatch_table[DETECT_ENIP_CAPABILITIES].AppLayerTxMatch = DetectEnipCapabilitiesMatch; + sigmatch_table[DETECT_ENIP_CAPABILITIES].Setup = DetectEnipCapabilitiesSetup; + sigmatch_table[DETECT_ENIP_CAPABILITIES].Free = DetectEnipCapabilitiesFree; + + DetectAppLayerInspectEngineRegister2("enip.capabilities", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, + DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister2("enip.capabilities", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectGenericList, NULL); + + g_enip_capabilities_id = DetectBufferTypeGetByName("enip.capabilities"); +} diff --git a/src/app-layer-enip.h b/src/detect-enip-capabilities.h similarity index 68% rename from src/app-layer-enip.h rename to src/detect-enip-capabilities.h index 25cb1d5745da..3c329885f69a 100644 --- a/src/app-layer-enip.h +++ b/src/detect-enip-capabilities.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2015 Open Information Security Foundation +/* Copyright (C) 2023 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 @@ -18,15 +18,12 @@ /** * \file * - * \author Kevin Wong + * \author Philippe Antoine */ -#ifndef __APP_LAYER_ENIP_H__ -#define __APP_LAYER_ENIP_H__ +#ifndef _DETECT_ENIP_CAPABILITIES_H +#define _DETECT_ENIP_CAPABILITIES_H +void DetectEnipCapabilitiesRegister(void); -void RegisterENIPUDPParsers(void); -void RegisterENIPTCPParsers(void); -void ENIPParserRegisterTests(void); - -#endif /* __APP_LAYER_ENIP_H__ */ +#endif /* _DETECT_ENIP_CAPABILITIES_H */ diff --git a/src/detect-enip-cip-attribute.c b/src/detect-enip-cip-attribute.c new file mode 100644 index 000000000000..f6f56ad7db32 --- /dev/null +++ b/src/detect-enip-cip-attribute.c @@ -0,0 +1,102 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Set up ENIP cip attribute keyword + */ + +#include "suricata-common.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-uint.h" +#include "rust.h" + +#include "detect-enip-cip-attribute.h" + +static int g_enip_cip_attribute_id = 0; + +static void DetectEnipCipAttributeFree(DetectEngineCtx *de_ctx, void *ptr) +{ + rs_detect_u32_free(ptr); +} + +/** + * \brief this function is used to parse enip_cip_attribute 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 cip_attribute options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectEnipCipAttributeSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) + return -1; + + DetectU32Data *du32 = DetectU32Parse(rulestr); + if (du32 == NULL) { + return -1; + } + + if (SigMatchAppendSMToList(de_ctx, s, DETECT_ENIP_CIPATTRIBUTE, (SigMatchCtx *)du32, + g_enip_cip_attribute_id) == NULL) { + DetectEnipCipAttributeFree(de_ctx, du32); + SCReturnInt(-1); + } + SCReturnInt(0); +} + +/** + * \brief This function is used to match enip cip_attribute type rule option on a transaction + * with those passed via enip_cip_attribute: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectEnipCipAttributeMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) + +{ + return rs_enip_tx_has_cip_attribute(txv, ctx); +} + +/** + * \brief Registration function for enip_cip_attribute: keyword + */ +void DetectEnipCipAttributeRegister(void) +{ + sigmatch_table[DETECT_ENIP_CIPATTRIBUTE].name = "enip.cip_attribute"; // rule keyword + sigmatch_table[DETECT_ENIP_CIPATTRIBUTE].desc = "rules for detecting EtherNet/IP cip_attribute"; + sigmatch_table[DETECT_ENIP_CIPATTRIBUTE].url = "/rules/enip-keyword.html#enip-cip-attribute"; + sigmatch_table[DETECT_ENIP_CIPATTRIBUTE].Match = NULL; + sigmatch_table[DETECT_ENIP_CIPATTRIBUTE].AppLayerTxMatch = DetectEnipCipAttributeMatch; + sigmatch_table[DETECT_ENIP_CIPATTRIBUTE].Setup = DetectEnipCipAttributeSetup; + sigmatch_table[DETECT_ENIP_CIPATTRIBUTE].Free = DetectEnipCipAttributeFree; + + DetectAppLayerInspectEngineRegister2("enip.cip_attribute", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, + DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister2("enip.cip_attribute", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectGenericList, NULL); + + g_enip_cip_attribute_id = DetectBufferTypeGetByName("enip.cip_attribute"); +} diff --git a/src/detect-engine-enip.h b/src/detect-enip-cip-attribute.h similarity index 50% rename from src/detect-engine-enip.h rename to src/detect-enip-cip-attribute.h index 3c263f999770..2d917fe9ad2e 100644 --- a/src/detect-engine-enip.h +++ b/src/detect-enip-cip-attribute.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2015 Open Information Security Foundation +/* Copyright (C) 2023 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 @@ -15,20 +15,15 @@ * 02110-1301, USA. */ -/** \file +/** + * \file * - * \author Kevin Wong + * \author Philippe Antoine */ -#ifndef __DETECT_ENGINE_ENIP_H__ -#define __DETECT_ENGINE_ENIP_H__ +#ifndef _DETECT_ENIP_CIP_ATTRIBUTE_H +#define _DETECT_ENIP_CIP_ATTRIBUTE_H -uint8_t DetectEngineInspectCIP(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *, - const struct DetectEngineAppInspectionEngine_ *, const Signature *, Flow *, uint8_t, void *, - void *, uint64_t); +void DetectEnipCipAttributeRegister(void); -uint8_t DetectEngineInspectENIP(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *, - const struct DetectEngineAppInspectionEngine_ *, const Signature *, Flow *, uint8_t, void *, - void *, uint64_t); - -#endif /* __DETECT_ENGINE_ENIP_H__ */ +#endif /* _DETECT_ENIP_CIP_ATTRIBUTE_H */ diff --git a/src/detect-enip-cip-class.c b/src/detect-enip-cip-class.c new file mode 100644 index 000000000000..1271fc1ab2bf --- /dev/null +++ b/src/detect-enip-cip-class.c @@ -0,0 +1,102 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Set up ENIP cip class keyword + */ + +#include "suricata-common.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-uint.h" +#include "rust.h" + +#include "detect-enip-cip-class.h" + +static int g_enip_cip_class_id = 0; + +static void DetectEnipCipClassFree(DetectEngineCtx *de_ctx, void *ptr) +{ + rs_detect_u32_free(ptr); +} + +/** + * \brief this function is used to parse enip_cip_class 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 cip_class options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectEnipCipClassSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) + return -1; + + DetectU32Data *du32 = DetectU32Parse(rulestr); + if (du32 == NULL) { + return -1; + } + + if (SigMatchAppendSMToList(de_ctx, s, DETECT_ENIP_CIPCLASS, (SigMatchCtx *)du32, + g_enip_cip_class_id) == NULL) { + DetectEnipCipClassFree(de_ctx, du32); + SCReturnInt(-1); + } + SCReturnInt(0); +} + +/** + * \brief This function is used to match enip cip_class type rule option on a transaction + * with those passed via enip_cip_class: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectEnipCipClassMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) + +{ + return rs_enip_tx_has_cip_class(txv, ctx); +} + +/** + * \brief Registration function for enip_cip_class: keyword + */ +void DetectEnipCipClassRegister(void) +{ + sigmatch_table[DETECT_ENIP_CIPCLASS].name = "enip.cip_class"; // rule keyword + sigmatch_table[DETECT_ENIP_CIPCLASS].desc = "rules for detecting EtherNet/IP cip_class"; + sigmatch_table[DETECT_ENIP_CIPCLASS].url = "/rules/enip-keyword.html#enip-cip-class"; + sigmatch_table[DETECT_ENIP_CIPCLASS].Match = NULL; + sigmatch_table[DETECT_ENIP_CIPCLASS].AppLayerTxMatch = DetectEnipCipClassMatch; + sigmatch_table[DETECT_ENIP_CIPCLASS].Setup = DetectEnipCipClassSetup; + sigmatch_table[DETECT_ENIP_CIPCLASS].Free = DetectEnipCipClassFree; + + DetectAppLayerInspectEngineRegister2("enip.cip_class", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, + DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister2("enip.cip_class", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectGenericList, NULL); + + g_enip_cip_class_id = DetectBufferTypeGetByName("enip.cip_class"); +} diff --git a/src/detect-enip-cip-class.h b/src/detect-enip-cip-class.h new file mode 100644 index 000000000000..d4d261b70466 --- /dev/null +++ b/src/detect-enip-cip-class.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 Philippe Antoine + */ + +#ifndef _DETECT_ENIP_CIP_CLASS_H +#define _DETECT_ENIP_CIP_CLASS_H + +void DetectEnipCipClassRegister(void); + +#endif /* _DETECT_ENIP_CIP_CLASS_H */ diff --git a/src/detect-enip-cip-extendedstatus.c b/src/detect-enip-cip-extendedstatus.c new file mode 100644 index 000000000000..d37f58937332 --- /dev/null +++ b/src/detect-enip-cip-extendedstatus.c @@ -0,0 +1,106 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Set up ENIP cip extendedstatus keyword + */ + +#include "suricata-common.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-uint.h" +#include "rust.h" + +#include "detect-enip-cip-extendedstatus.h" + +static int g_enip_cip_extendedstatus_id = 0; + +static void DetectEnipCipExtendedstatusFree(DetectEngineCtx *de_ctx, void *ptr) +{ + rs_detect_u16_free(ptr); +} + +/** + * \brief this function is used to parse enip_cip_extendedstatus 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 cip_extendedstatus options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectEnipCipExtendedstatusSetup( + DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) + return -1; + + DetectU16Data *du16 = DetectU16Parse(rulestr); + if (du16 == NULL) { + return -1; + } + + if (SigMatchAppendSMToList(de_ctx, s, DETECT_ENIP_CIPEXTENDEDSTATUS, (SigMatchCtx *)du16, + g_enip_cip_extendedstatus_id) == NULL) { + DetectEnipCipExtendedstatusFree(de_ctx, du16); + SCReturnInt(-1); + } + SCReturnInt(0); +} + +/** + * \brief This function is used to match enip cip_extendedstatus type rule option on a transaction + * with those passed via enip_cip_extendedstatus: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectEnipCipExtendedstatusMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) + +{ + return rs_enip_tx_has_cip_extendedstatus(txv, ctx); +} + +/** + * \brief Registration function for enip_cip_extendedstatus: keyword + */ +void DetectEnipCipExtendedstatusRegister(void) +{ + sigmatch_table[DETECT_ENIP_CIPEXTENDEDSTATUS].name = "enip.cip_extendedstatus"; // rule keyword + sigmatch_table[DETECT_ENIP_CIPEXTENDEDSTATUS].desc = + "rules for detecting EtherNet/IP cip_extendedstatus"; + sigmatch_table[DETECT_ENIP_CIPEXTENDEDSTATUS].url = + "/rules/enip-keyword.html#enip-cip-extendedstatus"; + sigmatch_table[DETECT_ENIP_CIPEXTENDEDSTATUS].Match = NULL; + sigmatch_table[DETECT_ENIP_CIPEXTENDEDSTATUS].AppLayerTxMatch = + DetectEnipCipExtendedstatusMatch; + sigmatch_table[DETECT_ENIP_CIPEXTENDEDSTATUS].Setup = DetectEnipCipExtendedstatusSetup; + sigmatch_table[DETECT_ENIP_CIPEXTENDEDSTATUS].Free = DetectEnipCipExtendedstatusFree; + + DetectAppLayerInspectEngineRegister2("enip.cip_extendedstatus", ALPROTO_ENIP, SIG_FLAG_TOSERVER, + 0, DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister2("enip.cip_extendedstatus", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, + 0, DetectEngineInspectGenericList, NULL); + + g_enip_cip_extendedstatus_id = DetectBufferTypeGetByName("enip.cip_extendedstatus"); +} diff --git a/src/detect-enip-cip-extendedstatus.h b/src/detect-enip-cip-extendedstatus.h new file mode 100644 index 000000000000..2bf694f7c472 --- /dev/null +++ b/src/detect-enip-cip-extendedstatus.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 Philippe Antoine + */ + +#ifndef _DETECT_ENIP_CIP_EXTENDEDSTATUS_H +#define _DETECT_ENIP_CIP_EXTENDEDSTATUS_H + +void DetectEnipCipExtendedstatusRegister(void); + +#endif /* _DETECT_ENIP_CIP_EXTENDEDSTATUS_H */ diff --git a/src/detect-enip-cip-instance.c b/src/detect-enip-cip-instance.c new file mode 100644 index 000000000000..8f16a96641af --- /dev/null +++ b/src/detect-enip-cip-instance.c @@ -0,0 +1,102 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Set up ENIP cip instance keyword + */ + +#include "suricata-common.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-uint.h" +#include "rust.h" + +#include "detect-enip-cip-instance.h" + +static int g_enip_cip_instance_id = 0; + +static void DetectEnipCipInstanceFree(DetectEngineCtx *de_ctx, void *ptr) +{ + rs_detect_u32_free(ptr); +} + +/** + * \brief this function is used to parse enip_cip_instance 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 cip_instance options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectEnipCipInstanceSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) + return -1; + + DetectU32Data *du32 = DetectU32Parse(rulestr); + if (du32 == NULL) { + return -1; + } + + if (SigMatchAppendSMToList(de_ctx, s, DETECT_ENIP_CIPINSTANCE, (SigMatchCtx *)du32, + g_enip_cip_instance_id) == NULL) { + DetectEnipCipInstanceFree(de_ctx, du32); + SCReturnInt(-1); + } + SCReturnInt(0); +} + +/** + * \brief This function is used to match enip cip_instance type rule option on a transaction + * with those passed via enip_cip_instance: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectEnipCipInstanceMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) + +{ + return rs_enip_tx_has_cip_instance(txv, ctx); +} + +/** + * \brief Registration function for enip_cip_instance: keyword + */ +void DetectEnipCipInstanceRegister(void) +{ + sigmatch_table[DETECT_ENIP_CIPINSTANCE].name = "enip.cip_instance"; // rule keyword + sigmatch_table[DETECT_ENIP_CIPINSTANCE].desc = "rules for detecting EtherNet/IP cip_instance"; + sigmatch_table[DETECT_ENIP_CIPINSTANCE].url = "/rules/enip-keyword.html#enip-cip-instance"; + sigmatch_table[DETECT_ENIP_CIPINSTANCE].Match = NULL; + sigmatch_table[DETECT_ENIP_CIPINSTANCE].AppLayerTxMatch = DetectEnipCipInstanceMatch; + sigmatch_table[DETECT_ENIP_CIPINSTANCE].Setup = DetectEnipCipInstanceSetup; + sigmatch_table[DETECT_ENIP_CIPINSTANCE].Free = DetectEnipCipInstanceFree; + + DetectAppLayerInspectEngineRegister2("enip.cip_instance", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, + DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister2("enip.cip_instance", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectGenericList, NULL); + + g_enip_cip_instance_id = DetectBufferTypeGetByName("enip.cip_instance"); +} diff --git a/src/detect-enip-cip-instance.h b/src/detect-enip-cip-instance.h new file mode 100644 index 000000000000..1f4314c86203 --- /dev/null +++ b/src/detect-enip-cip-instance.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 Philippe Antoine + */ + +#ifndef _DETECT_ENIP_CIP_INSTANCE_H +#define _DETECT_ENIP_CIP_INSTANCE_H + +void DetectEnipCipInstanceRegister(void); + +#endif /* _DETECT_ENIP_CIP_INSTANCE_H */ diff --git a/src/detect-enip-cip-status.c b/src/detect-enip-cip-status.c new file mode 100644 index 000000000000..301fba8f2bad --- /dev/null +++ b/src/detect-enip-cip-status.c @@ -0,0 +1,102 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Set up ENIP cip status keyword + */ + +#include "suricata-common.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-uint.h" +#include "rust.h" + +#include "detect-enip-cip-status.h" + +static int g_enip_cip_status_id = 0; + +static void DetectEnipCipStatusFree(DetectEngineCtx *de_ctx, void *ptr) +{ + rs_detect_u8_free(ptr); +} + +/** + * \brief this function is used to parse enip_cip_status 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 cip_status options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectEnipCipStatusSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) + return -1; + + DetectU8Data *du8 = DetectU8Parse(rulestr); + if (du8 == NULL) { + return -1; + } + + if (SigMatchAppendSMToList(de_ctx, s, DETECT_ENIP_CIPSTATUS, (SigMatchCtx *)du8, + g_enip_cip_status_id) == NULL) { + DetectEnipCipStatusFree(de_ctx, du8); + SCReturnInt(-1); + } + SCReturnInt(0); +} + +/** + * \brief This function is used to match enip cip_status type rule option on a transaction + * with those passed via enip_cip_status: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectEnipCipStatusMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) + +{ + return rs_enip_tx_has_cip_status(txv, ctx); +} + +/** + * \brief Registration function for enip_cip_status: keyword + */ +void DetectEnipCipStatusRegister(void) +{ + sigmatch_table[DETECT_ENIP_CIPSTATUS].name = "enip.cip_status"; // rule keyword + sigmatch_table[DETECT_ENIP_CIPSTATUS].desc = "rules for detecting EtherNet/IP cip_status"; + sigmatch_table[DETECT_ENIP_CIPSTATUS].url = "/rules/enip-keyword.html#enip-cip-status"; + sigmatch_table[DETECT_ENIP_CIPSTATUS].Match = NULL; + sigmatch_table[DETECT_ENIP_CIPSTATUS].AppLayerTxMatch = DetectEnipCipStatusMatch; + sigmatch_table[DETECT_ENIP_CIPSTATUS].Setup = DetectEnipCipStatusSetup; + sigmatch_table[DETECT_ENIP_CIPSTATUS].Free = DetectEnipCipStatusFree; + + DetectAppLayerInspectEngineRegister2("enip.cip_status", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, + DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister2("enip.cip_status", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectGenericList, NULL); + + g_enip_cip_status_id = DetectBufferTypeGetByName("enip.cip_status"); +} diff --git a/src/detect-enip-cip-status.h b/src/detect-enip-cip-status.h new file mode 100644 index 000000000000..d347f4b6e146 --- /dev/null +++ b/src/detect-enip-cip-status.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 Philippe Antoine + */ + +#ifndef _DETECT_ENIP_CIP_STATUS_H +#define _DETECT_ENIP_CIP_STATUS_H + +void DetectEnipCipStatusRegister(void); + +#endif /* _DETECT_ENIP_CIP_STATUS_H */ diff --git a/src/detect-enip-command.c b/src/detect-enip-command.c new file mode 100644 index 000000000000..a4e3a01407e4 --- /dev/null +++ b/src/detect-enip-command.c @@ -0,0 +1,114 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Set up ENIP Command rule parsing and entry point for matching + */ + +#include "suricata-common.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "rust.h" + +#include "detect-enip-command.h" + +static int g_enip_buffer_id = 0; + +/** + * \brief this function will free memory associated + * + * \param ptr pointer to u16 + */ +static void DetectEnipCommandFree(DetectEngineCtx *de_ctx, void *ptr) +{ + SCFree(ptr); +} + +/** + * \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, const char *rulestr) +{ + uint16_t cmdparsed; + + if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) + return -1; + + if (!rs_enip_parse_command(rulestr, &cmdparsed)) { + SCLogWarning("rule %u has invalid value for enip_command %s", s->id, rulestr); + return -1; + } + + uint16_t *enipcmdd = SCCalloc(1, sizeof(uint16_t)); + if (enipcmdd == NULL) + return -1; + *enipcmdd = cmdparsed; + + if (SigMatchAppendSMToList( + de_ctx, s, DETECT_ENIPCOMMAND, (SigMatchCtx *)enipcmdd, g_enip_buffer_id) == NULL) { + DetectEnipCommandFree(de_ctx, enipcmdd); + SCReturnInt(-1); + } + SCReturnInt(0); +} + +/** + * \brief This function is used to match enip command type rule option on a transaction with those + * passed via enip_command: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectEnipCommandMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) + +{ + uint16_t *cmd = (uint16_t *)ctx; + return rs_enip_tx_is_cmd(txv, flags, *cmd); +} + +/** + * \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].url = "/rules/enip-keyword.html#enip_command"; + sigmatch_table[DETECT_ENIPCOMMAND].Match = NULL; + sigmatch_table[DETECT_ENIPCOMMAND].AppLayerTxMatch = DetectEnipCommandMatch; + sigmatch_table[DETECT_ENIPCOMMAND].Setup = DetectEnipCommandSetup; + sigmatch_table[DETECT_ENIPCOMMAND].Free = DetectEnipCommandFree; + + DetectAppLayerInspectEngineRegister2( + "enip", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister2( + "enip", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectGenericList, NULL); + + g_enip_buffer_id = DetectBufferTypeGetByName("enip"); +} diff --git a/src/detect-enip-command.h b/src/detect-enip-command.h new file mode 100644 index 000000000000..43f450f35ba3 --- /dev/null +++ b/src/detect-enip-command.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 Philippe Antoine + */ + +#ifndef _DETECT_ENIP_COMMAND_H +#define _DETECT_ENIP_COMMAND_H + +void DetectEnipCommandRegister(void); + +#endif /* _DETECT_ENIP_COMMAND_H */ diff --git a/src/detect-enip-device-type.c b/src/detect-enip-device-type.c new file mode 100644 index 000000000000..6cc328525793 --- /dev/null +++ b/src/detect-enip-device-type.c @@ -0,0 +1,106 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Set up ENIP device type keyword + */ + +#include "suricata-common.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-uint.h" +#include "rust.h" + +#include "detect-enip-device-type.h" + +static int g_enip_device_type_id = 0; + +static void DetectEnipDeviceTypeFree(DetectEngineCtx *de_ctx, void *ptr) +{ + rs_detect_u16_free(ptr); +} + +/** + * \brief this function is used to parse enip_device_type 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 device_type options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectEnipDeviceTypeSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) + return -1; + + DetectU16Data *du16 = DetectU16Parse(rulestr); + if (du16 == NULL) { + return -1; + } + + if (SigMatchAppendSMToList(de_ctx, s, DETECT_ENIP_DEVICETYPE, (SigMatchCtx *)du16, + g_enip_device_type_id) == NULL) { + DetectEnipDeviceTypeFree(de_ctx, du16); + SCReturnInt(-1); + } + SCReturnInt(0); +} + +/** + * \brief This function is used to match enip device_type type rule option on a transaction with + * those passed via enip_device_type: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectEnipDeviceTypeMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) + +{ + uint16_t value; + if (!rs_enip_get_device_type(txv, &value)) + SCReturnInt(0); + const DetectU16Data *du16 = (const DetectU16Data *)ctx; + return DetectU16Match(value, du16); +} + +/** + * \brief Registration function for enip_device_type: keyword + */ +void DetectEnipDeviceTypeRegister(void) +{ + sigmatch_table[DETECT_ENIP_DEVICETYPE].name = "enip.device_type"; // rule keyword + sigmatch_table[DETECT_ENIP_DEVICETYPE].desc = "rules for detecting EtherNet/IP device_type"; + sigmatch_table[DETECT_ENIP_DEVICETYPE].url = "/rules/enip-keyword.html#enip-device-type"; + sigmatch_table[DETECT_ENIP_DEVICETYPE].Match = NULL; + sigmatch_table[DETECT_ENIP_DEVICETYPE].AppLayerTxMatch = DetectEnipDeviceTypeMatch; + sigmatch_table[DETECT_ENIP_DEVICETYPE].Setup = DetectEnipDeviceTypeSetup; + sigmatch_table[DETECT_ENIP_DEVICETYPE].Free = DetectEnipDeviceTypeFree; + + DetectAppLayerInspectEngineRegister2("enip.device_type", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, + DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister2("enip.device_type", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectGenericList, NULL); + + g_enip_device_type_id = DetectBufferTypeGetByName("enip.device_type"); +} diff --git a/src/detect-enip-device-type.h b/src/detect-enip-device-type.h new file mode 100644 index 000000000000..2369afded6d8 --- /dev/null +++ b/src/detect-enip-device-type.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 Philippe Antoine + */ + +#ifndef _DETECT_ENIP_DEVICE_TYPE_H +#define _DETECT_ENIP_DEVICE_TYPE_H + +void DetectEnipDeviceTypeRegister(void); + +#endif /* _DETECT_ENIP_DEVICE_TYPE_H */ diff --git a/src/detect-enip-identity-status.c b/src/detect-enip-identity-status.c new file mode 100644 index 000000000000..d1cc6ae7c530 --- /dev/null +++ b/src/detect-enip-identity-status.c @@ -0,0 +1,108 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Set up ENIP identity status keyword + */ + +#include "suricata-common.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-uint.h" +#include "rust.h" + +#include "detect-enip-identity-status.h" + +static int g_enip_identity_status_id = 0; + +static void DetectEnipIdentityStatusFree(DetectEngineCtx *de_ctx, void *ptr) +{ + rs_detect_u16_free(ptr); +} + +/** + * \brief this function is used to parse enip_identity_status 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 identity_status options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectEnipIdentityStatusSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) + return -1; + + DetectU16Data *du16 = DetectU16Parse(rulestr); + if (du16 == NULL) { + return -1; + } + + if (SigMatchAppendSMToList(de_ctx, s, DETECT_ENIP_IDENTITYSTATUS, (SigMatchCtx *)du16, + g_enip_identity_status_id) == NULL) { + DetectEnipIdentityStatusFree(de_ctx, du16); + SCReturnInt(-1); + } + SCReturnInt(0); +} + +/** + * \brief This function is used to match enip identity_status type rule option on a transaction with + * those passed via enip_identity_status: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectEnipIdentityStatusMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) + +{ + uint16_t value; + if (!rs_enip_get_identity_status(txv, &value)) + SCReturnInt(0); + const DetectU16Data *du16 = (const DetectU16Data *)ctx; + return DetectU16Match(value, du16); +} + +/** + * \brief Registration function for enip_identity_status: keyword + */ +void DetectEnipIdentityStatusRegister(void) +{ + sigmatch_table[DETECT_ENIP_IDENTITYSTATUS].name = "enip.identity_status"; // rule keyword + sigmatch_table[DETECT_ENIP_IDENTITYSTATUS].desc = + "rules for detecting EtherNet/IP identity_status"; + sigmatch_table[DETECT_ENIP_IDENTITYSTATUS].url = + "/rules/enip-keyword.html#enip-identity-status"; + sigmatch_table[DETECT_ENIP_IDENTITYSTATUS].Match = NULL; + sigmatch_table[DETECT_ENIP_IDENTITYSTATUS].AppLayerTxMatch = DetectEnipIdentityStatusMatch; + sigmatch_table[DETECT_ENIP_IDENTITYSTATUS].Setup = DetectEnipIdentityStatusSetup; + sigmatch_table[DETECT_ENIP_IDENTITYSTATUS].Free = DetectEnipIdentityStatusFree; + + DetectAppLayerInspectEngineRegister2("enip.identity_status", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, + DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister2("enip.identity_status", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectGenericList, NULL); + + g_enip_identity_status_id = DetectBufferTypeGetByName("enip.identity_status"); +} diff --git a/src/detect-enip-identity-status.h b/src/detect-enip-identity-status.h new file mode 100644 index 000000000000..1d8b29bb5986 --- /dev/null +++ b/src/detect-enip-identity-status.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 Philippe Antoine + */ + +#ifndef _DETECT_ENIP_IDENTITY_STATUS_H +#define _DETECT_ENIP_IDENTITY_STATUS_H + +void DetectEnipIdentityStatusRegister(void); + +#endif /* _DETECT_ENIP_IDENTITY_STATUS_H */ diff --git a/src/detect-enip-product-code.c b/src/detect-enip-product-code.c new file mode 100644 index 000000000000..558eda4c5ed4 --- /dev/null +++ b/src/detect-enip-product-code.c @@ -0,0 +1,106 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Set up ENIP product code keyword + */ + +#include "suricata-common.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-uint.h" +#include "rust.h" + +#include "detect-enip-product-code.h" + +static int g_enip_product_code_id = 0; + +static void DetectEnipProductCodeFree(DetectEngineCtx *de_ctx, void *ptr) +{ + rs_detect_u16_free(ptr); +} + +/** + * \brief this function is used to parse enip_product_code 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 product_code options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectEnipProductCodeSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) + return -1; + + DetectU16Data *du16 = DetectU16Parse(rulestr); + if (du16 == NULL) { + return -1; + } + + if (SigMatchAppendSMToList(de_ctx, s, DETECT_ENIP_PRODUCTCODE, (SigMatchCtx *)du16, + g_enip_product_code_id) == NULL) { + DetectEnipProductCodeFree(de_ctx, du16); + SCReturnInt(-1); + } + SCReturnInt(0); +} + +/** + * \brief This function is used to match enip product_code type rule option on a transaction with + * those passed via enip_product_code: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectEnipProductCodeMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) + +{ + uint16_t value; + if (!rs_enip_get_product_code(txv, &value)) + SCReturnInt(0); + const DetectU16Data *du16 = (const DetectU16Data *)ctx; + return DetectU16Match(value, du16); +} + +/** + * \brief Registration function for enip_product_code: keyword + */ +void DetectEnipProductCodeRegister(void) +{ + sigmatch_table[DETECT_ENIP_PRODUCTCODE].name = "enip.product_code"; // rule keyword + sigmatch_table[DETECT_ENIP_PRODUCTCODE].desc = "rules for detecting EtherNet/IP product_code"; + sigmatch_table[DETECT_ENIP_PRODUCTCODE].url = "/rules/enip-keyword.html#enip-product-code"; + sigmatch_table[DETECT_ENIP_PRODUCTCODE].Match = NULL; + sigmatch_table[DETECT_ENIP_PRODUCTCODE].AppLayerTxMatch = DetectEnipProductCodeMatch; + sigmatch_table[DETECT_ENIP_PRODUCTCODE].Setup = DetectEnipProductCodeSetup; + sigmatch_table[DETECT_ENIP_PRODUCTCODE].Free = DetectEnipProductCodeFree; + + DetectAppLayerInspectEngineRegister2("enip.product_code", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, + DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister2("enip.product_code", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectGenericList, NULL); + + g_enip_product_code_id = DetectBufferTypeGetByName("enip.product_code"); +} diff --git a/src/detect-enip-product-code.h b/src/detect-enip-product-code.h new file mode 100644 index 000000000000..80ee1ed2d27a --- /dev/null +++ b/src/detect-enip-product-code.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 Philippe Antoine + */ + +#ifndef _DETECT_ENIP_PRODUCT_CODE_H +#define _DETECT_ENIP_PRODUCT_CODE_H + +void DetectEnipProductCodeRegister(void); + +#endif /* _DETECT_ENIP_PRODUCT_CODE_H */ diff --git a/src/detect-enip-product-name.c b/src/detect-enip-product-name.c new file mode 100644 index 000000000000..3c741f490bbe --- /dev/null +++ b/src/detect-enip-product-name.c @@ -0,0 +1,97 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Set up ENIP Product name keyword + */ + +#include "suricata-common.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-prefilter.h" +#include "rust.h" + +#include "detect-enip-product-name.h" + +static int g_enip_product_name_id = 0; + +/** + * \brief this function is used to setup sticky buffer inspection for product_name + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param rulestr pointer to the user provided enip product name options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectEnipProductNameSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) +{ + if (DetectBufferSetActiveList(de_ctx, s, g_enip_product_name_id) < 0) + return -1; + + if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) + return -1; + + return 0; +} + +static InspectionBuffer *GetData(DetectEngineThreadCtx *det_ctx, + const DetectEngineTransforms *transforms, Flow *_f, const uint8_t _flow_flags, void *txv, + const int list_id) +{ + InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id); + if (buffer->inspect == NULL) { + const uint8_t *b = NULL; + uint32_t b_len = 0; + + if (rs_enip_tx_get_product_name(txv, &b, &b_len) != 1) + return NULL; + if (b == NULL || b_len == 0) + return NULL; + + InspectionBufferSetup(det_ctx, list_id, buffer, b, b_len); + InspectionBufferApplyTransforms(buffer, transforms); + } + return buffer; +} + +/** + * \brief Registration function for enip.product_name: keyword + */ +void DetectEnipProductNameRegister(void) +{ + sigmatch_table[DETECT_ENIP_PRODUCTNAME].name = "enip.product_name"; // rule keyword + sigmatch_table[DETECT_ENIP_PRODUCTNAME].desc = + "sticky buffer to match EtherNet/IP product name"; + sigmatch_table[DETECT_ENIP_PRODUCTNAME].url = "/rules/enip-keyword.html#enip-product-name"; + sigmatch_table[DETECT_ENIP_PRODUCTNAME].Setup = DetectEnipProductNameSetup; + sigmatch_table[DETECT_ENIP_PRODUCTNAME].flags |= SIGMATCH_NOOPT; + + DetectAppLayerInspectEngineRegister2("enip.product_name", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectBufferGeneric, GetData); + + DetectAppLayerMpmRegister2("enip.product_name", SIG_FLAG_TOCLIENT, 2, + PrefilterGenericMpmRegister, GetData, ALPROTO_ENIP, 1); + + DetectBufferTypeSetDescriptionByName("enip.product_name", "ENIP product name"); + g_enip_product_name_id = DetectBufferTypeGetByName("enip.product_name"); +} diff --git a/src/detect-enip-product-name.h b/src/detect-enip-product-name.h new file mode 100644 index 000000000000..e1ff31c66e2b --- /dev/null +++ b/src/detect-enip-product-name.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 Philippe Antoine + */ + +#ifndef _DETECT_ENIP_PRODUCT_NAME_H +#define _DETECT_ENIP_PRODUCT_NAME_H + +void DetectEnipProductNameRegister(void); + +#endif /* _DETECT_ENIP_PRODUCT_NAME_H */ diff --git a/src/detect-enip-protocol-version.c b/src/detect-enip-protocol-version.c new file mode 100644 index 000000000000..7b65677871b9 --- /dev/null +++ b/src/detect-enip-protocol-version.c @@ -0,0 +1,109 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Set up ENIP protocol version keyword + */ + +#include "suricata-common.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-uint.h" +#include "rust.h" + +#include "detect-enip-protocol-version.h" + +static int g_enip_protocol_version_id = 0; + +static void DetectEnipProtocolVersionFree(DetectEngineCtx *de_ctx, void *ptr) +{ + rs_detect_u16_free(ptr); +} + +/** + * \brief this function is used to parse enip_protocol_version 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 protocol_version options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectEnipProtocolVersionSetup( + DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) + return -1; + + DetectU16Data *du16 = DetectU16Parse(rulestr); + if (du16 == NULL) { + return -1; + } + + if (SigMatchAppendSMToList(de_ctx, s, DETECT_ENIP_PROTOCOLVERSION, (SigMatchCtx *)du16, + g_enip_protocol_version_id) == NULL) { + DetectEnipProtocolVersionFree(de_ctx, du16); + SCReturnInt(-1); + } + SCReturnInt(0); +} + +/** + * \brief This function is used to match enip protocol_version type rule option on a transaction + * with those passed via enip_protocol_version: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectEnipProtocolVersionMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) + +{ + uint16_t value; + if (!rs_enip_get_protocol_version(txv, flags, &value)) + SCReturnInt(0); + const DetectU16Data *du16 = (const DetectU16Data *)ctx; + return DetectU16Match(value, du16); +} + +/** + * \brief Registration function for enip_protocol_version: keyword + */ +void DetectEnipProtocolVersionRegister(void) +{ + sigmatch_table[DETECT_ENIP_PROTOCOLVERSION].name = "enip.protocol_version"; // rule keyword + sigmatch_table[DETECT_ENIP_PROTOCOLVERSION].desc = + "rules for detecting EtherNet/IP protocol_version"; + sigmatch_table[DETECT_ENIP_PROTOCOLVERSION].url = + "/rules/enip-keyword.html#enip-protocol-version"; + sigmatch_table[DETECT_ENIP_PROTOCOLVERSION].Match = NULL; + sigmatch_table[DETECT_ENIP_PROTOCOLVERSION].AppLayerTxMatch = DetectEnipProtocolVersionMatch; + sigmatch_table[DETECT_ENIP_PROTOCOLVERSION].Setup = DetectEnipProtocolVersionSetup; + sigmatch_table[DETECT_ENIP_PROTOCOLVERSION].Free = DetectEnipProtocolVersionFree; + + DetectAppLayerInspectEngineRegister2("enip.protocol_version", ALPROTO_ENIP, SIG_FLAG_TOSERVER, + 0, DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister2("enip.protocol_version", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, + 0, DetectEngineInspectGenericList, NULL); + + g_enip_protocol_version_id = DetectBufferTypeGetByName("enip.protocol_version"); +} diff --git a/src/detect-enip-protocol-version.h b/src/detect-enip-protocol-version.h new file mode 100644 index 000000000000..7a33fde9fcd7 --- /dev/null +++ b/src/detect-enip-protocol-version.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 Philippe Antoine + */ + +#ifndef _DETECT_ENIP_PROTOCOL_VERSION_H +#define _DETECT_ENIP_PROTOCOL_VERSION_H + +void DetectEnipProtocolVersionRegister(void); + +#endif /* _DETECT_ENIP_PROTOCOL_VERSION_H */ diff --git a/src/detect-enip-revision.c b/src/detect-enip-revision.c new file mode 100644 index 000000000000..601f32ceae3a --- /dev/null +++ b/src/detect-enip-revision.c @@ -0,0 +1,106 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Set up ENIP revision keyword + */ + +#include "suricata-common.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-uint.h" +#include "rust.h" + +#include "detect-enip-revision.h" + +static int g_enip_revision_id = 0; + +static void DetectEnipRevisionFree(DetectEngineCtx *de_ctx, void *ptr) +{ + rs_detect_u16_free(ptr); +} + +/** + * \brief this function is used to parse enip_revision 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 revision options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectEnipRevisionSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) + return -1; + + DetectU16Data *du16 = DetectU16Parse(rulestr); + if (du16 == NULL) { + return -1; + } + + if (SigMatchAppendSMToList( + de_ctx, s, DETECT_ENIP_REVISION, (SigMatchCtx *)du16, g_enip_revision_id) == NULL) { + DetectEnipRevisionFree(de_ctx, du16); + SCReturnInt(-1); + } + SCReturnInt(0); +} + +/** + * \brief This function is used to match enip revision type rule option on a transaction with those + * passed via enip_revision: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectEnipRevisionMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) + +{ + uint16_t value; + if (!rs_enip_get_revision(txv, &value)) + SCReturnInt(0); + const DetectU16Data *du16 = (const DetectU16Data *)ctx; + return DetectU16Match(value, du16); +} + +/** + * \brief Registration function for enip_revision: keyword + */ +void DetectEnipRevisionRegister(void) +{ + sigmatch_table[DETECT_ENIP_REVISION].name = "enip.revision"; // rule keyword + sigmatch_table[DETECT_ENIP_REVISION].desc = "rules for detecting EtherNet/IP revision"; + sigmatch_table[DETECT_ENIP_REVISION].url = "/rules/enip-keyword.html#enip-revision"; + sigmatch_table[DETECT_ENIP_REVISION].Match = NULL; + sigmatch_table[DETECT_ENIP_REVISION].AppLayerTxMatch = DetectEnipRevisionMatch; + sigmatch_table[DETECT_ENIP_REVISION].Setup = DetectEnipRevisionSetup; + sigmatch_table[DETECT_ENIP_REVISION].Free = DetectEnipRevisionFree; + + DetectAppLayerInspectEngineRegister2("enip.revision", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, + DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister2("enip.revision", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectGenericList, NULL); + + g_enip_revision_id = DetectBufferTypeGetByName("enip.revision"); +} diff --git a/src/detect-enip-revision.h b/src/detect-enip-revision.h new file mode 100644 index 000000000000..0cb7bf27c583 --- /dev/null +++ b/src/detect-enip-revision.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 Philippe Antoine + */ + +#ifndef _DETECT_ENIP_REVISION_H +#define _DETECT_ENIP_REVISION_H + +void DetectEnipRevisionRegister(void); + +#endif /* _DETECT_ENIP_REVISION_H */ diff --git a/src/detect-enip-serial.c b/src/detect-enip-serial.c new file mode 100644 index 000000000000..b17c83dfad5a --- /dev/null +++ b/src/detect-enip-serial.c @@ -0,0 +1,106 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Set up ENIP serial keyword + */ + +#include "suricata-common.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-uint.h" +#include "rust.h" + +#include "detect-enip-serial.h" + +static int g_enip_serial_id = 0; + +static void DetectEnipSerialFree(DetectEngineCtx *de_ctx, void *ptr) +{ + rs_detect_u32_free(ptr); +} + +/** + * \brief this function is used to parse enip_serial 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 serial options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectEnipSerialSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) + return -1; + + DetectU32Data *du32 = DetectU32Parse(rulestr); + if (du32 == NULL) { + return -1; + } + + if (SigMatchAppendSMToList( + de_ctx, s, DETECT_ENIP_SERIAL, (SigMatchCtx *)du32, g_enip_serial_id) == NULL) { + DetectEnipSerialFree(de_ctx, du32); + SCReturnInt(-1); + } + SCReturnInt(0); +} + +/** + * \brief This function is used to match enip serial type rule option on a transaction with those + * passed via enip_serial: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectEnipSerialMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) + +{ + uint32_t value; + if (!rs_enip_get_serial(txv, &value)) + SCReturnInt(0); + const DetectU32Data *du32 = (const DetectU32Data *)ctx; + return DetectU32Match(value, du32); +} + +/** + * \brief Registration function for enip_serial: keyword + */ +void DetectEnipSerialRegister(void) +{ + sigmatch_table[DETECT_ENIP_SERIAL].name = "enip.serial"; // rule keyword + sigmatch_table[DETECT_ENIP_SERIAL].desc = "rules for detecting EtherNet/IP serial"; + sigmatch_table[DETECT_ENIP_SERIAL].url = "/rules/enip-keyword.html#enip-serial"; + sigmatch_table[DETECT_ENIP_SERIAL].Match = NULL; + sigmatch_table[DETECT_ENIP_SERIAL].AppLayerTxMatch = DetectEnipSerialMatch; + sigmatch_table[DETECT_ENIP_SERIAL].Setup = DetectEnipSerialSetup; + sigmatch_table[DETECT_ENIP_SERIAL].Free = DetectEnipSerialFree; + + DetectAppLayerInspectEngineRegister2("enip.serial", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, + DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister2("enip.serial", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectGenericList, NULL); + + g_enip_serial_id = DetectBufferTypeGetByName("enip.serial"); +} diff --git a/src/detect-enip-serial.h b/src/detect-enip-serial.h new file mode 100644 index 000000000000..37647271945b --- /dev/null +++ b/src/detect-enip-serial.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 Philippe Antoine + */ + +#ifndef _DETECT_ENIP_SERIAL_H +#define _DETECT_ENIP_SERIAL_H + +void DetectEnipSerialRegister(void); + +#endif /* _DETECT_ENIP_SERIAL_H */ diff --git a/src/detect-enip-service-name.c b/src/detect-enip-service-name.c new file mode 100644 index 000000000000..7a47d174fb63 --- /dev/null +++ b/src/detect-enip-service-name.c @@ -0,0 +1,95 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Set up ENIP service name keyword + */ + +#include "suricata-common.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-prefilter.h" +#include "rust.h" + +#include "detect-enip-service-name.h" + +static int g_enip_service_name_id = 0; + +/** + * \brief this function is used to parse enip_service_name 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 service_name options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectEnipServiceNameSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) +{ + if (DetectBufferSetActiveList(de_ctx, s, g_enip_service_name_id) < 0) + return -1; + + if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) + return -1; + + return 0; +} + +static InspectionBuffer *GetData(DetectEngineThreadCtx *det_ctx, + const DetectEngineTransforms *transforms, Flow *_f, const uint8_t _flow_flags, void *txv, + const int list_id) +{ + InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id); + if (buffer->inspect == NULL) { + const uint8_t *b = NULL; + uint32_t b_len = 0; + + if (rs_enip_tx_get_service_name(txv, &b, &b_len) != 1) + return NULL; + if (b == NULL || b_len == 0) + return NULL; + + InspectionBufferSetup(det_ctx, list_id, buffer, b, b_len); + InspectionBufferApplyTransforms(buffer, transforms); + } + return buffer; +} + +/** + * \brief Registration function for enip_service_name: keyword + */ +void DetectEnipServiceNameRegister(void) +{ + sigmatch_table[DETECT_ENIP_SERVICENAME].name = "enip.service_name"; // rule keyword + sigmatch_table[DETECT_ENIP_SERVICENAME].desc = "rules for detecting EtherNet/IP service_name"; + sigmatch_table[DETECT_ENIP_SERVICENAME].url = "/rules/enip-keyword.html#enip-service-name"; + sigmatch_table[DETECT_ENIP_SERVICENAME].Setup = DetectEnipServiceNameSetup; + sigmatch_table[DETECT_ENIP_PRODUCTNAME].flags |= SIGMATCH_NOOPT; + + DetectAppLayerInspectEngineRegister2("enip.service_name", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectBufferGeneric, GetData); + DetectAppLayerMpmRegister2("enip.service_name", SIG_FLAG_TOCLIENT, 2, + PrefilterGenericMpmRegister, GetData, ALPROTO_ENIP, 1); + + DetectBufferTypeSetDescriptionByName("enip.service_name", "ENIP service name"); + g_enip_service_name_id = DetectBufferTypeGetByName("enip.service_name"); +} diff --git a/src/detect-enip-service-name.h b/src/detect-enip-service-name.h new file mode 100644 index 000000000000..638f5b82baa0 --- /dev/null +++ b/src/detect-enip-service-name.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 Philippe Antoine + */ + +#ifndef _DETECT_ENIP_SERVICE_NAME_H +#define _DETECT_ENIP_SERVICE_NAME_H + +void DetectEnipServiceNameRegister(void); + +#endif /* _DETECT_ENIP_SERVICE_NAME_H */ diff --git a/src/detect-enip-state.c b/src/detect-enip-state.c new file mode 100644 index 000000000000..14cac85f0bcd --- /dev/null +++ b/src/detect-enip-state.c @@ -0,0 +1,106 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Set up ENIP state keyword + */ + +#include "suricata-common.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-uint.h" +#include "rust.h" + +#include "detect-enip-state.h" + +static int g_enip_state_id = 0; + +static void DetectEnipStateFree(DetectEngineCtx *de_ctx, void *ptr) +{ + rs_detect_u8_free(ptr); +} + +/** + * \brief this function is used to parse enip_state 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 state options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectEnipStateSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) + return -1; + + DetectU8Data *du8 = DetectU8Parse(rulestr); + if (du8 == NULL) { + return -1; + } + + if (SigMatchAppendSMToList(de_ctx, s, DETECT_ENIP_STATE, (SigMatchCtx *)du8, g_enip_state_id) == + NULL) { + DetectEnipStateFree(de_ctx, du8); + SCReturnInt(-1); + } + SCReturnInt(0); +} + +/** + * \brief This function is used to match enip state type rule option on a transaction with those + * passed via enip_state: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectEnipStateMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, void *state, + void *txv, const Signature *s, const SigMatchCtx *ctx) + +{ + uint8_t value; + if (!rs_enip_get_state(txv, &value)) + SCReturnInt(0); + const DetectU8Data *du8 = (const DetectU8Data *)ctx; + return DetectU8Match(value, du8); +} + +/** + * \brief Registration function for enip_state: keyword + */ +void DetectEnipStateRegister(void) +{ + sigmatch_table[DETECT_ENIP_STATE].name = "enip.state"; // rule keyword + sigmatch_table[DETECT_ENIP_STATE].desc = "rules for detecting EtherNet/IP state"; + sigmatch_table[DETECT_ENIP_STATE].url = "/rules/enip-keyword.html#enip-state"; + sigmatch_table[DETECT_ENIP_STATE].Match = NULL; + sigmatch_table[DETECT_ENIP_STATE].AppLayerTxMatch = DetectEnipStateMatch; + sigmatch_table[DETECT_ENIP_STATE].Setup = DetectEnipStateSetup; + sigmatch_table[DETECT_ENIP_STATE].Free = DetectEnipStateFree; + + DetectAppLayerInspectEngineRegister2( + "enip.state", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister2( + "enip.state", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectGenericList, NULL); + + g_enip_state_id = DetectBufferTypeGetByName("enip.state"); +} diff --git a/src/detect-enip-state.h b/src/detect-enip-state.h new file mode 100644 index 000000000000..0ffac0c6f414 --- /dev/null +++ b/src/detect-enip-state.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 Philippe Antoine + */ + +#ifndef _DETECT_ENIP_STATE_H +#define _DETECT_ENIP_STATE_H + +void DetectEnipStateRegister(void); + +#endif /* _DETECT_ENIP_STATE_H */ diff --git a/src/detect-enip-status.c b/src/detect-enip-status.c new file mode 100644 index 000000000000..0c8e6066410e --- /dev/null +++ b/src/detect-enip-status.c @@ -0,0 +1,111 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Set up ENIP Status rule parsing and entry point for matching + */ + +#include "suricata-common.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-uint.h" +#include "rust.h" + +#include "detect-enip-status.h" + +static int g_enip_status_id = 0; + +/** + * \brief this function will free memory associated + * + * \param ptr pointer to u16 + */ +static void DetectEnipStatusFree(DetectEngineCtx *de_ctx, void *ptr) +{ + rs_detect_u32_free(ptr); +} + +/** + * \brief this function is used by enipcmdd to parse enip_status 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 status options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectEnipStatusSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) + return -1; + + DetectU32Data *du32 = DetectU32Parse(rulestr); + if (du32 == NULL) { + return -1; + } + + if (SigMatchAppendSMToList( + de_ctx, s, DETECT_ENIPSTATUS, (SigMatchCtx *)du32, g_enip_status_id) == NULL) { + DetectEnipStatusFree(de_ctx, du32); + SCReturnInt(-1); + } + SCReturnInt(0); +} + +/** + * \brief This function is used to match enip status type rule option on a transaction with those + * passed via enip_status: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectEnipStatusMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) + +{ + uint32_t status; + if (!rs_enip_get_status(txv, flags, &status)) + SCReturnInt(0); + const DetectU32Data *du32 = (const DetectU32Data *)ctx; + return DetectU32Match(status, du32); +} + +/** + * \brief Registration function for enip_status: keyword + */ +void DetectEnipStatusRegister(void) +{ + sigmatch_table[DETECT_ENIPSTATUS].name = "enip.status"; // rule keyword + sigmatch_table[DETECT_ENIPSTATUS].desc = "rules for detecting EtherNet/IP status"; + sigmatch_table[DETECT_ENIPSTATUS].url = "/rules/enip-keyword.html#enip-status"; + sigmatch_table[DETECT_ENIPSTATUS].Match = NULL; + sigmatch_table[DETECT_ENIPSTATUS].AppLayerTxMatch = DetectEnipStatusMatch; + sigmatch_table[DETECT_ENIPSTATUS].Setup = DetectEnipStatusSetup; + sigmatch_table[DETECT_ENIPSTATUS].Free = DetectEnipStatusFree; + + DetectAppLayerInspectEngineRegister2("enip.status", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, + DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister2("enip.status", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectGenericList, NULL); + + g_enip_status_id = DetectBufferTypeGetByName("enip.status"); +} diff --git a/src/detect-enip-status.h b/src/detect-enip-status.h new file mode 100644 index 000000000000..bfa2ef2d8f27 --- /dev/null +++ b/src/detect-enip-status.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 Philippe Antoine + */ + +#ifndef _DETECT_ENIP_STATUS_H +#define _DETECT_ENIP_STATUS_H + +void DetectEnipStatusRegister(void); + +#endif /* _DETECT_ENIP_STATUS_H */ diff --git a/src/detect-enip-vendor-id.c b/src/detect-enip-vendor-id.c new file mode 100644 index 000000000000..8586b893d6db --- /dev/null +++ b/src/detect-enip-vendor-id.c @@ -0,0 +1,106 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Set up ENIP vendor id keyword + */ + +#include "suricata-common.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-uint.h" +#include "rust.h" + +#include "detect-enip-vendor-id.h" + +static int g_enip_vendor_id_id = 0; + +static void DetectEnipVendorIdFree(DetectEngineCtx *de_ctx, void *ptr) +{ + rs_detect_u16_free(ptr); +} + +/** + * \brief this function is used to parse enip_vendor_id 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 vendor_id options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectEnipVendorIdSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rulestr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) + return -1; + + DetectU16Data *du16 = DetectU16Parse(rulestr); + if (du16 == NULL) { + return -1; + } + + if (SigMatchAppendSMToList(de_ctx, s, DETECT_ENIP_VENDORID, (SigMatchCtx *)du16, + g_enip_vendor_id_id) == NULL) { + DetectEnipVendorIdFree(de_ctx, du16); + SCReturnInt(-1); + } + SCReturnInt(0); +} + +/** + * \brief This function is used to match enip vendor_id type rule option on a transaction with those + * passed via enip_vendor_id: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectEnipVendorIdMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) + +{ + uint16_t value; + if (!rs_enip_get_vendor_id(txv, &value)) + SCReturnInt(0); + const DetectU16Data *du16 = (const DetectU16Data *)ctx; + return DetectU16Match(value, du16); +} + +/** + * \brief Registration function for enip_vendor_id: keyword + */ +void DetectEnipVendorIdRegister(void) +{ + sigmatch_table[DETECT_ENIP_VENDORID].name = "enip.vendor_id"; // rule keyword + sigmatch_table[DETECT_ENIP_VENDORID].desc = "rules for detecting EtherNet/IP vendor_id"; + sigmatch_table[DETECT_ENIP_VENDORID].url = "/rules/enip-keyword.html#enip-vendor-id"; + sigmatch_table[DETECT_ENIP_VENDORID].Match = NULL; + sigmatch_table[DETECT_ENIP_VENDORID].AppLayerTxMatch = DetectEnipVendorIdMatch; + sigmatch_table[DETECT_ENIP_VENDORID].Setup = DetectEnipVendorIdSetup; + sigmatch_table[DETECT_ENIP_VENDORID].Free = DetectEnipVendorIdFree; + + DetectAppLayerInspectEngineRegister2("enip.vendor_id", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, + DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister2("enip.vendor_id", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectGenericList, NULL); + + g_enip_vendor_id_id = DetectBufferTypeGetByName("enip.vendor_id"); +} diff --git a/src/detect-enip-vendor-id.h b/src/detect-enip-vendor-id.h new file mode 100644 index 000000000000..1157fbe125ae --- /dev/null +++ b/src/detect-enip-vendor-id.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 Philippe Antoine + */ + +#ifndef _DETECT_ENIP_VENDOR_ID_H +#define _DETECT_ENIP_VENDOR_ID_H + +void DetectEnipVendorIdRegister(void); + +#endif /* _DETECT_ENIP_VENDOR_ID_H */ diff --git a/src/output-json-enip.c b/src/output-json-enip.c new file mode 100644 index 000000000000..77106a079982 --- /dev/null +++ b/src/output-json-enip.c @@ -0,0 +1,161 @@ +/* Copyright (C) 2023 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 Philippe Antoine + * + * Implement JSON/eve logging app-layer Enip. + */ + +#include "suricata-common.h" +#include "detect.h" +#include "pkt-var.h" +#include "conf.h" + +#include "threads.h" +#include "threadvars.h" +#include "tm-threads.h" + +#include "util-unittest.h" +#include "util-buffer.h" +#include "util-debug.h" +#include "util-byte.h" + +#include "output.h" +#include "output-json.h" + +#include "app-layer.h" +#include "app-layer-parser.h" + +#include "output-json-enip.h" +#include "rust.h" + +typedef struct LogEnipFileCtx_ { + uint32_t flags; + OutputJsonCtx *eve_ctx; +} LogEnipFileCtx; + +typedef struct LogEnipLogThread_ { + LogEnipFileCtx *eniplog_ctx; + OutputJsonThreadCtx *ctx; +} LogEnipLogThread; + +static int JsonEnipLogger(ThreadVars *tv, void *thread_data, const Packet *p, Flow *f, void *state, + void *tx, uint64_t tx_id) +{ + LogEnipLogThread *thread = thread_data; + + JsonBuilder *js = + CreateEveHeader(p, LOG_DIR_PACKET, "enip", NULL, thread->eniplog_ctx->eve_ctx); + if (unlikely(js == NULL)) { + return TM_ECODE_FAILED; + } + + if (!rs_enip_logger_log(tx, js)) { + goto error; + } + + OutputJsonBuilderBuffer(js, thread->ctx); + jb_free(js); + + return TM_ECODE_OK; + +error: + jb_free(js); + return TM_ECODE_FAILED; +} + +static void OutputEnipLogDeInitCtxSub(OutputCtx *output_ctx) +{ + LogEnipFileCtx *eniplog_ctx = (LogEnipFileCtx *)output_ctx->data; + SCFree(eniplog_ctx); + SCFree(output_ctx); +} + +static OutputInitResult OutputEnipLogInitSub(ConfNode *conf, OutputCtx *parent_ctx) +{ + OutputInitResult result = { NULL, false }; + OutputJsonCtx *ajt = parent_ctx->data; + + LogEnipFileCtx *eniplog_ctx = SCCalloc(1, sizeof(*eniplog_ctx)); + if (unlikely(eniplog_ctx == NULL)) { + return result; + } + eniplog_ctx->eve_ctx = ajt; + + OutputCtx *output_ctx = SCCalloc(1, sizeof(*output_ctx)); + if (unlikely(output_ctx == NULL)) { + SCFree(eniplog_ctx); + return result; + } + output_ctx->data = eniplog_ctx; + output_ctx->DeInit = OutputEnipLogDeInitCtxSub; + + AppLayerParserRegisterLogger(IPPROTO_UDP, ALPROTO_ENIP); + AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_ENIP); + + result.ctx = output_ctx; + result.ok = true; + return result; +} + +static TmEcode JsonEnipLogThreadInit(ThreadVars *t, const void *initdata, void **data) +{ + LogEnipLogThread *thread = SCCalloc(1, sizeof(*thread)); + if (unlikely(thread == NULL)) { + return TM_ECODE_FAILED; + } + + if (initdata == NULL) { + SCLogDebug("Error getting context for EveLogEnip. \"initdata\" is NULL."); + goto error_exit; + } + + thread->eniplog_ctx = ((OutputCtx *)initdata)->data; + thread->ctx = CreateEveThreadCtx(t, thread->eniplog_ctx->eve_ctx); + if (!thread->ctx) { + goto error_exit; + } + *data = (void *)thread; + + return TM_ECODE_OK; + +error_exit: + SCFree(thread); + return TM_ECODE_FAILED; +} + +static TmEcode JsonEnipLogThreadDeinit(ThreadVars *t, void *data) +{ + LogEnipLogThread *thread = (LogEnipLogThread *)data; + if (thread == NULL) { + return TM_ECODE_OK; + } + FreeEveThreadCtx(thread->ctx); + SCFree(thread); + return TM_ECODE_OK; +} + +void JsonEnipLogRegister(void) +{ + /* Register as an eve sub-module. */ + OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonEnipLog", "eve-log.enip", + OutputEnipLogInitSub, ALPROTO_ENIP, JsonEnipLogger, JsonEnipLogThreadInit, + JsonEnipLogThreadDeinit, NULL); +} diff --git a/src/output-json-enip.h b/src/output-json-enip.h new file mode 100644 index 000000000000..27832bb85022 --- /dev/null +++ b/src/output-json-enip.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2023 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 Philippe Antoine + */ + +#ifndef __OUTPUT_JSON_ENIP_H__ +#define __OUTPUT_JSON_ENIP_H__ + +void JsonEnipLogRegister(void); + +#endif /* __OUTPUT_JSON_ENIP_H__ */ diff --git a/src/output.c b/src/output.c index 149dda58c284..15d6f5ca19a3 100644 --- a/src/output.c +++ b/src/output.c @@ -80,6 +80,7 @@ #include "output-json-rfb.h" #include "output-json-mqtt.h" #include "output-json-pgsql.h" +#include "output-json-enip.h" #include "output-json-template.h" #include "output-json-rdp.h" #include "output-json-http2.h" @@ -1117,6 +1118,8 @@ void OutputRegisterLoggers(void) JsonMQTTLogRegister(); /* Pgsql JSON logger. */ JsonPgsqlLogRegister(); + /* Enip JSON logger. */ + JsonEnipLogRegister(); /* Template JSON logger. */ JsonTemplateLogRegister(); /* RDP JSON logger. */ @@ -1143,7 +1146,7 @@ static EveJsonSimpleAppLayerLogger simple_json_applayer_loggers[ALPROTO_MAX] = { { ALPROTO_IRC, NULL }, // no parser, no logging { ALPROTO_DNS, AlertJsonDns }, { ALPROTO_MODBUS, (EveJsonSimpleTxLogFunc)rs_modbus_to_json }, - { ALPROTO_ENIP, NULL }, // no logging + { ALPROTO_ENIP, rs_enip_logger_log }, { ALPROTO_DNP3, AlertJsonDnp3 }, { ALPROTO_NFS, NULL }, // special: uses state { ALPROTO_NTP, NULL }, // no logging diff --git a/src/runmode-unittests.c b/src/runmode-unittests.c index 1150bad89580..7518bbe0349d 100644 --- a/src/runmode-unittests.c +++ b/src/runmode-unittests.c @@ -38,7 +38,6 @@ #include "detect-engine-dcepayload.h" #include "detect-engine-state.h" #include "detect-engine-tag.h" -#include "detect-engine-enip.h" #include "detect-fast-pattern.h" #include "flow.h" #include "flow-timeout.h" diff --git a/suricata.yaml.in b/suricata.yaml.in index 630399126dbe..7a0732d4fcff 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -279,6 +279,7 @@ outputs: #md5: [body, subject] #- dnp3 + #- enip - ftp - rdp - nfs