Back

DNS Preprocessor for 2.8.3, Snort DNS Preprocessor 1.4, DNS Plugin 1.2, and
Shared Resolver Library



Overview of New Software:

On the suggestion of Andre Ludwig, I ported the crusty old DNS preprocessor code to a newer version (2.8.3.beta), and am releasing it for general use. In it's current form I just made a few modifications for data structure changes and attached it to the excellent existing DNS preprocessor. Testing for the legitimacy of traffic has also been removed as it would be redundant.

There is a significant chance that additional changes will be made as feedback returns with regard to functionality and tuning. I will try to keep a list of changes available here regarding this.

New options for snort.conf are:

# dns_session : activates session monitoring
# dns_state_window

Sample use of new functionality:
preprocessor dns: \
ports { 53 } \
enable_rdata_overflow \
enable_experimental_types \
dns_session

A tarball of the current preprocessor and libs is here:
dns_state.1.tar.gz

which can be rolled out from the source root.
Overview:

The DNS preprocessor and plugin have been designed to work together in order to keep an eye on the DNS traffic that passes across your network. The majority of traffic is functional and uninteresting, but these tools will allow the detection of data streams in normal DNS channels, state problems associated with the same traffic, and packets that are technically within spec, but exhibit highly suspicious characteristics.

In the 1.9 version, I have combined the two software components and a shared resolver library into a single package. The shared resolver library was done to simplify the code base and to prevent possible problems with version controll. It is not necessisary to use both components if you are not interested in the functionality provided.

The individual components are described below:

  • DNS Preprocessor: This will decode all UDP port 53 traffic, making sure that it is of legitimate format. In addition it maintains state of the client <-> server connections, making sure that the 'balance of traffic' is not too unusual.
  • DNS Plugin: While the preprocessor watches the overall movement of the traffic, the plugin allows you to write rules to alert on packets with unusual header or data characteristics. These characteristics are highly site specific, but there are some things that you just shouldn't be seeing in a packet.
  • DNS Package Install Directions

  • DNS Header data structure.
In the case of the plugin, I will briefly describe what is required to extend the notion of the Packet data type to incorporate some kind of protocol smarts and to write rules based on this information. In addition, since it is more complex on the user end of things, more information regarding configuration and setup will be provided.



Snort DNS preprocessor

It is not unusual for firewalls to allow DNS traffic to connect from a host (pc) to an arbitrary server on the internet. This makes it an ideal vehicle for user's unregulated traffic. This plugin watches this traffic on two levels. The first verifies that UDP port 53 traffic is composed of the correct data structures. The second watches the connections between client and server, and makes sure that each request is getting a single response.


Software:
Click on the following links to download the complete source code. Follow directions below (or see README.PLUGINS).

snort_dns_package1.4.tar.gz

Example A.C.I.D Screenshot can be seen here.


Change log:
    spp_dns_session changes for 1.4.1 : 7/23/02

  • Andreas Östling provided a quick patch for null arguments in snort.conf file. Thanks!
  • spp_dns_session changes for 1.4 : 7/15/02

  • Added new check for multiple responses to a client query. Changed debug info to new 1.9 handlers. Checked source against a series of recursive attacks, modify header files for 1.9 additions. Also modified the logging functionality to keep it updated with newer preprocessors.
  • spp_dns_session changes for 1.3 : 7/08/02

  • Audited code against Bind 8.3.3 - no major changes, but the special() call has been augmented with "(" and ")" characters. Note, this is the last version for 1.8.x .

  • spp_dns_session changes for 1.2 : 2/19/02

  • Removed dependency on -lresolv libraries by digging out all the routines from bind and jamming them into the spp.
  • Expressly define header options and begin making room for clean/unclean decision based on header contents.
  • Removed include requirements for arpa/nameser_compat.h, arpa/nameser.h and resolv.h by rudely packing them into the generic header for the spp.

  • spp_dns_session changes for 1.1 : 1/21/02

  • Added GPL to code. Doh!
  • Changed the number of connection attempts to three before setting off an alarm. DNS requests on a fast LAN will normally be 1:1 to server responses. On a slower WAN connection we may see two or three client connection attempts (with same UDP port and DNS ID number) before a response is heard (depending on the client being used). This should greatly reduce the number of false alerts

