]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #1751 in SNORT/snort3 from ~PRDAMODH/snort3:S7COMMPLUS-dev to...
authorShravan Rangarajuvenkata (shrarang) <shrarang@cisco.com>
Fri, 1 Nov 2019 01:47:54 +0000 (21:47 -0400)
committerShravan Rangarajuvenkata (shrarang) <shrarang@cisco.com>
Fri, 1 Nov 2019 01:47:54 +0000 (21:47 -0400)
Squashed commit of the following:

commit c5548d43e80b6dd1534e2e7a218c6bc5e2ff1200
Author: Pradeep Damodharan <prdamodh@cisco.com>
Date:   Wed Sep 18 15:54:12 2019 -0400

    s7commplus: Initial working version of s7commplus service inspector

15 files changed:
src/service_inspectors/CMakeLists.txt
src/service_inspectors/s7commplus/CMakeLists.txt [new file with mode: 0644]
src/service_inspectors/s7commplus/dev_notes.txt [new file with mode: 0644]
src/service_inspectors/s7commplus/ips_s7comm_content.cc [new file with mode: 0644]
src/service_inspectors/s7commplus/ips_s7comm_func.cc [new file with mode: 0644]
src/service_inspectors/s7commplus/ips_s7comm_opcode.cc [new file with mode: 0644]
src/service_inspectors/s7commplus/s7comm.cc [new file with mode: 0644]
src/service_inspectors/s7commplus/s7comm.h [new file with mode: 0644]
src/service_inspectors/s7commplus/s7comm_decode.cc [new file with mode: 0644]
src/service_inspectors/s7commplus/s7comm_decode.h [new file with mode: 0644]
src/service_inspectors/s7commplus/s7comm_module.cc [new file with mode: 0644]
src/service_inspectors/s7commplus/s7comm_module.h [new file with mode: 0644]
src/service_inspectors/s7commplus/s7comm_paf.cc [new file with mode: 0644]
src/service_inspectors/s7commplus/s7comm_paf.h [new file with mode: 0644]
src/service_inspectors/service_inspectors.cc

index 02c6cae2cbc92a7c30791ac0becd23ff6a77c08f..962f60433e43d75117a807fd1ba7644b2fcb8dcc 100644 (file)
@@ -16,6 +16,7 @@ add_subdirectory(smtp)
 add_subdirectory(ssh)
 add_subdirectory(ssl)
 add_subdirectory(wizard)
+add_subdirectory(s7commplus)
 
 if (STATIC_INSPECTORS)
     set (STATIC_INSPECTOR_OBJS
@@ -32,6 +33,7 @@ if (STATIC_INSPECTORS)
         $<TARGET_OBJECTS:smtp>
         $<TARGET_OBJECTS:ssh>
         $<TARGET_OBJECTS:wizard>
+        $<TARGET_OBJECTS:s7commplus>
     )
 endif()
 
