]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #3207: Mms service inspector
authorTom Peters (thopeter) <thopeter@cisco.com>
Fri, 15 Apr 2022 19:28:53 +0000 (19:28 +0000)
committerTom Peters (thopeter) <thopeter@cisco.com>
Fri, 15 Apr 2022 19:28:53 +0000 (19:28 +0000)
Merge in SNORT/snort3 from ~JRITTLE/snort3:mms_service_inspector to master

Squashed commit of the following:

commit 748bd178828da9d67a303ee24971f03ff0bc7e4f
Author: jrittle <jrittle@cisco.com>
Date:   Fri Jul 2 14:04:54 2021 -0400

    mms: adding new service inspector for the IEC61850 MMS protocol

33 files changed:
lua/snort.lua
lua/snort_defaults.lua
src/service_inspectors/CMakeLists.txt
src/service_inspectors/mms/CMakeLists.txt [new file with mode: 0644]
src/service_inspectors/mms/dev_notes.txt [new file with mode: 0644]
src/service_inspectors/mms/ips_mms_data.cc [new file with mode: 0644]
src/service_inspectors/mms/ips_mms_func.cc [new file with mode: 0644]
src/service_inspectors/mms/mms.cc [new file with mode: 0644]
src/service_inspectors/mms/mms.h [new file with mode: 0644]
src/service_inspectors/mms/mms_decode.cc [new file with mode: 0644]
src/service_inspectors/mms/mms_decode.h [new file with mode: 0644]
src/service_inspectors/mms/mms_module.cc [new file with mode: 0644]
src/service_inspectors/mms/mms_module.h [new file with mode: 0644]
src/service_inspectors/mms/mms_splitter.cc [new file with mode: 0644]
src/service_inspectors/mms/mms_splitter.h [new file with mode: 0644]
src/service_inspectors/mms/tpkt/cotp_decode.cc [new file with mode: 0644]
src/service_inspectors/mms/tpkt/cotp_decode.h [new file with mode: 0644]
src/service_inspectors/mms/tpkt/osi_acse_decode.cc [new file with mode: 0644]
src/service_inspectors/mms/tpkt/osi_acse_decode.h [new file with mode: 0644]
src/service_inspectors/mms/tpkt/osi_pres_decode.cc [new file with mode: 0644]
src/service_inspectors/mms/tpkt/osi_pres_decode.h [new file with mode: 0644]
src/service_inspectors/mms/tpkt/osi_session_decode.cc [new file with mode: 0644]
src/service_inspectors/mms/tpkt/osi_session_decode.h [new file with mode: 0644]
src/service_inspectors/mms/tpkt/tpkt_decode.cc [new file with mode: 0644]
src/service_inspectors/mms/tpkt/tpkt_decode.h [new file with mode: 0644]
src/service_inspectors/mms/util_tpkt.cc [new file with mode: 0644]
src/service_inspectors/mms/util_tpkt.h [new file with mode: 0644]
src/service_inspectors/service_inspectors.cc
src/service_inspectors/wizard/CMakeLists.txt
src/service_inspectors/wizard/curses.cc
src/service_inspectors/wizard/curses.h
src/service_inspectors/wizard/mms_curse.h [new file with mode: 0644]
src/service_inspectors/wizard/wiz_module.cc

index 3579a4d53f2bc01c6493a2ab9f4b368c582cc611..24d984370a0403b1712b43a926ccb4e9c00e1885 100644 (file)
@@ -56,6 +56,7 @@ dnp3 = { }
 dns = { }
 imap = { }
 iec104 = { }
+mms = { }
 modbus = { }
 netflow = {}
 normalizer = { }
@@ -140,6 +141,7 @@ binder =
     { when = { service = 'http' },             use = { type = 'http_inspect' } },
     { when = { service = 'http2' },            use = { type = 'http2_inspect' } },
     { when = { service = 'iec104' },           use = { type = 'iec104' } },
+    { when = { service = 'mms' },              use = { type = 'mms' } },
     { when = { service = 'modbus' },           use = { type = 'modbus' } },
     { when = { service = 'pop3' },             use = { type = 'pop' } },
     { when = { service = 'ssh' },              use = { type = 'ssh' } },
