From: Tom Peters (thopeter) Date: Fri, 15 Apr 2022 19:28:53 +0000 (+0000) Subject: Pull request #3207: Mms service inspector X-Git-Tag: 3.1.28.0~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0a6f6ad15e27ba17aef3d75580de33470d7eb869;p=thirdparty%2Fsnort3.git Pull request #3207: Mms service inspector Merge in SNORT/snort3 from ~JRITTLE/snort3:mms_service_inspector to master Squashed commit of the following: commit 748bd178828da9d67a303ee24971f03ff0bc7e4f Author: jrittle Date: Fri Jul 2 14:04:54 2021 -0400 mms: adding new service inspector for the IEC61850 MMS protocol --- diff --git a/lua/snort.lua b/lua/snort.lua index 3579a4d53..24d984370 100644 --- a/lua/snort.lua +++ b/lua/snort.lua @@ -56,6 +56,7 @@ dnp3 = { } dns = { } imap = { } iec104 = { } +mms = { } modbus = { } netflow = {} normalizer = { } @@ -140,6 +141,7 @@ binder = { when = { service = 'http' }, use = { type = 'http_inspect' } }, { when = { service = 'http2' }, use = { type = 'http2_inspect' } }, { when = { service = 'iec104' }, use = { type = 'iec104' } }, + { when = { service = 'mms' }, use = { type = 'mms' } }, { when = { service = 'modbus' }, use = { type = 'modbus' } }, { when = { service = 'pop3' }, use = { type = 'pop' } }, { when = { service = 'ssh' }, use = { type = 'ssh' } }, diff --git a/lua/snort_defaults.lua b/lua/snort_defaults.lua index 41eef5823..76ec107d6 100644 --- a/lua/snort_defaults.lua +++ b/lua/snort_defaults.lua @@ -415,7 +415,7 @@ default_wizard = to_server = telnet_commands, to_client = telnet_commands }, }, - curses = {'dce_udp', 'dce_tcp', 'dce_smb', 'sslv2'} + curses = {'dce_udp', 'dce_tcp', 'dce_smb', 'mms', 'sslv2'} } --------------------------------------------------------------------------- diff --git a/src/service_inspectors/CMakeLists.txt b/src/service_inspectors/CMakeLists.txt index 01343fb21..c4d54ac27 100644 --- a/src/service_inspectors/CMakeLists.txt +++ b/src/service_inspectors/CMakeLists.txt @@ -10,6 +10,7 @@ add_subdirectory(http_inspect) add_subdirectory(http2_inspect) add_subdirectory(iec104) add_subdirectory(imap) +add_subdirectory(mms) add_subdirectory(modbus) add_subdirectory(netflow) add_subdirectory(pop) @@ -32,6 +33,7 @@ if (STATIC_INSPECTORS) $ $ $ + $ $ $ $ diff --git a/src/service_inspectors/mms/CMakeLists.txt b/src/service_inspectors/mms/CMakeLists.txt new file mode 100644 index 000000000..abff2d9ba --- /dev/null +++ b/src/service_inspectors/mms/CMakeLists.txt @@ -0,0 +1,34 @@ + +set( FILE_LIST + mms.cc + mms.h + mms_decode.cc + mms_decode.h + mms_module.cc + mms_module.h + mms_splitter.cc + mms_splitter.h + ips_mms_data.cc + ips_mms_func.cc + tpkt/tpkt_decode.cc + tpkt/tpkt_decode.h + tpkt/cotp_decode.cc + tpkt/cotp_decode.h + tpkt/osi_session_decode.cc + tpkt/osi_session_decode.h + tpkt/osi_pres_decode.cc + tpkt/osi_pres_decode.h + tpkt/osi_acse_decode.cc + tpkt/osi_acse_decode.h + util_tpkt.cc + util_tpkt.h +) + +if (STATIC_INSPECTORS) + add_library(mms OBJECT ${FILE_LIST}) + +else (STATIC_INSPECTORS) + add_dynamic_module(mms inspectors ${FILE_LIST}) + +endif (STATIC_INSPECTORS) + diff --git a/src/service_inspectors/mms/dev_notes.txt b/src/service_inspectors/mms/dev_notes.txt new file mode 100644 index 000000000..5119088c7 --- /dev/null +++ b/src/service_inspectors/mms/dev_notes.txt @@ -0,0 +1,111 @@ +*** MMS service inspector overview *** +IEC 61850 is a family of protocols distributed by the International +Electrotechnical Commission (IEC) that provide a standardized method of +sending service messages between various manufacturing and process control +devices, typically running on TCP port 102. + +It is used in combination with various parts of the OSI model, most notably +the TPKT, COTP, Session, Presentation, and ACSE layers, to provide reliable +transport via TCP/IP. + +The MMS inspector decodes the OSI layers encapsulating the MMS protocol and +provides rule options to access certain protocol fields and data content. +This allows the user to write rules for MMS messages without decoding the +protocol. + + +*** Encapsulation overview *** +MMS can be transported via TCP/102 when encapsulated in TPKT and the +associated OSI layers. The two structures most commonly encountered are +shown below: + +Connection Request/Response: + TCP -> TPKT -> COTP -> OSI Session -> OSI Presentation -> OSI ACSE -> MMS + +Confirmed Request/Response: + TCP -> TPKT -> COTP -> OSI Session -> OSI Presentation -> MMS + +Parsing functions for each of these layers can be found in `util_tpkt`. + + +*** MMS message pipelining overview *** +Multiple MMS messages can be sent within a single TCP stream through use of +a variety of techniques. Splitting for known forms is handled as follows: + +Case 1 Combined: +In this case multiple messages are contained within one TCP packet. This can +occur at any of the following layers: TPKT, OSI Presentation, MMS, or any +combination thereof. Example layouts are shown below: + +Combined at the TPKT layer: + TCP -> TPKT -> COTP -> OSI Session -> OSI Presentation -> MMS -> TPKT + -> COTP -> OSI Session -> OSI Presentation -> MMS + +Combined at the OSI Presentation layer: + TCP -> TPKT -> COTP -> OSI Session -> OSI Presentation -> MMS + -> OSI Presentation -> MMS + +Combined at the MMS layer: + TCP -> TPKT -> COTP -> OSI Session -> OSI Presentation -> MMS -> MMS + +Due to the ability for messages to be combined at multiple layers, the +starting layer of the cursor following a successful split cannot be assumed. +To solve this, a set of functions have been developed in `util_tpkt` to make +a best guess at the layer at the cursor start (`isTpkt`, `isCotp`, etc.). An +example of usage can be found in the MMS Splitter. No special considerations +were needed in the curse to handle this case. + +Case 2 Split: +In this case a single message is split across multiple TCP packets within the +same stream. This can occur at any point across the packet data, not +necessarily on a layer boundary. Due to this case it is necessary to buffer +the incoming packet data until a flush point has been found. To handle this, +the both the MMS curse and splitter need to have special considerations. + +In the MMS curse, current and prior state are tracked as the potential message +is processed byte by byte. The current state indicates the part of a fully +encapsulated MMS message that the byte in that position would represent if it +was found to contain MMS. The prior state is tracked so that the state machine +knows where to pick up in the event of a message being split. + +In the MMS splitter, two buffers (one for each direction) are used to track +message data across packets. At the beginning of each scan, the direction is +determined and the appropriate buffer is selected. The new packet data is then +appended to the selected buffer. This buffer, not the current packet data, is +then searched for a MMS message through use of various functions exposed +in `util_tpkt`. + +Case 3 Combined and Split: +In this case, the above two techniques are combined to create a stream where +one packet contains a complete MMS message as well as the start of a second, +and then the packets that follow contain the remainder of the second MMS +message. No additional considerations beyond those implemented for the prior +two techniques were needed. + + +*** MMS curse *** +In the MMS curse, current and prior state are tracked as the potential message +is processed byte by byte. The current state indicates the part of a fully +encapsulated MMS message that the byte in that position would represent if it +was found to contain MMS. The prior state is tracked so that the state machine +knows where to pick up in the event of a message being split. Two special +states exist which are used to assist in determining when to stop +processing: MMS_STATE__MMS and MMS_STATE__NOT_FOUND. + +During processing only states that are required to be specific values are +enforced, leaving more specific enforcement to the MMS inspector itself. +This choice was made to help avoid false negatives caused by states that +would not affect the inspector's parsing. For example, the TPKT version is +expected to be the value 0x03, however this does not make any material +difference at this time to how the message is parsed so it is not enforced. +By contrast, the COTP PDU value is enforced to be of type `DT_DATA` as other +values indicate that MMS is most likely not in use. + +If all of the encapsulation layer checks pass, a best guess is made to +determine if the payload is a MMS message. This is done by searching for any +of the known MMS message types. Unfortunately, this approach has the potential +for some false positives to slip into the MMS inspector processing as the +message type indicators are only one byte in size. This was deemed preferred +as it is enough to kick out the majority of non-MMS traffic while not needing +to run the more intensive parsing that occurs in the inspector. + diff --git a/src/service_inspectors/mms/ips_mms_data.cc b/src/service_inspectors/mms/ips_mms_data.cc new file mode 100644 index 000000000..dc50af24d --- /dev/null +++ b/src/service_inspectors/mms/ips_mms_data.cc @@ -0,0 +1,161 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// ips_mms_data.cc author Jared Rittle +// modeled after ips_modbus_data.cc author Russ Combs + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "framework/cursor.h" +#include "framework/ips_option.h" +#include "framework/module.h" +#include "hash/hash_key_operations.h" +#include "profiler/profiler.h" +#include "protocols/packet.h" + +#include "mms.h" + +using namespace snort; + +static const char* s_name = "mms_data"; + +//------------------------------------------------------------------------- +// version option +//------------------------------------------------------------------------- + +static THREAD_LOCAL ProfileStats mms_data_prof; + +class MmsDataOption : public IpsOption +{ +public: + MmsDataOption() : IpsOption(s_name, RULE_OPTION_TYPE_BUFFER_SET) { } + + CursorActionType get_cursor_type() const override + { return CAT_SET_OTHER; } + + EvalStatus eval(Cursor&, Packet*) override; +}; + +IpsOption::EvalStatus MmsDataOption::eval(Cursor& c, Packet* p) +{ + RuleProfile profile(mms_data_prof); + + if (!p->flow) + { + return NO_MATCH; + } + + // not including any checks for a full PDU as we're not guaranteed to + // have one with the available pipelining options to get to MMS + + MmsFlowData* mmsfd = (MmsFlowData*)p->flow->get_flow_data(MmsFlowData::inspector_id); + + if (!mmsfd) + { + return NO_MATCH; + } + + if (!mmsfd->is_mms_found()) + { + return NO_MATCH; + } + + if (mmsfd->get_mms_offset() >= p->dsize) + { + return NO_MATCH; + } + + // setting the cursor to the offset previously determined by util_tpkt + // to be the start of the MMS message + c.set(s_name, p->data + mmsfd->get_mms_offset(), p->dsize - mmsfd->get_mms_offset()); + + return MATCH; +} + +//------------------------------------------------------------------------- +// module +//------------------------------------------------------------------------- + +#define s_help \ + "rule option to set cursor to MMS data" + +class MmsDataModule : public Module +{ +public: + MmsDataModule() : Module(s_name, s_help) { } + + ProfileStats* get_profile() const override + { return &mms_data_prof; } + + Usage get_usage() const override + { return DETECT; } +}; + +//------------------------------------------------------------------------- +// api +//------------------------------------------------------------------------- + +static Module* mod_ctor() +{ + return new MmsDataModule; +} + +static void mod_dtor(Module* m) +{ + delete m; +} + +static IpsOption* opt_ctor(Module*, OptTreeNode*) +{ + return new MmsDataOption; +} + +static void opt_dtor(IpsOption* p) +{ + delete p; +} + +static const IpsApi ips_api = +{ + { + PT_IPS_OPTION, + sizeof(IpsApi), + IPSAPI_VERSION, + 0, + API_RESERVED, + API_OPTIONS, + s_name, + s_help, + mod_ctor, + mod_dtor + }, + OPT_TYPE_DETECTION, + 0, PROTO_BIT__TCP, + nullptr, + nullptr, + nullptr, + nullptr, + opt_ctor, + opt_dtor, + nullptr +}; + +const BaseApi* ips_mms_data = &ips_api.base; + diff --git a/src/service_inspectors/mms/ips_mms_func.cc b/src/service_inspectors/mms/ips_mms_func.cc new file mode 100644 index 000000000..2276f81bf --- /dev/null +++ b/src/service_inspectors/mms/ips_mms_func.cc @@ -0,0 +1,435 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// ips_mms_func.cc author Jared Rittle +// modeled after ips_modbus_func.cc (author Russ Combs ) + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "framework/cursor.h" +#include "framework/ips_option.h" +#include "framework/module.h" +#include "hash/hash_key_operations.h" +#include "protocols/packet.h" +#include "profiler/profiler.h" +#include "utils/util_ber.h" + +#include "mms.h" + +using namespace snort; + +static const char* s_name = "mms_func"; + +//------------------------------------------------------------------------- +// function lookup +//------------------------------------------------------------------------- + +#define MMS_FUNC__UNSET 255 + +struct MmsFuncMap +{ + const char* name; + uint8_t func; +}; + +// Mapping of name -> function code for 'mms_func' option +static MmsFuncMap mms_func_map[] = +{ + { "status", 0 }, + { "get_name_list", 1 }, + { "identify", 2 }, + { "rename", 3 }, + { "read", 4 }, + { "write", 5 }, + { "get_variable_access_attributes", 6 }, + { "define_named_variable", 7 }, + { "define_scattered_access", 8 }, + { "get_scattered_access_attributes", 9 }, + { "delete_variable_access", 10 }, + { "define_named_variable_list", 11 }, + { "get_named_variable_list_attributes", 12 }, + { "delete_named_variable_list", 13 }, + { "define_named_type", 14 }, + { "get_named_type_attributes", 15 }, + { "delete_named_type", 16 }, + { "input", 17 }, + { "output", 18 }, + { "take_control", 19 }, + { "relinquish_control", 20 }, + { "define_semaphore", 21 }, + { "delete_semaphore", 22 }, + { "report_semaphore_status", 23 }, + { "report_pool_semaphore_status", 24 }, + { "report_semaphore_entry_status", 25 }, + { "initiate_download_sequence", 26 }, + { "download_segment", 27 }, + { "terminate_download_sequence", 28 }, + { "initiate_upload_sequence", 29 }, + { "upload_segment", 30 }, + { "terminate_upload_sequence", 31 }, + { "request_domain_download", 32 }, + { "request_domain_upload", 33 }, + { "load_domain_content", 34 }, + { "store_domain_content", 35 }, + { "delete_domain", 36 }, + { "get_domain_attributes", 37 }, + { "create_program_invocation", 38 }, + { "delete_program_invocation", 39 }, + { "start", 40 }, + { "stop", 41 }, + { "resume", 42 }, + { "reset", 43 }, + { "kill", 44 }, + { "get_program_invocation_attributes", 45 }, + { "obtain_file", 46 }, + { "define_event_condition", 47 }, + { "delete_event_condition", 48 }, + { "get_event_condition_attributes", 49 }, + { "report_event_condition_status", 50 }, + { "alter_event_condition_monitoring", 51 }, + { "trigger_event", 52 }, + { "define_event_action", 53 }, + { "delete_event_action", 54 }, + { "get_event_action_attributes", 55 }, + { "report_event_action_status", 56 }, + { "define_event_enrollment", 57 }, + { "delete_event_enrollment", 58 }, + { "alter_event_enrollment", 59 }, + { "report_event_enrollment_status", 60 }, + { "get_event_enrollment_attributes", 61 }, + { "acknowledge_event_notification", 62 }, + { "get_alarm_summary", 63 }, + { "get_alarm_enrollment_summary", 64 }, + { "read_journal", 65 }, + { "write_journal", 66 }, + { "initialize_journal", 67 }, + { "report_journal_status", 68 }, + { "create_journal", 69 }, + { "delete_journal", 70 }, + { "get_capability_list", 71 }, + { "file_open", 72 }, + { "file_read", 73 }, + { "file_close", 74 }, + { "file_rename", 75 }, + { "file_delete", 76 }, + { "file_directory", 77 }, + { "additional_service", 78 }, + // 79 not defined + { "get_data_exchange_attributes", 80 }, + { "exchange_data", 81 }, + { "define_access_control_list", 82 }, + { "get_access_control_list_attributes", 83 }, + { "report_access_controlled_objects", 84 }, + { "delete_access_control_list", 85 }, + { "change_access_control", 86 }, + { "reconfigure_program_invocation", 87 }, +}; + +static bool get_func(const char* s, long& n) +{ + constexpr size_t max = (sizeof(mms_func_map) / sizeof(MmsFuncMap)); + + for (size_t i = 0; i < max; ++i) + { + // return true when the passed string matches a known function + if (strcmp(s, mms_func_map[i].name) == 0) + { + n = mms_func_map[i].func; + return true; + } + } + return false; +} + +//------------------------------------------------------------------------- +// func option +//------------------------------------------------------------------------- + +static THREAD_LOCAL ProfileStats mms_func_prof; + +class MmsFuncOption : public IpsOption +{ +public: + MmsFuncOption(uint16_t v) : + IpsOption(s_name) + { + func = v; + } + + uint32_t hash() const override; + bool operator==(const IpsOption&) const override; + + EvalStatus eval(Cursor&, Packet*) override; + +public: + uint16_t func = MMS_FUNC__UNSET; +}; + +uint32_t MmsFuncOption::hash() const +{ + uint32_t a = func, b = IpsOption::hash(), c = 0; + + mix(a, b, c); + finalize(a, b, c); + + return c; +} + +bool MmsFuncOption::operator==(const IpsOption& ips) const +{ + if (!IpsOption::operator==(ips)) + { + return false; + } + + const MmsFuncOption& rhs = (const MmsFuncOption&)ips; + return(func == rhs.func); +} + +IpsOption::EvalStatus MmsFuncOption::eval(Cursor&, Packet* p) +{ + RuleProfile profile(mms_func_prof); + + if (!p->flow) + { + return NO_MATCH; + } + + // check if the packet function matches the rule option function + MmsFlowData* mmsfd = (MmsFlowData*)p->flow->get_flow_data(MmsFlowData::inspector_id); + if (!mmsfd) + { + return NO_MATCH; + } + + if (!mmsfd->is_mms_found()) + { + return NO_MATCH; + } + + Cursor eval_cur = Cursor(p); + if (!eval_cur.set_pos(mmsfd->get_mms_offset())) + { + return NO_MATCH; + } + + BerReader ber(eval_cur); + BerElement e; + + if (!ber.read(eval_cur.start(), e)) + { + return NO_MATCH; + } + + // check for a message type that contains a service + switch (e.type) + { + case MMS_MSG__CONFIRMED_REQUEST: // fallthrough + case MMS_MSG__CONFIRMED_RESPONSE: + // shift cursor to next tag + if (eval_cur.add_pos(e.header_length)) + { + // skip past the `invoke_id` + if (ber.read(eval_cur.start(), e)) + { + if (eval_cur.add_pos(e.header_length + e.length)) + { + // get the next tag + if (ber.read(eval_cur.start(), e)) + { + // check to see if the optional `list_of_modifiers` field is in use and + // skip when it is + const uint32_t OPTIONAL_LIST_OF_MODIFIERS_TAG = 0x10; + if (e.type == OPTIONAL_LIST_OF_MODIFIERS_TAG) + { + if (!eval_cur.add_pos(e.header_length + e.length)) + { + return NO_MATCH; + } + + // get the next tag + if (!ber.read(eval_cur.start(), e)) + { + return NO_MATCH; + } + } + + // check to see if the byte is less than the value of the + // `status` service request (0x80). When this is the case + // it indicates that a service tag value larger than what + // could fit within the available space in the preceding + // byte (5 bits). + if (e.type < 0x80) + { + // if the type is the same as what was requested by the user, return a + // match + // no mask used here as the long form is in use, which causes + // the next byte to contain the tag value. + // the entire byte can be used in this case + // When looking at this type of message in a traffic capture, + // the byte being compared here will be preceded by 0xBF + // which is handled by the BER reader utility + if ((e.type) == func) + { + return MATCH; + } + } + else + { + // if the type is the same as what was requested by the user, return a + // match + // a mask is used since only the low 5 bits can be used + uint8_t ber_tag_value_mask = 0b00011111; + if ((e.type & ber_tag_value_mask) == func) + { + return MATCH; + } + } + } + } + } + } + + break; + + // no default + } + + return NO_MATCH; +} + +//------------------------------------------------------------------------- +// module +//------------------------------------------------------------------------- + +static const Parameter s_params[] = +{ + { "~", Parameter::PT_STRING, nullptr, nullptr, + "func to match" }, + + { nullptr, Parameter::PT_MAX, nullptr, nullptr,nullptr } +}; + +#define s_help \ + "rule option to check MMS function" + +class MmsFuncModule : public Module +{ +public: + MmsFuncModule() : + Module(s_name, s_help, s_params) + { + } + + bool set(const char*, Value&, SnortConfig*) override; + + ProfileStats* get_profile() const override + { + return &mms_func_prof; + } + + Usage get_usage() const override + { + return DETECT; + } + +public: + uint8_t func = MMS_FUNC__UNSET; +}; + +bool MmsFuncModule::set(const char*, Value& v, SnortConfig*) +{ + if (!v.is("~")) + { + return false; + } + + long n; + + if (v.strtol(n)) + { + func = (uint16_t)n; + } + else if (get_func(v.get_string(), n)) + { + func = static_cast(n); + } + else + { + return false; + } + + return true; +} + +//------------------------------------------------------------------------- +// api +//------------------------------------------------------------------------- + +static Module* mod_ctor() +{ + return new MmsFuncModule; +} + +static void mod_dtor(Module* m) +{ + delete m; +} + +static IpsOption* opt_ctor(Module* m, OptTreeNode*) +{ + MmsFuncModule* mod = (MmsFuncModule*)m; + + return new MmsFuncOption(mod->func); +} + +static void opt_dtor(IpsOption* p) +{ + delete p; +} + +static const IpsApi ips_api = +{ + { + PT_IPS_OPTION, + sizeof(IpsApi), + IPSAPI_VERSION, + 0, + API_RESERVED, + API_OPTIONS, + s_name, + s_help, + mod_ctor, + mod_dtor + }, + OPT_TYPE_DETECTION, + 0, + PROTO_BIT__TCP, + nullptr, + nullptr, + nullptr, + nullptr, + opt_ctor, + opt_dtor, + nullptr +}; + +const BaseApi* ips_mms_func = &ips_api.base; + diff --git a/src/service_inspectors/mms/mms.cc b/src/service_inspectors/mms/mms.cc new file mode 100644 index 000000000..3b6008d3a --- /dev/null +++ b/src/service_inspectors/mms/mms.cc @@ -0,0 +1,195 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// mms.cc author Jared Rittle +// modeled after modbus.cc (author Russ Combs ) +// modeled after s7comm.cc (author Pradeep Damodharan ) + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mms.h" + +#include "detection/detection_engine.h" +#include "events/event_queue.h" +#include "profiler/profiler.h" +#include "protocols/packet.h" + +#include "mms_decode.h" +#include "mms_module.h" +#include "mms_splitter.h" + +using namespace snort; + +THREAD_LOCAL MmsStats mms_stats; + +//------------------------------------------------------------------------- +// flow stuff +//------------------------------------------------------------------------- + +unsigned MmsFlowData::inspector_id = 0; + +void MmsFlowData::init() +{ + inspector_id = FlowData::create_flow_data_id(); +} + +MmsFlowData::MmsFlowData() : + FlowData(inspector_id) +{ + mms_stats.concurrent_sessions++; + if (mms_stats.max_concurrent_sessions < mms_stats.concurrent_sessions) + { + mms_stats.max_concurrent_sessions = mms_stats.concurrent_sessions; + } +} + +MmsFlowData::~MmsFlowData() +{ + assert(mms_stats.concurrent_sessions > 0); + mms_stats.concurrent_sessions--; +} + +//------------------------------------------------------------------------- +// class stuff +//------------------------------------------------------------------------- + +class Mms : public Inspector +{ +public: + // default ctor / dtor + void eval(Packet*) override; + + uint32_t get_message_type(uint32_t version, const char* name); + uint32_t get_info_type(uint32_t version, const char* name); + + StreamSplitter* get_splitter(bool c2s) override + { + return new MmsSplitter(c2s); + } +}; + +void Mms::eval(Packet* p) +{ + Profile profile(mms_prof); + + // preconditions - what we registered for + assert(p->has_tcp_data()); + + MmsFlowData* mmsfd = (MmsFlowData*)p->flow->get_flow_data(MmsFlowData::inspector_id); + + // not including any checks for a full PDU as we're not guaranteed to + // have one with the available pipelining options to get to MMS + + if (!mmsfd) + { + mmsfd = new MmsFlowData; + p->flow->set_flow_data(mmsfd); + mms_stats.sessions++; + } + + // update stats + mms_stats.frames++; + + // When pipelined MMS PDUs appear in a single TCP segment, the + // detection engine caches the results of the rule options after + // evaluating on the first PDU. Setting this flag stops the caching. + p->packet_flags |= PKT_ALLOW_MULTIPLE_DETECT; + + if (!mms_decode(p, mmsfd)) + { + mmsfd->reset(); + } +} + +//------------------------------------------------------------------------- +// plugin stuff +//------------------------------------------------------------------------- + +static Module* mod_ctor() +{ + return new MmsModule; +} + +static void mod_dtor(Module* m) +{ + delete m; +} + +static void mms_init() +{ + MmsFlowData::init(); +} + +static Inspector* mms_ctor(Module*) +{ + return new Mms; +} + +static void mms_dtor(Inspector* p) +{ + delete p; +} + +//------------------------------------------------------------------------- + +static const InspectApi mms_api = +{ + { + PT_INSPECTOR, + sizeof(InspectApi), + INSAPI_VERSION, + 0, + API_RESERVED, + API_OPTIONS, + MMS_NAME, + MMS_HELP, + mod_ctor, + mod_dtor + }, + IT_SERVICE, + PROTO_BIT__PDU, + nullptr, + "mms", + mms_init, + nullptr, + nullptr, // tinit + nullptr, // tterm + mms_ctor, + mms_dtor, + nullptr, // ssn + nullptr // reset +}; + +// BaseApi for each rule option +extern const BaseApi* ips_mms_data; +extern const BaseApi* ips_mms_func; + +#ifdef BUILDING_SO +SO_PUBLIC const BaseApi* snort_plugins[] = +#else +const BaseApi * sin_mms[] = +#endif +{ + &mms_api.base, + ips_mms_data, + ips_mms_func, + nullptr +}; + diff --git a/src/service_inspectors/mms/mms.h b/src/service_inspectors/mms/mms.h new file mode 100644 index 000000000..3a42184b2 --- /dev/null +++ b/src/service_inspectors/mms/mms.h @@ -0,0 +1,113 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// mms.h author Jared Rittle +// modeled after modbus.h (author Russ Combs ) +// modeled after s7comm.h (author Pradeep Damodharan ) + +#ifndef MMS_H +#define MMS_H + +// MMS adds a new service inspector designed to process Manufacturing +// Message Specification (MMS) traffic defined within the IEC-61850 family +// of protocols + +#include "flow/flow.h" +#include "framework/counts.h" + +struct MmsStats +{ + PegCount sessions; + PegCount frames; + PegCount concurrent_sessions; + PegCount max_concurrent_sessions; +}; + +class MmsSessionData +{ +public: + uint32_t offset = 0; + bool mms_found = false; + + void session_data_reset() + { + offset = 0; + mms_found = false; + } +}; + +class MmsFlowData : public snort::FlowData +{ +public: + MmsFlowData(); + ~MmsFlowData() override; + + static void init(); + + void reset() + { + ssn_data.session_data_reset(); + } + + void set_mms_offset(uint32_t offset) + { + ssn_data.mms_found = true; + ssn_data.offset = offset; + } + + uint32_t get_mms_offset() + { + return ssn_data.offset; + } + + bool is_mms_found() + { + return ssn_data.mms_found; + } + +public: + static unsigned inspector_id; + MmsSessionData ssn_data; +}; + +uint32_t get_message_type(uint32_t version, const char* name); +uint32_t get_info_type(uint32_t version, const char* name); + +enum MmsMsgType +{ + MMS_MSG__PDU_NOT_SET = 0x00, + MMS_MSG__CONFIRMED_REQUEST = 0xA0, + MMS_MSG__CONFIRMED_RESPONSE = 0xA1, + MMS_MSG__CONFIRMED_ERROR = 0xA2, + MMS_MSG__UNCONFIRMED = 0xA3, + MMS_MSG__REJECT = 0xA4, + MMS_MSG__CANCEL_REQUEST = 0x85, + MMS_MSG__CANCEL_RESPONSE = 0x86, + MMS_MSG__CANCEL_ERROR = 0xA7, + MMS_MSG__INITIATE_REQUEST = 0xA8, + MMS_MSG__INITIATE_RESPONSE = 0xA9, + MMS_MSG__INITIATE_ERROR = 0xAA, + MMS_MSG__CONCLUDE_REQUEST = 0x8B, + MMS_MSG__CONCLUDE_RESPONSE = 0x8C, + MMS_MSG__CONCLUDE_ERROR = 0xAD, +}; + +extern THREAD_LOCAL MmsStats mms_stats; + +#endif + diff --git a/src/service_inspectors/mms/mms_decode.cc b/src/service_inspectors/mms/mms_decode.cc new file mode 100644 index 000000000..1756b3916 --- /dev/null +++ b/src/service_inspectors/mms/mms_decode.cc @@ -0,0 +1,73 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// mms_decode.cc author Jared Rittle +// modeled after modbus_decode.cc (author Russ Combs ) +// modeled after s7comm_decode.cc (author Pradeep Damodharan ) + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mms_decode.h" + +#include "detection/detection_engine.h" +#include "events/event_queue.h" +#include "log/messages.h" +#include "main/snort_debug.h" +#include "managers/plugin_manager.h" +#include "protocols/packet.h" +#include "utils/util_ber.h" + +#include "mms.h" +#include "mms_module.h" + +using namespace snort; + +bool mms_decode(Packet* p, MmsFlowData* mmsfd) +{ + if (p->dsize < MMS_MIN_LEN) + { + return false; + } + + switch (*(p->data + mmsfd->get_mms_offset())) + { + case MMS_MSG__CONFIRMED_REQUEST: // fallthrough + case MMS_MSG__CONFIRMED_RESPONSE: // fallthrough + case MMS_MSG__CONFIRMED_ERROR: // fallthrough + case MMS_MSG__UNCONFIRMED: // fallthrough + case MMS_MSG__REJECT: // fallthrough + case MMS_MSG__CANCEL_REQUEST: // fallthrough + case MMS_MSG__CANCEL_RESPONSE: // fallthrough + case MMS_MSG__CANCEL_ERROR: // fallthrough + case MMS_MSG__INITIATE_REQUEST: // fallthrough + case MMS_MSG__INITIATE_RESPONSE: // fallthrough + case MMS_MSG__INITIATE_ERROR: // fallthrough + case MMS_MSG__CONCLUDE_REQUEST: // fallthrough + case MMS_MSG__CONCLUDE_RESPONSE: // fallthrough + case MMS_MSG__CONCLUDE_ERROR: + // allow these through + break; + + default: + return false; + } + return true; +} + diff --git a/src/service_inspectors/mms/mms_decode.h b/src/service_inspectors/mms/mms_decode.h new file mode 100644 index 000000000..57ed07a2d --- /dev/null +++ b/src/service_inspectors/mms/mms_decode.h @@ -0,0 +1,44 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- +// +// mms_decode.h author Jared Rittle +// modeled after modbus_decode.h (author Russ Combs ) +// modeled after s7comm_decode.h (author Pradeep Damodharan ) + +#ifndef MMS_DECODE_H +#define MMS_DECODE_H + +// MMS Decode provides the final processing to determine whether or not the +// message being processed is of a known type + +#include "framework/cursor.h" + +namespace snort +{ +struct Packet; +} + +class MmsFlowData; + +/* Need at least 3 bytes to cover one TLV group */ +#define MMS_MIN_LEN 3 + +bool mms_decode(snort::Packet*, MmsFlowData* mmsfd); + +#endif + diff --git a/src/service_inspectors/mms/mms_module.cc b/src/service_inspectors/mms/mms_module.cc new file mode 100644 index 000000000..fbd0cf115 --- /dev/null +++ b/src/service_inspectors/mms/mms_module.cc @@ -0,0 +1,83 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// mms_module.cc author Jared Rittle +// modeled after modbus_module.c (author Russ Combs ) +// modeled after s7comm_module.c (author Pradeep Damodharan ) + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mms_module.h" + +#include "profiler/profiler.h" + +#include "mms.h" + +using namespace snort; + +THREAD_LOCAL ProfileStats mms_prof; + +//------------------------------------------------------------------------- +// stats +//------------------------------------------------------------------------- + +const PegInfo peg_names[] = +{ + { CountType::SUM, "sessions", "total sessions processed" }, + { CountType::SUM, "frames", "total MMS messages" }, + { CountType::NOW, "concurrent_sessions", "total concurrent MMS sessions" }, + { CountType::MAX, "max_concurrent_sessions", "maximum concurrent MMS sessions" }, + + { CountType::END, nullptr, nullptr } +}; + +const PegInfo* MmsModule::get_pegs() const +{ + return peg_names; +} + +PegCount* MmsModule::get_counts() const +{ + return (PegCount*)&mms_stats; +} + +//------------------------------------------------------------------------- +// rules +//------------------------------------------------------------------------- + +static const RuleMap Mms_rules[] = +{ + { 0, nullptr } +}; + +const RuleMap* MmsModule::get_rules() const +{ + return Mms_rules; +} + +//------------------------------------------------------------------------- +// params +//------------------------------------------------------------------------- + +MmsModule::MmsModule() : + Module(MMS_NAME, MMS_HELP) +{ +} + diff --git a/src/service_inspectors/mms/mms_module.h b/src/service_inspectors/mms/mms_module.h new file mode 100644 index 000000000..1ff0dee7c --- /dev/null +++ b/src/service_inspectors/mms/mms_module.h @@ -0,0 +1,70 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// mms_module.h author Jared Rittle +// modeled after modbus_module.h (author Russ Combs ) +// modeled after s7comm_module.h (author Pradeep Damodharan ) + +#ifndef MMS_MODULE_H +#define MMS_MODULE_H + +// MMS Module defines everything needed to integrate the MMS service +// inspector into the framework + +#include "framework/module.h" + +#define GID_MMS 152 + +#define MMS_NAME "mms" +#define MMS_HELP "mms inspection" + +extern THREAD_LOCAL snort::ProfileStats mms_prof; + +class MmsModule : public snort::Module +{ +public: + MmsModule(); + + unsigned get_gid() const override + { + return GID_MMS; + } + + const snort::RuleMap* get_rules() const override; + + const PegInfo* get_pegs() const override; + PegCount* get_counts() const override; + + snort::ProfileStats* get_profile() const override + { + return &mms_prof; + } + + Usage get_usage() const override + { + return INSPECT; + } + + bool is_bindable() const override + { + return true; + } +}; + +#endif + diff --git a/src/service_inspectors/mms/mms_splitter.cc b/src/service_inspectors/mms/mms_splitter.cc new file mode 100644 index 000000000..5d6137a57 --- /dev/null +++ b/src/service_inspectors/mms/mms_splitter.cc @@ -0,0 +1,361 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// mms_splitter.cc author Jared Rittle + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mms_splitter.h" + +#include "detection/detection_engine.h" +#include "events/event_queue.h" +#include "profiler/profiler.h" +#include "utils/util_ber.h" + +#include "mms.h" +#include "mms_decode.h" +#include "mms_module.h" +#include "util_tpkt.h" + +using namespace snort; + +static void reset_packet_data(Packet*, TpktFlowData*, CurPacketContext*); +static void update_flow_data(Packet*, TpktFlowData*, CurPacketContext*); +static bool populate_cur_pkt_ctx(Packet*, TpktFlowData*, CurPacketContext*); +static void update_cur_pkt_ctx_exit_offset(Packet*, TpktFlowData*, CurPacketContext*); + +static void process_mms_not_found_result(Packet*, TpktFlowData*, CurPacketContext*, + TpktAppliSearchStateType, Packet*); + +static void reset_packet_data(Packet* p, TpktFlowData* tpktfd, CurPacketContext* cur_pkt_ctx) +{ + assert(p->is_from_client() or p->is_from_server()); + if (p->is_from_client()) + { + tpktfd->reset_packet_data(TPKT_PACKET_DATA_DIRECTION__CLIENT); + cur_pkt_ctx->data = tpktfd->ssn_data.client_packet_data; + } + else if (p->is_from_server()) + { + tpktfd->reset_packet_data(TPKT_PACKET_DATA_DIRECTION__SERVER); + cur_pkt_ctx->data = tpktfd->ssn_data.server_packet_data; + } +} + +static void update_flow_data(Packet* p, TpktFlowData* tpktfd, CurPacketContext* cur_pkt_ctx) +{ + assert(p->is_from_client() or p->is_from_server()); + if (p->is_from_client()) + { + // set new packet data buffer + tpktfd->ssn_data.client_packet_len = cur_pkt_ctx->len; + tpktfd->ssn_data.client_start_offset = cur_pkt_ctx->start_offset; + tpktfd->ssn_data.client_splitter_offset = cur_pkt_ctx->splitter_offset; + tpktfd->ssn_data.client_exit_offset = cur_pkt_ctx->exit_offset; + } + else if (p->is_from_server()) + { + // set new packet data buffer + tpktfd->ssn_data.server_packet_len = cur_pkt_ctx->len; + tpktfd->ssn_data.server_start_offset = cur_pkt_ctx->start_offset; + tpktfd->ssn_data.server_splitter_offset = cur_pkt_ctx->splitter_offset; + tpktfd->ssn_data.server_exit_offset = cur_pkt_ctx->exit_offset; + } +} + +static bool populate_cur_pkt_ctx(Packet* p, TpktFlowData* tpktfd, CurPacketContext* cur_pkt_ctx) +{ + assert(p->is_from_client() or p->is_from_server()); + bool res = false; + if (p->is_from_client()) + { + cur_pkt_ctx->data = tpktfd->ssn_data.client_packet_data; + cur_pkt_ctx->len = tpktfd->ssn_data.client_packet_len; + cur_pkt_ctx->start_offset = tpktfd->ssn_data.client_start_offset; + cur_pkt_ctx->splitter_offset = tpktfd->ssn_data.client_splitter_offset; + cur_pkt_ctx->exit_offset = tpktfd->ssn_data.client_exit_offset; + res = true; + } + else if (p->is_from_server()) + { + cur_pkt_ctx->data = tpktfd->ssn_data.server_packet_data; + cur_pkt_ctx->len = tpktfd->ssn_data.server_packet_len; + cur_pkt_ctx->start_offset = tpktfd->ssn_data.server_start_offset; + cur_pkt_ctx->splitter_offset = tpktfd->ssn_data.server_splitter_offset; + cur_pkt_ctx->exit_offset = tpktfd->ssn_data.server_exit_offset; + res = true; + } + + return res; +} + +static void update_cur_pkt_ctx_exit_offset(Packet* p, TpktFlowData* tpktfd, + CurPacketContext* cur_pkt_ctx) +{ + assert(p->is_from_client() or p->is_from_server()); + if (p->is_from_client()) + { + cur_pkt_ctx->exit_offset = tpktfd->ssn_data.client_exit_offset; + } + else if (p->is_from_server()) + { + cur_pkt_ctx->exit_offset = tpktfd->ssn_data.server_exit_offset; + } +} + +static void process_mms_not_found_result(Packet* p, TpktFlowData* tpktfd, + CurPacketContext* cur_pkt_ctx, TpktAppliSearchStateType res, Packet* tmp_pkt) +{ + if (res == TPKT_APPLI_SEARCH_STATE__EXIT) + { + cur_pkt_ctx->splitter_offset = cur_pkt_ctx->exit_offset; + } + else + { + // track how far into the full message we have processed + // for the next loop + cur_pkt_ctx->splitter_offset = cur_pkt_ctx->len; + } + cur_pkt_ctx->start_offset = cur_pkt_ctx->exit_offset; + update_flow_data(p, tpktfd, cur_pkt_ctx); + delete tmp_pkt; +} + +MmsSplitter::MmsSplitter(bool b) : + StreamSplitter(b) +{ +} + +// MMS Splitter: +// Statefully inspects MMS traffic from the start of a session, +// Reads up until the start of the MMS message and then sets a flush point +// at the end of that message +StreamSplitter::Status MmsSplitter::scan(Packet* p, const uint8_t* data, uint32_t len, + uint32_t /*flags*/, uint32_t* fp) +{ + // create TPKT flow data and add it to the packet + TpktFlowData* tpktfd = (TpktFlowData*)p->flow->get_flow_data(TpktFlowData::inspector_id); + + if (!tpktfd) + { + TpktFlowData::init(); + tpktfd = new TpktFlowData; + p->flow->set_flow_data(tpktfd); + tpktfd->reset_packet_data(TPKT_PACKET_DATA_DIRECTION__SERVER); + tpktfd->reset_packet_data(TPKT_PACKET_DATA_DIRECTION__CLIENT); + } + + CurPacketContext cur_pkt_ctx; + if (!populate_cur_pkt_ctx(p, tpktfd, &cur_pkt_ctx)) + { + tpktfd->reset(); + return StreamSplitter::ABORT; + } + + // verify that there is enough space in the buffer for the new data + if (cur_pkt_ctx.len + len >= TPKT_PACKET_DATA_BUF_SIZE) + { + tpktfd->reset(); + return StreamSplitter::ABORT; + } + + // append the new data to the existing buffer + memcpy(cur_pkt_ctx.data + cur_pkt_ctx.len, data, len); + + // increase the packet length to include both the prior and the existing data lengths + cur_pkt_ctx.len += len; + + // create a cursor to keep track of position through later layers + // can't use the packet provided at the beginning as it isn't populated yet + Packet* tmp_pkt = new Packet(false); + tmp_pkt->data = cur_pkt_ctx.data; + tmp_pkt->dsize = cur_pkt_ctx.len; + + Cursor mms_cur = Cursor(tmp_pkt); + + mms_cur.set_pos(cur_pkt_ctx.start_offset); + + // make the best guess of what the starting layer + TpktEncapLayerType layer = get_next_tpkt_encap_layer(p, &mms_cur); + + // set the exit offset + update_cur_pkt_ctx_exit_offset(p, tpktfd, &cur_pkt_ctx); + + // reset the cursor position + mms_cur.set_pos(cur_pkt_ctx.start_offset); + + // start the parsing based on the layer determination + switch (layer) + { + case TPKT_ENCAP_LAYER__TPKT: + { + TpktAppliSearchStateType res = tpkt_search_from_tpkt_layer(&mms_cur); + if (res != TPKT_APPLI_SEARCH_STATE__MMS_FOUND) + { + process_mms_not_found_result(p, tpktfd, &cur_pkt_ctx, res, tmp_pkt); + return StreamSplitter::SEARCH; + } + break; + } + + case TPKT_ENCAP_LAYER__COTP: + { + TpktAppliSearchStateType res = tpkt_search_from_cotp_layer(&mms_cur); + if (res != TPKT_APPLI_SEARCH_STATE__MMS_FOUND) + { + process_mms_not_found_result(p, tpktfd, &cur_pkt_ctx, res, tmp_pkt); + return StreamSplitter::SEARCH; + } + break; + } + + case TPKT_ENCAP_LAYER__OSI_SESSION: + { + TpktAppliSearchStateType res = tpkt_search_from_osi_session_layer(&mms_cur, + OSI_SESSION_PROCESS_AS_DT__FALSE); + if (res != TPKT_APPLI_SEARCH_STATE__MMS_FOUND) + { + process_mms_not_found_result(p, tpktfd, &cur_pkt_ctx, res, tmp_pkt); + return StreamSplitter::SEARCH; + } + break; + } + + case TPKT_ENCAP_LAYER__OSI_PRES: + { + TpktAppliSearchStateType res = tpkt_search_from_osi_pres_layer(&mms_cur); + if (res != TPKT_APPLI_SEARCH_STATE__MMS_FOUND) + { + process_mms_not_found_result(p, tpktfd, &cur_pkt_ctx, res, tmp_pkt); + return StreamSplitter::SEARCH; + } + break; + } + + case TPKT_ENCAP_LAYER__OSI_ACSE: + { + TpktAppliSearchStateType res = tpkt_search_from_osi_acse_layer(&mms_cur); + if (res != TPKT_APPLI_SEARCH_STATE__MMS_FOUND) + { + process_mms_not_found_result(p, tpktfd, &cur_pkt_ctx, res, tmp_pkt); + return StreamSplitter::SEARCH; + } + break; + } + + case TPKT_ENCAP_LAYER__MMS: + // no need to do anything since the cursor is sitting on MMS + break; + + case TPKT_ENCAP_LAYER__PARTIAL: + cur_pkt_ctx.splitter_offset = cur_pkt_ctx.len; + update_flow_data(p, tpktfd, &cur_pkt_ctx); + delete tmp_pkt; + return StreamSplitter::SEARCH; + + // no valid layer found + default: + delete tmp_pkt; + tpktfd->reset(); + return StreamSplitter::ABORT; + } + + // save the MMS offset + MmsFlowData* mmsfd = (MmsFlowData*)p->flow->get_flow_data(MmsFlowData::inspector_id); + if (!mmsfd) + { + mmsfd = new MmsFlowData; + p->flow->set_flow_data(mmsfd); + mms_stats.sessions++; + } + + // store the offset to the start of the MMS message + mmsfd->set_mms_offset(mms_cur.get_pos()); + + // build a ber element + BerReader ber(mms_cur); + BerElement e; + + // read the first TLV of the MMS message + if (ber.read(mms_cur.start(), e)) + { + // add the size of the MMS message to the cursor so our flush point + // lands at the end of that message + // e.header_length holds the length of the Type and Length fields + // e.length holds the value of the Length field + if (mms_cur.add_pos(e.header_length + e.length)) + { + // make sure that the mms data fits within the reported packet length + if (mms_cur.get_pos() <= cur_pkt_ctx.len) + { + // set the flush point + // the fp is not necessarily just the current position when messages are pipelined + *fp = mms_cur.get_pos() - cur_pkt_ctx.splitter_offset; + + // clean up tracking details when there is no more data to parse + if (mms_cur.get_pos() < mms_cur.size()) + { + // calculate the new length by taking the full message length minus our current + // position + uint32_t new_len = cur_pkt_ctx.len - mms_cur.get_pos(); + + // create a buffer to hold the combined packet + uint8_t* new_data = new uint8_t[new_len]; + memcpy(new_data, cur_pkt_ctx.data + mms_cur.get_pos(), new_len); + + // clear the existing buffer + reset_packet_data(p, tpktfd, &cur_pkt_ctx); + + // copy the new data into the flowdata buffer + memcpy(cur_pkt_ctx.data, new_data, new_len); + + // deallocate the newdata + delete [] new_data; + + // update the remaining flowdata + cur_pkt_ctx.splitter_offset = 0; + cur_pkt_ctx.len = new_len; + cur_pkt_ctx.exit_offset = 0; + update_flow_data(p, tpktfd, &cur_pkt_ctx); + } + else + { + cur_pkt_ctx.len = 0; + cur_pkt_ctx.start_offset = 0; + cur_pkt_ctx.splitter_offset = 0; + cur_pkt_ctx.exit_offset = 0; + update_flow_data(p, tpktfd, &cur_pkt_ctx); + } + + // flush + delete tmp_pkt; + return StreamSplitter::FLUSH; + } + } + } + + // if execution gets here the reported length doesn't match up with mms + mmsfd->reset(); + + delete tmp_pkt; + tpktfd->reset(); + return StreamSplitter::ABORT; +} + diff --git a/src/service_inspectors/mms/mms_splitter.h b/src/service_inspectors/mms/mms_splitter.h new file mode 100644 index 000000000..c4114e01b --- /dev/null +++ b/src/service_inspectors/mms/mms_splitter.h @@ -0,0 +1,54 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// mms_splitter.h author Jared Rittle +// modeled after modbus_paf.h (author Russ Combs ) +// modeled after s7comm_paf.h (author Pradeep Damodharan ) + +#ifndef MMS_SPLITTER__H +#define MMS_SPLITTER__H + +// MMS Splitter provides a means of normalizing MMS traffic. It does so by +// splitting apart or reassembling pipelined messages as necessary, and then +// parses and strips off the encapsulation layers leaving only the MMS +// message itself for analysis. + +#include "stream/stream_splitter.h" + +struct CurPacketContext +{ + uint8_t* data; + uint32_t len; + uint32_t start_offset; + uint32_t splitter_offset; + uint32_t exit_offset; +}; + +class MmsSplitter : public snort::StreamSplitter +{ +public: + MmsSplitter(bool); + + Status scan(snort::Packet*, const uint8_t* data, uint32_t len, uint32_t flags, + uint32_t* fp) override; + + bool is_paf() override { return true; } +}; + +#endif + diff --git a/src/service_inspectors/mms/tpkt/cotp_decode.cc b/src/service_inspectors/mms/tpkt/cotp_decode.cc new file mode 100644 index 000000000..b5ef7aa3c --- /dev/null +++ b/src/service_inspectors/mms/tpkt/cotp_decode.cc @@ -0,0 +1,73 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// cotp_decode.cc author Jared Rittle + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "cotp_decode.h" + +using namespace snort; + +TpktAppliSearchStateType tpkt_internal_search_from_cotp_layer(Cursor* tpkt_cur) +{ + // make sure there is enough data in the cursor + if (tpkt_cur->length() >= sizeof(CotpHdr)) + { + // overlay the struct + const CotpHdr* hdr = (const CotpHdr*)tpkt_cur->start(); + + if (hdr->pdu_type == COTP_PDU_TYPE_DT_DATA) + { + // move cursor to the beginning of the next layer + if (tpkt_cur->add_pos(sizeof(CotpHdr))) + { + // if MMS is sent directly in COTP parse it from that layer immediately + switch (*tpkt_cur->start()) + { + case MMS_MSG__CONFIRMED_REQUEST: // fallthrough + case MMS_MSG__CONFIRMED_RESPONSE: // fallthrough + case MMS_MSG__CONFIRMED_ERROR: // fallthrough + case MMS_MSG__UNCONFIRMED: // fallthrough + case MMS_MSG__REJECT: // fallthrough + case MMS_MSG__CANCEL_REQUEST: // fallthrough + case MMS_MSG__CANCEL_RESPONSE: // fallthrough + case MMS_MSG__CANCEL_ERROR: // fallthrough + case MMS_MSG__INITIATE_REQUEST: // fallthrough + case MMS_MSG__INITIATE_RESPONSE: // fallthrough + case MMS_MSG__INITIATE_ERROR: // fallthrough + case MMS_MSG__CONCLUDE_REQUEST: // fallthrough + case MMS_MSG__CONCLUDE_RESPONSE: // fallthrough + case MMS_MSG__CONCLUDE_ERROR: + return TPKT_APPLI_SEARCH_STATE__MMS_FOUND; + + // otherwise check if the session layer is in use + default: + return tpkt_search_from_osi_session_layer(tpkt_cur, + OSI_SESSION_PROCESS_AS_DT__FALSE); + } + } + } + } + + // unsupported COTP + return TPKT_APPLI_SEARCH_STATE__EXIT; +} + diff --git a/src/service_inspectors/mms/tpkt/cotp_decode.h b/src/service_inspectors/mms/tpkt/cotp_decode.h new file mode 100644 index 000000000..d4a3e5973 --- /dev/null +++ b/src/service_inspectors/mms/tpkt/cotp_decode.h @@ -0,0 +1,50 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// cotp_decode.h author Jared Rittle + +#ifndef COTP_DECODE__H +#define COTP_DECODE__H + +// COTP Decode provides an interface for COTP message identification +// and parsing. +// It is intended to be used through `util_tpkt`, not directly + +#include "../util_tpkt.h" + +// COTP PDU Types +// other types exist, but at this time DT_DATA is the only one in use +enum CotpPduType +{ + COTP_PDU_TYPE_DT_DATA = 0x0F, +}; + +struct CotpHdr +{ + uint8_t length; + uint8_t dest_ref : 4; + uint8_t pdu_type : 4; + uint8_t tpdu_num : 7; + uint8_t last_data_unit : 1; +} +__attribute__((packed)); + +TpktAppliSearchStateType tpkt_internal_search_from_cotp_layer(Cursor*); + +#endif + diff --git a/src/service_inspectors/mms/tpkt/osi_acse_decode.cc b/src/service_inspectors/mms/tpkt/osi_acse_decode.cc new file mode 100644 index 000000000..9e85adeba --- /dev/null +++ b/src/service_inspectors/mms/tpkt/osi_acse_decode.cc @@ -0,0 +1,428 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// osi_acse_decode.cc author Jared Rittle + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "osi_acse_decode.h" + +#include "osi_session_decode.h" +#include "osi_pres_decode.h" + +using namespace snort; + +static TpktAppliSearchStateType decode_osi_acse_proto_ver_param(const BerElement*, Cursor*); +static TpktAppliSearchStateType decode_osi_acse_context_name_param(BerReader*, Cursor*); +static TpktAppliSearchStateType decode_osi_acse_user_data_param(BerReader*, Cursor*); +static TpktAppliSearchStateType decode_osi_acse_aarq_param(BerReader*, Cursor*); +static TpktAppliSearchStateType decode_osi_acse_aare_param(BerReader*, Cursor*); + +static TpktAppliSearchStateType decode_osi_acse_proto_ver_param(const BerElement* e, + Cursor* tpkt_cur) +{ + if (!tpkt_cur->add_pos(e->length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + return TPKT_APPLI_SEARCH_STATE__SEARCH; +} + +static TpktAppliSearchStateType decode_osi_acse_context_name_param(BerReader* ber, + Cursor* tpkt_cur) +{ + BerElement e; + + enum + { + ASO_CONTEXT_NAME_OID_TAG = 0x06, + }; + + // read message type tag + if (!process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_HEADER_AND_PAYLOAD)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + switch (e.type) + { + case ASO_CONTEXT_NAME_OID_TAG: + break; + + default: + tpkt_cur->set_pos(tpkt_cur->size()); + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + return TPKT_APPLI_SEARCH_STATE__SEARCH; +} + +static TpktAppliSearchStateType decode_osi_acse_user_data_param(BerReader* ber, Cursor* tpkt_cur) +{ + enum + { + USER_INFORMATION_ASSOCIATION_DATA_TAG = 0x28, + }; + + BerElement e; + + if (!process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_HEADER_ONLY)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + switch (e.type) + { + case USER_INFORMATION_ASSOCIATION_DATA_TAG: + { + // save off the current index for loop comparison later + const uint32_t user_data_idx = tpkt_cur->get_pos(); + + // context definition list length + const uint32_t user_data_length = e.length; + + // loop through the parameters + // minus one is to account for the cursor being 0-indexed and + // the length being 1-indexed + // set a maximum number of loops to the remaining number of + // bytes in the cursor divided by the smallest TLV (0x03) + const uint32_t max_loops = get_max_loops(tpkt_cur); + uint32_t loop_iteration = 0x00; + while (tpkt_cur->get_pos() < (user_data_idx + user_data_length - 1) and loop_iteration < + max_loops) + { + enum + { + ASSOCIATION_DATA_DIRECT_REFERENCE = 0x06, + ASSOCIATION_DATA_INDIRECT_REFERENCE = 0x02, + ASSOCIATION_DATA_ENCODING_ASN1 = 0xA0, + }; + + if (!process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_HEADER_ONLY)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + switch (e.type) + { + case ASSOCIATION_DATA_DIRECT_REFERENCE: // fallthrough + case ASSOCIATION_DATA_INDIRECT_REFERENCE: + if (!tpkt_cur->add_pos(e.length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + break; + + case ASSOCIATION_DATA_ENCODING_ASN1: + // don't increment the cursor past these bytes as this + // is actually the start of MMS + + // read the next tag to get the MMS size + if (process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_NONE)) + { + // make sure the cursor has the byte to be requested + if (tpkt_cur->get_pos() + e.length <= tpkt_cur->size()) + { + // return true indicating that we have found MMS data + return TPKT_APPLI_SEARCH_STATE__MMS_FOUND; + } + } + return TPKT_APPLI_SEARCH_STATE__EXIT; + + default: + tpkt_cur->set_pos(tpkt_cur->size()); + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + loop_iteration++; + } + + break; + } + + default: + tpkt_cur->set_pos(tpkt_cur->size()); + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + return TPKT_APPLI_SEARCH_STATE__SEARCH; +} + +static TpktAppliSearchStateType decode_osi_acse_aarq_param(BerReader* ber, Cursor* tpkt_cur) +{ + // define types relevant + enum + { + OSI_ACSE_AARQ_PROTO_VERSION = 0x80, + OSI_ACSE_AARQ_ASO_CONTEXT_NAME = 0xA1, + OSI_ACSE_AARQ_CALLED_AP_TITLE = 0xA2, + OSI_ACSE_AARQ_CALLED_AE_QUALIFIER = 0xA3, + OSI_ACSE_AARQ_CALLING_AP_TITLE = 0xA6, + OSI_ACSE_AARQ_CALLING_AE_QUALIFIER = 0xA7, + OSI_ACSE_AARQ_USER_INFORMATION = 0xBE, + }; + + BerElement e; + + if (!process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_HEADER_ONLY)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + switch (e.type) + { + case OSI_ACSE_AARQ_PROTO_VERSION: + return decode_osi_acse_proto_ver_param(&e, tpkt_cur); + + case OSI_ACSE_AARQ_ASO_CONTEXT_NAME: + return decode_osi_acse_context_name_param(ber, tpkt_cur); + + case OSI_ACSE_AARQ_CALLED_AP_TITLE: + enum + { + CALLED_AP_TITLE_OID_TAG = 0x06, + }; + + if (!process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_HEADER_AND_PAYLOAD)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + switch (e.type) + { + case CALLED_AP_TITLE_OID_TAG: + break; + + default: + tpkt_cur->set_pos(tpkt_cur->size()); + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + + case OSI_ACSE_AARQ_CALLED_AE_QUALIFIER: + enum + { + CALLED_AE_QUALIFIER_VALUE_TAG = 0x02, + }; + + if (!process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_HEADER_AND_PAYLOAD)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + switch (e.type) + { + case CALLED_AE_QUALIFIER_VALUE_TAG: + break; + + default: + tpkt_cur->set_pos(tpkt_cur->size()); + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + + case OSI_ACSE_AARQ_CALLING_AP_TITLE: + enum + { + CALLING_AP_TITLE_OID_TAG = 0x06, + }; + + if (!process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_HEADER_AND_PAYLOAD)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + switch (e.type) + { + case CALLING_AP_TITLE_OID_TAG: + break; + + default: + tpkt_cur->set_pos(tpkt_cur->size()); + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + + case OSI_ACSE_AARQ_CALLING_AE_QUALIFIER: + enum + { + CALLING_AE_QUALIFIER_VALUE_TAG = 0x02, + }; + + if (!process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_HEADER_AND_PAYLOAD)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + switch (e.type) + { + case CALLING_AE_QUALIFIER_VALUE_TAG: + break; + + default: + tpkt_cur->set_pos(tpkt_cur->size()); + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + + case OSI_ACSE_AARQ_USER_INFORMATION: + return decode_osi_acse_user_data_param(ber, tpkt_cur); + + default: + tpkt_cur->set_pos(tpkt_cur->size()); + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + return TPKT_APPLI_SEARCH_STATE__SEARCH; +} + +static TpktAppliSearchStateType decode_osi_acse_aare_param(BerReader* ber, Cursor* tpkt_cur) +{ + // define types relevant + enum + { + OSI_ACSE_AARE_PROTO_VERSION = 0x80, + OSI_ACSE_AARE_ASO_CONTEXT_NAME = 0xA1, + OSI_ACSE_AARE_RESULT = 0xA2, + OSI_ACSE_AARE_RESULT_SOURCE_DIAGNOSTIC = 0xA3, + OSI_ACSE_AARE_RESPONDING_AP_TITLE = 0xA4, + OSI_ACSE_AARE_RESPONDING_AE_QUALIFIER = 0xA5, + OSI_ACSE_AARE_USER_INFORMATION = 0xBE, + }; + + BerElement e; + + if (!process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_HEADER_ONLY)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + switch (e.type) + { + case OSI_ACSE_AARE_PROTO_VERSION: + return decode_osi_acse_proto_ver_param(&e, tpkt_cur); + + case OSI_ACSE_AARE_ASO_CONTEXT_NAME: + return decode_osi_acse_context_name_param(ber, tpkt_cur); + + case OSI_ACSE_AARE_RESULT: + if (!tpkt_cur->add_pos(e.length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + break; + + case OSI_ACSE_AARE_RESULT_SOURCE_DIAGNOSTIC: // fallthrough + case OSI_ACSE_AARE_RESPONDING_AP_TITLE: // fallthrough + case OSI_ACSE_AARE_RESPONDING_AE_QUALIFIER: + if (!tpkt_cur->add_pos(e.length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + break; + + case OSI_ACSE_AARE_USER_INFORMATION: + return decode_osi_acse_user_data_param(ber, tpkt_cur); + + default: + tpkt_cur->set_pos(tpkt_cur->size()); + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + return TPKT_APPLI_SEARCH_STATE__SEARCH; +} + +TpktAppliSearchStateType tpkt_internal_search_from_osi_acse_layer(Cursor* tpkt_cur) +{ + // initialize a BER reader + BerReader ber(*tpkt_cur); + BerElement e; + + // read message type tag + if (!process_next_ber_tag(&ber, &e, tpkt_cur, BER_TAG__ADD_HEADER_ONLY)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + // determine message type + switch (e.type) + { + case OSI_ACSE_AARQ: + { + const uint32_t aarq_idx = tpkt_cur->get_pos(); + const uint32_t aarq_length = e.length; + + // loop through the parameters + // minus one is to account for the cursor being 0-indexed and + // the length being 1-indexed + // set a maximum number of loops to the remaining number of + // bytes in the cursor divided by the smallest TLV (0x03) + const uint32_t max_loops = get_max_loops(tpkt_cur); + uint32_t loop_iteration = 0x00; + while (tpkt_cur->get_pos() < (aarq_idx + aarq_length - 1) and loop_iteration < max_loops) + { + TpktAppliSearchStateType res = decode_osi_acse_aarq_param(&ber, tpkt_cur); + if (res != TPKT_APPLI_SEARCH_STATE__SEARCH) + { + return res; + } + loop_iteration++; + } + break; + } + + case OSI_ACSE_AARE: + { + const uint32_t aare_idx = tpkt_cur->get_pos(); + const uint32_t aare_length = e.length; + + // loop through the parameters + // minus one is to account for the cursor being 0-indexed and + // the length being 1-indexed + // set a maximum number of loops to the remaining number of + // bytes in the cursor divided by the smallest TLV (0x03) + const uint32_t max_loops = get_max_loops(tpkt_cur); + uint32_t loop_iteration = 0x00; + while (tpkt_cur->get_pos() < (aare_idx + aare_length - 1) and loop_iteration < max_loops) + { + TpktAppliSearchStateType res = decode_osi_acse_aare_param(&ber, tpkt_cur); + if (res != TPKT_APPLI_SEARCH_STATE__SEARCH) + { + return res; + } + loop_iteration++; + } + + break; + } + + default: + tpkt_cur->set_pos(tpkt_cur->size()); + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + return TPKT_APPLI_SEARCH_STATE__SEARCH; +} + diff --git a/src/service_inspectors/mms/tpkt/osi_acse_decode.h b/src/service_inspectors/mms/tpkt/osi_acse_decode.h new file mode 100644 index 000000000..427c39c77 --- /dev/null +++ b/src/service_inspectors/mms/tpkt/osi_acse_decode.h @@ -0,0 +1,40 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// osi_acse_decode.h author Jared Rittle + +#ifndef OSI_ACSE_DECODE__H +#define OSI_ACSE_DECODE__H + +// OSI ACSE Decode provides an interface for ACSE message identification +// and parsing. +// It is intended to be used through `util_tpkt`, not directly + +#include "../util_tpkt.h" + +enum +{ + OSI_ACSE_AARQ = 0x60, + OSI_ACSE_AARE = 0x61, +}; + +// function stubs +TpktAppliSearchStateType tpkt_internal_search_from_osi_acse_layer(Cursor*); + +#endif + diff --git a/src/service_inspectors/mms/tpkt/osi_pres_decode.cc b/src/service_inspectors/mms/tpkt/osi_pres_decode.cc new file mode 100644 index 000000000..3baf39570 --- /dev/null +++ b/src/service_inspectors/mms/tpkt/osi_pres_decode.cc @@ -0,0 +1,540 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// osi_pres_decode.cc author Jared Rittle + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "osi_pres_decode.h" + +#include "service_inspectors/mms/mms_decode.h" + +#include "osi_session_decode.h" + +using namespace snort; + +static OsiPresPpduModeSelectorType resolve_mode_selector(BerReader*, Cursor*, uint32_t); +static TpktAppliSearchStateType decode_osi_pres_context_list_item(BerReader*, Cursor*); +static TpktAppliSearchStateType decode_osi_pres_normal_mode_params(BerReader*, Cursor*); + +bool process_next_ber_tag(BerReader* ber, BerElement* e, Cursor* tpkt_cur, BerTagProcessType + process_type) +{ + if (ber->read(tpkt_cur->start(), *e)) + { + // determine if the cursor needs to be incremented by only the header + // length or both the header and payload length + switch (process_type) + { + case BER_TAG__ADD_NONE: + return true; + + case BER_TAG__ADD_HEADER_ONLY: + return tpkt_cur->add_pos(e->header_length); + + case BER_TAG__ADD_HEADER_AND_PAYLOAD: + return tpkt_cur->add_pos(e->header_length + e->length); + } + } + return false; +} + +static OsiPresPpduModeSelectorType resolve_mode_selector(BerReader* ber, Cursor* tpkt_cur, uint32_t + spdu_type) +{ + // define types relevant for the mode selector + enum + { + OSI_PRES_TYPE_SEQ = 0x31, + OSI_PRES_MODE_VALUE = 0x80, + OSI_PRES_MODE_SELECTOR_SEQ = 0xA0, + }; + + BerElement e; + + switch (spdu_type) + { + case OSI_SESSION_SPDU__CN: // fallthrough + case OSI_SESSION_SPDU__AC: + // read the CP-type PDU + if (process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_NONE)) + { + if (e.type == OSI_PRES_TYPE_SEQ) + { + // increment the cursor to point to the next tag + if (tpkt_cur->add_pos(e.header_length)) + { + // read the mode selector value + if (process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_NONE)) + { + if (e.type == OSI_PRES_MODE_SELECTOR_SEQ) + { + // increment the cursor to point to the next tag + if (tpkt_cur->add_pos(e.header_length)) + { + // read the mode selector value + if (process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_NONE)) + { + if (e.type == OSI_PRES_MODE_VALUE) + { + if (!tpkt_cur->add_pos(e.header_length + e.length)) + { + break; + } + if ((OsiPresPpduModeSelectorType) * e.data == + OSI_PRES_MODE__NORMAL) + { + return OSI_PRES_MODE__NORMAL; + } + } + } + } + } + } + } + } + } + break; + } + + tpkt_cur->set_pos(tpkt_cur->size()); + return OSI_PRES_MODE__INVALID; +} + +static TpktAppliSearchStateType decode_osi_pres_context_list_item(BerReader* ber, Cursor* tpkt_cur) +{ + // define types relevant for the context definition result list + enum OsiPresContextListItemType + { + OSI_PRES_CONTEXT_LIST_PRES_CONTEXT_ID = 0x02, + OSI_PRES_CONTEXT_LIST_ABSTRACT_SYNTAX_NAME = 0x06, + OSI_PRES_CONTEXT_LIST_TRANSFER_SYNTAX_NAME_LIST = 0x30, + OSI_PRES_CONTEXT_LIST_RESULT = 0x80, + OSI_PRES_CONTEXT_LIST_TRANSFER_SYNTAX_NAME = 0x81, + OSI_PRES_CONTEXT_LIST_PRES_DATA_VALUES = 0xA0, + }; + + // save off the current index for loop comparison later + uint32_t context_list_param_idx = tpkt_cur->get_pos(); + + // null the context identifier + uint8_t context_id = OSI_PRES_CONTEXT_ID__NULL; + + BerElement e; + + if (!process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_HEADER_ONLY)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + // context definition list length + uint32_t context_list_length = e.length; + + // loop through the parameters + // set a maximum number of loops to the remaining number of + // bytes in the cursor divided by the smallest TLV (0x03) + uint32_t max_loops = get_max_loops(tpkt_cur); + uint32_t max_pos = get_max_pos(context_list_param_idx, context_list_length); + uint32_t loop_iteration = 0x00; + while (tpkt_cur->get_pos() < max_pos and loop_iteration < max_loops) + { + if (!process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_NONE)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + switch (e.type) + { + case OSI_PRES_CONTEXT_LIST_PRES_CONTEXT_ID: + { + struct OsiPresContextListPresContextId + { + uint8_t id; + }; + + const OsiPresContextListPresContextId* param = (const + OsiPresContextListPresContextId*)e.data; + + switch (param->id) + { + case OSI_PRES_CONTEXT_ID__ACSE: + context_id = OSI_PRES_CONTEXT_ID__ACSE; + break; + + case OSI_PRES_CONTEXT_ID__MMS: + context_id = OSI_PRES_CONTEXT_ID__MMS; + break; + + default: + tpkt_cur->set_pos(tpkt_cur->size()); + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + // increment cursor to next group + if (!tpkt_cur->add_pos(e.header_length + e.length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + } + + case OSI_PRES_CONTEXT_LIST_ABSTRACT_SYNTAX_NAME: + // increment cursor to next group + if (!tpkt_cur->add_pos(e.header_length + e.length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + + case OSI_PRES_CONTEXT_LIST_TRANSFER_SYNTAX_NAME_LIST: + // increment cursor to next group + if (!tpkt_cur->add_pos(e.header_length + e.length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + + case OSI_PRES_CONTEXT_LIST_RESULT: + // increment cursor to next group + if (!tpkt_cur->add_pos(e.header_length + e.length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + + case OSI_PRES_CONTEXT_LIST_TRANSFER_SYNTAX_NAME: + // increment cursor to next group + if (!tpkt_cur->add_pos(e.header_length + e.length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + + case OSI_PRES_CONTEXT_LIST_PRES_DATA_VALUES: + enum OsiPresDataValuesType + { + OSI_PRES_SINGLE_ASN1_TYPE = 0xA0, + }; + + // get the data value type + if (!process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_HEADER_ONLY)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + // at this time only single asn1 type is expected + switch (e.type) + { + case OSI_PRES_SINGLE_ASN1_TYPE: + switch (context_id) + { + case OSI_PRES_CONTEXT_ID__ACSE: + return tpkt_search_from_osi_acse_layer(tpkt_cur); + + case OSI_PRES_CONTEXT_ID__MMS: + // read the next tag to get the MMS size + if (process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_NONE)) + { + if (tpkt_cur->get_pos() + e.length <= tpkt_cur->size()) + { + // return true indicating that we have found MMS data + return TPKT_APPLI_SEARCH_STATE__MMS_FOUND; + } + } + return TPKT_APPLI_SEARCH_STATE__EXIT; + + default: + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + break; + + default: + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + break; + } + + loop_iteration++; + } + + return TPKT_APPLI_SEARCH_STATE__SEARCH; +} + +static TpktAppliSearchStateType decode_osi_pres_normal_mode_params(BerReader* ber, + Cursor* tpkt_cur) +{ + // define types relevant for the normal mode params + enum OsiPresNormalModeParamsType + { + OSI_PRES_NORMAL_PARAM_PROTO_VERSION = 0x80, + OSI_PRES_NORMAL_PARAM_CALLING_PRES_SELECTOR = 0x81, + OSI_PRES_NORMAL_PARAM_CALLED_PRES_SELECTOR = 0x82, + OSI_PRES_NORMAL_PARAM_RESPONDING_PRES_SELECTOR = 0x83, + OSI_PRES_NORMAL_PARAM_PRES_CONTEXT_DEFINITION_LIST = 0xA4, + OSI_PRES_NORMAL_PARAM_PRES_CONTEXT_LIST_ITEM = 0x30, + OSI_PRES_NORMAL_PARAM_PRES_CONTEXT_DEFINITION_RESULT_LIST = 0xA5, + OSI_PRES_NORMAL_PARAM_PRES_REQS = 0x88, + OSI_PRES_NORMAL_PARAM_USER_DATA = 0x61, + }; + + BerElement e; + + // save off the current index for loop comparison later + uint32_t normal_mode_param_idx = tpkt_cur->get_pos(); + + // increment the cursor to point to the next tag + if (!process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_HEADER_ONLY)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + // get the normal mode parameters length + uint32_t normal_mode_param_length = e.total_length - e.header_length; + + // loop through the parameters + // set a maximum number of loops to the remaining number of + // bytes in the cursor divided by the smallest TLV (0x03) + uint32_t normal_mode_param_max_loops = get_max_loops(tpkt_cur); + uint32_t normal_mode_param_max_pos = get_max_pos(normal_mode_param_idx, + normal_mode_param_length); + uint32_t normal_mode_param_loop_iteration = 0x00; + while (tpkt_cur->get_pos() < normal_mode_param_max_pos and normal_mode_param_loop_iteration < + normal_mode_param_max_loops) + { + if (!process_next_ber_tag(ber, &e, tpkt_cur, BER_TAG__ADD_NONE)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + switch (e.type) + { + case OSI_PRES_NORMAL_PARAM_PROTO_VERSION: + if (!tpkt_cur->add_pos(e.header_length + e.length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + + case OSI_PRES_NORMAL_PARAM_CALLING_PRES_SELECTOR: + if (!tpkt_cur->add_pos(e.header_length + e.length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + + case OSI_PRES_NORMAL_PARAM_CALLED_PRES_SELECTOR: + if (!tpkt_cur->add_pos(e.header_length + e.length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + + case OSI_PRES_NORMAL_PARAM_RESPONDING_PRES_SELECTOR: + if (!tpkt_cur->add_pos(e.header_length + e.length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + + case OSI_PRES_NORMAL_PARAM_PRES_CONTEXT_DEFINITION_LIST: + { + if (!tpkt_cur->add_pos(e.header_length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + // save off the current index for loop comparison later + uint32_t context_definition_list_idx = tpkt_cur->get_pos(); + + // context definition list length + uint32_t context_definition_list_length = e.length; + + // loop through the parameters + // set a maximum number of loops to the remaining number of + // bytes in the cursor divided by the smallest TLV (0x03) + uint32_t context_list_item_max_loops = get_max_loops(tpkt_cur); + uint32_t max_pos = get_max_pos(context_definition_list_idx, + context_definition_list_length); + uint32_t context_list_item_loop_iteration = 0x00; + while (tpkt_cur->get_pos() < max_pos and context_list_item_loop_iteration < + context_list_item_max_loops) + { + TpktAppliSearchStateType res = decode_osi_pres_context_list_item(ber, tpkt_cur); + if (res != TPKT_APPLI_SEARCH_STATE__SEARCH) + { + return res; + } + context_list_item_loop_iteration++; + } + + break; + } + + case OSI_PRES_NORMAL_PARAM_PRES_CONTEXT_DEFINITION_RESULT_LIST: + { + if (!tpkt_cur->add_pos(e.header_length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + // save off the current index for loop comparison later + uint32_t context_definition_result_list_idx = tpkt_cur->get_pos(); + + // context definition list length + uint32_t context_definition_result_list_length = e.length; + + // loop through the parameters + // set a maximum number of loops to the remaining number of + // bytes in the cursor divided by the smallest TLV (0x03) + uint32_t context_list_item_max_loops = get_max_loops(tpkt_cur); + uint32_t context_list_item_max_pos = get_max_pos( + context_definition_result_list_idx, context_definition_result_list_length); + uint32_t context_list_item_loop_iteration = 0x00; + while (tpkt_cur->get_pos() < context_list_item_max_pos and + context_list_item_loop_iteration < context_list_item_max_loops) + { + TpktAppliSearchStateType res = decode_osi_pres_context_list_item(ber, tpkt_cur); + if (res != TPKT_APPLI_SEARCH_STATE__SEARCH) + { + return res; + } + context_list_item_loop_iteration++; + } + + break; + } + + case OSI_PRES_NORMAL_PARAM_PRES_REQS: + if (!tpkt_cur->add_pos(e.header_length + e.length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + + case OSI_PRES_NORMAL_PARAM_USER_DATA: + { + TpktAppliSearchStateType res = TPKT_APPLI_SEARCH_STATE__EXIT; + if (tpkt_cur->add_pos(e.header_length)) + { + // Decode the user data item + // Returning directly here as this item is the determining factor + // on whether or not a sub protocol is contained within the data + // No need to loop here as the User Data item is not a list + res = decode_osi_pres_context_list_item(ber, tpkt_cur); + } + + return res; + } + + default: + tpkt_cur->set_pos(tpkt_cur->size()); + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + normal_mode_param_loop_iteration++; + } + + return TPKT_APPLI_SEARCH_STATE__SEARCH; +} + +// process the data with the starting assumption of an OSI Presentation layer +TpktAppliSearchStateType tpkt_internal_search_from_osi_pres_layer(Cursor* tpkt_cur) +{ + // get the flow data + Packet* p = DetectionEngine::get_current_packet(); + TpktFlowData* tpktfd = (TpktFlowData*)p->flow->get_flow_data(TpktFlowData::inspector_id); + + // if flow data cannot be found something went wrong and we should just + // exit. the flow data should not be initialized at this point + if (tpktfd) + { + // retrieve the SPDU type in our flow data + OsiSessionSpduType spdu_type = tpktfd->ssn_data.cur_spdu_type; + + // prepare to read the BER data + BerReader ber(*tpkt_cur); + BerElement e; + + // change parsing based on what SPDU type we noticed in the Session layer + switch (spdu_type) + { + // Give Tokens or Data Transfer + case OSI_SESSION_SPDU__GT_DT: + // get the normal mode parameters length + if (process_next_ber_tag(&ber, &e, tpkt_cur, BER_TAG__ADD_HEADER_ONLY)) + { + return decode_osi_pres_context_list_item(&ber, tpkt_cur); + } + break; + + // Connect + case OSI_SESSION_SPDU__CN: + { + // determine the mode + uint32_t mode = resolve_mode_selector(&ber, tpkt_cur, spdu_type); + + switch (mode) + { + case OSI_PRES_MODE__NORMAL: + return decode_osi_pres_normal_mode_params(&ber, tpkt_cur); + + default: + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + } + + // Accept + case OSI_SESSION_SPDU__AC: + { + // determine the mode + uint32_t mode = resolve_mode_selector(&ber, tpkt_cur, spdu_type); + + switch (mode) + { + case OSI_PRES_MODE__NORMAL: + return decode_osi_pres_normal_mode_params(&ber, tpkt_cur); + + default: + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + } + + default: + break; + } + } + + return TPKT_APPLI_SEARCH_STATE__EXIT; +} + diff --git a/src/service_inspectors/mms/tpkt/osi_pres_decode.h b/src/service_inspectors/mms/tpkt/osi_pres_decode.h new file mode 100644 index 000000000..1df6e1297 --- /dev/null +++ b/src/service_inspectors/mms/tpkt/osi_pres_decode.h @@ -0,0 +1,58 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// osi_pres_decode.h author Jared Rittle + +#ifndef OSI_PRES_DECODE__H +#define OSI_PRES_DECODE__H + +// OSI PRES Decode provides an interface for PRES message identification +// and parsing. +// It is intended to be used through `util_tpkt`, not directly + +#include "../util_tpkt.h" + +// named identifiers for ber tag processing +enum BerTagProcessType +{ + BER_TAG__ADD_NONE = 0x00, + BER_TAG__ADD_HEADER_ONLY = 0x01, + BER_TAG__ADD_HEADER_AND_PAYLOAD = 0x02, +}; + +// OSI Session SPDU Types +enum OsiPresPpduModeSelectorType +{ + OSI_PRES_MODE__INVALID = 0x00, + OSI_PRES_MODE__NORMAL = 0x01, +}; + +// OSI Presentation Context Identifiers +enum OsiPresContextIdentifierType +{ + OSI_PRES_CONTEXT_ID__NULL = 0x00, + OSI_PRES_CONTEXT_ID__ACSE = 0x01, + OSI_PRES_CONTEXT_ID__MMS = 0x03, +}; + +TpktAppliSearchStateType tpkt_internal_search_from_osi_pres_layer(Cursor*); + +bool process_next_ber_tag(snort::BerReader*, snort::BerElement*, Cursor*, BerTagProcessType); + +#endif + diff --git a/src/service_inspectors/mms/tpkt/osi_session_decode.cc b/src/service_inspectors/mms/tpkt/osi_session_decode.cc new file mode 100644 index 000000000..00bda4a7a --- /dev/null +++ b/src/service_inspectors/mms/tpkt/osi_session_decode.cc @@ -0,0 +1,367 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// osi_session_decode.cc author Jared Rittle + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "osi_session_decode.h" + +#include "service_inspectors/mms/mms_decode.h" + +using namespace snort; + +static TpktAppliSearchStateType resolve_spdu_parameter(Cursor*, const OsiSessionHdr*); + +uint32_t get_max_loops(Cursor* tpkt_cur) +{ + // set a maximum number of loops to the remaining number of + // bytes in the cursor divided by the smallest TLV (0x03) + return (tpkt_cur->size() - tpkt_cur->get_pos()) / TPKT_SMALLEST_TLV_SIZE; +} + +uint32_t get_max_pos(uint32_t idx, uint32_t length) +{ + // minus one is to account for the cursor being 0-indexed and + // the length being 1-indexed + return idx + length - 1; +} + +// parse the SPDU param at the current cursor position looking for an +// indication that the desired protocol is in use +static TpktAppliSearchStateType resolve_spdu_parameter(Cursor* tpkt_cur, const OsiSessionHdr* hdr) +{ + const OsiSessionSpduParameterHdr* generic_hdr = (const + OsiSessionSpduParameterHdr*)tpkt_cur->start(); + + switch (generic_hdr->type) + { + case OSI_SESSION_SPDU_PARAM__CN_ACCEPT_ITEM: + { + struct OsiSessionSpduConnectAcceptItem + { + OsiSessionSpduParameterHdr param_hdr; + }; + + // overlay the appropriate struct + const OsiSessionSpduConnectAcceptItem* param = (const + OsiSessionSpduConnectAcceptItem*)tpkt_cur->start(); + + // param length is expected to be 0x06 + + // increment the idx to account for the header bytes + if (!tpkt_cur->add_pos(sizeof(OsiSessionSpduParameterHdr) + param->param_hdr.length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + } + + case OSI_SESSION_SPDU_PARAM__SESSION_REQUIREMENT: + { + struct OsiSessionSpduSessionRequirement + { + OsiSessionSpduParameterHdr param_hdr; + uint16_t flags; + }; + + // overlay the appropriate struct + const OsiSessionSpduSessionRequirement* param = (const + OsiSessionSpduSessionRequirement*)tpkt_cur->start(); + + bool checks_pass = false; + + // param length is expected to be 0x02 + // flags value is expected to be 0x02 + + // make sure this is only occurring in a CONNECT or ACCEPT SPDU + if (hdr->spdu_type == OSI_SESSION_SPDU__CN or hdr->spdu_type == OSI_SESSION_SPDU__AC) + { + if (tpkt_cur->add_pos(sizeof(OsiSessionSpduParameterHdr) + param->param_hdr.length)) + { + checks_pass = true; + } + } + + if (!checks_pass) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + } + + case OSI_SESSION_SPDU_PARAM__CALLING_SESSION_SELECTOR: + { + struct OsiSessionSpduCallingSessionSelector + { + OsiSessionSpduParameterHdr param_hdr; + uint16_t calling_session_selector; + }; + + // overlay the appropriate struct + const OsiSessionSpduCallingSessionSelector* param = (const + OsiSessionSpduCallingSessionSelector*)tpkt_cur->start(); + + // param length is expected to be 0x02 + + // make sure this is only occurring in a CONNECT + if (hdr->spdu_type != OSI_SESSION_SPDU__CN) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + // update the cursor to include header and parameter + if (!tpkt_cur->add_pos(sizeof(OsiSessionSpduParameterHdr) + param->param_hdr.length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + } + + case OSI_SESSION_SPDU_PARAM__CALLED_SESSION_SELECTOR: + { + struct OsiSessionSpduCalledSessionSelector + { + OsiSessionSpduParameterHdr param_hdr; + uint16_t called_session_selector; + }; + + // overlay the appropriate struct + const OsiSessionSpduCalledSessionSelector* param = (const + OsiSessionSpduCalledSessionSelector*)tpkt_cur->start(); + + // param length is expected to be 0x02 + + // make sure this is only occurring in a CONNECT + if (hdr->spdu_type != OSI_SESSION_SPDU__CN) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + // update the cursor to include header and parameter + if (!tpkt_cur->add_pos(sizeof(OsiSessionSpduParameterHdr) + param->param_hdr.length)) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + break; + } + + case OSI_SESSION_SPDU_PARAM__SESSION_USER_DATA: + { + struct OsiSessionSpduSessionUserData + { + OsiSessionSpduParameterHdr param_hdr; + }; + + // overlay the appropriate struct + const OsiSessionSpduSessionUserData* param = (const + OsiSessionSpduSessionUserData*)tpkt_cur->start(); + + // param length must be less than the reported session layer length + if (param->param_hdr.length >= hdr->length) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + TpktAppliSearchStateType res = TPKT_APPLI_SEARCH_STATE__EXIT; + // increment the cursor to account for the header bytes + if (tpkt_cur->add_pos(sizeof(OsiSessionSpduParameterHdr))) + { + // pass off to the presentation layer for additional parsing + res = tpkt_search_from_osi_pres_layer(tpkt_cur); + } + return res; + } + } + + return TPKT_APPLI_SEARCH_STATE__SEARCH; +} + +// process the data with the starting assumption of an OSI Session layer +// since the Give Tokens (GT) and Data Transfer (DT) layers have the same +// values, the `process_as_data_transfer` variable is included for use +// making that distinction. By default it is set to `false` +TpktAppliSearchStateType tpkt_internal_search_from_osi_session_layer(Cursor* tpkt_cur, bool + process_as_data_transfer) +{ + // overlay the session header + const OsiSessionHdr* hdr = (const OsiSessionHdr*)tpkt_cur->start(); + + // get the flow data + const Packet* p = DetectionEngine::get_current_packet(); + TpktFlowData* tpktfd = (TpktFlowData*)p->flow->get_flow_data(TpktFlowData::inspector_id); + + bool checks_pass = false; + + // if flow data cannot be found something went wrong and we should just + // exit. the flow data should not be initialized at this point + if (tpktfd) + { + // track the SPDU type for when we get to the presentation layer + // this is tracked in the session data so that the value can be + // preserved even when a message is split at the start of the + // presentation layer + tpktfd->ssn_data.cur_spdu_type = (OsiSessionSpduType)hdr->spdu_type; + + // length must be smaller than or equal to the number of bytes remaining + // this is the case because in the TPKT stage we will have waited to + // start processing until all of the data for that layer was collected. + if (hdr->length <= tpkt_cur->size()) + { + // increase the `tpkt_cur` to point to the data following + // the OSI Session Layer + if (tpkt_cur->add_pos(sizeof(OsiSessionHdr))) + { + checks_pass = true; + } + } + } + + if (!checks_pass) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + // determine parsing from here on out based on the SPDU type + switch (hdr->spdu_type) + { + // Give Tokens or Data Transfer + case OSI_SESSION_SPDU__GT_DT: + // bail when the length field of this SPDU is non-null + if (hdr->length != 0x00) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + // Annoyingly the SPDU type for `Give Tokens` and + // `Data Transfer` are the same. Fortunately the + // `Data Transfer` seems to require a `Give Tokens` + // layer to have appeared first + if (process_as_data_transfer) + { + // treat this layer as a `Data Transfer` SPDU + // since there is no data allowed with the `Data Transfer` + // SPDU there is no need to increment the cursor + + // session layer evaluation has passed and processing gets + // handed off to the presentation layer + const TpktAppliSearchStateType res = tpkt_search_from_osi_pres_layer(tpkt_cur); + + // return the result when a definitive answer has been found + if (res != TPKT_APPLI_SEARCH_STATE__SEARCH) + { + return res; + } + } + else + { + // treat this layer as a `Give Tokens` SPDU + // since there is no data allowed with the `Give Tokens` + // SPDU there is no need to increment the cursor + + // next session layer should be treated as a DT SPDU + TpktAppliSearchStateType res = tpkt_search_from_osi_session_layer(tpkt_cur, + OSI_SESSION_PROCESS_AS_DT__TRUE); + + // return the result when a definitive answer has been found + if (res != TPKT_APPLI_SEARCH_STATE__SEARCH) + { + return res; + } + } + + break; + + // Connect + case OSI_SESSION_SPDU__CN: + { + // bail when the length field of this SPDU is non-null + if (hdr->length == 0x00) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + // save off the current index for loop comparison later + uint32_t connect_spdu_data_idx = tpkt_cur->get_pos(); + + // loop through the parameters + // minus one is to account for the cursor being 0-indexed and + // the length being 1-indexed + // set a maximum number of loops to the remaining number of + // bytes in the cursor divided by the smallest TLV (0x03) + uint32_t max_loops = get_max_loops(tpkt_cur); + uint32_t max_pos = get_max_pos(connect_spdu_data_idx, hdr->length); + uint32_t loop_iteration = 0x00; + while (tpkt_cur->get_pos() < max_pos and loop_iteration < max_loops) + { + TpktAppliSearchStateType res = resolve_spdu_parameter(tpkt_cur, hdr); + if (res != TPKT_APPLI_SEARCH_STATE__SEARCH) + { + return res; + } + loop_iteration++; + } + + break; + } + + // Accept + case OSI_SESSION_SPDU__AC: + { + // alert when the length field of this SPDU is null + if (hdr->length == 0x00) + { + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + // save off the current index for loop comparison later + uint32_t accept_spdu_data_idx = tpkt_cur->get_pos(); + + // loop through the parameters + // minus one is to account for the cursor being 0-indexed and + // the length being 1-indexed + uint32_t max_loops = get_max_loops(tpkt_cur); + uint32_t max_pos = get_max_pos(accept_spdu_data_idx, hdr->length); + uint32_t loop_iteration = 0x00; + while (tpkt_cur->get_pos() < max_pos and loop_iteration < max_loops) + { + TpktAppliSearchStateType res = resolve_spdu_parameter(tpkt_cur, hdr); + if (res != TPKT_APPLI_SEARCH_STATE__SEARCH) + { + return res; + } + loop_iteration++; + } + + break; + } + + default: + tpkt_cur->set_pos(tpkt_cur->size()); + return TPKT_APPLI_SEARCH_STATE__EXIT; + } + + return TPKT_APPLI_SEARCH_STATE__SEARCH; +} + diff --git a/src/service_inspectors/mms/tpkt/osi_session_decode.h b/src/service_inspectors/mms/tpkt/osi_session_decode.h new file mode 100644 index 000000000..b01484070 --- /dev/null +++ b/src/service_inspectors/mms/tpkt/osi_session_decode.h @@ -0,0 +1,61 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// osi_session_decode.h author Jared Rittle + +#ifndef OSI_SESSION_DECODE__H +#define OSI_SESSION_DECODE__H + +// OSI Session Decode provides an interface for Session message +// identification and parsing. +// It is intended to be used through `util_tpkt`, not directly + +#include "../util_tpkt.h" + +#include "cotp_decode.h" + +enum OsiSessionSpduParameterType +{ + OSI_SESSION_SPDU_PARAM__CN_ACCEPT_ITEM = 0x05, + OSI_SESSION_SPDU_PARAM__PROTOCOL_OPTIONS = 0x13, + OSI_SESSION_SPDU_PARAM__SESSION_REQUIREMENT = 0x14, + OSI_SESSION_SPDU_PARAM__VERSION_NUMBER = 0x16, + OSI_SESSION_SPDU_PARAM__CALLING_SESSION_SELECTOR = 0x33, + OSI_SESSION_SPDU_PARAM__CALLED_SESSION_SELECTOR = 0x34, + OSI_SESSION_SPDU_PARAM__SESSION_USER_DATA = 0xC1, +}; + +struct OsiSessionHdr +{ + uint8_t spdu_type; + uint8_t length; +}; + +struct OsiSessionSpduParameterHdr +{ + uint8_t type; + uint8_t length; +}; + +TpktAppliSearchStateType tpkt_internal_search_from_osi_session_layer(Cursor*, bool); +uint32_t get_max_loops(Cursor*); + +uint32_t get_max_pos(uint32_t, uint32_t); + +#endif + diff --git a/src/service_inspectors/mms/tpkt/tpkt_decode.cc b/src/service_inspectors/mms/tpkt/tpkt_decode.cc new file mode 100644 index 000000000..48b3acf9c --- /dev/null +++ b/src/service_inspectors/mms/tpkt/tpkt_decode.cc @@ -0,0 +1,43 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// tpkt_decode.cc author Jared Rittle + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "tpkt_decode.h" + +#include "cotp_decode.h" + +using namespace snort; + +TpktAppliSearchStateType tpkt_internal_search_from_tpkt_layer(Cursor* tpkt_cur) +{ + // none of the values in the TPKT header affect parsing at this time so + // doing any extra verification here would just lead to false negatives + TpktAppliSearchStateType res = TPKT_APPLI_SEARCH_STATE__EXIT; + + if (tpkt_cur->add_pos(sizeof(TpktHdr))) + { + res = tpkt_search_from_cotp_layer(tpkt_cur); + } + return res; +} + diff --git a/src/service_inspectors/mms/tpkt/tpkt_decode.h b/src/service_inspectors/mms/tpkt/tpkt_decode.h new file mode 100644 index 000000000..37465f861 --- /dev/null +++ b/src/service_inspectors/mms/tpkt/tpkt_decode.h @@ -0,0 +1,55 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// tpkt_decode.h author Jared Rittle + +#ifndef TPKT_DECODE_H +#define TPKT_DECODE_H + +// TPKT Decode provides an interface for TPKT message identification +// and parsing. +// It is intended to be used through `util_tpkt`, not directly + +#include "../util_tpkt.h" + +namespace snort +{ +struct Packet; +} + +// set a minimum length for TPKT to ensure it only gets processed +// when it makes sense +// 0x03 bytes for TPKT +// 0x04 bytes for COTP +// 0x02 bytes for the smallest MMS message +#define TPKT_MIN_LEN 0x09 + +// defined as the maximum in the spec +#define TPKT_MAX_LEN 0xFFFB + +struct TpktHdr +{ + uint8_t version; + uint8_t reserved; + uint16_t length; +}; + +TpktAppliSearchStateType tpkt_internal_search_from_tpkt_layer(Cursor*); + +#endif + diff --git a/src/service_inspectors/mms/util_tpkt.cc b/src/service_inspectors/mms/util_tpkt.cc new file mode 100644 index 000000000..9b558e3ea --- /dev/null +++ b/src/service_inspectors/mms/util_tpkt.cc @@ -0,0 +1,419 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// util_tpkt.cc author Jared Rittle + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "util_tpkt.h" + +#include "tpkt/cotp_decode.h" +#include "tpkt/osi_acse_decode.h" +#include "tpkt/osi_pres_decode.h" +#include "tpkt/osi_session_decode.h" +#include "tpkt/tpkt_decode.h" + +using namespace snort; + +//------------------------------------------------------------------------- +// flow stuff +//------------------------------------------------------------------------- + +unsigned TpktFlowData::inspector_id = 0; + +void TpktFlowData::init() +{ + inspector_id = FlowData::create_flow_data_id(); +} + +TpktFlowData::TpktFlowData() : + FlowData(inspector_id) +{ +} + +TpktFlowData::~TpktFlowData() +{ + delete [] ssn_data.server_packet_data; + delete [] ssn_data.client_packet_data; +} + +//------------------------------------------------------------------------- +// parsing stuff +//------------------------------------------------------------------------- + +static TpktEncapLayerSearchStateType is_tpkt(Cursor*, TpktFlowData*, bool); +static bool is_cotp(Cursor*); +static bool is_osi_session(Cursor*); +static bool is_osi_pres(Cursor*); +static bool is_osi_acse(Cursor* c); +static bool is_mms(Cursor* c); + +// internal function used by `get_next_tpkt_encap_layer` that checks the data at +// the current position for a pattern consistent with a valid TPKT layer +TpktEncapLayerSearchStateType is_tpkt(Cursor* c, TpktFlowData* tpktfd, bool is_from_client) +{ + // not starting with a struct overlay approach here as we need to be + // able to support any number of bytes coming in + const uint32_t remaining_bytes = c->length(); + + if (remaining_bytes == 0) + { + return TPKT_ENCAP_LAYER_SEARCH_STATE__EXIT; + } + + // it is possible that we have a partial message if only the TPKT + // version byte has come through so far + // if it turns out not to be a partial it will get caught on the + // next loop + const uint8_t tpkt_version = *(c->start()); + if (tpkt_version == 0x03 and remaining_bytes > 0x00 and remaining_bytes < sizeof(TpktHdr)) + { + return TPKT_ENCAP_LAYER_SEARCH_STATE__PARTIAL; + } + + // overlay the TPKT header at the cursor start + const TpktHdr* hdr = (const TpktHdr*)c->start(); + + // check for the static parts of the expected header + if (hdr->version == 0x03 and hdr->reserved == 0x00) + { + // check to see if the reported length fits within the given data + // when it doesn't it most likely means that we have a split message + // and need to process it differently + if (htons(hdr->length) <= c->size()) + { + // make sure the reported length is long enough to even potentially + // contain a MMS message + // otherwise flow through to TPKT_ENCAP_LAYER_SEARCH_STATE__EXIT + if (htons(hdr->length) >= TPKT_MIN_LEN) + { + if (!c->add_pos(sizeof(TpktHdr))) + { + return TPKT_ENCAP_LAYER_SEARCH_STATE__EXIT; + } + + // before indicating that the layer is most likely TPKT, + // update the appropriate exit offset tracker + if (is_from_client) + { + tpktfd->ssn_data.client_exit_offset = tpktfd->ssn_data.client_splitter_offset + + htons(hdr->length); + } + else + { + tpktfd->ssn_data.server_exit_offset = tpktfd->ssn_data.server_splitter_offset + + htons(hdr->length); + } + return TPKT_ENCAP_LAYER_SEARCH_STATE__FOUND; + } + } + else + { + return TPKT_ENCAP_LAYER_SEARCH_STATE__PARTIAL; + } + } + return TPKT_ENCAP_LAYER_SEARCH_STATE__EXIT; +} + +// internal function used by `get_next_tpkt_encap_layer` that checks the data at +// the current position for a pattern consistent with a valid COTP layer +static bool is_cotp(Cursor* c) +{ + // make sure we have enough data in the cursor to overlay the struct + if (c->length() >= sizeof(CotpHdr)) + { + // overlay the struct + const CotpHdr* hdr = (const CotpHdr*)c->start(); + + if (hdr->length == 0x02 and hdr->pdu_type == COTP_PDU_TYPE_DT_DATA and + hdr->last_data_unit == 0x01) + { + if (c->add_pos(sizeof(CotpHdr))) + { + return true; + } + } + } + return false; +} + +// internal function used by `get_next_tpkt_encap_layer` that checks the data at +// the current position for a pattern consistent with a valid OSI Session layer +static bool is_osi_session(Cursor* c) +{ + // make sure we have enough data in the cursor to overlay the struct + if (c->length() >= sizeof(OsiSessionHdr)) + { + // overlay the struct + const OsiSessionHdr* hdr = (const OsiSessionHdr*)c->start(); + + // check #1 + // look for Give Tokens (GT) followed by Data Transfer (DT) + // this pattern will indicate that a Confirmed Request or Response + // message may be present + // both of these types are the same value and appear to be distinguished by + // their location in the message +#define OSI_SESSION_SPDU_GT_DT_SIZE 0x02 + if (hdr->spdu_type == OSI_SESSION_SPDU__GT_DT and hdr->length == 0x00) + { + hdr += OSI_SESSION_SPDU_GT_DT_SIZE; + if (hdr->spdu_type == OSI_SESSION_SPDU__GT_DT and hdr->length == 0x00) + { + bool res = false; + if (c->add_pos(sizeof(OsiSessionHdr))) + { + res = true; + } + return res; + } + } + + // check #2 + // look for a Connect (CN) or Accept (AC) SPDU + // this pattern will indicate that an initiate Request or Response + // message may be present + if (hdr->spdu_type == OSI_SESSION_SPDU__CN or hdr->spdu_type == OSI_SESSION_SPDU__AC) + { + if (hdr->length < c->size()) + { + return true; + } + } + } + + return false; +} + +// internal function used by `get_next_tpkt_encap_layer` that checks the data at +// the current position for a pattern consistent with a valid OSI Presentation layer +static bool is_osi_pres(Cursor* c) +{ + BerReader ber(*c); + BerElement e; + + // read the first BER tag and bail if anything goes wrong + if (ber.read(c->start(), e)) + { + enum OsiPresMsgType + { + OSI_PRES_MSG__CP_OR_CPA = 0x31, + OSI_PRES_MSG__CPC = 0x61, + }; + + // check the first BER tag for a known type that can be decoded + // OSI_PRES_MSG__CP_OR_CPA indicates an potential initiate Request or Response + // OSI_PRES_MSG__CPC indicates a potential confirmed Request or Response + if (e.type == OSI_PRES_MSG__CP_OR_CPA or e.type == OSI_PRES_MSG__CPC) + { + if (e.length < c->size()) + { + if (c->add_pos(e.header_length + e.length)) + { + return true; + } + } + } + } + + return false; +} + +// internal function used by `get_next_tpkt_encap_layer` that checks the data at +// the current position for a pattern consistent with a valid OSI ACSE layer +static bool is_osi_acse(Cursor* c) +{ + BerReader ber(*c); + BerElement e; + + // read the first BER tag and bail if anything goes wrong + if (ber.read(c->start(), e)) + { + // check the first BER tag for a known type that can be decoded + // OSI_ACSE_AARQ indicates a potential initiate Request + // OSI_ACSE_AARE indicates a potential initiate Response + if (e.type == OSI_ACSE_AARQ or e.type == OSI_ACSE_AARE) + { + if (e.length < c->size()) + { + if (c->add_pos(e.header_length + e.length)) + { + return true; + } + } + } + } + return false; +} + +// internal function used by `get_next_tpkt_encap_layer` that checks the data at +// the current position for a pattern consistent with a valid MMS layer +static bool is_mms(Cursor* c) +{ + BerReader ber(*c); + BerElement e; + + // read the first BER tag and bail if anything goes wrong + if (ber.read(c->start(), e)) + { + // check the first BER tag for a known MMS message type + switch (e.type) + { + case MMS_MSG__CONFIRMED_REQUEST: // fallthrough + case MMS_MSG__CONFIRMED_RESPONSE: // fallthrough + case MMS_MSG__CONFIRMED_ERROR: // fallthrough + case MMS_MSG__UNCONFIRMED: // fallthrough + case MMS_MSG__REJECT: // fallthrough + case MMS_MSG__CANCEL_REQUEST: // fallthrough + case MMS_MSG__CANCEL_RESPONSE: // fallthrough + case MMS_MSG__CANCEL_ERROR: // fallthrough + case MMS_MSG__INITIATE_REQUEST: // fallthrough + case MMS_MSG__INITIATE_RESPONSE: // fallthrough + case MMS_MSG__INITIATE_ERROR: // fallthrough + case MMS_MSG__CONCLUDE_REQUEST: // fallthrough + case MMS_MSG__CONCLUDE_RESPONSE: // fallthrough + case MMS_MSG__CONCLUDE_ERROR: + if (e.length < c->size()) + { + if (c->add_pos(e.header_length + e.length)) + { + return true; + } + } + break; + + default: + // continue on if none of the known cases are found + break; + } + } + + return false; +} + +// function to run analysis on the data and return the best guess of which +// TPKT encapsulation layer is present starting from the current cursor pos +TpktEncapLayerType get_next_tpkt_encap_layer(Packet* p, Cursor* c) +{ + // create TPKT flow data and add it to the packet + TpktFlowData* tpktfd = (TpktFlowData*)p->flow->get_flow_data(TpktFlowData::inspector_id); + + if (!tpktfd) + { + TpktFlowData::init(); + tpktfd = new TpktFlowData; + p->flow->set_flow_data(tpktfd); + } + + // check for the TPKT layer + switch (is_tpkt(c, tpktfd, p->is_from_client())) + { + case TPKT_ENCAP_LAYER_SEARCH_STATE__FOUND: + return TPKT_ENCAP_LAYER__TPKT; + + // for the TPKT layer specifically it is possible to have a partial + // message in pipelined cases. When this happens we essentially add + // in all of the data to the current cursor and move on to the next + // loop for continued processing + case TPKT_ENCAP_LAYER_SEARCH_STATE__PARTIAL: + return TPKT_ENCAP_LAYER__PARTIAL; + + default: + // assume not found when nothing explicit comes out + break; + } + + // check for the COTP layer + if (is_cotp(c)) + { + return TPKT_ENCAP_LAYER__COTP; + } + + // check for the OSI Session layer + if (is_osi_session(c)) + { + return TPKT_ENCAP_LAYER__OSI_SESSION; + } + + // check for the OSI Pres layer + if (is_osi_pres(c)) + { + return TPKT_ENCAP_LAYER__OSI_PRES; + } + + // check for the OSI ACSE layer + if (is_osi_acse(c)) + { + return TPKT_ENCAP_LAYER__OSI_ACSE; + } + + // check for the MMS layer + if (is_mms(c)) + { + return TPKT_ENCAP_LAYER__MMS; + } + + // if a decision cannot be made on what layer is present, set the exit + // offset to the end of the current data before returning no layer + // found. in this case we are just moving on from the data and not + // sending it to the service inspector + if (p->is_from_client()) + { + tpktfd->ssn_data.client_exit_offset = c->size(); + } + else + { + tpktfd->ssn_data.server_exit_offset = c->size(); + } + + // no valid layer found + return TPKT_ENCAP_LAYER__NONE; +} + +// exposing the all of the layer search functions from a single location +TpktAppliSearchStateType tpkt_search_from_tpkt_layer(Cursor* c) +{ + return tpkt_internal_search_from_tpkt_layer(c); +} + +// exposing the all of the layer search functions from a single location +TpktAppliSearchStateType tpkt_search_from_cotp_layer(Cursor* c) +{ + return tpkt_internal_search_from_cotp_layer(c); +} + +// exposing the all of the layer search functions from a single location +TpktAppliSearchStateType tpkt_search_from_osi_session_layer(Cursor* c, bool + process_as_data_transfer) +{ + return tpkt_internal_search_from_osi_session_layer(c, process_as_data_transfer); +} + +// exposing the all of the layer search functions from a single location +TpktAppliSearchStateType tpkt_search_from_osi_pres_layer(Cursor* c) +{ + return tpkt_internal_search_from_osi_pres_layer(c); +} + +// exposing the all of the layer search functions from a single location +TpktAppliSearchStateType tpkt_search_from_osi_acse_layer(Cursor* c) +{ + return tpkt_internal_search_from_osi_acse_layer(c); +} + diff --git a/src/service_inspectors/mms/util_tpkt.h b/src/service_inspectors/mms/util_tpkt.h new file mode 100644 index 000000000..1da42a1da --- /dev/null +++ b/src/service_inspectors/mms/util_tpkt.h @@ -0,0 +1,186 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// util_tpkt.h author Jared Rittle + +#ifndef TPKT_H +#define TPKT_H + +// The TPKT util provides an interface for processing the encapsulation +// layers associated with TPKT, COTP, OSI Session, OSI Pres, and OSI ACSE + +#include "detection/detection_engine.h" +#include "flow/flow.h" +#include "framework/counts.h" +#include "framework/cursor.h" +#include "protocols/packet.h" +#include "service_inspectors/mms/mms.h" +#include "utils/util_ber.h" + +namespace snort +{ +struct Packet; +} + +#define TPKT_PACKET_DATA_BUF_SIZE 0x10000 +#define TPKT_SMALLEST_TLV_SIZE 0x03 + +//------------------------------------------------------------------------- +// type definitions +//------------------------------------------------------------------------- + +enum OsiSessionSpduType +{ + OSI_SESSION_SPDU__GT_DT = 0x01, + OSI_SESSION_SPDU__CN = 0x0D, + OSI_SESSION_SPDU__AC = 0x0E, + OSI_SESSION_SPDU__NOT_SET = 0xFFFFFFFF, +}; + +enum ProcessAsDataTransferType +{ + OSI_SESSION_PROCESS_AS_DT__TRUE = true, + OSI_SESSION_PROCESS_AS_DT__FALSE = false, +}; + +enum TpktEncapLayerType +{ + TPKT_ENCAP_LAYER__NONE, + TPKT_ENCAP_LAYER__TPKT, + TPKT_ENCAP_LAYER__COTP, + TPKT_ENCAP_LAYER__OSI_SESSION, + TPKT_ENCAP_LAYER__OSI_PRES, + TPKT_ENCAP_LAYER__OSI_ACSE, + TPKT_ENCAP_LAYER__MMS, + TPKT_ENCAP_LAYER__PARTIAL, +}; + +enum TpktEncapLayerSearchStateType +{ + TPKT_ENCAP_LAYER_SEARCH_STATE__EXIT, + TPKT_ENCAP_LAYER_SEARCH_STATE__FOUND, + TPKT_ENCAP_LAYER_SEARCH_STATE__PARTIAL, +}; + +enum TpktAppliSearchStateType +{ + TPKT_APPLI_SEARCH_STATE__MMS_FOUND, + TPKT_APPLI_SEARCH_STATE__SEARCH, + TPKT_APPLI_SEARCH_STATE__EXIT, +}; + +enum TpktPacketDataDirectionType +{ + TPKT_PACKET_DATA_DIRECTION__SERVER, + TPKT_PACKET_DATA_DIRECTION__CLIENT, +}; + +//------------------------------------------------------------------------- +// flow stuff +//------------------------------------------------------------------------- + +class TpktSessionData +{ +public: + OsiSessionSpduType cur_spdu_type = OSI_SESSION_SPDU__NOT_SET; + bool process_as_data_transfer = OSI_SESSION_PROCESS_AS_DT__FALSE; + uint8_t* server_packet_data = nullptr; + uint32_t server_packet_len = 0; + uint32_t server_start_offset = 0; + uint32_t server_splitter_offset = 0; + uint32_t server_exit_offset = 0; + uint8_t* client_packet_data = nullptr; + uint32_t client_packet_len = 0; + uint32_t client_start_offset = 0; + uint32_t client_splitter_offset = 0; + uint32_t client_exit_offset = 0; + + void packet_data_reset(TpktPacketDataDirectionType direction) + { + if (direction == TPKT_PACKET_DATA_DIRECTION__SERVER) + { + delete [] server_packet_data; + server_packet_data = new uint8_t[TPKT_PACKET_DATA_BUF_SIZE]; + } + else if (direction == TPKT_PACKET_DATA_DIRECTION__CLIENT) + { + delete [] client_packet_data; + client_packet_data = new uint8_t[TPKT_PACKET_DATA_BUF_SIZE]; + } + } + + void session_data_reset() + { + cur_spdu_type = OSI_SESSION_SPDU__NOT_SET; + process_as_data_transfer = OSI_SESSION_PROCESS_AS_DT__FALSE; + + packet_data_reset(TPKT_PACKET_DATA_DIRECTION__SERVER); + server_packet_len = 0; + server_start_offset = 0; + server_splitter_offset = 0; + server_exit_offset = 0; + + packet_data_reset(TPKT_PACKET_DATA_DIRECTION__CLIENT); + client_packet_len = 0; + client_start_offset = 0; + client_splitter_offset = 0; + client_exit_offset = 0; + } +}; + +class TpktFlowData : public snort::FlowData +{ +public: + TpktFlowData(); + ~TpktFlowData() override; + + static void init(); + + void reset() + { + ssn_data.session_data_reset(); + } + + void reset_packet_data(TpktPacketDataDirectionType direction) + { + ssn_data.packet_data_reset(direction); + } + + size_t size_of() override + { + return sizeof(*this); + } + +public: + static unsigned inspector_id; + TpktSessionData ssn_data; +}; + +//------------------------------------------------------------------------- +// function definitions +//------------------------------------------------------------------------- + +TpktEncapLayerType get_next_tpkt_encap_layer(snort::Packet*, Cursor*); +TpktAppliSearchStateType tpkt_search_from_tpkt_layer(Cursor*); +TpktAppliSearchStateType tpkt_search_from_cotp_layer(Cursor*); +TpktAppliSearchStateType tpkt_search_from_osi_session_layer(Cursor*, bool); +TpktAppliSearchStateType tpkt_search_from_osi_pres_layer(Cursor*); +TpktAppliSearchStateType tpkt_search_from_osi_acse_layer(Cursor*); + +#endif + diff --git a/src/service_inspectors/service_inspectors.cc b/src/service_inspectors/service_inspectors.cc index 21783793c..20eea1754 100644 --- a/src/service_inspectors/service_inspectors.cc +++ b/src/service_inspectors/service_inspectors.cc @@ -53,6 +53,7 @@ extern const BaseApi* sin_dce[]; extern const BaseApi* sin_dnp3[]; extern const BaseApi* sin_gtp[]; extern const BaseApi* sin_iec104[]; +extern const BaseApi* sin_mms[]; extern const BaseApi* sin_modbus[]; extern const BaseApi* sin_netflow[]; extern const BaseApi* sin_s7commplus[]; @@ -94,6 +95,7 @@ void load_service_inspectors() PluginManager::load_plugins(sin_dnp3); PluginManager::load_plugins(sin_gtp); PluginManager::load_plugins(sin_iec104); + PluginManager::load_plugins(sin_mms); PluginManager::load_plugins(sin_modbus); PluginManager::load_plugins(sin_netflow); PluginManager::load_plugins(sin_s7commplus); diff --git a/src/service_inspectors/wizard/CMakeLists.txt b/src/service_inspectors/wizard/CMakeLists.txt index 31cbc290c..63eb3c132 100644 --- a/src/service_inspectors/wizard/CMakeLists.txt +++ b/src/service_inspectors/wizard/CMakeLists.txt @@ -4,6 +4,7 @@ set(FILE_LIST curses.h magic.cc magic.h + mms_curse.h hexes.cc spells.cc wizard.cc diff --git a/src/service_inspectors/wizard/curses.cc b/src/service_inspectors/wizard/curses.cc index b14727cab..f0f1969b2 100644 --- a/src/service_inspectors/wizard/curses.cc +++ b/src/service_inspectors/wizard/curses.cc @@ -16,6 +16,7 @@ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. //-------------------------------------------------------------------------- // curses.cc author Maya Dagon +// mms curse author Jared Rittle #ifdef HAVE_CONFIG_H #include "config.h" @@ -23,6 +24,8 @@ #include "curses.h" +#include + using namespace std; enum DceRpcPduType @@ -258,6 +261,220 @@ static bool dce_smb_curse(const uint8_t* data, unsigned len, CurseTracker* track return false; } + +static bool mms_curse(const uint8_t* data, unsigned len, CurseTracker* tracker) +{ + // peg the tracker to MMS + CurseTracker::MMS& mms = tracker->mms; + + // if the state is set to MMS_STATE__SEARCH it means we most likely + // have a split pipelined message coming through and will need to + // reset the state + if (mms.state == MMS_STATE__SEARCH) + { + mms.state = mms.last_state; + } + + // define all known MMS tags to check for + enum + { + MMS_CONFIRMED_REQUEST_TAG = 0xA0, + MMS_CONFIRMED_RESPONSE_TAG = 0xA1, + MMS_CONFIRMED_ERROR_TAG = 0xA2, + MMS_UNCONFIRMED_TAG = 0xA3, + MMS_REJECT_TAG = 0xA4, + MMS_CANCEL_REQUEST_TAG = 0x85, + MMS_CANCEL_RESPONSE_TAG = 0x86, + MMS_CANCEL_ERROR_TAG = 0xA7, + MMS_INITIATE_REQUEST_TAG = 0xA8, + MMS_INITIATE_RESPONSE_TAG = 0xA9, + MMS_INITIATE_ERROR_TAG = 0xAA, + MMS_CONCLUDE_REQUEST_TAG = 0x8B, + MMS_CONCLUDE_RESPONSE_TAG = 0x8C, + MMS_CONCLUDE_ERROR_TAG = 0xAD, + }; + + uint32_t idx = 0; + while (idx < len) + { + switch (mms.state) + { + case MMS_STATE__TPKT_VER: + { + mms.state = MMS_STATE__TPKT_RES; + break; + } + + case MMS_STATE__TPKT_RES: + { + mms.state = MMS_STATE__TPKT_LEN1; + break; + } + + case MMS_STATE__TPKT_LEN1: + { + mms.state = MMS_STATE__TPKT_LEN2; + break; + } + + case MMS_STATE__TPKT_LEN2: + { + mms.state = MMS_STATE__COTP_LEN; + break; + } + + case MMS_STATE__COTP_LEN: + { + mms.state = MMS_STATE__COTP_PDU; + break; + } + + case MMS_STATE__COTP_PDU: + { + // 7 6 5 4 3 2 1 0 + // --------------- + // . . . . x x x x Destination Reference + // x x x x . . . . PDU Type + const uint32_t MMS_COTP_PDU_DT_DATA = 0x0F; + if (data[idx] >> 0x04 != MMS_COTP_PDU_DT_DATA) + { + mms.state = MMS_STATE__NOT_FOUND; + break; + } + mms.state = MMS_STATE__COTP_TPDU_NUM; + break; + } + + case MMS_STATE__COTP_TPDU_NUM: + { + mms.state = MMS_STATE__OSI_SESSION_SPDU; + break; + } + + case MMS_STATE__OSI_SESSION_SPDU: + { + // define all known OSI Session layer SPDU tags to check + enum + { + MMS_OSI_SESSION_SPDU_GT_DT = 0x01, + MMS_OSI_SESSION_SPDU_CN = 0x0D, + MMS_OSI_SESSION_SPDU_AC = 0x0E, + }; + + switch (data[idx]) + { + // check for a known MMS message tag in the event Session/Pres/ACSE aren't used + case MMS_CONFIRMED_REQUEST_TAG: // fallthrough intentional + case MMS_CONFIRMED_RESPONSE_TAG: // fallthrough intentional + case MMS_CONFIRMED_ERROR_TAG: // fallthrough intentional + case MMS_UNCONFIRMED_TAG: // fallthrough intentional + case MMS_REJECT_TAG: // fallthrough intentional + case MMS_CANCEL_REQUEST_TAG: // fallthrough intentional + case MMS_CANCEL_RESPONSE_TAG: // fallthrough intentional + case MMS_CANCEL_ERROR_TAG: // fallthrough intentional + case MMS_INITIATE_REQUEST_TAG: // fallthrough intentional + case MMS_INITIATE_RESPONSE_TAG: // fallthrough intentional + case MMS_INITIATE_ERROR_TAG: // fallthrough intentional + case MMS_CONCLUDE_REQUEST_TAG: // fallthrough intentional + case MMS_CONCLUDE_RESPONSE_TAG: // fallthrough intentional + case MMS_CONCLUDE_ERROR_TAG: + { + // if an MMS tag exists in the remaining data, + // hand off to the MMS service inspector + mms.state = MMS_STATE__FOUND; + break; + } + + // if mms isn't found, search for an OSI Session layer + case MMS_OSI_SESSION_SPDU_GT_DT: // fallthrough intentional + case MMS_OSI_SESSION_SPDU_CN: // fallthrough intentional + case MMS_OSI_SESSION_SPDU_AC: + { + mms.state = MMS_STATE__MMS; + break; + } + + // if neither are found, it is most likely not MMS + default: + { + mms.state = MMS_STATE__NOT_FOUND; + } + } + + break; + } + + case MMS_STATE__MMS: + { + // loop through the remaining bytes in the buffer checking for known MMS tags + for (uint32_t i=idx; i < len; i++) + { + // for each remaining byte check to see if it is in the known tag map + switch (data[i]) + { + case MMS_CONFIRMED_REQUEST_TAG: // fallthrough intentional + case MMS_CONFIRMED_RESPONSE_TAG: // fallthrough intentional + case MMS_CONFIRMED_ERROR_TAG: // fallthrough intentional + case MMS_UNCONFIRMED_TAG: // fallthrough intentional + case MMS_REJECT_TAG: // fallthrough intentional + case MMS_CANCEL_REQUEST_TAG: // fallthrough intentional + case MMS_CANCEL_RESPONSE_TAG: // fallthrough intentional + case MMS_CANCEL_ERROR_TAG: // fallthrough intentional + case MMS_INITIATE_REQUEST_TAG: // fallthrough intentional + case MMS_INITIATE_RESPONSE_TAG: // fallthrough intentional + case MMS_INITIATE_ERROR_TAG: // fallthrough intentional + case MMS_CONCLUDE_REQUEST_TAG: // fallthrough intentional + case MMS_CONCLUDE_RESPONSE_TAG: // fallthrough intentional + case MMS_CONCLUDE_ERROR_TAG: + { + // if an MMS tag exists in the remaining data, + // hand off to the MMS service inspector + mms.state = MMS_STATE__FOUND; + break; + } + // no default here as it we don't know when we would hit + // the first MMS tag without doing full parsing + } + + // exit the loop when a state has been determined + if (mms.state == MMS_STATE__NOT_FOUND + or mms.state == MMS_STATE__SEARCH + or mms.state == MMS_STATE__FOUND) + { + break; + } + } + break; + } + + case MMS_STATE__FOUND: + { + mms.state = MMS_STATE__TPKT_VER; + return true; + } + + case MMS_STATE__NOT_FOUND: + { + mms.state = MMS_STATE__TPKT_VER; + return false; + } + + default: + { + mms.state = MMS_STATE__NOT_FOUND; + assert(false); + break; + } + } + idx++; + } + + mms.last_state = mms.state; + mms.state = MMS_STATE__SEARCH; + return false; +} + + namespace SSL_Const { static constexpr uint8_t hdr_len = 9; @@ -396,6 +613,7 @@ static vector curse_map // name service alg is_tcp { "dce_udp", "dcerpc" , dce_udp_curse, false }, { "dce_tcp", "dcerpc" , dce_tcp_curse, true }, + { "mms" , "mms" , mms_curse , true }, { "dce_smb", "netbios-ssn", dce_smb_curse, true }, { "sslv2" , "ssl" , ssl_v2_curse , true } }; diff --git a/src/service_inspectors/wizard/curses.h b/src/service_inspectors/wizard/curses.h index 1ffe226a2..c7815efe4 100644 --- a/src/service_inspectors/wizard/curses.h +++ b/src/service_inspectors/wizard/curses.h @@ -24,6 +24,8 @@ #include #include +#include "mms_curse.h" + enum DCE_State { STATE_0 = 0, @@ -65,6 +67,12 @@ public: uint32_t helper; } dce; + struct MMS + { + MMS_State state; + MMS_State last_state; + } mms; + struct SSL { SSL_State state; @@ -77,6 +85,8 @@ public: CurseTracker() { dce.state = DCE_State::STATE_0; + mms.state = MMS_State::MMS_STATE__TPKT_VER; + mms.last_state = mms.state; ssl.state = SSL_State::BYTE_0_LEN_MSB; } }; diff --git a/src/service_inspectors/wizard/mms_curse.h b/src/service_inspectors/wizard/mms_curse.h new file mode 100644 index 000000000..5a2888feb --- /dev/null +++ b/src/service_inspectors/wizard/mms_curse.h @@ -0,0 +1,46 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2022-2022 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// 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 along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- +// mms_curse.h author Jared Rittle + +#ifndef MMS_CURSE_H +#define MMS_CURSE_H + +// MMS curse provides the ability to determine if the traffic being processed +// conforms to the Manufacturing Message Specification (MMS) traffic defined +// within the IEC-61850 family of protocols + +#include "curses.h" + +enum MMS_State +{ + MMS_STATE__TPKT_VER = 0, + MMS_STATE__TPKT_RES, + MMS_STATE__TPKT_LEN1, + MMS_STATE__TPKT_LEN2, + MMS_STATE__COTP_LEN, + MMS_STATE__COTP_PDU, + MMS_STATE__COTP_TPDU_NUM, + MMS_STATE__OSI_SESSION_SPDU, + MMS_STATE__MMS, + MMS_STATE__FOUND, + MMS_STATE__SEARCH, + MMS_STATE__NOT_FOUND, +}; + +#endif + diff --git a/src/service_inspectors/wizard/wiz_module.cc b/src/service_inspectors/wizard/wiz_module.cc index 5a9973f7a..d21ca1ced 100644 --- a/src/service_inspectors/wizard/wiz_module.cc +++ b/src/service_inspectors/wizard/wiz_module.cc @@ -103,7 +103,7 @@ static const Parameter s_params[] = { "spells", Parameter::PT_LIST, wizard_spells_params, nullptr, "criteria for text service identification" }, - { "curses", Parameter::PT_MULTI, "dce_smb | dce_udp | dce_tcp | sslv2", nullptr, + { "curses", Parameter::PT_MULTI, "dce_smb | dce_udp | dce_tcp | mms | sslv2", nullptr, "enable service identification based on internal algorithm" }, { "max_search_depth", Parameter::PT_INT, "0:65535", "8192",