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
page_inject — CVE-2026-31431-killed page-cache exploit — execução de código em containers que compartilham a mesma camada de imagem | Kitploit
Ferramentas/GitHubGitHub/sgkdev/page_inject
Análise de VulnerabilidadesExploraçãoPós-ExploraçãoTestes de PenetraçãoRed TeamingEscape de ContêinerExploração de Binários
GitHubsgkdev/page_inject

page_inject

CVE-2026-31431-killed page-cache exploit — execução de código em containers que compartilham a mesma camada de imagem

Ver Repositório
7414há 3 mesesRevisado 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

page_inject - escape entre contêineres via AF_ALG aead

Exploit AF_ALG aead de escape entre contêineres -- pivot de um contêiner comprometido para todos os contêineres irmãos que compartilham a mesma camada de imagem libc.so.6.

Esta é uma primitiva de escape: executa de dentro de um contêiner sem privilégios que o atacante já comprometeu, e usa o bug de escrita arbitrária de 4 bytes por rotação ESN authencesn do AF_ALG (CVE-2026-31431) para plantar um hook persistente de read() nas páginas do page-cache de libc.so.6. Como o Docker / containerd fazem o backing das camadas inferiores do overlayfs com inodes compartilhados, essas páginas são visíveis para todos os contêineres irmãos instanciados a partir da mesma imagem -- o hook dispara também nos processos deles, e o atacante obtém execução de comandos dentro de cada um.

Modelo de ameaça

  • O atacante tem acesso a shell a um único contêiner (chamado de victim) em um host que executa outros contêineres (siblings) a partir da mesma imagem que victim.
  • victim executa com a postura padrão do Docker/k8s: uid sem privilégios dentro do user namespace do contêiner, perfil seccomp padrão, perfil AppArmor padrão, sem capabilities especiais, sem bind mounts do host.
  • victim tem apenas:
    • acesso de leitura à sua própria libc (/usr/lib/x86_64-linux-gnu/libc.so.6 ou onde a distro a instala)
    • a família de syscalls padrão socket(AF_ALG, ...)
    • os syscalls padrão splice / vmsplice
    • acesso de escrita a um diretório onde possa fazer chmod +x (ex.: /tmp)
  • O kernel deve estar vulnerável a CVE-2026-31431 (qualquer build algif_aead + authencesn anterior ao fix de revert upstream).

Isto é tudo. Sem CAP_* especial, sem acesso ao filesystem do host. O atacante deposita um binário estático autocontido dentro do contêiner, o executa, e a corrupção do page-cache -- e, portanto, o hook -- torna-se visível para todos os irmãos.

Como o exploit se encadeia

  1. Identidade da página do page-cache. Dentro de um contêiner overlayfs, /usr/lib/.../libc.so.6 é servido pelo inode ext4 da camada inferior da imagem. Todo contêiner iniciado a partir da mesma imagem compartilha esse inode de backing, e o page-cache do kernel é indexado pelo inode subjacente -- não pelo overlay nem pelo namespace. Portanto, uma única escrita de 4 bytes em uma página do page-cache é visível para todos os processos dos contêineres irmãos que têm essa página mmap'd.

  2. A vuln AF_ALG aead transforma uma dessas escritas em muitas. algif_aead encadeia o iovec RX do usuário com os bytes authsize finais do SGL TX com splice, e a rotação ESN de authencesn estaciona 4 bytes do campo seq_high do AAD em dst[assoclen + cryptlen] -- que é o primeiro byte dessa cauda externa encadeada. A página com splice é uma página do page-cache de um arquivo ao qual o atacante tem apenas acesso de leitura, mas o cipher copia bytes para ela mesmo assim, sem bookkeeping de dirty. (Veja crypto/algif_aead.c e crypto/authencesn.c para a mecânica subjacente.)

  3. Bootstrapping de uma primitiva chamável. A primeira coisa que page_inject faz é fazer o bootstrap da Zona A -- uma re-implementação codificada em asm da mesma dança AF_ALG (), colocada na cave da libc. Isso torna a escrita de 4 bytes um regular a partir de qualquer payload de hook futuro, sem necessidade de setup de socket a cada chamada.

Compilação

O injetor é compilado fora do contêiner vítima -- tipicamente na própria máquina de desenvolvimento do atacante -- porque a maioria das imagens de contêiner de produção não inclui um compilador. Um ambiente de desenvolvimento Linux x86_64 padrão com gcc (com suporte a link -static) e nasm é suficiente.

root@kitploit:~
make            # assembles .asm sources via gen_arrays.sh, links static page_inject
make shellcode  # also produces inspectable .bin flat binaries
make clean      # removes generated files and the binary

O resultado é um único ELF estaticamente linkado (./page_inject) que executa em qualquer kernel Linux x86_64 moderno.

Entrega e uso (a partir do contêiner vítima)