Theory of Operation

The preprocessor will look at all UDP traffic going to and from port 53. This covers most standard DNS query and response behavior. When a packet is received, it is first run through a series of generic resolver routines based on the BSD version of Bind. If the packet fails to successfully decode the packet (for example it is a instant messenger communication), it is logged and an alarm is thrown. If, after successful decoding, there is data remaining, it too is flagged as an error condition.

If an individual were to stream data through snort via legitimate DNS packets, the previous analysis would not pick up the problem. To solve this, the preprocessor keeps track of DNS query - response pairs via the source port, destination port and ID number triplet. When a query is seen, an entry is made into the session list. After, responses are compared to the list entries. If there is a matching entry it is adjusted to reflect this new information. If there is no such entry, this is seen as an error condition. If there has been a response already, this is also flagged as an error condition.

An important tuning value is the number of seconds that packets are allowed to live on the list. The default value for this is 20 seconds. If it is set too low, slow responses will be missed and there will be too many "ANSWER_ERROR_CONDITION" messages. If it is too high, the list will grow unreasonably large and overall Snort performance will be impacted.

There are four messages given by the plugin:

  • spp_dns(%d): resource record expansion failure
    This error indicates that the UDP port 53 traffic can not be fit into the resolver templates. This usually indicates that there is traffic going out/in that is not legitimate DNS data.
  • spp_dns(%d): extra data in dns packet
    This indicates that there is data left over in the packet after the resolver takes it apart. This is unusual since there is no place in the protocol for this sort of thing. I did not see this message with legitimate traffic over a period of a month or so, but an unusual client/server may do this.
  • spp_dns(%d): unknown DNS session traffic
    Here we have session traffic that the proprocessor doesn't know what to do with. This is expected behavior once in a while, but if there are a series of them in a row it may be worth investigating.
  • spp_dns(%d): multiple responses to query - possible spoof
    In this situation, we are seeing more than one reply to the users question. This is suspicous behavior since you are only suposed to get one response back from a unique server. If there is more than one reply with the same IP and ID information, somthing may be wrong. There is a window where the check is done to reduce the (slight) possablilty of a false positive.
With (%d) being the snort sensor number.

As mentioned above, the most useful parameter to tune will be the lifetime timeout value for linked list members. In general the default value of 20 seconds does not lead to too many false alarms, but if the network connection is a little slow, you will probably need to turn up this value. If the snort instance is running between two fast lans, it may be advised to lower the value for performance reasons.

The other three flags are self explanatory. Each will disable one of the mechanisms used by the plugin. If there is no session monitoring, the multiple response flag will be ignored.

Gratitude

I would like to thank Martin Roesch and the entire snort community for providing a well written code base to work from. I have borrowed liberally from the general snort code and have learned enormously from this work. The Oreilly "DNS and Bind" book and Stevens "TCP IP Illustrated" have made the complex DNS protocol less mysterious.


Snort DNS Plugin

The function of a plugin is to provide an access point for a ruleset to make decisions based on network content. In order to provide this access, it was recessionary to extend the basic Packet data type to provide for the ability to make these decisions. Below you will find comments on the configuration and writing of rulesets for this plugin, then a short section on what is required to extend the Packet type and interact with the new information. I found a general lack of documentation on this (and in the open source spirit) will provide some insite on what I have figured out. Hopefully the information is not in the FAQ - I would not want to be needlessly responsible for making people drink more than they need to.

The sections below are:


Software:
Click on the following links to download the source code. Follow directions below (or see README.PLUGINS).

snort_dns_package1.4.tar.gz


Change log:
    sp_dns_head changes for 1.2 : 7/15/02

  • Changed debug info to new 1.9 handlers. Checked source against a series of recursive attacks, modify header files for 1.9 additions.
  • sp_dns_head changes for 1.1 : 7/08/02

  • Audited code against Bind 8.3.3 - no major changes, but the special() call has been augmented with "(" and ")" characters

  • sp_dns_head changes for 1.0 : 3/13/02

  • Initial release. Since I am not a "Software Developer" by trade, I will not only start numbering the version at 1.0, but am hopeful that the release is without bugs.

