Zoom Icon

Drivers Services and Signatures

From UIC Archive

Drivers, Services and Signatures

Contents


Drivers Services and Signatures
Author: X-Treem
Email: [email protected]
Website:
Date: 02/05/2011 (dd/mm/yyyy)
Level: Luck and skills are required
Language: English Flag English.gif
Comments: Heaven Gates, Hell Gates, Bill Gates!



Introduction

We are going to make a step forward starting from my previous tutorial about DKOM. In this one we are going to see an extended driver compared to the previous version and three main new things: a Windows service, a driver inverse call and signatures. All targetting a Windows XP machine.



Tools


Source


Essay

I am again assuming that you already got the basics from Quequero's tutorial and my tutorial.

Overview

In my previous tutorial I had a driver that was being called by a test application and, upon request, the driver would hide the application from the active processes list. This was achieved through the calling of a specific DeviceIoControl code. Even though the outcame of my expansion of the tutorial would not give a usefull piece of software there are three main topics that we are going to see that can be really interesting. What happens in this new version of the software is that upon hide request from our test application, the driver, using an inverse call model, interrogate a windows service that in turn will check file Authenticode signature, and if signature is valid will confirm validation to the driver that will procede with hiding the requesting application. Drivers Services and Signatures OVERVIEW.jpg

The inverse call model

As everybody know a usermode application has many ways to communicate with other applications, services or drivers but what if a driver want to communicate with a service or an application? There is not a simple call that you will do like a DeviceIoControl or a Write/ReadFile or a ControlService. Here comes the inverse call model to help us. Basically your driver and application should be crafted so that you application send an output DeviceIoControl to the driver and the driver hold (pend) that request untile it needs it. This achieve the possibility of a driver sending request to a usermode application even thou the request is not really driver initiated.

This could be quite a difficult task in complex drivers and applications because it would possibly require a lot of synchronization mechanisms to keep pending request and answers consistent throughout the driver and application life.

The service

I will not discuss on how to start or install a service because MSDN examples are quite self explaining and I reused most of the code you can find there. For sure my example shows altogether how to install,start,stop, uninstall the service and how to run as a service itself whenever called by the Service Manager.

The only part we are giving highlight to is the DriverControlThread function inside WinTrustProxySvc.cpp. As you can see we are calling

