Tuesday, June 4, 2013

Loading in the environment they call Safe Mode!

Sometime back I had a requirement of loading a driver in Safe Mode. Most of us who have been dealing with drivers know that if your driver is not a boot driver, Windows does not load it in Safe mode. Infact Microsoft generally discourages third party drivers from loading in safe mode and hence a lot of boot time drivers detect the booting of Windows in Safe Mode in DriverEntry and return a failure code.
So, who would want to really load in Safe Mode? Well, certain Anti-Malware related drivers for instance are good candidates for running in such an environment.
Before we actually go into details, it is worth mentioning that all this is just about creating the required registry entries to whitelist the driver and telling Windows that it is fine to load it.
Assuming that our driver's service name is MySMDrv and the binary names are MySMDrvx86.sys and MySMDrvx64.sys on x86 and x64 platform.
 
Let's take it step by step:
Where is the service entry created?
The service entry for all the drivers gets created under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services. In our case, it would be
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MySMDrv.
It is important to note that CurrentControlSet is just a link to another key (as an example ControlSet001). So, HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MySMDrv will actually be a link to HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\MySMDrv.
This becomes clear if you notice the registry path that comes as the second parameter in DriverEntry.
What decides what CurrentControlSet points to?
Depending on the environment that Windows has booted into, CurrentControlSet can point to ControlSet001, ControlSet002 and so on. This is indicated by ValueName "Current" of "HKEY_LOCAL_MACHINE\SYSTEM\Select". For instance, value of 1 for "Current" indicates that CurrentControlSet points to ControlSet001.
Installing in Safe Mode
If your driver is not a boot-time driver, you most likely will have to install it from within the Safe Mode to make loadable. So, once you are in safe mode, install your driver such that it creates the required registry entries. Assuming that ControlSet001 represents normal boot, ControlSet002 will most likely represent the Safe Mode boot environment. Again, this information must be extracted on the fly using the registry key mentioned above.
Trying to load a driver that is not whitelisted
If you try to load a non-whitelister driver in Safe Mode, you will receive either error ERROR_NOT_SAFEBOOT_SERVICE or ERROR_NOT_SAFE_MODE_DRIVER.
Whitelisting the driver and getting it to load
Whitelisting is all about making the required registry entry somewhere under
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SafeBoot. CurrentControlSet ofcourse needs to be translated into the correct path. You will notice that there are some more subkeys under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SafeBoot- Minimal and Network. They in turn represent the specific SafeMode boot type. This is where the entry really needs to go.
Let's look at the entries required for whitelisting:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SafeBoot\Minimal\MySMDrv
    with the default (unnamed) value name set to "Driver"
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SafeBoot\Network\MySMDrv
    with the default (unnamed) value name set to "Driver"
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SafeBoot\Minimal\MySMDrv.sys
    with the default (unnamed) value name set to "Driver"
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SafeBoot\Network\MySMDrv.sys
    with the default (unnamed) value name set to "Driver"
 
Look at the last two entries. They don't really make sense. Our driver name is MySMDrvx86.sys and MySMDrvx64.sys on x86 and x64 respectively. However, this is precisisely what is needed to get it to work and something that I had trouble with initially; but found out eventually!
It is also worth mentioning that if your driver name is MySMDrv.sys (that is same as Service name plus the .sys extension), you just need to create the MySMDrv.sys entry (atleast this seems to be from looking at the existing registry entries). However, it is safe to create both entries and just be sure that it would work!
 
Last but not the least, the above has been tested on Windows 8 too and works just fine.
 
Oh yeah, don't forget to sign your driver. ;)

Wednesday, December 16, 2009

Driver to Service communication- Don’t reinvent the wheel!

Most of us have at one time or the other implemented "something" that was required for the communication between the user mode service and the kernel mode driver. Things are pretty straightforward when the communication is triggered from the service. For example, the service wants to send a list of files to be excluded from whatever magic the driver is doing. Now, let's talk about the communication originating from the driver. What then? Those who have implemented something like this know that it is not as straightforward as the previous case. Ofcoure, if you are writing a file system minifilter, then you are good. Just call FltSendMessage and you are done.

So, let's see if we can make use of the filter manager's communication port functionality to send and receive messages in our driver even if our driver is not a FS minifilter. Let's try to divide the functionality provided by filter manager in two categories: FS filtering and communication. If you observe carefully, both of them are independent of each other. However, the initial call to register the driver with the filter manager does check some registry entries that are specific to a FS minifilter; for example the altitude. Anyways, I tried to write a driver that received process notifications using the Process manager callbacks. My objective was to be able to receive the callback and send the information to the user mode service. So, I started off by using the INF of nullfilter minifilter sample.

Note that the driver that I tried to write was a "no stack" driver- meaning that it was not attached to network stack or storage stack, etc. Still need to figure out if that will work. Anyways, coming back to the driver, I cooked up the FLT_REGISTRATION structure in the following fashion:

CONST FLT_REGISTRATION registrationData =

{

sizeof (FLT_REGISTRATION),

FLT_REGISTRATION_VERSION,

0,

NULL,

NULL,

FooUnload,

NULL,

NULL,

NULL,

NULL,

NULL,

NULL,

NULL,

};


If you observe carefully, the only non-NULL parameter in this structure (except for the first two) is the FooUnload where I have registerd the unload routine. The idea of using this kind of registration structure was to tell the filter manager that we are not interested in any FS I/O callbacks (create, read, write, etc.) or in attaching to any volumes. Note that the 7th parameter is NULL (used to specify the Instance setup callback) but it does not mean that the filter manager will not create instance corresponding to this filter for the volumes. To be able to tell filter manager not to attach to any volume, you need to register an Instance setup callback and then return STATUS_FLT_DO_NOT_ATTACH for all the instance setup callbacks. I had other reasons for letting filter manager create the instances for me, so set it to NULL. Next interesting thing to note here is that you must specify a unload routine in the registration structure. And this where things change a bit. The signature of the unload routine for a regular driver is different from that of a minifilter. So, you need to use the signature required by filter manager model and simply do all the required cleanup in this unload routine.

Next step is to call FltRegisterFilter which will register the driver with the filter manager. And then you can simply create the communication port using the FltCreateCommunicationPort call. And then? Simply start firing requests as you do in the minifilter.

Couple of points to ponder and explore:

  1. What altitude should we ask from MS for this kind of a filter? We are not going to be layered between other drivers in the FS stack. Well, it doesn't really matter what altitude you get because altitudes define the order in which you will see the I/O requests with respect to other filters. So, simply ask for a activity monitoring range.
  2. What happens if we want to attach to a storage stack? The filter manager might not be avialable that soon. Well, think of it this way, you need to communicate with you service only when the service is up. Right? So, you can delay registering your driver with the filter manager by "somehow" detecting that the user mode environment is ready. Or, if it is acceptable for your driver's functionality, you can simply put a dependency on fltmgr.sys in your driver's registry entry.
  3. Implications of LoadOrderGroup and driver type.

Will try out more experiments for the above points and post them soon.

However, there is an alternate way of achieving this. Make a common communication driver that implements this functionalty and export various communication functions to be used by other drivers that you develop. And I like this one. This export driver can load after the filter manager is up. But your driver can load even before that. And anyways, you can be sure that your communication driver is up when you "actually" want to communicate with your service. Just remember to set the start type of your export driver appropriately.