index 41eef5823f9ae2f9a7d8e04d0b7a7654d904db3e..76ec107d63a41ebfb80710099491848c8c1d3dd5 100644 (file)
@@ -415,7 +415,7 @@ default_wizard =
           to_server = telnet_commands, to_client = telnet_commands },
     },
 
-    curses = {'dce_udp', 'dce_tcp', 'dce_smb', 'sslv2'}
+    curses = {'dce_udp', 'dce_tcp', 'dce_smb', 'mms', 'sslv2'}
 }
 
 ---------------------------------------------------------------------------
index 01343fb21e6e82a445c4c66bc7500d1ef6652d67..c4d54ac27d77f3e2142e6ffc00b8b66363436cf8 100644 (file)
@@ -10,6 +10,7 @@ add_subdirectory(http_inspect)
 add_subdirectory(http2_inspect)
 add_subdirectory(iec104)
 add_subdirectory(imap)
+add_subdirectory(mms)
 add_subdirectory(modbus)
 add_subdirectory(netflow)
 add_subdirectory(pop)
@@ -32,6 +33,7 @@ if (STATIC_INSPECTORS)
         $<TARGET_OBJECTS:gtp_inspect>
         $<TARGET_OBJECTS:iec104>
         $<TARGET_OBJECTS:imap>
+        $<TARGET_OBJECTS:mms>
         $<TARGET_OBJECTS:modbus>
         $<TARGET_OBJECTS:netflow>
         $<TARGET_OBJECTS:pop>