DeviceIoControlResult = DeviceIoControl( hProcHiderDevice, IOCTL_TESTDRIVER_PENDING_USERMODE_REQUEST, NULL, 0, pDeviceOutputBuffer, sizeof(TCHAR)*1024, //number of bytes &DeviceOutputCount, &OverlapResult );



This is the part that sends the inverse call model request to the driver and that the sits there waiting for a request from the driver.

It is worth mentioning that I am using FILE_FLAG_OVERLAPPED when creating the handle to the device driver. This is due to a Windows XP limitation of the I/O cancellation. When I started implementing this part of the service I was using a Windows Vista machine and they added a bunch of I/O cancellation functions starting from Vista+ (I.e. CancelSynchronousIo). Under Windows XP if you had a sync I/O request to a driver there was no way to cancel that request if for instance you wanted to stop your program (and let the thread terminate gracefully). I/O were only cancelled by a forced process termination or thread termination. So, under Vista I could have a sync DeviceIoControl request pending in one thread and on shutdown of my software call CancelSynchronousIo from another thread (and this works great in services) to send I/O cancel request down to the driver that is holding that request (and the driver must support I/O cancellation). As I said I discovered lately that Windows XP was not exporting that neat API function and so after reading a lot around I finally managed that the better way to support I/O cancellation was overlapped I/O. Overlapped I/O has the nice feature of event signaling upon I/O completion, this led me to use an event for the I/O and a global event for shutdown and then using WaitForMultipleObjects to sit there and wait for an I/O to complete or a shutdown to be signaled and in case of shutdown use the CancelIo (the only useful I/O cancellation API function on XP) from within the thread that called DeviceIoControl(you have to call it from the same thread that issued the async I/O request). switch(LastError) { case ERROR_IO_PENDING: case ERROR_IO_INCOMPLETE: WaitResult = WaitForMultipleObjects( 2, ShutdownAndIOEventArray, FALSE, INFINITE );

switch(WaitResult) { case SHUTDOWN_EVENT: //we were signaled to shutdown so cancel outstanding I/O OutputDebugString(_T("DriverControlThread - Shutdown received\n"));

ShutDownRequested = TRUE;

CancelIo(hProcHiderDevice); // //This GetOverlappedResult should return ERROR_OPERATION_ABORTED //Should prevent this thread to cancel the Event while the OVERLAPPED //operation is still in the I/O Manager by waiting on it // GetOverlappedResult( hProcHiderDevice, &OverlapResult, &DeviceOutputCount, TRUE );

   //
   //...
   //

Sync vs Async

As any noob of driver writing I had an hard time understanding Overlapped I/O and Non-Overlapped I/O and how (and if) they should be handled by a driver.

Lets forget of usermode for a while. The basics of everything is that your driver should always be coded asynchronously: this means that if you have a request (IRP) that can be satisfied immediately you just satisfy it and return STATUS_SUCCESS. If your request cannot be satisfied immediately then you return STATUS_PENDING.

As you can see in my driver, when I receive the inverse model call from the service I return STATUS_PENDING on that IRP as I am going to use it only when any application request to be hidden, so my driver is completing that IRP asynchronously. Same thing for hide request, because I have to interrogate the service, wait for its answer, verify the answer then hide or not hide the requesting application, I have to pend the IRP and complete it when I receive the Ack from service and we do not want to wait inside our driver. The service Ack of a verified application instead can be completed immediately thus I complete the request in the dispatch routine and return STATUS_SUCCESS immediately.

For instance if I would have cached signature answers in the driver, an application that would have requested hiding more than once would have been answered asynchronously the first time and then synchronously every other times because I would have kept the positive check in the cache so I could have answered immediately with STATUS_SUCCESS.

What is the difference from usermode point of view?

There are four cases (I am using DeviceIoControl but can be applied to any communication API)

Overlapped I/O, driver return STATUS_PENDING.

Application sends DeviceIoControl request

I/O Manager dispatch the request to Driver

Driver has not data ready so return STATUS_PENDING

Application DeviceIoControl fails with status ERROR_IO_PENDING, application thread can continue working and check for data later using GetOverlappedResult or WaitForSingleObject on the Overlapped event

Driver has data ready, completes the IRP setting STATUS_SUCCESS in the IRP IoStatus block.

I/O Manager sets the Overlapped event

Application GetOverlappedResult or WaitForSingleObject trigger, can use data



Overlapped I/O, driver return STATUS_SUCCESS.

Application sends DeviceIoControl request

I/O Manager dispatch the request to Driver

Driver has data ready so return STATUS_SUCCESS

Application DeviceIoControl succeed

Application can use data



Non-Overlapped I/O, driver return STATUS_SUCCESS.

Application sends DeviceIoControl request

I/O Manager dispatch the request to Driver

Driver has data ready so return STATUS_SUCCESS

Application DeviceIoControl succeed

Application can use data



Non-Overlapped I/O, driver return STATUS_PENDING.

Application sends DeviceIoControl request

I/O Manager dispatch the request to Driver

Driver has not data ready so return STATUS_PENDING

I/O Manager blocks the Application DeviceIoControl

Driver has data ready, completes the IRP setting STATUS_SUCCESS in the IRP IoStatus block.

I/O Manager unblock the Application DeviceIoControl

Application DeviceIoControl succeed (application is unaware that time elapsed)

Application can use data



As you can see if STATUS_SUCCESS is returned there is no difference between Overlapped and Non-Overlapped I/O.

Same thing with the driver: the driver is unaware if the usermode application was Overlapped or Non-Overlapped as the I/O manager takes care of isolating the two worlds.

The only difference is when STATUS_PENDING is returned. In one case the I/O manager does not block and the Application thread continues to run and can check on I/O status later, in the other case the I/O manager kindly put the Application thread to sleep in a blocking fashion and unlocks it whenever the I/O completes.

You can actually see that in WinTrustProxySvc.cpp that I coded the support for both a pending or succesful return from DeviceIoControl

if(DeviceIoControlResult == 0) { switch(LastError) { case ERROR_IO_PENDING: case ERROR_IO_INCOMPLETE: WaitResult = WaitForMultipleObjects( 2, ShutdownAndIOEventArray, FALSE, INFINITE ); switch(WaitResult) { // //... // case IO_EVENT: // //we were signaled that I/O completed // GetOverlappedResultResult = GetOverlappedResult( hProcHiderDevice, &OverlapResult, &DeviceOutputCount, TRUE ); if(GetOverlappedResultResult == 0) { UnknownError = TRUE; } else { GotData = TRUE; } break; } //switch(WaitResult)

break; default: UnknownError = TRUE;

break; } //switch(LastError)

} //if(DeviceIoControlResult == 0) else { // //DeviceIoControl already returned data // GotData = TRUE; }

After all this switching, GotData is set to true if we have valid data indipendently weather the request is completed synchronously or asynchronously by the driver. Again, I am just stressing this out when you have FILE_FLAG_OVERLAPPED. If you did not open the device driver for overlapped operations the I/O manager is going to take care for blocking the DeviceIoControl and unblock it when data is ready, so it is basically doing the switching job you just saw for you.

The Signature

Once the service receive the signature check request from the driver it calls WinTrustProxySvcCheckSignature that is in WinTrustProxySvcCheckSignature.cpp. This part is pretty standard as it comes out straight from MSDN and is the basic for checking the Authenticode signature of a file. You could modify this part to make different kinds of checking instead of relying just on the authenticode, you could check revocations, you could do certificate matching you could do certificate chain matching and a lot other kind of certificate stuffs.

Here is the very simple code that was preceeded by filling some default values in the structures used by WinVerifyTrust // //... // GUID ActionGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2; //verify using Authenticode // //... // WinVerifyTrust( NULL, &ActionGUID, &WinTrustData ) // //... //

The Driver

From the last version, the driver has changed a lot. The MJDeviceControl.c source shows that we now support three control codes. The old one for application to request hiding, and two new ones: one for the inverse call model and one for the signature check answer from the service.

I know that the pros will complain about how I coded the functions that handles the various IOCTL codes. You are right. My bad. I should have used NTSTATUS as return type. Just for the noob. You should always code your functions to return NTSTATUS. It makes it easyer and consistent throughout the OS and to debug problems. Add also that all kernel mode APIs return NSTATUS and those NTSTATUSes translate to usermode error codes too. If for instance my driver failed to use ExAllocatePoolWithTag I could simply propagate a STATUS_INSUFFICIENT_RESOURCES in subsequent IRPs that needed that buffer. If the IRP is coming from a DeviceIoControl, the function will fail and GetLastError would return ERROR_NO_SYSTEM_RESOURCES or I could propagate a STATUS_NO_MEMORY that would have translated to ERROR_NOT_ENOUGH_MEMORY system error code. Anyway, keep in mind that driver error codes maps directly to system error codes, so knowing why your application fails and with which codes can help you do troubleshooting. For a mapping refer to this NTSTATUS to System Codes mapping

IOCTL_TESTDRIVER_PENDING_USERMODE_REQUEST

This is the inverse call model request from the service. It is very simple as I am going to immediately store it. The mechanism we are using to store this kind of IRPs is through windows linked list. IRP structure kindly offer IRP.Tail.Overlay.ListEntry that can be used by us if we use our ouw implementation of queues to handle pending IRPs. As I want to support I/O cancellation I have to set up a cancellation routine and as you can see I am using a spin lock as a synchronization mechanism to exclusively access the queue. In the LinkPendingIrpContainer I use an interlocked queuing function to pend the irp as it is just arrived while in the cancel routine I explicitly call the KeAcquire and KeRelease on the spin lock as those IRPs can in certain particular conditions be canceled right before being completed so I want to protect under the spin lock the check of blink and flink values to see if the IRP is already in use by another function (either the cancel or the one that completes them)

IOCTL_TESTDRIVER_HIDE_PROCESS

This is the old IOCTL we had in the previous tutorial but carefull eyes will immediately see that the successful return value is no more STATUS_SUCCESS but STATUS_PENDING. Why is that?

If you look at the Overview image you see that apart of the process hiding our driver now handle a request for signature validation upon a hiding request. We really dont know how long this request is going to take before being satisfied so, to abide to asynchrony, we do not block inside the driver but we store the request for later use. As a synchronization mechanism this time I am not using a linked list but an array with a lenght defined in common.h

  1. define MAXIMUM_PENDING_HIDE_REQUEST 32

So our driver will basically support only 32 concurrent hide requests. Its quite an extremely uncommon situation but remeber this number as we need to take care of overflowing request, parameters validation, etc. Our routine will receive the IRP, store it in the array, store some information directly in the IRP in the kindly provided IRP.Tail.Overlay.DriverContext[4] array that we can use to store the associated process EPROCESS structure we can get upon hide request and the pending array storage index. At this point we pend the IRP and we go in our inverse call model pending IRP to find an IRP to dequeue and complete it to send the request to the service to check signature. There is only a problem in this process: when getting image names in kernel mode, all of them do not show the classic X:\ or \\servername\ but they show the fancy volume device object names. Solution could be to let the user mode service translate the names or we can do it in the driver. It is usually better doing it in user mode as any bug in string manipulation or bug of any sort will not cause BSODs. Anyway I coded it at kernel mode because I was curious. It is actually only supporting \Device\HarddiskVolumeX format but it can be extended to support \Device\LanmanDirector\Sharename and USB drives that shows different pattern names. To translate the volume name I have to isolate the kernel mode name of the volume, open a file handle to it, get the FILE_OBJECT pointer to it and, inside the FILE_OBJECT structure, take the FILE_OBJECT.DeviceObject pointer to the \Device\HarddiskVolumeX DEVICE_OBJECT. This way I can use IoVolumeDeviceToDosName to translate \Device\HarddiskVolumeX to X:\ In my code you will se some commented areas as it took me different approach to finally find one that was working and I left the commented parts because they were interesting.

IOCTL_TESTDRIVER_HIDE_PROCESS_ACK

Finally, once the service finished checking the signature it will send an IOCTL_TESTDRIVER_HIDE_PROCESS_ACK back to the driver. As part of the Driver-Service communication I send the pending hide request array index so when the service responds to the driver, the driver knows to which pending IRP the service responded to and I simply remove it from the array (always using a spin lock for access synchronization and cancel routine), via IRP.Tail.Overlay.DriverContext[4] I do a check if array index and stored index match and I can get the stored EPROCESS pointer, depending on service answer I can hide or not the process from the active processes list and then complete the hide request IRP with status success or not supported accordingly and finally complete the service Ack IRP that was the one that really initiated (or we can say confirmed) the hide routine.

Here is a PDF that may better explain the process


Final Notes

I know you will yell at me because this is poorly coded and sources are quite messy but anyway, for noobs this is a good starting point in understanding how IRPs work, what means pending an IRP, how to handle queues and I/O cancellation. It is also a good starting point to show how NT services work and how they can interact with drivers. I dont see any practical application of these sources, they were just an exercise to show some techniques.

X-Treem


Thanks

As usual, thanks to Quequero for this website, to my wife for standing me, and my little Ethan for bringing me joy.


Disclaimer

I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.

Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevole e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.