Uma vez que o atacante tem shell em victim, ele envia o binário para um diretório gravável (tipicamente /tmp):

root@kitploit:~
# inside the compromised container, attacker session
victim$ ./page_inject

Sem argumentos, page_inject usa como padrão /usr/lib/x86_64-linux-gnu/libc.so.6 (o local pós-merge do Debian/Ubuntu). Para outras distros a libc está em um caminho diferente; ou passe-o explicitamente ou use --root / para escanear a tabela de lookup embutida a partir da raiz do contêiner:

root@kitploit:~
# Fedora / Rocky / CentOS
victim$ ./page_inject /usr/lib64/libc.so.6

# Arch
victim$ ./page_inject /usr/lib/libc.so.6

# Auto-detect, regardless of distro:
victim$ ./page_inject --root /

Qualquer uma das invocações faz a mesma coisa: faz parse de ELF da libc dentro do contêiner, instala o hook no page-cache dela, monitora a tabela de slots por ~30 s enquanto os irmãos se registram, e executa um id one-shot contra o primeiro irmão que se registrou como verificação de sanidade.

Após o bootstrap, entre no shell de comando para dirigir qualquer irmão registrado:

root@kitploit:~
victim$ ./page_inject --shell --no-bootstrap
=== page-cache shell ===
Containers (3):
  [0] 0x0018598d  <- target
  [1] 0x001859ab
  [2] 0x001859cd

inject:0018598d> exec id
uid=0(root) gid=0(root) groups=0(root)
inject:0018598d> target 0x001859ab
inject:001859ab> exec hostname
1ccd66abee9d
inject:001859ab> exec cat /etc/shadow
root:$6$.....
inject:001859ab> unhook
... read() prologue restored, slot table zeroed ...

unhook limpa o hook de todos os contêineres irmãos de uma só vez e deixa os filhos hook se autoterminarem.

root@kitploit:~
Usage: page_inject [OPTIONS] [LIBC_PATH]

Options:
  --root <prefix>   Auto-resolve libc.so.6 under <prefix> using the
                    built-in fixed-path lookup table. Inside the
                    victim container that's normally --root / .
  --shell [0xKEY]   Drop into interactive command shell after
                    injection. Optional KEY pre-selects the target.
  --no-bootstrap    Skip injection (shell-only; hook must already
                    be live in the page cache).
  --timeout SEC     Slot monitoring timeout in --shell mode
                    (default 30 s).
  --help, -h        Show help.

Default libc (when no --root and no LIBC_PATH given):
  /usr/lib/x86_64-linux-gnu/libc.so.6

Caminho de injeção duplo

Builds diferentes de glibc deixam diferentes quantidades de espaço de cave .text entre o segmento LOAD executável e o próximo LOAD somente leitura. page_inject seleciona entre dois layouts no momento da injeção:

  • Caminho A -- somente libc (padrão). Tanto a Zona C quanto a Zona A ficam na cave .text da libc. As áreas de tabela de slots + CMD + OUTPUT ficam na seção .hash da libc -- dados de hash SysV legados que o ld.so não lê em tempo de execução, já que ele usa .gnu.hash em vez disso. Quando .hash está ausente (toolchain moderna do Arch), page_inject esculpe a região de slots a partir da cauda de .eh_frame_hdr em vez disso, depois de primeiro encolher o campo fde_count para que o unwinder não considere mais os bytes liberados como parte do índice de busca binária FDE (o unwinder cai de forma transparente em uma varredura linear de .eh_frame para qualquer IP cujo FDE costumava estar no intervalo truncado -- comportamento exigido pela LSB).

  • Caminho B -- trampolim na libc + payload em ld.so. Alguns builds de glibc encolhem a cave da libc para abaixo do tamanho necessário para o payload completo de Zona C + Zona A (Ubuntu 24.04 / glibc 2.39 traz uma cave de 711 B). Nesse caso, page_inject escreve um trampolim de 36 B na cave da libc -- ele faz o gate da chave .bss do fast-path intra-libc -- e no slow path calcula a base em tempo de execução do ld.so a partir do slot GOT da libc para (um símbolo do lado do ld.so que toda glibc importa privadamente) e salta para uma variante de base-registrador da Zona C na cave do . A tabela de slots + CMD + OUTPUT + chave permanecem na libc; a Zona C do lado do ld.so as alcança via depois que o trampolim semeia .

Se nenhum dos layouts couber, page_inject recusa de forma limpa, sem escrever nada na libc nem no ld.so em disco ou no page-cache.

Tratamento do prólogo de read()

Versões diferentes de glibc emitem sequências de abertura diferentes em read(). O injetor reconhece cada uma, lê de volta os bytes que o hook desloca e os emula no fast-path da Zona C para que read() single-threaded retome corretamente em read+N:

