Escrevendo Filtros

20 de January de 2010 - Fernando Roberto

Vamos brincar de algo mais interessante agora. Obviamente ainda vamos dar passos pequenos para não nos perder com tantos detalhes. Hoje falarei sobre filtros de drivers. O IoManager do Windows nos permite adicionar funcionalidade a determinados drivers sem que tenhamos que substituí-lo. Um exemplo clássico seria um driver de criptografia de arquivos. Você não precisa escrever um novo driver de file system para que se tenha tal funcionalidade. Você pode simplesmente escrever um filtro que ficaria entre o driver de file system e o restante do sistema.

Na figura abaixo podemos observar o fluxo de IRPs que vão do IoManager para um certo driver, um filtro é instalado sobre o driver existente e passa a receber as IRPs no lugar do driver original. Com isso o filtro tem a oportunidade de alterar os parâmetros das IRPs recebidas, ou logar a atividade do driver original, ou duplicar solicitações do sistema para esse driver, ou mesmo negar serviço do driver original.

Numa operação de escrita, um filtro poderia criptografar os dados antes de enviá-los ao driver original, e de maneira similar, numa operação de leitura o filtro poderia decriptografar dados antes de entregá-los ao sistema.

Ainda não vai ser desta vez que construiremos um filtro de criptografia de file system. Filtros de criptografia de arquivos em tempo real estão entre os drivers mais complexos a serem escritos. Vamos escolher um driver mais simples, pra não dizer um bem besta, para aplicar os conceitos básicos demonstrados aqui.

Por falar em driver besta, vamos utilizar o driver deste post que já foi visto aqui. Este driver simplesmente armazena uma lista de strings enviadas por uma aplicação em operações de escrita. Tais strings são retornadas à aplicação em operações de leituras.

Escrevendo a DriverEntry

Como vimos neste outro post, uma das das coisas que a função DriverEntry() faz num driver comum é setar as Dispatch Routines que o driver vai atender para certo dispositivo. Para isso deve-se preencher o array de Major Functions que fica na estrutura DRIVER_OBJECT.

    //-f--> Seta as dispatch routines do driver.
    pDriverObj->MajorFunction[IRP_MJ_CREATE] = OnCreate;
    pDriverObj->MajorFunction[IRP_MJ_CLEANUP] = OnCleanup;
    pDriverObj->MajorFunction[IRP_MJ_CLOSE] = OnClose;
    pDriverObj->MajorFunction[IRP_MJ_READ] = OnRead;
    pDriverObj->MajorFunction[IRP_MJ_WRITE] = OnWrite;

No caso do nosso filtro de exemplo, queremos apenas monitorar a atividade do driver ao qual estamos atachados, dessa forma teremos sempre que encaminhar quaisquer IRPs recebidas ao driver de baixo. Uma forma simples e comum de fazer isso é setar todas dispath routines para uma única função. Essa função se encarrega de simplesmente logar a solicitação recebida e passá-la a diante.

Se dermos uma olhada na definicão de IRP_MJ_CREATE e seus amigos, veremos o seguinte trecho do arquivo wdm.h.

