]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #152 in SNORT/snort3 from dnp3_2 to master
authorRuss Combs (rucombs) <rucombs@cisco.com>
Tue, 24 Nov 2015 21:38:14 +0000 (16:38 -0500)
committerRuss Combs (rucombs) <rucombs@cisco.com>
Tue, 24 Nov 2015 21:38:14 +0000 (16:38 -0500)
Squashed commit of the following:

commit 1619f91f932d5d2b4fbec6b15069d4bc0ee7e754
Author: mdagon <mdagon@cisco.com>
Date:   Tue Nov 24 16:32:34 2015 -0500

    cmake fix, code review comments

commit afb271e012136648246ef42bf34ab196efab4af5
Author: rrp <rrp@cisco.com>
Date:   Tue Nov 24 14:04:11 2015 -0500

    Addressed all the comments from CR #2 except building with cmake which is being worked on by Maya

commit 87323a71b9a284ed532e0d58afcbb690b6f1630a
Author: mdagon <mdagon@cisco.com>
Date:   Tue Nov 24 11:56:37 2015 -0500

    Code review comments:
    1. Copyright update
    2. lua/snort.lua update

commit d6226e7572f481a74f14671b9b16b3ea6eda3f51
Author: rrp <rrp@cisco.com>
Date:   Thu Nov 19 11:57:17 2015 -0500

    changes based on code review comments

commit bbb0defd183f2171486768bc22eca671f4fa10d1
Author: rrp <rrp@cisco.com>
Date:   Mon Nov 2 12:44:58 2015 -0500

    DNP3 preprocessor port
    Includes the following
    -port 2x code and add new code
    -snort2lua changes
    -port tests and get them to work (tests will be submitted to test repo after code check-in)
    -update documentation -dev notes and bugs
    -verified stats generation and profiling

34 files changed:
configure.ac
doc/bugs.txt
lua/snort.lua
src/Makefile.am
src/service_inspectors/CMakeLists.txt
src/service_inspectors/Makefile.am
src/service_inspectors/dnp3/CMakeLists.txt [new file with mode: 0644]
src/service_inspectors/dnp3/Makefile.am [new file with mode: 0644]
src/service_inspectors/dnp3/dev_notes.txt [new file with mode: 0644]
src/service_inspectors/dnp3/dnp3.cc [new file with mode: 0644]
src/service_inspectors/dnp3/dnp3.h [new file with mode: 0644]
src/service_inspectors/dnp3/dnp3_map.cc [new file with mode: 0644]
src/service_inspectors/dnp3/dnp3_map.h [new file with mode: 0644]
src/service_inspectors/dnp3/dnp3_module.cc [new file with mode: 0644]
src/service_inspectors/dnp3/dnp3_module.h [new file with mode: 0644]
src/service_inspectors/dnp3/dnp3_paf.cc [new file with mode: 0644]
src/service_inspectors/dnp3/dnp3_paf.h [new file with mode: 0644]
src/service_inspectors/dnp3/dnp3_reassembly.cc [new file with mode: 0644]
src/service_inspectors/dnp3/dnp3_reassembly.h [new file with mode: 0644]
src/service_inspectors/dnp3/ips_dnp3_data.cc [new file with mode: 0644]
src/service_inspectors/dnp3/ips_dnp3_func.cc [new file with mode: 0644]
src/service_inspectors/dnp3/ips_dnp3_ind.cc [new file with mode: 0644]
src/service_inspectors/dnp3/ips_dnp3_obj.cc [new file with mode: 0644]
src/service_inspectors/service_inspectors.cc
tools/snort2lua/preprocessor_states/CMakeLists.txt
tools/snort2lua/preprocessor_states/Makefile.am
tools/snort2lua/preprocessor_states/pps_dnp3.cc [new file with mode: 0644]
tools/snort2lua/preprocessor_states/preprocessor_api.cc
tools/snort2lua/rule_states/CMakeLists.txt
tools/snort2lua/rule_states/Makefile.am
tools/snort2lua/rule_states/rule_api.cc
tools/snort2lua/rule_states/rule_convert_comma_list.cc [new file with mode: 0644]
tools/snort2lua/rule_states/rule_dnp3_obj.cc [new file with mode: 0644]
tools/snort2lua/rule_states/rule_unchanged.cc

index e4e09b83ac02fae093a577bbc855e4e25f713538..bdd09eb689ba8dda4d0e541eb09647da9facc984 100644 (file)
@@ -965,6 +965,7 @@ src/service_inspectors/back_orifice/Makefile \
 src/service_inspectors/dns/Makefile \
 src/service_inspectors/ftp_telnet/Makefile \
 src/service_inspectors/gtp/Makefile \
+src/service_inspectors/dnp3/Makefile \
 src/service_inspectors/http_inspect/Makefile \
 src/service_inspectors/imap/Makefile \
 src/service_inspectors/modbus/Makefile \
index 28b0997143ec0a46b26b0773a242ed0e0c371753..cc9beeada130898d50ccf6ad2c0d3240d6ef1195 100644 (file)
@@ -78,6 +78,7 @@
 
 * There is a bug in pps_stream_tcp.cc.. when stream_tcp: is
   specified without any arguments, snort2lua doesn't convert it.
+  Same for stream_udp.
 
 * Loses the ip list delimiters [ ]; change to ( )
 
@@ -90,6 +91,7 @@
 * alert_fast and alert_full: output configuration includes "file = 'foo.bar'", but file is a bool
   and you cannot specify an output file name in the configuration.
 
+* preprocessor ports option: ports <number> not supported.
 
 ==== Runtime
 
 * The hext DAQ does not support embedded quotes in text lines (use hex
   lines as a workaround).
 
+* Stream TCP alert squash mechanism incorrectly squashes alerts for different TCP packets.
+
index f80e1b199a472f9aa3607be112bae81d45d5de93..45aa52a985cefd6189f437240d6c4af9e819c898 100644 (file)
@@ -67,6 +67,7 @@ stream_file = { }
 
 arp_spoof = { }
 back_orifice = { }
+dnp3 = { }
 dns = { }
 gtp_inspect = default_gtp
 imap = { }
index 6de33c6241e556cb94c9daac6fa067042aada05c..b9f185305fe65d72b2c66dee1e9ae11505684194 100644 (file)
@@ -12,6 +12,7 @@ service_inspectors/back_orifice/libback_orifice.a \
 service_inspectors/dns/libdns.a \
 service_inspectors/ftp_telnet/libftp_telnet.a \
 service_inspectors/gtp/libgtp_inspect.a \
+service_inspectors/dnp3/libdnp3.a \
 service_inspectors/modbus/libmodbus.a \
 service_inspectors/nhttp_inspect/libnhttp_inspect.a \
 service_inspectors/rpc_decode/librpc_decode.a \
index bafbb44707750940e5ac157100125b80d787c474..f7f1e61ec2ecebd6fab4ad569569d9d567c6b003 100644 (file)
@@ -1,5 +1,6 @@
 
 add_subdirectory(back_orifice)
+add_subdirectory(dnp3)
 add_subdirectory(dns)
 add_subdirectory(ftp_telnet)
 add_subdirectory(gtp)
