sábado, febrero 17, 2018

Debugger Registry Key: Nuestro aliado en Windows para mitigar ataques Fileless UAC bypass y DLL Hijacking

Hace tiempo que mi compañero Pablo González y yo empezamos a estudiar todo lo relacionado con la evasión de UAC. Muestra de ello es la herramienta que hace poco hemos publicado ya en nuestra web de ElevenPaths, que nos llevó a BlackHat Europa y de la que ya hemos hablado anteriormente en este blog, UAC-A-MOLA. Probablemente una de las fases más complicadas en el desarrollo de la herramienta, fue la fase de mitigación.

Figura 1: Debugger Registry Key: Nuestro aliado en Windows
para mitigar ataques Fileless UAC bypass y DLL Hijacking

Como hemos contado desde el principio, queríamos que la herramienta no solo fuera capaz de detectar, realizar e investigar evasiones de UAC, sino que también fuera capaz de mitigarlas. Y esto no fue una tarea sencilla. Hubo que hacer muchas pequeñas cosas por detrás para lograr que esto fuera así.

Figura 2: Download UAC-A-Mola

Lo que hoy os traigo es un pequeño hack que encontramos en sistemas operativos Microsoft Windows, que nos permite realizar esto sin apenas sobrecargar la máquina anfitriona y que, como veremos más adelante, tiene muchas más aplicaciones que mitigar este tipo de debilidades de seguridad.

Problema que se plantea

Se plantean las siguientes cuestiones:
  • Sabemos que existe un conjunto de binarios oficiales de Microsoft Windows que por sus características son vulnerables a la evasión de UAC. Ya sea mediante la creación de una serie de claves y valores en el registro de Windows o mediante la creación de una estructura de directorios y de una DLL (Dynamic-Link Library) maliciosa en el sistema de archivos.
  • Sabemos que la evasión de UAC se realiza en las primeras fases de ejecución del binario, el programa original nunca llega a abrirse.
  • No queremos que el usuario tenga que realizar acciones adicionales a las que trae el sistema operativo por defecto (como por ejemplo introducir una contraseña cada vez que se ejecuta uno de estos binarios). En definitiva, no queremos endurecer las políticas de seguridad de UAC.
  • Necesitamos saber cuando se ejecuta un binario potencialmente vulnerable a la evasión de UAC, y antes de que se produzcan las primeras fases de la ejecución del mismo, comprobar si ese binario se esta ejecutando de una forma legítima o se esta utilizando para realizar una evasión del UAC.
  • Por último, tenemos que realizar toda la monitorización anterior sin que haya una sobrecarga notable en el rendimiento de la máquina. El usuario no debe notar que se están monitorizando ciertos binarios.
Estas son a grandes rasgos las características que buscábamos para poder realizar una mitigación eficiente de la evasión de UAC. Vamos a ver algunas de las soluciones que se nos ocurrieron:

Hooking de determinadas llamas al sistema

Una de las primeras cosas que hicimos fue hookear las llamadas al sistema que se hacían en el sistema operativo MS Windows desde espacio de usuario. Cuando veíamos una llamada de creación de un proceso, comprobábamos si ese proceso se correspondía con alguno de los binarios vulnerables, si esto era así, suspendíamos el proceso, lo hookeabamos y monitorizábamos todas las llamadas del mismo al registro y al sistema de ficheros, si en alguna de esas llamadas se alcanzaba un ruta potencialmente peligrosa, se detenía la ejecución del binario y se avisaba al usuario si quería continuar con su ejecución.

Hicimos todo esto en Python (sí, Python también funciona en Windows :P) utilizando Deviare un motor profesional de hooking que tiene la ventaja de que esta implementada como un componente COM, por lo que se puede integrar con todos los lenguajes de programación que soporten COM. Esta parte daría para otro post, así que no voy a extenderme más, quizá en un futuro os escriba sobre ello.

Figura 3: Deviare API Hook

