Lo siguiente que hay que hacer es llamar a la función de la API, CreateProcess(). El valor wShowWindow de la estructura STARTUP_INFO pasada a la función debería ser
SW_HIDE, con el objeto de mantener la ventana del navegador escondida.
Nota: Si quieres asegurarte completamente de que no se mostrará ninguna ventana en la pantalla, podrías, por ejemplo, crear un hook para mantener todas las ventanas
que son creadas por el proceso escondidas. Sólo he probado mi ejemplo con Internet Exlorer, y el truco SW_HIDE funciona correctamente,
de hecho, debería funcionar en cualquier aplicación con una interfaz gráfica más o menos normal.
Para asegurarnos de que el proceso ha cargado ya todas las librerías necesarias, y que ha alcanzado un nivel estable, utilizaremos la función WaitForInputIdle(), para darle tiempo
a la aplicación a estabilizarse.
Ahora procedemos a llamar a la función VirtualAllocExec(), para colocar memoria en los procesos creados
y WriteProcessMemory() para copiar nuestro código que trabajará en la red (aquel que hará como puerta trasera/troyano)
Finalmente usaremos CreateRemoteThread() para ejecutar el código, y entonces, sólo tenemos que esperar a que termine el hilo.
Al fin y al cabo, la inyección en sí no es tan complicada.
La función que será inyectada sólo puede recibir un argumento, una palabra doble. En el ejemplo que se presentará, el procedimiento infectado
se conectará a
www.phrack.org por el puerto 80 y enviará HTTP GET para recibir la cabecera. Tras recibir la cabecera, la mostrará un mensaje. unque es un ejemplo muy básico, el procedimiento no necesita más información.
De todas maneras, todavía tenemos que usar el parámetro para pasar el valor de 32 bits (una palabra doble) a nuestro procedimiento inyectado:
su dirección base. Con eso, el código inyectado sabe en que dirección de memoria ha sido situado, en el contexto del otro proceso.
Esto es muy importante porque no pordemos leer directamente del registro EIP, y porque nuestro código inyectado tendrá que acceder a direcciones de memoria de estructuras
de datos dentro del código inyectado.
Una vez inyectado y situado dentro del procedimiento remoto, el código inyectado practicamente no sabe nada. La primera tarea que realizará será buscar
la dirección base de la librería kernel32.dll en el espacio de memoria del procedimiento remoto, y desde allí, la dirección de la función GetProcAdress() para cargar
cualquier otra cosa que necesitemos.. No explicaré en detalle cómo se consiguen estos valores, todo no se puede cubrir en este documento.
Si estás interesado en detalles, te remito al documento sobre los componentes de win32 en el ensamblador, del grupo Last Stage of Delirium. He utilizado grandes partes de su documento para la escritur del código que
se describirá en los párrafos siguientes.
En términos simples, conseguimos la dirección base de kernel32.dll de la estructura Process Environmente Block (PEB), la cual se puede encontrar dentro de la estructura
Thread Environment Block (TEB). La dirección del offset del TEB siempre se almacena dentro del registro FS, así que es sencillo conseguirla.
Y desde que sabemos que el kernel32.dll se ha cargado, sólo tenemos que encontar la dirección de GetProcAdress(). Si no estás familiarizado con el formato PE, no te preocupes.
Una librería de enlace dinámico contiene una "sección de exportación". En esta seción, a los offsets de todas las funciones exportadas se les asigna nombres legibles para los humanos,
es decir, cadenas. De hecho, hay dos arrays (matrices o estructuras) que nos interesan. Hay más de dos arrays dentro de esta sección, pero a nosotros sólo nos interesan dos de ellas.
Durante el resto del documento, trataré los términos lista y array igualmente, puesto que a este nivel de programación no hay diferencia.
El primer array es una lista de cadenas estándar de C, que contienen los nombres de las funciones. El otro array contiene los puntos de entrada de las funciones,
es decir, los offsets.
Haremos algo muy parecido a lo que GetProcAdress() hace: buscaremos "GetProcAdress" en el primer array y encontraremos su offset en la segunda lista.
Desafortunadamente, a Microsoft se le ocurrió una idea para la exportación de sus DLL's, y esto se torna mucho más complicado. Esta idea consiste en los punteros
(Nota del traductor: se refiere a "forwarders", lo he traducido a "punteros" porque creo que está relacionado con los punteros de ciertos lenguajes de programación y porque era lo más "parecido"
al castellano. Originalmente "forward" quiere decir "adelante" , y básicamente quiere decir que una dll puede traspasar la función a otra dll.
En vez de apuntar al offset del código de una función dentro de una dll, el offset del segundo array apunta a una cadena. Por ejemplo:
la función HeapAlloc() de la librería Kernel32.dll apunta a la función RtlAllocoteHeap() de la librería ntdll.dll.
Esto siginifica que el citado offset de HeapAlloc() en kernel32.dll no serña el offset de una función que ha sido implementada en kernel32.dll, sino que será el offset de una cadena que ha sido situada dentro de kernel32.dll.
Esta cadena es: "NTDLL.RtlAllocateHeap".
Después de un rato, me di cuenta de que este puntero/cadena está situado inmediatamente después del nombre del función en el primer array.
Dentro de kernel32.dll se encontraría algo así:
Cita:
48 65 61 70 41 6C 6C 6F HeapAllo
63 00 4E 54 44 4C 4C 2E c.NTDLL.
52 74 6C 41 6C 6C 6F 63 RtlAlloc
61 74 65 48 65 61 70 00 ateHeap.
= "HeapAlloc\0NTDLL.RtlAllocateHeap\0"
Esto es por supuesto un poco confuso porque hay más cadenas en el primer array que offsets en el segundo - cada puntero parece el nombre de una función.
Para identificar la cadena "GetProcAdress", hago uso de una función hash para cadenas cortas del grupo LSD. La función hash es así en C:
Código:
unsigned long hash(const char* strData) {
unsigned long hash = 0;
char* tChar = (char*) strData;
while (*tChar) hash = ((hash<<5)|(hash>>27))+*tChar++;
return hash;
}
El hash calculado para "GetProcAdress" es 0x099c95590, y nosotros buscaremos una cadena en la sección de exportación (Nota del traductor: la de los dos arrays)
de kernel32.dll que coincida con el hash. Una vez que tenemos la dirección del GetProcAddress() y la dirección base de kernel32 podemos cargar
fácilmente todas las llamadas a la API que necesitemos. A partir de aquí, todo lo que tenemos que hacer es cargar la librería ws2_32.dll para utilizar los sockets.
Nota: Recomendaría leer [0x0B] ahora.
:_Límites de la implementación_____________________________________________________________________________________________________________
El código de ejemplo presentado dará un ejecutable que se ejecuta en el ring 3 (Nota del traductor: En la revista 18 hay un artículo sobre Virus,
en cuyo apartado 2.1.5 se habla de los privilegios o "rings" . Estoy seguro de que la mayoría de los cortafuegos contienen drivers en modo kernel
con la capacidad de ejecutar tareas más poderosas que este ejecutable inyector, por tanto, la capacidad de este ejecutable de saltarse la protección es limitada.
Lo he probado en los siguiente cortafuegos con los siguientes resultados:
Zone Alarm 4 vulnerable
Zone Alarm Pro 4 vulnerable
Sygate Pro 5.5 vulnerable
BlackIce 3.6 vulnerable
Tiny 5.0 immune
Tiny alerta al usuario de que el ejecutable inyector se "acopla" al proceso del navegador, intentado acceder a la red de esta manera. Parece que Tiny
actúa como todos los demas cortafuegos, pero simplemente es un poco más cuidadoso. Tiny, además, le hace un hook a la función CreateProcess() y CreateRemoteThread(),
por lo tanto, puede proteger a los usuarios de este tipo de ataques.
De todas maneras, por el resultado de las pruebas, confirmé que los cortafuegos actúan como módulos del kernel.
Así que no he presentado un ataque 100% efectivo, es sólo un ejemplo.
:_Otro método de infección____________________________________________________________________________________________________________________
El equipo de Phrack sugirió presentar un análisis sobre el problema de la infección con Tiny, pero supe que este no es el único problema
con el que hay que tener cuidado, ya que Tiny le hace un hook a la función CreateRemoteThread(). Desafortunadamente, estaba en lo correcto, y el simple hecho de
infectar un proceso que ya está ejecutandose hace saltar la alarma del cortafuegos tiny.
De todas maneras, aún hay más maneras de forzar la ejecución de nuestro propio código inyectado, y explicaré brevemente en que consiste este
método para aquellos que estén interesados. Lo que estoy intentando demostrar es que se pueden superar las restricciones de los cortafuegos mediante un esfuerzo a la hora
de escribir el código.
Las API's principales que utilzaremos serán GetThreadContext() y SetThreadContext(). Estas dos funciones poco documentadas te permiten modificar el contexto de un proceso.
¿Qué es el contexto de un proceso? El contexto es una estructura que contiene el valor actual de cada uno de los registros de un determinado proceso. Por lo tanto, con estas dos funciones
puedes obtener el valor de los registros, y, lo que es más importante, aplicar nuevos valores a cada uno de los registros de la CPU del proceso remoto.
De gran interés para nosotros es la posibilidad de modificar el registro EIP, el puntero de instrucciones de un hilo. (Nota del traductor: En el número 23 de la revista
hay un artículo interesante sobre Buffers Overflows en el que se explican detalladamente los registros, punteros, direcciones de memoria...Lectura recomendad )
Bueno, lo primero, fácilmente encontraremos un proceso al que se le permita la comunicación con la red.
Entonces, como siempre, inyectaremos nuestro código en su memoria con los métodos explicados anteriormente. Esta vez, sin embargo, no crearemos un nuevo hilo,
sino que secuestraremos el hilo primario del proceso de confianza para el cortafuegos y modificaremos el EIP para que apunte a nuestro propio código.
Esa es toda la teoría que hay detrás de este segundo método. En la práctica, procederemos de manera lo más cuidadosa posible para que el código sea lo más robusto posible.
No sólo escribiremos la función de inyección, sino varios códigos en ensamblador, con el fin de devolver al proceso inyectado a su estado
inicial después de que el nuestro haya terminado su trabajo. Cómo puedes ver, vamos a insertar una cadena de shellcode [0x0c] al proceso, que es así en un debugger:
Cita:
<base + 0x00> PUSHAD ; guarda todos los registros
<base + 0x01> PUSHFD ; guarda todas las flags (banderas)
<base + 0x02> PUSH <base + 0x13> ; primer argumento: dirección propia
<base + 0x07> CALL <base + 0x13> ; llama al código inyectado
<base + 0x0C> POPFD ; recupera las banderas
<base + 0x0D> POPAD ; recupera los registros
<base + 0x0E> JMP <orignal EIP> ; recupera el estado inicial (contexto)
<base + 0x13> ... ; el código de la función inyectada empieza aquí
Recuerda, este código será inyectado en una dirección de memoria (offset) muy alejada del contexto inicial del proceso.
Por eso vamos a necesitar un número de 4 bytes para la dirección relativa que necesitamos en el JMP.
Esta es una solución simple para evitar que nuestro proceso de confianza del cortafuegos se bloquee tras haber
ejecutado el código inyectado. Es más, decidí usar un evento que salta cuando el proceso inyectado ha recibido la cabecera HTTP.
De esta manera, el ejecutable inyector es informado una vez que la rutina que hemos inyectado ha terminado su trabajo.
Podemos desalojar ese espacio de memoria remoto y de esta manera realizar una "limpieza". EL sigilo lo es todo.
Debería decir que este método es un poco más frágil y menos fiable que el método mostrado anteriormente.
Este último método explicado funcionará con todos los cortafuegos que he probado y muy probablemente con
todos los demás. Pero de todas maneras debes tener en cuenta que para que estos ataques funcionen, hemos dado por hecho
que el Internet Explorer tiene acceso a internet, sino...búscate la vida con otro programa.
He probado este ataque junto con el Internet Explorer, pero otras aplicaciones podrían no reqerir que secuestraras el hilo primario
El hilo primario normalmente es una apuesta fiable porque asumimos que no se bloqueara en el momento de la infección, aunque
teóricamente podría ocurrir que la interfaz del programa se bloquease de repente porque el código inyectado se ejecuta más que el
código que trata de ejecutar el programa. Con este ejemplo tan simple e Internet Explorer, no me topé con dichos programas.
También funciona con "Outlook.exe", así que creo que se puede considerar un método bueno y estable.
:_Conclusión_______________________________________________________________________________________________________________________________
Creo que puedo estar contento con los resultados que he obtenido. Aunque el ejecutable inyector es inferior a cualquier cortafuegos con
módulos en el kernel, creo que funcionará con el 80% de los firewalls.
Mi segundo método incluso funciona contra todos ellos. Ambos ejemplos simnplemente mandan una petición de la cabecera HTTP, pero
sería relativamente fácil que hiciesen cualquier otra tarea en la red. Por ejemplo, mandar un e-mail con información crítica
se podría hacer casi de la misma manera. El código sólo tendría que ser un poco más sosfisticado y grande que el provisto aquí.
Teniendo en cuenta que he conseguido esto con una aplicación 5k (Nota del traductor: 5k ¿? (5k user-mode application))
estoy seguro de que será más fácil con un programa ejecutándose en el Ring 0, haciendo hooks a las llamadas de bajo nivel.
Quien sabe, quizás alguien que hizo el mismo tipo de investigación esté utilizando ya esta técnica. La conclusión general es que los
cortafuegos son inseguros. Y estoy muy de acuerdo con esta generalización: el problema de los cortafuegos es
el concepto, no la implementación.
El software no te puede proteger de otro software sin estar en constante riesgo de ser "esquivado" por otro software.
¿Porqué es esto un riesgo? Este es de hecho un gran riesgo porque los cortafuegos de hardware se están utilizando en estaciones windows
de manera generalizada. Dentro de una red, es común encontrar ambos cortafuegos, hardware y software. Además los cortafuegos de software
en dichas redes sirven al propósito de defender la red de troyanos y puertas traseras. Y, después de lo que hemos visto,
esta protección es muy débil.
Aparte del peligro de los ordenadores privados, que se ha demostrado que están insuficientemente protegidos contra troyanos y gusanos,
la explotación de un sistema remoto usando un cortafiegos puede abarcar los métodos descritos en este documento. El código en
ensamblador de cualquiera de los métodos se puede convertir en una shellcode para cualquier exploit remoto.
Una vez que hemos descubierto una vulnerabilidad en un sistema remoto, sería posible también saltarse la protección del cortafuegos así.
Las aplicaciones de ejemlo se conectan a
www.phrack.org por el puerto 80, pero podrías hacer incluso que se conectasen a tu IP.