📬 Toda semana, um resumo de segurança, Clean Code e IA no seu e-mail Inscreva-se →

Cache Attacks: como a cache do processador vaza chaves de AES e DES

Duas VMs de clientes diferentes, mesma máquina física, mesmo provedor de nuvem. Uma delas está processando operações de AES com uma chave privada. A outra não tem nenhum acesso de leitura àquela VM, nenhuma permissão especial, nenhum bug de isolamento de hipervisor explorado. E ainda assim, só medindo quanto tempo leva pra acessar certas posições de memória numa cache L3 que as duas compartilham fisicamente, é possível reconstruir bytes da chave AES da vizinha. Isso não é hipótese acadêmica distante — é um resultado replicado desde meados dos anos 2000 contra DES e depois contra AES, inclusive em cenários reais de cloud multi-tenant.

Já falei de side-channel attacks de forma ampla em outro artigo aqui no blog. Este aqui é mais específico: por que a cache — que existe unicamente pra te dar performance — também é um dos vazamentos mais praticáveis contra implementações de cifra, e o que de fato resolve o problema (dica: não é “usar uma cifra mais forte”).

O problema não é o algoritmo, é como ele acessa memória

RSA e AES são seguros matematicamente. O problema aparece na hora de implementar. Uma implementação clássica de AES em software usa tabelas de lookup pré-computadas (T-tables) pra acelerar a substituição de bytes do S-box: em vez de calcular a substituição, o código indexa uma tabela na memória usando um byte do estado intermediário, que depende da chave. Faz sentido do ponto de vista de performance — é exatamente esse tipo de otimização que qualquer engenheiro faria sem pensar duas vezes.

O problema é que “qual posição da tabela foi acessada” fica gravado, por um instante, no estado da cache do processador. E cache é compartilhada entre todos os processos que rodam na mesma CPU física — inclusive processos que não têm nenhuma relação lógica entre si, rodando em containers ou VMs diferentes.

Como um processo “lê” a cache de outro sem ler a memória dele

O truque central é que acessar uma posição de memória que já está em cache é rápido (poucos nanossegundos); acessar uma posição que não está é lento (dezenas de nanossegundos, porque o dado precisa vir da RAM). Um processo consegue medir esse tempo com precisão de nanossegundo usando instruções como rdtsc. Ele não precisa ler o conteúdo da memória do vizinho — só precisa saber se um endereço específico está ou não em cache, e isso já entrega padrão de acesso.

Duas técnicas concretas dominam essa família de ataque:

Flush+Reload — o atacante remove deliberadamente uma linha de cache específica (via instrução como clflush, quando disponível e compartilhamento de página é possível), espera a vítima rodar, e depois recarrega aquele mesmo endereço medindo o tempo. Se foi rápido, a vítima acessou aquele endereço nesse meio tempo (recarregou a linha pra ele). Se foi lento, a vítima não tocou naquele endereço. Repetindo isso pra cada entrada da T-table do AES, o atacante reconstrói qual entrada da tabela foi usada em cada rodada — e isso correlaciona diretamente com bytes da chave.

Prime+Probe — usado quando não há memória compartilhada entre atacante e vítima (cenário mais realista entre VMs distintas). O atacante preenche um conjunto inteiro da cache com dados próprios (“prime”), espera a vítima rodar, e depois acessa os mesmos endereços de novo medindo o tempo (“probe”). Se a vítima usou aquele conjunto de cache durante a execução, ela expulsou os dados do atacante, e o “probe” vai ser lento — revelando que aquele conjunto foi tocado.

Casos reais que tiraram isso do papel

Em 2003, pesquisadores (Tsunoo et al.) demonstraram um ataque de cache trace-driven contra as duas primeiras rodadas do DES, recuperando a chave de 56 bits com cerca de 2^10 entradas escolhidas — em vez da busca exaustiva de 2^56. Isso por si só já mostrava que “o algoritmo é seguro” e “a implementação é segura” são coisas diferentes.

Contra AES, ataques Flush+Reload e Prime+Probe em servidores compartilhados de nuvem conseguiram extrair chaves em questão de segundos, sem exigir nenhuma falha de isolamento entre VMs — só o fato de compartilharem a mesma cache física. Esse é o motivo pelo qual esse tema não é curiosidade acadêmica: se seu produto roda em infraestrutura compartilhada (e hoje quase tudo roda), esse é um vetor de ataque real contra qualquer implementação de cifra que use tabelas indexadas por dado secreto.

O que realmente resolve isso

A lista de “boas práticas” genéricas (constant-time, evitar branches condicionados por segredo) é real, mas o ponto específico de cache attack tem soluções mais concretas:

  • Eliminar lookup tables indexadas por segredo. Implementações “bitsliced” de AES processam os bits em paralelo usando só operações lógicas (AND, OR, XOR, shift), sem nenhum acesso a tabela dependente de chave. É mais lento em cenários single-block, mas competitivo (ou mais rápido) quando processa múltiplos blocos em paralelo, que é o caso comum em servidores.
  • Usar aceleração de hardware dedicada (AES-NI). Em CPUs x86 modernas, as instruções AES-NI implementam AES diretamente em silício, sem passar por memória cacheável nenhuma no processo. Isso resolve cache attacks contra AES de forma bem mais direta do que qualquer técnica de software — mas só existe pra AES, não pra qualquer cifra customizada que você tenha implementado.
  • Non-cacheable memory regions pra dados secretos, quando a plataforma permite marcar regiões de memória como não-cacheáveis.
  • Cache partitioning (cache coloring), isolando o uso de cache entre processos de forma que um não consiga observar o comportamento do outro — mais uma decisão de arquitetura de sistema do que de implementação de cifra.

Vale ser honesto sobre o trade-off: se você está num microcontrolador sem AES-NI, sem MMU pra marcar regiões non-cacheable, e sem orçamento de performance pra bitslicing, a superfície de ataque continua aberta. Isso não é motivo pra ignorar — é motivo pra decidir conscientemente qual nível de exposição seu produto aceita, e documentar isso, em vez de assumir que “AES é seguro” resolve o problema.

Esse tema conecta direto com power analysis (que cubro em outro artigo, olhando pro consumo de energia em vez de tempo de cache) e com scan chain attacks (que exploram uma superfície de ataque completamente diferente: a infraestrutura de teste do próprio chip). Se você trabalha com cripto embarcada, os três se complementam pra formar o quadro real do que “implementação segura” significa na prática.

Leandro Rosendo Candido

Leandro Rosendo Candido

Engenheiro de Computação, mestre pela USP, 10+ anos com Embedded Linux. Escreve e grava sobre segurança, Clean Code e IA aplicada à programação.

Rolar para cima