La conclusión es que esta técnica funcionó relativamente bien, deteniendo la ejecución el binario cuando pretendía realizar una evasión de UAC y permitiendo su ejecución cuando lo hacía de manera legítima. A pesar de esto, esta técnica tenía un gran problema, el hooking continuo de todas esas llamadas al sistema ralentiza mucho la máquina y por lo tanto no es útil para un entorno real, suficiente para ser descartada.

Debugger Registry Key

Vamos a meternos en materia con la solución que quiero presentaros en este artículo. Como hemos visto en el apartado anterior, tenemos algunos problemas relativamente complejos que solucionar:
  • ¿Cómo podemos saber cuándo se ha ejecutado un binario concreto desde cualquier parte del sistema operativo MS Windows (terminal, acceso directo, invocado desde un intérprete…) sin monitorizar el resto de los procesos para no sobrecargar la máquina?
Y lo que es más importante:
  • Una vez que hemos detectado que se ejecuta uno de los binarios potencialmente peligrosos, ¿Cómo podemos detener su ejecución antes de que realice la evasión de UAC (que se realiza en las primeras fases de ejecución del mismo) y ejecutar un pequeño código nuestro que verifique si ese binario se está ejecutando de manera legítima?
Después de mucho rompernos la cabeza con esto, encontramos lo siguiente en MSDN de Microsoft:

Figura 4: Información en MSDN

¿Curioso verdad? Eso es justo lo que nosotros queremos, con la diferencia de que, en vez de un debugger, queremos ejecutar otro código que verifique si ese binario se ejecuta de manera legítima. Vamos a seguir leyendo:

Figura 5: Proceso para lanzar el debugger automáticamente en MSDN

Parece bastante sencillito, simplemente tenemos que crear esa clave en el registro de MS Windows con el valor del binario que queremos monitorizar, supongamos por ejemplo sdclt.exe, y en su interior creamos un valor debugger con el script que queremos que se ejecute cuando este binario sea invocado por el usuario, vamos a hacer la prueba con calc.exe.

Figura 6: Valor debugger creado

El resultado, como podéis comprobar, es la ejecución de una calculadora cada vez que ejecutamos sdclt.exe desde cualquier parte del sistema operativo. Estupendo, ya tenemos una forma de detectar cuando se ejecuta un binario concreto sin ninguna sobrecarga para la máquina del usuario y, además, podemos ejecutar un script que compruebe si ese binario se va a ejecutar de forma legítima antes de que lo haga.

Ahora, vamos a ver como podemos aprovechar esta característica para montarnos un programita en Python que automatice las comprobaciones utilizando esta técnica de manera transparente para el usuario y sin añadir sobrecarga en el equipo.

Mitigando la evasión de UAC y el DLL Hijacking

Ahora es donde la cosa se pone interesante, vamos a utilizar la técnica anterior y vamos a desarrollar un servicio y alguna cosita más en Python para aprovecharla en la mitigación de evasiones de UAC. Lo primero de todo, vamos a ver algunas características que tiene la técnica del debugger, que pueden suponer un problema o una deficiencia:
  • Si creamos la clave binario.exe y el valor debugger con la ruta de nuestro script de comprobación, hay que tener en cuenta que cuando el usuario ejecute binario.exe solo se ejecutará nuestro script de comprobación, que tendrá que ser el encargado de ejecutar binario.exe si todo está correcto.
  • Como se ha dicho en el punto anterior, será nuestro script de comprobación el encargado de ejecutar binario.exe después de haber realizado las comprobaciones, pero antes de su ejecución, deberá eliminar la clave del registro, y tras su ejecución, deberá volverla a poner. De lo contrario, entrará en un bucle infinito llamando constantemente al script de comprobación.
  • Por último, pero no menos importante, para poder añadir y eliminar esas claves y valores en el registro de la máquina necesitamos permisos de administración.