//
// Define the major function codes for IRPs.
//
 
 
#define IRP_MJ_CREATE                   0x00
#define IRP_MJ_CREATE_NAMED_PIPE        0x01
#define IRP_MJ_CLOSE                    0x02
#define IRP_MJ_READ                     0x03
#define IRP_MJ_WRITE                    0x04
#define IRP_MJ_QUERY_INFORMATION        0x05
#define IRP_MJ_SET_INFORMATION          0x06
#define IRP_MJ_QUERY_EA                 0x07
#define IRP_MJ_SET_EA                   0x08
#define IRP_MJ_FLUSH_BUFFERS            0x09
#define IRP_MJ_QUERY_VOLUME_INFORMATION 0x0a
#define IRP_MJ_SET_VOLUME_INFORMATION   0x0b
#define IRP_MJ_DIRECTORY_CONTROL        0x0c
#define IRP_MJ_FILE_SYSTEM_CONTROL      0x0d
#define IRP_MJ_DEVICE_CONTROL           0x0e
#define IRP_MJ_INTERNAL_DEVICE_CONTROL  0x0f
#define IRP_MJ_SHUTDOWN                 0x10
#define IRP_MJ_LOCK_CONTROL             0x11
#define IRP_MJ_CLEANUP                  0x12
#define IRP_MJ_CREATE_MAILSLOT          0x13
#define IRP_MJ_QUERY_SECURITY           0x14
#define IRP_MJ_SET_SECURITY             0x15
#define IRP_MJ_POWER                    0x16
#define IRP_MJ_SYSTEM_CONTROL           0x17
#define IRP_MJ_DEVICE_CHANGE            0x18
#define IRP_MJ_QUERY_QUOTA              0x19
#define IRP_MJ_SET_QUOTA                0x1a
#define IRP_MJ_PNP                      0x1b
#define IRP_MJ_PNP_POWER                IRP_MJ_PNP      // Obsolete....
#define IRP_MJ_MAXIMUM_FUNCTION         0x1b

Note que existe uma definição especial, a IRP_MJ_MAXIMUM_FUNCTION, que indica o índice máximo da tabela de dispatch rotines. Utilizaremos um loop simples para fazer com que todas rotinas em nossa tabela aponte para uma única rotina que daremos o nome de OnForwardDispatch.

    //-f--> Seta todas as dispatch routines do driver para
    //      uma que encaminhe a IRP para o driver original.
    for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
        pDriverObj->MajorFunction[i] = OnForwardDispatch;

Veremos a implementação dessa rotina mais tarde. O próximo passo que daremos aqui é localizar o device ao qual vamos nos atachar. Para fazer isso, usaremos a rotina IoGetDeviceObjectPointer(). Ela basicamente recebe o nome do device e nos retorna uma referência para ele.

NTSTATUS 
  IoGetDeviceObjectPointer(
    IN PUNICODE_STRING  ObjectName,
    IN ACCESS_MASK  DesiredAccess,
    OUT PFILE_OBJECT  *FileObject,
    OUT PDEVICE_OBJECT  *DeviceObject
    );

Observe que são dois os parâmetros de saída dessa rotina. Além do ponteiro para o device object ainda recebemos um ponteiro para um file object. Já ví algumas pessoas fazerem confusão com estes dois parâmetros, então vou dar alguma ênfase nisso.

O ponteiro para o file object representa uma conexão criada entre seu driver e o device que você abriu. Como vimos neste post, um file object é criado para respresentar conexões entre aplicações user mode e seu driver. Aplicações usam esse file object através do handle obtido na chamada à rotina CreateFile(). Aqui temos algo similar, mas apenas o Kernel foi envolvido. Isso significa que se você quizesse, você poderia lançar IRPs para o device solicitando operações como uma aplicação faria, mas não veremos isso hoje, ainda temos um filtro para terminar.

A grande confusão referente a estes dois parâmetros é com relação às referências entre os objetos. Na documentação vemos que o chamador desta rotina deverá liberar a referência que ganhou quando o device não for mais utilizado. Fazemos isso simplesmente usando a rotina ObDereferenceObject().

VOID 
  ObDereferenceObject(
    IN PVOID  Object
    );

"Já sei! Como estamos obtendo uma referência para um device object, então devo passar o ponteiro do device object. Certo?"

Er... Na verdade não. O file object é uma referência indireta ao device object. Conforme a figura abaixo, se imaginarmos que os contadores de referência apenas dizem respeito às nossas referências, quando chamarmos ObDereferenceObject() para o file object, seu contador de referência cairia para zero e uma nova chamada para ObDeferenceObject() seria feita indiretamente para o device object, fazendo com que seu contador de referência também caisse para zero destruindo o objeto.

Depois de obter o ponteiro para o device destino, teremos que criar nosso próprio device, o qual receberá as IRPs no lugar do device original. Para isso ainda usaremos a rotina IoCreateDevice() como faziamos com drivers, mas com algumas diferenças.

