From: Russ Combs Date: Mon, 30 Mar 2015 16:14:59 +0000 (-0400) Subject: ported dns preprocessor X-Git-Tag: 3.0.0-233~1009 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c8cd356f6b172d46a4e6a0dfbb241572c0ac7ae2;p=thirdparty%2Fsnort3.git ported dns preprocessor --- diff --git a/ChangeLog b/ChangeLog index a02aca8df..f601ac2f8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,10 @@ +Pending - build 144 + +-- ported dns inspector + 15/03/26 - build 143 --- added ssh inspector +-- ported ssh inspector -- apply service from hosts when inspector already bound to flow -- ensure direction and service are applied to packet regardless of flow state -- enable active for react / reject only if used in configuration diff --git a/configure.ac b/configure.ac index 374ecac3f..be9a2884c 100644 --- a/configure.ac +++ b/configure.ac @@ -967,6 +967,7 @@ src/packet_io/Makefile \ src/parser/Makefile \ src/service_inspectors/Makefile \ src/service_inspectors/back_orifice/Makefile \ +src/service_inspectors/dns/Makefile \ src/service_inspectors/ftp_telnet/Makefile \ src/service_inspectors/http_inspect/Makefile \ src/service_inspectors/nhttp_inspect/Makefile \ diff --git a/lua/snort.lua b/lua/snort.lua index 7b84c10f5..62d9e743b 100644 --- a/lua/snort.lua +++ b/lua/snort.lua @@ -62,12 +62,13 @@ stream_icmp = { } stream_tcp = { } stream_udp = { } -perf_monitor = { } - arp_spoof = { } back_orifice = { } -rpc_decode = { } +dns = { } +perf_monitor = { } port_scan = { } +rpc_decode = { } +ssh = { } telnet = { } -- use http_inspect or new_http_inspect (incomplete) @@ -78,7 +79,5 @@ ftp_server = default_ftp_server ftp_client = { } ftp_data = { } -ssh = { } - wizard = default_wizard diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 015f8570a..85ffc2d57 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -55,6 +55,7 @@ if (STATIC_INSPECTORS) set (STATIC_INSPECTOR_LIBRARIES arp_spoof back_orifice + dns ftp_telnet nhttp_inspect rpc_decode diff --git a/src/Makefile.am b/src/Makefile.am index e66948fba..432892c4f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -10,6 +10,7 @@ if STATIC_INSPECTORS lib_list = \ network_inspectors/arp_spoof/libarp_spoof.a \ service_inspectors/back_orifice/libback_orifice.a \ +service_inspectors/dns/libdns.a \ service_inspectors/ftp_telnet/libftp_telnet.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 44657f344..df448bbc1 100644 --- a/src/service_inspectors/CMakeLists.txt +++ b/src/service_inspectors/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(back_orifice) add_subdirectory(ftp_telnet) +add_subdirectory(dns) add_subdirectory(http_inspect) add_subdirectory(nhttp_inspect) add_subdirectory(rpc_decode) @@ -11,9 +12,10 @@ if (STATIC_INSPECTORS) set (STATIC_INSECTOR_LIBS back_orifice ftp_telnet + dns nhttp_inspect - ssh rpc_decode + ssh wizard ) endif() diff --git a/src/service_inspectors/Makefile.am b/src/service_inspectors/Makefile.am index 3f8b28463..067ee9aab 100644 --- a/src/service_inspectors/Makefile.am +++ b/src/service_inspectors/Makefile.am @@ -18,6 +18,7 @@ service_inspectors.h SUBDIRS = \ back_orifice \ +dns \ ftp_telnet \ http_inspect \ nhttp_inspect \ diff --git a/src/service_inspectors/dns/CMakeLists.txt b/src/service_inspectors/dns/CMakeLists.txt new file mode 100644 index 000000000..94ab376fe --- /dev/null +++ b/src/service_inspectors/dns/CMakeLists.txt @@ -0,0 +1,15 @@ + +set( FILE_LIST + dns.cc + dns.h + dns_module.cc + dns_module.h +) + +if (STATIC_INSPECTORS) + add_library( dns STATIC ${FILE_LIST}) + +else (STATIC_INSPECTORS) + add_shared_library(dns inspectors ${FILE_LIST}) + +endif (STATIC_INSPECTORS) diff --git a/src/service_inspectors/dns/Makefile.am b/src/service_inspectors/dns/Makefile.am new file mode 100644 index 000000000..72f57bfd3 --- /dev/null +++ b/src/service_inspectors/dns/Makefile.am @@ -0,0 +1,21 @@ +AUTOMAKE_OPTIONS=foreign no-dependencies + +file_list = \ +dns.cc \ +dns.h \ +dns_module.cc \ +dns_module.h + +if STATIC_INSPECTORS +noinst_LIBRARIES = libdns.a +libdns_a_SOURCES = $(file_list) +else +shlibdir = $(pkglibdir)/inspectors +shlib_LTLIBRARIES = libdns.la +libdns_la_CXXFLAGS = $(AM_CXXFLAGS) -DBUILDING_SO +libdns_la_LDFLAGS = -export-dynamic -shared +libdns_la_SOURCES = $(file_list) +endif + +AM_CXXFLAGS = @AM_CXXFLAGS@ + diff --git a/src/service_inspectors/dns/dns.cc b/src/service_inspectors/dns/dns.cc new file mode 100644 index 000000000..c55c7d181 --- /dev/null +++ b/src/service_inspectors/dns/dns.cc @@ -0,0 +1,1173 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2015 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2004-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. +//-------------------------------------------------------------------------- +// + +/* + * DNS preprocessor + * Author: Chris Sherwin + * Contributors: Adam Keeton, Ryan Jordan + * + * + * Alert for Gobbles, CRC32, protocol mismatch (Cisco catalyst vulnerability), + * and a SecureCRT vulnerability. Will also alert if the client or server + * traffic appears to flow the wrong direction, or if packets appear + * malformed/spoofed. + * + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "snort_types.h" +#include "snort_debug.h" + +#include "dns.h" +#include "dns_module.h" +#include "profiler.h" +#include "stream/stream_api.h" +#include "parser.h" +#include "framework/inspector.h" +#include "utils/sfsnprintfappend.h" + +THREAD_LOCAL ProfileStats dnsPerfStats; +THREAD_LOCAL SimpleStats dnsstats; + +#define MIN_UDP_PAYLOAD 0x1FFF +#define DNS_RR_PTR 0xC0 + +/* + * Function prototype(s) + */ +static void snort_dns(Packet* p); + +unsigned DnsFlowData::flow_id = 0; + +DNSData udpSessionData; + +DNSData* SetNewDNSData(Packet* p) +{ + DnsFlowData* fd; + + if (p->is_udp()) + return NULL; + + fd = new DnsFlowData; + + p->flow->set_application_data(fd); + return &fd->session; +} + +static DNSData* get_dns_session_data(Packet* p) +{ + DnsFlowData* fd; + + if (p->is_udp()) + { + if (p->dsize < (sizeof(DNSHdr) + sizeof(DNSRR) + MIN_UDP_PAYLOAD)) + return NULL; + + memset(&udpSessionData, 0, sizeof(udpSessionData)); + return &udpSessionData; + } + + fd = (DnsFlowData*)((p->flow)->get_application_data( + DnsFlowData::flow_id)); + + return fd ? &fd->session : NULL; +} + +static uint16_t ParseDNSHeader(const unsigned char* data, + uint16_t bytes_unused, + DNSData* dnsSessionData) +{ + if (bytes_unused == 0) + { + return bytes_unused; + } + + switch (dnsSessionData->state) + { + case DNS_RESP_STATE_LENGTH: + /* First two bytes are length in TCP */ + dnsSessionData->length = ((uint8_t)*data) << 8; + dnsSessionData->state = DNS_RESP_STATE_LENGTH_PART; + data++; + bytes_unused--; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_LENGTH_PART: + dnsSessionData->length |= ((uint8_t)*data); + dnsSessionData->state = DNS_RESP_STATE_HDR_ID; + data++; + bytes_unused--; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_HDR_ID: + dnsSessionData->hdr.id = (uint8_t)*data << 8; + data++; + bytes_unused--; + dnsSessionData->state = DNS_RESP_STATE_HDR_ID_PART; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_HDR_ID_PART: + dnsSessionData->hdr.id |= (uint8_t)*data; + data++; + bytes_unused--; + dnsSessionData->state = DNS_RESP_STATE_HDR_FLAGS; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_HDR_FLAGS: + dnsSessionData->hdr.flags = (uint8_t)*data << 8; + data++; + bytes_unused--; + dnsSessionData->state = DNS_RESP_STATE_HDR_FLAGS_PART; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_HDR_FLAGS_PART: + dnsSessionData->hdr.flags |= (uint8_t)*data; + data++; + bytes_unused--; + dnsSessionData->state = DNS_RESP_STATE_HDR_QS; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_HDR_QS: + dnsSessionData->hdr.questions = (uint8_t)*data << 8; + data++; + bytes_unused--; + dnsSessionData->state = DNS_RESP_STATE_HDR_QS_PART; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_HDR_QS_PART: + dnsSessionData->hdr.questions |= (uint8_t)*data; + data++; + bytes_unused--; + dnsSessionData->state = DNS_RESP_STATE_HDR_ANSS; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_HDR_ANSS: + dnsSessionData->hdr.answers = (uint8_t)*data << 8; + data++; + bytes_unused--; + dnsSessionData->state = DNS_RESP_STATE_HDR_ANSS_PART; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_HDR_ANSS_PART: + dnsSessionData->hdr.answers |= (uint8_t)*data; + data++; + bytes_unused--; + dnsSessionData->state = DNS_RESP_STATE_HDR_AUTHS; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_HDR_AUTHS: + dnsSessionData->hdr.authorities = (uint8_t)*data << 8; + data++; + bytes_unused--; + dnsSessionData->state = DNS_RESP_STATE_HDR_AUTHS_PART; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_HDR_AUTHS_PART: + dnsSessionData->hdr.authorities |= (uint8_t)*data; + data++; + bytes_unused--; + dnsSessionData->state = DNS_RESP_STATE_HDR_ADDS; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_HDR_ADDS: + dnsSessionData->hdr.additionals = (uint8_t)*data << 8; + data++; + bytes_unused--; + dnsSessionData->state = DNS_RESP_STATE_HDR_ADDS_PART; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_HDR_ADDS_PART: + dnsSessionData->hdr.additionals |= (uint8_t)*data; + data++; + bytes_unused--; + dnsSessionData->state = DNS_RESP_STATE_QUESTION; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + default: + /* Continue -- we're beyond the header */ + break; + } + + return bytes_unused; +} + +uint16_t ParseDNSName(const unsigned char* data, + uint16_t bytes_unused, + DNSData* dnsSessionData) +{ + uint16_t bytes_required = dnsSessionData->curr_txt.txt_len - + dnsSessionData->curr_txt.txt_bytes_seen; + + while (dnsSessionData->curr_txt.name_state != DNS_RESP_STATE_NAME_COMPLETE) + { + if (bytes_unused == 0) + { + return bytes_unused; + } + + switch (dnsSessionData->curr_txt.name_state) + { + case DNS_RESP_STATE_NAME_SIZE: + dnsSessionData->curr_txt.txt_len = (uint8_t)*data; + data++; + bytes_unused--; + dnsSessionData->bytes_seen_curr_rec++; + if (dnsSessionData->curr_txt.txt_len == 0) + { + dnsSessionData->curr_txt.name_state = DNS_RESP_STATE_NAME_COMPLETE; + return bytes_unused; + } + + dnsSessionData->curr_txt.name_state = DNS_RESP_STATE_NAME; + dnsSessionData->curr_txt.txt_bytes_seen = 0; + + if ((dnsSessionData->curr_txt.txt_len & DNS_RR_PTR) == DNS_RR_PTR) + { + /* A reference to another location... + This is an offset */ + dnsSessionData->curr_txt.offset = (dnsSessionData->curr_txt.txt_len & ~0xC0) << 8; + bytes_required = dnsSessionData->curr_txt.txt_len = 1; + dnsSessionData->curr_txt.relative = 1; + /* Setup to read 2nd Byte of Location */ + } + else + { + bytes_required = dnsSessionData->curr_txt.txt_len; + dnsSessionData->curr_txt.offset = 0; + dnsSessionData->curr_txt.relative = 0; + } + + if (bytes_unused == 0) + { + return bytes_unused; + } + + /* Fall through */ + case DNS_RESP_STATE_NAME: + if (bytes_required <= bytes_unused) + { + bytes_unused -= bytes_required; + if (dnsSessionData->curr_txt.relative) + { + /* If this one is a relative offset, read that extra byte */ + dnsSessionData->curr_txt.offset |= *data; + } + data += bytes_required; + dnsSessionData->bytes_seen_curr_rec += bytes_required; + dnsSessionData->curr_txt.txt_bytes_seen += bytes_required; + + if (bytes_unused == 0) + { + return bytes_unused; + } + } + else + { + dnsSessionData->bytes_seen_curr_rec+= bytes_unused; + dnsSessionData->curr_txt.txt_bytes_seen += bytes_unused; + return 0; + } + if (dnsSessionData->curr_txt.relative) + { + /* And since its relative, we're done */ + dnsSessionData->curr_txt.name_state = DNS_RESP_STATE_NAME_COMPLETE; + return bytes_unused; + } + break; + } + + /* Go to the next portion of the name */ + dnsSessionData->curr_txt.name_state = DNS_RESP_STATE_NAME_SIZE; + } + + return bytes_unused; +} + +static uint16_t ParseDNSQuestion(const unsigned char* data, + uint16_t /*data_size*/, + uint16_t bytes_unused, + DNSData* dnsSessionData) +{ + uint16_t bytes_used = 0; + uint16_t new_bytes_unused = 0; + + if (bytes_unused == 0) + { + return bytes_unused; + } + + if (dnsSessionData->curr_rec_state < DNS_RESP_STATE_Q_NAME_COMPLETE) + { + new_bytes_unused = ParseDNSName(data, bytes_unused, dnsSessionData); + bytes_used = bytes_unused - new_bytes_unused; + + if (dnsSessionData->curr_txt.name_state == DNS_RESP_STATE_NAME_COMPLETE) + { + dnsSessionData->curr_rec_state = DNS_RESP_STATE_Q_TYPE; + memset(&dnsSessionData->curr_txt, 0, sizeof(DNSNameState)); + data = data + bytes_used; + bytes_unused = new_bytes_unused; + + if (bytes_unused == 0) + { + /* ran out of data */ + return bytes_unused; + } + } + else + { + /* Should be 0 -- ran out of data */ + return new_bytes_unused; + } + } + + switch (dnsSessionData->curr_rec_state) + { + case DNS_RESP_STATE_Q_TYPE: + dnsSessionData->curr_q.type = (uint8_t)*data << 8; + data++; + bytes_unused--; + dnsSessionData->curr_rec_state = DNS_RESP_STATE_Q_TYPE_PART; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_Q_TYPE_PART: + dnsSessionData->curr_q.type |= (uint8_t)*data; + data++; + bytes_unused--; + dnsSessionData->curr_rec_state = DNS_RESP_STATE_Q_CLASS; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_Q_CLASS: + dnsSessionData->curr_q.dns_class = (uint8_t)*data << 8; + data++; + bytes_unused--; + dnsSessionData->curr_rec_state = DNS_RESP_STATE_Q_CLASS_PART; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_Q_CLASS_PART: + dnsSessionData->curr_q.dns_class |= (uint8_t)*data; + data++; + bytes_unused--; + dnsSessionData->curr_rec_state = DNS_RESP_STATE_Q_COMPLETE; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + default: + /* Continue -- we're beyond this question */ + break; + } + + return bytes_unused; +} + +uint16_t ParseDNSAnswer(const unsigned char* data, + uint16_t /*data_size*/, + uint16_t bytes_unused, + DNSData* dnsSessionData) +{ + uint16_t bytes_used = 0; + uint16_t new_bytes_unused = 0; + + if (bytes_unused == 0) + { + return bytes_unused; + } + + if (dnsSessionData->curr_rec_state < DNS_RESP_STATE_RR_NAME_COMPLETE) + { + new_bytes_unused = ParseDNSName(data, bytes_unused, dnsSessionData); + bytes_used = bytes_unused - new_bytes_unused; + + if (dnsSessionData->curr_txt.name_state == DNS_RESP_STATE_NAME_COMPLETE) + { + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_TYPE; + memset(&dnsSessionData->curr_txt, 0, sizeof(DNSNameState)); + data = data + bytes_used; + } + bytes_unused = new_bytes_unused; + + if (bytes_unused == 0) + { + /* ran out of data */ + return bytes_unused; + } + } + + switch (dnsSessionData->curr_rec_state) + { + case DNS_RESP_STATE_RR_TYPE: + dnsSessionData->curr_rr.type = (uint8_t)*data << 8; + data++; + bytes_unused--; + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_TYPE_PART; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_RR_TYPE_PART: + dnsSessionData->curr_rr.type |= (uint8_t)*data; + data++; + bytes_unused--; + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_CLASS; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_RR_CLASS: + dnsSessionData->curr_rr.dns_class = (uint8_t)*data << 8; + data++; + bytes_unused--; + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_CLASS_PART; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_RR_CLASS_PART: + dnsSessionData->curr_rr.dns_class |= (uint8_t)*data; + data++; + bytes_unused--; + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_TTL; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_RR_TTL: + dnsSessionData->curr_rr.ttl = (uint8_t)*data << 24; + data++; + bytes_unused--; + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_TTL_PART; + dnsSessionData->bytes_seen_curr_rec = 1; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_RR_TTL_PART: + while (dnsSessionData->bytes_seen_curr_rec < 4) + { + dnsSessionData->bytes_seen_curr_rec++; + dnsSessionData->curr_rr.ttl |= + (uint8_t)*data << (4-dnsSessionData->bytes_seen_curr_rec)*8; + data++; + bytes_unused--; + if (bytes_unused == 0) + { + return bytes_unused; + } + } + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_RDLENGTH; + /* Fall through */ + case DNS_RESP_STATE_RR_RDLENGTH: + dnsSessionData->curr_rr.length = (uint8_t)*data << 8; + data++; + bytes_unused--; + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_RDLENGTH_PART; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_RR_RDLENGTH_PART: + dnsSessionData->curr_rr.length |= (uint8_t)*data; + data++; + bytes_unused--; + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_RDATA_START; + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + default: + /* Continue -- we're beyond this answer */ + break; + } + + return bytes_unused; +} + +/* The following check is to look for an attempt to exploit + * a vulnerability in the DNS client, per MS 06-041. + * + * For details, see: + * http://www.microsoft.com/technet/security/bulletin/ms06-007.mspx + * http://cve.mitre.org/cgi-bin/cvename.cgi?name=2006-3441 + * + * Vulnerability Research by Lurene Grenier, Judy Novak, + * and Brian Caswell. + */ +uint16_t CheckRRTypeTXTVuln(const unsigned char* data, + uint16_t bytes_unused, + DNSData* dnsSessionData) +{ + uint16_t bytes_required = dnsSessionData->curr_txt.txt_len - + dnsSessionData->curr_txt.txt_bytes_seen; + + while (dnsSessionData->curr_txt.name_state != DNS_RESP_STATE_RR_NAME_COMPLETE) + { + if (dnsSessionData->bytes_seen_curr_rec == dnsSessionData->curr_rr.length) + { + /* Done with the name */ + dnsSessionData->curr_txt.name_state = DNS_RESP_STATE_RR_NAME_COMPLETE; + /* Got to the end of the rdata in this packet! */ + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_COMPLETE; + return bytes_unused; + } + + if (bytes_unused == 0) + { + return bytes_unused; + } + + switch (dnsSessionData->curr_txt.name_state) + { + case DNS_RESP_STATE_RR_NAME_SIZE: + dnsSessionData->curr_txt.txt_len = (uint8_t)*data; + dnsSessionData->curr_txt.txt_count++; + + /* include the NULL */ + dnsSessionData->curr_txt.total_txt_len += dnsSessionData->curr_txt.txt_len + 1; + + if (!dnsSessionData->curr_txt.alerted) + { + uint32_t overflow_check = (dnsSessionData->curr_txt.txt_count * 4) + + (dnsSessionData->curr_txt.total_txt_len * 2) + 4; + /* if txt_count * 4 + total_txt_len * 2 + 4 > FFFF, vulnerability! */ + if (overflow_check > 0xFFFF) + { + /* Alert on obsolete DNS RR types */ + SnortEventqAdd(GID_DNS, DNS_EVENT_RDATA_OVERFLOW); + + dnsSessionData->curr_txt.alerted = 1; + } + } + + data++; + bytes_unused--; + dnsSessionData->bytes_seen_curr_rec++; + if (dnsSessionData->curr_txt.txt_len > 0) + { + dnsSessionData->curr_txt.name_state = DNS_RESP_STATE_RR_NAME; + dnsSessionData->curr_txt.txt_bytes_seen = 0; + bytes_required = dnsSessionData->curr_txt.txt_len; + } + else + { + continue; + } + if (bytes_unused == 0) + { + return bytes_unused; + } + /* Fall through */ + case DNS_RESP_STATE_RR_NAME: + if (bytes_required <= bytes_unused) + { + bytes_unused -= bytes_required; + dnsSessionData->bytes_seen_curr_rec += bytes_required; + data += bytes_required; + dnsSessionData->curr_txt.txt_bytes_seen += bytes_required; + if (bytes_unused == 0) + { + return bytes_unused; + } + } + else + { + dnsSessionData->curr_txt.txt_bytes_seen += bytes_unused; + dnsSessionData->bytes_seen_curr_rec += bytes_unused; + return 0; + } + break; + } + + /* Go to the next portion of the name */ + dnsSessionData->curr_txt.name_state = DNS_RESP_STATE_RR_NAME_SIZE; + } + + return bytes_unused; +} + +uint16_t SkipDNSRData(const unsigned char* data, + uint16_t bytes_unused, + DNSData* dnsSessionData) +{ + uint16_t bytes_required = dnsSessionData->curr_rr.length - dnsSessionData->bytes_seen_curr_rec; + + if (bytes_required <= bytes_unused) + { + bytes_unused -= bytes_required; + data += bytes_required; + dnsSessionData->bytes_seen_curr_rec += bytes_required; + } + else + { + dnsSessionData->bytes_seen_curr_rec += bytes_unused; + return 0; + } + + /* Got to the end of the rdata in this packet! */ + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_COMPLETE; + return bytes_unused; +} + +uint16_t ParseDNSRData(Packet*, + const unsigned char* data, + uint16_t bytes_unused, + DNSData* dnsSessionData) +{ + if (bytes_unused == 0) + { + return bytes_unused; + } + + switch (dnsSessionData->curr_rr.type) + { + case DNS_RR_TYPE_TXT: + /* Check for RData Overflow */ + bytes_unused = CheckRRTypeTXTVuln(data, bytes_unused, dnsSessionData); + break; + + case DNS_RR_TYPE_MD: + case DNS_RR_TYPE_MF: + /* Alert on obsolete DNS RR types */ + SnortEventqAdd(GID_DNS, DNS_EVENT_OBSOLETE_TYPES); + bytes_unused = SkipDNSRData(data, bytes_unused, dnsSessionData); + break; + + case DNS_RR_TYPE_MB: + case DNS_RR_TYPE_MG: + case DNS_RR_TYPE_MR: + case DNS_RR_TYPE_NULL: + case DNS_RR_TYPE_MINFO: + /* Alert on experimental DNS RR types */ + SnortEventqAdd(GID_DNS, DNS_EVENT_EXPERIMENTAL_TYPES); + bytes_unused = SkipDNSRData(data, bytes_unused, dnsSessionData); + break; + case DNS_RR_TYPE_A: + case DNS_RR_TYPE_NS: + case DNS_RR_TYPE_CNAME: + case DNS_RR_TYPE_SOA: + case DNS_RR_TYPE_WKS: + case DNS_RR_TYPE_PTR: + case DNS_RR_TYPE_HINFO: + case DNS_RR_TYPE_MX: + bytes_unused = SkipDNSRData(data, bytes_unused, dnsSessionData); + break; + default: + /* Not one of the known types. Stop looking at this session + * as DNS. */ + dnsSessionData->flags |= DNS_FLAG_NOT_DNS; + break; + } + + return bytes_unused; +} + +void ParseDNSResponseMessage(Packet* p, DNSData* dnsSessionData) +{ + uint16_t bytes_unused = p->dsize; + int i; + const unsigned char* data = p->data; + + while (bytes_unused) + { + /* Parse through the DNS Header */ + if (dnsSessionData->state < DNS_RESP_STATE_QUESTION) + { + /* Length only applies on a TCP packet, skip to header ID + * if at beginning of a UDP Response. + */ + if ((dnsSessionData->state == DNS_RESP_STATE_LENGTH) && + p->is_udp()) + { + dnsSessionData->state = DNS_RESP_STATE_HDR_ID; + } + + bytes_unused = ParseDNSHeader(data, bytes_unused, dnsSessionData); + if (bytes_unused > 0) + { + data = p->data + (p->dsize - bytes_unused); + } + else + { + /* No more data */ + return; + } + + dnsSessionData->curr_rec_state = DNS_RESP_STATE_Q_NAME; + dnsSessionData->curr_rec = 0; + } + + /* Print out the header (but only once -- when we're ready to parse the Questions */ + if ((dnsSessionData->curr_rec_state == DNS_RESP_STATE_Q_NAME) && + (dnsSessionData->curr_rec == 0)) + { + DEBUG_WRAP( + DebugMessage(DEBUG_DNS, + "DNS Header: length %d, id 0x%x, flags 0x%x, " + "questions %d, answers %d, authorities %d, additionals %d\n", + dnsSessionData->length, dnsSessionData->hdr.id, + dnsSessionData->hdr.flags, dnsSessionData->hdr.questions, + dnsSessionData->hdr.answers, + dnsSessionData->hdr.authorities, + dnsSessionData->hdr.additionals); + ); + } + + if (!(dnsSessionData->hdr.flags & DNS_HDR_FLAG_RESPONSE)) + { + /* Not a response */ + return; + } + + /* Handle the DNS Queries */ + if (dnsSessionData->state == DNS_RESP_STATE_QUESTION) + { + /* Skip over the 4 byte question records... */ + for (i=dnsSessionData->curr_rec; i< dnsSessionData->hdr.questions; i++) + { + bytes_unused = ParseDNSQuestion(data, p->dsize, bytes_unused, dnsSessionData); + + if (dnsSessionData->curr_rec_state == DNS_RESP_STATE_Q_COMPLETE) + { + DEBUG_WRAP( + DebugMessage(DEBUG_DNS, + "DNS Question %d: type %d, class %d\n", + i, dnsSessionData->curr_q.type, + dnsSessionData->curr_q.dns_class); + ); + dnsSessionData->curr_rec_state = DNS_RESP_STATE_Q_NAME; + dnsSessionData->curr_rec++; + } + if (bytes_unused > 0) + { + data = p->data + (p->dsize - bytes_unused); + } + else + { + /* No more data */ + return; + } + } + dnsSessionData->state = DNS_RESP_STATE_ANS_RR; + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_NAME_SIZE; + dnsSessionData->curr_rec = 0; + } + + /* Handle the RRs */ + switch (dnsSessionData->state) + { + case DNS_RESP_STATE_ANS_RR: /* ANSWERS section */ + for (i=dnsSessionData->curr_rec; ihdr.answers; i++) + { + bytes_unused = ParseDNSAnswer(data, p->dsize, + bytes_unused, dnsSessionData); + + if (bytes_unused == 0) + { + /* No more data */ + return; + } + + switch (dnsSessionData->curr_rec_state) + { + case DNS_RESP_STATE_RR_RDATA_START: + DEBUG_WRAP( + DebugMessage(DEBUG_DNS, + "DNS ANSWER RR %d: type %d, class %d, " + "ttl %d rdlength %d\n", i, + dnsSessionData->curr_rr.type, + dnsSessionData->curr_rr.dns_class, + dnsSessionData->curr_rr.ttl, + dnsSessionData->curr_rr.length); + ); + + dnsSessionData->bytes_seen_curr_rec = 0; + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_RDATA_MID; + /* Fall through */ + case DNS_RESP_STATE_RR_RDATA_MID: + /* Data now points to the beginning of the RDATA */ + data = p->data + (p->dsize - bytes_unused); + bytes_unused = ParseDNSRData(p, data, bytes_unused, dnsSessionData); + if (dnsSessionData->curr_rec_state != DNS_RESP_STATE_RR_COMPLETE) + { + /* Out of data, pick up on the next packet */ + return; + } + else + { + /* Go to the next record */ + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_NAME_SIZE; + dnsSessionData->curr_rec++; + + if (dnsSessionData->curr_rr.type == DNS_RR_TYPE_TXT) + { + /* Reset the state tracking for this record */ + memset(&dnsSessionData->curr_txt, 0, sizeof(DNSNameState)); + } + data = p->data + (p->dsize - bytes_unused); + } + } + } + dnsSessionData->state = DNS_RESP_STATE_AUTH_RR; + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_NAME_SIZE; + dnsSessionData->curr_rec = 0; + /* Fall through */ + case DNS_RESP_STATE_AUTH_RR: /* AUTHORITIES section */ + for (i=dnsSessionData->curr_rec; ihdr.authorities; i++) + { + bytes_unused = ParseDNSAnswer(data, p->dsize, + bytes_unused, dnsSessionData); + + if (bytes_unused == 0) + { + /* No more data */ + return; + } + + switch (dnsSessionData->curr_rec_state) + { + case DNS_RESP_STATE_RR_RDATA_START: + DEBUG_WRAP( + DebugMessage(DEBUG_DNS, + "DNS AUTH RR %d: type %d, class %d, " + "ttl %d rdlength %d\n", i, + dnsSessionData->curr_rr.type, + dnsSessionData->curr_rr.dns_class, + dnsSessionData->curr_rr.ttl, + dnsSessionData->curr_rr.length); + ); + + dnsSessionData->bytes_seen_curr_rec = 0; + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_RDATA_MID; + /* Fall through */ + case DNS_RESP_STATE_RR_RDATA_MID: + /* Data now points to the beginning of the RDATA */ + data = p->data + (p->dsize - bytes_unused); + bytes_unused = ParseDNSRData(p, data, bytes_unused, dnsSessionData); + if (dnsSessionData->curr_rec_state != DNS_RESP_STATE_RR_COMPLETE) + { + /* Out of data, pick up on the next packet */ + return; + } + else + { + /* Go to the next record */ + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_NAME_SIZE; + dnsSessionData->curr_rec++; + + if (dnsSessionData->curr_rr.type == DNS_RR_TYPE_TXT) + { + /* Reset the state tracking for this record */ + memset(&dnsSessionData->curr_txt, 0, sizeof(DNSNameState)); + } + data = p->data + (p->dsize - bytes_unused); + } + } + } + dnsSessionData->state = DNS_RESP_STATE_ADD_RR; + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_NAME_SIZE; + dnsSessionData->curr_rec = 0; + /* Fall through */ + case DNS_RESP_STATE_ADD_RR: /* ADDITIONALS section */ + for (i=dnsSessionData->curr_rec; ihdr.authorities; i++) + { + bytes_unused = ParseDNSAnswer(data, p->dsize, + bytes_unused, dnsSessionData); + + if (bytes_unused == 0) + { + /* No more data */ + return; + } + + switch (dnsSessionData->curr_rec_state) + { + case DNS_RESP_STATE_RR_RDATA_START: + DEBUG_WRAP( + DebugMessage(DEBUG_DNS, + "DNS ADDITONAL RR %d: type %d, class %d, " + "ttl %d rdlength %d\n", i, + dnsSessionData->curr_rr.type, + dnsSessionData->curr_rr.dns_class, + dnsSessionData->curr_rr.ttl, + dnsSessionData->curr_rr.length); + ); + + dnsSessionData->bytes_seen_curr_rec = 0; + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_RDATA_MID; + /* Fall through */ + case DNS_RESP_STATE_RR_RDATA_MID: + /* Data now points to the beginning of the RDATA */ + data = p->data + (p->dsize - bytes_unused); + bytes_unused = ParseDNSRData(p, data, bytes_unused, dnsSessionData); + if (dnsSessionData->curr_rec_state != DNS_RESP_STATE_RR_COMPLETE) + { + /* Out of data, pick up on the next packet */ + return; + } + else + { + /* Go to the next record */ + dnsSessionData->curr_rec_state = DNS_RESP_STATE_RR_NAME_SIZE; + dnsSessionData->curr_rec++; + + if (dnsSessionData->curr_rr.type == DNS_RR_TYPE_TXT) + { + /* Reset the state tracking for this record */ + memset(&dnsSessionData->curr_txt, 0, sizeof(DNSNameState)); + } + data = p->data + (p->dsize - bytes_unused); + } + } + } + /* Done with this one, onto the next -- may also be in this packet */ + dnsSessionData->state = DNS_RESP_STATE_LENGTH; + dnsSessionData->curr_rec_state = 0; + dnsSessionData->curr_rec = 0; + } + } +} + +static void snort_dns(Packet* p) +{ + DNSData* dnsSessionData = NULL; + uint8_t direction = 0; + PROFILE_VARS; + + /* For TCP, do a few extra checks... */ + if (p->is_udp()) + { + /* If session picked up mid-stream, do not process further. + * Would be almost impossible to tell where we are in the + * data stream. */ + if ( stream.get_session_flags(p->flow) & SSNFLAG_MIDSTREAM ) + { + return; + } + + if ( stream.is_stream_sequenced(p->flow, SSN_DIR_FROM_CLIENT) ) + { + return; + } + + /* If we're waiting on stream reassembly, don't process this packet. */ + if ( p->packet_flags & PKT_STREAM_INSERT ) + { + return; + } + } + + /* Get the direction of the packet. */ + direction = ( (p->packet_flags & PKT_FROM_SERVER ) ? + DNS_DIR_FROM_SERVER : DNS_DIR_FROM_CLIENT ); + + MODULE_PROFILE_START(dnsPerfStats); + + /* Attempt to get a previously allocated DNS block. */ + dnsSessionData = get_dns_session_data(p); + + if (dnsSessionData == NULL) + { + /* Check the stream session. If it does not currently + * have our DNS data-block attached, create one. + */ + dnsSessionData = SetNewDNSData(p); + + if ( !dnsSessionData ) + { + /* Could not get/create the session data for this packet. */ + MODULE_PROFILE_END(dnsPerfStats); + return; + } + } + + if (dnsSessionData->flags & DNS_FLAG_NOT_DNS) + { + MODULE_PROFILE_END(dnsPerfStats); + return; + } + + if (direction == DNS_DIR_FROM_SERVER) + { + ParseDNSResponseMessage(p, dnsSessionData); + } + + MODULE_PROFILE_END(dnsPerfStats); +} + +//------------------------------------------------------------------------- +// class stuff +//------------------------------------------------------------------------- + +class Dns : public Inspector +{ +public: + Dns(DnsModule*); + + void show(SnortConfig*) override; + void eval(Packet*) override; +}; + +Dns::Dns(DnsModule*) +{ } + +void Dns::show(SnortConfig*) +{ + LogMessage("DNS\n"); +} + +void Dns::eval(Packet* p) +{ + // precondition - what we registered for + assert(p->is_udp() && p->is_tcp() && p->dsize && p->data); + + ++dnsstats.total_packets; + snort_dns(p); +} + +//------------------------------------------------------------------------- +// api stuff +//------------------------------------------------------------------------- + +static Module* mod_ctor() +{ return new DnsModule; } + +static void mod_dtor(Module* m) +{ delete m; } + +static void dns_init() +{ + DnsFlowData::init(); +} + +static Inspector* dns_ctor(Module* m) +{ + DnsModule* mod = (DnsModule*)m; + return new Dns(mod); +} + +static void dns_dtor(Inspector* p) +{ + delete p; +} + +const InspectApi dns_api = +{ + { + PT_INSPECTOR, + sizeof(InspectApi), + INSAPI_VERSION, + 0, + API_RESERVED, + API_OPTIONS, + DNS_NAME, + DNS_HELP, + mod_ctor, + mod_dtor + }, + IT_SERVICE, + (uint16_t)PktType::TCP | (uint16_t)PktType::UDP, + nullptr, // buffers + "dns", + dns_init, + nullptr, // pterm + nullptr, // tinit + nullptr, // tterm + dns_ctor, + dns_dtor, + nullptr, // ssn + nullptr // reset +}; + +#ifdef BUILDING_SO +SO_PUBLIC const BaseApi* snort_plugins[] = +{ + &dns_api.base, + nullptr +}; +#else +const BaseApi* sin_dns = &dns_api.base; +#endif + diff --git a/src/service_inspectors/dns/dns.h b/src/service_inspectors/dns/dns.h new file mode 100644 index 000000000..11a6dc2d8 --- /dev/null +++ b/src/service_inspectors/dns/dns.h @@ -0,0 +1,201 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2015 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2004-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. +//-------------------------------------------------------------------------- +// + +/* + * dns.h: Definitions, structs, function prototype(s) for + * the DNS service inspectors. + * Author: Chris Sherwin + */ + +#ifndef DNS_H +#define DNS_H + +#include "protocols/packet.h" +#include "stream/stream_api.h" +#include "profiler.h" + +/* + * Directional defines + */ +#define DNS_DIR_FROM_SERVER 1 +#define DNS_DIR_FROM_CLIENT 2 + +/****** A few data structures ******/ +typedef struct _DNSHdr +{ + uint16_t id; + uint16_t flags; + uint16_t questions; + uint16_t answers; + uint16_t authorities; + uint16_t additionals; +} DNSHdr; + +#define DNS_HDR_FLAG_REPLY_CODE_MASK 0x000F +#define DNS_HDR_FLAG_NON_AUTHENTICATED_OK 0x0010 +#define DNS_HDR_FLAG_ANS_AUTHENTICATED 0x0020 +#define DNS_HDR_FLAG_RESERVED 0x0040 +#define DNS_HDR_FLAG_RECURSION_AVAIL 0x0080 +#define DNS_HDR_FLAG_RECURSION_DESIRED 0x0100 +#define DNS_HDR_FLAG_TRUNCATED 0x0200 +#define DNS_HDR_FLAG_AUTHORITATIVE 0x0400 +#define DNS_HDR_FLAG_OPCODE_MASK 0x7800 +#define DNS_HDR_FLAG_RESPONSE 0x8000 + +typedef struct _DNSQuestion +{ + uint16_t type; + uint16_t dns_class; +} DNSQuestion; + +typedef struct _DNSRR +{ + uint16_t type; + uint16_t dns_class; + uint32_t ttl; + uint16_t length; +} DNSRR; + +typedef struct _DNSNameState +{ + uint32_t txt_count; + uint32_t total_txt_len; + uint8_t txt_len; + uint8_t txt_bytes_seen; + uint8_t name_state; + uint8_t alerted; + uint16_t offset; + uint8_t relative; +} DNSNameState; + +#define DNS_RR_TYPE_A 0x0001 +#define DNS_RR_TYPE_NS 0x0002 +#define DNS_RR_TYPE_MD 0x0003 /* obsolete */ +#define DNS_RR_TYPE_MF 0x0004 /* obsolete */ +#define DNS_RR_TYPE_CNAME 0x0005 +#define DNS_RR_TYPE_SOA 0x0006 +#define DNS_RR_TYPE_MB 0x0007 /* experimental */ +#define DNS_RR_TYPE_MG 0x0008 /* experimental */ +#define DNS_RR_TYPE_MR 0x0009 /* experimental */ +#define DNS_RR_TYPE_NULL 0x000a /* experimental */ +#define DNS_RR_TYPE_WKS 0x000b +#define DNS_RR_TYPE_PTR 0x000c +#define DNS_RR_TYPE_HINFO 0x000d +#define DNS_RR_TYPE_MINFO 0x000e /* experimental */ +#define DNS_RR_TYPE_MX 0x000f +#define DNS_RR_TYPE_TXT 0x0010 + +#define DNS_FLAG_NOT_DNS 0x01 + +/* DNSSessionData States */ +#define DNS_RESP_STATE_LENGTH 0x00 /* 2 bytes - TCP only*/ +#define DNS_RESP_STATE_LENGTH_PART 0x01 /* Partial length */ + +#define DNS_RESP_STATE_HDR 0x10 /* 12 bytes */ +#define DNS_RESP_STATE_HDR_ID 0x11 /* (2 bytes) */ +#define DNS_RESP_STATE_HDR_ID_PART 0x12 /* (2 bytes) */ +#define DNS_RESP_STATE_HDR_FLAGS 0x13 /* (2 bytes) */ +#define DNS_RESP_STATE_HDR_FLAGS_PART 0x14 /* (2 bytes) */ +#define DNS_RESP_STATE_HDR_QS 0x15 /* (2 bytes) */ +#define DNS_RESP_STATE_HDR_QS_PART 0x16 /* (2 bytes) */ +#define DNS_RESP_STATE_HDR_ANSS 0x17 /* (2 bytes) */ +#define DNS_RESP_STATE_HDR_ANSS_PART 0x18 /* (2 bytes) */ +#define DNS_RESP_STATE_HDR_AUTHS 0x19 /* (2 bytes) */ +#define DNS_RESP_STATE_HDR_AUTHS_PART 0x1a /* (2 bytes) */ +#define DNS_RESP_STATE_HDR_ADDS 0x1b /* (2 bytes) */ +#define DNS_RESP_STATE_HDR_ADDS_PART 0x1c /* (2 bytes) */ + +#define DNS_RESP_STATE_QUESTION 0x20 /* 4 bytes */ +#define DNS_RESP_STATE_Q_NAME 0x21 /* (size depends on data) */ +#define DNS_RESP_STATE_Q_NAME_COMPLETE 0x22 /* (size depends on data) */ +#define DNS_RESP_STATE_Q_TYPE 0x23 /* (2 bytes) */ +#define DNS_RESP_STATE_Q_TYPE_PART 0x24 /* (2 bytes) */ +#define DNS_RESP_STATE_Q_CLASS 0x25 /* (2 bytes) */ +#define DNS_RESP_STATE_Q_CLASS_PART 0x26 /* (2 bytes) */ +#define DNS_RESP_STATE_Q_COMPLETE 0x27 + +#define DNS_RESP_STATE_NAME_SIZE 0x31 /* (1 byte) */ +#define DNS_RESP_STATE_NAME 0x32 /* (size depends on field) */ +#define DNS_RESP_STATE_NAME_COMPLETE 0x33 + +#define DNS_RESP_STATE_ANS_RR 0x40 /* (size depends on field) */ +#define DNS_RESP_STATE_RR_NAME_SIZE 0x41 /* (1 byte) */ +#define DNS_RESP_STATE_RR_NAME 0x42 /* (size depends on field) */ +#define DNS_RESP_STATE_RR_NAME_COMPLETE 0x43 +#define DNS_RESP_STATE_RR_TYPE 0x44 /* (2 bytes) */ +#define DNS_RESP_STATE_RR_TYPE_PART 0x45 /* (2 bytes) */ +#define DNS_RESP_STATE_RR_CLASS 0x46 /* (2 bytes) */ +#define DNS_RESP_STATE_RR_CLASS_PART 0x47 /* (2 bytes) */ +#define DNS_RESP_STATE_RR_TTL 0x48 /* (4 bytes) */ +#define DNS_RESP_STATE_RR_TTL_PART 0x49 /* (4 bytes) */ +#define DNS_RESP_STATE_RR_RDLENGTH 0x4a /* (2 bytes) */ +#define DNS_RESP_STATE_RR_RDLENGTH_PART 0x4b /* (2 bytes) */ +#define DNS_RESP_STATE_RR_RDATA_START 0x4c /* (size depends on RDLENGTH) */ +#define DNS_RESP_STATE_RR_RDATA_MID 0x4d /* (size depends on RDLENGTH) */ +#define DNS_RESP_STATE_RR_COMPLETE 0x4e + +#define DNS_RESP_STATE_AUTH_RR 0x50 +#define DNS_RESP_STATE_ADD_RR 0x60 + +/* + * Per-session data block containing current state + * of the DNS preprocessor for the session. + * + * state: The current state of the session. + * num_records: Number of records in the session. + * curr_record: Record number for the current record + * curr_record_length: Current record length. + * total_record_length: Total data length of records. + * length: Total length of DNS response (TCP only) + * hdr: Copy of the data from the DNS Header + */ +struct DNSData +{ + uint32_t state; + uint16_t curr_rec; + uint16_t curr_rec_length; + uint16_t bytes_seen_curr_rec; + uint16_t length; + uint8_t curr_rec_state; + DNSHdr hdr; + DNSQuestion curr_q; + DNSRR curr_rr; + DNSNameState curr_txt; + uint8_t flags; +}; + +class DnsFlowData : public FlowData +{ +public: + DnsFlowData() : FlowData(flow_id) + { memset(&session, 0, sizeof(session)); } + + ~DnsFlowData() { } + + static void init() + { flow_id = FlowData::get_flow_id(); } + +public: + static unsigned flow_id; + DNSData session; +}; + +#endif /* DNS_H */ + diff --git a/src/service_inspectors/dns/dns_module.cc b/src/service_inspectors/dns/dns_module.cc new file mode 100644 index 000000000..21e43d4f5 --- /dev/null +++ b/src/service_inspectors/dns/dns_module.cc @@ -0,0 +1,67 @@ +//-------------------------------------------------------------------------- +// 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. +//-------------------------------------------------------------------------- + +// dns_module.cc author Bhagyashree Bantwal + +#include "dns_module.h" +#include +#include +#include "main/snort_config.h" + +using namespace std; + +#define DNS_EVENT_OBSOLETE_TYPES_STR \ + "Obsolete DNS RR Types" +#define DNS_EVENT_EXPERIMENTAL_TYPES_STR \ + "Experimental DNS RR Types" +#define DNS_EVENT_RDATA_OVERFLOW_STR \ + "DNS Client rdata txt Overflow" + +static const Parameter s_params[] = +{ + { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr } +}; + +static const RuleMap dns_rules[] = +{ + { DNS_EVENT_OBSOLETE_TYPES, DNS_EVENT_OBSOLETE_TYPES_STR }, + { DNS_EVENT_EXPERIMENTAL_TYPES, DNS_EVENT_EXPERIMENTAL_TYPES_STR }, + { DNS_EVENT_RDATA_OVERFLOW, DNS_EVENT_RDATA_OVERFLOW_STR }, + + { 0, nullptr } +}; + +//------------------------------------------------------------------------- +// dns module +//------------------------------------------------------------------------- + +DnsModule::DnsModule() : Module(DNS_NAME, DNS_HELP, s_params) +{ } + +const RuleMap* DnsModule::get_rules() const +{ return dns_rules; } + +const PegInfo* DnsModule::get_pegs() const +{ return simple_pegs; } + +PegCount* DnsModule::get_counts() const +{ return (PegCount*)&dnsstats; } + +ProfileStats* DnsModule::get_profile() const +{ return &dnsPerfStats; } + diff --git a/src/service_inspectors/dns/dns_module.h b/src/service_inspectors/dns/dns_module.h new file mode 100644 index 000000000..7edba8d2a --- /dev/null +++ b/src/service_inspectors/dns/dns_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. +//-------------------------------------------------------------------------- + +// dns_module.h author Bhagyashree Bantwal + +#ifndef DNS_MODULE_H +#define DNS_MODULE_H + +#include "framework/module.h" +#include "framework/bits.h" +#include "main/thread.h" + +#define GID_DNS 131 + +#define DNS_EVENT_OBSOLETE_TYPES 1 +#define DNS_EVENT_EXPERIMENTAL_TYPES 2 +#define DNS_EVENT_RDATA_OVERFLOW 3 + +#define DNS_NAME "dns" +#define DNS_HELP "dns inspection" + +struct SnortConfig; + +extern THREAD_LOCAL SimpleStats dnsstats; +extern THREAD_LOCAL ProfileStats dnsPerfStats; + +class DnsModule : public Module +{ +public: + DnsModule(); + + bool set(const char*, Value&, SnortConfig*) override + { return false; } + + unsigned get_gid() const override + { return GID_DNS; } + + const RuleMap* get_rules() const override; + const PegInfo* get_pegs() const override; + PegCount* get_counts() const override; + ProfileStats* get_profile() const override; +}; + +#endif + diff --git a/src/service_inspectors/service_inspectors.cc b/src/service_inspectors/service_inspectors.cc index dc625cbec..ae4ae7461 100644 --- a/src/service_inspectors/service_inspectors.cc +++ b/src/service_inspectors/service_inspectors.cc @@ -29,6 +29,7 @@ extern const BaseApi* sin_http_inspect; #ifdef STATIC_INSPECTORS extern const BaseApi* sin_bo; +extern const BaseApi* sin_dns; extern const BaseApi* sin_ftp_client; extern const BaseApi* sin_ftp_server; extern const BaseApi* sin_ftp_data; @@ -46,6 +47,7 @@ const BaseApi* service_inspectors[] = #ifdef STATIC_INSPECTORS sin_bo, + sin_dns, sin_ftp_client, sin_ftp_server, sin_ftp_data, diff --git a/src/service_inspectors/ssh/ssh.cc b/src/service_inspectors/ssh/ssh.cc index d0b74b4fc..1e062513e 100644 --- a/src/service_inspectors/ssh/ssh.cc +++ b/src/service_inspectors/ssh/ssh.cc @@ -363,8 +363,7 @@ static inline int SSHCheckStrlen(char* str, int max) * p: Pointer to the packet to inspect. * direction: Which direction the packet is going. * - * RETURNS: SSH_SUCCESS, if successfully processed a proto exch msg - * SSH_FAILURE, otherwise. + * RETURNS: offset processed */ static unsigned int ProcessSSHProtocolVersionExchange(SSH_PROTO_CONF* config, SSHData* sessionp, Packet* p, uint8_t direction) @@ -444,8 +443,7 @@ static unsigned int ProcessSSHProtocolVersionExchange(SSH_PROTO_CONF* config, SS * p: Pointer to the packet to inspect. * direction: Which direction the packet is going. * - * RETURN: SSH_SUCCESS, if a valid key exchange message is processed - * SSH_FAILURE, otherwise. + * RETURNS: offset processed */ static unsigned int ProcessSSHKeyInitExchange(SSHData* sessionp, Packet* p, uint8_t direction, unsigned int offset) @@ -622,8 +620,7 @@ static unsigned int ProcessSSHKeyInitExchange(SSHData* sessionp, Packet* p, * p: Pointer to the packet to inspect. * direction: Which direction the packet is going. * - * RETURN: SSH_SUCCESS, if a valid key exchange message is processed - * SSH_FAILURE, otherwise. + * RETURNS: offset processed */ static unsigned int ProcessSSHKeyExchange(SSHData* sessionp, Packet* p, uint8_t direction, unsigned int offset) diff --git a/src/service_inspectors/ssh/ssh.h b/src/service_inspectors/ssh/ssh.h index a2f075b0f..268ffbb70 100644 --- a/src/service_inspectors/ssh/ssh.h +++ b/src/service_inspectors/ssh/ssh.h @@ -67,8 +67,6 @@ public: SSHData session; }; -#define SSH_TRUE (1) -#define SSH_FALSE (0) /* * * Error codes. @@ -178,32 +176,5 @@ typedef struct _ssh2Packet #define SSH_DIR_FROM_SERVER (0x1) #define SSH_DIR_FROM_CLIENT (0x2) -/* - * Keyword strings for parsing configuration options. - */ -#define SSH_MAX_ENC_PKTS_KEYWORD "max_encrypted_packets" -#define SSH_MAX_CLIENT_BYTES_KEYWORD "max_client_bytes" -#define SSH_MAX_SERVER_VERSION_KEYWORD "max_server_version_len" -#define SSH_ENABLE_RESPOVERFLOW_KEYWORD "enable_respoverflow" -#define SSH_ENABLE_CRC32_KEYWORD "enable_ssh1crc32" -#define SSH_ENABLE_SECURECRT_KEYWORD "enable_srvoverflow" -#define SSH_ENABLE_PROTOMISMATCH_KEYWORD "enable_protomismatch" -#define SSH_ENABLE_WRONGDIR_KEYWORD "enable_badmsgdir" -#define SSH_ENABLE_PAYLOAD_SIZE "enable_paysize" -#define SSH_ENABLE_UNRECOGNIZED_VER "enable_recognition" - -/* - * SSH alert flags - */ -#define SSH_ALERT_NONE (0x0) -#define SSH_ALERT_RESPOVERFLOW (0x1) -#define SSH_ALERT_CRC32 (0x2) -#define SSH_ALERT_SECURECRT (0x4) -#define SSH_ALERT_PROTOMISMATCH (0x8) -#define SSH_ALERT_WRONGDIR (0x10) -#define SSH_ALERT_PAYSIZE (0x20) -#define SSH_ALERT_UNRECOGNIZED (0x40) -#define SSH_ALERT_ALL (0xFFFF) - #endif /* SSH_H */ diff --git a/src/service_inspectors/ssh/ssh_config.h b/src/service_inspectors/ssh/ssh_config.h index 019c3cd2b..e8d6a8061 100644 --- a/src/service_inspectors/ssh/ssh_config.h +++ b/src/service_inspectors/ssh/ssh_config.h @@ -29,22 +29,18 @@ /* * Global SSH preprocessor configuration. * - * AutodetectEnabled: Whether or not to apply auto-detection of SSH - * to ports other than those configured. * MaxEncryptedPackets: Maximum number of encrypted packets examined per * session. * MaxClientBytes: Maximum bytes of encrypted data that can be * sent by client without a server response. * MaxServerVersionLen: Maximum length of a server's version string. * Configurable threshold for Secure CRT-style overflow. - * EnabledAlerts: Bit vector describing which alerts are enabled. */ struct SSH_PROTO_CONF { uint16_t MaxEncryptedPackets; uint16_t MaxClientBytes; uint16_t MaxServerVersionLen; - bool AutodetectEnabled; }; #define SSH_DEFAULT_MAX_ENC_PKTS 25 diff --git a/tools/snort2lua/preprocessor_states/CMakeLists.txt b/tools/snort2lua/preprocessor_states/CMakeLists.txt index 56c7ba203..67f5a04f5 100644 --- a/tools/snort2lua/preprocessor_states/CMakeLists.txt +++ b/tools/snort2lua/preprocessor_states/CMakeLists.txt @@ -13,6 +13,7 @@ add_library(preprocessor_states pps_perfmonitor.cc pps_rpc_decode.cc pps_ssh.cc + pps_dns.cc pps_sfportscan.cc pps_stream5_ip.cc pps_stream5_global.cc diff --git a/tools/snort2lua/preprocessor_states/Makefile.am b/tools/snort2lua/preprocessor_states/Makefile.am index 478d32635..d88d0f06e 100644 --- a/tools/snort2lua/preprocessor_states/Makefile.am +++ b/tools/snort2lua/preprocessor_states/Makefile.am @@ -17,6 +17,7 @@ pps_normalizers.cc \ pps_perfmonitor.cc \ pps_rpc_decode.cc \ pps_ssh.cc \ +pps_dns.cc \ pps_sfportscan.cc \ pps_stream5_ip.cc \ pps_stream5_global.cc \ diff --git a/tools/snort2lua/preprocessor_states/pps_dns.cc b/tools/snort2lua/preprocessor_states/pps_dns.cc new file mode 100644 index 000000000..88e4b095d --- /dev/null +++ b/tools/snort2lua/preprocessor_states/pps_dns.cc @@ -0,0 +1,123 @@ +//-------------------------------------------------------------------------- +// 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_dns.cc author Bhagya Bantwal + +#include +#include + +#include "conversion_state.h" +#include "helpers/s2l_util.h" +#include "helpers/util_binder.h" + +namespace preprocessors +{ +namespace +{ +class Dns : public ConversionState +{ +public: + Dns(Converter& c) : ConversionState(c) { } + virtual ~Dns() { } + virtual bool convert(std::istringstream& data_stream); + +}; +} // namespace + +bool Dns::convert(std::istringstream& data_stream) +{ + std::string keyword; + bool retval = true; + bool ports_set = false; + Binder bind(table_api); + + bind.set_when_proto("tcp"); + bind.set_use_type("dns"); + + table_api.open_table("dns"); + + + // parse the file configuration + while (data_stream >> keyword) + { + bool tmpval = true; + + if (!keyword.compare("enable_obsolete_types")) + table_api.add_deleted_comment("enable_obsolete_types"); + + else if (!keyword.compare("enable_experimental_types")) + table_api.add_deleted_comment("enable_experimental_types"); + + else if (!keyword.compare("enable_rdata_overflow")) + table_api.add_deleted_comment("enable_rdata_overflow"); + + 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; + 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) + bind.add_when_port("53"); + + return retval; +} + +/************************** + ******* A P I *********** + **************************/ + +static ConversionState* ctor(Converter& c) +{ + return new Dns(c); +} + +static const ConvertMap preprocessor_dns = +{ + "dns", + ctor, +}; + +const ConvertMap* dns_map = &preprocessor_dns; +} + diff --git a/tools/snort2lua/preprocessor_states/preprocessor_api.cc b/tools/snort2lua/preprocessor_states/preprocessor_api.cc index ac6dcc119..c8e77a4c9 100644 --- a/tools/snort2lua/preprocessor_states/preprocessor_api.cc +++ b/tools/snort2lua/preprocessor_states/preprocessor_api.cc @@ -39,6 +39,7 @@ extern const ConvertMap* normalizer_tcp_map; extern const ConvertMap* perfmonitor_map; extern const ConvertMap* rpc_decode_map; extern const ConvertMap* ssh_map; +extern const ConvertMap* dns_map; extern const ConvertMap* sfportscan_map; extern const ConvertMap* stream_ip_map; extern const ConvertMap* stream_global_map; @@ -65,6 +66,7 @@ const std::vector preprocessor_api = perfmonitor_map, rpc_decode_map, ssh_map, + dns_map, sfportscan_map, stream_ip_map, stream_global_map, diff --git a/tools/snort2lua/tests/snort.conf.in b/tools/snort2lua/tests/snort.conf.in index 253d5cd14..c6bcdfc3f 100644 --- a/tools/snort2lua/tests/snort.conf.in +++ b/tools/snort2lua/tests/snort.conf.in @@ -791,7 +791,7 @@ preprocessor ssh: server_ports { 22 } \ # # TODO: Add once this is included in Snort++ -#preprocessor dns: ports { 53 } enable_rdata_overflow +preprocessor dns: ports { 53 } enable_rdata_overflow # TODO: Add once this is included in Snort++