Random IRC quote :      12:08 <@chupameunpie> Yo quiero a nullsub que se le pueden hacer peinados

Saltándonos DEP: a lo Savant

Estas últimas 3 semanas he estado picado con un bug que me decidí a salsear para seguir aprendiendo más cosas de estas que no se enseñan en la escuela. El bug en cuestión se trata de un clásico stack overflow en la version 3.1 y inferiores de Savant Web Server. A pesar de haber varios exploits ya publicados para este fallo, todos ellos estaban diseñados para funcionar en XP SP2 o inferiores. En mi caso particular se dio la coincidencia de que tengo instalada una maquina virtual de 2003 Server SP1. Este sistema tiene dos peculiaridades. La primera, es que desde Service Pack 1 incluye soporte para tecnologias DEP o lo que los chicos de Microsoft llaman prevención de ejecución de datos. La segunda tiene que ver con las politicas de implantación de DEP. Tal y como MS nos cuenta en su web, existen varias politicas definidas para ello. La que nos interesa es la llamada OptOut, la cual por defecto activa DEP para todos los procesos del sistema que explícitamente no indiquen lo contrario.

Volviendo al tema del bug en cuestión, como ya he dicho se trata de un clásico overflow de buffer en la pila. En este caso la situación se dá cuando un usuario realiza una petición HTTP que no está reconocida, o dicho de otra forma, cuando el usuario tiene ganas de marcha. He dicho que se trata de un clásico overflow en la pila, y eso no es nada nuevo. Cierto. Pero por algo hemos comentado el DEP en Windows 2003 Server SP1. Hablando en plata, lo que no he encontrado en ningún sitio es un exploit público que sobrepase DEP para este bug, y puesto que ya está hecho a lo mejor le sirve de ayuda a alguien para sus futuros planes de dominar el mundo 🙂

Bromas aparte, por motivos éticos, no voy a publicar el codigo del exploit, por dos razones basicas:

  • Cualquiera que séa capaz de entender este post, es capaz de escribir su propio exploit.
  • El bug sigue sin ser corregido en la última versión del software.

Desgraciadamente, parece ser que el proyecto lleva algún tiempo abandonado y aprovechando esto, con motivos siempre educativos, vamos a ver que se puede hacer. Odio tener que poner disclaimers en los posts, pero es mejor prevenir que curar. El que utilice lo aquí mostrado con cualquier tipo de fin, lo hace bajo su propia responsabilidad y sin ninguna garantía. Accediendo a esta pagina estas de acuerdo con lo aquí mencionado. Ahora al lio.

Si hacemos una traza del flujo de un paquete poniendo unos breakpoints en los tipicos recv() y recvfrom(), veremos que se hace uso de un wrapper en 0x00401023. Una vez recibido el paquete, se procede a analizar la cabecera para comprobar que tipo de petición se ha recibido. Si hemos tenido la mala suerte de hacer una petición que no corresponde a ninguno de los que el servidor reconoce, acabamos llegando a la rutina en 0x0040BFA0, que es donde vamos a exprimir la naranja. Lo primero que se hace en esta función es llamar a una rutina en 0x00401285 que se encarga de encadenar el path que nuestro paquete señala, al path del directorio de trabajo de Savant. A pesar de que se hacen varios usos de nuestro amigo strcpy() el bug reside hacia el final de la rutina, concretamente en:

.text:00411A6A                 mov     edx, [ebp+arg_14]
.text:00411A6D                 add     edx, [ebp+var_8]
.text:00411A70                 mov     eax, [ebp+Str1]
.text:00411A73                 add     eax, [ebp+var_C]
.text:00411A76                 mov     cl, [eax]
.text:00411A78                 mov     [edx], cl       ; PWN stack

La cagada ya está hecha. Ahora volvemos al caller que será el encargado de redirigir la ejecución a donde nosotros le hayamos dicho. Ahora vamos a pensar que tipo de petición vamos a construir. Lo que haremos sera algo que guardara un lejano parecido con una correcta petición HTTP:

