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.
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.
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().
"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.
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.
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.
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.
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.
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.