Plugin options:
The following represent the possible sets of options that are allowed for the plugin. In all cases, the entry will look like:
alert udp any 53 -> any any (dns:QUERY,R; msg:sample_dns_command;)
NOTE - There should be no space between the comma and the second entry (in this case a "R").

A complete listing of all values is as follows:

QUERY - query/response QUERY options
Q = query
R = response
OPCODE - standard/inverse/server status OPCODE options
SQ = standard query
IQ = inverse query
SS = server status request
OTHER = reserved/unused
AA - authorative answer N = not authorative
Y = authorative for answer
TC - truncated ( ex len > 512 on UDP) N = not truncated
Y = truncated
RD - recursion desired N = no recursion
Y = recursion
RA - recursion available N = no recursion (not normal)
Y = recursion
ZERO - this block *must* be zero CHECK = will flag on any non-zero data
RCODE - return code (ie error/noerror) RCODE Options
NOERR = no error
FORMERR = format error
SERFAIL = server failure
NAMEERR = dn referenced does not exist
NOIMP = requested serv not implemented on server
REFUSED = ns refuses for policy reasons
RESERVED = reserved for future use

The following values can also be filtered on, but they require the data packet to be disassembled which is a much more complex and time consuming operation. These values have some of the most interesting information in them, but should not be activated on a loaded snort instance.
QTYPE - query type QTYPE Options
A (1) = IP Address
NS (2) = name server
CNAME (5) = canonical name
PTR (12) = pointer record
HINFO (13) = host info
MX (15) = mail exchange information
AXFR (252) = zone transfer request
ANY (255) = request for all records
QCLASS - query class QCLASS Options
I (1) = internet address
X (0) = everything else including:
cookie
unallocated/unsupported (2)
MIT Chaos-net (3)
MIT Hesiod (4)
none (254)
wildcard match (255)
TTL - time to live Command option:
The TTL value is the number of seconds the RR can be cached by the client. The argument presented here will flag any DNS packet with a TTL less than the value listed here. This is different than any of the other arguments since it is an actual value.


Sample Rules:
A series of sample rules are as follows. Do not just cut and paste this into your configuration since it will just log every DNS packet and clog up your machine.
# error on name lookup
alert udp any 53 -> any any (dns:RCODE,NAMEERR; msg:resolutin_error;)
# dns ttl < 100 sec
alert udp any 53 -> any any (dns:TTL,100; msg:dns_ttl_lt_100;)
# mail record lookups - server and client
alert udp any 53 -> any any (dns:QTYPE,MX; msg:dns_rule_MXsvr;)
alert udp any any -> any 53 (dns:QTYPE,MX; msg:dns_rule_MXcli;)
# name record lookups - server and client
alert udp any 53 -> any any (dns:QTYPE,NS; msg:dns_rule_NSsvr;)
alert udp any any -> any 53 (dns:QTYPE,NS; msg:dns_rule_NScli;)
# look up out/inbound inverse lookups
alert udp any any -> any 53 (dns:OPCODE,IQ; msg:scott_iqcli;)
alert udp any 53 -> any any (dns:OPCODE,IQ; msg:scott_iqsrv;)
#everything else. don't try this on a fast network ..
alert udp any 53 -> any any (dns:QUERY,R; msg:dns_r_rule;)
alert udp any any -> any 53 (dns:QUERY,Q; msg:dns_q_rule;)
Note the distinction between the client (in this case udp any any), and the server (udp any 53). If this does not make sense to you, please think about it (or something like telnet) till you do.

The load difference between the options in the first table and those in the second can not be stressed enough. For the first, we just do a simple cast of the packet data into a DNS header data struct. This is cheap in the scheme of things. To get to the QTYPE, QCLASS and TTL we need to take apart the packet and deal with many (12 is not unusual) separate values per packet. The overhead on this is enormous, so be careful in using these values. You can look at something like "snoop -v port 53" output or etherial to get a good idea about this.


