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
vsock_poc — Investigando o bug por trás de CVE-2021-26708 | Kitploit
Ferramentas/GitHubGitHub/jordan9001/vsock_poc
Análise de VulnerabilidadesExploraçãoDepuradoresPapers e PesquisaAprendizado e EducaçãoExploração de Binários
GitHubjordan9001/vsock_poc

vsock_poc

Investigando o bug por trás de CVE-2021-26708

Ver Repositório
282há 5 anosRevisado pelo Kitploit

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

vsock_poc

Investigando o bug por trás do CVE-2021-26708


Este repositório contém um pequeno relatório sobre o CVE-2021-26708, e como esse bug pode ser transformado em uma primitiva de escrita Use After Free. O PoC aqui não é um exploit completo, apenas o meu harness que usei enquanto investigava este bug. Ele consegue utilizar com sucesso uma entrada do cache kmalloc-64 após ela ser liberada, mas não possui código para preparar a memória e colocar algo de interesse no slot.

Este é um bug divertido reportado por @a13xp0p0v. Chamou minha atenção porque o patch era tão simples, apenas impedindo que uma referência a vsk->transport fosse obtida fora do lock em 5 lugares diferentes. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c518adafa39f37858697ac9309c6cf1805581446

Abaixo está um breve guia do processo para ir do patch a uma primitiva use-after-free que poderia ser usada para exploração. O guia deve ser útil para outros que queiram explorar este bug.

Environment Setup

Baixei o kernel linux 5.10.13 e desfiz manualmente o patch mostrado acima. Para mais informações sobre como compilar e executar o kernel, a seguinte referência é boa.

https://fedoraproject.org/wiki/Building_a_custom_kernel

Também modifiquei os parâmetros de boot para habilitar a depuração do kernel com kgdb. Usando gdb com o arquivo vmlinux que eu havia compilado antes, eu tinha todos os símbolos do kernel para o kernel principal, mas não para quaisquer módulos carregáveis do kernel. O código associado à vulnerabilidade não era carregado por padrão, mas seria carregado no kernel quando a família PF_VSOCK fosse usada (dependendo de como você compilou seu kernel).

Para obter os símbolos no kgdb para módulos carregados, certifiquei-me de usar o socket vsock pelo menos uma vez, depois usei sudo cat /proc/modules | grep vsock para obter os endereços base dos módulos associados. No gdb, eu então fazia algo como (gdb) add-symbol-file ./net/vmw_vsock/vsock.ko 0xffffffffc0567000 para que o gdb soubesse onde na memória os símbolos daquele arquivo ko estão. vsock.ko e vmw_vsock_virtio_transport_common.ko foram os dois mais relevantes.

Hunting the Primitive

É divertido trabalhar de trás para frente a partir de patches porque, ao contrário de muita caça a vulnerabilidades, você sabe com certeza que já está olhando no lugar certo. Neste caso, sabemos pelo patch que uma referência ao transport é salva antes de o sock_lock ser obtido. Podemos esperar com segurança que a vulnerabilidade seja devida à mudança do transport, mas a referência antiga é usada.

Neste tipo de cenário, esperaríamos que o próprio transport fosse um objeto alocado dinamicamente que pudesse ser liberado e substituído por algum outro objeto entre a obtenção da referência e o lock ser mantido. Infelizmente, quando rastreamos os tempos de vida dos transports relevantes implementados pelos outros módulos, eles parecem estar todos na memória global. Portanto, vamos procurar um nível mais profundo por itens usados quando fora de escopo.

The Free pt. 1

Olhando em af_vsock.c, podemos encontrar dois lugares onde o vsk->transport é modificado. Em vsock_assign_transport e vsock_deassign_transport. Em vsock_assign_transport podemos ver que se houver um transport existente diferente, então vsock_deassign_transport é chamado antes de colocar o novo transport. Se olharmos para as possibilidades para a chamada vsk->transport->destruct(vsk) aqui, vemos que tanto o transport loopback quanto o virtio apenas darão kfree no parâmetro vsk->trans aqui. Bingo! Se conseguirmos encontrar (1) um caminho para esta chamada que possa competir com (2) uma função vulnerável que usa uma referência de transport de antes de ser destruído para acessar o vsk->trans, então teremos nossa primitiva.

Procurando um caminho para vsock_deassign_transport, vemos que é chamado de vsock_sk_destruct ou vsock_assign_transport. vsock_sk_destruct é definido como a função sock->destruct e, portanto, chamadas para __sys_close, ou outras chamadas disponíveis ao longo do caminho de destruição como sock_put, sock_close ou vsock_release podem acabar aqui.