diff --git a/src/service_inspectors/mms/CMakeLists.txt b/src/service_inspectors/mms/CMakeLists.txt
new file mode 100644 (file)
index 0000000..abff2d9
--- /dev/null
@@ -0,0 +1,34 @@
+
+set( FILE_LIST
+    mms.cc
+    mms.h
+    mms_decode.cc
+    mms_decode.h
+    mms_module.cc
+    mms_module.h
+    mms_splitter.cc
+    mms_splitter.h
+    ips_mms_data.cc
+    ips_mms_func.cc
+    tpkt/tpkt_decode.cc
+    tpkt/tpkt_decode.h
+    tpkt/cotp_decode.cc
+    tpkt/cotp_decode.h
+    tpkt/osi_session_decode.cc
+    tpkt/osi_session_decode.h
+    tpkt/osi_pres_decode.cc
+    tpkt/osi_pres_decode.h
+    tpkt/osi_acse_decode.cc
+    tpkt/osi_acse_decode.h
+    util_tpkt.cc
+    util_tpkt.h
+)
+
+if (STATIC_INSPECTORS)
+    add_library(mms OBJECT ${FILE_LIST})
+
+else (STATIC_INSPECTORS)
+    add_dynamic_module(mms inspectors ${FILE_LIST})
+
+endif (STATIC_INSPECTORS)
+
diff --git a/src/service_inspectors/mms/dev_notes.txt b/src/service_inspectors/mms/dev_notes.txt
new file mode 100644 (file)
index 0000000..5119088
--- /dev/null
@@ -0,0 +1,111 @@
+*** MMS service inspector overview ***
+IEC 61850 is a family of protocols distributed by the International 
+Electrotechnical Commission (IEC) that provide a standardized method of
+sending service messages between various manufacturing and process control
+devices, typically running on TCP port 102.
+
+It is used in combination with various parts of the OSI model, most notably
+the TPKT, COTP, Session, Presentation, and ACSE layers, to provide reliable
+transport via TCP/IP. 
+
+The MMS inspector decodes the OSI layers encapsulating the MMS protocol and 
+provides rule options to access certain protocol fields and data content. 
+This allows the user to write rules for MMS messages without decoding the 
+protocol. 
+
+
+*** Encapsulation overview ***
+MMS can be transported via TCP/102 when encapsulated in TPKT and the 
+associated OSI layers. The two structures most commonly encountered are
+shown below:
+
+Connection Request/Response:
+    TCP -> TPKT -> COTP -> OSI Session -> OSI Presentation -> OSI ACSE -> MMS
+
+Confirmed Request/Response:
+    TCP -> TPKT -> COTP -> OSI Session -> OSI Presentation -> MMS
+
+Parsing functions for each of these layers can be found in `util_tpkt`. 
+
+
+*** MMS message pipelining overview ***
+Multiple MMS messages can be sent within a single TCP stream through use of
+a variety of techniques. Splitting for known forms is handled as follows:
+
+Case 1 Combined:
+In this case multiple messages are contained within one TCP packet. This can 
+occur at any of the following layers: TPKT, OSI Presentation, MMS, or any 
+combination thereof. Example layouts are shown below:
+
+Combined at the TPKT layer:
+    TCP -> TPKT -> COTP -> OSI Session -> OSI Presentation -> MMS -> TPKT 
+    -> COTP -> OSI Session -> OSI Presentation -> MMS
+
+Combined at the OSI Presentation layer:
+    TCP -> TPKT -> COTP -> OSI Session -> OSI Presentation -> MMS 
+    -> OSI Presentation -> MMS
+
+Combined at the MMS layer:
+    TCP -> TPKT -> COTP -> OSI Session -> OSI Presentation -> MMS -> MMS
+
+Due to the ability for messages to be combined at multiple layers, the 
+starting layer of the cursor following a successful split cannot be assumed. 
+To solve this, a set of functions have been developed in `util_tpkt` to make 
+a best guess at the layer at the cursor start (`isTpkt`, `isCotp`, etc.). An 
+example of usage can be found in the MMS Splitter. No special considerations
+were needed in the curse to handle this case. 
+
+Case 2 Split: 
+In this case a single message is split across multiple TCP packets within the 
+same stream. This can occur at any point across the packet data, not 
+necessarily on a layer boundary. Due to this case it is necessary to buffer
+the incoming packet data until a flush point has been found. To handle this, 
+the both the MMS curse and splitter need to have special considerations. 
+
+In the MMS curse, current and prior state are tracked as the potential message
+is processed byte by byte. The current state indicates the part of a fully
+encapsulated MMS message that the byte in that position would represent if it
+was found to contain MMS. The prior state is tracked so that the state machine
+knows where to pick up in the event of a message being split. 
+
+In the MMS splitter, two buffers (one for each direction) are used to track 
+message data across packets. At the beginning of each scan, the direction is
+determined and the appropriate buffer is selected. The new packet data is then
+appended to the selected buffer. This buffer, not the current packet data, is 
+then searched for a MMS message through use of various functions exposed 
+in `util_tpkt`. 
+
+Case 3 Combined and Split:
+In this case, the above two techniques are combined to create a stream where 
+one packet contains a complete MMS message as well as the start of a second,
+and then the packets that follow contain the remainder of the second MMS 
+message. No additional considerations beyond those implemented for the prior
+two techniques were needed.
+
+
+*** MMS curse ***
+In the MMS curse, current and prior state are tracked as the potential message
+is processed byte by byte. The current state indicates the part of a fully
+encapsulated MMS message that the byte in that position would represent if it
+was found to contain MMS. The prior state is tracked so that the state machine
+knows where to pick up in the event of a message being split. Two special 
+states exist which are used to assist in determining when to stop 
+processing: MMS_STATE__MMS and MMS_STATE__NOT_FOUND. 
+
+During processing only states that are required to be specific values are 
+enforced, leaving more specific enforcement to the MMS inspector itself.
+This choice was made to help avoid false negatives caused by states that
+would not affect the inspector's parsing. For example, the TPKT version is
+expected to be the value 0x03, however this does not make any material 
+difference at this time to how the message is parsed so it is not enforced. 
+By contrast, the COTP PDU value is enforced to be of type `DT_DATA` as other
+values indicate that MMS is most likely not in use. 
+
+If all of the encapsulation layer checks pass, a best guess is made to 
+determine if the payload is a MMS message. This is done by searching for any
+of the known MMS message types. Unfortunately, this approach has the potential
+for some false positives to slip into the MMS inspector processing as the 
+message type indicators are only one byte in size. This was deemed preferred
+as it is enough to kick out the majority of non-MMS traffic while not needing
+to run the more intensive parsing that occurs in the inspector. 
+
diff --git a/src/service_inspectors/mms/ips_mms_data.cc b/src/service_inspectors/mms/ips_mms_data.cc
new file mode 100644 (file)
index 0000000..dc50af2
--- /dev/null
@@ -0,0 +1,161 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// ips_mms_data.cc author Jared Rittle <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;
+
diff --git a/src/service_inspectors/mms/ips_mms_func.cc b/src/service_inspectors/mms/ips_mms_func.cc
new file mode 100644 (file)
index 0000000..2276f81
--- /dev/null
@@ -0,0 +1,435 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// ips_mms_func.cc author Jared Rittle <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;
+
diff --git a/src/service_inspectors/mms/mms.cc b/src/service_inspectors/mms/mms.cc
new file mode 100644 (file)
index 0000000..3b6008d
--- /dev/null
@@ -0,0 +1,195 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// mms.cc author Jared Rittle <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
+};
+
diff --git a/src/service_inspectors/mms/mms.h b/src/service_inspectors/mms/mms.h
new file mode 100644 (file)
index 0000000..3a42184
--- /dev/null
@@ -0,0 +1,113 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// mms.h author Jared Rittle <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
+
diff --git a/src/service_inspectors/mms/mms_decode.cc b/src/service_inspectors/mms/mms_decode.cc
new file mode 100644 (file)
index 0000000..1756b39
--- /dev/null
@@ -0,0 +1,73 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// mms_decode.cc author Jared Rittle <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;
+}
+
diff --git a/src/service_inspectors/mms/mms_decode.h b/src/service_inspectors/mms/mms_decode.h
new file mode 100644 (file)
index 0000000..57ed07a
--- /dev/null
@@ -0,0 +1,44 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+//
+// mms_decode.h author Jared Rittle <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
+
diff --git a/src/service_inspectors/mms/mms_module.cc b/src/service_inspectors/mms/mms_module.cc
new file mode 100644 (file)
index 0000000..fbd0cf1
--- /dev/null
@@ -0,0 +1,83 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// mms_module.cc author Jared Rittle <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)
+{
+}
+
diff --git a/src/service_inspectors/mms/mms_module.h b/src/service_inspectors/mms/mms_module.h
new file mode 100644 (file)
index 0000000..1ff0dee
--- /dev/null
@@ -0,0 +1,70 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// mms_module.h author Jared Rittle <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
+
diff --git a/src/service_inspectors/mms/mms_splitter.cc b/src/service_inspectors/mms/mms_splitter.cc
new file mode 100644 (file)
index 0000000..5d6137a
--- /dev/null
@@ -0,0 +1,361 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// mms_splitter.cc author Jared Rittle <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;
+}
+
diff --git a/src/service_inspectors/mms/mms_splitter.h b/src/service_inspectors/mms/mms_splitter.h
new file mode 100644 (file)
index 0000000..c4114e0
--- /dev/null
@@ -0,0 +1,54 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// mms_splitter.h author Jared Rittle <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
+
diff --git a/src/service_inspectors/mms/tpkt/cotp_decode.cc b/src/service_inspectors/mms/tpkt/cotp_decode.cc
new file mode 100644 (file)
index 0000000..b5ef7aa
--- /dev/null
@@ -0,0 +1,73 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// cotp_decode.cc author Jared Rittle <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;
+}
+
diff --git a/src/service_inspectors/mms/tpkt/cotp_decode.h b/src/service_inspectors/mms/tpkt/cotp_decode.h
new file mode 100644 (file)
index 0000000..d4a3e59
--- /dev/null
@@ -0,0 +1,50 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// cotp_decode.h author Jared Rittle <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
+
diff --git a/src/service_inspectors/mms/tpkt/osi_acse_decode.cc b/src/service_inspectors/mms/tpkt/osi_acse_decode.cc
new file mode 100644 (file)
index 0000000..9e85ade
--- /dev/null
@@ -0,0 +1,428 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// osi_acse_decode.cc author Jared Rittle <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;
+}
+
diff --git a/src/service_inspectors/mms/tpkt/osi_acse_decode.h b/src/service_inspectors/mms/tpkt/osi_acse_decode.h
new file mode 100644 (file)
index 0000000..427c39c
--- /dev/null
@@ -0,0 +1,40 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// osi_acse_decode.h author Jared Rittle <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
+
diff --git a/src/service_inspectors/mms/tpkt/osi_pres_decode.cc b/src/service_inspectors/mms/tpkt/osi_pres_decode.cc
new file mode 100644 (file)
index 0000000..3baf395
--- /dev/null
@@ -0,0 +1,540 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// osi_pres_decode.cc author Jared Rittle <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;
+}
+
diff --git a/src/service_inspectors/mms/tpkt/osi_pres_decode.h b/src/service_inspectors/mms/tpkt/osi_pres_decode.h
new file mode 100644 (file)
index 0000000..1df6e12
--- /dev/null
@@ -0,0 +1,58 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// osi_pres_decode.h author Jared Rittle <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
+
diff --git a/src/service_inspectors/mms/tpkt/osi_session_decode.cc b/src/service_inspectors/mms/tpkt/osi_session_decode.cc
new file mode 100644 (file)
index 0000000..00bda4a
--- /dev/null
@@ -0,0 +1,367 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// osi_session_decode.cc author Jared Rittle <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;
+}
+
diff --git a/src/service_inspectors/mms/tpkt/osi_session_decode.h b/src/service_inspectors/mms/tpkt/osi_session_decode.h
new file mode 100644 (file)
index 0000000..b014840
--- /dev/null
@@ -0,0 +1,61 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// osi_session_decode.h author Jared Rittle <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
+
diff --git a/src/service_inspectors/mms/tpkt/tpkt_decode.cc b/src/service_inspectors/mms/tpkt/tpkt_decode.cc
new file mode 100644 (file)
index 0000000..48b3acf
--- /dev/null
@@ -0,0 +1,43 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// tpkt_decode.cc author Jared Rittle <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;
+}
+
diff --git a/src/service_inspectors/mms/tpkt/tpkt_decode.h b/src/service_inspectors/mms/tpkt/tpkt_decode.h
new file mode 100644 (file)
index 0000000..37465f8
--- /dev/null
@@ -0,0 +1,55 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// tpkt_decode.h author Jared Rittle <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
+
diff --git a/src/service_inspectors/mms/util_tpkt.cc b/src/service_inspectors/mms/util_tpkt.cc
new file mode 100644 (file)
index 0000000..9b558e3
--- /dev/null
@@ -0,0 +1,419 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// util_tpkt.cc author Jared Rittle <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);
+}
+
diff --git a/src/service_inspectors/mms/util_tpkt.h b/src/service_inspectors/mms/util_tpkt.h
new file mode 100644 (file)
index 0000000..1da42a1
--- /dev/null
@@ -0,0 +1,186 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// util_tpkt.h author Jared Rittle <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
+
index 21783793c39f7bc3102e60b00de687ab45818f36..20eea1754d0905578144c4746d2c1992d1940e32 100644 (file)
@@ -53,6 +53,7 @@ extern const BaseApi* sin_dce[];
 extern const BaseApi* sin_dnp3[];
 extern const BaseApi* sin_gtp[];
 extern const BaseApi* sin_iec104[];
