08 - Hunting em Linux
video: https://youtu.be/... no frontmatter da nota.Hunting em Linux
Nota 08 do capítulo 08 (Técnicas de Hunting por TTPs), transferindo o exercício de caça para o ambiente Linux, de servidores a estações de desenvolvimento.
Levanta a telemetria disponível (auditd, journald, eBPF com Tetragon, logs de autenticação), mapeia TTPs recorrentes e entrega comandos de caça no host.
Dialoga com as notas 06 e 07 em rede, com a nota 09 em Windows e AD e com os controles DAT.1 e TH.11 do capítulo 15.
1. Contexto e Posicionamento na Biblioteca
Linux concentra a maior parte da carga crítica de servidor, de container e de pipeline de CI/CD, e mesmo assim é o ambiente onde hunting costuma ser mais improvisado, porque o SOC raramente tem EDR em todo parque e a telemetria nativa exige configuração deliberada. A consequência prática é que incidentes em Linux são descobertos tarde, com o adversário já persistente em cron, em serviço do systemd ou com chave SSH plantada. Esta nota organiza o que existe de telemetria nativa, mostra onde cada TTP deixa rastro e entrega comandos de caça executáveis no host, para o analista que chega sem EDR e precisa responder perguntas com o que o sistema já registra.
O posicionamento na biblioteca é o de par da nota 09 em Windows: a mesma disciplina de hipótese do capítulo 07 aplicada a outra pilha de telemetria. A diferença estrutural é que em Linux o hunting acontece com frequência direto no host, com comandos locais, e menos no SIEM, porque a centralização de logs de Linux é historicamente mais fraca; por isso esta nota dá mais peso a comandos locais de auditoria do que consultas KQL. As técnicas mapeadas aqui alimentam o inventário de cobertura do controle TH.11 e as lacunas do MAP.4.
2. Telemetria Linux Disponível para Hunting
A base de telemetria nativa se divide em quatro fontes, cada uma com alcance e custo próprios. O auditd é o mais poderoso e o mais negligenciado: com regras bem desenhadas, grava syscalls e acesso a arquivos com contexto de processo e de usuário, e entrega reportes prontos via aureport e ausearch. O journald registra o diário de serviços e do kernel, com busca temporal rica via journalctl. A camada eBPF, com ferramentas como Tetragon, Falco ou Tracee, adiciona visibilidade de execução, rede e acesso a arquivos em tempo real com políticas declarativas, sem tocar o kernel com módulos proprietários. Os logs de autenticação, em /var/log/auth.log ou /var/log/secure, registram SSH, sudo e PAM, e continuam sendo a fonte mais citada em relatórios de incidente.
| Fonte | O que registra | Vantagem para hunting | Custo e atenção |
|---|---|---|---|
| auditd | Syscalls, acesso a arquivos, execução, com regras | Contexto rico de usuário e processo; reportes prontos | Regras mal dimensionadas enchem o disco e o log |
| journald | Diário de serviços, kernel, cron, SSH | Busca temporal e filtros por unidade e prioridade | Persistência volátil se Storage não for persistente |
| eBPF (Tetragon, Falco, Tracee) | Execução, rede, arquivos, credenciais em tempo real | Política declarativa e contexto de container | Requer kernel recente e agente instalado |
| auth.log / secure | SSH, sudo, PAM, mudança de usuário | Fonte universal em qualquer distribuição | Formato texto; rotação apaga histórico rapidamente |
A decisão de cobertura segue a mesma lógica do capítulo 02: sem auditd configurado e sem persistência do journald, o hunting em Linux se resume a reação. O conjunto mínimo recomendado para servidores é auditd com regras de persistência e de execução, journald persistente e encaminhamento dos dois para o SIEM, mais eBPF nas cargas de maior risco. A seção de comandos assume esse conjunto mínimo e trabalha com o que ele produz.
A priorização entre classes de host merece decisão documentada, porque o parque de Linux costuma ser grande e heterogêneo. Servidores expostos e de diretório recebem o conjunto completo, estações de desenvolvedor recebem auditd restrito mais eBPF, e contêineres recebem a camada eBPF no nó, com política por namespace; essa graduação evita gastar o orçamento de ingestão em hosts de baixo risco e garante que os hosts que sustentam o negócio tenham a telemetria que as hipóteses desta nota exigem.
3. TTPs Recorrentes em Linux e onde Deixam Rastro
Os TTPs que dominam incidentes em Linux são de persistência e de execução disfarçada, e todos têm arquivos, chamadas de sistema ou logs característicos. A tabela abaixo mapeia as técnicas de maior incidência prática para as fontes que as expõem, e é a ponte entre o relatório de CTI que diz o que o adversário faz e o comando de caça que verifica se ele fez.
| Técnica | ATT&CK | Rastro característico | Fonte primária |
|---|---|---|---|
| Persistência em cron | T1053.003 | Entradas novas em /etc/cron.*, /var/spool/cron, jobs apontando para /tmp ou /dev/shm | auditd com watch em cron; crontab local |
| Serviço do systemd | T1543.002 | Unit nova em /etc/systemd/system habilitada, ExecStart para binário fora do pacote | journald; systemctl; auditd |
| LD_PRELOAD e ld.so.preload | T1574.006 | Linha em /etc/ld.so.preload; biblioteca não empacotada carregada em processos | auditd com watch em ld.so.preload |
| Chave SSH autorizada | T1098.004 | authorized_keys novo ou alterado fora do fluxo de provisionamento | auditd; find em /home e /root |
| Módulo de kernel | T1547.006 | Carregamento via init_module ou modprobe de .ko não assinado | auditd na syscall; lsmod |
| Rootkit de espaço de usuário | T1014 | Inconsistência entre saída de ps e /proc, binários alterados | Verificação com artefato limpo e eBPF |
Duas observações orientam o uso da tabela. Primeiro, cron e systemd dominam porque são simples, estáveis e sobrevivem a reboot, então qualquer caça de Linux começa por eles e só sobe de nível se sair limpo. Segundo, os rastros de arquivo são frágeis, porque o adversário os apaga depois de instalar; por isso o auditd com watch, que grava a mudança no momento em que ela acontece, vale mais do que qualquer varredura retrospectiva de sistema de arquivos quando o objetivo é detectar instalação, e a varredura retrospectiva serve para descobrir o que já passou.
4. Configuração de Auditoria e Comandos de Caça no Host
A configuração mínima do auditd para hunting de persistência usa watches em diretórios críticos e uma regra de syscall para carregamento de módulo, como no bloco abaixo. As regras entram em /etc/audit/rules.d/hunting.rules e exigem reload com augenrules --load; o parâmetro -k nomeia a chave que ausearch usa depois para filtrar. O dimensionamento é deliberadamente restrito às áreas de persistência, porque regra de execução global em servidor movimentado produz volume insustentável.
-w /etc/cron.d/ -p wa -k persistence_cron
-w /etc/cron.daily/ -p wa -k persistence_cron
-w /etc/crontab -p wa -k persistence_cron
-w /var/spool/cron/ -p wa -k persistence_cron
-w /etc/systemd/system/ -p wa -k persistence_service
-w /etc/ld.so.preload -p wa -k preload_change
-w /root/.ssh/ -p wa -k ssh_keys_change
-a always,exit -F arch=b64 -S init_module,finit_module -k lkm_load
-a always,exit -F arch=b64 -S execve -F euid=0 -k root_exec
Com a telemetria garantida, os comandos abaixo formam o estoque básico de caça direto no host, ordenados do inventário à investigação. Eles respondem às perguntas clássicas de um caso de persistência: o que roda agendado, o que está habilitado como serviço, quem pode entrar por SSH e o que foi carregado no kernel.
# 1. Inventário de cron de todos os usuários, com nome de usuário em cada linha
for u in $(cut -d: -f1 /etc/passwd); do
crontab -l -u "$u" 2>/dev/null | grep -v '^#' | sed "s|^|${u}: |"
done
grep -R . /etc/cron.d/ /etc/crontab 2>/dev/null
# 2. Eventos de mudança em cron, serviço e SSH capturados pelo auditd
ausearch -k persistence_cron -i --start today
ausearch -k persistence_service -i --start -7
ausearch -k ssh_keys_change -i --start -30
# 3. Serviços habilitados e criados recentemente, fora dos pacotes oficiais
systemctl list-unit-files --type=service --state=enabled | tail -n +2 | sort
find /etc/systemd/system /usr/lib/systemd/system -mtime -14 -name "*.service" -exec stat -c '%y %n' {} \;
# 4. Chaves SSH autorizadas alteradas no último mês
find / -name authorized_keys -newermt "30 days ago" -exec stat -c '%y %U %n' {} \; 2>/dev/null | sort -r
# 5. Preload e módulos de kernel carregados fora do estoque de pacotes
cat /etc/ld.so.preload 2>/dev/null
lsmod | awk 'NR>1 {print $1}' | sort
modinfo $(lsmod | awk 'NR>1 {print $1}') 2>/dev/null | grep -E "^(filename|signer)" | sort -u
# 6. Histórico de execução e autenticação pelo journald
journalctl --since "-7 days" -u cron -u CRON --no-pager | tail -100
journalctl --since "-7 days" _COMM=sshd --no-pager | grep -iE "accepted|failed|invalid" | tail -100
aureport --tty --summary
A leitura dos resultados exige referência: a lista de serviços habilitados só tem valor comparada com a imagem dourada ou com o inventário de software do controle INS.2, e chaves SSH autorizadas só são anômalas contra a lista de provisionamento do gerenciador de configuração. Por isso o hunting em Linux corporativo começa na configuração, e o comando é o último elo da cadeia, e não o primeiro.
Quando a centralização existe, as mesmas perguntas migram para o SIEM e ganham escala sobre centenas de hosts, e a consulta deixa de ser comando local para ser agregação. O exemplo abaixo caça em um ambiente com auditd encaminhado, procurando escritas em áreas de persistência feitas por processos que não são de gerenciamento de configuração, que é o mesmo critério do comando local com a vantagem de correlacionar horário, usuário e sessão de origem em um só lugar.
// Escritas em areas de persistencia Linux fora do fluxo de gerenciamento
Syslog
| where TimeGenerated > ago(7d) and Facility =~ "audit"
| where SyslogMessage has_any ("/etc/cron.d/", "/etc/crontab", "/var/spool/cron", "/etc/systemd/system/", "/etc/ld.so.preload")
| extend Host = Computer
| where SyslogMessage !has "ansible" and SyslogMessage !has "puppet" and SyslogMessage !has "chef"
| summarize Mudancas = count(), Primeira = min(TimeGenerated), Ultima = max(TimeGenerated)
by Host, Mensagem = SyslogMessage
| order by Mudancas desc
5. Hipóteses Estruturadas para Linux
As hipóteses seguem o formato SE-ENTÃO do controle TH.6 e transformam cada linha da tabela da seção 3 em exercício executável. A formulação explícita evita a caça vagabunda, em que o analista roda comandos sem critério de sucesso, e produz material reaproveitável como regra de detecção quando confirmada.
- Se um atacante instalou persistência em cron, então existe entrada nova em /etc/cron.* ou /var/spool/cron apontando para binário fora do inventário, e o auditd registrou a escrita com a chave persistence_cron em janela compatível com o horário da intrusão.
- Se o adversário mantém controle por serviço do systemd, então existe unit habilitada com ExecStart para caminho fora dos pacotes oficiais e o journald registra reinícios da unidade em horários sem manutenção planejada.
- Se houve uso de LD_PRELOAD para interceptar funções, então /etc/ld.so.preload tem linha ativa ou o auditd registrou escrita no arquivo, mesmo que a linha tenha sido removida depois, porque o watch grava no momento da mudança.
- Se o acesso é mantido por chave SSH plantada, então authorized_keys alterado nos últimos 30 dias em /home ou /root não corresponde a ticket de provisionamento, e o auth.log mostra login de chave da conta no horário seguinte.
- Se houve carregamento de módulo de kernel malicioso, então a syscall init_module ou finit_module foi executada fora do fluxo de atualização do pacote, com a chave lkm_load no auditd, e modinfo não mostra assinatura do fornecedor.
6. Exemplo de Caça de Persistência em Cron
O caso simula a resposta a um alerta de execução suspeita em servidor de aplicação, com a hipótese do item 1 da seção 5. O roteiro mostra a sequência completa: confirmar a entrada de cron, datar a mudança, identificar o processo que escreveu e medir a execução do payload. Cada passo tem comando e critério de saída, no mesmo padrão dos exemplos das notas anteriores.
# Passo 1: listar tudo que roda agendado e destacar caminhos suspeitos
for u in $(cut -d: -f1 /etc/passwd); do
crontab -l -u "$u" 2>/dev/null | grep -v '^#' | sed "s|^|${u}: |"
done | tee /tmp/cron_inventario.txt
grep -E "/tmp|/dev/shm|curl|wget|base64|python" /tmp/cron_inventario.txt
# Passo 2: datar e atribuir a mudança com o auditd
ausearch -k persistence_cron -i --start -7 | tail -50
ausearch -k persistence_cron -i --start -7 | aureport -f -i
# Passo 3: medir a execução do payload e o contexto do processo
journalctl --since "-7 days" _COMM=CRON --no-pager | grep -F "$(grep -oE '[^ ]+\.(sh|py)' /tmp/cron_inventario.txt | head -1)"
ausearch --start -7 -i -x /dev/shm/.update 2>/dev/null | tail -30
A interpretação do exemplo se apoia em três perguntas que fecham o caso. A entrada é nova em relação ao inventário do gerenciador de configuração, e não apenas nova na memória do analista, porque cron legítimo não documentado é problema de configuração e não de intrusão. O caminho do payload é incompatível com provisionamento, e caminhos em /tmp, /dev/shm e diretórios de usuário com nomes genéricos de atualização são o padrão clássico de implante. E o histórico do auditd mostra a escrita precedida de sessão SSH ou de processo web, o que conecta a persistência ao vetor de entrada e transforma o achado de hunting em caso de resposta com escopo definido. Se o servidor roda container e a entrada aparece no host em vez de no contêiner, a investigação sobe de nível, porque indica escape de contêiner, e o controle PRT.4 do capítulo 15 passa a ser parte do escopo do caso.
7. Erros Comuns e Pontos de Atenção
- Assumir telemetria que não existe: auditd sem regras grava quase nada, e journald sem Storage=persistent apaga o diário a cada reboot; validar a configuração antes de prometer caça, senão a primeira constatação do caso é que não há dados, e a segunda é que o período de interesse já rotacionou.
- Regra de execução global no auditd: logar todo execve em servidor movimentado enche o disco e derruba o próprio serviço de auditoria; restringir por usuário alvo, por diretório de persistência ou por caminho suspeito, e monitorar o próprio log de auditoria como ativo.
- Caçar apenas em arquivos atuais: o adversário limpa rastros, então a varredura de cron, preload e authorized_keys precisa ser complementada pelo histórico do auditd e do journald, que registram a mudança mesmo depois da remoção do rastro.
- Tratar comando root como malicioso por si só: automação de gerenciamento de configuração executa comandos root em massa a cada ciclo, e a distinção está na identidade da sessão, no horário e na cadeia de processo; excluir as identidades de automação com documento, e não com memória.
- Ignorar a fronteira host e container: ambientes modernos movem a superfície para dentro de contêineres, e cron de contêiner efêmero desaparece com ele; as ferramentas eBPF com política declarativa são a resposta adequada, e a ausência delas deve constar como lacuna no MAP.4.
- Depender de .bash_history como evidência: o arquivo é editável, apagável e nem sempre é gravado em sessões não interativas, então vale como pista e nunca como prova; o histórico confiável vem do auditd com logging de execução ou da camada eBPF, que registram a chamada no momento em que ela acontece.
8. Integração com o Restante da Biblioteca
Esta nota opera com as fontes inventariadas no capítulo 02 e no controle DAT.1, aplica a formulação de hipóteses do TH.6 e alimenta o inventário de cobertura de endpoint do TH.11 e as lacunas do MAP.4 com as técnicas da tabela da seção 3. A nota 09 é o par em Windows e Active Directory, com a mesma disciplina de evento e hipótese, e as notas 06 e 07 completam o quadro com a camada de rede que o host sozinho não vê. Os comandos confirmados devem virar regras no capítulo 18, com o auditd e o eBPF alimentando o SIEM pelo pipeline do controle DAT.4, e as métricas de cobertura de persistência em Linux entram no painel do MET.2. Quando o caso confirma grupo conhecido, o capítulo 01 recebe o perfil de TTP observado e a nota de atribuição do capítulo 05 orienta quanto nível de confiança é comunicável, fechando o ciclo de inteligência a partir do host.