diff --git a/src/service_inspectors/s7commplus/CMakeLists.txt b/src/service_inspectors/s7commplus/CMakeLists.txt
new file mode 100644 (file)
index 0000000..6280158
--- /dev/null
@@ -0,0 +1,22 @@
+set( FILE_LIST
+    s7comm.cc
+    s7comm.h
+    s7comm_decode.cc
+    s7comm_decode.h
+    s7comm_module.cc
+    s7comm_module.h
+    s7comm_paf.cc
+    s7comm_paf.h
+    ips_s7comm_content.cc
+    ips_s7comm_func.cc
+    ips_s7comm_opcode.cc
+)
+
+if (STATIC_INSPECTORS)
+    add_library(s7commplus OBJECT ${FILE_LIST})
+
+else (STATIC_INSPECTORS)
+    add_dynamic_module(s7commplus inspectors ${FILE_LIST})
+
+endif (STATIC_INSPECTORS)
+
diff --git a/src/service_inspectors/s7commplus/dev_notes.txt b/src/service_inspectors/s7commplus/dev_notes.txt
new file mode 100644 (file)
index 0000000..8087ba3
--- /dev/null
@@ -0,0 +1,16 @@
+S7comm (S7 Communication)and S7commplus are Siemens proprietary protocols that
+runs between programmable logic controllers (PLCs) of the Siemens S7-300/400 
+and S7-1500/1200(v4.0) families, typically on TCP port 102.
+
+It is used for PLC programming, exchanging data between PLCs, accessing PLC 
+data from SCADA systems and diagnostic purposes. The S7CommPlus protocol is
+a new version of the original S7Comm protocol.
+
+The S7comm and S7commplus data comes as payload of COTP data packets. The first 
+byte is 0x32 for S7comm and 0x72 for S7commplus as protocol identifiers. The same 
+inspector will be used for both protocols, although currently only S7commplus is 
+supported.
+
+The S7Commplus inspector decodes the S7Commplus protocol and provides rule options
+to access certain protocol fields and Data content. This allows the user to write 
+rules for S7commplus packets without decoding the protocol.
diff --git a/src/service_inspectors/s7commplus/ips_s7comm_content.cc b/src/service_inspectors/s7commplus/ips_s7comm_content.cc
new file mode 100644 (file)
index 0000000..550cdca
--- /dev/null
@@ -0,0 +1,158 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2018-2019 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_s7comm_content.cc author Pradeep Damodharan <prdamodh@cisco.com>
+// based on work by Jeffrey Gu <jgu@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/hashfcn.h"
+#include "profiler/profiler.h"
+#include "protocols/packet.h"
+
+#include "s7comm_decode.h"
+
+using namespace snort;
+
+static const char* s_name = "s7commplus_content";
+
+//-------------------------------------------------------------------------
+// version option
+//-------------------------------------------------------------------------
+
+static THREAD_LOCAL ProfileStats s7commplus_content_prof;
+
+class S7commplusContentOption : public IpsOption
+{
+public:
+    S7commplusContentOption() : IpsOption(s_name, RULE_OPTION_TYPE_BUFFER_SET) { }
+
+    uint32_t hash() const override;
+    bool operator==(const IpsOption&) const override;
+
+    EvalStatus eval(Cursor&, Packet*) override;
+};
+
+uint32_t S7commplusContentOption::hash() const
+{
+    uint32_t a = 0, b = 0, c = 0;
+
+    mix_str(a, b, c, get_name());
+    finalize(a,b,c);
+
+    return c;
+}
+
+bool S7commplusContentOption::operator==(const IpsOption& ips) const
+{
+    return !strcmp(get_name(), ips.get_name());
+}
+
+IpsOption::EvalStatus S7commplusContentOption::eval(Cursor& c, Packet* p)
+{
+    RuleProfile profile(s7commplus_content_prof);
+
+    if ( !p->flow )
+        return NO_MATCH;
+
+    if ( !p->is_full_pdu() )
+        return NO_MATCH;
+
+    if ( p->dsize < S7COMMPLUS_MIN_HDR_LEN )
+        return NO_MATCH;
+
+    c.set(s_name, p->data + S7COMMPLUS_MIN_HDR_LEN, p->dsize - S7COMMPLUS_MIN_HDR_LEN);
+
+    return MATCH;
+}
+
+//-------------------------------------------------------------------------
+// module
+//-------------------------------------------------------------------------
+
+#define s_help \
+    "rule option to set cursor to s7commplus content"
+
+class S7commplusContentModule : public Module
+{
+public:
+    S7commplusContentModule() : Module(s_name, s_help) { }
+
+    ProfileStats* get_profile() const override
+    { return &s7commplus_content_prof; }
+
+    Usage get_usage() const override
+    { return DETECT; }
+};
+
+//-------------------------------------------------------------------------
+// api
+//-------------------------------------------------------------------------
+
+static Module* mod_ctor()
+{
+    return new S7commplusContentModule;
+}
+
+static void mod_dtor(Module* m)
+{
+    delete m;
+}
+
+static IpsOption* opt_ctor(Module*, OptTreeNode*)
+{
+    return new S7commplusContentOption;
+}
+
+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_s7commplus_content = &ips_api.base;
+
diff --git a/src/service_inspectors/s7commplus/ips_s7comm_func.cc b/src/service_inspectors/s7commplus/ips_s7comm_func.cc
new file mode 100644 (file)
index 0000000..7cb3d01
--- /dev/null
@@ -0,0 +1,241 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2018-2019 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_s7comm_func.cc author Pradeep Damodharan <prdamodh@cisco.com>
+// based on work by Jeffrey Gu <jgu@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "framework/ips_option.h"
+#include "framework/module.h"
+#include "hash/hashfcn.h"
+#include "protocols/packet.h"
+#include "profiler/profiler.h"
+
+#include "s7comm.h"
+
+using namespace snort;
+
+static const char* s_name = "s7commplus_func";
+
+//-------------------------------------------------------------------------
+// func lookup
+//-------------------------------------------------------------------------
+
+struct S7commplusFuncMap
+{
+    const char* name;
+    uint16_t func;
+};
+
+/* Mapping of name -> function code for 's7commplus_func' option. */
+static S7commplusFuncMap s7commp_func_map[] =
+{
+    { "explore",          0x04BB },
+    { "createobject",     0x04CA },
+    { "deleteobject",     0x04D4 },
+    { "setvariable",      0x04F2 },
+    { "getlink",          0x0524 },
+    { "setmultivar",      0x0542 },
+    { "getmultivar",      0x054C },
+    { "beginsequence",    0x0556 },
+    { "endsequence",      0x0560 },
+    { "invoke",           0x056B },
+    { "getvarsubstr",     0x0586 }
+};
+
+static bool get_func(const char* s, long& n)
+{
+    constexpr size_t max = (sizeof(s7commp_func_map) / sizeof(S7commplusFuncMap));
+
+    for ( size_t i = 0; i < max; ++i )
+    {
+        if ( !strcmp(s, s7commp_func_map[i].name) )
+        {
+            n = s7commp_func_map[i].func;
+            return true;
+        }
+    }
+    return false;
+}
+
+//-------------------------------------------------------------------------
+// func option
+//-------------------------------------------------------------------------
+
+static THREAD_LOCAL ProfileStats s7commplus_func_prof;
+
+class S7commplusFuncOption : public IpsOption
+{
+public:
+    S7commplusFuncOption(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;
+};
+
+uint32_t S7commplusFuncOption::hash() const
+{
+    uint32_t a = func, b = 0, c = 0;
+
+    mix_str(a, b, c, get_name());
+    finalize(a,b,c);
+
+    return c;
+}
+
+bool S7commplusFuncOption::operator==(const IpsOption& ips) const
+{
+    if ( strcmp(get_name(), ips.get_name()) )
+        return false;
+
+    const S7commplusFuncOption& rhs = (const S7commplusFuncOption&)ips;
+    return ( func == rhs.func );
+}
+
+IpsOption::EvalStatus S7commplusFuncOption::eval(Cursor&, Packet* p)
+{
+    RuleProfile profile(s7commplus_func_prof);
+
+    if ( !p->flow )
+        return NO_MATCH;
+
+    if ( !p->is_full_pdu() )
+        return NO_MATCH;
+
+    S7commplusFlowData* mfd =
+        (S7commplusFlowData*)p->flow->get_flow_data(S7commplusFlowData::inspector_id);
+
+    if ( mfd and func == mfd->ssn_data.s7commplus_function )
+        return MATCH;
+
+    return NO_MATCH;
+}
+
+//-------------------------------------------------------------------------
+// module
+//-------------------------------------------------------------------------
+
+static const Parameter s_params[] =
+{
+    { "~", Parameter::PT_STRING, nullptr, nullptr,
+      "function code to match" },
+
+    { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+#define s_help \
+    "rule option to check s7commplus function code"
+
+class S7commplusFuncModule : public Module
+{
+public:
+    S7commplusFuncModule() : Module(s_name, s_help, s_params) { }
+
+    bool set(const char*, Value&, SnortConfig*) override;
+
+    ProfileStats* get_profile() const override
+    { return &s7commplus_func_prof; }
+
+    Usage get_usage() const override
+    { return DETECT; }
+
+public:
+    //uint8_t func;
+    uint16_t func;
+};
+
+bool S7commplusFuncModule::set(const char*, Value& v, SnortConfig*)
+{
+    if ( !v.is("~") )
+        return false;
+
+    long n;
+
+    if ( v.strtol(n) )
+        func = static_cast<uint16_t>(n);
+
+    else if ( get_func(v.get_string(), n) )
+        func = static_cast<uint16_t>(n);
+
+    else
+        return false;
+
+    return true;
+}
+
+//-------------------------------------------------------------------------
+// api
+//-------------------------------------------------------------------------
+
+static Module* mod_ctor()
+{
+    return new S7commplusFuncModule;
+}
+
+static void mod_dtor(Module* m)
+{
+    delete m;
+}
+
+static IpsOption* opt_ctor(Module* m, OptTreeNode*)
+{
+    S7commplusFuncModule* mod = (S7commplusFuncModule*)m;
+    return new S7commplusFuncOption(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_s7commplus_func = &ips_api.base;
+
diff --git a/src/service_inspectors/s7commplus/ips_s7comm_opcode.cc b/src/service_inspectors/s7commplus/ips_s7comm_opcode.cc
new file mode 100644 (file)
index 0000000..26cd8aa
--- /dev/null
@@ -0,0 +1,233 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2018-2019 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_s7comm_opcode.cc author Pradeep Damodharan <prdamodh@cisco.com>
+// based on work by Jeffrey Gu <jgu@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "framework/ips_option.h"
+#include "framework/module.h"
+#include "hash/hashfcn.h"
+#include "protocols/packet.h"
+#include "profiler/profiler.h"
+
+#include "s7comm.h"
+
+using namespace snort;
+
+static const char* s_name = "s7commplus_opcode";
+
+//-------------------------------------------------------------------------
+// func lookup
+//-------------------------------------------------------------------------
+
+struct S7commplusOpcodeMap
+{
+    const char* name;
+    uint8_t opcode;
+};
+
+/* Mapping of name -> opcode for 's7p_opcode' option. */
+static S7commplusOpcodeMap s7commp_opcode_map[] =
+{
+    { "request",      0x31 },
+    { "response",     0x32 },
+    { "notification", 0x33 },
+    { "response2",    0x02 }
+};
+
+static bool get_opcode(const char* s, long& n)
+{
+    constexpr size_t max = (sizeof(s7commp_opcode_map) / sizeof(S7commplusOpcodeMap));
+
+    for ( size_t i = 0; i < max; ++i )
+    {
+        if ( !strcmp(s, s7commp_opcode_map[i].name) )
+        {
+            n = s7commp_opcode_map[i].opcode;
+            return true;
+        }
+    }
+    return false;
+}
+
+//-------------------------------------------------------------------------
+// opcode option
+//-------------------------------------------------------------------------
+
+static THREAD_LOCAL ProfileStats s7commplus_opcode_prof;
+
+class S7commplusOpcodeOption : public IpsOption
+{
+public:
+    S7commplusOpcodeOption(uint8_t v) : IpsOption(s_name)
+    { opcode = v; }
+
+    uint32_t hash() const override;
+    bool operator==(const IpsOption&) const override;
+
+    EvalStatus eval(Cursor&, Packet*) override;
+
+public:
+    uint8_t opcode;
+};
+
+uint32_t S7commplusOpcodeOption::hash() const
+{
+    uint32_t a = opcode, b = 0, c = 0;
+
+    mix_str(a, b, c, get_name());
+    finalize(a,b,c);
+
+    return c;
+}
+
+bool S7commplusOpcodeOption::operator==(const IpsOption& ips) const
+{
+    if ( strcmp(get_name(), ips.get_name()) )
+        return false;
+
+    const S7commplusOpcodeOption& rhs = (const S7commplusOpcodeOption&)ips;
+    return ( opcode == rhs.opcode );
+}
+
+IpsOption::EvalStatus S7commplusOpcodeOption::eval(Cursor&, Packet* p)
+{
+    RuleProfile profile(s7commplus_opcode_prof);
+
+    if ( !p->flow )
+        return NO_MATCH;
+
+    if ( !p->is_full_pdu() )
+        return NO_MATCH;
+
+    S7commplusFlowData* mfd =
+        (S7commplusFlowData*)p->flow->get_flow_data(S7commplusFlowData::inspector_id);
+
+    if ( mfd and opcode == mfd->ssn_data.s7commplus_opcode)
+        return MATCH;
+
+    return NO_MATCH;
+}
+
+//-------------------------------------------------------------------------
+// module
+//-------------------------------------------------------------------------
+
+static const Parameter s_params[] =
+{
+    { "~", Parameter::PT_STRING, nullptr, nullptr,
+      "opcode code to match" },
+
+    { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+#define s_help \
+    "rule option to check s7commplus opcode code"
+
+class S7commplusOpcodeModule : public Module
+{
+public:
+    S7commplusOpcodeModule() : Module(s_name, s_help, s_params) { }
+
+    bool set(const char*, Value&, SnortConfig*) override;
+
+    ProfileStats* get_profile() const override
+    { return &s7commplus_opcode_prof; }
+
+    Usage get_usage() const override
+    { return DETECT; }
+
+public:
+    uint8_t opcode;
+};
+
+bool S7commplusOpcodeModule::set(const char*, Value& v, SnortConfig*)
+{
+    if ( !v.is("~") )
+        return false;
+
+    long n;
+
+    if ( v.strtol(n) )
+        opcode = (uint8_t)n;
+
+    else if ( get_opcode(v.get_string(), n) )
+        opcode = (uint8_t)n;
+
+    else
+        return false;
+
+    return true;
+}
+
+//-------------------------------------------------------------------------
+// api
+//-------------------------------------------------------------------------
+
+static Module* mod_ctor()
+{
+    return new S7commplusOpcodeModule;
+}
+
+static void mod_dtor(Module* m)
+{
+    delete m;
+}
+
+static IpsOption* opt_ctor(Module* m, OptTreeNode*)
+{
+    S7commplusOpcodeModule* mod = (S7commplusOpcodeModule*)m;
+    return new S7commplusOpcodeOption(mod->opcode);
+}
+
+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_s7commplus_opcode = &ips_api.base;
+
diff --git a/src/service_inspectors/s7commplus/s7comm.cc b/src/service_inspectors/s7commplus/s7comm.cc
new file mode 100644 (file)
index 0000000..38e152e
--- /dev/null
@@ -0,0 +1,192 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2018-2019 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.
+//--------------------------------------------------------------------------
+
+// s7comm.cc author Pradeep Damodharan <prdamodh@cisco.com>
+// based on work by Jeffrey Gu <jgu@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "s7comm.h"
+
+#include "events/event_queue.h"
+#include "detection/detection_engine.h"
+#include "profiler/profiler.h"
+#include "protocols/packet.h"
+
+#include "s7comm_decode.h"
+#include "s7comm_module.h"
+#include "s7comm_paf.h"
+
+using namespace snort;
+
+THREAD_LOCAL S7commplusStats s7commplus_stats;
+
+//-------------------------------------------------------------------------
+// flow stuff
+//-------------------------------------------------------------------------
+
+unsigned S7commplusFlowData::inspector_id = 0;
+
+void S7commplusFlowData::init()
+{
+    inspector_id = FlowData::create_flow_data_id();
+}
+
+S7commplusFlowData::S7commplusFlowData() : FlowData(inspector_id)
+{
+    s7commplus_stats.concurrent_sessions++;
+    if (s7commplus_stats.max_concurrent_sessions < s7commplus_stats.concurrent_sessions)
+        s7commplus_stats.max_concurrent_sessions = s7commplus_stats.concurrent_sessions;
+}
+
+S7commplusFlowData::~S7commplusFlowData()
+{
+    assert(s7commplus_stats.concurrent_sessions > 0);
+    s7commplus_stats.concurrent_sessions--;
+}
+
+//-------------------------------------------------------------------------
+// class stuff
+//-------------------------------------------------------------------------
+
+class S7commplus : public Inspector
+{
+public:
+    // default ctor / dtor
+    void eval(Packet*) override;
+
+    int get_message_type(int version, const char* name);
+    int get_info_type(int version, const char* name);
+
+    StreamSplitter* get_splitter(bool c2s) override
+    { return new S7commplusSplitter(c2s); }
+};
+
+void S7commplus::eval(Packet* p)
+{
+    Profile profile(s7commplus_prof);
+
+    // preconditions - what we registered for
+    assert(p->has_tcp_data());
+
+    S7commplusFlowData* mfd =
+        (S7commplusFlowData*)p->flow->get_flow_data(S7commplusFlowData::inspector_id);
+
+    if ( !p->is_full_pdu() )
+    {
+        if ( mfd )
+            mfd->reset();
+
+        // If a packet is rebuilt, but not a full PDU, then it's garbage that
+        // got flushed at the end of a stream.
+        if ( p->packet_flags & (PKT_REBUILT_STREAM|PKT_PDU_HEAD) )
+            DetectionEngine::queue_event(GID_S7COMMPLUS, S7COMMPLUS_BAD_LENGTH);
+
+        return;
+    }
+
+    if ( !mfd )
+    {
+        mfd = new S7commplusFlowData;
+        p->flow->set_flow_data(mfd);
+        s7commplus_stats.sessions++;
+    }
+
+    // When pipelined S7commplus 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 ( !S7commplusDecode(p) )
+        mfd->reset();
+}
+
+//-------------------------------------------------------------------------
+// plugin stuff
+//-------------------------------------------------------------------------
+
+static Module* mod_ctor()
+{ return new S7commplusModule; }
+
+static void mod_dtor(Module* m)
+{ delete m; }
+
+static void s7commplus_init()
+{
+    S7commplusFlowData::init();
+}
+
+static Inspector* s7commplus_ctor(Module*)
+{
+    return new S7commplus;
+}
+
+static void s7commplus_dtor(Inspector* p)
+{
+    delete p;
+}
+
+//-------------------------------------------------------------------------
+
+static const InspectApi s7commplus_api =
+{
+    {
+        PT_INSPECTOR,
+        sizeof(InspectApi),
+        INSAPI_VERSION,
+        0,
+        API_RESERVED,
+        API_OPTIONS,
+        S7COMMPLUS_NAME,
+        S7COMMPLUS_HELP,
+        mod_ctor,
+        mod_dtor
+    },
+    IT_SERVICE,
+    PROTO_BIT__PDU,
+    nullptr,
+    "s7commplus",
+    s7commplus_init,
+    nullptr,
+    nullptr, // tinit
+    nullptr, // tterm
+    s7commplus_ctor,
+    s7commplus_dtor,
+    nullptr, // ssn
+    nullptr  // reset
+};
+
+extern const BaseApi* ips_s7commplus_opcode;
+extern const BaseApi* ips_s7commplus_func;
+extern const BaseApi* ips_s7commplus_content;
+
+#ifdef BUILDING_SO
+SO_PUBLIC const BaseApi* snort_plugins[] =
+#else
+const BaseApi* sin_s7commplus[] =
+#endif
+{
+    &s7commplus_api.base,
+    ips_s7commplus_opcode,
+    ips_s7commplus_func,
+    ips_s7commplus_content,
+    nullptr
+};
+
diff --git a/src/service_inspectors/s7commplus/s7comm.h b/src/service_inspectors/s7commplus/s7comm.h
new file mode 100644 (file)
index 0000000..1ff4414
--- /dev/null
@@ -0,0 +1,81 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2018-2019 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.
+//--------------------------------------------------------------------------
+
+// s7comm.h author Pradeep Damodharan <prdamodh@cisco.com>
+// based on work by Jeffrey Gu <jgu@cisco.com>
+
+#ifndef S7COMM_H
+#define S7COMM_H
+
+#include "flow/flow.h"
+#include "framework/counts.h"
+
+struct S7commplusStats
+{
+    PegCount sessions;
+    PegCount frames;
+    PegCount concurrent_sessions;
+    PegCount max_concurrent_sessions;
+};
+
+struct S7commplusSessionData
+{
+    uint8_t s7commplus_proto_id = 0;
+    uint8_t s7commplus_pdu_type = 0;
+    uint16_t s7commplus_data_len = 0;
+    uint8_t s7commplus_opcode = 0;
+    uint16_t s7commplus_reserved_1 = 0;
+    uint16_t s7commplus_function = 0;
+    uint16_t s7commplus_reserved_2 = 0;
+
+    void session_data_reset()
+    {
+        s7commplus_proto_id = s7commplus_pdu_type = s7commplus_opcode = 0;
+        s7commplus_data_len = s7commplus_function = 0;
+        s7commplus_reserved_1 = s7commplus_reserved_2 = 0;
+    }
+};
+
+class S7commplusFlowData : public snort::FlowData
+{
+public:
+    S7commplusFlowData();
+    ~S7commplusFlowData() override;
+
+    static void init();
+
+    void reset()
+    {
+        ssn_data.session_data_reset();
+    }
+
+    size_t size_of() override
+    { return sizeof(*this); }
+
+public:
+    static unsigned inspector_id;
+    S7commplusSessionData ssn_data;
+};
+
+int get_message_type(int version, const char* name);
+int get_info_type(int version, const char* name);
+
+extern THREAD_LOCAL S7commplusStats s7commplus_stats;
+
+#endif
+
diff --git a/src/service_inspectors/s7commplus/s7comm_decode.cc b/src/service_inspectors/s7commplus/s7comm_decode.cc
new file mode 100644 (file)
index 0000000..af84ea3
--- /dev/null
@@ -0,0 +1,144 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2018-2019 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.
+//--------------------------------------------------------------------------
+
+// s7comm_decode.cc author Pradeep Damodharan <prdamodh@cisco.com>
+// based on work by Jeffrey Gu <jgu@cisco.com>
+
+/*
+ * This is the encapsulation of S7comm/S7comm-plus protocol:
+ *   Ethernet | IP | TCP (server port 102) | TPKT | COTP | S7comm or S7comm-plus
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "s7comm_decode.h"
+
+#include "detection/detection_engine.h"
+#include "events/event_queue.h"
+#include "protocols/packet.h"
+
+#include "s7comm.h"
+#include "s7comm_module.h"
+
+#pragma pack(1)
+/* TPKT header */
+struct TpktHeader
+{
+    uint8_t version;
+    uint8_t reserved;
+    uint16_t length;
+};
+
+/* COTP header */
+struct CotpHeader
+{
+    uint8_t length;
+    uint8_t pdu_type;
+    uint8_t tpdu_num;
+};
+
+struct S7commplusHeader
+{
+    uint8_t proto_id;
+    uint8_t pdu_type;
+    uint16_t data_len;
+};
+
+struct S7commplusDataHeader
+{
+    uint8_t opcode;
+    uint16_t reserved_1;
+    uint16_t function;
+    uint16_t reserved_2;
+};
+#pragma pack()
+
+using namespace snort;
+
+static bool S7commPlusProtocolDecode(S7commplusSessionData* session, Packet* p)
+{
+    const S7commplusHeader* s7commplus_header;
+    const S7commplusDataHeader* s7commplus_data_header;
+    int offset;
+
+    if ( p->dsize < (sizeof(TpktHeader) + sizeof(CotpHeader) + \
+        sizeof(S7commplusHeader) + sizeof(S7commplusDataHeader)) )
+        return false;
+
+    offset = sizeof(TpktHeader) + sizeof(CotpHeader);
+
+    s7commplus_header = (const S7commplusHeader*)(p->data + offset);
+    /* Set the session data. Swap byte order for 16-bit fields. */
+    session->s7commplus_proto_id = s7commplus_header->proto_id;
+    session->s7commplus_pdu_type = s7commplus_header->pdu_type;
+    session->s7commplus_data_len = ntohs(s7commplus_header->data_len);
+
+    offset += sizeof(S7commplusHeader);
+
+    s7commplus_data_header = (const S7commplusDataHeader*)(p->data + offset);
+    /* Set the session data. Swap byte order for 16-bit fields. */
+    session->s7commplus_opcode = s7commplus_data_header->opcode;
+    session->s7commplus_reserved_1 = ntohs(s7commplus_data_header->reserved_1);
+    session->s7commplus_function = ntohs(s7commplus_data_header->function);
+    session->s7commplus_reserved_2 = ntohs(s7commplus_data_header->reserved_2);
+
+    return true;
+}
+
+bool S7commplusDecode(Packet* p)
+{
+    const TpktHeader* tpkt_header;
+    const CotpHeader* cotp_header;
+    const S7commplusHeader* s7commplus_header;
+    uint16_t tpkt_length;
+
+    if (p->dsize < TPKT_MIN_HDR_LEN)
+        return false;
+
+    S7commplusFlowData* mfd =
+        (S7commplusFlowData*)p->flow->get_flow_data(S7commplusFlowData::inspector_id);
+    tpkt_header = (const TpktHeader*)p->data;
+    cotp_header = (const CotpHeader*)(p->data + sizeof(TpktHeader));
+    tpkt_length = ntohs(tpkt_header->length);
+
+    /* It might be a TPKT/COTP packet for other purpose, e.g. connect */
+    if (cotp_header->length != COTP_HDR_LEN_FOR_S7COMMPLUS||
+        cotp_header->pdu_type != COTP_HDR_PDU_TYPE_DATA)
+        return true;
+    /* It might be COTP fragment data */
+    if (tpkt_length == TPKT_MIN_HDR_LEN)
+    {
+        mfd->reset();
+        return true;
+    }
+
+    s7commplus_header = (const S7commplusHeader*)(p->data +
+        sizeof(TpktHeader) + sizeof(CotpHeader));
+
+    if (s7commplus_header->proto_id == S7COMMPLUS_PROTOCOL_ID)
+    {
+        return (S7commPlusProtocolDecode(&mfd->ssn_data, p));
+    }
+    else
+    {
+        DetectionEngine::queue_event(GID_S7COMMPLUS, S7COMMPLUS_BAD_PROTO_ID);
+        return false;
+    }
+}
+
diff --git a/src/service_inspectors/s7commplus/s7comm_decode.h b/src/service_inspectors/s7commplus/s7comm_decode.h
new file mode 100644 (file)
index 0000000..e8f4747
--- /dev/null
@@ -0,0 +1,65 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2018-2019 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.
+//--------------------------------------------------------------------------
+//
+// s7comm_decode.h author Pradeep Damodharan <prdamodh@cisco.com>
+// based on work by Jeffrey Gu <jgu@cisco.com>
+
+#ifndef S7COMM_DECODE_H
+#define S7COMM_DECODE_H
+
+namespace snort
+{
+struct Packet;
+}
+
+/* S7comm defines */
+#define S7COMMPLUS_PDUTYPE_CONNECT                 0x01
+#define S7COMMPLUS_PDUTYPE_DATA                    0x02
+#define S7COMMPLUS_PDUTYPE_DATAFW1_5               0x03
+#define S7COMMPLUS_PDUTYPE_KEEPALIVE               0xFF
+
+#define COTP_HDR_LEN_FOR_S7COMMPLUS 2
+#define COTP_HDR_PDU_TYPE_DATA  0xF0
+
+#define S7COMM_PROTOCOL_ID      0x32
+#define S7COMMPLUS_PROTOCOL_ID 0x72
+
+#define TPKT_MIN_HDR_LEN 7     /* length field in TPKT header for S7comm */
+#define TPKT_MAX_HDR_LEN       /* Undecided */
+#define S7COMMPLUS_MIN_HDR_LEN 4
+
+/* Need 8 bytes for MBAP Header + Function Code */
+#define S7COMMPLUS_MIN_LEN 8       this value needs to be decided
+
+/* GIDs, SIDs, and Strings */
+#define GENERATOR_SPP_S7COMMPLUS 149   /* matches generators.h */
+
+#define S7COMMPLUS_BAD_LENGTH 1
+#define S7COMMPLUS_BAD_PROTO_ID 2
+#define S7COMMPLUS_RESERVED_FUNCTION 3
+
+#define S7COMMPLUS_BAD_LENGTH_STR \
+    "(spp_s7commplus): Length in S7commplus header does not match the length needed for the given S7commplus function."
+#define S7COMMPLUS_BAD_PROTO_ID_STR "(spp_s7commplus): S7commplus protocol ID is non-zero."
+#define S7COMMPLUS_RESERVED_FUNCTION_STR \
+    "(spp_s7commplus): Reserved S7commplus function code in use."
+
+bool S7commplusDecode(snort::Packet*);
+
+#endif
+
diff --git a/src/service_inspectors/s7commplus/s7comm_module.cc b/src/service_inspectors/s7commplus/s7comm_module.cc
new file mode 100644 (file)
index 0000000..9872119
--- /dev/null
@@ -0,0 +1,85 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2018-2019 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.
+//--------------------------------------------------------------------------
+
+// s7comm_module.cc author Pradeep Damodharan <prdamodh@cisco.com>
+// based on work by Jeffrey Gu <jgu@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "s7comm_module.h"
+
+#include "profiler/profiler.h"
+
+#include "s7comm.h"
+
+using namespace snort;
+
+THREAD_LOCAL ProfileStats s7commplus_prof;
+
+//-------------------------------------------------------------------------
+// stats
+//-------------------------------------------------------------------------
+
+const PegInfo peg_names[] =
+{
+    { CountType::SUM, "sessions", "total sessions processed" },
+    { CountType::SUM, "frames", "total S7commplus messages" },
+    { CountType::NOW, "concurrent_sessions", "total concurrent s7commplus sessions" },
+    { CountType::MAX, "max_concurrent_sessions", "maximum concurrent s7commplus sessions" },
+
+    { CountType::END, nullptr, nullptr }
+};
+
+const PegInfo* S7commplusModule::get_pegs() const
+{ return peg_names; }
+
+PegCount* S7commplusModule::get_counts() const
+{ return (PegCount*)&s7commplus_stats; }
+
+//-------------------------------------------------------------------------
+// rules
+//-------------------------------------------------------------------------
+
+#define S7COMMPLUS_BAD_LENGTH_STR \
+    "length in S7commplus MBAP header does not match the length needed for the given S7commplus function"
+
+#define S7COMMPLUS_BAD_PROTO_ID_STR      "S7commplus protocol ID is non-zero"
+#define S7COMMPLUS_RESERVED_FUNCTION_STR "reserved S7commplus function code in use"
+
+static const RuleMap S7commplus_rules[] =
+{
+    { S7COMMPLUS_BAD_LENGTH, S7COMMPLUS_BAD_LENGTH_STR },
+    { S7COMMPLUS_BAD_PROTO_ID, S7COMMPLUS_BAD_PROTO_ID_STR },
+    { S7COMMPLUS_RESERVED_FUNCTION, S7COMMPLUS_RESERVED_FUNCTION_STR },
+
+    { 0, nullptr }
+};
+
+const RuleMap* S7commplusModule::get_rules() const
+{ return S7commplus_rules; }
+
+//-------------------------------------------------------------------------
+// params
+//-------------------------------------------------------------------------
+
+S7commplusModule::S7commplusModule() :
+    Module(S7COMMPLUS_NAME, S7COMMPLUS_HELP)
+{ }
+
diff --git a/src/service_inspectors/s7commplus/s7comm_module.h b/src/service_inspectors/s7commplus/s7comm_module.h
new file mode 100644 (file)
index 0000000..0d1a30f
--- /dev/null
@@ -0,0 +1,59 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2018-2019 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.
+//--------------------------------------------------------------------------
+
+// s7comm_module.h author Pradeep Damodharan <prdamodh@cisco.com>
+// based on work by Jeffrey Gu <jgu@cisco.com>
+
+#ifndef S7COMM_MODULE_H
+#define S7COMM_MODULE_H
+
+#include "framework/module.h"
+
+#define GID_S7COMMPLUS 149
+
+#define S7COMMPLUS_BAD_LENGTH        1
+#define S7COMMPLUS_BAD_PROTO_ID      2
+#define S7COMMPLUS_RESERVED_FUNCTION 3
+
+#define S7COMMPLUS_NAME "s7commplus"
+#define S7COMMPLUS_HELP "s7commplus inspection"
+
+extern THREAD_LOCAL snort::ProfileStats s7commplus_prof;
+
+class S7commplusModule : public snort::Module
+{
+public:
+    S7commplusModule();
+
+    unsigned get_gid() const override
+    { return GID_S7COMMPLUS; }
+
+    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 &s7commplus_prof; }
+
+    Usage get_usage() const override
+    { return INSPECT; }
+};
+
+#endif
+
diff --git a/src/service_inspectors/s7commplus/s7comm_paf.cc b/src/service_inspectors/s7commplus/s7comm_paf.cc
new file mode 100644 (file)
index 0000000..bb75f17
--- /dev/null
@@ -0,0 +1,98 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2018-2019 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.
+//--------------------------------------------------------------------------
+
+// s7comm_paf.cc author Pradeep Damodharan <prdamodh@cisco.com>
+// based on work by Jeffrey Gu <jgu@cisco.com>
+// Protocol-Aware Flushing (PAF) code for the S7commplus preprocessor.
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "s7comm_paf.h"
+
+#include "detection/detection_engine.h"
+#include "events/event_queue.h"
+
+#include "s7comm.h"
+#include "s7comm_decode.h"
+#include "s7comm_module.h"
+
+using namespace snort;
+
+#define S7COMMPLUS_MIN_HDR_LEN 4        // Enough for Unit ID + Function
+
+S7commplusSplitter::S7commplusSplitter(bool b) : StreamSplitter(b)
+{
+    state = S7COMMPLUS_PAF_STATE__TPKT_VER;
+    tpkt_length = 0;
+}
+
+// S7comm/TCP PAF:
+// Statefully inspects S7comm traffic from the start of a session,
+// Reads up until the length octet is found, then sets a flush point.
+
+StreamSplitter::Status S7commplusSplitter::scan(
+    Packet*, const uint8_t* data, uint32_t len, uint32_t /*flags*/, uint32_t* fp)
+{
+    uint32_t bytes_processed = 0;
+
+    /* Process this packet 1 byte at a time */
+    while (bytes_processed < len)
+    {
+        switch (state)
+        {
+        /* Skip the Transaction & Protocol IDs */
+        case S7COMMPLUS_PAF_STATE__TPKT_VER:
+        case S7COMMPLUS_PAF_STATE__TPKT_RESERVED:
+        case S7COMMPLUS_PAF_STATE__COTP_LEN:
+        case S7COMMPLUS_PAF_STATE__COTP_PDU_TYPE:
+            state = (s7commplus_paf_state_t)(((int)state) + 1);     //Set the state to next PAF
+                                                                    // state
+            break;
+
+        /* Read length 1 byte at a time, in case a TCP segment is sent
+         * with xxx bytes from the S7CPAP header */
+        case S7COMMPLUS_PAF_STATE__TPKT_LEN_1:
+            tpkt_length |= ( *(data + bytes_processed) << 8 );
+            state = S7COMMPLUS_PAF_STATE__TPKT_LEN_2;
+            break;
+
+        case S7COMMPLUS_PAF_STATE__TPKT_LEN_2:
+            tpkt_length |= *(data + bytes_processed);
+            state = S7COMMPLUS_PAF_STATE__COTP_LEN;
+            break;
+
+        case S7COMMPLUS_PAF_STATE__SET_FLUSH:
+            if ((tpkt_length < TPKT_MIN_HDR_LEN))
+            {
+                DetectionEngine::queue_event(GID_S7COMMPLUS, S7COMMPLUS_BAD_LENGTH);
+            }
+
+            *fp = tpkt_length;      // flush point at the end of payload
+            state = S7COMMPLUS_PAF_STATE__TPKT_VER;
+            tpkt_length = 0;
+            return StreamSplitter::FLUSH;
+        }
+
+        bytes_processed++;
+    }
+
+    return StreamSplitter::SEARCH;
+}
+
diff --git a/src/service_inspectors/s7commplus/s7comm_paf.h b/src/service_inspectors/s7commplus/s7comm_paf.h
new file mode 100644 (file)
index 0000000..b82f1c1
--- /dev/null
@@ -0,0 +1,56 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2018-2019 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.
+//--------------------------------------------------------------------------
+//
+// s7comm_paf.h author Pradeep Damodharan <prdamodh@cisco.com>
+// based on work by Jeffrey Gu <jgu@cisco.com>
+
+#ifndef S7COMM_PAF__H
+#define S7COMM_PAF__H
+
+// Protocol-Aware Flushing (PAF) code for the S7commplus preprocessor.
+
+#include "stream/stream_splitter.h"
+
+enum s7commplus_paf_state_t
+{
+    S7COMMPLUS_PAF_STATE__TPKT_VER = 0,
+    S7COMMPLUS_PAF_STATE__TPKT_RESERVED,
+    S7COMMPLUS_PAF_STATE__TPKT_LEN_1,
+    S7COMMPLUS_PAF_STATE__TPKT_LEN_2,
+    S7COMMPLUS_PAF_STATE__COTP_LEN,
+    S7COMMPLUS_PAF_STATE__COTP_PDU_TYPE,
+    S7COMMPLUS_PAF_STATE__SET_FLUSH
+};
+
+class S7commplusSplitter : public snort::StreamSplitter
+{
+public:
+    S7commplusSplitter(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; }
+
+private:
+    s7commplus_paf_state_t state;
+    uint16_t tpkt_length;
+};
+
+#endif
+
index 752e2467a6293673147fb633b5c1e8431612d37f..eba4435235c9e03607f1a8925268cf823ae5ba0f 100644 (file)
@@ -52,6 +52,7 @@ extern const BaseApi* sin_dce[];
 extern const BaseApi* sin_dnp3[];
 extern const BaseApi* sin_gtp[];
 extern const BaseApi* sin_modbus[];
+extern const BaseApi* sin_s7commplus[];
 #endif
 
 const BaseApi* service_inspectors[] =
@@ -89,6 +90,7 @@ void load_service_inspectors()
     PluginManager::load_plugins(sin_dnp3);
     PluginManager::load_plugins(sin_gtp);
     PluginManager::load_plugins(sin_modbus);
+    PluginManager::load_plugins(sin_s7commplus);
 #endif
 }