dns = { }
imap = { }
iec104 = { }
+mms = { }
modbus = { }
netflow = {}
normalizer = { }
{ 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' } },
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'}
}
---------------------------------------------------------------------------
add_subdirectory(http2_inspect)
add_subdirectory(iec104)
add_subdirectory(imap)
+add_subdirectory(mms)
add_subdirectory(modbus)
add_subdirectory(netflow)
add_subdirectory(pop)
$<TARGET_OBJECTS:gtp_inspect>
$<TARGET_OBJECTS:iec104>
$<TARGET_OBJECTS:imap>
+ $<TARGET_OBJECTS:mms>
$<TARGET_OBJECTS:modbus>
$<TARGET_OBJECTS:netflow>
$<TARGET_OBJECTS:pop>
--- /dev/null
+
+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)
+
--- /dev/null
+*** 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.
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+// modeled after ips_modbus_data.cc author Russ Combs <rucombs@cisco.com>
+
+#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;
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+// modeled after ips_modbus_func.cc (author Russ Combs <rucombs@cisco.com>)
+
+#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<uint8_t>(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;
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+// modeled after modbus.cc (author Russ Combs <rucombs@cisco.com>)
+// modeled after s7comm.cc (author Pradeep Damodharan <prdamodh@cisco.com>)
+
+#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
+};
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+// modeled after modbus.h (author Russ Combs <rucombs@cisco.com>)
+// modeled after s7comm.h (author Pradeep Damodharan <prdamodh@cisco.com>)
+
+#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
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+// modeled after modbus_decode.cc (author Russ Combs <rucombs@cisco.com>)
+// modeled after s7comm_decode.cc (author Pradeep Damodharan <prdamodh@cisco.com>)
+
+#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;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+// modeled after modbus_decode.h (author Russ Combs <rucombs@cisco.com>)
+// modeled after s7comm_decode.h (author Pradeep Damodharan <prdamodh@cisco.com>)
+
+#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
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+// modeled after modbus_module.c (author Russ Combs <rucombs@cisco.com>)
+// modeled after s7comm_module.c (author Pradeep Damodharan <prdamodh@cisco.com>)
+
+#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)
+{
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+// modeled after modbus_module.h (author Russ Combs <rucombs@cisco.com>)
+// modeled after s7comm_module.h (author Pradeep Damodharan <prdamodh@cisco.com>)
+
+#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
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+
+#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;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+// modeled after modbus_paf.h (author Russ Combs <rucombs@cisco.com>)
+// modeled after s7comm_paf.h (author Pradeep Damodharan <prdamodh@cisco.com>)
+
+#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
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+
+#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;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+
+#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
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+
+#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;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+
+#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
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+
+#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;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+
+#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
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+
+#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;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+
+#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
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+
+#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;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+
+#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
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+
+#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);
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+
+#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
+
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[];
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);
curses.h
magic.cc
magic.h
+ mms_curse.h
hexes.cc
spells.cc
wizard.cc
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//--------------------------------------------------------------------------
// curses.cc author Maya Dagon <mdagon@cisco.com>
+// mms curse author Jared Rittle <jared.rittle@cisco.com>
#ifdef HAVE_CONFIG_H
#include "config.h"
#include "curses.h"
+#include <assert.h>
+
using namespace std;
enum DceRpcPduType
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;
// 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 }
};
#include <string>
#include <vector>
+#include "mms_curse.h"
+
enum DCE_State
{
STATE_0 = 0,
uint32_t helper;
} dce;
+ struct MMS
+ {
+ MMS_State state;
+ MMS_State last_state;
+ } mms;
+
struct SSL
{
SSL_State state;
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;
}
};
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <jared.rittle@cisco.com>
+
+#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
+
{ "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",