O que esperar de uma chamada de demonstração com a Safetica
Está a considerar a solução de data loss prevention (DLP) e insider risk management (IRM) da Safetica e gostaria de saber mais sobre como o nosso produto se ...
Um dos nossos clientes reportou recentemente um problema ao executar o nosso software em conjunto com o VMware vShield. Embora a solução se tenha revelado bastante simples, o processo de investigação merece um artigo de blogue, uma vez que revelou alguns factos interessantes relacionados com o comportamento dos drivers no momento do arranque.
A ideia principal por detrás do VMware vShield é fornecer serviços de antivírus a máquinas virtuais sem instalar uma solução completa de antivírus nos sistemas operativos convidados. Os dados de entrada das máquinas virtuais são obtidos a partir de um pequeno driver chamado vsepflt.sys e designado vShield Endpoint Thin Agent. O driver regista-se como um minifilter de sistema de ficheiros e depois comunica com a solução de antivírus no sistema anfitrião. Infelizmente, quando o nosso software estava a ser executado dentro de uma máquina virtual, o driver vsepflt.sys recusava-se misteriosamente a iniciar no momento do arranque, embora funcionasse bem quando carregado manualmente.
Achámos que a solução de carregamento manual não era suficientemente boa, porque, embora provavelmente funcionasse bem na prática, podia indicar algo desagradável dentro do nosso software. Tudo o que sabíamos era que a inicialização do driver vsepflt.sys falhava com o código de erro 0x80000011 (STATUS_DEVICE_BUSY).
Como não havia indicação direta nem indireta de onde poderia estar o problema no nosso software e não era facilmente possível examinar as condições exatas na máquina do cliente durante o arranque do sistema, decidi analisar um pouco o driver vsepflt.sys. O driver (pelo menos as suas rotinas de inicialização) revelou-se bastante bem escrito — todos os caminhos de erro na sua função DriverEntry (a rotina responsável pela inicialização do driver) pareciam produzir uma impressão de debug a indicar o que provavelmente correu mal. Os nomes das rotinas eram normalmente incluídos nas mensagens de debug, o que tornava o código mais legível. Além disso, as rotinas responsáveis pelas impressões de debug sugeriam que as mensagens não são apenas enviadas para o depurador, mas também para outro local fora da máquina virtual onde o cliente as pode recolher e fornecer-nos ( ativar o registo de debug para o driver thin agent VMware Tools vShield Endpoint ). Passado algum tempo isto realmente aconteceu e, depois de instruir o VMware a produzir registos mais detalhados, foi descoberta a seguinte mensagem de debug:
Listagem 1: Mensagens de debug do vsepflt.sys
DEBUG: VFileUmcReadParams : umcMsgTimeoutMs: 2000
DEBUG: VFileUmcReadParams : umcMsgTimeout100Ns: -20000000
INFO: DriverEntry : Created EPSec control device: 0xfffffa8003e1a6a0.
DEBUG: VSockLayer_Init : Initializing VSockLayer module
ERROR: VSockLayer_Init : VMCISock_WskCaptureProviderNPI: 0x80000011
ERROR: VFileSocketMgr_Init : Failed to initialize vsock layer: 0x80000011
ERROR: DriverEntry : VFileSocketMgr_Init() failed: 0x80000011
AUDIT: DriverEntry : vfileFilter build-2530600 load failed: 0x80000011
A mensagem indicava que uma chamada à rotina WskCaptureProviderNPI falhou com um código de erro STATUS_DEVICE_BUSY. Aqueles de vós familiarizados com a interface tipo socket Winsock Kernel , introduzida no Windows Vista, que permite a componentes do kernel comunicar convenientemente em rede, sabem que a rotina com o mesmo nome precisa de ser chamada quando se inicializa o lado cliente da interface (registando uma aplicação Winsock Kernel). Contudo, não se revelou assim tão simples. O driver vsepflt.sys não usa Winsock Kernel (isso causaria problemas de compatibilidade com versões anteriores ao Vista) e nenhuma das rotinas de inicialização WSK (WskRegister, WskCaptureProviderNPI) contém um caminho de código que obviamente contivesse o código de erro que tínhamos visto. Por isso, foi necessária uma análise mais profunda.
Verificou-se que o driver vsepflt.sys usa a interface VMware vSockets para comunicar com software no sistema operativo anfitrião. VMware vSockets é uma interface tipo socket feita especificamente para esse propósito. Consiste num driver de kernel e numa DLL que se regista como Winsock Service Provider (WSP), para que uma aplicação possa usar funções socket padrão para utilizar a interface; basta especificar uma constante de família de endereços especial ao criar um socket. A boa notícia era que a interface vSockets está documentada. Infelizmente, havia também más notícias: a documentação afirma claramente que a interface se destina a ser usada apenas por aplicações e não por componentes do kernel de terceiros.
Isso causou um problema considerável, uma vez que tínhamos especulado sobre criar um driver minifilter de sistema de ficheiros simples que agisse como o vsepflt.sys, pelo menos durante a fase de inicialização. Felizmente, sabíamos com bastante precisão onde as coisas corriam mal dentro do vsepflt.sys (as mensagens de debug ajudaram aqui). Mais reverse engineering revelou que a falha é causada pelo envio de um pedido IOCTL para o objeto de dispositivo chamado \Device\vmci, servido por \Driver\vsock, o driver que implementa a parte kernel do VMware vSockets. A descoberta permitiu-nos criar um driver de teste que tentasse enviar o mesmo IOCTL durante a sua inicialização. O código que envia o IOCTL é mostrado na Listagem 2. Como pode ver, o IOCTL não é muito difícil de construir, uma vez que basta zerar todo o buffer de entrada exceto o primeiro DWORD, que precisa de ser definido como 1. As coisas são um pouco mais complicadas dentro do vsepflt.sys; provavelmente existe uma biblioteca estática que trata das operações vSockets, mas isso pouco interessa se só estivermos interessados em enviar o IOCTL.
Listagem 2: Envio do IOCTL tipo WskCaptureProviderNPI (para versões 32 bits do Windows)
NTSTATUS _ConnectVSocks(void)
{
LARGE_INTEGER timeout;
PIRP irp = NULL;
PIO_STACK_LOCATION irpStack = NULL;
KEVENT event;
IO_STATUS_BLOCK iosb;
PDEVICE_OBJECT deviceObject = NULL;
PFILE_OBJECT fileObject = NULL;
UNICODE_STRING uDeviceName;
NTSTATUS status = STATUS_UNSUCCESSFUL;
timeout.QuadPart = -10000000;
RtlInitUnicodeString(&uDeviceName, L"\\DosDevices\\vmci");
for (SIZE_T i = 0; i < 100; ++i) {
status = IoGetDeviceObjectPointer(&uDeviceName, 0x1, &fileObject, &deviceObject);
if (NT_SUCCESS(status)) {
ULONG inputBuffer[0x5C / sizeof(ULONG)];
memset(inputBuffer, 0, 0x5C);
inputBuffer[0] = 1;
KeInitializeEvent(&event, NotificationEvent, FALSE);
irp = IoBuildDeviceIoControlRequest(0x810320C0, deviceObject, inputBuffer, sizeof(inputBuffer), inputBuffer, sizeof(inputBuffer), FALSE, &event, &iosb);
if (irp != NULL) {
irpStack = IoGetNextIrpStackLocation(irp);
irpStack->FileObject = fileObject;
status = IoCallDriver(deviceObject, irp);
if (status == STATUS_PENDING) {
(VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
status = iosb.Status;
}
if (NT_SUCCESS(status))
break;
} else {
status = STATUS_INSUFFICIENT_RESOURCES;
ObDereferenceObject(fileObject);
}
}
if (!NT_SUCCESS(status))
KeDelayExecutionThread(KernelMode, FALSE, &timeout);
}
return status;
}
Depois do driver de teste estar construído, conseguimos reproduzir com sucesso o problema no nosso ambiente. Quando o nosso software estava em execução, o IOCTL falhava sempre com STATUS_DEVICE_BUSY, independentemente de quantas tentativas fizéssemos (a Listagem 2 indica que o driver de teste tenta enviar o IOCTL 100 vezes antes de desistir). A melhor coisa do driver de teste, contudo, era que conseguíamos examinar o estado do sistema operativo. A coisa mais interessante que encontrámos foi a lista de drivers de boot-start inicializados (ou em processo de inicialização) mostrada na Listagem 3. Para comparar a lista com uma máquina que apenas contém o VMware Tools, veja a Listagem 4. Decidi listar objetos de driver, e não ficheiros de driver mapeados, para ter uma melhor ideia de quais já estavam inicializados.
Listagem 3: Drivers inicializados cedo no momento do arranque numa máquina a executar o nosso software
0: kd> !object \Driver
Object: 8c458b08 Type: (85547b50) Directory
ObjectHeader: 8c458af0 (new version)
HandleCount: 0 PointerCount: 23
Directory Object: 8c408ed0 Name: Driver
Hash Address Type Name
---- ------- ---- ----
01 855d2a70 Driver LSI_SAS
04 8556bf38 Driver vmbus
8555c798 Driver Compbatt
85628f38 Driver msisadrv
05 855665c8 Driver mountmgr
08 8556b788 Driver atapi
09 8555d7d8 Driver volmgrx
11 8556da20 Driver amdxata
16 8556b670 Driver vsock
17 85566e40 Driver vmci
18 856481b8 Driver 00000142
855f18a8 Driver WMIxWDM
855f92d8 Driver ACPI_HAL
25 8555c670 Driver volmgr
29 85628978 Driver pci
30 8555ce10 Driver partmgr
32 8554c908 Driver ACPI
8556d030 Driver msahci
8554d320 Driver Wdf01000
33 85619668 Driver PnpManager
34 856287d0 Driver vdrvroot
36 85566f38 Driver intelide
0: kd> !drvobj \Driver\vmci
Driver object (85566e40) is for:
*** ERROR: Symbol file could not be found. Defaulted to export symbols for vmci.sys -
\Driver\vmci
Driver Extension List: (id , addr)
Device Object list:
0: kd> !object \Global??\VMCIDev
Object Global??\VMCIDev not found
Listagem 4: Lista de drivers de arranque inicializados numa máquina quase limpa
0: kd> !object \Driver
Object: 8c067dd8 Type: (851ce040) Directory
ObjectHeader: 8c067dc0 (new version)
HandleCount: 0 PointerCount: 23
Directory Object: 8c004e18 Name: Driver
Hash Address Type Name
---- ------- ---- ----
01 85825460 Driver LSI_SAS
04 8539f1e8 Driver vmbus
85b60730 Driver Compbatt
85ca3330 Driver msisadrv
05 8539b1e8 Driver mountmgr
08 8537f1e8 Driver atapi
09 85cf1998 Driver volmgrx
11 85213568 Driver amdxata
16 8539e1e8 Driver vsock
17 853291e8 Driver vmci
18 852471b8 Driver 00000142
85230030 Driver WMIxWDM
8522f908 Driver ACPI_HAL
25 85b17030 Driver volmgr
29 858f3088 Driver pci
30 85a4a728 Driver partmgr
32 853971e8 Driver msahci
8514c8c0 Driver ACPI
85235db8 Driver Wdf01000
33 85222ce0 Driver PnpManager
34 85be4e88 Driver vdrvroot
36 853411e8 Driver intelide
0: kd> !drvobj \Driver\vmci
Driver object (853291e8) is for:
*** ERROR: Symbol file could not be found. Defaulted to export symbols for vmci.sys -
\Driver\vmci
Driver Extension List: (id , addr)
Device Object list:
8584c6e8 8584c890
0: kd> !object \Global??\VMCIDev
Object: 8c1cb978 Type: (851cef78) SymbolicLink
ObjectHeader: 8c1cb960 (new version)
HandleCount: 0 PointerCount: 1
Directory Object: 8c008d20 Name: VMCIDev
Target String is '\Device\VMCIHostDev'
0: kd> !devobj \Device\VMCIHostDev
Device object (8584c890) is for:
VMCIHostDev \Driver\vmci DriverObject 853291e8
Current Irp 00000000 RefCount 0 Type 00000004 Flags 00002040
Dacl 8c1fbca8 DevExt 8584c948 DevObjExt 8584c9c8
ExtensionFlags (0000000000)
Characteristics (0x00000100) FILE_DEVICE_SECURE_OPEN
AttachedTo (Lower) 8522f030 \Driver\PnpManager
Device queue is not busy.
O estranho na Listagem 3 era que nenhum dos nossos drivers estava efetivamente carregado, embora estivessem registados como drivers de arranque. O driver vsock.sys estava carregado e provavelmente inicializado o suficiente para receber pedidos IOCTL. Decidi dar uma vista de olhos rápida também a esse driver, apenas para verificar se havia dependências óbvias com outros drivers. E havia uma.
O driver vsock.sys comunica com outro driver da VMware, vmci.sys (\Driver\vmci, não confundir com \Device\vmci pertencente a \Driver\vsock) que também é iniciado no momento do arranque. A ligação simbólica \DosDevices\VMCIDev é usada para estabelecer um canal de comunicação com o driver. Como se vê na Listagem 3, essa ligação simbólica não existe, enquanto está presente na Listagem 4. Por isso, deduzi que o nosso software de alguma forma impede o driver vmci.sys de criar os seus objetos de dispositivo suficientemente cedo.
O driver vmci.sys serve um dispositivo virtual PnP da classe System setup que provavelmente representa um dispositivo de bus raiz para toda a atividade relacionada com o VMware Tools. Como um dos nossos drivers se regista como upper filter para esta classe de setup, a inicialização do driver vmci.sys foi adiada até que todos os upper filters estivessem prontos ( especificar a Driver Load Order ). É exatamente por isso que o driver não estava pronto quando era necessário ao vsock.sys. E foi isso que causou o problema de STATUS_DEVICE_BUSY. Lembre-se de que a Listagem 3 mostra que nenhum dos nossos drivers é carregado tão cedo durante o arranque.
Toda a história teve afinal o seu final feliz. Propusemos duas soluções para o problema: remover o nosso upper filter para a classe System device setup ou mover os nossos drivers para um grupo de ordem de carregamento, como o System Boot Extender, que carrega mais cedo no arranque. Aprendemos novamente que nunca se é demasiado cuidadoso quando se trata da inicialização de drivers de arranque. Mexer com a ordem de carregamento no momento do arranque pode não só pôr o sistema num estado em que não consegue arrancar, como também levar a problemas com causas muito menos óbvias.
Por fim, gostaríamos de agradecer ao nosso cliente por ter sido realmente prestável quando precisámos de informação adicional relacionada com o problema. Nem sempre é o caso.
Escrito por Martin Drab, Developer @Safetica Technologies
O Martin escava sempre fundo para saber como as coisas funcionam por dentro. Especialmente interessado em programação de baixo nível e de kernel em Windows, mantém partes essenciais do Device Control para si. Código a funcionar sem falhas, um artigo técnico interessante e um bom tempo a nadar são as coisas que mais lhe agradam.
Está a considerar a solução de data loss prevention (DLP) e insider risk management (IRM) da Safetica e gostaria de saber mais sobre como o nosso produto se ...
Na nossa busca por espreitar o futuro da proteção de dados, reunimos perspetivas da equipa de especialistas da Safetica, incluindo analistas de cibersegurança, ...
Há muitas formas de crescer. Na Safetica, acreditamos que crescer ao lado dos nossos parceiros e clientes é a forma mais poderosa. Por isso, este ano partimos ...