Share via


A Batalha pelo Kernel

Em um sistema operacional como o Windows os programas executados pelos usuários rodam dentro de processos. Cada processo é isolado dos demais processos - ele roda em sua própria área de memória, e uma falha em um processo não tem como afetar os outros. Cada processo também tem uma identidade, em geral a do usuário que o iniciou, que é usada para verificar as permissões no acesso a objetos como arquivos e chaves de registry.

Isto em resumo é o que chamamos de "modo usuário". Embaixo disso roda o núcleo do sistema operacional, ou kernel. Dentro do kernel estão as rotinas de controle de processos, o gerenciador de memória, o monitor de referência de segurança, protocolos de rede, interface gráfica, e os drivers de dispositivo, entre outros que fornecem a base para que os processos possam executar no modo usuário.

No kernel as regras de isolamento e segurança são bem diferentes do modo usuário. Não existem permissões ou privilégios dentro do kernel, e todo o código roda com o mesmo privilégio. Também não existe isolamento entre os módulos do kernel e cada um pode fazer o que quiser dentro do kernel (no modo usuário caso um processo apresente problemas ele é terminado sem prejuízo dos demais - no kernel uma falha em geral se torna catastrófica e leva a uma parada do sistema).  Na verdade do ponto de vista de segurança o kernel é todo ele um grande e único módulo.

Os modos usuário e kernel rodam em níveis de privilégio diferentes, isolados pela própria arquitetura do processador. Nos processadores x86 o kernel roda no anel de proteção zero (ring 0), o mais poderoso, que tem privilégios especiais somente disponíveis a este anel, como acesso direto a memória física e as interrupções de hardware. Os programas rodam no modo usuário, no anel de proteção 3 dos processadores x86, isolados e sob controle do código do kernel no anel 0. Quando algum programa em modo usuário precisa fazer uma operação privilegiada, ele a faz não acessando diretamente o hardware e sim através de uma chamada de sistema ao kernel.

Malware no Kernel

Um atacante em princípio não precisa entrar dentro do kernel para comprometer um sistema. Bancos de dados, servidores web, navegadores, arquivos pessoais, tudo isso está no modo usuário, e ataques contra estes alvos podem ser feitos facilmente usando código em modo usuário, escrito em VB ou mesmo em script, rodando como administrador.  Basta um bug em uma aplicação, ou um anexo de e-mail executado pelo usuário, e o ataque pode ser feito sem a necessidade de lidar com a complexidade de um kernel.

Mas para quê então um malware iria querer obter acesso ao kernel, se ele já tem privilégio de administrador e pode tranqüilamente fazer o ataque? Principalmente para não ser detectado. Mesmo as ações de um administrador são registradas pelo auditoria do sistema, por exemplo. E malwares em modo usuário são um alvo fácil para softwares antivirus e de prevenção de intrusão rodando dentro do kernel, que tem visibilidade total sobre tudo o que é feito em modo usuário. Ao rodar dentro do kernel o malware pode bypassar totalmente todas as rotinas de segurança do sistema e ficar completamente invisível, e pode tornar também invisível outros softwares à sua escolha.

Por isso nos últimos anos estamos vendo o aumento de rootkitsrodando em modo kernel, usados principalmente para esconder ou impedir a remoção de spywares. Neste "combo" o spyware roda em modo usuário e é escondido pelo componente em modo kernel, que impede que outros softwares consigam enxergar o spyware. E mesmo se o spyware for eventualmente detectado e removido, uma rotina rodando dentro do kernel reinstala o spyware. O processo de remoção de algo deste tipo pode ser tão complexo que em alguns casos ela somente é possível reinstalando o sistema.

O número de ataques deste tipo tem crescido exponencialmente. Nas estatísticas de malware removidos pela ferramenta de remoção de software malicioso da Microsoft, por exemplo, um rootkit de kernel (FU) foi o quinto malware mais encontrado. E estes números provavelmente ainda estão bem abaixo da realidade, porque rodando dentro do kernel em tese é possível construir rootkits virtualmente indetectáveis. As ferramentas de detecção têm que procurar por erros na implementação do rootkit (que são logo corrigidas - vide a batalha entre Mark Russinovitch e o "Holy Father"), e na verdade a única forma segura de detectar um rootkit é reiniciando o sistema a partir de uma midia confiável, assumindo que o rootkit seja desenhado para sobreviver ao reboot.

As técnicas uasdas pelos rootkits para ficarem indetectáveis dentro do kernel são um assunto muito longo para esta nota, mas para os interessados uma boa introdução pode ser encontrada aquiaqui. Em resumo, elas envolvem principalmente a alteração (hooking) de tabelas de chamadas do kernel (SSDT/IDT), ou manipulação direta de objetos críticos do kernel como o agendador de processos e os token de privilégios dos usuários.

Impedindo Malware de Entrar no Kernel

