Calculation of DNS Query Entropy Directly Within Zeek

Hi !

I have previously discussed the value of calculating the entropy (in the “Shannon” sense) of DNS queries. I had previously used R scripts to perform the calculations, since the underlying idea was to graph what the tool was observing.

I’ve since had another idea: why not have Zeek perform the calculation directly and add the resulting value to the “dns.log” file? This would allow more people to read it and process the information as they wish.

Enough talk, let’s get to the script (as usual, I’ll try to document each line as best as possible to make the code more readable)..

# Defines the namespace for this script named DNS_Entropy. This prevents variable or function name collisions with other Zeek scripts.
module DNS_Entropy;

# An export block used to make variables, types, or structure modifications accessible globally throughout the Zeek engine.
export {

          # Uses the redef (redefinition) keyword to modify Zeek's built-in DNS::Info record structure (which generates dns.log). 
          # The '+=' operator specifies that we are appending a new field to the existing structure without overwriting it.
          redef record DNS::Info += {
             # Declares the new field named entropy:
             # double: The data type (floating-point number).
             # &optional: Indicates the field is not required for every record (prevents runtime errors if entropy isn't computed).
             # &log: Critical attribute telling Zeek to write this value as a dedicated column in dns.log.
             entropy: double &optional &log;
          };
}

# Declares an event handler that triggers whenever Zeek observes a DNS request in the network traffic:
# 'c': The object representing the network connection (IP addresses, ports, metadata).
# query: The string containing the requested domain name (e.g., "google.com").
event dns_request(c: connection, msg: dns_msg, query: string, qtype: count, qclass: count)
    {
    # A safety check using the ?$ operator. 
    # It verifies whether the DNS state record ($dns) is attached to the connection c in memory before trying to access it.
    if ( c?$dns )
        {
            # Declares a local variable e and stores the result of the built-in "find_entropy(query)" function. 
            # This one returns a structure containing the Shannon entropy calculation for the query string.
            local e = find_entropy(query);
        
            # Extracts the numerical entropy value (e$entropy) and assigns it to the entropy field of the active DNS session (c$dns). 
            # This populates the column in 'dns.log'.
            c$dns$entropy = e$entropy;
        }
    }

Let’s see what this shows in the ‘dns.log’ file.

#fields ts      uid     id.orig_h       id.orig_p       id.resp_h       id.resp_p       proto   trans_id        rtt     query   qclass  qclass_name     qtype   qtype_name      rcode   rcode_name      AA     
      TC      RD      RA      Z       answers TTLs    rejected        opcode  opcode_name     entropy
#types  time    string  addr    port    addr    port    enum    count   interval        string  count   string  count   string  count   string  bool    bool    bool    bool    count   vector[string]  vector[interval]        bool    count   string  double
1789581627.186833       CgmXXK38WUAlu9S8D3      162.212.157.188 19637   8.8.8.8 53      udp     49781   0.012198        27.in-addr.arpa 1       C_INTERNET      48      DNSKEY  0       NOERROR F       F
       T       T       1       DNSKEY 13,DNSKEY 13,RRSIG 48 27.in-addr.arpa    3119.000000,3119.000000,3119.000000     F       0       query   3.189898
1789581627.177670       CKIdZH1b7y4SDL7GHj      162.212.157.188 41821   194.0.1.18      53      udp     34191   -       vietel.com.vn   1       C_INTERNET      1       A       0       NOERROR F       F
       F       F       1       -       -       F       0       query   3.238901

Cheers.