A primeira diferença é que seu device normalmente não tem nome. É possível criar filtros com nomes, mas isso pode gerar uma falha de segurança. Isso acontece pois quando um nome é consultado no Object Manager, suas regras de segurança são avaliadas. Quando criamos um filtro com nome, criamos a possibilidade de o mesmo objeto ser obtido por um nome diferente e que pode ter regras menos restritivas de segurança. Mas esse é outro assunto.

Quando criamos um device, precisamos informar o tamanho do device extension.

"O que vem a ser um device extension?"

Device extension é simplesmente um espaço de memória que está associada ao device object. Tal espaço normalmente mantém informações que dizem respeito ao device. O endereço do device ao qual estamos atachados normalmente fica no device extension. Desta forma, podemos definir que nosso device extension deve conter a seguinte estrutura.

//-f--> Nosso device externsion conterá apenas o endereço
//      do device ao qual estamos atachados.
typedef struct _DEVICE_EXTENSION
{
    PDEVICE_OBJECT  pNextDeviceObj;
 
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

Depois de criar nosso device object, configuramos o método de I/O copiando os bits DO_BUFFERED_IO e DO_DIRECT_IO. Se você não se lembra destes bits, dê uma olhada neste post. O filtro deve propagar a escolha do driver original e o driver tem o compromisso de não mudar método durante seu tempo de vida.

Agora estamos prontos para nos atachar ao device escolhido, e faremos isso utilizando a rotina IoAttachDeviceToDeviceStackSafe() para fazer nosso device entrar na pilha de dispositivos.

NTSTATUS
  IoAttachDeviceToDeviceStackSafe(
    IN PDEVICE_OBJECT  SourceDevice,
    IN PDEVICE_OBJECT  TargetDevice,
    IN OUT PDEVICE_OBJECT  *AttachedToDeviceObject 
    );

Com esta chamada obteremos o ponteiro do device ao qual estaremos atachados, esse device será o próximo device que receberá a IRP depois que você a passar a diante.

"Mas Fernando, nós já não temos o ponteiro do device de destino?"

Muito bem, quando você obtém o ponteiro para um device, você teoricamente não sabe se existem filtros já atachados sobre ele. O ponteiro que você recebe nesta rotina pode não ser o ponteiro para o device de destino. Na figura abaixo podemos entender como essa relação acontece.

"Fernando, existe uma versão unsafe desta rotina?"

Na verdade existe, a IoAttachDeviceToDeviceStack().

PDEVICE_OBJECT 
  IoAttachDeviceToDeviceStack(
    IN PDEVICE_OBJECT  SourceDevice,
    IN PDEVICE_OBJECT  TargetDevice
    );

Ela é considerada unsafe por causa de uma pequena janela de tempo que pode causar um race condition. Repare que a diferença entre estas rotinas é a forma de obter o endereço do próximo device. Na versão original, este endereço é obtido pelo retorno na função. Se colocarmos essa função para rodar em câmera lenta veremos os seguintes passos.


  1. Seu device é atachado à pilha de dispositivos.
  2. O endereço do próximo device é retornado pela função.
  3. Seu driver recebe este endereço no retorno da função e atualiza o device extension.
  4. IRPs começam a chegar e seu driver às encaminham para o próximo device da lista.


Tudo parece lindo e até dá a impressão de que tudo vai funcionar muito bem em qualquer situação, mas desenvolvedor de driver é um bixo treinado para buscar race conditions. Dê mais uma olhada na sequência, mas agora em câmera super lenta. Com essa câmera super lenta agora podemos observar os passos que podem ocorrer entre os passos 2 e 3.


  1. Seu device é atachado à lista de dispositivos.
  2. O endereço do próximo device é retornado pela função.
    1. Sua thread é interrompida e uma IRP lançada por uma aplicação que roda em paralelo começa sua viagem por essa pilha de dispositivos.
    2. Seu device, que já está atachado, recebe a IRP e tenta encaminhá-la ao device de baixo.
    3. Oops! Nosso device extension ainda não foi atualizado com tal endereço.
    4. Seu driver se lembra de quando era uma criança e de tudo o que vivera até ali.
    5. Ele decide entrar de vez naquela dança e enviar a IRP para um device cujo ponteiro ainda é NULL causando um BSOD.
  3. "Jeremias, eu sou homem. Coisa que você não é e não atiro pelas costas não..."


Enfim, entenda que mesmo que você utilize o retorno da rotina IoAttachDeviceToDeviceStack() diretamente para atualizar seu device extension, ainda assim existe uma janela de tempo em que seu device estará atachado, mas que o valor ainda não foi atualizado no device extension. Isso porque o valor de retorno de uma rotina vem por um registrador. Pegar o valor desse registrador e atualizar uma variável ainda dá chances para o azar.

    //-f--> ----==== NÃO COPIE ISSO ====----
    //      Aqui ainda temos uma janela de tempo entre o device ser
    //      atachado e o valor de pNextDeviceObj ser atualizado.
    pDeviceExt->pNextDeviceObj = IoAttachDeviceToDeviceStack(pMyDeviceObj,
                                                             pTargetDeviceObj);

A rotina IoAttachDeviceToDeviceStackSafe() faz o sistema interromper o fluxo de IRPs nesta pilha até que a variável apontada pelo ponteiro de saída seja atualizado. Por essa razão, o endereço passado nesta rotina deve ser o endereço final da variável onde este valor será armazenado, que em nosso caso é pDeviceExt->pNextDeviceObj sem passar por variáveis intermediárias.

Esses detalhes são importantes e farão você entender que usar a versão safe desta rotina não é garantia de que tudo dará certo. Imagine que usando a versão safe você receba o endereço do próximo device em uma variável local e depois atualize seu device extension. Esse é um daqueles típicos casos que é necessário substituir aquele componente que fica entre o teclado e a cadeira.

Acha preciosismo? Tente ler o capítulo 5 do livro "Programming the Microsoft Windows Driver Model" onde Walter Oney fala sobre como lidar com cancelamento de IRPs.

Depois de atacharmos nosso device já podemos liberar a referência obtida por IoGetDeviceObjectPointer() utilizando a rotina ObDereferenceObject(), já que a rotina IoAttachDeviceToDeviceStackSafe() já garantiu a referência até que essa ligação seja desfeita.

Muito bem. Pra quem não conseguiu entender quase nada do que eu disse, segue o código fonte da implementação da nossa DriverEntry() de exemplo. Sabe como é cabeça de programador, as vezes um if vale mais que mil páginas de explicação.

/****
***     DriverEntry
**
**      Ponto de entrada do driver.
**      Bem vindo ao inferno.
*/
extern "C"
NTSTATUS
DriverEntry(IN PDRIVER_OBJECT  pDriverObj,
            IN PUNICODE_STRING pusRegistryPath)
{
    NTSTATUS            nts;
    PDEVICE_OBJECT      pMyDeviceObj;
    int                 i;
    UNICODE_STRING      usDeviceName;
    PDEVICE_OBJECT      pTargetDeviceObj;
    PFILE_OBJECT        pFileObj;
    PDEVICE_EXTENSION   pDeviceExt;
 
    //-f--> Seta a rotina de descarga do driver.
    pDriverObj->DriverUnload = OnDriverUnload;
 
    //-f--> Seta todas as dispatch routines do driver para
    //      uma que encaminhe a IRP para o driver original.
    for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
        pDriverObj->MajorFunction[i] = OnForwardDispatch;
 
    //-f--> Inicializamos a string com o nome do device ao
    //      qual queremos nos atachar.
    RtlInitUnicodeString(&usDeviceName,
                         L"\\Device\\StringList");
 
    //-f--> Obtemos o ponteito do device de destino
    nts = IoGetDeviceObjectPointer(&usDeviceName,
                                   FILE_READ_DATA,
                                   &pFileObj,
                                   &pTargetDeviceObj);
    if (!NT_SUCCESS(nts))
        return nts;
 
    //-f--> Criamos nosso device object
    nts = IoCreateDevice(pDriverObj,
                         sizeof(DEVICE_EXTENSION),
                         NULL,
                         pTargetDeviceObj->DeviceType,
                         pTargetDeviceObj->Characteristics,
                         FALSE,
                         &pMyDeviceObj);
    if (!NT_SUCCESS(nts))
    {
        //-f--> Oops!
        ObDereferenceObject(pFileObj);
        return nts;
    }
 
    //-f--> Obtem nosso DEVICE_EXTENSION
    pDeviceExt = (PDEVICE_EXTENSION)pMyDeviceObj->DeviceExtension;
 
    //-f--> Utiliza o mesmo método de IO do driver original
    pMyDeviceObj->Flags |= pTargetDeviceObj->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO);
 
    //-f--> Aqui nosso driver entra na pilha de dispositivos
    nts = IoAttachDeviceToDeviceStackSafe(pMyDeviceObj,
                                          pTargetDeviceObj,
                                          &pDeviceExt->pNextDeviceObj);
    if (!NT_SUCCESS(nts))
    {
        //-f--> Oops!
        IoDeleteDevice(pMyDeviceObj);
        //-f--> Aqui não está faltando um return.
    }
 
    //-f--> Independente de estarmos atachados ou não, devemos liberar
    //      a referência obtida do device de destino.
    ObDereferenceObject(pFileObj);
    return nts;
}

