add_subdirectory(ssh)
add_subdirectory(ssl)
add_subdirectory(wizard)
+add_subdirectory(s7commplus)
if (STATIC_INSPECTORS)
set (STATIC_INSPECTOR_OBJS
$<TARGET_OBJECTS:smtp>
$<TARGET_OBJECTS:ssh>
$<TARGET_OBJECTS:wizard>
+ $<TARGET_OBJECTS:s7commplus>
)
endif()
--- /dev/null
+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)
+
--- /dev/null
+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.
--- /dev/null
+//--------------------------------------------------------------------------
+// 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;
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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;
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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;
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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
+};
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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;
+ }
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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)
+{ }
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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
+
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[] =
PluginManager::load_plugins(sin_dnp3);
PluginManager::load_plugins(sin_gtp);
PluginManager::load_plugins(sin_modbus);
+ PluginManager::load_plugins(sin_s7commplus);
#endif
}