1120 Alameda Orquidea, Atibaia, SP,  BRA

fernando@driverentry.com.br

De quem é essa IRP? (Process ID)

Existem casos onde é necessário saber qual processo lançou determinada IRP. Isso é muito comum em Firewalls ou em outros programas de segurança, que interceptam operações de I/O para verificar em suas bases de dados se determinado processo tem ou não acesso a um determinado recurso ou serviço. Mas como posso saber qual é o processo dono daquela IRP?

Bom, eu começo pensando que é muito fácil fazer isso. Como sabemos, o IoManager nos entrega as IRPs no contexto do processo que fez a requisição. Assim, conhecendo a API PsGetCurrentProcessId, podemos obter o ID do processo que lançou a IRP. Veja como é simples:

/****
***     OnDispatchProc
**
**      Ô nominho genérico sem vergonha...
*/
NTSTATUS OnDispatchProc(PDEVICE_OBJECT pDeviceObject,
                        PIRP           pIrp)
{
    //-f--> Estou pensando que é fácil, não copiem isso.
    HANDLE hProcessID;
 
    //-f--> Obtém o ID do processo corrente.
    hProcessID = PsGetCurrentProcessId();
 
    ...
}

Viu como é simples pensar que está tudo certo e estar redondamente enganado?

De fato, o IoManager entrega as IRPs no contexto do processo que está fazendo o I/O. Entretanto, o que aconteceria se um filtro de terceiro se atachar ao seu driver? Sim, isso é possível, e uma vez que tais drivers receberem estas IRPs, não é garantido que eles as repassarão para o nosso driver ainda no mesmo contexto. Vamos supor que escrevemos o driver de um dispositivo:

  • Uma aplicação solicita a escrita no dispositivo.
  • IoManager cria e envia a IRP para o nosso driver.
  • Um filtro atachado ao nosso device recebe a IRP.
  • O filtro realiza uma consulta assíncrona, possivelmente utilizando ExQueueWorkItem.
  • Enquanto a consulta não termina, o filtro marca a IRP como pendente e retorna STATUS_PENDING.
  • A operação assíncrona, neste caso, é realizada por uma thread de sistema, e ao final da consulta, o filtro encaminha a IRP para o driver abaixo dele, que no caso é o nosso driver.
  • Nosso driver então recebe a IRP no contexto de sistema e não no contexto do processo que originou a IRP.

Quando o filtro mantém a IRP pendente e retorna da função de Dispatch, a thread que originou a IRP segue em frente e vai realizar outras tarefas. A thread original pode ainda retornar ao processo que iniciou toda esta operação, caso estejam sendo utilizadas as estruturas OVERLAPPED nas chamadas ao driver.

A IRP agora será executada no contexto do processo que chamar IoCallDriver passando como parâmetro esta IRP que ficou pendente. Em nosso exemplo, o processo que vai fazer isso é o System. Utilizando o código acima, obteremos o PID do processo System no lugar do PID do processo que iniciou a IRP.

Para corretamente obter a informação que estamos procurando, teremos que dar uma volta um pouco maior. Toda IRP, quando é criada, entra na lista de IRPs pendentes da thread que a criou. Para obter esta thread, utilizamos o campo pIrp->Tail.Overlay.Thread. Este campo possui um ponteiro para a estrutura ETHREAD da thread que criou esta IRP. Para chegar ao processo a partir da thread, utilizamos a API IoThreadToProcess. Veja o trecho abaixo.

/****
***     OnDispatchProc
**
**      Ô nominho genérico sem vergonha...
*/
NTSTATUS OnDispatchProc(PDEVICE_OBJECT pDeviceObject,
                        PIRP           pIrp)
{
    PEPROCESS   pEProcess;
    PETHREAD    pEThread;
 
    //-f--> Aqui obtemos o ponteiro da thread que criou esta
    //      IRP.
    pEThread = pIrp->Tail.Overlay.Thread;
 
 
    //-f--> Agora obtemos o processo ao qual esta thread
    //      percente.
    pEProcess = IoThreadToProcess(pEThread);
 
    ...
}