Notes about plugin writing (and other stuff ...).
The problem with the DNS protocol is that it does not fall on nice boundaries. In fact, it is recursive and hideous. It was designed with the purpose of conveying a maximum amount of information in a minimum number of bytes, hence it's high personality. To deal with this, we had to extend the basic Packet type to include header data. This is not without precedent - see the HttpUri field for example. Extending the Packet type allows us to drag around detailed DNS information without much overhead beyond the initial setup.

The mechanics for this are simple - just include the DNS_HEADER structure in the decode.h file, then add a data type DNS_HEADER dns_head to the packet type definition as defined in decode.c . Now you can fill up and reference this structure via the plugin.

Understanding that the value set while registering the plugin is the operator name for rules was the key to writing the plugin. This may be obvious to many of you out there - especially those of you who have written the excellent plugins that I shamelessly worked off of - there was no place that I could find that suggested it. Again, if this is not the case, I apologies.

Looking at the sp_template.c provides a wealth of information. Reading it after the fact makes me wonder why I was unable to read the perfectly clear and helpful comments. Oh well. The most confusing things that I found (on the first time through) was the use of "keyword" in the registration, the OptTreeNode, the PLUGIN_TEMPLATE_NUMBER and the local data structure for holding values. Some of these will be obvious to you, but I will quickly cover them all since the role of each was not obvious to me at the time.

  • "keyword" use:
    In the SetupTemplate() function we see:
    void SetupTemplate()
    {
    /* map the keyword to an initialization/processing function */
    RegisterPlugin("keyword", TemplateInit);

    #ifdef DEBUG
    printf("Plugin: TemplateName Setup\n");
    #endif
    }
    I have already talked about this question. A little greping provided the answer that the "keyword" is just an operator registered with snort for rule processing like flags or content.
  • OptTreeNode:
    It took me a few, but the OptTreeNode *otn is just a pointer to the rule that you are working with. It will be required to allocate a chunk of memory to put the rule line options in, parse them out and fill up the structure with them. This (*otn) provides way of referencing that structure. Why did it take an hour to figure it out? Got me.
  • PLUGIN_TEMPLATE_NUMBER:
    Ok, this was one of those wtf! moment. Again, with a little discrete grepping, it became clear that this is just a unique identifier for each plugin type. I just looked over the current values and incremented the largest I could find by two (in case there is someone else out there also doing the same...).
  • Local data structure:
    The local data structure, called TemplateData in the template example, is used to hold parsed information read from the rule that was just processed. It is not directly related to the packet being looked at, but rather is used to get an idea about what you are looking for. In the case of the sp_dns_head, we see:
    typedef struct _DnsHeadData
    {
    int value; /* field in dns data struct */
    int type; /* expected value for this field */
    } DnsHeadData;
    These are the values of the general [column, value] set that we are looking to match. In the plugin a series of defines are used to make the reading of the decision tree simpler to visualize.
This is by no means a criticism of the code or docs. The example templates were excellent - within a few hours I had a working prototype.


Snort Package Install Directions