O slot de emulação do fast-path na Zona C é dimensionado para o maior prólogo conhecido (8 bytes) mais o jmp rel32 de 5 bytes; prólogos mais curtos preenchem o byte final do slot com um filler NOP para que o comprimento total do slot seja constante.

Estrutura de arquivos

root@kitploit:~
page_inject/
  page_inject.c           Main injector: ELF parsing, vuln primitive,
                          dual-path layout selection, inject + unhook.
  zone_c.asm              Path-A hook dispatcher shellcode.
  zone_c_ld.asm           Path-B hook dispatcher (rbp-base variant).
  trampoline.asm          Path-B 36-byte libc-side stub.
  write_cache.asm         Zone A (vuln write primitive shellcode).
  gen_arrays.sh           Assemble .asm -> asm_bytecode.c.
  asm_bytecode.c          [generated] shellcode byte arrays.
  Makefile                Build system.

Matriz testada e suportada

O exploit foi verificado de ponta a ponta nas seguintes distros snap de contêiner. Em cada entrada, page_inject foi injetado de dentro de um contêiner e seu hook foi visto disparando em um contêiner irmão iniciado a partir da mesma imagem; os comandos executaram corretamente via o canal de page-cache; e o unhook restaurou o estado das páginas da libc de forma limpa.

Notas operacionais

  • page_inject é estaticamente linkado de propósito para que o próprio processo do atacante não seja afetado pelo hook que ele instala.
  • page_inject reconhece uma libc "já com hook" (E9 + nops no prólogo de read()) e se recusa a reinjetar. Se você estiver em um ambiente de teste e seu page-cache estiver travado nesse estado, pare todos os contêineres que usam a imagem e faça drop_caches para redefinir.
Baixar ferramenta
write_cache.asm
.text
call
  • Instalação do hook. O injetor então escreve a Zona C (zone_c.asm) na cave .text da libc e patcha os primeiros 7-12 bytes de read() com um salto E9 disp32 para ela. Os bytes deslocados do prólogo são emulados fielmente no fast-path da Zona C (três prólogos glibc distintos são reconhecidos -- veja "Tratamento do prólogo de read()" abaixo). O hook agora está vivo no page-cache da libc.

  • Propagação do hook. Todo contêiner irmão executa processos que chamam read() constantemente (daemons de log, healthchecks, cat /etc/hostname, qualquer coisa). Na primeira dessas chamadas dentro de um contêiner irmão, o prólogo sequestrado salta para a Zona C, que:

    • faz stat("/") do inode raiz do contêiner (um ID por namespace estável), usando-o como chave de slot do contêiner,
    • varre a tabela de slots em busca de uma entrada existente com essa chave,
    • se ausente, registra a chave e faz fork() de um filho de longa duração command-loop que faz polling da área CMD por ordens,
    • retorna para read()+N para que o chamador não perceba nada. O processo irmão original continua executando. A partir de agora o atacante tem um daemon dentro desse contêiner.
  • Canal de comando. O atacante usa o mesmo binário page_inject no modo --shell para escrever comandos na área CMD da região de slots. Cada filho hook registrado de cada contêiner irmão faz polling, faz fork de /bin/sh -c <cmd>, captura stdout/stderr na área OUTPUT, sinaliza a conclusão e volta ao polling. O shell mostra a saída. Como toda escrita CMD/OUTPUT também passa pela primitiva da vuln, nenhum privilégio especial é necessário.

  • Remoção do hook. Quando terminar, unhook restaura os bytes originais do prólogo de read() e zera a tabela de slots; os filhos hook veem um slot vazio na próxima iteração e se autoterminam. As modificações do page-cache em si são limpas (o kernel nunca marcou as páginas modificadas como dirty), então, uma vez que todo contêiner que tem a libc mmap'd seja parado, um drop_caches reverte o cache completamente -- nenhum artefato em disco permanece.

  • _rtld_global
    .text
    ld.so
    .bss
    rbp + offset
    rbp = libc_base
    Faixa de glibcPrólogo (após endbr64 opcional)Notas
    2.36 / 2.39cmpb $0x0, __libc_single_threaded(%rip)7 bytes; o cmpb emulado define ZF para o jne .Lthreaded original.
    2.43push rbp; movsxd rdi,edi; xor r9d,r9d7 bytes; emulado byte a byte.
    2.31 / 2.35mov eax, fs:[0x18]8 bytes; emulado byte a byte ([disp32] prefixado com FS é absoluto, não relativo a RIP, então a cópia de bytes é fiel).
    ImagemglibcCaminho de injeçãoPrólogo de read()Região de slots
    debian:bookworm2.36Acmpb.hash
    ubuntu:24.042.39Bcmpb.hash (lado da libc, endereçado via rbp a partir do ld.so)
    ubuntu:22.042.35ATLS-fs.hash
    fedora:402.39Acmpb.hash
    archlinux:latest2.43Apush-rbp.eh_frame_hdr (cauda truncada)