Escrevendo a OnDriverUnload

Aqui é onde a festa acaba, antes de o nosso driver ser descarregado pelo sistema, teremos que remover nosso device da pilha de dispositivos e destruí-lo. (Risadas maléficas)

O código aqui é simples e não requer explicações se você for capaz de ler os comentários contidos nele.

/****
***     OnDriverUnload
**
**      Rotina de descarga do driver.
*/
VOID
OnDriverUnload(IN PDRIVER_OBJECT  pDriverObj)
{
    PDEVICE_OBJECT      pMyDeviceObj;
    PDEVICE_EXTENSION   pDeviceExt;
 
    //-f--> Nosso device está na lista de devices criados por este driver
    //      então vamos obtê-lo simples assim:
    pMyDeviceObj = pDriverObj->DeviceObject;
 
    //-f--> Aqui obtêmos o device extension.
    pDeviceExt = (PDEVICE_EXTENSION)pMyDeviceObj->DeviceExtension;
 
    //-f--> Aqui removemos nosso device da pilha passando o endereço do
    //      próximo device para a rotina abaixo.
    IoDetachDevice(pDeviceExt->pNextDeviceObj);
 
    //-f--> Agora podemos destruir nosso device (risadas maléficas)
    IoDeleteDevice(pMyDeviceObj);
}