These directions will show how to install the preprocessor, plugin and resolver library at the same time.

  1. Unzip the source file in the ~snort directory. It will locate the ~snort/src, ~snort/src/preprocessor and ~snort/src/detection-plugins directorys.

  2. Edit the plugbase.c file:

    • Include the line:
      #include "preprocessors/spp_dns_session.h
      under the "/* built-in preprocessors */" comment (aprox line 44) to include the header file.

    • Include the line:
      #include "detection-plugins/sp_dns_head.h"
      under the "/* built-in detection plugins */" comment (aprox line 60)

    • Include the line:
      SetupDnsSession();
      under the "****** Preprocessor Plugin API ******/" comment to register the preprocessor init function.

    • Include the line:
      SetupDNSTemplate();
      under the "****** Detection Plugin API *******" comment. Make sure not to put the line between the ENABLE_RESPONSE test to make sure that it will be loaded on startup.

  3. Edit the generators.h file and add the following entries to the list of error handling information:
    #define GENERATOR_SPP_DNS 120
    #define DNS_REC_EXPAND_FAIL 1
    #define DNS_XDATA_IN_PKT 2
    #define DNS_SESSION_UNKNOWN 3
    #define DNS_MULT_RESP_POSS_SPOOF 4
    Then below in the error definitions:

    #define DNS_SESSION_REC_EXPAND_FAIL "(spp_dns) resource record expansion failure"
    #define DNS_SESSION_XDATA_IN_PACKET "(spp_dns) extra data in dns packet"
    #define DNS_SESSION_UNKNOWN_SESSION "(spp_dns) unknown DNS session traffic"
    #define DNS_SESSION_MULT_RESP_POSS_SPOOF "(spp_dns) multiple responses to query - possible spoof"

    If you have modified this file for some other reason already, make sure that the addition on this information does not introduce conflicting values.

  4. Extend the 'Packet' type by adding the following following structs to decode.h:

    typedef struct _DNS_HEADER {
    unsigned id :16; /* query identification number */
    #if defined(WORDS_BIGENDIAN)
    /* fields in third byte */
    unsigned qr: 1; /* response flag */
    unsigned opcode: 4; /* purpose of message */
    unsigned aa: 1; /* authoritive answer */
    unsigned tc: 1; /* truncated message */
    unsigned rd: 1; /* recursion desired */
    /* fields in fourth byte */
    unsigned ra: 1; /* recursion available */
    unsigned unused :1; /* unused bits (MBZ as of 4.9.3a3) */
    unsigned ad: 1; /* authentic data from named */
    unsigned cd: 1; /* checking disabled by resolver */
    unsigned rcode :4; /* response code */
    //#endif
    #else
    /* fields in third byte */
    unsigned rd :1; /* recursion desired */
    unsigned tc :1; /* truncated message */
    unsigned aa :1; /* authoritive answer */
    unsigned opcode :4; /* purpose of message */
    unsigned qr :1; /* response flag */
    /* fields in fourth byte */
    unsigned rcode :4; /* response code */
    unsigned cd: 1; /* checking disabled by resolver */
    unsigned ad: 1; /* authentic data from named */
    unsigned unused :1; /* unused bits (MBZ as of 4.9.3a3) */
    unsigned ra :1; /* recursion available */
    #endif
    /* remaining bytes */
    unsigned qdcount :16; /* number of question entries */
    unsigned ancount :16; /* number of answer entries */
    unsigned nscount :16; /* number of authority entries */
    unsigned arcount :16; /* number of resource entries */
    } DNS_HEADER;

    This should be placed anywhere in the decode.h file before the typedef of Packet. In addition, the DNS_HEADER type needs to be added to the Packet type itself. In keeping with moving the application data typed to the bottom, I inserted the line:

    HttpUri URI; /* keep track of HTTP URIs for better
    * pattern matching
    */
    DNS_HEADER *dns_head; /* for rules based on dns header information */

    void *ssnptr; /* for session tracking info... */

    within the Packet typedef itself.

  5. Edit the Makefile located in ~snort/src/preprocessors, and add the names of the two preprocessor source files to the list of names on the "libspp_a_SOURCES" line. You should see references to the other .c and .h preprocessor files there.

    In addition, add "spp_dns_session.o" to the end of the "libspp_a_OBJECTS" defins.

  6. Edit the Makefile located in ~snort/src/detecton-plugins, and add the names of the two plugin source files to the list of names on the "libspd_a_SOURCES" line. You should see references to the other .c and .h plugin files there.

    In addition, add "sp_dns_head.o" to the end of the "libspd_a_OBJECTS" defins.

  7. Edit the Makefile located in ~snort/src, and add the names of the two resolver library files (in a process which is now becoming familier) to the list defined by "snort_SOURCES".

    In addition, add "dnslib.o" to the "snort_OBJECTS" line.

  8. Edit the snort.conf file by adding the preprocessor loading and control information below:
    # Arguments:
    # -nosession : disables session monitoring. Enabled by default.
    # -noclean : disables traffic verification. Enabled by default.
    # -timeout : modifies session timeout. Default is 20 seconds.
    # -norespcheck : disables multiple response checking. Enabled by default.
    preprocessor dns_session:
    Make any changes required to the preprocessor if required.

Back