Análisis de malware
Introspección de máquinas virtuales en el análisis de malware: caso de uso
marzo 26, por Youness Zougar
Para determinar el comportamiento de un malware, desarrollaremos un script (basado en funciones de LibVMI) que nos permitirá rastrear las API del Kernel ejecutadas por un malware y sus argumentos.
1. Preparación
Después de elegir el nombre de dominio de la máquina, cree el archivo que contiene el diccionario y determine el nombre del archivo de malware que se analizará.
¡Conviértete en un ingeniero inverso certificado!
Obtenga capacitación práctica en vivo sobre análisis de malware desde cualquier lugar y conviértase en un analista certificado de ingeniería inversa. Comienza a aprender
El siguiente comando se ejecuta desde el hipervisor:
./monitor_api w6164-1 /tmp/w6164-1.json malware.exe
Este script tomará varias entradas como argumento:
- w6164-1: El nombre de dominio de la máquina virtual en Xen
- /tmp/w6164-1.json : El archivo que contiene las funciones/estructuras y las compensaciones correspondientes.
- malware.exe : El malware que queremos analizar.
A continuación se muestra el código que permite la inicialización de nuestro sistema de introspección:
// Crea una vista altp2m que se modificará con puntos de interrupción...
xc_altp2m_set_domain_state(xch, id_dominio, 1);
...
xc_altp2m_create_view(xch, domain_id, 0, shadow_view);
...
2. Inserción de puntos de interrupción
Primero definimos las API que queremos monitorear. Tomemos, por ejemplo, las siguientes tres API:
- NtCreateFile : permite la creación/apertura de archivos
- NtSetValueKey : crea o reemplaza el valor de una clave de registro
- NtDelayExecution : Retrasa la ejecución (esta API se ejecuta cuando la llamada a la API Sleep se realiza desde el modo usuario). Se puede utilizar como técnica de evasión.
Para obtener más información sobre estas API, puede consultar la documentación de MSDN.
Luego, insertamos los puntos de interrupción en la vista altp2m para cada API.
// Agregar puntos de interrupción en las API monitoreadas en la vista altp2m...
xc_altp2m_set_domain_state(xch, id_dominio, 1); trampa uint8_t = 0xcc;
vmi_write_8_pa(vmi, (sombra 12) + sombra_offset, trap);
...
3. Inicialización de devoluciones de llamada
En este ejemplo, nos basaremos en tres tipos de devoluciones de llamada que inicializaremos.
3.1. Devolución de llamada de eventos de interrupción
Esta devolución de llamada se activa cada vez que se atrapa un punto de interrupción. Recuperará la información deseada a través de la función process_event() . Esta función permitirá identificar el nombre de la API que alcanzó el punto de interrupción y sus argumentos.
// Registra la devolución de llamada del evento de interrupción, que se ejecutará cada vez que se capture un punto de interrupción...
SETUP_INTERRUPT_EVENT ( trap_event, 0, interrupción_event_cb);
vmi_register_event (vmi y trap_event);
...
event_response_t interrupt_event_cb (vmi_instance_t vmi, vmi_event_t * evento)
{
...
evento_proceso (...);
evento- slat_id = 0;
evento-interrupt_event.reinject = 0;
devolver VMI_EVENT_RESPONSE_TOGGLE_SINGLESTEP | VMI_EVENT_RESPONSE_VMM_PAGETABLE_ID;
}
3.2. Devolución de llamada de eventos paso a paso
Esta devolución de llamada se activa directamente después de que se ejecuta la devolución de llamada de interrupción. Esto nos permite ejecutar, paso a paso (como ocurre con los depuradores), la instrucción del punto de interrupción desde la vista inalterada. Luego reanudamos la ejecución desde la vista alterada. En el caso de que se asignen varias vCPU a la máquina virtual, es necesario realizar un bucle en la cantidad de vCPU y crear una devolución de llamada para cada una.
// Registra la devolución de llamada de un evento de un solo paso que ejecutará una instrucción...
int vcpus = vmi_get_num_vcpus (vmi);
para (i = 0; i vcpus; i ++)
{
SETUP_SINGLESTEP_EVENT ( singlestep_event [i], 1u i, singlestep_event_cb, 0);
vmi_register_event (vmi y singlestep_event [i]);
}
...
event_response_t singlestep_event_cb (vmi_instance_t vmi, vmi_event_t * evento)
{
evento- slat_id = shadow_view;
devolver VMI_EVENT_RESPONSE_TOGGLE_SINGLESTEP | VMI_EVENT_RESPONSE_VMM_PAGETABLE_ID;
}
3.3. Devolución de llamada de eventos de lectura/escritura de memoria
Esta devolución de llamada se activa cuando se realiza una lectura o escritura en una de las páginas de memoria de la instantánea. Si este es el caso, se realiza instantáneamente un cambio de vista en la vista inalterada. Esto evita un BSOD cuando el sistema o el malware realiza una verificación de integridad.
// Registra la devolución de llamada del evento de memoria, que cambiará la vista de altp2m a la vista inalterada// Para permitir otras acciones (por ejemplo, nunca alterar una verificación de integridad, de lo contrario aparecerá BSOD)
...
SETUP_MEM_EVENT ( mem_event, ~ 0ULL, VMI_MEMACCESS_RW, memoria_event_cb, 1);
vmi_register_event (vmi y mem_event);
...
event_response_t memoria_event_cb (vmi_instance_t vmi, vmi_event_t * evento)
{
evento- slat_id = 0;
devolver VMI_EVENT_RESPONSE_TOGGLE_SINGLESTEP | VMI_EVENT_RESPONSE_VMM_PAGETABLE_ID;
}
Para obtener más información sobre el funcionamiento de estas devoluciones de llamada, consulte https://libvmi.com
4. Recuperar información
Cuando se alcanza un punto de interrupción, se confronta con el diccionario API y de compensación. Luego se recupera la información deseada (argumentos de la API) y se continúa con la ejecución.
Tomemos como ejemplo NtCreateFile . Para obtener los argumentos de esta función, primero definimos su prototipo:
NTSTATUS NtCreateFile (FUERA PHANDLE FileHandle,
EN ACCESS_MASK Acceso deseado,
EN POBJECT_ATTRIBUTES Atributos de objeto,
FUERA PIO_STATUS_BLOCK IoStatusBlock,
IN PLARGE_INTEGER Tamaño de asignación OPCIONAL,
EN ULONG FileAttributes,
EN ULONG ShareAccess,
EN ULONG CreateDisposition,
EN ULONG CreateOptions,
EN PVOID EaBuffer OPCIONAL,
EN ULONG Longitud Ea
);
El nombre del archivo accedido y los derechos de acceso son, entre otros, la información que puede interesarnos sobre esta API. El nombre del archivo está en la estructura OBJECT_ATTRIBUTES. Para analizar esta estructura utilizaremos el diccionario generado previamente, que contiene los desplazamientos de las distintas entradas de la estructura:
"_OBJECT_ATTRIBUTES": [48, {"Atributos": [24, ["unsigned long", {}]],
"Longitud": [0, ["largo sin firmar", {}]],
"Nombre de objeto": [16, ["Puntero", {"objetivo": "_UNICODE_STRING"}]]
Haremos lo mismo con las demás estructuras.
No es necesario modificar los argumentos de la API, aunque sea posible hacerlo. El objetivo es dejar que el malware se ejecute sin modificar su comportamiento. Recuperamos la información de interés sobre la marcha y luego, al final de su ejecución, analizamos la información recopilada y determinamos su comportamiento.
A continuación se muestra un registro de eventos generado durante el análisis de malware. Sólo se conserva información referente a posibles comportamientos maliciosos.
...{"target":"C:UsersFrancisAppDataRoamingWindowsconhost.exe","process":"C:UsersFrancisDesktopsample.exe","pid":2616,"api":"NtCreateFile","access_mask":1074790528},
...
...
{"target":"REGISTRYUSERS-1-5-21-336141597-709016518-532797093-1001SOFTWAREMICROSOFTWINDOWSCURRENTVERSIONRUNPersistence","process": "C:UsersFrancisDesktopsample.exe","pid":2616,"value":"C:UsersFrancisAppDataRoamingWindowscon anfitrión. exe","api":"NtSetValueKey"},
...
...
{"value":3600,"process":"C:UsersFrancisDesktopsample.exe","pid":2616,"api":"NtDelayExecution"},
...
5. Conclusión
Aside from using introspection in virtualization, security solutions developers took advantage of this technology to use it in other areas such as malware analysis. Its features are essential assets for dealing with advanced and sophisticated forms of malware. The use of introspection in malware analysis is not yet fully democratized, since the technology requires quite advanced skills on Windows and Linux internals.
A special thanks to Tamas (aka @tklengyel), who is doing a great job out there with the LibVMI framework.