O caminho mais relevante para vsock_assign_transport é através de vsock_stream_connect, mas requer que o socket esteja em alguns estados específicos, e só acaba chamando vsock_deassign_transport se o transport mudar. E substituiria o parâmetro vsk->trans se não terminarmos com um novo transport NULL.

The Use

Antes de irmos muito longe encontrando qual caminho para o free é o certo para nós, queremos determinar que existe um caminho válido que usa o membro vsk->trans com uma referência inválida para um transport destruído. Podemos verificar metodicamente cada lugar onde o transport é usado com uma referência possivelmente inválida. Rastreando essas brechas, podemos encontrar aquelas onde o vsk->trans é usado. O melhor caminho parece ser através de vsock_stream_setsockopt aqui, quando transport->notify_buffer_size escreve em um offset dentro do vsk->trans para os transports loopback e virtio bem aqui. Se o trans já estiver liberado quando for usado ali, obtemos uma bela escrita de um u32 em um offset de 0x28 em uma alocação kmalloc-64.

The Race

O uso de vsock_stream_setsockopt como primitiva depende de uma race onde, entre a obtenção da referência ao transport e a obtenção do sock_lock, há uma pequena janela, e há muitas instruções para chegar lá. Então aqui podemos usar um recurso interessante no linux chamado userfaultfd para aumentar nossas chances. Esse mecanismo nos permite lidar com pagefaults em modo de usuário à nossa vontade. Veja https://man7.org/linux/man-pages/man2/ioctl_userfaultfd.2.html e https://man7.org/linux/man-pages/man2/userfaultfd.2.html.

Com isso, podemos ter uma thread (o gater) que obterá o sock_lock e então acessará alguma memória do usuário e causará uma page fault. Podemos manter essa thread pausada (com o lock ainda mantido) pelo tempo que quisermos. Outras threads que tentarem obter esse lock ficarão lá até deixarmos o gater ir e liberar o lock. Podemos alinhar nossa thread que acabará fazendo o destruct, e nossa thread que usará a referência inválida. Ambas esperarão no sock_lock, e agora temos uma grande chance de vencer a race. Se a thread que faz o destruct for escolhida para obter o lock em seguida, então nossa chamada setsockopt será concluída depois. Ela usará o ponteiro vsk->trans após ele ter sido liberado (e substituído).

Se a chamada setsockopt for primeiro, perdemos a race, mas podemos tentar todo o processo novamente com segurança.

The Free pt. 2

Ao tentar construir isso, passei um tempo seguindo o caminho errado. Tentei fazer o vsock_deassign_transport ser chamado via close e timeouts, mas encontrei muitas verificações de contagem de referência que atrasaram a destruição real até que fosse tarde demais.

Como um aparte, depurar esses caminhos pode ser difícil; como você pode imaginar, um breakpoint na chamada de sistema close será acionado muitas vezes. Mesmo se você usar breakpoints condicionais para parar apenas na thread correta, a máquina ficará extremamente lenta. Um caminho interessante para contornar isso é usar ebpf com tracepoints que só chamarão bpf_trace_printk se as condições estiverem corretas. Então um breakpoint kgdb pode ser colocado em bpf_trace_printk, o que nos levará ao local correto. Isso não funciona com kprobes porque você já está em um manipulador de breakpoint. Acho que adicionar uma chamada bpf_trace_kgdb_break ao ebpf poderia ser uma boa adição ao kernel.

Quando finalmente mudei para olhar o caminho vsock_assign_transport, tudo se encaixou rapidamente. Para satisfazer os requisitos, primeiro nos conectamos a VM_ADDR_CID_LOCAL, quando não há servidor ouvindo. Isso nos dará o transport loopback, mas então quando nossa conexão expirar ou falhar, nosso estado retornará a SS_UNCONNECTED. Isso nos permite fazer outra conexão para um endereço maior que VM_ADDR_CID_HOST, o que fará nosso transport mudar, destruindo nosso transport existente e causando o free. É importante fazermos isso quando não houver nenhum transport_g2h ou transport_h2g registrado, então nosso novo transport será NULL, e nossa referência liberada permanecerá no vsk->trans.

Exploitation

Com tudo isso alinhado, obtemos um use-after-free confiável que poderia ser usado para escalada de privilégios.

Este repositório é apenas sobre nós chegarmos ao use-after-free inicial. Mas agora temos uma primitiva para escrever um valor em um offset no cache kmalloc-64 onde o virtio_vsock_sock foi alocado anteriormente. Decidi parar o guia por aí porque, o quê, tenho que fazer tudo aqui?

Baixar ferramenta