From: Russ Combs (rucombs) Date: Tue, 24 Nov 2015 21:38:14 +0000 (-0500) Subject: Merge pull request #152 in SNORT/snort3 from dnp3_2 to master X-Git-Tag: 3.0.0-233~710 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=83b2a66fa89df6ec6a1a8f732931db7ef781e6b6;p=thirdparty%2Fsnort3.git Merge pull request #152 in SNORT/snort3 from dnp3_2 to master Squashed commit of the following: commit 1619f91f932d5d2b4fbec6b15069d4bc0ee7e754 Author: mdagon Date: Tue Nov 24 16:32:34 2015 -0500 cmake fix, code review comments commit afb271e012136648246ef42bf34ab196efab4af5 Author: rrp 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 Date: Tue Nov 24 11:56:37 2015 -0500 Code review comments: 1. Copyright update 2. lua/snort.lua update commit d6226e7572f481a74f14671b9b16b3ea6eda3f51 Author: rrp Date: Thu Nov 19 11:57:17 2015 -0500 changes based on code review comments commit bbb0defd183f2171486768bc22eca671f4fa10d1 Author: rrp 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 --- diff --git a/configure.ac b/configure.ac index e4e09b83a..bdd09eb68 100644 --- a/configure.ac +++ b/configure.ac @@ -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 \ diff --git a/doc/bugs.txt b/doc/bugs.txt index 28b099714..cc9beeada 100644 --- a/doc/bugs.txt +++ b/doc/bugs.txt @@ -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 not supported. ==== Runtime @@ -103,3 +105,5 @@ * 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. + diff --git a/lua/snort.lua b/lua/snort.lua index f80e1b199..45aa52a98 100644 --- a/lua/snort.lua +++ b/lua/snort.lua @@ -67,6 +67,7 @@ stream_file = { } arp_spoof = { } back_orifice = { } +dnp3 = { } dns = { } gtp_inspect = default_gtp imap = { } diff --git a/src/Makefile.am b/src/Makefile.am index 6de33c624..b9f185305 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/service_inspectors/CMakeLists.txt b/src/service_inspectors/CMakeLists.txt index bafbb4470..f7f1e61ec 100644 --- a/src/service_inspectors/CMakeLists.txt +++ b/src/service_inspectors/CMakeLists.txt @@ -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 diff --git a/src/service_inspectors/Makefile.am b/src/service_inspectors/Makefile.am index b361a5ef9..bfb583af5 100644 --- a/src/service_inspectors/Makefile.am +++ b/src/service_inspectors/Makefile.am @@ -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 index 000000000..e4394ccd5 --- /dev/null +++ b/src/service_inspectors/dnp3/CMakeLists.txt @@ -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 index 000000000..6f640176d --- /dev/null +++ b/src/service_inspectors/dnp3/Makefile.am @@ -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 index 000000000..0e4c40b9c --- /dev/null +++ b/src/service_inspectors/dnp3/dev_notes.txt @@ -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 index 000000000..8ef7a536a --- /dev/null +++ b/src/service_inspectors/dnp3/dnp3.cc @@ -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 +// 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 index 000000000..7151bd04f --- /dev/null +++ b/src/service_inspectors/dnp3/dnp3.h @@ -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 +// 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 index 000000000..4b7d204fb --- /dev/null +++ b/src/service_inspectors/dnp3/dnp3_map.cc @@ -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 +#include + +/* 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 index 000000000..6b4c0d30c --- /dev/null +++ b/src/service_inspectors/dnp3/dnp3_map.h @@ -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 + +/* 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 index 000000000..5d54593b8 --- /dev/null +++ b/src/service_inspectors/dnp3/dnp3_module.cc @@ -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 + +#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 index 000000000..367177bad --- /dev/null +++ b/src/service_inspectors/dnp3/dnp3_module.h @@ -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 + +#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 index 000000000..470535795 --- /dev/null +++ b/src/service_inspectors/dnp3/dnp3_paf.cc @@ -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 +// 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 index 000000000..ed6d39351 --- /dev/null +++ b/src/service_inspectors/dnp3/dnp3_paf.h @@ -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 +// 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 index 000000000..c01d11929 --- /dev/null +++ b/src/service_inspectors/dnp3/dnp3_reassembly.cc @@ -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 + +#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 index 000000000..3c2c46853 --- /dev/null +++ b/src/service_inspectors/dnp3/dnp3_reassembly.h @@ -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 +// 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 index 000000000..0f5add0ce --- /dev/null +++ b/src/service_inspectors/dnp3/ips_dnp3_data.cc @@ -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 +// 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 index 000000000..6d60f638d --- /dev/null +++ b/src/service_inspectors/dnp3/ips_dnp3_func.cc @@ -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 +// 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 index 000000000..67fc2b655 --- /dev/null +++ b/src/service_inspectors/dnp3/ips_dnp3_ind.cc @@ -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 +// 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 index 000000000..98eaa7e25 --- /dev/null +++ b/src/service_inspectors/dnp3/ips_dnp3_obj.cc @@ -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 +// 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; + diff --git a/src/service_inspectors/service_inspectors.cc b/src/service_inspectors/service_inspectors.cc index 026c3357d..1ef07be0e 100644 --- a/src/service_inspectors/service_inspectors.cc +++ b/src/service_inspectors/service_inspectors.cc @@ -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, diff --git a/tools/snort2lua/preprocessor_states/CMakeLists.txt b/tools/snort2lua/preprocessor_states/CMakeLists.txt index cbc34b254..5f54037ba 100644 --- a/tools/snort2lua/preprocessor_states/CMakeLists.txt +++ b/tools/snort2lua/preprocessor_states/CMakeLists.txt @@ -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 diff --git a/tools/snort2lua/preprocessor_states/Makefile.am b/tools/snort2lua/preprocessor_states/Makefile.am index 88608d847..3f56e00e3 100644 --- a/tools/snort2lua/preprocessor_states/Makefile.am +++ b/tools/snort2lua/preprocessor_states/Makefile.am @@ -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 index 000000000..5045dc9a9 --- /dev/null +++ b/tools/snort2lua/preprocessor_states/pps_dnp3.cc @@ -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 + +#include +#include + +#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 "); + 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; +} + diff --git a/tools/snort2lua/preprocessor_states/preprocessor_api.cc b/tools/snort2lua/preprocessor_states/preprocessor_api.cc index 5ae7f2421..c8d91f83a 100644 --- a/tools/snort2lua/preprocessor_states/preprocessor_api.cc +++ b/tools/snort2lua/preprocessor_states/preprocessor_api.cc @@ -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 preprocessor_api = arpspoof_map, arpspoof_host_map, bo_map, + dnp3_map, frag3_engine_map, frag3_global_map, ftptelnet_map, diff --git a/tools/snort2lua/rule_states/CMakeLists.txt b/tools/snort2lua/rule_states/CMakeLists.txt index cd9415841..3b22a3861 100644 --- a/tools/snort2lua/rule_states/CMakeLists.txt +++ b/tools/snort2lua/rule_states/CMakeLists.txt @@ -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 diff --git a/tools/snort2lua/rule_states/Makefile.am b/tools/snort2lua/rule_states/Makefile.am index f03e5437e..7614a1775 100644 --- a/tools/snort2lua/rule_states/Makefile.am +++ b/tools/snort2lua/rule_states/Makefile.am @@ -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 \ diff --git a/tools/snort2lua/rule_states/rule_api.cc b/tools/snort2lua/rule_states/rule_api.cc index 9edf24c18..7d3b4e75b 100644 --- a/tools/snort2lua/rule_states/rule_api.cc +++ b/tools/snort2lua/rule_states/rule_api.cc @@ -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 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 index 000000000..cb067a935 --- /dev/null +++ b/tools/snort2lua/rule_states/rule_convert_comma_list.cc @@ -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 + +#include + +#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 +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 +static ConversionState* comma_list_conversion_ctor(Converter& c) +{ + return new CommaListRuleOption(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 index 000000000..f48ea5f30 --- /dev/null +++ b/tools/snort2lua/rule_states/rule_dnp3_obj.cc @@ -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 + +#include + +#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 + diff --git a/tools/snort2lua/rule_states/rule_unchanged.cc b/tools/snort2lua/rule_states/rule_unchanged.cc index af60e727e..d04752e99 100644 --- a/tools/snort2lua/rule_states/rule_unchanged.cc +++ b/tools/snort2lua/rule_states/rule_unchanged.cc @@ -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