From: Russ Combs (rucombs) Date: Thu, 22 Oct 2015 00:04:32 +0000 (-0400) Subject: Merge pull request #92 in SNORT/snort3 from crc/modbus2 to master X-Git-Tag: 3.0.0-233~773 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=52a9ccfa44b4dc1a180bc1decb18ae86b2444c20;p=thirdparty%2Fsnort3.git Merge pull request #92 in SNORT/snort3 from crc/modbus2 to master Squashed commit of the following: commit d39dd81b300723d990c42b301e73aaa0e8db8386 Author: Russ Combs Date: Wed Oct 21 15:08:25 2015 -0400 review updates commit 97306ddbf13f8dccf692613b0f6835e68841e8d0 Author: Russ Combs Date: Tue Oct 20 23:22:06 2015 -0400 initial modbus port --- diff --git a/configure.ac b/configure.ac index 97d3e40dd..e20712630 100644 --- a/configure.ac +++ b/configure.ac @@ -1004,6 +1004,7 @@ src/service_inspectors/ftp_telnet/Makefile \ src/service_inspectors/gtp/Makefile \ src/service_inspectors/http_inspect/Makefile \ src/service_inspectors/imap/Makefile \ +src/service_inspectors/modbus/Makefile \ src/service_inspectors/nhttp_inspect/Makefile \ src/service_inspectors/pop/Makefile \ src/service_inspectors/rpc_decode/Makefile \ diff --git a/doc/style.txt b/doc/style.txt index ebc8f4898..156140ce9 100644 --- a/doc/style.txt +++ b/doc/style.txt @@ -244,7 +244,7 @@ The working config is crusty.cfg in the top level directory. It does well but will munge some things. Specially formatted INDENT-OFF comments were added in 2 places to avoid a real mess. -Example usage: +You can use uncrustify something like this: - uncrustify -c crusty.cfg --replace + uncrustify -c crusty.cfg --replace file.cc diff --git a/src/Makefile.am b/src/Makefile.am index 021998053..ef62c41c3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -13,6 +13,7 @@ service_inspectors/dns/libdns.a \ service_inspectors/ftp_telnet/libftp_telnet.a \ service_inspectors/gtp/libgtp_inspect.a \ service_inspectors/imap/libimap.a \ +service_inspectors/modbus/libmodbus.a \ service_inspectors/nhttp_inspect/libnhttp_inspect.a \ service_inspectors/pop/libpop.a \ service_inspectors/rpc_decode/librpc_decode.a \ diff --git a/src/service_inspectors/CMakeLists.txt b/src/service_inspectors/CMakeLists.txt index ba11297b7..8111545e3 100644 --- a/src/service_inspectors/CMakeLists.txt +++ b/src/service_inspectors/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(ftp_telnet) add_subdirectory(gtp) add_subdirectory(http_inspect) add_subdirectory(imap) +add_subdirectory(modbus) add_subdirectory(nhttp_inspect) add_subdirectory(pop) add_subdirectory(rpc_decode) @@ -21,6 +22,7 @@ if (STATIC_INSPECTORS) ftp_telnet gtp imap + modbus nhttp_inspect pop rpc_decode diff --git a/src/service_inspectors/Makefile.am b/src/service_inspectors/Makefile.am index dea53a1aa..b361a5ef9 100644 --- a/src/service_inspectors/Makefile.am +++ b/src/service_inspectors/Makefile.am @@ -21,6 +21,7 @@ ftp_telnet \ gtp \ http_inspect \ imap \ +modbus \ nhttp_inspect \ pop \ rpc_decode \ diff --git a/src/service_inspectors/modbus/CMakeLists.txt b/src/service_inspectors/modbus/CMakeLists.txt new file mode 100644 index 000000000..9cbd7dc5b --- /dev/null +++ b/src/service_inspectors/modbus/CMakeLists.txt @@ -0,0 +1,22 @@ +set( FILE_LIST + modbus.cc + modbus.h + modbus_decode.cc + modbus_decode.h + modbus_module.cc + modbus_module.h + modbus_paf.cc + modbus_paf.h + ips_modbus_data.cc + ips_modbus_func.cc + ips_modbus_unit.cc +) + +if (STATIC_INSPECTORS) + add_library(modbus STATIC ${FILE_LIST}) + +else (STATIC_INSPECTORS) + add_shared_library(modbus inspectors ${FILE_LIST}) + +endif (STATIC_INSPECTORS) + diff --git a/src/service_inspectors/modbus/Makefile.am b/src/service_inspectors/modbus/Makefile.am new file mode 100644 index 000000000..ceaea07b5 --- /dev/null +++ b/src/service_inspectors/modbus/Makefile.am @@ -0,0 +1,29 @@ + +file_list = \ +ips_modbus_data.cc \ +ips_modbus_func.cc \ +ips_modbus_unit.cc \ +modbus.cc \ +modbus.h \ +modbus_decode.cc \ +modbus_decode.h \ +modbus_module.cc \ +modbus_module.h \ +modbus_paf.cc \ +modbus_paf.h + +if STATIC_INSPECTORS +noinst_LIBRARIES = libmodbus.a +libmodbus_a_SOURCES = $(file_list) +else +shlibdir = $(pkglibdir)/inspectors +shlib_LTLIBRARIES = libmodbus.la +libmodbus_la_CXXFLAGS = $(AM_CXXFLAGS) -DBUILDING_SO +libmodbus_la_LDFLAGS = -export-dynamic -shared +libmodbus_la_SOURCES = $(file_list) +endif + +AM_CXXFLAGS = @AM_CXXFLAGS@ + +EXTRA_DIST = dev_notes.txt + diff --git a/src/service_inspectors/modbus/dev_notes.txt b/src/service_inspectors/modbus/dev_notes.txt new file mode 100644 index 000000000..c6ee14d4c --- /dev/null +++ b/src/service_inspectors/modbus/dev_notes.txt @@ -0,0 +1,12 @@ +Modbus is a protocol used in SCADA networks which is typically on TCP port +502 (aka Modbus TCP). + +The Modbus inspector decodes the Modbus protocol and provides rule options +to access certain protocol fields. This allows a user to write rules for +Modbus packets without decoding the protocol with a series of ”content” and +”byte test” options. + +The preprocessor only evaluates PAF-flushed PDUs. If the rule options don't +check for this, they'll fire on stale session data when the original packet +goes through before flushing. + diff --git a/src/service_inspectors/modbus/ips_modbus_data.cc b/src/service_inspectors/modbus/ips_modbus_data.cc new file mode 100644 index 000000000..64c967d90 --- /dev/null +++ b/src/service_inspectors/modbus/ips_modbus_data.cc @@ -0,0 +1,151 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2015 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2011-2013 Sourcefire, Inc. +// +// 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_modbus_data.cc author Russ Combs + +#include "main/snort_types.h" +#include "main/snort_debug.h" +#include "detection/detection_defines.h" +#include "framework/cursor.h" +#include "framework/ips_option.h" +#include "framework/module.h" +#include "hash/sfhashfcn.h" +#include "time/profiler.h" + +#include "modbus.h" +#include "modbus_decode.h" + +static const char* s_name = "modbus_data"; + +//------------------------------------------------------------------------- +// version option +//------------------------------------------------------------------------- + +static THREAD_LOCAL ProfileStats modbus_data_prof; + +class ModbusDataOption : public IpsOption +{ +public: + ModbusDataOption() : IpsOption(s_name) { } + + uint32_t hash() const override; + bool operator==(const IpsOption&) const override; + + int eval(Cursor&, Packet*) override; +}; + +uint32_t ModbusDataOption::hash() const +{ + uint32_t a = 0, b = 0, c = 0; + + mix_str(a, b, c, get_name()); + finalize(a,b,c); + + return c; +} + +bool ModbusDataOption::operator==(const IpsOption& ips) const +{ + return !strcmp(get_name(), ips.get_name()); +} + +int ModbusDataOption::eval(Cursor& c, Packet* p) +{ + PERF_PROFILE(modbus_data_prof); + + if ( !p->flow ) + return DETECTION_OPTION_NO_MATCH; + + if ( !p->is_full_pdu() ) + return DETECTION_OPTION_NO_MATCH; + + if ( p->dsize < MODBUS_MIN_LEN ) + return DETECTION_OPTION_NO_MATCH; + + c.set(s_name, p->data + MODBUS_MIN_LEN, p->dsize - MODBUS_MIN_LEN); + return DETECTION_OPTION_MATCH; +} + +//------------------------------------------------------------------------- +// module +//------------------------------------------------------------------------- + +#define s_help \ + "rule option to set cursor to modbus data" + +class ModbusDataModule : public Module +{ +public: + ModbusDataModule() : Module(s_name, s_help) { } + + ProfileStats* get_profile() const override + { return &modbus_data_prof; } +}; + +//------------------------------------------------------------------------- +// api +//------------------------------------------------------------------------- + +static Module* mod_ctor() +{ + return new ModbusDataModule; +} + +static void mod_dtor(Module* m) +{ + delete m; +} + +static IpsOption* opt_ctor(Module*, OptTreeNode*) +{ + return new ModbusDataOption; +} + +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_modbus_data = &ips_api.base; + diff --git a/src/service_inspectors/modbus/ips_modbus_func.cc b/src/service_inspectors/modbus/ips_modbus_func.cc new file mode 100644 index 000000000..5a48a9ffb --- /dev/null +++ b/src/service_inspectors/modbus/ips_modbus_func.cc @@ -0,0 +1,240 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2015 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2011-2013 Sourcefire, Inc. +// +// 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_modbus_func.cc author Russ Combs + +#include "main/snort_types.h" +#include "main/snort_debug.h" +#include "detection/detection_defines.h" +#include "framework/ips_option.h" +#include "framework/module.h" +#include "hash/sfhashfcn.h" +#include "time/profiler.h" + +#include "modbus.h" + +static const char* s_name = "modbus_func"; + +//------------------------------------------------------------------------- +// func lookup +//------------------------------------------------------------------------- + +struct modbus_func_map_t +{ + const char* name; + uint8_t func; +}; + +static modbus_func_map_t func_map[] = +{ + { "read_coils", 1 }, + { "read_discrete_inputs", 2 }, + { "read_holding_registers", 3 }, + { "read_input_registers", 4 }, + { "write_single_coil", 5 }, + { "write_single_register", 6 }, + { "read_exception_status", 7 }, + { "diagnostics", 8 }, + { "get_comm_event_counter", 11 }, + { "get_comm_event_log", 12 }, + { "write_multiple_coils", 15 }, + { "write_multiple_registers", 16 }, + { "report_slave_id", 17 }, + { "read_file_record", 20 }, + { "write_file_record", 21 }, + { "mask_write_register", 22 }, + { "read_write_multiple_registers", 23 }, + { "read_fifo_queue", 24 }, + { "encapsulated_interface_transport", 43 } +}; + +static bool get_func(const char* s, long& n) +{ + constexpr size_t max = (sizeof(func_map) / sizeof(modbus_func_map_t)); + + for ( size_t i = 0; i < max; ++i ) + { + if ( !strcmp(s, func_map[i].name) ) + { + n = func_map[i].func; + return true; + } + } + return false; +} + +//------------------------------------------------------------------------- +// func option +//------------------------------------------------------------------------- + +static THREAD_LOCAL ProfileStats modbus_func_prof; + +class ModbusFuncOption : public IpsOption +{ +public: + ModbusFuncOption(uint8_t v) : IpsOption(s_name) + { func = v; } + + uint32_t hash() const override; + bool operator==(const IpsOption&) const override; + + int eval(Cursor&, Packet*) override; + +public: + uint8_t func; +}; + +uint32_t ModbusFuncOption::hash() const +{ + uint32_t a = func, b = 0, c = 0; + + mix_str(a, b, c, get_name()); + finalize(a,b,c); + + return c; +} + +bool ModbusFuncOption::operator==(const IpsOption& ips) const +{ + if ( strcmp(get_name(), ips.get_name()) ) + return false; + + ModbusFuncOption& rhs = (ModbusFuncOption&)ips; + return ( func == rhs.func ); +} + +int ModbusFuncOption::eval(Cursor&, Packet* p) +{ + PERF_PROFILE(modbus_func_prof); + + if ( !p->flow ) + return DETECTION_OPTION_NO_MATCH; + + if ( !p->is_full_pdu() ) + return DETECTION_OPTION_NO_MATCH; + + ModbusFlowData* mfd = + (ModbusFlowData*)p->flow->get_application_data(ModbusFlowData::flow_id); + + if ( mfd and func == mfd->ssn_data.func ) + return DETECTION_OPTION_MATCH; + + return DETECTION_OPTION_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 modbus function code" + +class ModbusFuncModule : public Module +{ +public: + ModbusFuncModule() : Module(s_name, s_help, s_params) { } + + bool set(const char*, Value&, SnortConfig*) override; + + ProfileStats* get_profile() const override + { return &modbus_func_prof; } + +public: + uint8_t func; +}; + +bool ModbusFuncModule::set(const char*, Value& v, SnortConfig*) +{ + if ( !v.is("~") ) + return false; + + long n; + + if ( v.strtol(n) ) + func = (uint8_t)n; + + else if ( get_func(v.get_string(), n) ) + func = (uint8_t)n; + + else + return false; + + return true; +} + +//------------------------------------------------------------------------- +// api +//------------------------------------------------------------------------- + +static Module* mod_ctor() +{ + return new ModbusFuncModule; +} + +static void mod_dtor(Module* m) +{ + delete m; +} + +static IpsOption* opt_ctor(Module* m, OptTreeNode*) +{ + ModbusFuncModule* mod = (ModbusFuncModule*)m; + return new ModbusFuncOption(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_modbus_func = &ips_api.base; + diff --git a/src/service_inspectors/modbus/ips_modbus_unit.cc b/src/service_inspectors/modbus/ips_modbus_unit.cc new file mode 100644 index 000000000..705aa8e5f --- /dev/null +++ b/src/service_inspectors/modbus/ips_modbus_unit.cc @@ -0,0 +1,182 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2015 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2011-2013 Sourcefire, Inc. +// +// 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_modbus_unit.cc author Russ Combs + +#include "main/snort_types.h" +#include "main/snort_debug.h" +#include "detection/detection_defines.h" +#include "framework/ips_option.h" +#include "framework/module.h" +#include "hash/sfhashfcn.h" +#include "time/profiler.h" + +#include "modbus.h" + +static const char* s_name = "modbus_unit"; + +//------------------------------------------------------------------------- +// version option +//------------------------------------------------------------------------- + +static THREAD_LOCAL ProfileStats modbus_unit_prof; + +class ModbusUnitOption : public IpsOption +{ +public: + ModbusUnitOption(uint8_t u) : IpsOption(s_name) + { unit = u; } + + uint32_t hash() const override; + bool operator==(const IpsOption&) const override; + + int eval(Cursor&, Packet*) override; + +public: + uint8_t unit; +}; + +uint32_t ModbusUnitOption::hash() const +{ + uint32_t a = unit, b = 0, c = 0; + + mix_str(a, b, c, get_name()); + finalize(a,b,c); + + return c; +} + +bool ModbusUnitOption::operator==(const IpsOption& ips) const +{ + if ( strcmp(get_name(), ips.get_name()) ) + return false; + + ModbusUnitOption& rhs = (ModbusUnitOption&)ips; + return ( unit == rhs.unit ); +} + +int ModbusUnitOption::eval(Cursor&, Packet* p) +{ + PERF_PROFILE(modbus_unit_prof); + + if ( !p->flow ) + return DETECTION_OPTION_NO_MATCH; + + if ( !p->is_full_pdu() ) + return DETECTION_OPTION_NO_MATCH; + + ModbusFlowData* mfd = + (ModbusFlowData*)p->flow->get_application_data(ModbusFlowData::flow_id); + + if ( mfd and unit == mfd->ssn_data.unit ) + return DETECTION_OPTION_MATCH; + + return DETECTION_OPTION_NO_MATCH; +} + +//------------------------------------------------------------------------- +// module +//------------------------------------------------------------------------- + +static const Parameter s_params[] = +{ + { "~", Parameter::PT_INT, "0:255", nullptr, + "modbus unit ID" }, + + { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr } +}; + +#define s_help \ + "rule option to check modbus unit ID" + +class ModbusUnitModule : public Module +{ +public: + ModbusUnitModule() : Module(s_name, s_help, s_params) { } + + bool set(const char*, Value&, SnortConfig*) override; + + ProfileStats* get_profile() const override + { return &modbus_unit_prof; } + +public: + uint8_t unit; +}; + +bool ModbusUnitModule::set(const char*, Value& v, SnortConfig*) +{ + if ( !v.is("~") ) + return false; + + unit = v.get_long(); + return true; +} + +//------------------------------------------------------------------------- +// api +//------------------------------------------------------------------------- + +static Module* mod_ctor() +{ + return new ModbusUnitModule; +} + +static void mod_dtor(Module* m) +{ + delete m; +} + +static IpsOption* opt_ctor(Module* m, OptTreeNode*) +{ + ModbusUnitModule* mod = (ModbusUnitModule*)m; + return new ModbusUnitOption(mod->unit); +} + +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_modbus_unit = &ips_api.base; + diff --git a/src/service_inspectors/modbus/modbus.cc b/src/service_inspectors/modbus/modbus.cc new file mode 100644 index 000000000..d25ee714e --- /dev/null +++ b/src/service_inspectors/modbus/modbus.cc @@ -0,0 +1,178 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2015-2015 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. +//-------------------------------------------------------------------------- + +// modbus.cc author Russ Combs + +#include "modbus.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "managers/inspector_manager.h" +#include "time/profiler.h" + +#include "modbus_decode.h" +#include "modbus_module.h" +#include "modbus_paf.h" + +THREAD_LOCAL ModbusStats modbus_stats; + +//------------------------------------------------------------------------- +// flow stuff +//------------------------------------------------------------------------- + +unsigned ModbusFlowData::flow_id = 0; + +void ModbusFlowData::init() +{ + flow_id = FlowData::get_flow_id(); +} + +ModbusFlowData::ModbusFlowData() : FlowData(flow_id) +{ + reset(); +} + +//------------------------------------------------------------------------- +// class stuff +//------------------------------------------------------------------------- + +class Modbus : 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 ModbusSplitter(c2s); } +}; + +void Modbus::eval(Packet* p) +{ + PERF_PROFILE(modbus_prof); + + // preconditions - what we registered for + assert(p->has_tcp_data()); + + ModbusFlowData* mfd = + (ModbusFlowData*)p->flow->get_application_data(ModbusFlowData::flow_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) ) + SnortEventqAdd(GID_MODBUS, MODBUS_BAD_LENGTH); + + return; + } + + if ( !mfd ) + { + mfd = new ModbusFlowData; + p->flow->set_application_data(mfd); + } + + // When pipelined Modbus 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 ( !ModbusDecode(p) ) + mfd->reset(); +} + +//------------------------------------------------------------------------- +// plugin stuff +//------------------------------------------------------------------------- + +static Module* mod_ctor() +{ return new ModbusModule; } + +static void mod_dtor(Module* m) +{ delete m; } + +static void modbus_init() +{ + ModbusFlowData::init(); +} + +static Inspector* modbus_ctor(Module*) +{ + return new Modbus; +} + +static void modbus_dtor(Inspector* p) +{ + delete p; +} + +//------------------------------------------------------------------------- + +static const InspectApi modbus_api = +{ + { + PT_INSPECTOR, + sizeof(InspectApi), + INSAPI_VERSION, + 0, + API_RESERVED, + API_OPTIONS, + MODBUS_NAME, + MODBUS_HELP, + mod_ctor, + mod_dtor + }, + IT_SERVICE, + (uint16_t)PktType::PDU, + nullptr, + "modbus", + modbus_init, + nullptr, + nullptr, // tinit + nullptr, // tterm + modbus_ctor, + modbus_dtor, + nullptr, // ssn + nullptr // reset +}; + +#ifdef BUILDING_SO +extern const BaseApi* ips_modbus_data; +extern const BaseApi* ips_modbus_func; +extern const BaseApi* ips_modbus_func; + +SO_PUBLIC const BaseApi* snort_plugins[] = +{ + &modbus_api.base, + ips_modbus_data, + ips_modbus_func, + ips_modbus_unit, + nullptr +}; +#else +const BaseApi* sin_modbus = &modbus_api.base; +#endif + diff --git a/src/service_inspectors/modbus/modbus.h b/src/service_inspectors/modbus/modbus.h new file mode 100644 index 000000000..673d5e998 --- /dev/null +++ b/src/service_inspectors/modbus/modbus.h @@ -0,0 +1,65 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2015 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2011-2013 Sourcefire, Inc. +// +// 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. +//-------------------------------------------------------------------------- + +// modubs.h author Russ Combs + +#ifndef MODBUS_H +#define MODBUS_H + +#include "flow/flow.h" +#include "framework/counts.h" +#include "main/thread.h" + +struct ModbusStats +{ + PegCount sessions; +}; + +struct modbus_session_data_t +{ + uint16_t flags; + uint8_t func; + uint8_t unit; +}; + +class ModbusFlowData : public FlowData +{ +public: + ModbusFlowData(); + + static void init(); + + void reset() + { + ssn_data.func = ssn_data.unit = 0; + ssn_data.flags = 0; + } + +public: + static unsigned flow_id; + modbus_session_data_t ssn_data; +}; + +int get_message_type(int version, const char* name); +int get_info_type(int version, const char* name); + +extern THREAD_LOCAL ModbusStats modbus_stats; + +#endif + diff --git a/src/service_inspectors/modbus/modbus_decode.cc b/src/service_inspectors/modbus/modbus_decode.cc new file mode 100644 index 000000000..16bc1199a --- /dev/null +++ b/src/service_inspectors/modbus/modbus_decode.cc @@ -0,0 +1,439 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2015 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2011-2013 Sourcefire, Inc. +// +// 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. +//-------------------------------------------------------------------------- + +// modbus_decode.cc author Ryan Jordan + +#include "modbus_decode.h" + +#include "modbus.h" +#include "modbus_module.h" +#include "protocols/packet.h" +#include "stream/stream_api.h" +#include "events/event_queue.h" + +// FIXIT-L convert this stuff to a table and make configurable + +/* Modbus Function Codes */ +#define MODBUS_FUNC_READ_COILS 0x01 +#define MODBUS_FUNC_READ_DISCRETE_INPUTS 0x02 +#define MODBUS_FUNC_READ_HOLDING_REGISTERS 0x03 +#define MODBUS_FUNC_READ_INPUT_REGISTERS 0x04 +#define MODBUS_FUNC_WRITE_SINGLE_COIL 0x05 +#define MODBUS_FUNC_WRITE_SINGLE_REGISTER 0x06 +#define MODBUS_FUNC_READ_EXCEPTION_STATUS 0x07 +#define MODBUS_FUNC_DIAGNOSTICS 0x08 +#define MODBUS_FUNC_GET_COMM_EVENT_COUNTER 0x0B +#define MODBUS_FUNC_GET_COMM_EVENT_LOG 0x0C +#define MODBUS_FUNC_WRITE_MULTIPLE_COILS 0x0F +#define MODBUS_FUNC_WRITE_MULTIPLE_REGISTERS 0x10 +#define MODBUS_FUNC_REPORT_SLAVE_ID 0x11 +#define MODBUS_FUNC_READ_FILE_RECORD 0x14 +#define MODBUS_FUNC_WRITE_FILE_RECORD 0x15 +#define MODBUS_FUNC_MASK_WRITE_REGISTER 0x16 +#define MODBUS_FUNC_READ_WRITE_MULTIPLE_REGISTERS 0x17 +#define MODBUS_FUNC_READ_FIFO_QUEUE 0x18 +#define MODBUS_FUNC_ENCAPSULATED_INTERFACE_TRANSPORT 0x2B +#define MODBUS_SUB_FUNC_CANOPEN 0x0D +#define MODBUS_SUB_FUNC_READ_DEVICE_ID 0x0E + +/* Various Modbus lengths */ +#define MODBUS_BYTE_COUNT_SIZE 1 +#define MODBUS_DOUBLE_BYTE_COUNT_SIZE 2 +#define MODBUS_FILE_RECORD_SUB_REQUEST_SIZE 7 +#define MODBUS_FILE_RECORD_SUB_REQUEST_LEN_OFFSET 5 +#define MODBUS_READ_DEVICE_ID_HEADER_LEN 6 +#define MODBUS_READ_DEVICE_ID_NUM_OBJ_OFFSET 5 + +#define MODBUS_EMPTY_DATA_LEN 0 +#define MODBUS_FOUR_DATA_BYTES 4 +#define MODBUS_BYTE_COUNT_SIZE 1 +#define MODBUS_WRITE_MULTIPLE_BYTE_COUNT_OFFSET 4 +#define MODBUS_WRITE_MULTIPLE_MIN_SIZE 5 +#define MODBUS_MASK_WRITE_REGISTER_SIZE 6 +#define MODBUS_READ_WRITE_MULTIPLE_BYTE_COUNT_OFFSET 8 +#define MODBUS_READ_WRITE_MULTIPLE_MIN_SIZE 9 +#define MODBUS_READ_FIFO_SIZE 2 +#define MODBUS_MEI_MIN_SIZE 1 +#define MODBUS_FUNC_READ_EXCEPTION_RESP_SIZE 1 +#define MODBUS_SUB_FUNC_READ_DEVICE_ID_SIZE 3 +#define MODBUS_SUB_FUNC_READ_DEVICE_START_LEN 2 +#define MODBUS_SUB_FUNC_READ_DEVICE_LENGTH_OFFSET 1 + +/* Other defines */ +#define MODBUS_PROTOCOL_ID 0 + +/* Modbus data structures */ +struct modbus_header_t +{ + /* MBAP Header */ + uint16_t transaction_id; + uint16_t protocol_id; + uint16_t length; + uint8_t unit_id; + + /* PDU Start */ + uint8_t function_code; +}; + +static void ModbusCheckRequestLengths(modbus_session_data_t* session, Packet* p) +{ + uint16_t modbus_payload_len = p->dsize - MODBUS_MIN_LEN; + uint8_t tmp_count; + bool check_passed = false; + + switch (session->func) + { + case MODBUS_FUNC_READ_COILS: + case MODBUS_FUNC_READ_DISCRETE_INPUTS: + case MODBUS_FUNC_READ_HOLDING_REGISTERS: + case MODBUS_FUNC_READ_INPUT_REGISTERS: + case MODBUS_FUNC_WRITE_SINGLE_COIL: + case MODBUS_FUNC_WRITE_SINGLE_REGISTER: + case MODBUS_FUNC_DIAGNOSTICS: + if (modbus_payload_len == MODBUS_FOUR_DATA_BYTES) + check_passed = true; + break; + + case MODBUS_FUNC_READ_EXCEPTION_STATUS: + case MODBUS_FUNC_GET_COMM_EVENT_COUNTER: + case MODBUS_FUNC_GET_COMM_EVENT_LOG: + case MODBUS_FUNC_REPORT_SLAVE_ID: + if (modbus_payload_len == MODBUS_EMPTY_DATA_LEN) + check_passed = true; + break; + + case MODBUS_FUNC_WRITE_MULTIPLE_COILS: + case MODBUS_FUNC_WRITE_MULTIPLE_REGISTERS: + if (modbus_payload_len >= MODBUS_WRITE_MULTIPLE_MIN_SIZE) + { + tmp_count = *(p->data + MODBUS_MIN_LEN + + MODBUS_WRITE_MULTIPLE_BYTE_COUNT_OFFSET); + if (modbus_payload_len == tmp_count + MODBUS_WRITE_MULTIPLE_MIN_SIZE) + check_passed = true; + } + break; + + case MODBUS_FUNC_MASK_WRITE_REGISTER: + if (modbus_payload_len == MODBUS_MASK_WRITE_REGISTER_SIZE) + check_passed = true; + break; + + case MODBUS_FUNC_READ_WRITE_MULTIPLE_REGISTERS: + if (modbus_payload_len >= MODBUS_READ_WRITE_MULTIPLE_MIN_SIZE) + { + tmp_count = *(p->data + MODBUS_MIN_LEN + + MODBUS_READ_WRITE_MULTIPLE_BYTE_COUNT_OFFSET); + if (modbus_payload_len == MODBUS_READ_WRITE_MULTIPLE_MIN_SIZE + tmp_count) + check_passed = true; + } + break; + + case MODBUS_FUNC_READ_FIFO_QUEUE: + if (modbus_payload_len == MODBUS_READ_FIFO_SIZE) + check_passed = true; + break; + + case MODBUS_FUNC_ENCAPSULATED_INTERFACE_TRANSPORT: + if (modbus_payload_len >= MODBUS_MEI_MIN_SIZE) + { + uint8_t mei_type = *(p->data + MODBUS_MIN_LEN); + + /* MEI Type 0x0E is covered under the Modbus spec as + "Read Device Identification". Type 0x0D is defined in + the spec as "CANopen General Reference Request and Response PDU" + and falls outside the scope of the Modbus preprocessor. + + Other values are reserved. + */ + if ((mei_type == MODBUS_SUB_FUNC_READ_DEVICE_ID) && + (modbus_payload_len == MODBUS_SUB_FUNC_READ_DEVICE_ID_SIZE)) + check_passed = true; + } + break; + + case MODBUS_FUNC_READ_FILE_RECORD: + /* Modbus read file record request contains a byte count, followed + by a set of 7-byte sub-requests. */ + if (modbus_payload_len >= MODBUS_BYTE_COUNT_SIZE) + { + tmp_count = *(p->data + MODBUS_MIN_LEN); + if ((tmp_count == modbus_payload_len - MODBUS_BYTE_COUNT_SIZE) && + (tmp_count % MODBUS_FILE_RECORD_SUB_REQUEST_SIZE == 0)) + { + check_passed = true; + } + } + break; + + case MODBUS_FUNC_WRITE_FILE_RECORD: + /* Modbus write file record request contains a byte count, followed + by a set of sub-requests that contain a 7-byte header and a + variable amount of data. */ + + if (modbus_payload_len >= MODBUS_BYTE_COUNT_SIZE) + { + tmp_count = *(p->data + MODBUS_MIN_LEN); + if (tmp_count == modbus_payload_len - MODBUS_BYTE_COUNT_SIZE) + { + uint16_t bytes_processed = 0; + + while (bytes_processed < (uint16_t)tmp_count) + { + uint16_t record_length = 0; + + /* Check space for sub-request header info */ + if ((modbus_payload_len - bytes_processed) < + MODBUS_FILE_RECORD_SUB_REQUEST_SIZE) + break; + + /* Extract record length. */ + record_length = *(p->data + MODBUS_MIN_LEN + + MODBUS_BYTE_COUNT_SIZE + bytes_processed + + MODBUS_FILE_RECORD_SUB_REQUEST_LEN_OFFSET); + + record_length = record_length << 8; + + record_length |= *(p->data + MODBUS_MIN_LEN + + MODBUS_BYTE_COUNT_SIZE + bytes_processed + + MODBUS_FILE_RECORD_SUB_REQUEST_LEN_OFFSET + 1); + + /* Jump over record data. */ + bytes_processed += MODBUS_FILE_RECORD_SUB_REQUEST_SIZE + + 2*record_length; + + if (bytes_processed == (uint16_t)tmp_count) + check_passed = true; + } + } + } + break; + + default: /* Don't alert if we couldn't check the length. */ + check_passed = true; + break; + } + + if (!check_passed) + SnortEventqAdd(GID_MODBUS, MODBUS_BAD_LENGTH); +} + +static void ModbusCheckResponseLengths(modbus_session_data_t* session, Packet* p) +{ + uint16_t modbus_payload_len = p->dsize - MODBUS_MIN_LEN; + uint8_t tmp_count; + bool check_passed = false; + + switch (session->func) + { + case MODBUS_FUNC_READ_COILS: + case MODBUS_FUNC_READ_DISCRETE_INPUTS: + case MODBUS_FUNC_GET_COMM_EVENT_LOG: + case MODBUS_FUNC_READ_WRITE_MULTIPLE_REGISTERS: + if (modbus_payload_len >= MODBUS_BYTE_COUNT_SIZE) + { + tmp_count = *(p->data + MODBUS_MIN_LEN); /* byte count */ + if (modbus_payload_len == MODBUS_BYTE_COUNT_SIZE + tmp_count) + check_passed = true; + } + break; + + case MODBUS_FUNC_READ_HOLDING_REGISTERS: + case MODBUS_FUNC_READ_INPUT_REGISTERS: + if (modbus_payload_len >= MODBUS_BYTE_COUNT_SIZE) + { + /* count of 2-byte registers*/ + tmp_count = *(p->data + MODBUS_MIN_LEN); + if (modbus_payload_len == MODBUS_BYTE_COUNT_SIZE + 2*tmp_count) + check_passed = true; + } + break; + + case MODBUS_FUNC_WRITE_SINGLE_COIL: + case MODBUS_FUNC_WRITE_SINGLE_REGISTER: + case MODBUS_FUNC_DIAGNOSTICS: + case MODBUS_FUNC_GET_COMM_EVENT_COUNTER: + case MODBUS_FUNC_WRITE_MULTIPLE_COILS: + case MODBUS_FUNC_WRITE_MULTIPLE_REGISTERS: + if (modbus_payload_len == MODBUS_FOUR_DATA_BYTES) + check_passed = true; + break; + + case MODBUS_FUNC_READ_EXCEPTION_STATUS: + if (modbus_payload_len == MODBUS_FUNC_READ_EXCEPTION_RESP_SIZE) + check_passed = true; + break; + + case MODBUS_FUNC_MASK_WRITE_REGISTER: + if (modbus_payload_len == MODBUS_MASK_WRITE_REGISTER_SIZE) + check_passed = true; + break; + + case MODBUS_FUNC_READ_FIFO_QUEUE: + if (modbus_payload_len >= MODBUS_DOUBLE_BYTE_COUNT_SIZE) + { + uint16_t tmp_count_16; + + /* This function uses a 2-byte byte count!! */ + tmp_count_16 = *(uint16_t*)(p->data + MODBUS_MIN_LEN); + tmp_count_16 = ntohs(tmp_count_16); + if (modbus_payload_len == MODBUS_DOUBLE_BYTE_COUNT_SIZE + tmp_count_16) + check_passed = true; + } + break; + + case MODBUS_FUNC_ENCAPSULATED_INTERFACE_TRANSPORT: + if (modbus_payload_len >= MODBUS_READ_DEVICE_ID_HEADER_LEN) + { + uint8_t mei_type = *(p->data + MODBUS_MIN_LEN); + uint8_t num_objects = *(p->data + MODBUS_MIN_LEN + + MODBUS_READ_DEVICE_ID_NUM_OBJ_OFFSET); + + /* MEI Type 0x0E is covered under the Modbus spec as + "Read Device Identification". Type 0x0D is defined in + the spec as "CANopen General Reference Request and Response PDU" + and falls outside the scope of the Modbus preprocessor. + + Other values are reserved. + */ + + if (mei_type == MODBUS_SUB_FUNC_CANOPEN) + check_passed = true; + + if (mei_type != MODBUS_SUB_FUNC_READ_DEVICE_ID) + break; + + /* Loop through sub-requests, make sure that the lengths inside + don't violate our total Modbus PDU size. */ + uint16_t offset = MODBUS_READ_DEVICE_ID_HEADER_LEN; + uint8_t i; + + for ( i = 0; i < num_objects; i++) + { + uint8_t sub_request_data_len; + + /* Sub request starts with 2 bytes, type + len */ + if (offset + MODBUS_SUB_FUNC_READ_DEVICE_START_LEN > modbus_payload_len) + break; + + /* Length is second byte in sub-request */ + sub_request_data_len = *(p->data + MODBUS_MIN_LEN + + offset + MODBUS_SUB_FUNC_READ_DEVICE_LENGTH_OFFSET); + + /* Set offset to byte after sub-request */ + offset += (MODBUS_SUB_FUNC_READ_DEVICE_START_LEN + sub_request_data_len); + } + + if ((i == num_objects) && (offset == modbus_payload_len)) + check_passed = true; + } + break; + + /* Cannot check this response, as it is device specific. */ + case MODBUS_FUNC_REPORT_SLAVE_ID: + + /* Cannot check these responses, as their sizes depend on the corresponding + requests. Can re-visit if we bother with request/response tracking. */ + case MODBUS_FUNC_READ_FILE_RECORD: + case MODBUS_FUNC_WRITE_FILE_RECORD: + + default: /* Don't alert if we couldn't check the lengths. */ + check_passed = true; + break; + } + + if (!check_passed) + SnortEventqAdd(GID_MODBUS, MODBUS_BAD_LENGTH); +} + +static void ModbusCheckReservedFuncs(modbus_header_t* header, Packet* p) +{ + switch (header->function_code) + { + /* Only some sub-functions are reserved here. */ + case MODBUS_FUNC_DIAGNOSTICS: + { + uint16_t sub_func; + + if (p->dsize < MODBUS_MIN_LEN+2) + break; + + sub_func = *((uint16_t*)(p->data + MODBUS_MIN_LEN)); + sub_func = ntohs(sub_func); + + if ((sub_func == 19) || (sub_func >= 21)) + SnortEventqAdd(GID_MODBUS, MODBUS_RESERVED_FUNCTION); + } + break; + + /* Reserved function codes */ + case 0x09: + case 0x0A: + case 0x0D: + case 0x0E: + case 0x29: + case 0x2A: + case 0x5A: + case 0x5B: + case 0x7D: + case 0x7E: + case 0x7F: + SnortEventqAdd(GID_MODBUS, MODBUS_RESERVED_FUNCTION); + break; + } +} + +bool ModbusDecode(Packet* p) +{ + modbus_header_t* header; + + if (p->dsize < MODBUS_MIN_LEN) + return false; + + ModbusFlowData* mfd = + (ModbusFlowData*)p->flow->get_application_data(ModbusFlowData::flow_id); + + /* Lay the header struct over the payload */ + header = (modbus_header_t*)p->data; + + /* The protocol ID field should read 0x0000 for Modbus. It allows for + multiplexing with some other protocols over serial line. */ + if (header->protocol_id != MODBUS_PROTOCOL_ID) + { + SnortEventqAdd(GID_MODBUS, MODBUS_BAD_PROTO_ID); + return false; + } + + /* Set the session data. + Normally we'd need to swap byte order, but these are 8-bit fields. */ + mfd->ssn_data.unit = header->unit_id; + mfd->ssn_data.func = header->function_code; + + /* Check for reserved function codes */ + ModbusCheckReservedFuncs(header, p); + + /* Read the Modbus payload and check lengths against the expected length for + each function. */ + if (p->packet_flags & PKT_FROM_CLIENT) + ModbusCheckRequestLengths(&mfd->ssn_data, p); + else + ModbusCheckResponseLengths(&mfd->ssn_data, p); + + return true; +} + diff --git a/src/service_inspectors/modbus/modbus_decode.h b/src/service_inspectors/modbus/modbus_decode.h new file mode 100644 index 000000000..bc61fe874 --- /dev/null +++ b/src/service_inspectors/modbus/modbus_decode.h @@ -0,0 +1,31 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2015 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2011-2013 Sourcefire, Inc. +// +// 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. +//-------------------------------------------------------------------------- + +// modbus_decode.h author Ryan Jordan + +#ifndef MODBUS_DECODE_H +#define MODBUS_DECODE_H + +/* Need 8 bytes for MBAP Header + Function Code */ +#define MODBUS_MIN_LEN 8 + +bool ModbusDecode(struct Packet*); + +#endif + diff --git a/src/service_inspectors/modbus/modbus_module.cc b/src/service_inspectors/modbus/modbus_module.cc new file mode 100644 index 000000000..a0009d485 --- /dev/null +++ b/src/service_inspectors/modbus/modbus_module.cc @@ -0,0 +1,74 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2015 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. +//-------------------------------------------------------------------------- + +// modbus_module.cc author Russ Combs + +#include "modbus_module.h" + +#include "modbus.h" +#include "time/profiler.h" + +THREAD_LOCAL ProfileStats modbus_prof; + +//------------------------------------------------------------------------- +// stats +//------------------------------------------------------------------------- + +const PegInfo peg_names[] = +{ + { "sessions", "total sessions processed" }, + + { nullptr, nullptr } +}; + +const PegInfo* ModbusModule::get_pegs() const +{ return peg_names; } + +PegCount* ModbusModule::get_counts() const +{ return (PegCount*)&modbus_stats; } + +//------------------------------------------------------------------------- +// rules +//------------------------------------------------------------------------- + +#define MODBUS_BAD_LENGTH_STR \ + "length in Modbus MBAP header does not match the length needed for the given function" + +#define MODBUS_BAD_PROTO_ID_STR "Modbus protocol ID is non-zero" +#define MODBUS_RESERVED_FUNCTION_STR "Reserved Modbus function code in use" + +static const RuleMap modbus_rules[] = +{ + { MODBUS_BAD_LENGTH, MODBUS_BAD_LENGTH_STR }, + { MODBUS_BAD_PROTO_ID, MODBUS_BAD_PROTO_ID_STR }, + { MODBUS_RESERVED_FUNCTION, MODBUS_RESERVED_FUNCTION_STR }, + + { 0, nullptr } +}; + +const RuleMap* ModbusModule::get_rules() const +{ return modbus_rules; } + +//------------------------------------------------------------------------- +// params +//------------------------------------------------------------------------- + +ModbusModule::ModbusModule() : + Module(MODBUS_NAME, MODBUS_HELP) +{ } + diff --git a/src/service_inspectors/modbus/modbus_module.h b/src/service_inspectors/modbus/modbus_module.h new file mode 100644 index 000000000..a1b2d4bd2 --- /dev/null +++ b/src/service_inspectors/modbus/modbus_module.h @@ -0,0 +1,56 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2015 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. +//-------------------------------------------------------------------------- + +// modbus_module.cc author Russ Combs + +#ifndef MODUBS_MODULE_H +#define MODUBS_MODULE_H + +#include "framework/module.h" +#include "main/thread.h" + +#define GID_MODBUS 144 + +#define MODBUS_BAD_LENGTH 1 +#define MODBUS_BAD_PROTO_ID 2 +#define MODBUS_RESERVED_FUNCTION 3 + +#define MODBUS_NAME "modbus" +#define MODBUS_HELP "modbus inspection" + +extern THREAD_LOCAL ProfileStats modbus_prof; + +class ModbusModule : public Module +{ +public: + ModbusModule(); + + unsigned get_gid() const override + { return GID_MODBUS; } + + const RuleMap* get_rules() const override; + + const PegInfo* get_pegs() const override; + PegCount* get_counts() const override; + + ProfileStats* get_profile() const override + { return &modbus_prof; } +}; + +#endif + diff --git a/src/service_inspectors/modbus/modbus_paf.cc b/src/service_inspectors/modbus/modbus_paf.cc new file mode 100644 index 000000000..0b735b4a2 --- /dev/null +++ b/src/service_inspectors/modbus/modbus_paf.cc @@ -0,0 +1,90 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2015 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2011-2013 Sourcefire, Inc. +// +// 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. +//-------------------------------------------------------------------------- + +// modbus_paf.cc author Ryan Jordan +// Protocol-Aware Flushing (PAF) code for the Modbus preprocessor. + +#include "modbus_paf.h" + +#include "modbus_decode.h" +#include "modbus_module.h" +#include "events/event_queue.h" + +#define MODBUS_MIN_HDR_LEN 2 // Enough for Unit ID + Function +#define MODBUS_MAX_HDR_LEN 254 // Max PDU size is 260, 6 bytes already seen + +ModbusSplitter::ModbusSplitter(bool b) : StreamSplitter(b) +{ + state = MODBUS_PAF_STATE__TRANS_ID_1; + modbus_length = 0; +} + +// Modbus/TCP PAF: +// Statefully inspects Modbus traffic from the start of a session, +// Reads up until the length octet is found, then sets a flush point. + +StreamSplitter::Status ModbusSplitter::scan( + Flow*, 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 MODBUS_PAF_STATE__TRANS_ID_1: + case MODBUS_PAF_STATE__TRANS_ID_2: + case MODBUS_PAF_STATE__PROTO_ID_1: + case MODBUS_PAF_STATE__PROTO_ID_2: + state = (modbus_paf_state_t)(((int)state) + 1); + break; + + /* Read length 1 byte at a time, in case a TCP segment is sent + * with only 5 bytes from the MBAP header */ + case MODBUS_PAF_STATE__LENGTH_1: + modbus_length |= *(data + bytes_processed) << 8; + state = (modbus_paf_state_t)(((int)state) + 1); + break; + + case MODBUS_PAF_STATE__LENGTH_2: + modbus_length |= *(data + bytes_processed); + state = (modbus_paf_state_t)(((int)state) + 1); + break; + + case MODBUS_PAF_STATE__SET_FLUSH: + if ((modbus_length < MODBUS_MIN_HDR_LEN) || + (modbus_length > MODBUS_MAX_HDR_LEN)) + { + SnortEventqAdd(GID_MODBUS, MODBUS_BAD_LENGTH); + } + + *fp = modbus_length + bytes_processed; + state = MODBUS_PAF_STATE__TRANS_ID_1; + modbus_length = 0; + return StreamSplitter::FLUSH; + } + + bytes_processed++; + } + + return StreamSplitter::SEARCH; +} + diff --git a/src/service_inspectors/modbus/modbus_paf.h b/src/service_inspectors/modbus/modbus_paf.h new file mode 100644 index 000000000..507938667 --- /dev/null +++ b/src/service_inspectors/modbus/modbus_paf.h @@ -0,0 +1,59 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2015 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2011-2013 Sourcefire, Inc. +// +// 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. +//-------------------------------------------------------------------------- + +// modbus_paf.h author Ryan Jordan + +#ifndef MODBUS_PAF__H +#define MODBUS_PAF__H + +// Protocol-Aware Flushing (PAF) code for the Modbus preprocessor. + +#include "modbus.h" +#include "protocols/packet.h" +#include "stream/stream_splitter.h" + +enum modbus_paf_state_t +{ + MODBUS_PAF_STATE__TRANS_ID_1, + MODBUS_PAF_STATE__TRANS_ID_2, + MODBUS_PAF_STATE__PROTO_ID_1, + MODBUS_PAF_STATE__PROTO_ID_2, + MODBUS_PAF_STATE__LENGTH_1, + MODBUS_PAF_STATE__LENGTH_2, + MODBUS_PAF_STATE__SET_FLUSH +}; + +class ModbusSplitter : public StreamSplitter +{ +public: + ModbusSplitter(bool); + + Status scan( + Flow*, const uint8_t* data, uint32_t len, + uint32_t flags, uint32_t* fp) override; + + bool is_paf() override { return true; } + +private: + modbus_paf_state_t state; + uint16_t modbus_length; +}; + +#endif + diff --git a/src/service_inspectors/service_inspectors.cc b/src/service_inspectors/service_inspectors.cc index 55f4ac215..4e2afc37e 100644 --- a/src/service_inspectors/service_inspectors.cc +++ b/src/service_inspectors/service_inspectors.cc @@ -31,6 +31,9 @@ extern const BaseApi* sin_http_inspect; extern const BaseApi* ips_gtp_info; extern const BaseApi* ips_gtp_type; extern const BaseApi* ips_gtp_version; +extern const BaseApi* ips_modbus_data; +extern const BaseApi* ips_modbus_func; +extern const BaseApi* ips_modbus_unit; extern const BaseApi* ips_sip_body; extern const BaseApi* ips_sip_header; extern const BaseApi* ips_sip_method; @@ -44,6 +47,7 @@ extern const BaseApi* sin_ftp_server; extern const BaseApi* sin_ftp_data; extern const BaseApi* sin_gtp; extern const BaseApi* sin_imap; +extern const BaseApi* sin_modbus; extern const BaseApi* sin_nhttp; extern const BaseApi* sin_pop; extern const BaseApi* sin_rpc_decode; @@ -64,6 +68,9 @@ const BaseApi* service_inspectors[] = ips_gtp_info, ips_gtp_type, ips_gtp_version, + ips_modbus_data, + ips_modbus_func, + ips_modbus_unit, ips_sip_body, ips_sip_header, ips_sip_method, @@ -77,6 +84,7 @@ const BaseApi* service_inspectors[] = sin_ftp_data, sin_gtp, sin_imap, + sin_modbus, sin_nhttp, sin_pop, sin_rpc_decode,