Análisis de malware

Análisis del robot de Andrómeda, parte 1

septiembre 25, por Ayoub Faouzi

Introducción:

Andrómeda, también conocida como Win32/Gamarue, es una botnet basada en HTTP. Fue visto por primera vez a finales de y todavía en este momento se utiliza mucho en el pastoreo. También se ha observado que este tratamiento también elimina otros malwares como ZeuS, Torpig y Fareit.

Este artículo arrojará algo de luz sobre el funcionamiento interno de la última variante de esta botnet, cómo los malwares siguen cambiando su estructura para evadir los sistemas de análisis automáticos y frustrar a los analistas de malware. El cargador tiene funciones anti-VM y anti-depuración. Se inyectará en procesos confiables para ocultarse. Tiene algunas técnicas de persistencia. La interacción entre sus procesos maliciosos gemelos inyectados y su protocolo de comunicación con el servidor de comando y control está cifrada.

¡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

Al igual que los bots conocidos como ZeuS, Andromeda también es modular, lo que significa que admite un sistema de interfaz enchufable y puede incorporar varios módulos, como:

Aparte de eso, el código principal simplemente consiste en un cargador, que proporciona algunas características predeterminadas. Puede descargar y ejecutar otros ejecutables/DLL, así como actualizarse y eliminarse si es necesario.

Normalmente, las variantes del malware Andromeda se pueden comprar online por 300-500 dólares a través de un foro clandestino. Los precios varían dependiendo de la versión de la botnet y de cuánto está dispuesto a gastar el cliente en los diferentes módulos que la acompañan. El número de versión más reciente que he identificado es la versión 2.09.

Hilo de ventas:

A continuación se muestra una captura de pantalla del panel de administración de comando y control:

El vector de infección llega a través de medios familiares: desde correos electrónicos spam con archivos adjuntos maliciosos hasta kits de explotación como Sweet Orange o Blackhole alojados en sitios web pirateados que promueven Andromeda y también desde otros malwares que eliminan esta amenaza.

Herramientas y descargas:

  1. OllyDBG / IDA Pro / PETools / Explorador de procesos.
  2. Muestra y muestra desempaquetada
    [descargar]

Desembalaje:

La muestra que estamos analizando aquí se empaqueta primero con una empacadora personalizada. Primero desempaquémoslo para obtener el archivo original. En general, podrás reconocer fácilmente si un archivo está empaquetado:

Puede descomprimir un archivo simplemente rastreando todo el código auxiliar de descomprimido hasta que encuentre un JMP porque sabe que en algún momento debe transferir la ejecución al punto de entrada original (OEP), o creando un punto de interrupción de hardware en el cambio de registro ESP (o PUSHAD, POPAD truco), o a veces usando las excepciones generadas por el empaquetador.

Por supuesto, el desembalaje varía según la complejidad de la empacadora. A veces, el algoritmo de descompresión está muy confuso y tiene muchos trucos anti-depuración y anti-rastreo. Por ejemplo, la API ha sido redirigida, el empaquetador utiliza subprocesos múltiples, se han robado algunos bytes en el punto de entrada o se ha eliminado el encabezado PE, etc.

En el campo del análisis de malware, existe un enfoque que funciona la mayor parte del tiempo: los empaquetadores/criptadores de PE comprimen o cifran las secciones de PE o algunos otros datos utilizando algunos algoritmos de compresión/cifrado como LZMA. Antes de ejecutar el código malicioso real, el empaquetador tendría que descomprimir el código comprimido. Para hacer esto, generalmente asigna algo de espacio usando VirtualAlloc, VirtualAllocEx o ZwAllocateVirtualMemory. Luego descomprimirá los datos en la memoria asignada. Podemos establecer puntos de interrupción en estas API.

Luego, las importaciones se corrigen para que el malware pueda utilizar las API importadas. Para resolver las direcciones de importación utilizará la API GetProcAddress/LoadLibrary o dinámicamente con la estructura PEB_LDR_DATA. Verá que se llamará repetidamente a GetProcAddress en el bucle. Este bucle se utiliza para resolver todas las API en la DLL. También podemos establecer un punto de interrupción en estas API y omitir el ciclo para continuar con la depuración.

Carguemos la muestra en OllyDBG y BP en VirtualAlloc:

Después de alcanzar el BP, ejecute hasta regresar (CTRL+F9), luego F8, anote la dirección de devolución que para mí es 00390000. Este es el espacio de memoria asignado para el código, que se supone que debe escribirse. Luego, desplácese hacia abajo y continúe depurando hasta que vea:

