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
vsock_poc — Investigando il bug dietro CVE-2021-26708 | Kitploit
Strumenti/GitHubGitHub/jordan9001/vsock_poc
Analisi delle VulnerabilitàExploitDebuggerPaper e RicercaApprendimento e FormazioneBinary Exploitation
GitHubjordan9001/vsock_poc

vsock_poc

Investigando il bug dietro CVE-2021-26708

Vedi Repository
2825 anni faRevisionato da Kitploit

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

vsock_poc

Indagine sul bug dietro CVE-2021-26708


Questo repository contiene un piccolo writeup su CVE-2021-26708 e su come questo bug possa essere trasformato in una primitiva di scrittura Use After Free. La PoC qui non è un exploit completo, ma solo il mio harness che ho usato per investigare questo bug. Riesce a usare con successo una voce della cache kmalloc-64 dopo che è stata liberata, ma non ha alcun codice per fare grooming della memoria e posizionare qualcosa di interessante nello slot.

Questo è un bug divertente segnalato da @a13xp0p0v. Ha attirato la mia attenzione perché la patch era così semplice, solo impedire che un riferimento a vsk->transport venisse ottenuto al di fuori del lock in 5 punti diversi. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c518adafa39f37858697ac9309c6cf1805581446

Di seguito una breve panoramica del processo per passare dalla patch a una primitiva use-after-free che potrebbe essere usata per lo sfruttamento. La panoramica dovrebbe essere utile ad altri che vogliono esplorare questo bug.

Configurazione dell'ambiente

Ho scaricato il kernel linux 5.10.13 e ho annullato manualmente la patch mostrata sopra. Per maggiori informazioni su come compilare ed eseguire il kernel, questo è un buon riferimento.

https://fedoraproject.org/wiki/Building_a_custom_kernel

Ho anche modificato i parametri di avvio per abilitare il debug del kernel con kgdb. Usando gdb con il file vmlinux che avevo compilato in precedenza, avevo tutti i simboli del kernel principale, ma non quelli dei moduli kernel caricabili. Il codice associato alla vulnerabilità non veniva caricato di default, ma veniva caricato nel kernel quando viene usata la famiglia PF_VSOCK (a seconda di come hai compilato il kernel).

Per ottenere i simboli in kgdb per i moduli caricati, mi sono assicurato di usare il socket vsock almeno una volta, poi ho usato sudo cat /proc/modules | grep vsock per ottenere gli indirizzi di base dei moduli associati. In gdb facevo poi qualcosa come (gdb) add-symbol-file ./net/vmw_vsock/vsock.ko 0xffffffffc0567000 per far sapere a gdb dove in memoria si trovano i simboli di quel file .ko. vsock.ko e vmw_vsock_virtio_transport_common.ko erano i due più rilevanti.

Caccia alla primitiva

È divertente lavorare a ritroso partendo dalle patch perché, a differenza di molta caccia alle vulnerabilità, sai per certo di stare già guardando nel punto giusto. In questo caso sappiamo dalla patch che un riferimento al trasporto viene salvato prima che venga ottenuto sock_lock. Possiamo tranquillamente aspettarci che la vulnerabilità sia dovuta al cambiamento del trasporto, mentre viene usato il vecchio riferimento.

In questo tipo di scenario spereremmo che il trasporto stesso sia un oggetto allocato dinamicamente che può essere liberato e sostituito con qualche altro oggetto tra l'ottenimento del riferimento e il momento in cui il lock viene tenuto. Purtroppo, quando tracciamo i tempi di vita dei trasporti rilevanti implementati dagli altri moduli, sembrano essere tutti in memoria globale. Quindi cercheremo un livello più profondo per gli elementi usati quando si è fuori scope.

La Free pt. 1

Guardando in af_vsock.c, possiamo trovare due punti in cui vsk->transport viene modificato. In vsock_assign_transport e vsock_deassign_transport. In vsock_assign_transport possiamo vedere che se esiste un trasporto diverso, allora vsock_deassign_transport viene chiamata prima di inserire il nuovo trasporto. Se guardiamo le possibilità per la chiamata vsk->transport->destruct(vsk) qui, vediamo che sia il trasporto loopback che quello virtio fanno semplicemente kfree del parametro vsk->trans qui. Bingo! Se riusciamo a trovare (1) un percorso verso questa chiamata che possa essere in competizione (race) con (2) una funzione vulnerabile che usa un riferimento al trasporto ottenuto prima della sua distruzione per accedere a vsk->trans, allora abbiamo la nostra primitiva.

Cercando un percorso verso vsock_deassign_transport, la vediamo chiamata da vsock_sk_destruct o da vsock_assign_transport. vsock_sk_destruct è impostata come funzione sock->destruct e quindi chiamate a __sys_close, o altre chiamate disponibili lungo il percorso di distruzione come sock_put, sock_close, o vsock_release possono finire qui.

