Cómo hackear unikernels – Guía paso a paso
Mucha gente tiene la noción errónea de que las aplicaciones unikernel tienen una característica de ser ‘inhackeables’. Esto no es cierto. Son absolutamente hackeables dependiendo de lo que se implemente y cómo estén configurados.
Sin embargo, la razón por la que existe esta noción es que representan una desviación absoluta de los ataques tradicionales. Esto realmente obliga al atacante a trabajar arduamente. Atacar Linux boxes sigue siendo una hazaña increíblemente fácil porque el sistema en sí está básicamente diseñado para ello.
Habiendo dicho eso, tradicionalmente hablando, los ataques de código shell puro y los ataques construidos a partir de dispositivos ROP son de uso limitado. Al final del día, realmente actúan como una forma de abrir la puerta de la casa. Todavía quieres robar las armas y el dinero y eso sucede después de la explotación.
Realmente deseas ejecutar un programa diferente de algún tipo que no sea el que estás explotando. De lo contrario, no tiene sentido atacar, para empezar, mucha gente pasa por alto este hecho.
Ese programa preferiblemente debe ser en forma de shell. O al menos algo como mysqldump o un criptominero monero (que estaba de moda en los últimos años) o algo así. No deseas rellenar un cliente mysql completo como un payload escrito en shellcode o gadgets rop, eso no es realmente factible.
Problema
El problema es que los unikernels son un proceso único por definición. No tienen instalaciones inherentes dentro del kernel para ejecutar múltiples procesos. Aunque hay muchas implementaciones, nanos incluidos, que te permitirán acceder a todos los hilos que desees o necesites para que puedas tener todo el rendimiento.
Lo que esto significa es que no puede simplemente dividir/ejecutar y ejecutar tu criptominero, simplemente no hay soporte en el núcleo para hacerlo.
Entonces, incluso si descubres una vulnerabilidad dentro de un unikernel, ¿cómo haces para ejecutar ese otro programa que deseas ejecutar?
Inyección de proceso
Los VXers y los señores de las botnets han usado métodos como el vaciado de procesos y la inyección de procesos. Esto para ocultar, ofuscar y evadir la detección.
Cuando estás colocando un payload en unos cientos de miles de inocentes máquinas distribuidas por todo el mundo, la mayoría de ellas ejecutará algún tipo de protección antivirus.
La forma canónica de buscar malware es buscar lo que se llama un IOC (indicador de compromiso). Si vas a la sección del blog de una compañía de protección de endpoints y ves sus artículos de investigación, generalmente tendrán hashes de archivos. Además, ips que muestran los IOC del malware que están investigando.
Esto atrapará una gran cantidad de malware de baja calidad. Las técnicas como el vaciado de procesos y la inyección de procesos, por otro lado, actúan como buenos boy scouts (o quemadores). Estos no dejan rastro en el sistema de archivos. A veces se los conoce como “ataques en memoria”.
Windows ha hecho que esta “característica” sea bastante sencilla de explotar con llamadas como CreateRemoteThread, que hace exactamente lo que parece.
Sin embargo, si estás leyendo este artículo, probablemente no te interese Windows. Esa es una causa perdida, sin el conocimiento de los gobiernos de las ciudades pobres que sufren de ransomware. La infraestructura más moderna se ejecuta en Linux.
Atacando Linux
¿Mucha gente conoce estas técnicas para Windows, pero para Linux? Bueno, en realidad puedes comenzar a ejecutar un nuevo programa sin tener que dividirlo o sin tener que generar uno desde una shell.
Como ves en Linux, el iniciar un nuevo proceso generalmente implica ejecutar la función fork y ejecutarla. Todo el mundo sabe que los unikernels no admiten fork, pero algunas personas no se dan cuenta de que tampoco admiten exec.
Veamos algunos de los métodos que podrías usar en Linux.
Un método incluye el uso LD_PRELOAD con una biblioteca compartida para anular el código en el proceso que deseas ejecutar, aunque eso implica algunas cosas.
Uno: implica que estás comenzando un proceso con acceso local y dos no es un proceso existente. Este es un proceso clásico de vaciado, básicamente ejecutando un programa inocente. Posteriormente cargar el parásito y luego ejecutarlo todo mientras dejas que el resto del sistema parezca que todo está bien.
Otro método usa ptrace.
Ptrace
ptrace es una llamada al sistema que se explota demasiado de muchas cosas diferentes.
La técnica aquí es bastante simple. Debes tomar tu biblioteca compartida y la inyectas a través de ptrace.
Lo bueno es que está casi garantizado que tendrás esta capacidad desactivada en cualquier box en la que te encuentres actualmente. Puedes comprobar a través de proc:
/proc/sys/kernel/yama/ptrace_scope
Los métodos más nuevos abusan de la memoria compartida con shm_open, aunque en muchos entornos /dev/shm se montará noexec. Puedes verificar si este es el caso o no al usar grep en la salida de montaje:
mount | grep /dev/shm tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
Como puede ver en este reciente Ubuntu box (bionic 18.04); significa que es vulnerable a este ataque.
MemFD_Create
Esto nos lleva a la técnica memfd_create. Esta técnica nos permite crear un archivo anónimo para insertar nuestro programa y luego podemos ejecutarlo. Echemos un vistazo a esto.
Digamos que tenemos control sobre un servidor web vulnerable que hemos atacado. Tal vez eso es a través del reciente exploit de ghostcat. Nos gustaría inyectar este pequeño y diminuto programa parasitario en el proceso del host y hacerlo de manera que no deje rastros en el disco.
En él, imprimimos la identificación del proceso y luego vemos si podemos encontrar el binario en el disco o no. Si podemos, sabemos que lo estamos ejecutando normalmente, pero si no, indicará que lo estamos ejecutando desde la memoria:
#include #include int main(int argc, char **argv) { printf("parasite process ID : \t%d\n", getpid()); FILE *parasite = fopen(argv[0], "r"); if (parasite == NULL) { while(1) { printf("running from memory!\n"); sleep(3); } } else { printf("exec'ing in disk\n"); } return 0; }
Ahora echemos un vistazo a cómo sería un payload teórico que podemos ejecutar en nuestro servidor web. En este ejemplo, cargamos el programa localmente desde el disco, pero también podrías enviarlo a través de http. Por ejemplo, un formulario de carga de imágenes; quiero decir, no es que existan POC para esto.
Ahora, cuando se carga, aparecerá en el proceso, sin embargo, como puedes ver, puedes nombrar el proceso como desees y disfrazarlo como algo inofensivo. Luego, simplemente emitimos una llamada al ejecutable.
#define _GNU_SOURCE #include #include <sys/syscall.h> #include <linux/memfd.h> #include #include #include int main() { FILE *f; unsigned char *bin; long filelen; printf("original process id:\t%d\n", getpid()); f = fopen("parasite", "rb"); fseek(f, 0, SEEK_END); filelen = ftell(f); rewind(f); bin = (unsigned char *)malloc(filelen * sizeof(unsigned char)); fread(bin, filelen, 1, f); fclose(f); int fd; if((fd = syscall(SYS_memfd_create, "h4x0r", MFD_CLOEXEC)) == -1) err(1, "memfd_create"); write(fd, bin, filelen*sizeof(char)); char *binpath; asprintf(&binpath, "/proc/self/fd/%d", fd); char* argv[] = { "h4x0r", NULL }; char* envp[] = { NULL }; execve(binpath, argv, envp); free(bin); free(binpath); }
Si ejecutas esto en Linux, puedes ver que hemos inyectado con éxito el proceso:
eyberg@box:~/scratch$ ./main original process id: 1480 parasite process ID : 1480 running from memory! running from memory!
Si busca el ID de proceso 1480, también encontrarás que el nombre ha cambiado a h4x0r. Así que este ataque definitivamente funciona en Linux. Algunas boxes son un poco más paranoicas y bloquean esto. Como no tenemos un ejecutable, podemos ver otras opciones.
Userland Exec
La gente ha tratado de sortear esta limitación de mil maneras diferentes, sobre todo esto fue originalmente explorado por grugq.
Básicamente, el concepto es reescribir exec en userland. Desde entonces, ha habido muchas reescrituras, como esta con capacidad de 64 bits. Empero, ha sido descartada debido a las protecciones de destrucción del conjunto. Asimismo, con una más reciente: libreflect de rapid7.
Muchos de los ejecutables de userland que encontrarás en línea no funcionarán en nuestro entorno por una variedad de razones. Estos no son transferibles de unikernel a unikernel.
Algunos son antiguos y están basados en 32 bits: nanos solo ejecuta código de 64 bits. Algunos solo funcionan para archivos binarios enlazados estáticamente: la mayoría de los archivos binarios en Linux hoy en día están enlazados dinámicamente. Luego está el problema de la destrucción del conjunto, que está activado de forma predeterminada.
Hay algunas cosas que impiden que esto funcione en Nanos hoy (incluido el hecho de que no tenemos procesos). No obstante, una vez dicho esto, si atacaras a un unikernel, esta es una forma de hacerlo.