@@ -18,6 +19,7 @@ add_subdirectory(wizard)
 if (STATIC_INSPECTORS)
     set (STATIC_INSECTOR_LIBS
         back_orifice
+        dnp3
         dns
         ftp_telnet
         gtp_inspect
index b361a5ef9e96fa2be2909dd64268b26d3d7cfa90..bfb583af5b4a4e0f9f7882a2a072b97834144707 100644 (file)
@@ -16,6 +16,7 @@ service_inspectors.h
 
 SUBDIRS = \
 back_orifice \
+dnp3 \
 dns \
 ftp_telnet \
 gtp \
diff --git a/src/service_inspectors/dnp3/CMakeLists.txt b/src/service_inspectors/dnp3/CMakeLists.txt
new file mode 100644 (file)
index 0000000..e4394cc
--- /dev/null
@@ -0,0 +1,27 @@
+
+set( FILE_LIST
+    dnp3.cc
+    dnp3.h
+    dnp3_map.cc
+    dnp3_map.h
+    dnp3_module.cc
+    dnp3_module.h
+    dnp3_reassembly.cc
+    dnp3_reassembly.h
+    dnp3_paf.cc
+    dnp3_paf.h
+    ips_dnp3_data.cc
+    ips_dnp3_func.cc
+    ips_dnp3_ind.cc
+    ips_dnp3_obj.cc
+
+)
+
+if (STATIC_INSPECTORS)
+    add_library(dnp3 STATIC ${FILE_LIST})
+
+else (STATIC_INSPECTORS)
+    add_shared_library(dnp3 inspectors ${FILE_LIST})
+
+endif (STATIC_INSPECTORS)
+
diff --git a/src/service_inspectors/dnp3/Makefile.am b/src/service_inspectors/dnp3/Makefile.am
new file mode 100644 (file)
index 0000000..6f64017
--- /dev/null
@@ -0,0 +1,31 @@
+
+file_list = \
+dnp3.cc \
+dnp3.h \
+dnp3_map.cc \
+dnp3_map.h \
+dnp3_module.cc \
+dnp3_module.h \
+dnp3_reassembly.cc \
+dnp3_reassembly.h \
+dnp3_paf.cc\
+dnp3_paf.h \
+ips_dnp3_data.cc \
+ips_dnp3_func.cc \
+ips_dnp3_ind.cc \
+ips_dnp3_obj.cc
+
+
+if STATIC_INSPECTORS
+noinst_LIBRARIES = libdnp3.a
+libdnp3_a_SOURCES = $(file_list)
+else
+shlibdir = $(pkglibdir)/inspectors
+shlib_LTLIBRARIES = libdnp3.la
+libdnp3_la_CXXFLAGS = $(AM_CXXFLAGS) -DBUILDING_SO
+libdnp3_la_LDFLAGS = -export-dynamic -shared
+libdnp3_la_SOURCES = $(file_list)
+endif
+
+AM_CXXFLAGS = @AM_CXXFLAGS@
+
diff --git a/src/service_inspectors/dnp3/dev_notes.txt b/src/service_inspectors/dnp3/dev_notes.txt
new file mode 100644 (file)
index 0000000..0e4c40b
--- /dev/null
@@ -0,0 +1,15 @@
+This directory contains all the files related to DNP3 protocol processing.
+
+DNP3 is supported over both TCP/IP and UDP. The inspector provides anomaly
+detection and IPS rule options for matching on data, functions, indications
+and objects in the messages.
+
+The protocol aware flushing (PAF) for DNP3 identifies and flushes frames at
+link layer. The reassembly code in dnp3_reassembly.cc handles transport layer 
+reassembly and generates application layer PDUs for inspection.
+For the UDP case, packets do not go through PAF and the inspector handles
+link layer decoding. Since there can be multiple application level PDUs in a 
+single UDP packet, the inspector performs detection as well.
+
+Configuration of ports is handled by the binder. The default server port is
+20000. The only other DNP3 specific configuration is CRC check enable/disable
diff --git a/src/service_inspectors/dnp3/dnp3.cc b/src/service_inspectors/dnp3/dnp3.cc
new file mode 100644 (file)
index 0000000..8ef7a53
--- /dev/null
@@ -0,0 +1,341 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 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.
+//--------------------------------------------------------------------------
+
+// dnp3.cc author Rashmi Pitre <rrp@cisco.com>
+// based on work by Ryan Jordan
+
+#include "dnp3.h"
+#include "dnp3_paf.h"
+#include "dnp3_reassembly.h"
+
+#include "events/event_queue.h"
+
+THREAD_LOCAL Dnp3Stats dnp3_stats;
+THREAD_LOCAL ProfileStats dnp3_perf_stats;
+
+Dnp3FlowData::Dnp3FlowData() : FlowData(flow_id)
+{
+}
+
+unsigned Dnp3FlowData::flow_id = 0;
+
+static dnp3_session_data_t* get_session_data(Flow* flow)
+{
+    Dnp3FlowData* fd = (Dnp3FlowData*)flow->get_application_data(
+        Dnp3FlowData::flow_id);
+
+    return fd ? &fd->dnp3_session : nullptr;
+}
+
+static dnp3_session_data_t* set_new_dnp3_session(Packet* p)
+{
+    Dnp3FlowData* fd = new Dnp3FlowData;
+
+    p->flow->set_application_data(fd);
+    return(&fd->dnp3_session);
+}
+
+const uint8_t* dnp3_get_alt_buffer(Packet* p, unsigned& len)
+{
+    dnp3_session_data_t* dnp3_sess = get_session_data(p->flow);
+    len = 0;
+    dnp3_reassembly_data_t* rdata;
+
+    if (dnp3_sess)
+    {
+        /* rdata->buffer will be the alt decode buffer.
+           This will be returned via the get_buf inspector API*/
+
+        if (dnp3_sess->direction == DNP3_CLIENT)
+            rdata = &(dnp3_sess->client_rdata);
+        else
+            rdata = &(dnp3_sess->server_rdata);
+        if (rdata->state == DNP3_REASSEMBLY_STATE__DONE)
+        {
+            len = rdata->buflen;
+        }
+    }
+    return len ? (const uint8_t*)rdata->buffer : nullptr;
+}
+
+void dnp3_reset_alt_buffer(const Packet* p)
+{
+    dnp3_session_data_t* dnp3_sess = get_session_data(p->flow);
+
+    if (dnp3_sess)
+    {
+        dnp3_reassembly_data_t* rdata;
+
+        if (dnp3_sess->direction == DNP3_CLIENT)
+            rdata = &(dnp3_sess->client_rdata);
+        else
+            rdata = &(dnp3_sess->server_rdata);
+        if (rdata->state == DNP3_REASSEMBLY_STATE__DONE)
+            dnp3_reassembly_reset(rdata);
+    }
+}
+
+static bool dnp3_process_udp(dnp3ProtoConf& config, dnp3_session_data_t* dnp3_sess, Packet* p)
+{
+    /* Possibly multiple PDUs in this UDP payload.
+       Split up and process individually. */
+
+    uint16_t bytes_processed = 0;
+    bool truncated_pdu = false;
+
+    while (bytes_processed < p->dsize)
+    {
+        uint8_t* pdu_start;
+        uint16_t user_data, num_crcs, pdu_length;
+        dnp3_link_header_t* link;
+
+        pdu_start = (uint8_t*)(p->data + bytes_processed);
+        link = (dnp3_link_header_t*)pdu_start;
+
+        /*Stop if the start bytes are not 0x0564 */
+        if ((p->dsize < bytes_processed + 2)
+            || (link->start != DNP3_START_BYTES))
+            break;
+
+        /* Alert and stop if there's not enough data to read a length */
+        if ((p->dsize - bytes_processed < (int)sizeof(dnp3_link_header_t)) ||
+            (link->len < DNP3_HEADER_REMAINDER_LEN))
+        {
+            truncated_pdu = true;
+            break;
+        }
+
+        /* Calculate the actual length of data to inspect */
+        user_data = link->len - DNP3_HEADER_REMAINDER_LEN;
+        num_crcs = 1 + (user_data/DNP3_CHUNK_SIZE) + (user_data % DNP3_CHUNK_SIZE ? 1 : 0);
+        pdu_length = DNP3_MIN_LEN + link->len + (DNP3_CRC_SIZE*num_crcs);
+
+        if (bytes_processed + pdu_length > p->dsize)
+        {
+            truncated_pdu = true;
+            break;
+        }
+
+        dnp3_full_reassembly(config,dnp3_sess, p, pdu_start,
+            pdu_length);
+
+        bytes_processed += pdu_length;
+    }
+
+    if (truncated_pdu)
+    {
+        SnortEventqAdd(GID_DNP3, DNP3_DROPPED_FRAME);
+    }
+
+    return true;
+}
+
+/* Main runtime entry point */
+
+static void process_dnp3(dnp3ProtoConf& config, Packet* p)
+{
+    if ( p->has_tcp_data() && !p->is_full_pdu() )
+    {
+        return;
+    }
+
+    /* Attempt to get a previously allocated DNP3 block. */
+    dnp3_session_data_t* dnp3_sess = get_session_data(p->flow);
+
+    if (dnp3_sess == nullptr)
+    {
+        /* Check the stream session. If it does not currently
+         * have our DNP3 data-block attached, create one.
+         */
+        dnp3_sess = set_new_dnp3_session(p);
+
+        if ( !dnp3_sess )
+        {
+            return;
+        }
+    }
+
+    /* When pipelined DNP3 PDUs appear in a single TCP segment or UDP packet,
+       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;
+
+    /* Set reassembly direction */
+    if (p->packet_flags & PKT_FROM_CLIENT)
+        dnp3_sess->direction = DNP3_CLIENT;
+    else
+        dnp3_sess->direction = DNP3_SERVER;
+
+    /* Do preprocessor-specific detection stuff here */
+    if (p->has_tcp_data())
+    {
+        ++dnp3_stats.tcp_pdus;
+        /* Single PDU. PAF already split them up into separate pseudo-packets. */
+        dnp3_full_reassembly(config, dnp3_sess, p,(uint8_t*)p->data,p->dsize);
+    }
+    else if (p->has_udp_data())
+    {
+        ++dnp3_stats.udp_packets;
+        dnp3_process_udp(config, dnp3_sess, p);
+    }
+}
+
+//-------------------------------------------------------------------------
+// class stuff
+//-------------------------------------------------------------------------
+
+class Dnp3 : public Inspector
+{
+public:
+    Dnp3(dnp3ProtoConf&);
+    ~Dnp3();
+
+    void show(SnortConfig*) override;
+    void eval(Packet*) override;
+    bool get_buf(InspectionBuffer::Type, Packet*, InspectionBuffer&) override;
+    void clear(Packet*) override;
+
+    StreamSplitter* get_splitter(bool c2s) override
+    {
+        return new Dnp3Splitter(c2s);
+    }
+
+private:
+    dnp3ProtoConf config;
+};
+
+Dnp3::Dnp3(dnp3ProtoConf& pc)
+{
+    config.check_crc = pc.check_crc;
+}
+
+Dnp3::~Dnp3()
+{
+}
+
+void Dnp3::show(SnortConfig*)
+{
+    print_dnp3_conf(config);
+}
+
+void Dnp3::eval(Packet* p)
+{
+    PERF_PROFILE(dnp3_perf_stats);
+
+    assert (p->has_tcp_data() || p->has_udp_data());
+    assert(p->flow);
+
+    ++dnp3_stats.total_packets;
+
+    process_dnp3(config, p);
+}
+
+bool Dnp3::get_buf(
+    InspectionBuffer::Type ibt, Packet* p, InspectionBuffer& b)
+{
+    if ( ibt != InspectionBuffer::IBT_ALT )
+        return false;
+
+    b.data = dnp3_get_alt_buffer(p,b.len);
+
+    return (b.data != nullptr);
+}
+
+void Dnp3::clear(Packet* p)
+{
+    dnp3_reset_alt_buffer(p);
+}
+
+//-------------------------------------------------------------------------
+// api stuff
+//-------------------------------------------------------------------------
+
+static Module* mod_ctor()
+{
+    return new Dnp3Module;
+}
+
+static void mod_dtor(Module* m)
+{
+    delete m;
+}
+
+static void dnp3_init()
+{
+    Dnp3FlowData::init();
+}
+
+static Inspector* dnp3_ctor(Module* m)
+{
+    Dnp3Module* mod = (Dnp3Module*)m;
+    dnp3ProtoConf config;
+    mod->get_data(config);
+    return new Dnp3(config);
+}
+
+static void dnp3_dtor(Inspector* p)
+{
+    delete p;
+}
+
+const InspectApi dnp3_api =
+{
+    {
+        PT_INSPECTOR,
+        sizeof(InspectApi),
+        INSAPI_VERSION,
+        0,
+        API_RESERVED,
+        API_OPTIONS,
+        DNP3_NAME,
+        DNP3_HELP,
+        mod_ctor,
+        mod_dtor
+    },
+    IT_SERVICE,
+    (uint16_t)PktType::PDU | (uint16_t)PktType::UDP,
+    nullptr,  // buffers
+    "dnp3",
+    dnp3_init,
+    nullptr, // pterm
+    nullptr, // tinit
+    nullptr, // tterm
+    dnp3_ctor,
+    dnp3_dtor,
+    nullptr, // ssn
+    nullptr  // reset
+};
+#ifdef BUILDING_SO
+extern const BaseApi* ips_dnp3_func;
+extern const BaseApi* ips_dnp3_ind;
+extern const BaseApi* ips_dnp3_obj;
+extern const BaseApi* ips_dnp3_data;
+
+SO_PUBLIC const BaseApi* snort_plugins[] =
+{
+    &dnp3_api.base,
+    ips_dnp3_func,
+    ips_dnp3_ind,
+    ips_dnp3_obj,
+    ips_dnp3_data,
+    nullptr
+};
+#else
+const BaseApi* sin_dnp3 = &dnp3_api.base;
+#endif
+
diff --git a/src/service_inspectors/dnp3/dnp3.h b/src/service_inspectors/dnp3/dnp3.h
new file mode 100644 (file)
index 0000000..7151bd0
--- /dev/null
@@ -0,0 +1,188 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 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.
+//--------------------------------------------------------------------------
+
+// dnp3.h author Rashmi Pitre <rrp@cisco.com>
+// based on work by Ryan Jordan
+
+#ifndef DNP3_H
+#define DNP3_H
+
+#include "protocols/packet.h"
+#include "profiler/profiler.h"
+#include "framework/counts.h"
+
+#define DNP3_NAME "dnp3"
+#define DNP3_HELP "dnp3 inspection"
+
+#define DNP3_BAD_CRC_STR  "DNP3 Link-Layer Frame contains bad CRC."
+#define DNP3_DROPPED_FRAME_STR "DNP3 Link-Layer Frame was dropped."
+#define DNP3_DROPPED_SEGMENT_STR "DNP3 Transport-Layer Segment was dropped during reassembly."
+#define DNP3_REASSEMBLY_BUFFER_CLEARED_STR \
+    "DNP3 Reassembly Buffer was cleared without reassembling a complete message."
+#define DNP3_RESERVED_ADDRESS_STR "DNP3 Link-Layer Frame uses a reserved address."
+#define DNP3_RESERVED_FUNCTION_STR "DNP3 Application-Layer Fragment uses a reserved function code."
+
+#define DNP3_BAD_CRC                    1
+#define DNP3_DROPPED_FRAME              2
+#define DNP3_DROPPED_SEGMENT            3
+#define DNP3_REASSEMBLY_BUFFER_CLEARED  4
+#define DNP3_RESERVED_ADDRESS           5
+#define DNP3_RESERVED_FUNCTION          6
+
+/* Packet directions */
+#define DNP3_CLIENT 0
+#define DNP3_SERVER 1
+
+/* Session data flags */
+#define DNP3_FUNC_RULE_FIRED    0x0001
+#define DNP3_OBJ_RULE_FIRED     0x0002
+#define DNP3_IND_RULE_FIRED     0x0004
+#define DNP3_DATA_RULE_FIRED    0x0008
+
+/* DNP3 minimum length: start (2 octets) + len (1 octet) */
+#define DNP3_MIN_LEN 3
+#define DNP3_LEN_OFFSET 2
+
+/* Length of the rest of a DNP3 link-layer header: ctrl + src + dest */
+#define DNP3_HEADER_REMAINDER_LEN 5
+
+#define DNP3_BUFFER_SIZE 2048
+
+#ifdef WORDS_BIGENDIAN
+#define DNP3_MIN_RESERVED_ADDR 0xF0FF
+#define DNP3_MAX_RESERVED_ADDR 0xFBFF
+#define DNP3_START_BYTES       0x0564
+#else
+#define DNP3_MIN_RESERVED_ADDR 0xFFF0
+#define DNP3_MAX_RESERVED_ADDR 0xFFFB
+#define DNP3_START_BYTES       0x6405
+#endif
+
+#define DNP3_START_BYTE_1   0x05
+#define DNP3_START_BYTE_2   0x64
+
+#define DNP3_CHUNK_SIZE     16
+#define DNP3_CRC_SIZE        2
+
+/* Minimum length of DNP3 "len" field in order to get a transport header. */
+#define DNP3_MIN_TRANSPORT_LEN 6
+#define DNP3_MAX_TRANSPORT_LEN 250
+
+#define DNP3_TPDU_MAX  250
+#define DNP3_LPDU_MAX  292
+
+#define DNP3_TRANSPORT_FIN(x) (x & 0x80)
+#define DNP3_TRANSPORT_FIR(x) (x & 0x40)
+#define DNP3_TRANSPORT_SEQ(x) (x & 0x3F)
+
+/* Yep, the locations of FIR and FIN are switched at this layer... */
+#define DNP3_APP_FIR(x) (x & 0x80)
+#define DNP3_APP_FIN(x) (x & 0x40)
+#define DNP3_APP_SEQ(x) (x & 0x0F)
+
+#define DNP3_OK true
+#define DNP3_FAIL false
+
+struct Dnp3Stats
+{
+    PegCount total_packets;
+    PegCount udp_packets;
+    PegCount tcp_pdus;
+    PegCount dnp3_link_layer_frames;
+    PegCount dnp3_application_pdus;
+};
+
+/* DNP3 header structures */
+struct dnp3_link_header_t
+{
+    uint16_t start;
+    uint8_t len;
+    uint8_t ctrl;
+    uint16_t dest;
+    uint16_t src;
+};
+
+struct dnp3_transport_header_t
+{
+    uint8_t control;
+};
+
+struct dnp3_app_request_header_t
+{
+    uint8_t control;
+    uint8_t function;
+};
+
+struct dnp3_app_response_header_t
+{
+    uint8_t control;
+    uint8_t function;
+    uint16_t indications;
+};
+
+enum dnp3_reassembly_state_t
+{
+    DNP3_REASSEMBLY_STATE__IDLE = 0,
+    DNP3_REASSEMBLY_STATE__ASSEMBLY,
+    DNP3_REASSEMBLY_STATE__DONE
+};
+
+struct dnp3_reassembly_data_t
+{
+    uint8_t buffer[DNP3_BUFFER_SIZE];
+    uint16_t buflen;
+    dnp3_reassembly_state_t state;
+    uint8_t last_seq;
+};
+
+/* DNP3 session data */
+struct dnp3_session_data_t
+{
+    /* Fields for rule option matching. */
+    uint8_t direction;
+    uint8_t func;
+    uint8_t obj_group;
+    uint8_t obj_var;
+    uint16_t indications;
+    uint16_t flags;
+
+    /* Reassembly stuff */
+    dnp3_reassembly_data_t client_rdata;
+    dnp3_reassembly_data_t server_rdata;
+};
+
+class Dnp3FlowData : public FlowData
+{
+public:
+    Dnp3FlowData();
+
+    static void init()
+    {
+        flow_id = FlowData::get_flow_id();
+    }
+
+public:
+    static unsigned flow_id;
+    dnp3_session_data_t dnp3_session;
+};
+
+extern THREAD_LOCAL Dnp3Stats dnp3_stats;
+extern THREAD_LOCAL ProfileStats dnp3_perf_stats;
+
+#endif
+
diff --git a/src/service_inspectors/dnp3/dnp3_map.cc b/src/service_inspectors/dnp3/dnp3_map.cc
new file mode 100644 (file)
index 0000000..4b7d204
--- /dev/null
@@ -0,0 +1,149 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+
+// dnp3_map.cc author Ryan Jordan
+
+#include "dnp3_map.h"
+#include <stdlib.h>
+#include <string.h>
+
+/* Name/value pair struct */
+struct dnp3_map_t
+{
+    const char* name;
+    uint16_t value;
+};
+
+/* Mapping of name -> function code for "dnp3_func" option. */
+static dnp3_map_t func_map[] =
+{
+    { "confirm", 0 },
+    { "read", 1 },
+    { "write", 2 },
+    { "select", 3 },
+    { "operate", 4 },
+    { "direct_operate", 5 },
+    { "direct_operate_nr", 6 },
+    { "immed_freeze", 7 },
+    { "immed_freeze_nr", 8 },
+    { "freeze_clear", 9 },
+    { "freeze_clear_nr", 10 },
+    { "freeze_at_time", 11 },
+    { "freeze_at_time_nr", 12 },
+    { "cold_restart", 13 },
+    { "warm_restart", 14 },
+    { "initialize_data", 15 },
+    { "initialize_appl", 16 },
+    { "start_appl", 17 },
+    { "stop_appl", 18 },
+    { "save_config", 19 },
+    { "enable_unsolicited", 20 },
+    { "disable_unsolicited", 21 },
+    { "assign_class", 22 },
+    { "delay_measure", 23 },
+    { "record_current_time", 24 },
+    { "open_file", 25 },
+    { "close_file", 26 },
+    { "delete_file", 27 },
+    { "get_file_info", 28 },
+    { "authenticate_file", 29 },
+    { "abort_file", 30 },
+    { "activate_config", 31 },
+    { "authenticate_req", 32 },
+    { "authenticate_err", 33 },
+    { "response", 129 },
+    { "unsolicited_response", 130 },
+    { "authenticate_resp", 131 }
+};
+
+/* Mapping of name -> indication bit for "dnp3_ind" option. */
+static dnp3_map_t indication_map[] =
+{
+    /* The order is strange, but this is the order in which the spec
+       lists them. */
+    { "all_stations", 0x0100 },
+    { "class_1_events", 0x0200 },
+    { "class_2_events", 0x0400 },
+    { "class_3_events", 0x0800 },
+    { "need_time", 0x1000 },
+    { "local_control", 0x2000 },
+    { "device_trouble", 0x4000 },
+    { "device_restart", 0x8000 },
+    { "no_func_code_support", 0x0001 },
+    { "object_unknown", 0x0002 },
+    { "parameter_error", 0x0004 },
+    { "event_buffer_overflow", 0x0008 },
+    { "already_executing", 0x0010 },
+    { "config_corrupt", 0x0020 },
+    { "reserved_2", 0x0040 },
+    { "reserved_1", 0x0080 },
+};
+
+bool dnp3_func_is_defined(uint16_t code)
+{
+    size_t num_funcs = sizeof(func_map) / sizeof(func_map[0]);
+    size_t i;
+    int func_is_defined = false;
+
+    /* Check to see if code is higher than all codes in func map */
+    if (code > func_map[num_funcs-1].value)
+        return func_is_defined;
+
+    for (i = 0; i < num_funcs-1; i++)
+    {
+        /* This short-circuit check assumes that the function map remains
+           in-order. */
+        if (code <= func_map[i].value)
+            break;
+    }
+
+    if (code == func_map[i].value)
+        func_is_defined = true;
+
+    return func_is_defined;
+}
+
+int dnp3_func_str_to_code(const char* name)
+{
+    size_t num_funcs = sizeof(func_map) / sizeof(func_map[0]);
+    size_t i;
+
+    for (i = 0; i < num_funcs; i++)
+    {
+        if (strcmp(name, func_map[i].name) == 0)
+            return func_map[i].value;
+    }
+
+    return -1;
+}
+
+int dnp3_ind_str_to_code(const char* name)
+{
+    size_t num_indications = sizeof(indication_map) / sizeof(indication_map[0]);
+    size_t i;
+
+    for (i = 0; i < num_indications; i++)
+    {
+        if (strcmp(name, indication_map[i].name) == 0)
+            return indication_map[i].value;
+    }
+
+    return -1;
+}
+
diff --git a/src/service_inspectors/dnp3/dnp3_map.h b/src/service_inspectors/dnp3/dnp3_map.h
new file mode 100644 (file)
index 0000000..6b4c0d3
--- /dev/null
@@ -0,0 +1,48 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 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.
+//--------------------------------------------------------------------------
+
+// dnp3_map.h author Ryan Jordan
+
+#ifndef DNP3_MAP__H
+#define DNP3_MAP__H
+
+#include <stdint.h>
+
+/* Check if "code" is in the function map.
+ *
+ * Returns: true on success, false on failure.
+ */
+bool dnp3_func_is_defined(uint16_t code);
+
+/* Return the DNP3 function code corresponding to "name".
+ *
+ * Returns: integer
+ *          -1 on failure
+ */
+int dnp3_func_str_to_code(const char* name);
+
+/* Return the DNP3 indication code corresponding to "name".
+ *
+ * Returns: integer
+ *          -1 on failure
+ */
+int dnp3_ind_str_to_code(const char* name);
+
+#endif
+
diff --git a/src/service_inspectors/dnp3/dnp3_module.cc b/src/service_inspectors/dnp3/dnp3_module.cc
new file mode 100644 (file)
index 0000000..5d54593
--- /dev/null
@@ -0,0 +1,104 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+
+// dnp3_module.cc author Rashmi Pitre <rrp@cisco.com>
+
+#include "dnp3_module.h"
+#include "dnp3.h"
+#include "main/snort_config.h"
+
+using namespace std;
+
+static const Parameter s_params[] =
+{
+    { "check_crc", Parameter::PT_BOOL, nullptr, "false",
+      "validate checksums in DNP3 link layer frames" },
+
+    { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+static const RuleMap dnp3_rules[] =
+{
+    { DNP3_BAD_CRC, DNP3_BAD_CRC_STR },
+    { DNP3_DROPPED_FRAME, DNP3_DROPPED_FRAME_STR },
+    { DNP3_DROPPED_SEGMENT, DNP3_DROPPED_SEGMENT_STR },
+    { DNP3_REASSEMBLY_BUFFER_CLEARED, DNP3_REASSEMBLY_BUFFER_CLEARED_STR },
+    { DNP3_RESERVED_ADDRESS, DNP3_RESERVED_ADDRESS_STR },
+    { DNP3_RESERVED_FUNCTION, DNP3_RESERVED_FUNCTION_STR },
+    { 0, nullptr }
+};
+
+static const PegInfo dnp3_pegs[] =
+{
+    { "total packets", "total packets" },
+    { "udp packets", "total udp packets" },
+    { "tcp pdus", "total tcp pdus" },
+    { "dnp3 link layer frames", "total dnp3 link layer frames" },
+    { "dnp3 application pdus", "total dnp3 application pdus" },
+
+    { nullptr, nullptr }
+};
+
+Dnp3Module::Dnp3Module() : Module(DNP3_NAME, DNP3_HELP, s_params)
+{
+}
+
+const RuleMap* Dnp3Module::get_rules() const
+{
+    return dnp3_rules;
+}
+
+const PegInfo* Dnp3Module::get_pegs() const
+{
+    return dnp3_pegs;
+}
+
+PegCount* Dnp3Module::get_counts() const
+{
+    return (PegCount*)&dnp3_stats;
+}
+
+ProfileStats* Dnp3Module::get_profile() const
+{
+    return &dnp3_perf_stats;
+}
+
+bool Dnp3Module::set(const char*, Value& v, SnortConfig*)
+{
+    if ( v.is("check_crc") )
+        config.check_crc = v.get_bool();
+
+    else
+        return false;
+
+    return true;
+}
+
+void Dnp3Module::get_data(dnp3ProtoConf& dnp3_config)
+{
+    dnp3_config.check_crc = config.check_crc;
+}
+
+void print_dnp3_conf(dnp3ProtoConf& config)
+{
+    LogMessage("DNP3 config: \n");
+    LogMessage("    Check CRC: %s\n",
+        config.check_crc ?
+        "ENABLED" : "DISABLED");
+}
+
diff --git a/src/service_inspectors/dnp3/dnp3_module.h b/src/service_inspectors/dnp3/dnp3_module.h
new file mode 100644 (file)
index 0000000..367177b
--- /dev/null
@@ -0,0 +1,60 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+//
+// dnp3_module.h author Rashmi Pitre <rrp@cisco.com>
+
+#ifndef DNP3_MODULE_H
+#define DNP3_MODULE_H
+
+#include "framework/module.h"
+
+#define GID_DNP3  145
+
+struct SnortConfig;
+struct dnp3ProtoConf
+{
+    bool check_crc;
+};
+
+class Dnp3Module : public Module
+{
+public:
+    Dnp3Module();
+
+    bool set(const char*, Value&, SnortConfig*) override;
+
+    unsigned get_gid() const override
+    {
+        return GID_DNP3;
+    }
+
+    const RuleMap* get_rules() const override;
+    const PegInfo* get_pegs() const override;
+    PegCount* get_counts() const override;
+    ProfileStats* get_profile() const override;
+
+    void get_data(dnp3ProtoConf&);
+
+private:
+    dnp3ProtoConf config;
+};
+
+void print_dnp3_conf(dnp3ProtoConf& config);
+
+#endif
+
diff --git a/src/service_inspectors/dnp3/dnp3_paf.cc b/src/service_inspectors/dnp3/dnp3_paf.cc
new file mode 100644 (file)
index 0000000..4705357
--- /dev/null
@@ -0,0 +1,113 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 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.
+//--------------------------------------------------------------------------
+
+// dnp3_paf.cc author Rashmi Pitre <rrp@cisco.com>
+// based on work by Ryan Jordan
+
+#include "dnp3_paf.h"
+#include "dnp3.h"
+
+/* Function: dnp3_paf()
+
+   Purpose: DNP3 PAF callback.
+            Statefully inspects DNP3 traffic from the start of a session,
+            Reads up until the length octet is found, then sets a flush point.
+            The flushed PDU is a DNP3 Link Layer frame, the inspector
+            handles reassembly of frames into Application Layer messages.
+
+*/
+
+static StreamSplitter::Status dnp3_paf(dnp3_paf_data* pafdata, const uint8_t* data,
+    uint32_t len, uint32_t* fp)
+{
+    //dnp3_paf_data_t *pafdata = user;
+    uint32_t bytes_processed = 0;
+
+    /* Process this packet 1 byte at a time */
+    while (bytes_processed < len)
+    {
+        uint16_t user_data = 0;
+        uint16_t num_crcs = 0;
+
+        switch (pafdata->paf_state)
+        {
+        /* Check the Start bytes. If they are not \x05\x64, don't advance state.
+           Could be out of sync, junk data between frames, mid-stream pickup, etc. */
+        case DNP3_PAF_STATE__START_1:
+            if (((uint8_t)*(data + bytes_processed)) == DNP3_START_BYTE_1)
+                pafdata->paf_state = (dnp3_paf_state)(((int)pafdata->paf_state) + 1);
+            else
+                return StreamSplitter::ABORT;
+            break;
+
+        case DNP3_PAF_STATE__START_2:
+            if (((uint8_t)*(data + bytes_processed)) == DNP3_START_BYTE_2)
+                pafdata->paf_state = (dnp3_paf_state)(((int)pafdata->paf_state) + 1);
+            else
+                return StreamSplitter::ABORT;
+            break;
+
+        /* Read the length. */
+        case DNP3_PAF_STATE__LENGTH:
+            pafdata->dnp3_length = (uint8_t)*(data + bytes_processed);
+
+            /* DNP3 length only counts non-CRC octets following the
+               length field itself. Each CRC is two octets. One follows
+               the headers, a CRC is inserted for every 16 octets of user data,
+               plus a CRC for the last bit of user data (< 16 octets) */
+
+            if (pafdata->dnp3_length < DNP3_HEADER_REMAINDER_LEN)
+            {
+                return StreamSplitter::ABORT;
+            }
+
+            user_data = pafdata->dnp3_length - DNP3_HEADER_REMAINDER_LEN;
+            num_crcs = 1 + (user_data/DNP3_CHUNK_SIZE) + (user_data % DNP3_CHUNK_SIZE ? 1 : 0);
+            pafdata->real_length = pafdata->dnp3_length + (DNP3_CRC_SIZE*num_crcs);
+
+            pafdata->paf_state = (dnp3_paf_state)(((int)pafdata->paf_state) + 1);
+            break;
+
+        /* Set the flush point. */
+        case DNP3_PAF_STATE__SET_FLUSH:
+            *fp = pafdata->real_length + bytes_processed;
+            pafdata->paf_state = DNP3_PAF_STATE__START_1;
+            return StreamSplitter::FLUSH;
+        }
+
+        bytes_processed++;
+    }
+
+    return StreamSplitter::SEARCH;
+}
+
+Dnp3Splitter::Dnp3Splitter(bool c2s) : StreamSplitter(c2s)
+{
+    state.paf_state = DNP3_PAF_STATE__START_1;
+    state.dnp3_length = 0;
+    state.real_length = 0;
+}
+
+StreamSplitter::Status Dnp3Splitter::scan(
+    Flow*, const uint8_t* data, uint32_t len,
+    uint32_t, uint32_t* fp)
+{
+    dnp3_paf_data* pfdata = &state;
+    return dnp3_paf(pfdata, data, len, fp);
+}
+
diff --git a/src/service_inspectors/dnp3/dnp3_paf.h b/src/service_inspectors/dnp3/dnp3_paf.h
new file mode 100644 (file)
index 0000000..ed6d393
--- /dev/null
@@ -0,0 +1,64 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+
+// dnp3_paf.h author Rashmi Pitre <rrp@cisco.com>
+// based on work by Ryan Jordan
+
+#ifndef DNP3_PAF_H
+#define DNP3_PAF_H
+
+// Protocol aware flushing for DNP3.
+
+#include "main/snort_types.h"
+#include "stream/stream_api.h"
+#include "stream/stream_splitter.h"
+
+enum dnp3_paf_state
+{
+    DNP3_PAF_STATE__START_1 = 0,
+    DNP3_PAF_STATE__START_2,
+    DNP3_PAF_STATE__LENGTH,
+    DNP3_PAF_STATE__SET_FLUSH
+};
+
+struct dnp3_paf_data
+{
+    dnp3_paf_state paf_state;
+    uint8_t dnp3_length;
+    uint16_t real_length;
+};
+
+class Dnp3Splitter : public StreamSplitter
+{
+public:
+    Dnp3Splitter(bool c2s);
+
+    Status scan(Flow*, const uint8_t* data, uint32_t len,
+        uint32_t flags, uint32_t* fp) override;
+
+    bool is_paf() override
+    {
+        return true;
+    }
+
+public:
+    dnp3_paf_data state;
+};
+
+#endif
+
diff --git a/src/service_inspectors/dnp3/dnp3_reassembly.cc b/src/service_inspectors/dnp3/dnp3_reassembly.cc
new file mode 100644 (file)
index 0000000..c01d119
--- /dev/null
@@ -0,0 +1,426 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+
+// dnp3_reassembly.cc author Ryan Jordan
+// modifications for snort3 by Rashmi Pitre <rrp@cisco.com>
+
+#include "dnp3_reassembly.h"
+#include "events/event_queue.h"
+#include "detection/detect.h"
+#include "dnp3_map.h"
+#include "dnp3.h"
+#include "dnp3_module.h"
+
+/* CRC look-up table, for computeCRC() below */
+static uint16_t crcLookUpTable[256] =
+{
+    0x0000, 0x365E, 0x6CBC, 0x5AE2, 0xD978, 0xEF26, 0xB5C4, 0x839A,
+    0xFF89, 0xC9D7, 0x9335, 0xA56B, 0x26F1, 0x10AF, 0x4A4D, 0x7C13,
+    0xB26B, 0x8435, 0xDED7, 0xE889, 0x6B13, 0x5D4D, 0x07AF, 0x31F1,
+    0x4DE2, 0x7BBC, 0x215E, 0x1700, 0x949A, 0xA2C4, 0xF826, 0xCE78,
+    0x29AF, 0x1FF1, 0x4513, 0x734D, 0xF0D7, 0xC689, 0x9C6B, 0xAA35,
+    0xD626, 0xE078, 0xBA9A, 0x8CC4, 0x0F5E, 0x3900, 0x63E2, 0x55BC,
+    0x9BC4, 0xAD9A, 0xF778, 0xC126, 0x42BC, 0x74E2, 0x2E00, 0x185E,
+    0x644D, 0x5213, 0x08F1, 0x3EAF, 0xBD35, 0x8B6B, 0xD189, 0xE7D7,
+    0x535E, 0x6500, 0x3FE2, 0x09BC, 0x8A26, 0xBC78, 0xE69A, 0xD0C4,
+    0xACD7, 0x9A89, 0xC06B, 0xF635, 0x75AF, 0x43F1, 0x1913, 0x2F4D,
+    0xE135, 0xD76B, 0x8D89, 0xBBD7, 0x384D, 0x0E13, 0x54F1, 0x62AF,
+    0x1EBC, 0x28E2, 0x7200, 0x445E, 0xC7C4, 0xF19A, 0xAB78, 0x9D26,
+    0x7AF1, 0x4CAF, 0x164D, 0x2013, 0xA389, 0x95D7, 0xCF35, 0xF96B,
+    0x8578, 0xB326, 0xE9C4, 0xDF9A, 0x5C00, 0x6A5E, 0x30BC, 0x06E2,
+    0xC89A, 0xFEC4, 0xA426, 0x9278, 0x11E2, 0x27BC, 0x7D5E, 0x4B00,
+    0x3713, 0x014D, 0x5BAF, 0x6DF1, 0xEE6B, 0xD835, 0x82D7, 0xB489,
+    0xA6BC, 0x90E2, 0xCA00, 0xFC5E, 0x7FC4, 0x499A, 0x1378, 0x2526,
+    0x5935, 0x6F6B, 0x3589, 0x03D7, 0x804D, 0xB613, 0xECF1, 0xDAAF,
+    0x14D7, 0x2289, 0x786B, 0x4E35, 0xCDAF, 0xFBF1, 0xA113, 0x974D,
+    0xEB5E, 0xDD00, 0x87E2, 0xB1BC, 0x3226, 0x0478, 0x5E9A, 0x68C4,
+    0x8F13, 0xB94D, 0xE3AF, 0xD5F1, 0x566B, 0x6035, 0x3AD7, 0x0C89,
+    0x709A, 0x46C4, 0x1C26, 0x2A78, 0xA9E2, 0x9FBC, 0xC55E, 0xF300,
+    0x3D78, 0x0B26, 0x51C4, 0x679A, 0xE400, 0xD25E, 0x88BC, 0xBEE2,
+    0xC2F1, 0xF4AF, 0xAE4D, 0x9813, 0x1B89, 0x2DD7, 0x7735, 0x416B,
+    0xF5E2, 0xC3BC, 0x995E, 0xAF00, 0x2C9A, 0x1AC4, 0x4026, 0x7678,
+    0x0A6B, 0x3C35, 0x66D7, 0x5089, 0xD313, 0xE54D, 0xBFAF, 0x89F1,
+    0x4789, 0x71D7, 0x2B35, 0x1D6B, 0x9EF1, 0xA8AF, 0xF24D, 0xC413,
+    0xB800, 0x8E5E, 0xD4BC, 0xE2E2, 0x6178, 0x5726, 0x0DC4, 0x3B9A,
+    0xDC4D, 0xEA13, 0xB0F1, 0x86AF, 0x0535, 0x336B, 0x6989, 0x5FD7,
+    0x23C4, 0x159A, 0x4F78, 0x7926, 0xFABC, 0xCCE2, 0x9600, 0xA05E,
+    0x6E26, 0x5878, 0x029A, 0x34C4, 0xB75E, 0x8100, 0xDBE2, 0xEDBC,
+    0x91AF, 0xA7F1, 0xFD13, 0xCB4D, 0x48D7, 0x7E89, 0x246B, 0x1235
+};
+
+/* Append a DNP3 Transport segment to the reassembly buffer.
+
+   Returns:
+    true:    Segment queued successfully.
+    false:  Data copy failed. Segment did not fit in reassembly buffer.
+*/
+static bool dnp3_queue_segment(dnp3_reassembly_data_t* rdata, char* buf, uint16_t buflen)
+{
+    if (rdata == NULL || buf == NULL)
+        return false;
+
+    /* We checked for DNP3_MAX_TRANSPORT_LEN earlier. */
+    if (buflen + rdata->buflen > DNP3_BUFFER_SIZE)
+        return false;
+
+    memcpy((rdata->buffer + rdata->buflen), buf, (size_t)buflen);
+
+    rdata->buflen += buflen;
+    return true;
+}
+
+/* Reset a DNP3 reassembly buffer */
+void dnp3_reassembly_reset(dnp3_reassembly_data_t* rdata)
+{
+    rdata->buflen = 0;
+    rdata->state = DNP3_REASSEMBLY_STATE__IDLE;
+    rdata->last_seq = 0;
+}
+
+/* DNP3 Transport-Layer reassembly state machine.
+
+   Arguments:
+     rdata:     DNP3 reassembly state object.
+     buf:       DNP3 Transport Layer segment
+     buflen:    Length of Transport Layer segment.
+
+   Returns:
+    false:     Segment was discarded.
+    true:       Segment was queued.
+*/
+static bool dnp3_reassemble_transport(dnp3_reassembly_data_t* rdata, char* buf, uint16_t buflen)
+{
+    dnp3_transport_header_t* trans_header;
+
+    if (rdata == NULL || buf == NULL || buflen < sizeof(dnp3_transport_header_t) ||
+        (buflen > DNP3_MAX_TRANSPORT_LEN))
+    {
+        return false;
+    }
+
+    /* Take the first byte as a transport header, cut it off of the buffer. */
+    trans_header = (dnp3_transport_header_t*)buf;
+    buf += sizeof(dnp3_transport_header_t);
+    buflen -= sizeof(dnp3_transport_header_t);
+
+    /* If the previously-existing state was DONE, we need to reset it back
+       to IDLE. */
+    if (rdata->state == DNP3_REASSEMBLY_STATE__DONE)
+        dnp3_reassembly_reset(rdata);
+
+    switch (rdata->state)
+    {
+    case DNP3_REASSEMBLY_STATE__IDLE:
+        /* Discard any non-first segment. */
+        if ( DNP3_TRANSPORT_FIR(trans_header->control) == 0 )
+            return false;
+
+        /* Reset the buffer & queue the first segment */
+        dnp3_reassembly_reset(rdata);
+        dnp3_queue_segment(rdata, buf, buflen);
+        rdata->last_seq = DNP3_TRANSPORT_SEQ(trans_header->control);
+
+        if ( DNP3_TRANSPORT_FIN(trans_header->control) )
+            rdata->state = DNP3_REASSEMBLY_STATE__DONE;
+        else
+            rdata->state = DNP3_REASSEMBLY_STATE__ASSEMBLY;
+
+        break;
+
+    case DNP3_REASSEMBLY_STATE__ASSEMBLY:
+        /* Reset if the FIR flag is set. */
+        if ( DNP3_TRANSPORT_FIR(trans_header->control) )
+        {
+            dnp3_reassembly_reset(rdata);
+            dnp3_queue_segment(rdata, buf, buflen);
+            rdata->last_seq = DNP3_TRANSPORT_SEQ(trans_header->control);
+
+            if (DNP3_TRANSPORT_FIN(trans_header->control))
+                rdata->state = DNP3_REASSEMBLY_STATE__DONE;
+
+            /* Raise an alert so it's clear the buffer was reset.
+               Could signify device trouble. */
+            SnortEventqAdd(GID_DNP3, DNP3_REASSEMBLY_BUFFER_CLEARED);
+        }
+        else
+        {
+            /* Same seq but FIN is set. Discard segment, BUT finish reassembly. */
+            if ((DNP3_TRANSPORT_SEQ(trans_header->control) == rdata->last_seq) &&
+                (DNP3_TRANSPORT_FIN(trans_header->control)))
+            {
+                SnortEventqAdd(GID_DNP3, DNP3_DROPPED_SEGMENT);
+                rdata->state = DNP3_REASSEMBLY_STATE__DONE;
+                return false;
+            }
+
+            /* Discard any other segments without the correct sequence. */
+            if (DNP3_TRANSPORT_SEQ(trans_header->control) !=
+                ((rdata->last_seq + 1) % 0x40 ))
+            {
+                SnortEventqAdd(GID_DNP3, DNP3_DROPPED_SEGMENT);
+                return false;
+            }
+
+            /* Otherwise, queue it up! */
+            dnp3_queue_segment(rdata, buf, buflen);
+            rdata->last_seq = DNP3_TRANSPORT_SEQ(trans_header->control);
+
+            if (DNP3_TRANSPORT_FIN(trans_header->control))
+                rdata->state = DNP3_REASSEMBLY_STATE__DONE;
+            else
+                rdata->state = DNP3_REASSEMBLY_STATE__ASSEMBLY;
+        }
+
+        break;
+
+    case DNP3_REASSEMBLY_STATE__DONE:
+        break;
+    }
+
+    return true;
+}
+
+/* Check for reserved application-level function codes. */
+static void dnp3_check_reserved_function(dnp3_session_data_t* session)
+{
+    if ( !(dnp3_func_is_defined( (uint16_t)session->func)) )
+    {
+        SnortEventqAdd(GID_DNP3, DNP3_RESERVED_FUNCTION);
+    }
+}
+
+/* Decode a DNP3 Application-layer Fragment, fill out the relevant session data
+   for rule option evaluation. */
+static bool dnp3_process_application(dnp3_session_data_t* session)
+{
+    dnp3_reassembly_data_t* rdata = NULL;
+
+    if (session == NULL)
+        return false;
+
+    /* Master and Outstation use slightly different Application-layer headers.
+       Only the outstation sends Internal Indications. */
+    if (session->direction == DNP3_CLIENT)
+    {
+        dnp3_app_request_header_t* request = NULL;
+        rdata = &(session->client_rdata);
+
+        if (rdata->buflen < sizeof(dnp3_app_request_header_t))
+            return false;
+
+        request = (dnp3_app_request_header_t*)(rdata->buffer);
+
+        session->func = request->function;
+    }
+    else if (session->direction == DNP3_SERVER)
+    {
+        dnp3_app_response_header_t* response = NULL;
+        rdata = &(session->server_rdata);
+
+        if (rdata->buflen < sizeof(dnp3_app_response_header_t))
+            return false;
+
+        response = (dnp3_app_response_header_t*)(rdata->buffer);
+
+        session->func = response->function;
+        session->indications = ntohs(response->indications);
+    }
+
+    dnp3_check_reserved_function(session);
+
+    return true;
+}
+
+/* Check a CRC in a single block.
+   This code is mostly lifted from the example in the DNP3 spec. */
+
+static inline void compute_crc(unsigned char data, uint16_t* crcAccum)
+{
+    *crcAccum =
+        (*crcAccum >> 8) ^ crcLookUpTable[(*crcAccum ^ data) & 0xFF];
+}
+
+static bool dnp3_check_crc(unsigned char* buf, uint16_t buflen)
+{
+    uint16_t idx;
+    uint16_t crc = 0;
+
+    /* Compute check code for data in received block */
+    for (idx = 0; idx < buflen-2; idx++)
+        compute_crc(buf[idx], &crc);
+    crc = ~crc; /* Invert */
+
+    /* Check CRC at end of block */
+    if (buf[idx++] == (unsigned char)crc &&
+        buf[idx] == (unsigned char)(crc >> 8))
+        return true;
+    else
+        return false;
+}
+
+/* Check CRCs in a Link-Layer Frame, then fill a buffer containing just the user data  */
+static bool dnp3_check_remove_crc(dnp3ProtoConf& config, uint8_t* pdu_start,
+    uint16_t pdu_length, char* buf, uint16_t* buflen)
+{
+    char* cursor;
+    uint16_t bytes_left;
+    uint16_t curlen = 0;
+
+    /* Check Header CRC */
+    if ((config.check_crc) &&
+        (dnp3_check_crc((unsigned char*)pdu_start, sizeof(dnp3_link_header_t)+2) == false))
+    {
+        SnortEventqAdd(GID_DNP3, DNP3_BAD_CRC);
+        return false;
+    }
+
+    cursor = (char*)pdu_start + sizeof(dnp3_link_header_t) + 2;
+    bytes_left = pdu_length - sizeof(dnp3_link_header_t) - 2;
+
+    /* Process whole 16-byte chunks (plus 2-byte CRC) */
+    while ( (bytes_left > (DNP3_CHUNK_SIZE + DNP3_CRC_SIZE)) &&
+        (curlen + DNP3_CHUNK_SIZE < *buflen) )
+    {
+        if ((config.check_crc) &&
+            (dnp3_check_crc((unsigned char*)cursor, (DNP3_CHUNK_SIZE+DNP3_CRC_SIZE)) == false))
+        {
+            SnortEventqAdd(GID_DNP3, DNP3_RESERVED_ADDRESS);
+            return false;
+        }
+
+        memcpy((buf + curlen), cursor, DNP3_CHUNK_SIZE);
+        curlen += DNP3_CHUNK_SIZE;
+        cursor += (DNP3_CHUNK_SIZE+DNP3_CRC_SIZE);
+        bytes_left -= (DNP3_CHUNK_SIZE+DNP3_CRC_SIZE);
+    }
+    /* Process leftover chunk, under 16 bytes */
+    if ( (bytes_left > DNP3_CRC_SIZE) &&
+        (curlen + bytes_left < *buflen) )
+    {
+        if ((config.check_crc) && (dnp3_check_crc((unsigned char*)cursor, bytes_left) == false))
+        {
+            SnortEventqAdd(GID_DNP3, DNP3_RESERVED_ADDRESS);
+            return false;
+        }
+
+        memcpy((buf + curlen), cursor, (bytes_left - DNP3_CRC_SIZE));
+        curlen += (bytes_left - DNP3_CRC_SIZE);
+        cursor += bytes_left;
+        bytes_left = 0;
+    }
+
+    *buflen = curlen;
+    return true;
+}
+
+static bool dnp3_check_reserved_addrs(dnp3_link_header_t* link)
+{
+    int bad_addr = 0;
+
+    if ((link->src >= DNP3_MIN_RESERVED_ADDR) && (link->src <= DNP3_MAX_RESERVED_ADDR))
+        bad_addr = 1;
+
+    else if ((link->dest >= DNP3_MIN_RESERVED_ADDR) && (link->dest <= DNP3_MAX_RESERVED_ADDR))
+        bad_addr = 1;
+
+    if (bad_addr)
+    {
+        SnortEventqAdd(GID_DNP3, DNP3_RESERVED_ADDRESS);
+        return false;
+    }
+
+    return true;
+}
+
+/* Main DNP3 Reassembly function. */
+bool dnp3_full_reassembly(dnp3ProtoConf& config, dnp3_session_data_t* session, Packet* packet,
+    uint8_t* pdu_start, uint16_t pdu_length)
+{
+    char buf[DNP3_TPDU_MAX];
+    uint16_t buflen = sizeof(buf);
+    dnp3_link_header_t* link;
+    dnp3_reassembly_data_t* rdata;
+
+    if (pdu_length < (sizeof(dnp3_link_header_t) + sizeof(dnp3_transport_header_t) + 2))
+        return false;
+
+    if ( pdu_length > DNP3_LPDU_MAX )
+        // this means PAF aborted - not DNP3
+        return false;
+
+    ++dnp3_stats.dnp3_link_layer_frames;
+
+    /* Step 1: Decode header and skip to data */
+    link = (dnp3_link_header_t*)pdu_start;
+
+    if (link->len < DNP3_MIN_TRANSPORT_LEN)
+    {
+        SnortEventqAdd(GID_DNP3, DNP3_DROPPED_FRAME);
+        return false;
+    }
+
+    /* Check reserved addresses */
+    if ( dnp3_check_reserved_addrs(link) == false )
+        return false;
+
+    /* FIXIT: NEED TO TRACK SEPARATE DNP3 SESSIONS OVER SINGLE TCP SESSION */
+
+    /* Step 2: Remove CRCs */
+    if ( dnp3_check_remove_crc(config, pdu_start, pdu_length, buf, &buflen) == false )
+        return false;
+
+    /* Step 3: Queue user data in frame for Transport-Layer reassembly */
+    if (session->direction == DNP3_CLIENT)
+        rdata = &(session->client_rdata);
+    else
+        rdata = &(session->server_rdata);
+
+    if (dnp3_reassemble_transport(rdata, buf, buflen) == false)
+        return false;
+
+    /* Step 4: Decode Application-Layer  */
+    if (rdata->state == DNP3_REASSEMBLY_STATE__DONE)
+    {
+        ++dnp3_stats.dnp3_application_pdus;
+
+        bool ret = dnp3_process_application(session);
+
+        /* To support multiple PDUs in UDP, we're going to call Detect()
+           on each individual PDU. The AltDecode buffer will be provided
+           via the get_buf() inspector api */
+        if ((ret == true) && (packet->is_udp()))
+        {
+            ProfileStats dnp3_detect_perf_stats;
+            PERF_PROFILE_BLOCK(dnp3_detect_perf_stats)
+            {
+                snort_detect(packet);
+            }
+            dnp3_perf_stats.elapsed -= dnp3_detect_perf_stats.elapsed;
+
+            /* Since detection was done, reset reassembly state to avoid double alerts
+               on the last PDU */
+            dnp3_reassembly_reset(rdata);
+        }
+        else
+            return ret;
+    }
+
+    return true;
+}
+
diff --git a/src/service_inspectors/dnp3/dnp3_reassembly.h b/src/service_inspectors/dnp3/dnp3_reassembly.h
new file mode 100644 (file)
index 0000000..3c2c468
--- /dev/null
@@ -0,0 +1,33 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+
+// dnp3_reassembly.h author Rashmi Pitre <rrp@cisco.com>
+// based on work by Ryan Jordan
+
+#ifndef DNP3_REASSEMBLY_H
+#define DNP3_REASSEMBLY_H
+
+#include "dnp3_module.h"
+#include "dnp3.h"
+
+bool dnp3_full_reassembly(dnp3ProtoConf& config, dnp3_session_data_t* session, Packet* p,
+    uint8_t* pdu_start, uint16_t pdu_length);
+void dnp3_reassembly_reset(dnp3_reassembly_data_t* rdata);
+
+#endif
+
diff --git a/src/service_inspectors/dnp3/ips_dnp3_data.cc b/src/service_inspectors/dnp3/ips_dnp3_data.cc
new file mode 100644 (file)
index 0000000..0f5add0
--- /dev/null
@@ -0,0 +1,164 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+
+// ips_dnp3_data.cc author Maya Dagon <mdagon@cisco.com>
+// based on work by Ryan Jordan
+
+#include "detection/detection_defines.h"
+#include "framework/cursor.h"
+#include "framework/ips_option.h"
+#include "framework/module.h"
+#include "hash/sfhashfcn.h"
+#include "protocols/packet.h"
+#include "profiler/profiler.h"
+
+#include "dnp3.h"
+
+#define s_name "dnp3_data"
+#define s_help \
+    "sets the cursor to dnp3 data"
+
+//-------------------------------------------------------------------------
+// DNP3 data rule option
+//-------------------------------------------------------------------------
+
+static THREAD_LOCAL ProfileStats dnp3_data_perf_stats;
+
+class Dnp3DataOption : public IpsOption
+{
+public:
+    Dnp3DataOption() : IpsOption(s_name) { }
+
+    uint32_t hash() const override;
+    bool operator==(const IpsOption&) const override;
+
+    int eval(Cursor&, Packet*) override;
+};
+
+uint32_t Dnp3DataOption::hash() const
+{
+    uint32_t a = 0, b = 0, c = 0;
+
+    mix_str(a, b, c, get_name());
+    finalize(a,b,c);
+
+    return c;
+}
+
+bool Dnp3DataOption::operator==(const IpsOption& ips) const
+{
+    return !strcmp(get_name(), ips.get_name());
+}
+
+int Dnp3DataOption::eval(Cursor& c, Packet* p)
+{
+    PERF_PROFILE(dnp3_data_perf_stats);
+
+    if ((p->has_tcp_data() && !p->is_full_pdu()) || !p->flow || !p->dsize)
+        return DETECTION_OPTION_NO_MATCH;
+
+    Dnp3FlowData* fd = (Dnp3FlowData*)p->flow->get_application_data(
+        Dnp3FlowData::flow_id);
+
+    if (!fd)
+        return DETECTION_OPTION_NO_MATCH;
+
+    dnp3_session_data_t* dnp3_session = &fd->dnp3_session;
+    dnp3_reassembly_data_t* rdata;
+
+    if (dnp3_session->direction == DNP3_CLIENT)
+        rdata = &(dnp3_session->client_rdata);
+    else
+        rdata = &(dnp3_session->server_rdata);
+
+    /* Only evaluate rules against complete Application-layer fragments */
+    if (rdata->state != DNP3_REASSEMBLY_STATE__DONE)
+        return DETECTION_OPTION_NO_MATCH;
+
+    c.set(s_name,(uint8_t*)rdata->buffer, rdata->buflen);
+
+    return DETECTION_OPTION_MATCH;
+}
+
+//-------------------------------------------------------------------------
+// module
+//-------------------------------------------------------------------------
+
+class Dnp3DataModule : public Module
+{
+public:
+    Dnp3DataModule() : Module(s_name, s_help) { }
+    ProfileStats* get_profile() const override;
+};
+
+ProfileStats* Dnp3DataModule::get_profile() const
+{
+    return &dnp3_data_perf_stats;
+}
+
+//-------------------------------------------------------------------------
+// api
+//-------------------------------------------------------------------------
+
+static Module* dnp3_data_mod_ctor()
+{
+    return new Dnp3DataModule;
+}
+
+static void dnp3_data_mod_dtor(Module* m)
+{
+    delete m;
+}
+
+static IpsOption* dnp3_data_ctor(Module*, OptTreeNode*)
+{
+    return new Dnp3DataOption;
+}
+
+static void dnp3_data_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,
+        dnp3_data_mod_ctor,
+        dnp3_data_mod_dtor
+    },
+    OPT_TYPE_DETECTION,
+    0, PROTO_BIT__TCP | PROTO_BIT__UDP,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    dnp3_data_ctor,
+    dnp3_data_dtor,
+    nullptr
+};
+
+const BaseApi* ips_dnp3_data = &ips_api.base;
+
diff --git a/src/service_inspectors/dnp3/ips_dnp3_func.cc b/src/service_inspectors/dnp3/ips_dnp3_func.cc
new file mode 100644 (file)
index 0000000..6d60f63
--- /dev/null
@@ -0,0 +1,222 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+
+// ips_dnp3_func.cc author Maya Dagon <mdagon@cisco.com>
+// based on work by Ryan Jordan
+
+#include "framework/ips_option.h"
+#include "framework/module.h"
+#include "framework/parameter.h"
+#include "detection/detect.h"
+#include "detection/detection_defines.h"
+#include "hash/sfhashfcn.h"
+#include "profiler/profiler.h"
+
+#include "dnp3.h"
+#include "dnp3_map.h"
+
+//-------------------------------------------------------------------------
+// DNP3 function code rule options
+//-------------------------------------------------------------------------
+
+#define s_name "dnp3_func"
+#define s_help \
+    "detection option to check dnp3 function code"
+
+static THREAD_LOCAL ProfileStats dnp3_func_perf_stats;
+
+class Dnp3FuncOption : public IpsOption
+{
+public:
+    Dnp3FuncOption(uint16_t v) : IpsOption(s_name)
+    { func = v; }
+
+    uint32_t hash() const override;
+    bool operator==(const IpsOption&) const override;
+    int eval(Cursor&, Packet*) override;
+
+private:
+    uint16_t func;
+};
+
+uint32_t Dnp3FuncOption::hash() const
+{
+    uint32_t a = func, b = 0, c = 0;
+
+    mix_str(a,b,c,get_name());
+    finalize(a,b,c);
+
+    return c;
+}
+
+bool Dnp3FuncOption::operator==(const IpsOption& ips) const
+{
+    if ( strcmp(get_name(), ips.get_name()) )
+        return false;
+
+    const Dnp3FuncOption& rhs = (Dnp3FuncOption&)ips;
+    return (func == rhs.func);
+}
+
+int Dnp3FuncOption::eval(Cursor&, Packet* p)
+{
+    PERF_PROFILE(dnp3_func_perf_stats);
+
+    if ((p->has_tcp_data() && !p->is_full_pdu()) || !p->flow || !p->dsize)
+        return DETECTION_OPTION_NO_MATCH;
+
+    Dnp3FlowData* fd = (Dnp3FlowData*)p->flow->get_application_data(
+        Dnp3FlowData::flow_id);
+
+    if (!fd)
+        return DETECTION_OPTION_NO_MATCH;
+
+    dnp3_session_data_t* dnp3_session = &fd->dnp3_session;
+    dnp3_reassembly_data_t* rdata;
+
+    if (dnp3_session->direction == DNP3_CLIENT)
+        rdata = &(dnp3_session->client_rdata);
+    else
+        rdata = &(dnp3_session->server_rdata);
+
+    /* Only evaluate rules against complete Application-layer fragments */
+    if (rdata->state != DNP3_REASSEMBLY_STATE__DONE)
+        return DETECTION_OPTION_NO_MATCH;
+
+    if (dnp3_session->func == func)
+        return DETECTION_OPTION_MATCH;
+
+    return DETECTION_OPTION_NO_MATCH;
+}
+
+//-------------------------------------------------------------------------
+// dnp3_func module
+//-------------------------------------------------------------------------
+
+static const Parameter s_params[] =
+{
+    { "~", Parameter::PT_STRING, nullptr, nullptr,
+      "match dnp3 function code or name" },
+
+    { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+class Dnp3FuncModule : public Module
+{
+public:
+    Dnp3FuncModule() : Module(s_name, s_help, s_params) { }
+    bool set(const char*, Value&, SnortConfig*) override;
+    ProfileStats* get_profile() const override;
+
+    uint16_t func;
+};
+
+ProfileStats* Dnp3FuncModule::get_profile() const
+{
+    return &dnp3_func_perf_stats;
+}
+
+bool Dnp3FuncModule::set(const char*, Value& v, SnortConfig*)
+{
+    if ( !v.is("~"))
+        return false;
+
+    long n;
+
+    if (v.strtol(n))
+    {
+        if ((n > 255) || (n < 0))
+        {
+            ParseError("dnp3_func requires a "
+                "number beween 0 and 255, or a valid function name.\n");
+            return false;
+        }
+    }
+    else
+    {
+        n = dnp3_func_str_to_code(v.get_string());
+        if (n == -1)
+        {
+            ParseError("dnp3_func requires a "
+                "number beween 0 and 255, or a valid function name.\n");
+            return false;
+        }
+    }
+
+    func = (uint16_t)n;
+
+    return true;
+}
+
+//-------------------------------------------------------------------------
+// dnp3_func api
+//-------------------------------------------------------------------------
+
+static Module* dnp3_func_mod_ctor()
+{
+    return new Dnp3FuncModule;
+}
+
+static void dnp3_func_mod_dtor(Module* m)
+{
+    delete m;
+}
+
+static IpsOption* dnp3_func_ctor(Module* p, OptTreeNode*)
+{
+    Dnp3FuncModule* m = (Dnp3FuncModule*)p;
+    return new Dnp3FuncOption(m->func);
+}
+
+static void dnp3_func_dtor(IpsOption* p)
+{
+    delete p;
+}
+
+static const IpsApi dnp3_func_api =
+{
+    {
+        PT_IPS_OPTION,
+        sizeof(IpsApi),
+        IPSAPI_VERSION,
+        0,
+        API_RESERVED,
+        API_OPTIONS,
+        s_name,
+        s_help,
+        dnp3_func_mod_ctor,
+        dnp3_func_mod_dtor
+    },
+    OPT_TYPE_DETECTION,
+    0, PROTO_BIT__TCP | PROTO_BIT__UDP,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    dnp3_func_ctor,
+    dnp3_func_dtor,
+    nullptr
+};
+
+//-------------------------------------------------------------------------
+// plugin
+//-------------------------------------------------------------------------
+
+// added to snort_plugins in dnp3.cc
+const BaseApi* ips_dnp3_func = &dnp3_func_api.base;
+
diff --git a/src/service_inspectors/dnp3/ips_dnp3_ind.cc b/src/service_inspectors/dnp3/ips_dnp3_ind.cc
new file mode 100644 (file)
index 0000000..67fc2b6
--- /dev/null
@@ -0,0 +1,224 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+
+// ips_dnp3_ind.cc author Maya Dagon <mdagon@cisco.com>
+// based on work by Ryan Jordan
+
+#include "framework/ips_option.h"
+#include "framework/module.h"
+#include "framework/parameter.h"
+#include "detection/detect.h"
+#include "detection/detection_defines.h"
+#include "hash/sfhashfcn.h"
+#include "profiler/profiler.h"
+
+#include "dnp3.h"
+#include "dnp3_map.h"
+
+//-------------------------------------------------------------------------
+// DNP3 indicator flags rule options
+//-------------------------------------------------------------------------
+
+#define s_name "dnp3_ind"
+#define s_help \
+    "detection option to check dnp3 indicator flags"
+
+static THREAD_LOCAL ProfileStats dnp3_ind_perf_stats;
+
+class Dnp3IndOption : public IpsOption
+{
+public:
+    Dnp3IndOption(uint16_t v) : IpsOption(s_name)
+    { flags = v; }
+
+    uint32_t hash() const override;
+    bool operator==(const IpsOption&) const override;
+    int eval(Cursor&, Packet*) override;
+
+private:
+    uint16_t flags;
+};
+
+uint32_t Dnp3IndOption::hash() const
+{
+    uint32_t a = flags, b = 0, c = 0;
+
+    mix_str(a,b,c,get_name());
+    finalize(a,b,c);
+
+    return c;
+}
+
+bool Dnp3IndOption::operator==(const IpsOption& ips) const
+{
+    if ( strcmp(get_name(), ips.get_name()) )
+        return false;
+
+    const Dnp3IndOption& rhs = (Dnp3IndOption&)ips;
+
+    return (flags == rhs.flags);
+}
+
+int Dnp3IndOption::eval(Cursor&, Packet* p)
+{
+    PERF_PROFILE(dnp3_ind_perf_stats);
+
+    if ((p->has_tcp_data() && !p->is_full_pdu()) || !p->flow || !p->dsize)
+        return DETECTION_OPTION_NO_MATCH;
+
+    Dnp3FlowData* fd = (Dnp3FlowData*)p->flow->get_application_data(
+        Dnp3FlowData::flow_id);
+
+    if (!fd)
+        return DETECTION_OPTION_NO_MATCH;
+
+    dnp3_session_data_t* dnp3_session = &fd->dnp3_session;
+
+    /* Internal Indications only apply to DNP3 responses, not requests. */
+    if (dnp3_session->direction == DNP3_CLIENT)
+        return DETECTION_OPTION_NO_MATCH;
+
+    dnp3_reassembly_data_t* rdata = &(dnp3_session->server_rdata);
+
+    /* Only evaluate rules against complete Application-layer fragments */
+    if (rdata->state != DNP3_REASSEMBLY_STATE__DONE)
+        return DETECTION_OPTION_NO_MATCH;
+
+    if (dnp3_session->indications & flags)
+        return DETECTION_OPTION_MATCH;
+
+    return DETECTION_OPTION_NO_MATCH;
+}
+
+//-------------------------------------------------------------------------
+// dnp3_ind module
+//-------------------------------------------------------------------------
+
+static const Parameter s_params[] =
+{
+    { "~", Parameter::PT_STRING, nullptr, nullptr,
+      "match given dnp3 indicator flags" },
+
+    { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+class Dnp3IndModule : public Module
+{
+public:
+    Dnp3IndModule() : Module(s_name, s_help, s_params) { }
+
+    bool set(const char*, Value&, SnortConfig*) override;
+    ProfileStats* get_profile() const override;
+
+    uint16_t flags;
+};
+
+bool Dnp3IndModule::set(const char*, Value& v, SnortConfig*)
+{
+    if ( !v.is("~") )
+        return false;
+
+    flags = 0;
+
+    v.set_first_token();
+    std::string tok;
+
+    while ( v.get_next_token(tok) )
+    {
+        int flag;
+
+        if ( tok[0] == '"' )
+            tok.erase(0, 1);
+
+        if ( tok[tok.length()-1] == '"' )
+            tok.erase(tok.length()-1, 1);
+
+        flag = dnp3_ind_str_to_code(tok.c_str());
+        if ( flag == -1 )
+        {
+            ParseError("dnp3_ind requires a valid indication flag "
+                "name. '%s' is invalid.\n", tok.c_str());
+            return false;
+        }
+        flags |= (uint16_t)flag;
+    }
+    return true;
+}
+
+ProfileStats* Dnp3IndModule::get_profile() const
+{
+    return &dnp3_ind_perf_stats;
+}
+
+//-------------------------------------------------------------------------
+// dnp3_ind api
+//-------------------------------------------------------------------------
+
+static Module* dnp3_ind_mod_ctor()
+{
+    return new Dnp3IndModule;
+}
+
+static void dnp3_ind_mod_dtor(Module* m)
+{
+    delete m;
+}
+
+static IpsOption* dnp3_ind_ctor(Module* p, OptTreeNode*)
+{
+    Dnp3IndModule* m = (Dnp3IndModule*)p;
+    return new Dnp3IndOption(m->flags);
+}
+
+static void dnp3_ind_dtor(IpsOption* p)
+{
+    delete p;
+}
+
+static const IpsApi dnp3_ind_api =
+{
+    {
+        PT_IPS_OPTION,
+        sizeof(IpsApi),
+        IPSAPI_VERSION,
+        0,
+        API_RESERVED,
+        API_OPTIONS,
+        s_name,
+        s_help,
+        dnp3_ind_mod_ctor,
+        dnp3_ind_mod_dtor
+    },
+    OPT_TYPE_DETECTION,
+    0, PROTO_BIT__TCP | PROTO_BIT__UDP,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    dnp3_ind_ctor,
+    dnp3_ind_dtor,
+    nullptr
+};
+
+//-------------------------------------------------------------------------
+// plugin
+//-------------------------------------------------------------------------
+
+// added to snort_plugins in dnp3.cc
+const BaseApi* ips_dnp3_ind = &dnp3_ind_api.base;
+
diff --git a/src/service_inspectors/dnp3/ips_dnp3_obj.cc b/src/service_inspectors/dnp3/ips_dnp3_obj.cc
new file mode 100644 (file)
index 0000000..98eaa7e
--- /dev/null
@@ -0,0 +1,254 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+
+// ips_dnp3_obj.cc author Maya Dagon <mdagon@cisco.com>
+// based on work by Ryan Jordan
+
+#include "framework/ips_option.h"
+#include "framework/module.h"
+#include "framework/parameter.h"
+#include "detection/detect.h"
+#include "detection/detection_defines.h"
+#include "hash/sfhashfcn.h"
+#include "profiler/profiler.h"
+
+#include "dnp3.h"
+
+//-------------------------------------------------------------------------
+// DNP3 object headers rule options
+//-------------------------------------------------------------------------
+
+#define s_name "dnp3_obj"
+#define s_help \
+    "detection option to check dnp3 object headers"
+
+/* Object decoding constants */
+#define DNP3_OBJ_HDR_MIN_LEN 3 /* group, var, qualifier */
+
+static THREAD_LOCAL ProfileStats dnp3_obj_perf_stats;
+
+static int dnp3_decode_object(uint8_t* buf, uint16_t buflen, uint8_t rule_group, uint8_t rule_var)
+{
+    uint8_t group, var;
+
+    if (buf == nullptr || buflen < DNP3_OBJ_HDR_MIN_LEN)
+        return DETECTION_OPTION_NO_MATCH;
+
+    /* Decode group */
+    group = *buf;
+    buf++;
+    buflen--;
+
+    /* Decode var */
+    var = *buf;
+    buf++;
+    buflen--;
+
+    /* Match the rule option here, quit decoding if we found the right header. */
+    if ((group == rule_group) && (var == rule_var))
+        return DETECTION_OPTION_MATCH;
+
+    return DETECTION_OPTION_NO_MATCH;
+}
+
+class Dnp3ObjOption : public IpsOption
+{
+public:
+    Dnp3ObjOption(uint8_t obj_group, uint8_t obj_var) :
+        IpsOption(s_name)
+    { group = obj_group; var = obj_var; }
+
+    uint32_t hash() const override;
+    bool operator==(const IpsOption&) const override;
+    int eval(Cursor&, Packet*) override;
+
+private:
+    uint8_t group;
+    uint8_t var;
+};
+
+uint32_t Dnp3ObjOption::hash() const
+{
+    uint32_t a = group, b = var, c = 0;
+
+    mix_str(a,b,c,get_name());
+    finalize(a,b,c);
+
+    return c;
+}
+
+bool Dnp3ObjOption::operator==(const IpsOption& ips) const
+{
+    if ( strcmp(get_name(), ips.get_name()) )
+        return false;
+
+    const Dnp3ObjOption& rhs = (Dnp3ObjOption&)ips;
+
+    return ((group == rhs.group) &&
+           (var == rhs.var));
+}
+
+int Dnp3ObjOption::eval(Cursor&, Packet* p)
+{
+    PERF_PROFILE(dnp3_obj_perf_stats);
+
+    size_t header_size;
+
+    if ((p->has_tcp_data() && !p->is_full_pdu()) || !p->flow || !p->dsize)
+        return DETECTION_OPTION_NO_MATCH;
+
+    Dnp3FlowData* fd = (Dnp3FlowData*)p->flow->get_application_data(
+        Dnp3FlowData::flow_id);
+
+    if (!fd)
+        return DETECTION_OPTION_NO_MATCH;
+
+    dnp3_session_data_t* dnp3_session = &fd->dnp3_session;
+    dnp3_reassembly_data_t* rdata;
+
+    if (dnp3_session->direction == DNP3_CLIENT)
+    {
+        rdata = &(dnp3_session->client_rdata);
+        header_size = sizeof(dnp3_app_request_header_t);
+    }
+    else
+    {
+        rdata = &(dnp3_session->server_rdata);
+        header_size = sizeof(dnp3_app_response_header_t);
+    }
+
+    /* Only evaluate rules against complete Application-layer fragments */
+    if (rdata->state != DNP3_REASSEMBLY_STATE__DONE)
+        return DETECTION_OPTION_NO_MATCH;
+
+    /* Skip over the App request/response header.
+       They are different sizes, depending on whether it is a request or response! */
+    if (rdata->buflen < header_size)
+        return DETECTION_OPTION_NO_MATCH;
+
+    uint8_t* obj_buffer = (uint8_t*)rdata->buffer + header_size;
+    uint16_t obj_buflen = rdata->buflen - header_size;
+
+    return dnp3_decode_object(obj_buffer, obj_buflen, group, var);
+}
+
+//-------------------------------------------------------------------------
+// dnp3_obj module
+//-------------------------------------------------------------------------
+
+static const Parameter s_params[] =
+{
+    { "group", Parameter::PT_INT, "0:255", "0",
+      "match given dnp3 object header group" },
+    { "var", Parameter::PT_INT, "0:255", "0",
+      "match given dnp3 object header var" },
+    { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+class Dnp3ObjModule : public Module
+{
+public:
+    Dnp3ObjModule() : Module(s_name, s_help, s_params) { }
+
+    bool begin(const char*, int, SnortConfig*) override;
+    bool set(const char*, Value&, SnortConfig*) override;
+    ProfileStats* get_profile() const override;
+
+    uint8_t group;
+    uint8_t var;
+};
+
+bool Dnp3ObjModule::begin(const char*, int, SnortConfig*)
+{
+    group = 0;
+    var = 0;
+    return true;
+}
+
+bool Dnp3ObjModule::set(const char*, Value& v, SnortConfig*)
+{
+    if ( v.is("group") )
+        group = v.get_long();
+    else if ( v.is("var") )
+        var = v.get_long();
+
+    return true;
+}
+
+ProfileStats* Dnp3ObjModule::get_profile() const
+{
+    return &dnp3_obj_perf_stats;
+}
+
+//-------------------------------------------------------------------------
+// dnp3_obj api
+//-------------------------------------------------------------------------
+
+static Module* dnp3_obj_mod_ctor()
+{
+    return new Dnp3ObjModule;
+}
+
+static void dnp3_obj_mod_dtor(Module* m)
+{
+    delete m;
+}
+
+static IpsOption* dnp3_obj_ctor(Module* p, OptTreeNode*)
+{
+    Dnp3ObjModule* m = (Dnp3ObjModule*)p;
+    return new Dnp3ObjOption(m->group, m->var);
+}
+
+static void dnp3_obj_dtor(IpsOption* p)
+{
+    delete p;
+}
+
+static const IpsApi dnp3_obj_api =
+{
+    {
+        PT_IPS_OPTION,
+        sizeof(IpsApi),
+        IPSAPI_VERSION,
+        0,
+        API_RESERVED,
+        API_OPTIONS,
+        s_name,
+        s_help,
+        dnp3_obj_mod_ctor,
+        dnp3_obj_mod_dtor
+    },
+    OPT_TYPE_DETECTION,
+    0, PROTO_BIT__TCP | PROTO_BIT__UDP,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    dnp3_obj_ctor,
+    dnp3_obj_dtor,
+    nullptr
+};
+
+//-------------------------------------------------------------------------
+// plugin
+//-------------------------------------------------------------------------
+
+// added to snort_plugins in dnp3.cc
+const BaseApi* ips_dnp3_obj = &dnp3_obj_api.base;
+
index 026c3357d8b0a112cb16b3578b52426000799bb1..1ef07be0e9d02c10742657bc2e572014a159f345 100644 (file)
@@ -32,6 +32,10 @@ extern const BaseApi* sin_smtp;
 extern const BaseApi* sin_ssl;
 
 #ifdef STATIC_INSPECTORS
+extern const BaseApi* ips_dnp3_data;
+extern const BaseApi* ips_dnp3_func;
+extern const BaseApi* ips_dnp3_ind;
+extern const BaseApi* ips_dnp3_obj;
 extern const BaseApi* ips_gtp_info;
 extern const BaseApi* ips_gtp_type;
 extern const BaseApi* ips_gtp_version;
@@ -46,6 +50,7 @@ extern const BaseApi* ips_ssl_state;
 extern const BaseApi* ips_ssl_version;
 
 extern const BaseApi* sin_bo;
+extern const BaseApi* sin_dnp3;
 extern const BaseApi* sin_dns;
 extern const BaseApi* sin_ftp_client;
 extern const BaseApi* sin_ftp_server;
@@ -70,6 +75,10 @@ const BaseApi* service_inspectors[] =
     sin_ssl,
 
 #ifdef STATIC_INSPECTORS
+    ips_dnp3_data,
+    ips_dnp3_func,
+    ips_dnp3_ind,
+    ips_dnp3_obj,
     ips_gtp_info,
     ips_gtp_type,
     ips_gtp_version,
@@ -84,6 +93,7 @@ const BaseApi* service_inspectors[] =
     ips_ssl_version,
 
     sin_bo,
+    sin_dnp3,
     sin_dns,
     sin_ftp_client,
     sin_ftp_server,
index cbc34b2542e10b79067e7b90de71664c8326f0b4..5f54037baeb8307cad0a043d40b350fbc5244d34 100644 (file)
@@ -2,6 +2,7 @@
 add_library(preprocessor_states
     pps_arpspoof.cc
     pps_bo.cc
+    pps_dnp3.cc
     pps_frag3_engine.cc
     pps_frag3_global.cc
     pps_ftp_telnet.cc
index 88608d8473129cd0c93f1aa504d2e49a5213fdf1..3f56e00e306940cd264a515a0893a7fe36e56cee 100644 (file)
@@ -4,6 +4,7 @@ noinst_LIBRARIES = libpreprocessor_states.a
 libpreprocessor_states_a_SOURCES = \
 pps_arpspoof.cc \
 pps_bo.cc \
+pps_dnp3.cc \
 pps_frag3_engine.cc \
 pps_frag3_global.cc \
 pps_ftp_telnet.cc \
diff --git a/tools/snort2lua/preprocessor_states/pps_dnp3.cc b/tools/snort2lua/preprocessor_states/pps_dnp3.cc
new file mode 100644 (file)
index 0000000..5045dc9
--- /dev/null
@@ -0,0 +1,157 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+// pps_dnp3.cc author Maya Dagon <mdagon@cisco.com>
+
+#include <sstream>
+#include <vector>
+
+#include "conversion_state.h"
+#include "helpers/s2l_util.h"
+#include "helpers/util_binder.h"
+
+namespace preprocessors
+{
+namespace
+{
+class Dnp3 : public ConversionState
+{
+public:
+    Dnp3(Converter& c) : ConversionState(c)
+    { converted_args = false; }
+
+    virtual ~Dnp3();
+    virtual bool convert(std::istringstream& data_stream);
+
+private:
+    bool converted_args;
+};
+} // namespace
+
+Dnp3::~Dnp3()
+{
+    if (converted_args)
+        return;
+
+    Binder tcp_bind(table_api);
+    tcp_bind.set_when_proto("tcp");
+    tcp_bind.add_when_port("20000");
+    tcp_bind.set_use_type("dnp3");
+
+    Binder udp_bind(table_api);
+    udp_bind.set_when_proto("udp");
+    udp_bind.add_when_port("20000");
+    udp_bind.set_use_type("dnp3");
+
+    table_api.open_table("dnp3");
+    table_api.close_table();
+}
+
+bool Dnp3::convert(std::istringstream& data_stream)
+{
+    std::string keyword;
+    bool retval = true;
+    bool ports_set = false;
+    Binder tcp_bind(table_api);
+    Binder udp_bind(table_api);
+
+    converted_args = true;
+
+    tcp_bind.set_when_proto("tcp");
+    tcp_bind.set_use_type("dnp3");
+    udp_bind.set_when_proto("udp");
+    udp_bind.set_use_type("dnp3");
+
+    table_api.open_table("dnp3");
+
+    // parse the file configuration
+    while (data_stream >> keyword)
+    {
+        bool tmpval = true;
+
+        if (!keyword.compare("disabled"))
+        {
+            table_api.add_deleted_comment("disabled");
+        }
+        else if (!keyword.compare("memcap"))
+        {
+            table_api.add_deleted_comment("memcap");
+            data_stream >> keyword;
+        }
+        else if (!keyword.compare("check_crc"))
+        {
+            table_api.add_option("check_crc", true);
+        }
+        else if (!keyword.compare("ports"))
+        {
+            std::string tmp = "";
+            table_api.add_diff_option_comment("ports", "bindings");
+
+            if ((data_stream >> keyword) && !keyword.compare("{"))
+            {
+                while (data_stream >> keyword && keyword.compare("}"))
+                {
+                    ports_set = true;
+                    tcp_bind.add_when_port(keyword);
+                    udp_bind.add_when_port(keyword);
+                }
+            }
+            else
+            {
+                data_api.failed_conversion(data_stream, "ports <bracketed_port_list>");
+                retval = false;
+            }
+        }
+        else
+        {
+            tmpval = false;
+        }
+
+        if (!tmpval)
+        {
+            data_api.failed_conversion(data_stream, keyword);
+            retval = false;
+        }
+    }
+
+    if (!ports_set)
+    {
+        tcp_bind.add_when_port("20000");
+        udp_bind.add_when_port("20000");
+    }
+
+    return retval;
+}
+
+/**************************
+ *******  A P I ***********
+ **************************/
+
+static ConversionState* ctor(Converter& c)
+{
+    return new Dnp3(c);
+}
+
+static const ConvertMap preprocessor_dnp3 =
+{
+    "dnp3",
+    ctor,
+};
+
+const ConvertMap* dnp3_map = &preprocessor_dnp3;
+}
+
index 5ae7f2421e4e54dc2655b6d0de1dd43524b68441..c8d91f83a5cd88018e8997f71bf3b5579cb5284e 100644 (file)
@@ -24,6 +24,7 @@ namespace preprocessors
 extern const ConvertMap* arpspoof_map;
 extern const ConvertMap* arpspoof_host_map;
 extern const ConvertMap* bo_map;
+extern const ConvertMap* dnp3_map;
 extern const ConvertMap* frag3_engine_map;
 extern const ConvertMap* frag3_global_map;
 extern const ConvertMap* ftptelnet_map;
@@ -57,6 +58,7 @@ const std::vector<const ConvertMap*> preprocessor_api =
     arpspoof_map,
     arpspoof_host_map,
     bo_map,
+    dnp3_map,
     frag3_engine_map,
     frag3_global_map,
     ftptelnet_map,
index cd941584168527375ce0e14cf74fcacfbdb7c3e8..3b22a3861811c2bc8faea9e45538018a3d077cfe 100644 (file)
@@ -2,6 +2,8 @@
 add_library( rule_states
     rule_base64_decode.cc
     rule_content.cc
+    rule_convert_comma_list.cc
+    rule_dnp3_obj.cc
     rule_file_data.cc
     rule_http_encode.cc
     rule_isdataat.cc
index f03e5437ee8640d55eb0d550bff627f2b47e926c..7614a177519b792362e9380573af936ab88fc75d 100644 (file)
@@ -4,6 +4,8 @@ noinst_LIBRARIES = librule_states.a
 librule_states_a_SOURCES = \
 rule_base64_decode.cc \
 rule_content.cc \
+rule_convert_comma_list.cc \
+rule_dnp3_obj.cc \
 rule_file_data.cc \
 rule_http_encode.cc \
 rule_isdataat.cc \
index 9edf24c18be2caefea93b61ebb1403c413c0a9ad..7d3b4e75bde26878728371f355008ca352b703bc 100644 (file)
@@ -35,6 +35,10 @@ extern const ConvertMap* classtype_map;
 extern const ConvertMap* content_map;
 extern const ConvertMap* cvs_map;
 extern const ConvertMap* detection_filter_map;
+extern const ConvertMap* dnp3_data_map;
+extern const ConvertMap* dnp3_func_map;
+extern const ConvertMap* dnp3_ind_map;
+extern const ConvertMap* dnp3_obj_map;
 extern const ConvertMap* dsize_map;
 extern const ConvertMap* file_data_map;
 extern const ConvertMap* flags_map;
@@ -104,6 +108,10 @@ const std::vector<const ConvertMap*> rule_options_api =
     content_map,
     cvs_map,
     detection_filter_map,
+    dnp3_data_map,
+    dnp3_func_map,
+    dnp3_ind_map,
+    dnp3_obj_map,
     dsize_map,
     file_data_map,
     flags_map,
diff --git a/tools/snort2lua/rule_states/rule_convert_comma_list.cc b/tools/snort2lua/rule_states/rule_convert_comma_list.cc
new file mode 100644 (file)
index 0000000..cb067a9
--- /dev/null
@@ -0,0 +1,77 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+// rule_convert_comma_list.cc author Maya Dagon <mdagon@cisco.com>
+
+#include <sstream>
+
+#include "conversion_state.h"
+#include "helpers/converter.h"
+#include "rule_states/rule_api.h"
+#include "helpers/s2l_util.h"
+
+namespace rules
+{
+/*
+ * Convert rule option from comma list to a space sperated list in qoutes:
+ * x,y,z to "x y z"
+ */
+
+template<const std::string* rule_name>
+class CommaListRuleOption : public ConversionState
+{
+public:
+    CommaListRuleOption(Converter& c) : ConversionState(c) { }
+    virtual ~CommaListRuleOption() { }
+
+    virtual bool convert(std::istringstream& stream)
+    {
+        std::string val = util::get_rule_option_args(stream);
+
+        size_t start_pos = 0;
+        while ((start_pos = val.find(',', start_pos)) != std::string::npos)
+        {
+            val.replace(start_pos, 1, " ");
+            start_pos += 1;
+        }
+        val.insert(0,1,'"');
+        val.insert(val.end(),1,'"');
+
+        rule_api.add_option(*rule_name, val);
+        return set_next_rule_state(stream);
+    }
+};
+
+template<const std::string* rule_name>
+static ConversionState* comma_list_conversion_ctor(Converter& c)
+{
+    return new CommaListRuleOption<rule_name>(c);
+}
+
+/************************************
+ *********  DNP3 IND **************
+ ************************************/
+static const std::string dnp3_ind = "dnp3_ind";
+static const ConvertMap dnp3_ind_api =
+{
+    dnp3_ind,
+    comma_list_conversion_ctor<& dnp3_ind>,
+};
+
+const ConvertMap* dnp3_ind_map = &dnp3_ind_api;
+} // namespace rules
+
diff --git a/tools/snort2lua/rule_states/rule_dnp3_obj.cc b/tools/snort2lua/rule_states/rule_dnp3_obj.cc
new file mode 100644 (file)
index 0000000..f48ea5f
--- /dev/null
@@ -0,0 +1,78 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+// rule_dnp3_obj.cc author Maya Dagon <mdagon@cisco.com>
+
+#include <sstream>
+
+#include "conversion_state.h"
+#include "helpers/converter.h"
+#include "rule_states/rule_api.h"
+#include "helpers/s2l_util.h"
+
+namespace rules
+{
+namespace
+{
+class DNP3Obj : public ConversionState
+{
+public:
+    DNP3Obj(Converter& c) : ConversionState(c) { }
+    virtual ~DNP3Obj() { }
+    virtual bool convert(std::istringstream& data);
+};
+} // namespace
+
+bool DNP3Obj::convert(std::istringstream& data)
+{
+    std::string val = util::get_rule_option_args(data);
+    std::string group = "group ";
+    std::string var = " var ";
+
+    /* convert from dnp3_obj: xxx,xxx to
+     * dnp3_obj: group xxx, var xxxx
+     */
+    val.insert(0, group);
+    size_t start_pos = val.find(',');
+    if (start_pos == std::string::npos)
+        rule_api.bad_rule(data, "dnp3_obj:expecting 2 args seperated by comma");
+    else
+        val.insert(start_pos+1, var);
+
+    rule_api.add_option("dnp3_obj", val);
+    return set_next_rule_state(data);
+}
+
+/**************************
+ *******  A P I ***********
+ **************************/
+
+static ConversionState* ctor(Converter& cv)
+{
+    return new DNP3Obj(cv);
+}
+
+static const std::string dnp3_obj = "dnp3_obj";
+static const ConvertMap dnp3_obj_api =
+{
+    dnp3_obj,
+    ctor,
+};
+
+const ConvertMap* dnp3_obj_map = &dnp3_obj_api;
+} // namespace rules
+
index af60e727e143b5eeb9e0dd11049f76c81e164732..d04752e995032bc859eafcac1b11dbffa551902f 100644 (file)
@@ -704,5 +704,30 @@ static const ConvertMap rule_ssl_version =
 
 const ConvertMap* ssl_version_map = &rule_ssl_version;
 
+/************************************
+ *******  DNP3 DATA  ************
+ ************************************/
+
+static const std::string dnp3_data = "dnp3_data";
+static const ConvertMap rule_dnp3_data =
+{
+    dnp3_data,
+    unchanged_rule_ctor<& dnp3_data, false>,
+};
+
+const ConvertMap* dnp3_data_map = &rule_dnp3_data;
+
+/************************************
+ *********  DNP3 FUNC **************
+ ************************************/
+
+static const std::string dnp3_func = "dnp3_func";
+static const ConvertMap rule_dnp3_func =
+{
+    dnp3_func,
+    unchanged_rule_ctor<& dnp3_func>,
+};
+
+const ConvertMap* dnp3_func_map = &rule_dnp3_func;
 } // namespace rule