
Corretor de Isca Experimental
Uma isca de rede conteinerizada (honeypot) que anuncia SSH, RDP e SMB, observa toda conexão de entrada com eBPF e faz proxy reverso de cada sessão para um contêiner isolado.
O design separa duas preocupações:
Esta é uma ferramenta defensiva para detectar e estudar atividade não autorizada em redes que você possui ou está autorizado a monitorar. Implante-a apenas onde você possui essa autoridade.
flowchart TB
A["Attacker / Scanner"]
subgraph host["Decoy Host"]
direction TB
NIC["broker eth0<br/>published: 22, 3389, 445"]
subgraph brk["broker container"]
direction TB
E["eBPF TC classifier<br/>logs every SYN<br/>sees true source IP"]
P["reverse proxy<br/>CONNECT to backend"]
L["structured JSON logs"]
end
subgraph dec["decoynet (internal, no host route)"]
direction LR
S["ssh-decoy<br/>OpenCanary ssh<br/>port 2222"]
D["rdp-decoy<br/>OpenCanary rdp<br/>port 3389"]
M["smb-decoy<br/>Impacket SMB server<br/>port 445"]
end
end
A --> NIC
NIC --> E
NIC --> P
E --> L
P --> S
P --> D
P --> M
Quatro contêineres no total:
| Contêiner | Função | Rede |
|---|---|---|
broker | Porta de entrada pública: observação eBPF + proxy reverso | edge + decoynet |
ssh-decoy | Módulo ssh do OpenCanary (handshake real, captura credenciais) | decoynet apenas |
rdp-decoy | Módulo rdp do OpenCanary (simula NLA, captura nomes de usuário) | decoynet apenas |
smb-decoy | SimpleSMBServer do Impacket (SMB2/3 real, captura autenticação) | decoynet apenas |
As iscas vivem em uma rede Docker internal (decoynet) sem rota para o host ou o mundo externo. Apenas o broker pode alcançá-las. Nada que um atacante faça dentro de uma isca pode alcançar diretamente a rede do host.
O broker publica as portas 22, 3389 e 445 para o host, então os pacotes de entrada chegam no eth0 do broker. Duas coisas acontecem com cada pacote:
broker/bpf/decoy.bpf.c) analisa os cabeçalhos Ethernet, IP e TCP, e para cada nova tentativa de conexão (SYN definido, ACK limpo) escreve um conn_event em um ring buffer: IP e porta de origem, porta de destino, flags TCP e se a porta é um serviço anunciado. O pacote é passado inalterado (TC_ACT_OK).CONNECT para o backend da isca daquele serviço, então retransmite bytes em ambos os sentidos.O mapa eBPF advertised_ports é preenchido na inicialização a partir de config.yaml, para que o classificador possa marcar se uma sonda atingiu uma porta servida ou não. Isso torna visíveis varreduras horizontais de portas, mesmo que apenas três portas sejam proxy reverso.
Se você quiser anunciar "tudo está aberto" e encaminhar portas de destino arbitrárias para o broker, estenda o classificador para reescrever a porta de destino ou use um redirecionamento TPROXY / bpf_sk_assign. A versão atual mantém o caminho do pacote intacto e limita-se à observação, que é o padrão mais seguro.
cyber-decoy/
├── README.md
├── docker-compose.yml # Stack de 4 contêineres
├── docker-compose.override.yml # Desenvolvimento local macOS: sem capacidades eBPF, remapeamento da porta 22
├── Makefile # Auxiliares build / up / down / bpf
├── LICENSE
├── scripts/
│ └── setup.sh # Verificações prévias do host
├── broker/
│ ├── Dockerfile # Compila o objeto eBPF + binário Go
│ ├── config.yaml # Serviços anunciados (configurável)
│ ├── go.mod
│ ├── main.go # Ponto de entrada
│ ├── bpf/
│ │ └── decoy.bpf.c # Classificador TC eBPF
│ └── internal/
│ ├── config/config.go # Carregador de configuração
│ ├── proxy/proxy.go # Proxy reverso TCP
│ └── bpf/loader.go # Carrega + anexa eBPF, transmite eventos
└── decoys/ # Todos os três executam OpenCanary
├── ssh/
│ ├── Dockerfile
│ └── opencanary.conf # Módulo ssh, porta 2222
├── rdp/
│ ├── Dockerfile
│ └── opencanary.conf # Módulo rdp, porta 3389
└── smb/
├── Dockerfile # Processo Python único, não-root
├── smb_decoy.py # SimpleSMBServer do Impacket + registro JSON
└── requirements.txt # impacket (pinned)
docker-compose.override.yml incluído, que depende das tags !reset / !override).sudo mount -t bpf bpf /sys/fs/bpf.A imagem do broker detecta sua arquitetura de compilação e passa a macro __TARGET_ARCH_* correspondente para o clang, portanto compila tanto em x86_64 quanto em aarch64 (Apple Silicon, Graviton). Observe que o gcc-multilib deliberadamente não está instalado: é um pacote apenas x86 sem candidato arm64, e incluí-lo quebra a compilação em arm64 com o código de saída apt 100. Apenas clang e libbpf-dev são necessários para compilar o objeto eBPF.
O Docker Desktop no macOS executa contêineres dentro de uma VM LinuxKit, não no kernel do host, portanto a anexação TC/TCX eBPF geralmente não funcionará lá. Isso não é fatal: o eBPF é intencionalmente de melhor esforço, então o broker registra ebpf disabled: attach failed e o proxy reverso mais todas as três iscas funcionam e registram normalmente. Você pode desenvolver e testar todo o caminho do proxy localmente e obter observação eBPF real quando implantar em um host Linux.
O docker-compose.override.yml é carregado automaticamente e torna isso agradável: ele remove as capacidades eBPF (inúteis na VM) e remapeia a porta 22 do host para 2022, já que o sshd do próprio Mac ocupa a porta 22.
docker compose up --build # Desenvolvimento local, override aplicado
docker compose -f docker-compose.yml up -d # Implantação real, override ignorado
Execute a verificação prévia primeiro:
./scripts/setup.sh
# 1. Construir todas as quatro imagens (compila o objeto eBPF dentro da imagem do broker)
make build
# 2. Iniciar a stack
make up
# 3. Observar o que acontece
make logs
Em seguida, sonda a partir de outra máquina (ou localhost para um teste rápido):
ssh -p 22 user@DECOY_HOST # Atinge a isca SSH
nc DECOY_HOST 3389 # Atinge a isca RDP
nc DECOY_HOST 445 # Atinge a isca SMB
nc DECOY_HOST 8080 # Não anunciada: observada pelo eBPF, sem proxy
O broker emite JSON para eventos de sonda eBPF e sessões com proxy; cada isca emite eventos JSON do OpenCanary. Para ver credenciais chegando:
docker compose logs -f ssh-decoy | grep 4002