METODO | <espacio en blanco> | / | <nuestro path>

En cuanto al espacio disponible, conocemos lo siguiente:

  • recv() nos permite hasta un máximo de 0x4000 bytes.
  • METODO puede tener como máximo 24 bytes.
  • <nuestro path> puede albergar un máximo de 250 bytes hasta (sin incluir) el valor ret, más 32 bytes adicionales después de este último.

Como ya he mencionado anteriormente, el jugo en este caso es saltarse la protección DEP. A pesar de que se han planteado varias soluciones, mi favorita es la propuesta por skape y Skywing en este articulo para el 2º Volumen de Uninformed. No voy a entrar en detalles de la tecnica, es una lectura bastante corta y recomendable, pero aquí os haré un resumen. Lo que trataremos de hacer, en vez de redefinir los parámetros de acceso de una región de memoria, será de desactivar DEP completamente para todo el proceso. Para ello haremos un ret2code o dicho en lenguaje llano, reutilizar codigo existente en memoria. En el articulo se demuestra una secuencia de código que yo personalmente no he encontrado en la versión española de la plataforma. Pero tras bucear entre dumps de memoria, encontramos la siguiente secuencia dentro de ntdll.dll:

.text:7C83E413                 mov     [ebp+var_4], 2
.text:7C83E41A
.text:7C83E41A loc_7C83E41A:                           ; CODE XREF: sub_7C83592A+45j
.text:7C83E41A                 push    4
.text:7C83E41C                 lea     eax, [ebp+var_4]
.text:7C83E41F                 push    eax
.text:7C83E420                 push    22h
.text:7C83E422                 push    0FFFFFFFFh
.text:7C83E424                 call    ZwSetInformationProcess
.text:7C83E429                 jmp     finish

Y tras el jump:

.text:7C835975                 or      byte ptr [esi+37h], 80h
.text:7C835979                 pop     esi
.text:7C83597A
.text:7C83597A locret_7C83597A:                        ; CODE XREF: sub_7C83592A+Dj
.text:7C83597A                 leave
.text:7C83597B                 retn    4

Ahora nos encontramos con un problema. El leave que se ejecuta justo antes de hacer ret, hace un cambio de frame en una sola instrucción y nos jode el fake frame que necesitamos para encadenar secuencias de ret’s y volver a ejecutar nuestro codigo. Así es como termina la secuencia de ejecución de 0x0040BFA0, que es la que nos da control sobre la ejecución:

.text:0040C102                 pop     edi
.text:0040C103                 pop     esi
.text:0040C104                 pop     ebx
.text:0040C105                 mov     esp, ebp
.text:0040C107                 pop     ebp
.text:0040C108                 retn

Como se puede observar, obtenemos ebp de la pila y aunque a primera vista esto no parezca un problema, lo es. El problema radica en que el fake frame que queremos construir esta en la pila, cuyas direcciones son del tipo 0x00XXXXXX. Así pues, no podemos poner valores de ese tipo en nuestro buffer por que tiene bytes nulos y se nos desmonta la barraca. Que podemos hacer frente a esto? Lo único que se me ha ocurrido ha sido utilizar algún tipo de operador logico para construir nuestro valor en tiempo de ejecución. Lo que nos lleva a la siguiente secuencia de codigo dentro de shell32.dll:

.text:7CA0E9BC                 or      cl, ch
.text:7CA0E9BE                 xor     ebp, 9090FFFFh
.text:7CA0E9C4                 nop
.text:7CA0E9C5                 nop
.text:7CA0E9C6                 nop
.text:7CA0E9C7
.text:7CA0E9C7 loc_7CA0E9C7:                           ; DATA XREF: .text:off_7C90FAE4o
.text:7CA0E9C7                 sub     dword ptr [esp+4], 20h
.text:7CA0E9CC                 jmp     sub_7CA0DF22

Que termina saltando a:

