Como Fazer um motor antivírus

Envio

Usuário Rating4.54(13 votos)

Introdução

Quando em roaming em torno de técnicos fóruns, muitas vezes eu vejo algumas pessoas (e muitos não é muito experiente, pedindo “Como eu faço um antivírus”, às vezes com não muito adaptado línguas (bat, PHP, …) e tendo uma ideia errada do que um antivírus é, e como ele deve ser construído.

eu também vi muitos “softwares antivírus” feitos por crianças, com muito poucas pessoas ainda na escola e cerca de 4 horas por dia de codificação em várias semanas. Eu não estou dizendo kiddies não são qualificados, mas eu estou dizendo a construção de um motor antivírus precisa de um monte de pessoas qualificadas com trabalho a tempo inteiro, além de muito tempo para liberar um software decente ou muito dinheiro para pagá-los 🙂 (no caso, eles não são voluntários).

então, vou cobrir aqui as Diretrizes para uma codificação antivírus Básica, Para Windows e em C/C++. Pode-se encontrar aqui os ponteiros para projetar um mecanismo antivírus ou simplesmente aprender como a maioria deles é construída.

proteção

para uma boa proteção, um antivírus deve ter pelo menos um driver, para poder executar o código no kernel e, em geral, ter acesso às APIs do kernel. A partir do Vista, a Microsoft entendeu que o setor de antivírus precisava de chaves para entrar no kernel e ativar filtros em locais estratégicos, como sistema de arquivos, registro e rede. Não fique surpreso se construir um antivírus para sistemas pré-Vista pode ser uma verdadeira dor, porque não foi projetado para isso.

  • no entanto, em Sistemas Pré-Vista, as empresas de antivírus costumavam usar recursos semelhantes ao rootkit para proteger as portas (mesmo que não fosse recomendado pela Microsoft) e ser capaz de proteger seu sistema. Eles usaram o que chamamos de “ganchos” (desvios de API para fins de filtragem).
  • no Vista+, a Microsoft forneceu APIs para inserir Nosso driver de baixo nível entre chamadas do userland e APIs do kernel. Dessa forma, é fácil registrar um produto antivírus no kernel. Mais, esse tipo de sistema baseado em registro nos permite despachar nossa segurança do sistema em camadas, onde vários produtos com diferentes objetivos podem coabitar. Este não foi o caso dos ganchos, pois a implementação era totalmente dependente do produto.

nota: Eu não vou cobrir as soluções alternativas com ganchos para pré-sistemas de Vista , porque é fácil de encontrar na internet, e porque seria necessário um capítulo inteiro para explicar como gancho, onde gancho e tanto… Mas você tem que saber o que é a mesma idéia do que as APIs do kernel, exceto que você terá para implementar-se o que a Microsoft apresentou em Vista+ sistemas.

para saber mais sobre drivers de codificação, você pode verificar se links úteis:
http://msdn.microsoft.com/en-us/library/windows/hardware/gg490655.aspx
http://www.codeproject.com/Articles/9504/Driver-Development-Part-1-Introduction-to-Drivers

para saber mais sobre ganchos, você pode verificar esse exemplo básico:
http://www.unknowncheats.me/forum/c-and-c/59147-writing-drivers-perform-kernel-level-ssdt-hooking.html

processo

a primeira coisa a proteger o Usuário é o lançamento de processos maliciosos. Esta é a coisa básica. O Antivírus deve registrar um retorno de chamada pssetcreateprocessnotifyroutineex. Ao fazer isso, em cada criação de processo, e antes que o thread principal comece a ser executado (e cause coisas maliciosas), o retorno de chamada do antivírus é notificado e recebe todas as informações necessárias.

recebe o nome do processo, o objeto do arquivo, o PID e assim por diante. Como o processo está pendente, o driver pode dizer ao seu serviço para analisar a memória do processo para qualquer coisa maliciosa. Ele funda algo, o driver simplesmente definirá CreationStatus como FALSE e retornará.

NTSTATUS PsSetCreateProcessNotifyRoutineEx( _In_ PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, _In_ BOOLEAN Remove);
VOID CreateProcessNotifyEx( _Inout_ PEPROCESS Process, _In_ HANDLE ProcessId, _In_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo);
typedef struct _PS_CREATE_NOTIFY_INFO { SIZE_T Size; union { ULONG Flags; struct { ULONG FileOpenNameAvailable :1; ULONG Reserved :31; }; }; HANDLE ParentProcessId; CLIENT_ID CreatingThreadId; struct _FILE_OBJECT *FileObject; PCUNICODE_STRING ImageFileName; PCUNICODE_STRING CommandLine; NTSTATUS CreationStatus;} PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;

Threads

na mesma ideia do que para processos, threads podem ser uma maneira de coisas maliciosas causarem danos. Por exemplo, pode-se injetar algum código em um processo legítimo e iniciar um thread remoto nesse código dentro do contexto do processo (fácil de seguir? 🙂 ). Dessa forma, um processo legítimo pode fazer coisas maliciosas.

podemos filtrar novos threads com o retorno de chamada pssetcreatethreadnotifyroutine. Cada vez que um thread é criado, o Antivírus é notificado com o TID e o PID. Assim, ele é capaz de examinar o código de endereço inicial do thread, analisá-lo e interromper o thread ou retomá-lo.

NTSTATUS PsSetCreateThreadNotifyRoutine( _In_ PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine);
VOID(*PCREATE_THREAD_NOTIFY_ROUTINE) ( IN HANDLE ProcessId, IN HANDLE ThreadId, IN BOOLEAN Create );

imagens

a terceira ameaça dinâmica é sobre imagens que podem ser carregadas na memória. Uma imagem é um arquivo PE, EXE, DLL ou sys. Para ser notificado de imagens carregadas, basta registrar pssetloadimagenotifyroutine. Esse retorno de chamada nos permite ser notificado quando a imagem é carregada na memória virtual, mesmo que nunca seja executada. Podemos então detectar quando um processo tenta carregar uma DLL, carregar um driver ou disparar um novo processo.

o retorno de chamada obtém informações sobre o caminho completo da imagem (útil para análise estática) e, o mais importante na minha opinião, o endereço base da imagem (para análise na memória). Se a imagem for maliciosa, o Antivírus pode usar pequenos truques para evitar a execução, como analisar a imagem na memória e ir para o ponto de entrada e, em seguida, chamar o código de operação de montagem “ret” para anulá-lo.

NTSTATUS PsSetLoadImageNotifyRoutine( _In_ PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine);
VOID (*PLOAD_IMAGE_NOTIFY_ROUTINE)( __in_opt PUNICODE_STRING FullImageName, __in HANDLE ProcessId, __in PIMAGE_INFO ImageInfo );
typedef struct _IMAGE_INFO { union { ULONG Properties; struct { ULONG ImageAddressingMode : 8; //code addressing mode ULONG SystemModeImage : 1; //system mode image ULONG ImageMappedToAllPids : 1; //mapped in all processes ULONG Reserved : 22; }; }; PVOID ImageBase; ULONG ImageSelector; ULONG ImageSize; ULONG ImageSectionNumber;} IMAGE_INFO, *PIMAGE_INFO;

sistema de arquivos

uma vez que cada coisa dinâmica é protegida, um antivírus deve ser capaz de notificar o Usuário para coisas maliciosas on-the-fly, não só quando eles estão prestes a começar. Um antivírus deve ser capaz de verificar arquivos quando o usuário abre uma pasta, um arquivo ou quando é baixado no disco. Além disso, um antivírus deve ser capaz de se proteger, proibindo qualquer programa de excluir seus arquivos.

a maneira de fazer tudo isso é instalar um driver no sistema de arquivos e, mais especificamente, um minifiltro de um filtro legado (maneira antiga). Aqui vamos falar sobre minifiltro.

um minifilter é um tipo específico de driver, capaz de registrar retornos de chamada em cada operação de leitura/gravação feita no sistema de arquivos (funções principais do IRP). Um IRP (Interrupt Request Paquet) é um objeto usado para descrever uma operação de leitura/gravação no disco, que é transmitida junto com a pilha do driver. O minifilter será simplesmente inserido nessa pilha e receberá esse IRP para decidir o que fazer com ele (Permitir/Negar operação).

para um pequeno exemplo de minifilter, verifique esse link útil ou aquele. As diretrizes da microsoft estão aqui.

você também encontrará 2 Exemplos da documentação do WDK aqui e aqui.

um retorno de chamada básico do minifilter é assim. Existem 2 tipos de retorno de chamada, pré-operação e pós-operação, que são capazes de filtrar antes de depois da consulta. Aqui está um pseudo código de pré-operação:

FLT_PREOP_CALLBACK_STATUS PreOperationCallback (__inout PFLT_CALLBACK_DATA Data, __in PCFLT_RELATED_OBJECTS FltObjects, __deref_out_opt PVOID *CompletionContext){ ... if ( all_good ) { return FLT_PREOP_SUCCESS_NO_CALLBACK; } else { // Access denied Data->IoStatus.Information = 0; Data->IoStatus.Status = STATUS_ACCESS_DENIED; return FLT_PREOP_COMPLETE; } }

registro

o registro é um dos lugares mais críticos para se proteger. Existem muitas maneiras de um malware manter a mão persistente no sistema, registrando uma única (ou poucas) chaves/valores no registro. Os lugares mais conhecidos são Executar chaves e serviços. Este também é o lugar onde o Antivírus pode ser derrotado (junto com o sistema de arquivos), simplesmente removendo suas chaves de driver/serviço, para que ele não seja mais reiniciado na inicialização do sistema.

esta não é uma necessidade real para um antivírus para proteger lugares reiniciar, a maioria deles não. mas eles devem proteger suas chaves de registro de instalação, para evitar ser derrotado facilmente por malwares. Isso pode ser feito registrando CmRegisterCallback.

o retorno de chamada fornece informações suficientes para obter o nome completo da chave, o tipo de acesso (Criar, renomear, excluir,…) e o PID do chamador. Dessa forma, é fácil conceder acesso ou não à chamada, definindo o campo Status do retorno de chamada pós-operação.

NTSTATUS CmRegisterCallbackEx(_In_ PEX_CALLBACK_FUNCTION Function,_In_ PCUNICODE_STRING Altitude,_In_ PVOID Driver,_In_opt_ PVOID Context,_Out_ PLARGE_INTEGER Cookie,_Reserved_ PVOID Reserved);
EX_CALLBACK_FUNCTION RegistryCallback;NTSTATUS RegistryCallback( _In_ PVOID CallbackContext, _In_opt_ PVOID Argument1, _In_opt_ PVOID Argument2){ ... }Argument1 = typedef enum _REG_NOTIFY_CLASS { RegNtDeleteKey, RegNtPreDeleteKey = RegNtDeleteKey,...Argument2 = typedef struct _REG_POST_OPERATION_INFORMATION { PVOID Object; NTSTATUS Status; PVOID PreInformation; NTSTATUS ReturnStatus; PVOID CallContext; PVOID ObjectContext; PVOID Reserved;} REG_POST_OPERATION_INFORMATION, *PREG_POST_OPERATION_INFORMATION;

de Rede (Firewall)

Para proteger as portas de todo o tráfego da internet que pode ser enorme em determinados sistemas (servidores, largura de banda enorme de usuários), sem ser retardado pela troca de contexto, que acontece no espaço do usuário, é totalmente não recomendado para instalar uma firewall que não tem nenhum driver subjacente, exceto para alguns navegador da web de filtros que podem ser o suficiente para o tráfego http, mas que não irá proteger contra malware comunicação in/out.

para ter uma implementação correta do firewall, deve-se codificar um NDIS, TDI ou outro método para driver de filtragem IP de baixo nível. NDIS / TDI é um pouco complicado de fazer, e exigiria muito conhecimento (mais do que outros filtros na minha opinião).

de qualquer forma, Aqui estão algumas dicas para começar a codificar esse driver, as diretrizes da microsoft e o antigo tutorial codeproject (mas ainda é bom ler), um exemplo de firewall NDIS e um exemplo de firewall TDI. Aqui está também uma boa escrita sobre NDIS firewall Bypass trick, e uma pequena explicação sobre a pilha de drivers de rede,

Userland protection

a proteção userland não é uma necessidade, mas pode ser um módulo adicional contra banqueiros Trojan, e mais especificamente contra espiões de processo. Eles geralmente são injetados em todos os processos, por vários motivos.

primeiro, eles são capazes (sob demanda) de matar o processo se ele foi identificado como malware (isso não deve acontecer, porque os AVs devem pará-lo antes de começar). É sempre mais fácil parar um processo quando você está em seu contexto.

em segundo lugar, eles são capazes de proteger o processo crítico, como navegadores da web, contra enganchar malwares capazes de desviar e filtrar chamadas de API para coletar senhas, informações bancárias e redirecionar o fluxo da internet para servidores de malware. Eles só observam a modificação do IAT, para splicing, e também podem definir Ganchos para evitar o LoadLibray de uma DLL de malware e, assim, proibir certos métodos de injeção de código.

uma maneira fácil de injetar uma DLL protetora em todos os processos é usar a chave de registro AppInitDll para registrar a DLL protetora. Ele carregará a DLL em todos os processos iniciados no sistema, assim que eles ligarem o Usuário32.imagem dll (a maioria deles faz).

Analysis Engine

o analysis engine é uma das partes mais importantes, é responsável por analisar amostras de Arquivo / Memória provenientes dos drivers. Se tem de ser rápido (mesmo com um enorme banco de dados), e deve ser capaz de lidar com a maioria dos tipos de arquivo (Auto-executáveis extraídos, Arquivos RAR, ZIP, arquivos compactados – UPX, … ) e, portanto, deve ter muitos módulos para fazer isso:

  • Unpacker : esse módulo deve ser capaz de detectar e descompactar a maioria das packers (UPX, Tatu, ASPack, …)
  • Assinatura do motor: O banco de dados de um antivírus contém milhões de assinaturas, e o motor deve ser capaz de fast search para eles em uma amostra. Assim, um algoritmo muito poderoso deve fazer parte dele. Exemplo : Ahocorasick, RabinKarp, algoritmos de correspondência de cordas.
  • Sandbox: esse módulo não é necessário, mas seria uma vantagem poder executar amostras em uma memória limitada, sem efeito no sistema. Isso pode ajudar a descompactar amostras embaladas com empacotadores desconhecidos e ajudar o mecanismo heurístico (veja depois) a detectar chamadas de API que podem ser consideradas suspeitas ou maliciosas. Uma boa caixa de areia aqui.
  • mecanismo heurístico: como dito acima, o mecanismo heurístico não procura assinaturas, mas sim procura comportamento suspeito (ie. exemplo que abre uma conexão no site hxxp: / / malware_besite. com). isso pode ser feito por análise estática ou por meio de uma sandbox.

sintaxe de assinatura

a sintaxe de assinatura é o” dicionário ” da linguagem que o mecanismo de assinatura entende. É uma maneira de formalizar Qual é o padrão a ser encontrado, como pesquisá-lo e onde pesquisá-lo na amostra. A sintaxe tem que ser simples o suficiente para os pesquisadores entenderem, poderosa o suficiente para lidar com todos os casos de uso e fácil de analisar para melhores desempenhos do motor.

VirusTotal desenvolveu uma boa sintaxe e mecanismo (projeto Yara), que é de código aberto. Isso deve ser um bom ponteiro para fazer sua própria sintaxe, ou simplesmente usá-lo. Aqui está também um bom blog de postagem sobre como criar assinaturas para Antivírus.

Exemplo de assinatura:

rule silent_banker : banker{ meta: description = "This is just an example" thread_level = 3 in_the_wild = true strings: $a = {6A 40 68 00 30 00 00 6A 14 8D 91} $b = {8D 4D B0 2B C1 83 C0 27 99 6A 4E 59 F7 F9} $c = "UVODFRYSIHLNWPEJXQZAKCBGMT" condition: $a or $b or $c}

de Auto-protecção

auto proteção é muito importante para um antivírus, para evitar ser derrotado por um malware e continuar a proteger o usuário. É por isso que um antivírus deve ser capaz de proteger sua própria instalação e manter a persistência na reinicialização.

existem vários lugares para proteger: arquivos, chaves de registro, processos/Threads, memória.

  • a proteção de arquivos é implementada no minifilter, com regras específicas sobre os arquivos do antivírus (sem acesso na exclusão, renomeação, movimentação, gravação).
  • a proteção do registro é feita no filtro de registro, com acesso negado para chaves de registro do driver e do serviço.
  • Os drivers threads são protegidos, porque é completamente impossível para descarregar o módulo do kernel sem falhas no sistema
  • Para ser capaz de proteger o serviço, que é uma ‘userland’ do processo, de 2 soluções:

  1. O mais fácil seria para adicionar regras para falhas no service manager e defina cada falha regra para “reiniciar serviço”. Dessa forma, quando o serviço não é interrompido pelo service manager, ele é reiniciado. Claro, o serviço não deve ser capaz de aceitar comandos até que o sistema não esteja reiniciando (ou parando).
491838-428-487-263x300
  1. o segundo método, que é mais genérico, seria definir retornos de chamada em identificadores de processo com ObRegisterCallbacks.

definindo o tipo de objecto para PsProcessType e a Operação de OB_OPERATION_HANDLE_CREATE, você recebe um Pré e Pós-operação de retorno de chamada, e são capazes de retornar ACCESS_DENIED em ReturnStatus se o identificador de processo consultado GrantedAccess que tem o processo terminar direitos (ou processo de gravação de direitos, ou qualquer coisa que possa levar a um crash/matar), e, claro, se o processo é um antivírus precisa de guarda (o seu serviço, por exemplo).

claro, também é necessário proteger o identificador duplicado e o PsThreadType para evitar qualquer método de terminação que exija agarrar uma alça no processo ou um thread. Aqui está um pequeno exemplo de uso desse retorno de chamada.

NTSTATUS ObRegisterCallbacks( _In_ POB_CALLBACK_REGISTRATION CallBackRegistration, _Out_ PVOID *RegistrationHandle);
typedef struct _OB_CALLBACK_REGISTRATION { USHORT Version; USHORT OperationRegistrationCount; UNICODE_STRING Altitude; PVOID RegistrationContext; OB_OPERATION_REGISTRATION *OperationRegistration;} OB_CALLBACK_REGISTRATION, *POB_CALLBACK_REGISTRATION;
typedef struct _OB_OPERATION_REGISTRATION { POBJECT_TYPE *ObjectType; OB_OPERATION Operations; POB_PRE_OPERATION_CALLBACK PreOperation; POB_POST_OPERATION_CALLBACK PostOperation;} OB_OPERATION_REGISTRATION, *POB_OPERATION_REGISTRATION;
VOID ObjectPostCallback( _In_ PVOID RegistrationContext, _In_ POB_POST_OPERATION_INFORMATION OperationInformation);
typedef struct _OB_POST_OPERATION_INFORMATION { OB_OPERATION Operation; union { ULONG Flags; struct { ULONG KernelHandle :1; ULONG Reserved :31; }; }; PVOID Object; POBJECT_TYPE ObjectType; PVOID CallContext; NTSTATUS ReturnStatus; POB_POST_OPERATION_PARAMETERS Parameters;} OB_POST_OPERATION_INFORMATION, *POB_POST_OPERATION_INFORMATION;
typedef union _OB_POST_OPERATION_PARAMETERS { OB_POST_CREATE_HANDLE_INFORMATION CreateHandleInformation; OB_POST_DUPLICATE_HANDLE_INFORMATION DuplicateHandleInformation;} OB_POST_OPERATION_PARAMETERS, *POB_POST_OPERATION_PARAMETERS;
typedef struct _OB_POST_CREATE_HANDLE_INFORMATION { ACCESS_MASK GrantedAccess;} OB_POST_CREATE_HANDLE_INFORMATION, *POB_POST_CREATE_HANDLE_INFORMATION;

GUI (Graphical User Interface)

Esta é a parte visível do iceberg. Na minha opinião, uma (talvez a) parte mais importante se você quiser vender seu produto. Os usuários adoram o que é bonito, fácil de usar, intuitivo. Mesmo que não seja 100% eficiente. A GUI deve ser sexy.

a GUI é apenas um shell vazio, faz apenas tratamentos gráficos e envia/recebe comandos para o núcleo (o serviço). Ele também exibe barras de progresso, o que está sendo analisado, fornece configuração e, portanto, aqui está a interface do usuário do Avast. Sexy, certo? 🙂

avast

Arquitetura

A arquitetura global pode ser algo que esta aparência:

  1. GUI : Sem direitos administrativos, o FRACO
  2. Guarda DLL(s) : navegador web de proteção, MÉDIO
  3. Serviço : direitos de Administrador. Serve como um gateway para o código do kernel e tomar decisões junto com algum banco de dados, strong
  4. Driver(s) : filtros do Kernel, STRONG

a GUI não precisa de nenhum direito administrativo, apenas realiza ações do Usuário e as transmite para o serviço. Ele também exibe o status do produto. Nada mais, este não é o seu objetivo. Se a GUI for morta, isso não é um problema, pois o serviço deve ser capaz de reiniciá-la.

as DLLs de guarda (se houver), são injetadas massivamente em todos os processos e devem ser capazes de procurar ganchos IAT e/ou threads maliciosos. Eles devem ser muito difíceis de descarregar ou derrotar. Eles não são críticos, mas importantes.

o serviço é o núcleo do produto. Deve ser impossível de matar, ou pelo menos deve ser capaz de reiniciar automaticamente na morte. O serviço é responsável pela comunicação entre todos os módulos do produto, envia comandos aos drivers, pega comandos do Usuário e consulta o banco de dados para análise de amostra. Este é o cérebro.

os drivers do kernel também são críticos. São os tentáculos que reúnem informações sobre tudo o que acontece no sistema e os transmitem ao serviço para decisão. Eles também podem negar o acesso a locais protegidos, com base na decisão do serviço.

Conclusão

Fazer forte, confiável e estável motor antivírus é uma tarefa complicada, que precisa experimentou as pessoas com um forte conhecimento no kernel do windows de programação, programação de aplicativo do windows, GUI design, arquitetura de software, análise de malware, …

Construção de drivers estáveis também é uma tarefa complicada, porque um pequeno grão de areia pode travar todo o sistema. Ele precisa de testes, testes e muitos testes. Quando seu mecanismo estiver pronto e funcionando, você só precisará contratar pesquisadores para analisar malwares e adicionar assinaturas ao seu banco de dados 🙂 o que você está esperando? Vamos! 😀

ligações

  • http://www.symantec.com/connect/articles/building-anti-virus-engine

Leave a Reply

O seu endereço de email não será publicado.