Snort: «byte_test» for dummies

Recientemente un usuario del blog pregunto por qué en las reglas de detección de Malware con Snort, cuando se quiere detectar la consulta DNS hacia ciertos dominios sospechosos, se emplean ciertos caracteres y condiciones como la comprobación «byte_test:1, !&, 0xF8, 2;«. Para explicarlo vamos a tomar como ejemplo la siguiente regla VRT para la detección del malware Gauss:

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

En la regla anterior lo único que se esta comprobando es que se trate de una consulta DNS al dominio «bestcomuteradvisor.com». Para evitar falsos positivos se emplean tres restricciones en la creación de la misma:

La primera es comprobar que se trate de un paquete UDP al puerto 53 procedente desde nuestra red interna.

La segunda comprobación radica en el principio «content» donde se tiene en cuenta el valor hexadecimal de inicio y final de la consulta DNS. Como sabemos un dominio esta separado por «.», como en «correo.empresa.es». Los paquetes DNS en vez de indicar este punto lo que hacen es indicar mediante un byte (dos números hexadecimales) la cantidad de caracteres que le preceden.

En el ejemplo (content:"|13|bestcomputeradvisor|03|com|00|") vemos que comprueba que haya primero un «13» en hexadecimal, es decir, que la parte del dominio que viene a continuación sean 19 caracteres (13 en hexadecimal). Si contamos el número de caracteres de la cadena «bestcomuteradvisor» tiene efectivamente 19. Luego comprueba que hay 3 caracteres, que pertenecen a «com». Además comprueba que la consulta DNS termina siempre con el valor «00», indicando que no hay más caracteres en el dominio a consultar.

La tercera y última comprobación, corresponde al famoso «byte_test:1, !&, 0xF8, 2;«. ¿Qué es esto del byte_test? No es más que algo que nos permite coger una cantidad de bytes del paquete a partir de una posición y comprobar si coincide con otro valor.

En «byte_test» (1, !&, 0xF8, 2) lo primero que tenemos que mirar es el último campo, en nuestro caso «2». Esto indica la posición dentro del paquete donde nos situaremos, teniendo en cuenta que siempre el primer byte vale 0. Por tanto el valor «2» nos indica que se situará en el tercer byte del paquete. Es muy importante entender que en Snort siempre que nos refiramos a posiciones del paquete ya se han descontado lo empleado por las cabeceras de enlace, red y transporte. Por tanto el tercer byte ya implica haber descontado la cabecera IP y la cabecera UDP: ya se está trabajando sobre la cabecera DNS.

Este el motivo por el cual cuando se emplean filtros BPF en herramientas como TCPdump se tiene que hacer referencia al protocolo. Tomando el ejemplo anterior, en lugar de buscar el tercer byte, se buscará en el undécimo byte a partir del inicio de la cabecera UDP (udp[10]). Si la cabecera UDP normalmente son 8 bytes, comprende por tanto udp[0-7], el primer byte del protocolo DNS es udp[8], el segundo udp[9] y el tercero es udp[10]. Por eso, indicar en Snort el tercer byte con «2» es lo mismo que indicar udp[10] en filtros BPF, ya que en ambos casos se empieza contando siempre desde 0.

Por tanto con «byte_test:1, !&, 0xF8, 2;» nos situamos en el tercer byte del protocolo DNS. Tras esto, se debe mirar el primer campo de byte_test, en este caso «1». Esto indica cuántos bytes vamos a coger a partir de la posición «2». Por tanto utilizaremos un solo byte. En pocas palabras, vamos a trabajar con el tercer byte del protocolo DNS. Si buscamos en google la cabecera DNS podremos obtener imágenes explicativas, como por ejemplo la obtenida de www.troyjessup.com:

Como vemos, el byte que se está comprobando está formado por los siguientes campos:

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

Tras esto, cogemos el segundo campo del byte_test cuyo valor es «!&«. Esto implica que con el valor del paquete se va a realizar una operación binaria «AND negada» sobre el valor indicado en el tercer campo de byte_test: «0xF8«. Si la condición es true hará «match» en la regla.

Siempre que se utilizan operaciones binarias de tipo AND u OR lo que se busca es que el valor de un campo sea uno en concreto o que al menos algunos de los campos tenga un valor. En nuestro ejemplo lo que se esta buscando es «F8«, en binario «1111 1000«. Por tanto vamos a trabajar con los bits del 7 al 3 asignados al campo QR y OpCode pertenecientes al tercer byte del protocolo DNS.

Si la comprobación fuera únicamente una operación AND («&») la regla lo que estaría intentado comprobar es que el campo QR valga 1 y que el campo OpCode valga «1111». Pero al ser un AND negado lo que hace es lo contrario, busca que ambos campos valgan 0. Si se revisa el protocolo DNS ambos campos valen 0 cuando se trata de una consulta DNS (query). Por tanto «byte_test:1, !&, 0xF8, 2;» lo único que hace es verificar que se trata de una consulta DNS.

Saludos hexadecimales.

Comments

  1. Buen post Ximo, cuanto estuve haciendo las reglas de APT1 estuve a punto de no poner el byte_test ya que es redundante en este caso, pero lo deje por herencia de VRT. Con la primera y segunda restricción de la que hablas es suficiente al menos en las pruebas que hice en el lab. A mi parecer incluso esa comprobación se podría mejorar con un byte_test:1, !&, 0x80, 2; comprobando tan solo si se trata de una petición de DNS (QR=0) y no una respuesta DNS (ya que no se distingue entre petición o respuesta con esa mascara 0xF8, el opcode te la invalida si uno de sus bits es 0). Pero como he dicho antes la primera restricción de la que hablas hace que sea redundante ya que no te vas a encontrar un QR=1 en un paquete que va a un puerto UDP 53.

    SAludos hexadecimales too!

  2. Una curiosidad más master… ¿Por qué no se tiene en cuenta el . del .com en el conteo de la cadena? es decir ¿por qué no pone |17|bestcomputeradvisor.com|00|?

  3. Lo ví!! es por especificación del propio protocolo DNS no por SNORT :)