Teniendo todo esto en cuenta, el flujo completo que se debería seguir la ejecución de un binario potencialmente vulnerable a la evasión de UAC es el siguiente:
1. El usuario invoca binario.exe. 
2. Se ejecuta automáticamente el script de comprobación que se encuentra en la rama binario.exe y en el valor debugger. 
3. El script de comprobación evalúa si la ejecución es legítima. 
4. Si se encuentran ramas del registro o estructuras en el sistema de ficheros peligrosas, el script de comprobación las elimina. 
5. El script de comprobación elimina el valor debugger. 
6. El script de comprobación ejecuta el binario.exe. 
7. El script de comprobación vuelve a escribir el valor debugger.
La única pega que encontramos en este proceso es que tiene que eliminar el valor del registro que se encuentra en el hive HKLM, que es una rama del registro de MS Windows que requiere permisos de administración para poder modificarse. Como consecuencia de esto, el script de comprobación debe ejecutarse con permisos de administración, y por lo tanto el usuario debería aceptar una pestaña de UAC cada vez que ejecuta uno de los binarios sospechosos. Esto provocaría una modificación del comportamiento de este tipo de binarios que no requieren aceptar el UAC ya que se auto elevan solos.

Figura 7: Descripción del proceso de control

Como lo que nosotros buscamos es mitigar la evasión de UAC sin provocar ningún cambio en el comportamiento del sistema operativo MS Windows por defecto, de forma que se mantenga la facilidad de uso al mismo tiempo que podamos garantizar cierto nivel de seguridad, vamos a utilizar la siguiente estrategia para que el script de comprobación se ejecute con permisos de administración, pero el usuario no tenga que aceptar el UAC, es decir, para que se mantenga el comportamiento por defecto. Lo que haremos será lo siguiente:
1. Crearemos un servicio de Windows, que correrá con privilegios elevados, y que se encargará de modificar el registro y el sistema de fichero. 
2. Por otra parte, crearemos un agente por cada uno de los binarios que queramos mitigar. Lo almacenaremos en una rama protegida del sistema y será el encargado de comunicarse de forma segura con el servicio cuando uno de los binarios sospechosos sea ejecutado por el usuario. 
3. El servicio pondrá en el valor debugger del registro de Windows de cada uno de los binarios la ruta al agente.
Programando el servicio y los agentes

Vamos a ver el código para realizar todo esto. Lo primero el del servicio, escrito en Python:


Figura 8: Código en Python del servicio

Como podéis observar lo que hace es esperar una conexión mediante un socket localhost con una determinada contraseña para que sea más seguro. El agente se comunicará con una serie de comandos, y en función del comando, el servicio realizará una acción u otra.

Por otra parte, el código de los agentes es extremadamente sencillo. Lo que vamos a hacer es tener una clase agente que implemente las funciones básicas de conexión con el servicio y luego heredar de ella para cada uno de los agentes concretos:


Figura 9: Código en Python de los agentes


Figura 10: Código en Python de instanciado de agentes

Una vez tenemos esto, lo único que hay que hacer es instalar el servicio en el sistema, que se ejecutará como cualquier otro.

Figura 11: PoC de UAC Bypass Mitigation

En conclusión, como puede observarse, con este método tenemos una forma de comprobar el estado de diferentes estructuras del sistema operativo antes de la ejecución de un binario. En nuestro caso, lo utilizamos para comprobar si existen determinadas ramas peligrosas en el registro de Microsoft Windows o determinadas construcciones en sistema de ficheros, pero puede ser utilizado para multitud de soluciones.

Autor: Santiago Hernández, Security Researcher en ElevenPaths

PD: Si te gusta este tema, las lecturas recomendadas son los libros de  "Hacking Windows" y "Máxima Seguridad Windows 4ª Edición". Te darán una perspectiva general de cómo funciona toda la arquitectura general de seguridad de los sistemas Microsoft Windows.

No hay comentarios:

Publicar un comentario