A melhor forma de evitar o comprometimento do kernel por um malware é simplesmente não deixar o malware entrar, colocando proteções que impeçam que um código malicioso rodando como administrador consiga se inserir dentro do kernel. Existem dois caminhos que um malware rodando como administrador pode colocar código dentro do kernel, pulando do anel 3 para o anel 0:

  1. O primeiro e mais simples é carregar um driver - afinal entre os privilégios básicos de um administrador está o de carregar um driver no sistema, e drivers rodam dentro do kernel sem nenhum tipo de isolamento.
  2. O segundo é utilizar um bug no processamento das chamadas de sistema, que são a interface entre o modo usuário e o kernel. Este bug pode estar em qualquer componente do kernel que receba chamadas do modo usuário, por exemplo em um protocolo de rede ou um driver de dispositivo.

O primeiro caminho é um problema de arquitetura: o malware estaria fazendo uma ação perfeitamente legítima (carregar um driver), previsto nas especificações do sistema. O segundo seria um problema de implementação: a ação é ilegítima e não deveria acontecer, mas se torna possível porque um driver ou componente do sistema foi implementado incorretamente.

Problema de arquitetura só podem ser corrigidos por mudanças na arquitetura, e para fechar o primeiro caminho o Windows Vista implementa o recurso de Code Integrity. Na plataforma de 64-bits (x64), todo o código que vai executar em modo kernel tem que estar assinado digitalmente, incluindo binários do sistema e drivers. A assinatura é feita pelo autor utilizando um certificado digital obtido em certificadoras públicas, que por sua vez têm que ter uma assinatura cruzada da Microsoft. O código só é carregado pelo kernel se a assinatura digital for validada pelo sistema, sem exceções. Desta forma pode-se assegurar a integridade e a procedência de todo o código rodando no kernel, fechando o caminho mais fácil para a inserção do malware.

Resta então o segundo caminho, e uma rápida olhada no Month of Kernel Bugs mostra como especialmente drivers de dispositivo podem ser uma fértil fonte de bugs que permitam o comprometimento do kernel. Infelizmente a única forma de evitar problemas de implementação nos drivers é ter uma implementação correta, o que só pode ser feito pelos fabricantes dos próprios drivers. Mas para isso a Microsoft está tomando as seguintes ações:

¦ A Microsoft disponibiliza como parte do kit de desenvolvimento de drivers (DDK) o PreFAST, uma ferramenta de análise de código C/C++ para identificação de estouros de buffer e outros problemas de segurança, incluindo pontos específicos relativos ao modo kernel. Ele faz parte de um conjunto maior de ferramenta de teste disponíveis para os fabricantes de drivers.

¦ A Microsoft também está fazendo treinamentos de segurança específicos para fabricantes de equipamentos. O blog do Michael Howard mostrou um destes treinamentos, e mais ações neste sentido virão.

E se Ainda Assim o Malware Entrar no Kernel?

Mas problemas de implementação vão continuar a ocorrer, não só nos drivers como também no próprio sistema. Como uma segunda camada de proteção, os Windows 64-bits implementam o Kernel Patch Protection ou PatchGuard, que protege as estruturas do kernel normalmente atacadas por malware. Caso por exemplo o SSDT, IDT e outros objetos sejam alterados indevidamente, o sistema irá parar (blue screen), impedindo por exemplo um rootkit de se instalar. Desta forma mesmo que um malware  consiga ter acesso ao kernel, o PatchGuard irá impedir que ele assuma o controle do sistema.

Um minuto - você disse que malware rodando dentro do kernel pode fazer qualquer coisa, então ele não poderia desabilitar o PatchGuard? Sim, poderia. Tanto o PatchGuard quanto o rootkit estariam rodando dentro do mesmo anel 0, e não existe então barreira de proteção firme que impeça o rootkit de eventualmente conseguir modificar o próprio PatchGuard.

Ainda assim ele é um recurso de proteção extremamente valioso, se tanto porque inverte completamente a lógica da batalha. Antes os softwares de segurança tinham que procurar uma falha no rootkit para poder detectá-lo, e o autor do malware rapidamente corrigia a falha e continuava o seu ataque. Agora são os rootkits que vão ter que procurar uma falha no PatchGuard - que se for encontrada a Microsoft irá corrigir.

(Joanna Rutkowska apresenta um outro argumento em favor do PatchGuard. Segundo ela, o PatchGuard define claramente a fronteira entre o quê um módulo do kernel pode fazer e o quê não pode. Desta forma os softwares legítimos não podem tomar ações similares a um rootkit, e fica mais fácil detectar que algo está sendo feito por um malware).

Já falei anteriormente aqui sobre as proteções do Windows Vista contra ataques em modo usuário, como o User Account Control e o controle mandatório de integridade, e da proteção contra ataques offline feita pelo BitLocker. Somado a eles, a assinatura de código e o PatchGuard do Vista x64 representam uma estratégia completa de proteção contra ataques ao sistema, e um avanço formidável sobre as versões anteriores do Windows. E se você precisava de mais algum incentivo para migrar para 64-bits, aqui está.

P.S. Mas isso é o fim? Ganhamos a guerra? Ainda não - em um próximo post vamos ver o cenário da próxima batalha, o hypervisor.