"Fernando, nosso driver é forçado a se descarregar quando o driver original é descarregado?"

Essa é a filosofia do WDM, drivers são carregados automáticamente quandos seus dispositivos são detectados e descarregados quando seus dispositivos são desativados ou removidos. Os filtros seguem as mesmas regras e são carregados/descarregados basendo-se nestes eventos.

Mas não é isso o que acontece aqui. Os drivers de exemplo que uso neste blog são do modelo Legacy, e não WDM. No modelo Legacy, drivers são iniciados seguindo sua ordem de carga no registry, e não tem nada a ver com a detecção do seu dispositivo. Os filtros precisam iniciar depois dos drivers originais, e isso também é controlado pela sua ordem de carga. Este post fala sobre a ordem de carga dos legacy drivers.

"Tá! Falou, falou e não respondeu minha pergunta. O que acontece se eu solicitar a descarga do driver original enquanto houver um filtro atachado sobre ele?"

A descarga dos drivers que formam uma pilha deve ocorrer de forma inversa à sua carga. Neste caso o filtro deve ser descarregado antes do driver original, desempilhando os devices de cima para baixo. Caso o driver original receba uma solicitação de descarga enquanto ainda hoverem referências a seus devices, seja por um filtro ou por uma aplicação, a descarga é adiada até que suas referências sejam desfeitas. Até lá, a tentativa de obter novas referências para um device que recebeu a solicitação de descarga serão negadas.