Il percorso più rilevante verso vsock_assign_transport passa attraverso vsock_stream_connect, ma richiede che il socket sia in alcuni stati specifici, e finisce per chiamare vsock_deassign_transport solo se il trasporto cambierebbe. E sostituirebbe il parametro vsk->trans se non finiamo con un nuovo trasporto NULL.

L'Use

Prima di spingerci troppo oltre nel trovare quale percorso verso la free sia quello giusto per noi, vogliamo determinare che esista un percorso valido che usi il membro vsk->trans con un riferimento non valido a un trasporto distrutto. Possiamo controllare metodicamente ogni punto in cui il trasporto viene usato con un riferimento potenzialmente non valido. Rintracciando quei buchi possiamo trovare quelli in cui viene usato vsk->trans. Il percorso migliore sembra essere attraverso vsock_stream_setsockopt qui, quando transport->notify_buffer_size scrive in un offset all'interno di vsk->trans per i trasporti loopback e virtio proprio qui. Se il trans è già stato liberato quando viene usato lì, otteniamo una bella scrittura di un u32 a un offset di 0x28 in un'allocazione kmalloc-64.

La Race

L'uso di vsock_stream_setsockopt come primitiva dipende da una race in cui, tra l'ottenimento del riferimento al trasporto e l'ottenimento di sock_lock, il trasporto venga liberato. È una finestra piccola, e ci sono molte istruzioni per arrivarci. Quindi qui possiamo usare una funzionalità interessante di linux chiamata userfaultfd per aumentare le nostre possibilità. Questo meccanismo ci permette di gestire i page fault in usermode quando vogliamo. Vedi https://man7.org/linux/man-pages/man2/ioctl_userfaultfd.2.html e https://man7.org/linux/man-pages/man2/userfaultfd.2.html.

Con questo possiamo avere un thread (il gater) che ottiene il sock_lock e poi accede a della memoria utente causando un page fault. Possiamo tenere quel thread in pausa (con il lock ancora tenuto) per tutto il tempo che vogliamo. Gli altri thread che tentano di ottenere quel lock rimarranno lì finché non lasciamo andare il gater e rilasciamo il lock. Possiamo allineare il nostro thread che finirà per fare la destruct e il nostro thread che userà il riferimento non valido. Entrambi aspetteranno su sock_lock, e ora abbiamo un'ottima possibilità di vincere la race. Se il thread che fa la destruct viene scelto per ottenere il lock successivamente, allora la nostra chiamata setsockopt verrà completata dopo. Userà il puntatore vsk->trans dopo che è stato liberato (e sostituito).

Se invece la chiamata setsockopt va per prima, perdiamo la race, ma possiamo tranquillamente riprovare l'intero processo.

La Free pt. 2

Quando cercavo di costruire questo, ho passato un po' di tempo andando per la strada sbagliata. Ho provato a far chiamare vsock_deassign_transport tramite close e timeout, ma mi sono imbattuto in molti controlli del reference count che ritardavano la distruzione effettiva finché non era troppo tardi.

Come nota a margine, fare debug di questi percorsi può essere difficile; come puoi immaginare, un breakpoint sulla system call close verrà colpito moltissimo. Anche se usi breakpoint condizionali per fermarti solo nel thread giusto, la macchina rallenterà fino a strisciare. Un percorso interessante per aggirare questo problema è usare ebpf con tracepoint che chiamano bpf_trace_printk solo se le condizioni sono corrette. Poi si può piazzare un breakpoint kgdb su bpf_trace_printk, che ci porterà vicino al punto giusto. Questo non funziona con kprobes perché sei già in un handler di breakpoint. Penso che aggiungere una chiamata bpf_trace_kgdb_break a ebpf potrebbe essere una bella aggiunta al kernel.

Quando finalmente sono passato a guardare il percorso di vsock_assign_transport, tutto si è messo insieme rapidamente. Per soddisfare i requisiti, prima ci connettiamo a VM_ADDR_CID_LOCAL, quando non c'è alcun server in ascolto. Questo ci darà il trasporto loopback, ma poi quando la nostra connessione va in timeout o fallisce, il nostro stato tornerà a SS_UNCONNECTED. Questo ci permette di fare un'altra connessione a un indirizzo maggiore di VM_ADDR_CID_HOST, che causerà il cambio del nostro trasporto, distruggendo il trasporto esistente e causando la free. È importante farlo quando non c'è effettivamente alcun transport_g2h o transport_h2g registrato, così il nostro nuovo trasporto sarà NULL, e il nostro riferimento liberato rimarrà in vsk->trans.

Sfruttamento

Con tutto questo allineato otteniamo un use-after-free affidabile che potrebbe essere usato per l'escalation dei privilegi.

Questo repository riguarda solo il nostro arrivo all'use-after-free iniziale. Ma ora abbiamo una primitiva per scrivere un valore a un offset nella cache kmalloc-64 dove era stato precedentemente allocato virtio_vsock_sock. Ho deciso di fermare la walkthrough lì perché, insomma, devo fare tutto io qui intorno?

Scarica lo strumento