Snort: “byte_test” for dummies

Recently a blog user asked why in in the Snort malware detection rules, when you want to detect the DNS query to certain suspicious domains, certain characters such as “byte_test:1, !&, 0xF8, 2;” are used as testing conditions. To explain let’s take as an example the following VRT rule for Gauss malware detection:

alert udp $HOME_NET any -> any 53 (msg:"BLACKLIST DNS request for known malware domain 
bestcomputeradvisor.com - Gauss"; flow:to_server; byte_test:1,!&,0xF8,2; 
content:"|13|bestcomputeradvisor|03|com|00|"; fast_pattern:only; metadata:impact_flag 
red, policy balanced-ips drop, policy security-ips drop, service dns; 
reference:url,gauss.crysys.hu/; reference:url,www.securelist.com/en/blog/208193767

In all the previous rule we only are checking that it is a DNS query to the domain “bestcomuteradvisor.com”. To avoid false positives three constraints are used.

The first is to check that it is a UDP packet to port 53 coming from our internal network.

The second check is the field “content” which looks at the hexadecimal values of the beginning and end of the DNS query. As we know a domain is separated by “.” as in “mail.company.com”. DNS packets rather than stating this point they use a byte (two hexadecimal numbers) to show the number of characters that precede it.

In our example (content:"|13|bestcomputeradvisor|03|com|00|") it checks for a “13” in hexadecimal, ie that the part of the domain that follows is 19 characters (13 in hexadecimal). If we count the number of characters in the string “bestcomuteradvisor” is actually 19. Then it checks that there are 3 characters that belong to “com”. It also check that the DNS query always ends with the value “00”, a proof that there are no more characters in the domain queried.

The third and final test is the famous “byte_test:1, !&, 0xF8, 2;“. What is this byte_test? It’s just something that allows us to take a number of bytes of the packet from a given position and see if it matches another value.

In “byte_test” (1, !&, 0xF8, 2) the first thing we look at is the last field, in our case “2”. This shows the position within the pack where we start, always considering that the first byte is 0. Thus the value “2” indicates that we will be in the third byte of the packet. It is very important to understand that in Snort whenever we refer to packet positions we have already discounted the link, network and transport headers. Therefore the third byte already discounts the IP header and the UDP header: ie we are already working on the DNS header.

This is the reason why when using BPF filters in tools like TCPdump we must set the protocol. Taking the above example, instead of searching for the third byte, we will take the eleventh byte from the start of the header UDP (udp[10]). If the UDP header is 8 bytes normally, thus comprises udp[0-7], the first byte of the DNS protocol is udp[8], the second udp[9] and the third is udp[10]. This way, in Snort to refer to the third byte with “2” is the same than to refer to udp[10] in BPF filters, as in both cases it always starts counting from 0.

So with “byte_test:1, !&, 0xF8, 2;” we are at the third byte of the DNS protocol. After this, you should look at the first field of byte_test, in this case “1”. This shows how many bytes we are going to take from the position “2”: a single byte. In short, we will work with the third byte of the DNS protocol. If we Google “DNS header” we will obtain several explanatory images, such as that obtained from www.troyjessup.com:

As we see the byte being tested consists of the following fields:

  • Bit 7: QR
  • Bit 6-3: OpCode
  • Bit 2: AA
  • Bit 1: TC
  • Bit 0: RD

After that, we take the second field of byte_test whose value is “!&“. This implies that we will perform a binary operation “AND NOT” on the value in the third field of byte_test: “0xF8“. If the condition is true it will “match” the rule.

Whenever we use binary type operation such as AND or OR we look for a given value in a field value or that at least any of the fields have a value. In our example we are looking for “F8” binary “1111 1000“. So we will work with bits 7 to 3 assigned to QR field and OpCode belonging to the third byte of the DNS protocol.

If the check were only an AND operation (‘&’) the rule would attempt to check that the QR field is “1” and that the OpCode field is “1111”. But being a negated AND it does the opposite: wants both fields worth 0. If we review the DNS protocol both fields are 0 when it comes to a DNS query (query). Thus “byte_test:1, !&, 0xF8, 2;” just checks this is a DNS query.

Hexadecimal greetings.