Pronto, vejam que maravilha. Agora você tem em suas mãos a estrutura EPROCESS, que segundo a documentação da Microsoft, é uma estrutura opaca utilizada internamente pelo sistema operacional.

“The EPROCESS structure is an opaque data structure used internally by the operating system.”

E o que eu faço com isso agora?

Embora eu tenha certeza que você já pensou em algo para eu fazer com esta estrutura, eu tenho uma sugestão bem melhor, e por que não dizer, bem mais apropriada. Mesmo porque podem ter crianças lendo isso. Apesar do EPROCESS não significar nada, ele ainda pode nos trazer alguma informação útil. Podemos obter o handle do processo identificado por essa estrutura e assim obter outras informações deste processo, tal como seu PID. Veja o exemplo abaixo.

//-f--> ZwQueryInformationProcess from
//      Windows NT/2000 Native API Reference
//      ISBN-10: 1578701996 
//      ISBN-13: 978-1578701995
 
NTSTATUS
NTAPI
ZwQueryInformationProcess
(
    IN  HANDLE            ProcessHandle,
    IN  PROCESSINFOCLASS  ProcessInformationClass,
    OUT PVOID             ProcessInformation,
    IN  ULONG             ProcessInformationLength,
    OUT PULONG            ReturnLength OPTIONAL
);
 
 
/****
***     MyGetProcessID
**
**      Obtém o ID de um processo a partir do seu EPROCESS
**      Por favor, usem sua criatividade e dêem um nome melhor
**      para esta função.
*/
 
NTSTATUS
MyGetProcessID(IN  PEPROCESS    pEProcess,
               OUT PHANDLE      phProcessId)
{
    NTSTATUS                    nts = STATUS_SUCCESS;
    HANDLE                      hProcess = NULL;
    PROCESS_BASIC_INFORMATION   ProcessInfo;
    ULONG                       ulSize;
 
    //-f--> Funções Zw são normalmente chamadas
    //      do User Mode, assim para chamá-las
    //      do Kernel, precisaremos no mínimo
    //      estar rodando em PASSIVE_LEVEL.
    ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
 
    __try
    {
        //-f--> Inicializa o parâmetro de saída
        *phProcessId = 0;
 
        //-f--> Obtemos um handle para o processo
        //      identificado pela estrutura EPROCESS
        nts = ObOpenObjectByPointer(pEProcess,
                                    OBJ_KERNEL_HANDLE,
                                    NULL,
                                    0,
                                    NULL,
                                    KernelMode,
                                    &hProcess);
        if (!NT_SUCCESS(nts))
        {
            ASSERT(FALSE);
            ExRaiseStatus(nts);
        }
 
        //-f--> Para utilizar esta API não documentada, basta
        //      declarar seu protótipo como é feito no início
        //      deste exemplo.
        nts = ZwQueryInformationProcess(hProcess,
                                        ProcessBasicInformation,
                                        &ProcessInfo,
                                        sizeof(ProcessInfo),
                                        &ulSize);
        if (NT_SUCCESS(nts))
        {
            ASSERT(FALSE);
            ExRaiseStatus(nts);
        }
 
        //-f--> Todos vivos até aqui, agora basta alimentar o
        //      parâmetro de saída com o que nos interessa
        *phProcessId = (HANDLE)ProcessInfo.UniqueProcessId;
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        //-f--> Ops... Deu Mer(pii)
        nts = GetExceptionCode();
    }
 
    //-f--> Libera o handle do processo que obtivemos.
    //      Desta forma, seu gerente não fica te olhando
    //      torto quando, apesar dos processos terminarem,
    //      ainda houver acúmulo de estruturas em RAM.
    if (hProcess)
        ZwClose(hProcess);
 
    //-f--> E todos viveram felizes para sempre.
    //      (inclusive o seu gerente)
    return nts;
}

É possível obter inúmeras informações a partir do handle do processo. Existem até meios de obter o Path completo do processo a partir do seu handle, mas vamos deixar essa brincadeira para um próximo post.

Até mais… 🙂

Leave a Reply

Your email address will not be published.