De quem é essa IRP? (Process ID)
5 de February de 2007 - Fernando RobertoExistem 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…