.text:7CA0DF22                 mov     edi, edi
.text:7CA0DF24                 push    ebp
.text:7CA0DF25                 mov     ebp, esp
.text:7CA0DF27                 push    [ebp+arg_8]
.text:7CA0DF2A                 push    [ebp+arg_4]
.text:7CA0DF2D                 push    offset off_7C8DC0A0
.text:7CA0DF32                 push    [ebp+arg_0]
.text:7CA0DF35                 call    SHLWAPI_219
.text:7CA0DF3A                 pop     ebp
.text:7CA0DF3B                 retn    0Ch

A pesar de que tenemos un call algo feo ahí, no hay nada de lo que preocuparse porque el ebp que acabamos de construir se salva con el push del prologo y el pop del epilogo. El resto del proceso de explotación no conlleva mayores cábalas. Como controlamos ebp, controlamos el ret de 0x7CA0DF3B. Bastaría con volver a algun codigo como jmp esp, para continuar ejecutando, ahora sin DEP, desde la pila. El ultimo problema que tenemos es que solo tenemos 250 bytes de espacio para albergar nuestro shellcode que, además debe estar con codificación alfanumérica. Por lo tanto, la estructura de nuestro buffer final podría ser algo así:

<padding> + <espacio en blanco> +  / + <padding> + <jmp_esp> + <shellcode> + <valor de ebp> + <primer ret> + <ret2dep> + <referenciable 1> + <referenciable 2> + <referenciable 3> + ‘\r\n\r\n’

Un par de comentarios sobre la estructura. Primero, recordar que el <valor de ebp> debe ser el adecuado para que despues del XOR obtengamos el valor que nos interesa. Segundo, los tres valores que marco como referenciables, son 3 valores de 4 bytes que apunten a una región de memoria con permisos de lectura y escritura, sino el programa no seguirá el camino que nos interesa. Por otro lado, entiendo que esta solución no es la optima y esta lejos de ser una solución universal, pero como ejemplo, pienso que demuestra un caso curioso.

Mención especial para Ruben y Joxean por su paciencia y atención!

La versión original de esto post la podeis encontrar aquí.

8 Comentarios para “Saltándonos DEP: a lo Savant”

  1. Comment por Ruben | 01/03/10 at 10:31 pm

    Nueva incorporación de 48bits, yujuuu!

    Que buena jon, y que bien explicado. El método es para apuntarlo porque es reusable 100%.
    Mucho ojo con Jon que va a dar que hablar jejeje Le podeís seguir también en http://www.morenops.com

  2. Comment por La Nuri | 01/04/10 at 4:59 am

    Mi nuevo heroe…

  3. jon
    Comment por jon | 01/04/10 at 5:00 am

    🙂

  4. Comment por La Nuri | 01/04/10 at 6:14 am

    Os molan los trios???

  5. Comment por Newlog | 01/05/10 at 4:58 am

    «Cualquiera que séa capaz de entender este post, es capaz de escribir su propio exploit.»

    Por ende, no soy capaz de escribir un exploit, no leo tu post!

    Mal planteamiento, y más si lo pones al principio!

  6. Comment por Mario | 01/06/10 at 8:49 am

    Muy bueno el post Jon!! 🙂

  7. jon
    Comment por jon | 01/06/10 at 9:07 am

    @Newlog
    Tienes razón en cuanto que sería mejor colocar esa frase al final del post. Lo demás, si te he entendido bien, si has sido capaz de enteder lo que he expuesto en el post, eres capaz de termina escribiendo un exploit para esto, eso es un hecho. No por eso quiero decir, que por no entender el post no seas capaz de escribir un exploit ni mucho menos. Exploits hay de muchos tipos y cada uno es un mundo.

  8. Comment por znk del club punto né | 01/25/10 at 9:18 pm

    Newlog, fallas.
    Según la lógica proposicional que se estudia en bto.

    si tenemos como premisas

    P -> Q
    ¬Q
    ——-

    podemos concluir que ¬P

    Por lo que si no puedes programar el exploit, entonces concluyes que no ENTIENDES el post, no que no lo LEAS, lo cual no contradice a jon.

    Feliz post y buen año nuevo

Se han cerrado los comentarios