+extern const BaseApi* sin_mms[];
 extern const BaseApi* sin_modbus[];
 extern const BaseApi* sin_netflow[];
 extern const BaseApi* sin_s7commplus[];
@@ -94,6 +95,7 @@ void load_service_inspectors()
     PluginManager::load_plugins(sin_dnp3);
     PluginManager::load_plugins(sin_gtp);
     PluginManager::load_plugins(sin_iec104);
+    PluginManager::load_plugins(sin_mms);
     PluginManager::load_plugins(sin_modbus);
     PluginManager::load_plugins(sin_netflow);
     PluginManager::load_plugins(sin_s7commplus);
index 31cbc290cd22e0d0cb264fe4fb9084ead4b54c8a..63eb3c132ab72ccfcae469e98bdc8e7d10d03e97 100644 (file)
@@ -4,6 +4,7 @@ set(FILE_LIST
     curses.h
     magic.cc
     magic.h
+    mms_curse.h
     hexes.cc
     spells.cc
     wizard.cc
index b14727cab81e76da1d6dca6b2a3715f06d2b689e..f0f1969b2a5456a8331d8be0ee17d409a4964fd1 100644 (file)
@@ -16,6 +16,7 @@
 // 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"
@@ -23,6 +24,8 @@
 
 #include "curses.h"
 
+#include <assert.h>
+
 using namespace std;
 
 enum DceRpcPduType
@@ -258,6 +261,220 @@ static bool dce_smb_curse(const uint8_t* data, unsigned len, CurseTracker* track
     return false;
 }
 
+
+static bool mms_curse(const uint8_t* data, unsigned len, CurseTracker* tracker)
+{
+    // peg the tracker to MMS
+    CurseTracker::MMS& mms = tracker->mms;
+
+    // if the state is set to MMS_STATE__SEARCH it means we most likely
+    // have a split pipelined message coming through and will need to
+    // reset the state
+    if (mms.state == MMS_STATE__SEARCH) 
+    {
+        mms.state = mms.last_state;
+    }
+
+    // define all known MMS tags to check for
+    enum 
+    {
+        MMS_CONFIRMED_REQUEST_TAG    = 0xA0,
+        MMS_CONFIRMED_RESPONSE_TAG   = 0xA1,
+        MMS_CONFIRMED_ERROR_TAG      = 0xA2,
+        MMS_UNCONFIRMED_TAG          = 0xA3,
+        MMS_REJECT_TAG               = 0xA4,
+        MMS_CANCEL_REQUEST_TAG       = 0x85,
+        MMS_CANCEL_RESPONSE_TAG      = 0x86,
+        MMS_CANCEL_ERROR_TAG         = 0xA7,
+        MMS_INITIATE_REQUEST_TAG     = 0xA8,
+        MMS_INITIATE_RESPONSE_TAG    = 0xA9,
+        MMS_INITIATE_ERROR_TAG       = 0xAA,
+        MMS_CONCLUDE_REQUEST_TAG     = 0x8B,
+        MMS_CONCLUDE_RESPONSE_TAG    = 0x8C,
+        MMS_CONCLUDE_ERROR_TAG       = 0xAD,
+    };
+
+    uint32_t idx = 0;
+    while (idx < len)
+    {
+        switch (mms.state)
+        {
+            case MMS_STATE__TPKT_VER:
+            {
+                mms.state = MMS_STATE__TPKT_RES;
+                break;
+            }
+
+            case MMS_STATE__TPKT_RES:
+            {
+                mms.state = MMS_STATE__TPKT_LEN1;
+                break;
+            }
+
+            case MMS_STATE__TPKT_LEN1:
+            {
+                mms.state = MMS_STATE__TPKT_LEN2;
+                break;
+            }
+
+            case MMS_STATE__TPKT_LEN2:
+            {
+                mms.state = MMS_STATE__COTP_LEN;
+                break;
+            }
+
+            case MMS_STATE__COTP_LEN:
+            {
+                mms.state = MMS_STATE__COTP_PDU;
+                break;
+            }
+
+            case MMS_STATE__COTP_PDU:
+            {
+                // 7 6 5 4 3 2 1 0
+                // ---------------
+                // . . . . x x x x   Destination Reference
+                // x x x x . . . .   PDU Type
+                const uint32_t MMS_COTP_PDU_DT_DATA = 0x0F;
+                if (data[idx] >> 0x04 != MMS_COTP_PDU_DT_DATA) 
+                {
+                    mms.state = MMS_STATE__NOT_FOUND;
+                    break;
+                }
+                mms.state = MMS_STATE__COTP_TPDU_NUM;
+                break;
+            }
+
+            case MMS_STATE__COTP_TPDU_NUM:
+            {
+                mms.state = MMS_STATE__OSI_SESSION_SPDU;
+                break;
+            }
+
+            case MMS_STATE__OSI_SESSION_SPDU:
+            {
+                // define all known OSI Session layer SPDU tags to check
+                enum 
+                {
+                    MMS_OSI_SESSION_SPDU_GT_DT = 0x01,
+                    MMS_OSI_SESSION_SPDU_CN = 0x0D,
+                    MMS_OSI_SESSION_SPDU_AC = 0x0E,
+                };
+
+                switch (data[idx]) 
+                {
+                    // check for a known MMS message tag in the event Session/Pres/ACSE aren't used
+                    case MMS_CONFIRMED_REQUEST_TAG:    // fallthrough intentional
+                    case MMS_CONFIRMED_RESPONSE_TAG:   // fallthrough intentional
+                    case MMS_CONFIRMED_ERROR_TAG:      // fallthrough intentional
+                    case MMS_UNCONFIRMED_TAG:          // fallthrough intentional
+                    case MMS_REJECT_TAG:               // fallthrough intentional
+                    case MMS_CANCEL_REQUEST_TAG:       // fallthrough intentional
+                    case MMS_CANCEL_RESPONSE_TAG:      // fallthrough intentional
+                    case MMS_CANCEL_ERROR_TAG:         // fallthrough intentional
+                    case MMS_INITIATE_REQUEST_TAG:     // fallthrough intentional
+                    case MMS_INITIATE_RESPONSE_TAG:    // fallthrough intentional
+                    case MMS_INITIATE_ERROR_TAG:       // fallthrough intentional
+                    case MMS_CONCLUDE_REQUEST_TAG:     // fallthrough intentional
+                    case MMS_CONCLUDE_RESPONSE_TAG:    // fallthrough intentional
+                    case MMS_CONCLUDE_ERROR_TAG:
+                    {
+                        // if an MMS tag exists in the remaining data,
+                        // hand off to the MMS service inspector
+                        mms.state = MMS_STATE__FOUND;
+                        break;
+                    }
+
+                    // if mms isn't found, search for an OSI Session layer 
+                    case MMS_OSI_SESSION_SPDU_GT_DT: // fallthrough intentional      
+                    case MMS_OSI_SESSION_SPDU_CN:    // fallthrough intentional   
+                    case MMS_OSI_SESSION_SPDU_AC:
+                    {
+                        mms.state = MMS_STATE__MMS;
+                        break;
+                    }
+
+                    // if neither are found, it is most likely not MMS
+                    default:
+                    {
+                        mms.state = MMS_STATE__NOT_FOUND;
+                    }
+                }
+
+                break;
+            }            
+
+            case MMS_STATE__MMS:
+            {
+                // loop through the remaining bytes in the buffer checking for known MMS tags
+                for (uint32_t i=idx; i < len; i++) 
+                {
+                    // for each remaining byte check to see if it is in the known tag map
+                    switch (data[i]) 
+                    {
+                        case MMS_CONFIRMED_REQUEST_TAG:    // fallthrough intentional
+                        case MMS_CONFIRMED_RESPONSE_TAG:   // fallthrough intentional
+                        case MMS_CONFIRMED_ERROR_TAG:      // fallthrough intentional
+                        case MMS_UNCONFIRMED_TAG:          // fallthrough intentional
+                        case MMS_REJECT_TAG:               // fallthrough intentional
+                        case MMS_CANCEL_REQUEST_TAG:       // fallthrough intentional
+                        case MMS_CANCEL_RESPONSE_TAG:      // fallthrough intentional
+                        case MMS_CANCEL_ERROR_TAG:         // fallthrough intentional
+                        case MMS_INITIATE_REQUEST_TAG:     // fallthrough intentional
+                        case MMS_INITIATE_RESPONSE_TAG:    // fallthrough intentional
+                        case MMS_INITIATE_ERROR_TAG:       // fallthrough intentional
+                        case MMS_CONCLUDE_REQUEST_TAG:     // fallthrough intentional
+                        case MMS_CONCLUDE_RESPONSE_TAG:    // fallthrough intentional
+                        case MMS_CONCLUDE_ERROR_TAG:
+                        {
+                            // if an MMS tag exists in the remaining data,
+                            // hand off to the MMS service inspector
+                            mms.state = MMS_STATE__FOUND;
+                            break;
+                        }
+                        // no default here as it we don't know when we would hit 
+                        // the first MMS tag without doing full parsing 
+                    }
+
+                    // exit the loop when a state has been determined
+                    if (mms.state == MMS_STATE__NOT_FOUND 
+                        or mms.state == MMS_STATE__SEARCH 
+                        or mms.state == MMS_STATE__FOUND) 
+                    {
+                        break;
+                    }
+                }
+                break;
+            }
+
+            case MMS_STATE__FOUND:
+            {
+                mms.state = MMS_STATE__TPKT_VER;
+                return true;
+            }
+
+            case MMS_STATE__NOT_FOUND:
+            {
+                mms.state = MMS_STATE__TPKT_VER;
+                return false;
+            }
+
+            default:
+            {
+                mms.state = MMS_STATE__NOT_FOUND;
+                assert(false);
+                break;
+            }
+        }
+        idx++;
+    }
+
+    mms.last_state = mms.state;
+    mms.state = MMS_STATE__SEARCH;
+    return false;
+}
+
+
 namespace SSL_Const
 {
 static constexpr uint8_t hdr_len = 9;
@@ -396,6 +613,7 @@ static vector<CurseDetails> curse_map
     // name      service        alg            is_tcp
     { "dce_udp", "dcerpc"     , dce_udp_curse, false },
     { "dce_tcp", "dcerpc"     , dce_tcp_curse, true  },
+    { "mms"    , "mms"        , mms_curse    , true  },
     { "dce_smb", "netbios-ssn", dce_smb_curse, true  },
     { "sslv2"  , "ssl"        , ssl_v2_curse , true  }
 };
index 1ffe226a289888eb2baadc4a698faaedc74eae03..c7815efe464b8d6b3ad64888d181385631d1c1b5 100644 (file)
@@ -24,6 +24,8 @@
 #include <string>
 #include <vector>
 
+#include "mms_curse.h"
+
 enum DCE_State
 {
     STATE_0 = 0,
@@ -65,6 +67,12 @@ public:
         uint32_t helper;
     } dce;
 
+    struct MMS
+    {
+        MMS_State state;
+        MMS_State last_state;
+    } mms;
+
     struct SSL
     {
         SSL_State state;
@@ -77,6 +85,8 @@ public:
     CurseTracker()
     {
         dce.state = DCE_State::STATE_0;
+        mms.state = MMS_State::MMS_STATE__TPKT_VER;
+        mms.last_state = mms.state;
         ssl.state = SSL_State::BYTE_0_LEN_MSB;
     }
 };
diff --git a/src/service_inspectors/wizard/mms_curse.h b/src/service_inspectors/wizard/mms_curse.h
new file mode 100644 (file)
index 0000000..5a2888f
--- /dev/null
@@ -0,0 +1,46 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2022-2022 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// mms_curse.h author Jared Rittle <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
+
index 5a9973f7a40502695b936d62af10edca7f071e88..d21ca1ced52d36e9e90249295e1da2d2f1b71acf 100644 (file)
@@ -103,7 +103,7 @@ static const Parameter s_params[] =
     { "spells", Parameter::PT_LIST, wizard_spells_params, nullptr,
       "criteria for text service identification" },
 
-    { "curses", Parameter::PT_MULTI, "dce_smb | dce_udp | dce_tcp | sslv2", nullptr,
+    { "curses", Parameter::PT_MULTI, "dce_smb | dce_udp | dce_tcp | mms | sslv2", nullptr,
       "enable service identification based on internal algorithm" },
 
     { "max_search_depth", Parameter::PT_INT, "0:65535", "8192",