001287F1 65:FFMP DWORD PTR GS:[EAX]

Coloque un BP en PUSH DS y en la dirección virtual (VA) 00390000, y asegúrese de que en la opción OllyDBG esté ignorando el rango de excepciones personalizadas de 00000000 a FFFFFFFF porque JMP DWORD PTR GS:[EAX]

en realidad generará una excepción o parcheará esta instrucción a JMP 00390000 y luego SHIFT + F9.

Entonces aterrizas aquí:

Luego, verá después del marco de la pila las instrucciones que buscan el PEB (Bloque de entorno de proceso), el TIB apunta al PEB.(Bloque de información del subproceso), que siempre se encuentra en FS:[0]. Una de las entradas PEB es un puntero a una estructura llamada PEB_LDR_DATA. Esta estructura contiene información sobre todos los módulos cargados en el proceso actual. En el desplazamiento 0x1C de PEB_LDR_DATA está el puntero de InInitializationOrderModuleList a lo largo de la lista de enlaces de InIntializationOrderModuleList donde puede encontrar la DLL cargada. Este empaquetador está buscando kernel32.dll. Después de encontrar kernel32.dll, el desplazamiento 0x08 contiene la dirección base de kernel32.dll en la memoria, el desplazamiento 0x3C es el encabezado PE de kernel32.dll y finalmente el desplazamiento 0x78 del encabezado PE es el puntero para exportar la tabla de direcciones de funciones.

Dado el puntero al EAT, entrará en un bucle que analiza el EAT para buscar la dirección de la función GetProcAddress. Esta API se utilizará junto con LoadLibrary para resolver direcciones API dinámicamente.

Después de leer este código, verá varias instrucciones MOV que copian por byte los nombres de las API que el empaquetador está buscando: TerminateThread, GetCurrentThreadId, GetCurrentThread, LoadLibraryA, CreateProcessA, ExitProcess, ResumeThread, SetThreadContext, GetThreadContext, WriteProcessMemory; VirtualAllocEx, ZwUnmapViewOfSection, GetModuleHandleA:

Continúe caminando hasta:

003907F7 FFD3 LLAMADA EBX; kernel32.VirtualAlloc

O simplemente presione F9 (ejecutar), recibirá la llamada a VirtualAlloc que me devolverá 003A0000. Anote el dwSize, que es 3600. Esta es la ubicación donde se descomprimirá nuestro archivo. Continúe rastreando hasta que vea:

Después de realizar toda la rutina de descompresión, verá aparecer la magia ‘MZ’ al comienzo de nuestro VA. Anota el VA y el tamaño.

Después de seguir el código, verá la resolución de algunas API. ¿Te suenan estas API?

De hecho, es un empaquetador RunPE típico, más conocido como “VBInject” o “VBCrypt” en la industria audiovisual. La principal diferencia en comparación con los empaquetadores tradicionales que sobrescriben la memoria de su propio proceso es que el ejecutable empaquetado genera un nuevo proceso en el que inyecta el binario PE malicioso real. Puede volver a iniciarse como un nuevo proceso o lanzar una nueva versión sagrada de una aplicación inocente como svchost.exe. El propósito de esta técnica es evadir la detección AV; todos los RunPE funcionan de la misma manera:

El cargador escribe la nueva dirección base en el PEB y llama a SetThreadContext para señalar a EAX al nuevo punto de entrada.

Finalmente, el cargador reanuda el hilo principal del proceso de destino con ResumeThread y el cargador de Windows PE hará su magia. El ejecutable ahora se asigna a la memoria sin tocar el disco.

Si está interesado en cómo se implementa esta técnica, aquí tiene una versión C++:

typedef LONG (WINAPI * NtUnmapViewOfSection)(HANDLE ProcessHandle, PVOID BaseAddress);

