Legal, mas o que é uma IRP?

12 de February de 2007 - Fernando Roberto

Meu amigo Lesma, assim que leu o título do meu último post, deu uma risada e disse que a maioria das pessoas que lessem isso perguntariam: “O que é uma IRP?”. Bom, pensando no que ele disse e levando em consideração a explicação nada simplificada que a referência do DDK nos oferece, vou dar uma descrição superficial da IRP, livrar minha consciência deste peso e poder dormir novamente.


Vamos deixar de lado os assustadores e horripilantes diagramas do DDK para tentar ver as coisas de uma maneira um pouco mais simples. Depois disso, você poderá recorrer à documentação sagrada para reforçar os conceitos e tirar eventuais dúvidas sobre algo que você já saiba o que é, ou que pelo menos imagina.

Vamos nos basear em um exemplo bem prático. Os drivers de File Systems por exemplo. O NTFS e o FAT são implementados como drivers de Kernel que recebem o nome File Systems Drivers. Imagino que muitos de vocês já tiveram a oportunidade de abrir e escrever em um arquivo.

Para começar a conversar com um driver, temos inicialmente que criar uma conexão com ele, e isso é feito utilizando a função CreateFile. Do contrário que parece, essa função não é restrita à criação de arquivos, na verdade, o fato de abrir um arquivo é uma maneira específica de se abrir a conexão com o driver de File System (Detalhes sobre isso em uma próxima vez). A função CreateFile vai nos retornar um handle que será utilizado para interagir com o driver através de funções tais como ReadFile, WriteFile e DeviceIoControl, que por sinal, também não são restritas à operações com arquivos. Quando uma aplicação chama a função ReadFile o subsystema Win32 encaminha esta solicitação para a API nativa NtReadFile, que por sua vez faz a transição para Kernel Mode e chama o IoManager, este vai empacotar os parâmetros desta solicicação em uma estrutura chamada IRP (I/O Request Packet).

O IoManager envia este pacote para o driver responsável passando pelos filtros que houverem instalados sobre ele. Um driver de anti-vírus é um exemplo perfeito para o senário que estamos falando aqui. Drivers de anti-vírus são implementados como File System Filters. Quando alguma aplicação escreve em um arquivo, a IRP de escrita passa pelo anti-vírus antes de chegar ao driver de File System, dando a oportunidade que o anti-vírus precisa para verificar se o que está sendo gravado contém a assinatura de algum vírus conhecido. No caso de uma leitura, a IRP passa pelo anti-vírus que instala uma CompletionRoutine nela, e desta forma ganha acesso aos dados quando a leitura for finalizada pelo driver final.

A IRP é basicamente dividida em duas partes, sendo elas o Header e as Stack Locations. No Header temos informações gerais da IRP, tais como status, ponteiro para a thread à qual esta IRP pertence, endereços dos buffers do usuário, endereço da rotina de cancelamento desta IRP e por aí vai. Nas Stack Locations estão os parâmetros específicos da solicitação. No caso de uma leitura de arquivo, teremos o offset, o tamanho da leitura e o alinhamento.

Dentro de uma IRP podem haver várias Stack Locations. Uma para cada device pertencente à cadeia de camadas que segue até chegar ao driver destino. Em outras palavras, seria uma Stack Location para o driver destino mais uma para cada filtro que estiver instalado sobre ele. A medida que a IRP vai descendo as camadas, cada driver repassa os parâmetros recebidos em sua Stack Location para a próxima, utilizando a função IoCopyCurrentIrpStackLocationToNext. Depois de copiados, esta camada pode fazer as alterações desejadas. Se não houver nenhuma alteração a ser feita nos parâmetros de uma camada para a outra, então pode-se utilizar a função IoSkipCurrentIrpStackLocation.

As IRPs vão de uma camada para outra quando o filtro que a recebeu a encaminha para o device o qual está atachado, utilizando a função IoCallDriver. Quando esta IRP chega ao driver destino, o driver tem basicamente duas opções. Se a solicitação puder ser atendida imediatamente, o driver toma a ação desejada e finaliza a IRP utilizando a função IoCompleteRequest. Mas se for necessário comunicar com um dispositivo ou mesmo com outro driver, a IRP é posta em uma fila, marcada como pendente e só será finalizada quando todo processamento for feito.

Voltando ao nosso exemplo, quando um driver de File System recebe uma IRP de escrita e dependendo de outras tantas condições, este driver coloca a IRP atual como pendente e cria uma nova IRP para os devices de Volume, onde estão as partições, que por sua vêz repassam solicitações para os devices de Storage. Desta forma fica fácil entender que drivers de disco não entendem nada de NTFS ou FAT. Drivers de storage podem possuir outros filtros de storage, como um RAID para espelhamento de discos entre outras coisas. Estas novas IRPs também são criadas pelo IoManager e todo o ciclo é refeito para cada nova solicitação.

Visualizando IRPs

O IrpTracker é uma ferramenta capaz de monitorar a atividade de IRPs de um determinado driver ou device. Veja a figura abaixo onde estou selecionando todo os devices do driver Kbdclass, responsável pela leitura de teclado. Não repare, minha máquina tem mesmo muitos teclados. Estive trabalhando em soluções anti key loggers atualmente. Selecionando todos os estes devices ficará bem visível o uso das IRPs para buscar as teclas que você pressiona.


Para cada tecla que você bate no teclado, são emitidas quatro linhas no IrpTracker. Sendo que cada tecla batida representa dois movimentos, o de descida da tecla e o de liberação da mesma. Cada movimento é tratado com uma IRP que vem acompanhada de sua Completion, ou seja, uma linha é identificada como Call e outra como Comp, que representam respectivamente o envio da IRP e seu retorno. Reparem que neste exemplo, a linha Comp sempre vem antes da linha Call.

Isso significa que a IRP teve sua completion antes de ser enviada para o driver do teclado?

Na verdade, a IRP de leitura de teclado é do tipo que fica pendente até que uma tecla seja recebida. Assim, o sistema envia uma IRP para o driver que fica aguardando eventos do teclado. Quando este evento acontece, o driver recebe a tecla, completa a IRP e o sistema recebe sua completion. Logo em seguida o sistema lança uma nova IRP que aguardará o próximo evento. Desta forma teremos sempre pares linhas, sendo elas a Comp da IRP anterior e a Call da próxima IRP.

Se você der um duplo clique em uma destas linhas, será possível ver os detalhes da cada campo da IRP como é exibido na figura abaixo.


Estou preparando uma evolução do driver Useless.sys citado como ponto de partida em um post anterior. Essa evolução vai permitir que ele não seja tão Useless e que exemplifique o tratamento de IRPs na prática. Mas isso vai ficar para uma outra vez.

Até lá…

Deixe um comentário