Escrevendo Dispatch Routines

As dispatch routines de um filtro também são diferentes das dispatch routines de um driver. Apensar de elas poderem completar uma IRP chamando IoCompleteRequest(), elas normalmente repassam a socilitação adiante utilizando a rotina IoCallDriver(). Vou falar mais sobre o comportamento das dispatch routines de um filtro em posts futuros. Neste filtro de exemplo vamos apenas logar a atividade e repassar a solicitação ao próximo driver.

Quando falamos em repassar uma solicitação estamos na verdade falando de repassar IRPs. A leitura deste outro post é essencial para o que vamos fazer na implementação de nossas dispatch routines.

Para acabar esse post ainda nessa vida, segue código da implementação de nossa dispatch routine. Depois de ler o post sobre IRPs que acabei de recomendar, ler os comentários deste código devem ser suficientes para entender tudo o que foi feito aqui, ou não.

/****
***     OnForwardDispatch
**
**      Nossa dispatch routine simplesmente loga a IRP
**      recebida e repassa a solicitação adiante.
**
*/
NTSTATUS
OnForwardDispatch(IN PDEVICE_OBJECT    pDeviceObj,
                  IN PIRP              pIrp)
{
    PDEVICE_EXTENSION   pDeviceExt;
    PIO_STACK_LOCATION  pStack;
 
    //-f--> Obtém ponteiro para o device extesion
    pDeviceExt = (PDEVICE_EXTENSION)pDeviceObj->DeviceExtension;
 
    //-f--> Obtém stack location corrente
    pStack = IoGetCurrentIrpStackLocation(pIrp);
 
    //-f--> Manda o nome na major routine da IRP
    ASSERT(pStack->MajorFunction <= IRP_MJ_MAXIMUM_FUNCTION);
    DbgPrint("[StringFilter] : %s\n", g_szMajorNames[pStack->MajorFunction]);
 
    //-f--> Como não estamos modificando nada na stack location,
    //      vamos deixá-la para que o próximo device a use.
    IoSkipCurrentIrpStackLocation(pIrp);
 
    //-f--> Encaminha a IRP para o próximo device.
    return IoCallDriver(pDeviceExt->pNextDeviceObj,
                        pIrp);
}

Caso não tenham entendido nada, não esqueçam de me mandar um e-mail expondo suas dúvidas (sem ofenças pessoais). Isso me ajudará a entender suas dificuldades e a melhorar minhas explicações.

Testando o Filtro

Essa é a parte fácil do post. Para testar o filtro teremos primeiro que compilar, instalar e iniciar o driver deste post. Se você ainda não sabe como fazer isso, este outro post pode te dar um ponto de partida. Depois disso, compile, instale e inicie o filtro.

Depois de instalados, podemos utilizar a aplicação de teste para exercitar o driver. Poderemos acompanhar a atividade do filtro observando suas mensagens de debug que podem ser vistas no depurador de Kernel ou simplemente usando esta aplicação, que dispensa o uso de um depurador para ver as mensagens de debug de um driver.

"Fernando, fiz um teste aqui e ví que ao iniciar o filtro ele loga um evento de IRP_MJ_CLOSE mesmo antes de iniciar a aplicação de teste. O que eu fiz de errado?"

Não há nada de errado. Isso acontece por causa da sequência de passos seguidos na rotina DriverEntry() do filtro. Inicialmente o driver chama a rotina IoGetDeviceObjectPointer(), isso faz com que o IoManager envie uma solicitação de IRP_MJ_CREATE para o driver original. Depois disso nos atachamos à pilha de dispositivos e por fim chamamos a rotina ObDereferenceObject() que vai finalizar a única referência do file object que recebemos, enviando uma solicitação de IRP_MJ_CLOSE para o driver de baixo. Como já estamos atachados a ele, então somos capazes de ver nossa própria atividade sobre o driver original. Isso pode ser observado pela pilha de chamadas que teremos se houver um breakpoint em nossa dispatch routine quando liberarmos a referência do file object ao final da DriverEntry().

