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 \
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 <file>
+ uncrustify -c crusty.cfg --replace file.cc
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 \
add_subdirectory(gtp)
add_subdirectory(http_inspect)
add_subdirectory(imap)
+add_subdirectory(modbus)
add_subdirectory(nhttp_inspect)
add_subdirectory(pop)
add_subdirectory(rpc_decode)
ftp_telnet
gtp
imap
+ modbus
nhttp_inspect
pop
rpc_decode
gtp \
http_inspect \
imap \
+modbus \
nhttp_inspect \
pop \
rpc_decode \
--- /dev/null
+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)
+
--- /dev/null
+
+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
+
--- /dev/null
+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.
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <rucombs@cisco.com>
+
+#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;
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <rucombs@cisco.com>
+
+#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;
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <rucombs@cisco.com>
+
+#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;
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <rucombs@cisco.com>
+
+#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
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <rucombs@cisco.com>
+
+#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
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <rucombs@cisco.com>
+
+#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)
+{ }
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 <rucombs@cisco.com>
+
+#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
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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
+
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;
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;
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,
sin_ftp_data,
sin_gtp,
sin_imap,
+ sin_modbus,
sin_nhttp,
sin_pop,
sin_rpc_decode,