ejecución de clase PE {

público:

ejecución vacía (LPSTR szFilePath, PVOID pFile)

{

PIMAGE_DOS_HEADER IDH;

PIMAGE_NT_HEADERS INH;

PIMAGE_SECTION_HEADER ISH;

PROCESO_INFORMACIÓN PI;

INICIOINFOA SI;

PCONTEXTO CTX;

PDWORD dwImageBase;

NtUnmapViewOfSection xNtUnmapViewOfSection;

LPVOID pImageBase;

int Conde;

IDH = PIMAGE_DOS_HEADER(pFile);

si (IDH-e_magic == IMAGE_DOS_SIGNATURE)

{

INH = PIMAGE_NT_HEADERS(DWORD(pFile) + IDH-e_lfanew);

si (INH-Firma == IMAGE_NT_SIGNATURE)

{

RtlZeroMemory(SI, tamaño de(SI));

RtlZeroMemory(PI, tamaño de(PI));

si (CreateProcessA (szFilePath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, SI, PI))

{

CTX = PCONTEXT(VirtualAlloc(NULL, tamaño de(CTX), MEM_COMMIT, PAGE_READWRITE));

CTX-ContextFlags = CONTEXT_FULL;

si (GetThreadContext(PI.hThread, LPCONTEXT(CTX)))

{

ReadProcessMemory(PI.hProcess, LPCVOID(CTX-Ebx + 8), LPVOID(dwImageBase), 4, NULL);

si (DWORD(dwImageBase) == INH-OptionalHeader.ImageBase)

{

xNtUnmapViewOfSection = NtUnmapViewOfSection(GetProcAddress(GetModuleHandleA(“ntdll.dll”), “NtUnmapViewOfSection”));

xNtUnmapViewOfSection(PI.hProcess, PVOID(dwImageBase));

}

pImageBase = VirtualAllocEx(PI.hProcess, LPVOID(INH-OptionalHeader.ImageBase), INH-OptionalHeader.SizeOfImage, 0x3000, PAGE_EXECUTE_READWRITE);

si (pImageBase)

{

WriteProcessMemory(PI.hProcess, pImageBase, pFile, INH-OptionalHeader.SizeOfHeaders, NULL);

para (Contar = 0; Contar INH-FileHeader.NumberOfSections; Contar++)

{

ISH = PIMAGE_SECTION_HEADER(DWORD(pFile) + IDH-e_lfanew + 248 + (Conteo * 40));

WriteProcessMemory(PI.hProcess, LPVOID(DWORD(pImageBase) + ISH-VirtualAddress), LPVOID(DWORD(pFile) + ISH-PointerToRawData), ISH-SizeOfRawData, NULL);

}

WriteProcessMemory(PI.hProcess, LPVOID(CTX-Ebx + 8), LPVOID(INH-OptionalHeader.ImageBase), 4, NULL);

CTX-Eax = DWORD(pImageBase) + INH-OptionalHeader.AddressOfEntryPoint;

SetThreadContext(PI.hThread, LPCONTEXT(CTX));

ResumeThread(PI.hThread);

}

}

}

}

}

VirtualFree(pFile, 0, MEM_RELEASE);

}

};

Las debilidades de RunPE deberían ser obvias para cualquiera: en algún momento, el cargador tiene que descifrar el ejecutable en el espacio de memoria del cargador. Además, el ejecutable original se asignará al espacio de memoria del proceso de destino en un estado legible; puedes volcar fácilmente el ejecutable en un archivo.

Ahora que conoce las funciones API correctas para iniciar, puede comenzar a descomprimirlas. A veces, el malware, para iniciar un nuevo proceso, puede llamar a CreateProcessInternal en lugar de CreateProcess, o para escribir en la nueva sección, puede llamar a ZwWriteVirtualMemory en lugar de WriteProcessMemory, lo que inutiliza el punto de interrupción en esa API.

Por lo tanto, siempre debe interrumpir las funciones ntdll si es posible, para asegurarse de que el malware no opere en un nivel inferior al suyo u otra opción es colocar un BP en LoadLibraryA y GetProcAddress para saber qué funciones se están utilizando. Además, otra cosa muy común entre todo el malware RunPE es la llamada a la función ZwResumeThread en el paso final, por lo que vale la pena intentarlo.

Por lo tanto, puede simplemente colocar un punto de interrupción en ZwResumeThread, esperar hasta que la ejecución se interrumpa allí, adjuntarlo al proceso generado, establecer un punto de interrupción en el punto de entrada del hilo suspendido y reanudarlo. Luego, la ejecución se detiene en el punto de entrada y puede volcar la memoria del proceso utilizando algún complemento de depuración como OllyDump o una herramienta independiente. Podrías ver la inyección en Process Explorer:

Por otro lado, lo que haré será simplemente desechar el código del proceso del empaquetador después de haberlo descifrado. ¿Recuerda VA 003A0000 y el tamaño 0x3600? Estoy usando PETools para realizar un volcado parcial: