Let’s Start Again

June 24th, 2007 - Fernando Roberto

Some drivers need to start as soon as the system loads, I mean, while the system loads. When we set our driver with Start = 0 (Boot), our driver loads among very basic drivers such as File Systems, Bus Drivers and so on. Once, I needed a Boot driver to open a file to get some system information. Unfortunately things like partitions, volumes and File Systems were still not working, so I had to postpone the process a little. In this post, I will comment about how to continue your driver DriverEntry start-up process after the function having ended.

To illustrate the case, I have written a driver that clearly illustrates this situation. The full project is on a link for downloading at the end of this post. Looking at out DriverEntry implementation, we have:

/****
***     DriverEntry
**
**      Driver entry point, which will be called while
**      the system Boots (like "I have just turned my computer on").
*/
extern "C" 
NTSTATUS DriverEntry(IN PDRIVER_OBJECT  pDriverObj,
                     IN PUNICODE_STRING pusRegistryPath)
{
    NTSTATUS    nts;
 
    //-f--> We're back...
    KdPrint(("Starting DrvReinit...\n"));
 
    //-f--> Register the unload callback routine
    pDriverObj->DriverUnload = OnDriverUnload;
 
    //-f--> Try to open a file as early as possible, thereafter
    //      I was born within seven months.
    nts = TryOpenFile();
 
    //-f--> And as you can see...
    if (!NT_SUCCESS(nts))
    {
        //-f--> The file was not opened.
        KdPrint(("Scheduling reinitialization.\n"));
 
        //-f--> And as far as I can imagine, the reason is as it follows:
        ASSERT(nts == STATUS_OBJECT_PATH_NOT_FOUND);
 
        //-f--> Register OnReinitialize routine so it'll be called later.
        //      In this case, later means whenever all boot drivers have
        //      been loaded.
        IoRegisterBootDriverReinitialization(pDriverObj,
                                             OnReinitialize,
                                             NULL);
    }
    else
    {
        //-f--> Er... Are you sure you have installed that driver
        //      correctly? Well, your system is not booting now
        //      and you just started your driver manually.
    }
 
    //-f--> If the DriverEntry routine doesn't return STATUS_SUCCESS,
    //      OnReinitialize will not be called.
    return STATUS_SUCCESS;
}

IoRegisterBootDriverReinitialization() function (Phew! Some of these functions require a breath) registers a callback routine that will be called whenever all boot drivers have been loaded. That will give us a second chance to try to do what has been proposed. This routine is typically used in filters that attach on non-Plug-and-Play devices, and thus, they cannot rely on AddDevice() function calling to be notified that a new device was created.

In our example, the goal is simply to read a file. Accordingly, the error we have received is STATUS_OBJECT_PATH_NOT_FOUND. To ensure that the file you’re about to read actually exists, we will use the actual file where the driver is implemented. Therefore, if our code is running, the file must be there.

/****
***     TryOpenFile
**
**      This routine tries to open the file where this driver is implemented.
**      If this code is running, then this file must be there.
*/
NTSTATUS TryOpenFile(VOID)
{
    NTSTATUS            nts;
    IO_STATUS_BLOCK     IoStatusBlock;
    HANDLE              hFile;
    OBJECT_ATTRIBUTES   ObjAttributes;
    UNICODE_STRING      usFilePath =
        RTL_CONSTANT_STRING(L"\\??\\C:\\Windows\\System32\\drivers\\DrvReinit.sys");
 
    //-f--> Hello debugger...
    KdPrint(("Trying open the file...\n"));
 
    //-f--> Filling the OBJECT_ATTRIBUTES structure.
    //      What can I do? It makes part of it.
    InitializeObjectAttributes(&ObjAttributes,
                               &usFilePath,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);
 
    //-f--> Try to open the file.
    nts = ZwCreateFile(&hFile,
                       GENERIC_READ,
                       &ObjAttributes,
                       &IoStatusBlock,
                       NULL,
                       0,
                       FILE_SHARE_READ | FILE_SHARE_WRITE,
                       FILE_OPEN,
                       0,
                       NULL,
                       0);
 
    //-f--> Did it open or not?
    if (!NT_SUCCESS(nts))
    {
        //-f--> I'm sorry, maybe next time.
        KdPrint(("Error 0x%08x opening the file.\n", nts));
    }
    else
    {
        //-f--> So, it wasn't that hard.
        KdPrint(("File opened OK. Er... So, let's close it now.\n"));
 
        //-f--> Close the file handle. We want nothing from it anyway.
        ZwClose(hFile);
    }
    return nts;
}

Initially, when we had tried to open this file from DriverEntry function and the error returned, indicating that it does not exist, we can fall into an existential crisis. After all, if the file has not existed, how was the driver loaded? Doesn’t the driver exist? Don’t I exist? Other crises could be mentioned as the well-known ones:

“God is love.
The love is blind.
Steve Wonder is blind.
So, Steve Wonder is God.

Someone told me I’m no one.
No one is perfect.
So, I am perfect.
But only God is perfect.
So, I am God.

If Steve Wonder is God, I am Steve Wonder!
My God, I am blind!”

But back to the subject, if the re-initialization routine is executed and the service that the driver needs is not yet available, so we can re-schedule this routine calling once again (or as many times as necessary). One of the parameters that re-initialization routine receives is the one which tells you how many times it had been called by the system. This could be used to give up on the resource that never appears when the thousandth attempt has failed. Follow our routine sample:

/****
***     OnReinitialize
**
**      This routine is registered by the IoRegisterBootDriverReinitialization 
**      routine to be called later.
*/
VOID OnReinitialize(IN PDRIVER_OBJECT     pDriverObj,
                    IN PVOID              pContext,
                    IN ULONG              ulCount)
{
    NTSTATUS    nts;
 
    //-f--> This routine can be rescheduled as many times as necessary.
    //      Let's show how many it's been so far.
    KdPrint(("OnReinitialize was called %d times...\n", ulCount));
 
    //-f--> If this commentary did not exist, you would never know
    //      the following routine would try open a file.
    nts = TryOpenFile();
 
    //-f--> So, did it work?
    if (!NT_SUCCESS(nts))
    {
        //-f--> If the error code is different from the one below, so
        //      I know nothing about it. It is not my fault. I don't even know you.
        ASSERT(nts == STATUS_OBJECT_PATH_NOT_FOUND);
 
        //-f--> It can't be true. This is not actually happening.
        //      Let's try again a bit latter.
        IoRegisterBootDriverReinitialization(pDriverObj,
                                             OnReinitialize,
                                             NULL);
    }
}

Installing a Boot driver

To ensure that we have a need to delay our driver start-up, we will make our sample driver be the first one to be loaded. For this, we use the aforementioned DriverLoader to install it on a specific group.

After compiling the sample code, copy the driver to the victim machine’s System32\drivers directory. Run DriverLoader, fill up the fields as demonstrated below and click on RegisterService.

To complete the experience, we will restart the system and connect the kernel debugger to follow ahead. We should have the following output.

But couldn’t we use a work item for this? Actually, yes, but there are subtle differences between these alternatives. Using a Work Item cannot guarantee that the routine is not being executed before DriverEntry finishes, and obviously it does not ensure that it runs only after all the boot drivers has been loaded.

See you next time… 😉

DrvReinit.zip

Leave a Reply