]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #92 in SNORT/snort3 from crc/modbus2 to master
authorRuss Combs (rucombs) <rucombs@cisco.com>
Thu, 22 Oct 2015 00:04:32 +0000 (20:04 -0400)
committerRuss Combs (rucombs) <rucombs@cisco.com>
Thu, 22 Oct 2015 00:04:32 +0000 (20:04 -0400)
Squashed commit of the following:

commit d39dd81b300723d990c42b301e73aaa0e8db8386
Author: Russ Combs <rucombs@cisco.com>
Date:   Wed Oct 21 15:08:25 2015 -0400

    review updates

commit 97306ddbf13f8dccf692613b0f6835e68841e8d0
Author: Russ Combs <rucombs@cisco.com>
Date:   Tue Oct 20 23:22:06 2015 -0400

    initial modbus port

20 files changed:
configure.ac
doc/style.txt
src/Makefile.am
src/service_inspectors/CMakeLists.txt
src/service_inspectors/Makefile.am
src/service_inspectors/modbus/CMakeLists.txt [new file with mode: 0644]
src/service_inspectors/modbus/Makefile.am [new file with mode: 0644]
src/service_inspectors/modbus/dev_notes.txt [new file with mode: 0644]
src/service_inspectors/modbus/ips_modbus_data.cc [new file with mode: 0644]
src/service_inspectors/modbus/ips_modbus_func.cc [new file with mode: 0644]
src/service_inspectors/modbus/ips_modbus_unit.cc [new file with mode: 0644]
src/service_inspectors/modbus/modbus.cc [new file with mode: 0644]
src/service_inspectors/modbus/modbus.h [new file with mode: 0644]
src/service_inspectors/modbus/modbus_decode.cc [new file with mode: 0644]
src/service_inspectors/modbus/modbus_decode.h [new file with mode: 0644]
src/service_inspectors/modbus/modbus_module.cc [new file with mode: 0644]
src/service_inspectors/modbus/modbus_module.h [new file with mode: 0644]
src/service_inspectors/modbus/modbus_paf.cc [new file with mode: 0644]
src/service_inspectors/modbus/modbus_paf.h [new file with mode: 0644]
src/service_inspectors/service_inspectors.cc

index 97d3e40dd09753d6d69b205aca7eddd6d330af21..e20712630adf74c50f2ba97e214b16343366f108 100644 (file)
@@ -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 \
index ebc8f4898ec4b9ebe056118c0a29b2fd4feca450..156140ce989fca7a322227f17bcb053444343106 100644 (file)
@@ -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 <file>
+    uncrustify -c crusty.cfg --replace file.cc
 
index 021998053db30a088e336bf70b2820a92c091806..ef62c41c3ea161dfd1644c29dfd047d5fb7bbd4c 100644 (file)
@@ -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 \
index ba11297b7e91df31af13561a84b62930af4338ad..8111545e3d13d649eb8bacc4fc18a64780cb98b0 100644 (file)
@@ -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
index dea53a1aa7844f0b0e3afdd038d073a87f620e83..b361a5ef9e96fa2be2909dd64268b26d3d7cfa90 100644 (file)
@@ -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 (file)
index 0000000..9cbd7dc
--- /dev/null
@@ -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 (file)
index 0000000..ceaea07
--- /dev/null
@@ -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 (file)
index 0000000..c6ee14d
--- /dev/null
@@ -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 (file)
index 0000000..64c967d
--- /dev/null
@@ -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 <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;
+
diff --git a/src/service_inspectors/modbus/ips_modbus_func.cc b/src/service_inspectors/modbus/ips_modbus_func.cc
new file mode 100644 (file)
index 0000000..5a48a9f
--- /dev/null
@@ -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 <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;
+
diff --git a/src/service_inspectors/modbus/ips_modbus_unit.cc b/src/service_inspectors/modbus/ips_modbus_unit.cc
new file mode 100644 (file)
index 0000000..705aa8e
--- /dev/null
@@ -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 <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;
+
diff --git a/src/service_inspectors/modbus/modbus.cc b/src/service_inspectors/modbus/modbus.cc
new file mode 100644 (file)
index 0000000..d25ee71
--- /dev/null
@@ -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 <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
+
diff --git a/src/service_inspectors/modbus/modbus.h b/src/service_inspectors/modbus/modbus.h
new file mode 100644 (file)
index 0000000..673d5e9
--- /dev/null
@@ -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 <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
+
diff --git a/src/service_inspectors/modbus/modbus_decode.cc b/src/service_inspectors/modbus/modbus_decode.cc
new file mode 100644 (file)
index 0000000..16bc119
--- /dev/null
@@ -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 (file)
index 0000000..bc61fe8
--- /dev/null
@@ -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 (file)
index 0000000..a0009d4
--- /dev/null
@@ -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 <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)
+{ }
+
diff --git a/src/service_inspectors/modbus/modbus_module.h b/src/service_inspectors/modbus/modbus_module.h
new file mode 100644 (file)
index 0000000..a1b2d4b
--- /dev/null
@@ -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 <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
+
diff --git a/src/service_inspectors/modbus/modbus_paf.cc b/src/service_inspectors/modbus/modbus_paf.cc
new file mode 100644 (file)
index 0000000..0b735b4
--- /dev/null
@@ -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 (file)
index 0000000..5079386
--- /dev/null
@@ -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
+
index 55f4ac21518b56082b7826abe3dca9f943c309b4..4e2afc37e3379dc9a37ffce75be8ed366fb52f08 100644 (file)
@@ -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,