From: Shravan Rangarajuvenkata (shrarang) Date: Fri, 1 Nov 2019 01:47:54 +0000 (-0400) Subject: Merge pull request #1751 in SNORT/snort3 from ~PRDAMODH/snort3:S7COMMPLUS-dev to... X-Git-Tag: 3.0.0-264~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=29b42a96592be32f2e989c631d1d5c76664d5485;p=thirdparty%2Fsnort3.git Merge pull request #1751 in SNORT/snort3 from ~PRDAMODH/snort3:S7COMMPLUS-dev to master Squashed commit of the following: commit c5548d43e80b6dd1534e2e7a218c6bc5e2ff1200 Author: Pradeep Damodharan Date: Wed Sep 18 15:54:12 2019 -0400 s7commplus: Initial working version of s7commplus service inspector --- diff --git a/src/service_inspectors/CMakeLists.txt b/src/service_inspectors/CMakeLists.txt index 02c6cae2c..962f60433 100644 --- a/src/service_inspectors/CMakeLists.txt +++ b/src/service_inspectors/CMakeLists.txt @@ -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) $ $ $ + $ ) endif() diff --git a/src/service_inspectors/s7commplus/CMakeLists.txt b/src/service_inspectors/s7commplus/CMakeLists.txt new file mode 100644 index 000000000..6280158d1 --- /dev/null +++ b/src/service_inspectors/s7commplus/CMakeLists.txt @@ -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 index 000000000..8087ba3ef --- /dev/null +++ b/src/service_inspectors/s7commplus/dev_notes.txt @@ -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 index 000000000..550cdca67 --- /dev/null +++ b/src/service_inspectors/s7commplus/ips_s7comm_content.cc @@ -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 +// based on work by Jeffrey Gu + +#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 index 000000000..7cb3d017b --- /dev/null +++ b/src/service_inspectors/s7commplus/ips_s7comm_func.cc @@ -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 +// based on work by Jeffrey Gu + +#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(n); + + else if ( get_func(v.get_string(), n) ) + func = static_cast(n); + + else + return false; + + return true; +} + +//------------------------------------------------------------------------- +// api +//------------------------------------------------------------------------- + +static Module* mod_ctor() +{ + return new 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 index 000000000..26cd8aa44 --- /dev/null +++ b/src/service_inspectors/s7commplus/ips_s7comm_opcode.cc @@ -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 +// based on work by Jeffrey Gu + +#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 index 000000000..38e152e50 --- /dev/null +++ b/src/service_inspectors/s7commplus/s7comm.cc @@ -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 +// based on work by Jeffrey Gu + +#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 index 000000000..1ff4414e8 --- /dev/null +++ b/src/service_inspectors/s7commplus/s7comm.h @@ -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 +// based on work by Jeffrey Gu + +#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 index 000000000..af84ea3bd --- /dev/null +++ b/src/service_inspectors/s7commplus/s7comm_decode.cc @@ -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 +// based on work by Jeffrey Gu + +/* + * 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 index 000000000..e8f4747ac --- /dev/null +++ b/src/service_inspectors/s7commplus/s7comm_decode.h @@ -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 +// based on work by Jeffrey Gu + +#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 index 000000000..987211969 --- /dev/null +++ b/src/service_inspectors/s7commplus/s7comm_module.cc @@ -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 +// based on work by Jeffrey Gu + +#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 index 000000000..0d1a30f2b --- /dev/null +++ b/src/service_inspectors/s7commplus/s7comm_module.h @@ -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 +// based on work by Jeffrey Gu + +#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 index 000000000..bb75f17ee --- /dev/null +++ b/src/service_inspectors/s7commplus/s7comm_paf.cc @@ -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 +// based on work by Jeffrey Gu +// 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 index 000000000..b82f1c1fe --- /dev/null +++ b/src/service_inspectors/s7commplus/s7comm_paf.h @@ -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 +// based on work by Jeffrey Gu + +#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 + diff --git a/src/service_inspectors/service_inspectors.cc b/src/service_inspectors/service_inspectors.cc index 752e2467a..eba443523 100644 --- a/src/service_inspectors/service_inspectors.cc +++ b/src/service_inspectors/service_inspectors.cc @@ -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 }