kd> k
ChildEBP RetAddr  
f8af9bcc 804ee129 StringFilter!OnForwardDispatch
f8af9bdc 80578f6a nt!IopfCallDriver+0x31
f8af9c14 805b0b18 nt!IopDeleteFile+0x132
f8af9c30 80522bd1 nt!ObpRemoveObjectRoutine+0xe0
f8af9c54 f8c80663 nt!ObfDereferenceObject+0x5f
f8af9c7c 805767ff StringFilter!DriverEntry+0xf3
f8af9d4c 8057690f nt!IopLoadDriver+0x66d
f8af9d74 80534c12 nt!IopLoadUnloadDriver+0x45
f8af9dac 805c61ee nt!ExpWorkerThread+0x100
f8af9ddc 80541de2 nt!PspSystemThreadStartup+0x34
00000000 00000000 nt!KiThreadStartup+0x16

Como de costume, o fonte do filtro que foi implementado neste post está disponível para download. Nosso filtro não faz quase nada, mas já servirá de base para posts futuros que darão mais funcionalidade a ele explicando como tais funcionalidades são implementadas.

Até mais!

StringFilter.zip

5 Responses to “Escrevendo Filtros”

  1. Leonardo says:

    Olá Fernando
    Parabéns!!! mais um brilhante post para os desenvolvedores de drivers. Os filters drivers são capazes de adicionar muitas funções aos drivers já instalados no sistema. Acredito que todos os softwares antí-virus utilizam um mecanismo ao nível de kernel(filtro de driver), para identificar a assinatura e detectar a presença de algum código malicioso que queira escrever no disco rígido(me corrija se estiver errado). Já li um texto, onde o autor “atachou” um filter no driver de teclado para logar e obter o código que estava sendo digitado, eu tenho fonte deste projeto se quiser posso dispor para ilustrar as capacidades de um filtro.

    Um abraço

    • Olá Leonardo,

      Obrigado pelos comentários. Concordo que filtros de mouse e teclado são fáceis de implementar e que demonstram com facilidade as capacidades de um filtro, porém tenho evitado colocar os fontes que fazem tais coisas justamente para prevenir que alguém pegue carona neste código fonte para desenvolver algo malicioso.

      Já trabalhei desenvolvendo ferramentas de segurança para evitar mouse logers e keyboard logs no Windows e sei bem como essa turma aproveita exemplo alheio pra criar programinhas maliciosos.

      Obrigado assim mesmo, um abraço.

  2. Fabio says:

    Muito bom o post, já terminei o Windows Internals mas ainda tenho pelo menos três livros enormes pra ler sobre drivers :

    The Windows 2000 Device Driver Book, A Guide for Programmers, Second Edition

    Windows NT File System Internals – Developer’s Guide

    Programming The Windows Driver Model 2nd Edition

    Minha situação é a seguinte: comecei e estudar c++ a dois meses, passei por dois livros de c++ e pelo Windows Internals, acho que eu deveria ler mais sobre C nu e cru agora não? Hehe
    Valeu

  3. Fausto Vaz says:

    Olá Fernando,
    muito interessante essa sério de posts que vc está escrevendo.
    Estou gostando muito e me aventurando nesse mundo dos drives.
    Sou desenvolvedor java mas sou fascinado por esse mundo mais baixo nível.
    Sobre evitar postar códigos com mais exemplos de filtros acho isso ruim, pois o objetivo aqui é o estudo e o aprendizado.
    Vejo que seria muito interessante disceminar esse conhecimento.

    Abraços

  4. "Brunildz" Bruno Moreira Guedes says:

    Cara, tu me respondestes um tópico sobre o tema na lista ccppbrasil, o meu sócio continuou trocando os e-mails contigo por mim agora.

    Mas fiquei curiosi, achei teus gostos sobre “que tipo de software desenvolver” parecidos com os meus, com a pequena diferença que eu sempre desenvolvi para sistemas POSIX(Linux, BSD, etc), e tu te focou no Windows.

    Mas bacana cara, fico contente de saber que não sou o único que gosto de grandes desafios!!

Deixe um comentário