More Contacts

September 28th, 2007 - Fernando Roberto

It has become more frequent, at least for me, the demand for skilled developers in the Windows Kernel. What is frequent for me? Well, besides the companies that I have regularly worked for, which I imagine are already tired of asking me if I know more Kernel programmers, two companies asked me indications just this last month. One of them is the company that is taking one of my friends, Rodrigo Strauss, to Porto Alegre. If you think it is hard to find places for this type of development, it is even harder to find developers for this type of vacancy. Usually when someone asks me for any developer’s indication, I just end up saying that, after more than ten years as a programmer, I can count every kernel developer I have know using just one hand.

The fact is that both are hard to find, so I’m providing a forward invitations and opportunities of Windows Kernel area to this blog readers who are interested in it. Those interested ones should contact me by e-mail so I can expand our network of contacts. Please, try to avoid leaving your e-mail in the post comment area, because in that way, anyone else (including spammers) can see them. If you have not gotten my e-mail yet, just get it from my Blogger profile. Even those who are not interested in new challenges can send me a hello. So that I can know how many of you have already worked with Windows kernel and attempt to start using the fingers of the other hand on that score.

There is an initiative like this is on the OSR Online site where several specialized companies post jobs about Windows Kernel. Maybe in a few hundred years, when my blog is famous worldwide, companies want to publish their vacancies for Brazilian Kernel programmers at DriverEntry.com.br.

For now I just want to know how many and who are the Brazilian Kernel Coders, but I’ll be open to suggestions. Everything is depending on the volume and quantity of responses that I will get during my attempt.

CYA…

But I don’t know English

September 27th, 2007 - Fernando Roberto

It was through this post that I found out that MSDN beta content (including WDK) is now available for the automatic translation into Portuguese and other languages.

For those who have trouble understanding the original text in English, they can now enjoy the comfort of having the same problems of understanding the same text in Portuguese. This is because the translation is not that good but, it helps for those who have little or no English knowledge.

The result is displayed on the already known MSDN layout, but the main text will come with a split that will separate the Portuguese text from its equivalent English. Maybe it gets better when it stops being beta, but anyway, it remains an alternative for it.

Take a look:
Introdução do kit de drivers do Windows

Automatic translations may be pretty funny. I’ve already seen nasty translations in manuals of imported goods. It’s a shame I have not found that PenDrive manual I have recently bought to share some “pearls” with you!

Device Driver Programming == Motorista de dispositivo programando

Be careful!

Try, Except, Finally and IoWriteErrorLogEntry (Part 1)

September 25th, 2007 - Fernando Roberto

Most of you have already known about exception handling in C. Knowing that the C Run Time Kernel does not support exception handling in C++, the exception handling in C fits us like a glove. Exceptions do not always mean that a critical error has occurred, but regardless, it is essential to treat them. An unhandled exception in kernel means blue screen. In the case of an unexpected exception, such as an Access Violation, using exception handlers could prevent the system from a blue finish. That’s great, congratulations, one more life saved, but we must not forget that there had been an unexpected exception. This post will not mention how to use exception handlers in details, but it will talk about how we can report such events to the system administrator.

A Typical Exception Handling

For those who are just waking up right now, I’ll just give you a brief description of exception handling in the source below. The details are in the reference. The following source was taken from one of the examples available on this blog. This feature can save us from a merciless blue screen whenever “a loser” who calls this function, passes an invalid string as a source of duplication.

/****
***     DupString
**
**      This routine receives a Unicode String and duplicates it.
**      The resulting string should be released
**      using the ExFreePool routine.
*/
 
NTSTATUS
DupString(IN PUNICODE_STRING   pusSource,
          OUT PUNICODE_STRING  pusTarget)
{
    NTSTATUS    nts = STATUS_SUCCESS;
 
    __try
    {
        __try
        {
            //-f--> Initializes the target
            pusTarget->Buffer = NULL;
 
            //-f--> Allocates the target buffer
            if (!(pusTarget->Buffer = (PWSTR)ExAllocatePool(PagedPool,
                                                            pusSource->Length)))
                ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
 
            //-f--> Copies the buffer
            RtlCopyMemory(pusTarget->Buffer,
                          pusSource->Buffer,
                          pusSource->Length);
 
            //-f--> Copies the buffer legths
            pusTarget->MaximumLength = pusTarget->Length = pusSource->Length;
        }
        __finally
        {
            //-f--> If something gets wrong, we must release the target buffer.
            if (AbnormalTermination())
                if (pusTarget->Buffer)
                    ExFreePool(pusTarget->Buffer);
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        nts = GetExceptionCode();
 
        DbgPrint("DupString >> An exception occourred at "__FUNCTION__
                 " with status 0x%08x.\n", nts);
 
        ASSERT(FALSE);
    }
    return nts;
}

Note that there are two blocks of execution. The outermost is the one, which will deal with unhandled exceptions, and the innermost the one, which will deal with resource releasing. The exception handler block will executes the __except block when any exception is thrown from the inside of __try block. This will give us the opportunity to find out what happened and, in some cases, to request that the instruction causing the exception is re-executed. The completion __finally block is executed when the thread goes out of the main block, either by reaching the block’s end, for a return, for a goto out of the block, or even an exception that was thrown. The __finally block is always executed. This gives us the opportunity to release any resource that has been pending during the execution of the block. Taken it together, we can imagine the following situation: suppose the function RtlCopyMemory() throw an exception. In this case, the __except block will run, but for this to occur, the execution flow will leave the inner block as well, thus causing the __finally block execution.

Within each handler, there is a special function call. Inside __except, we have GetExceptionCode(), which returns us the exception code that was thrown. This function can only be called within this context. Also note that, within __finally block, there is a call to the AbnormalTermination() function which indicates that the block was not executed until reaching its end. In our example, we use this function to determine that something wrong should have happened and it is necessary to release the buffer which would be returned to the calling function. Anyway, check out the reference for a complete description of these resources.

The main point here to note is the ASSERT at the end of the __except block. If this source is compiled in Checked, the ASSERT will cause a prompt in the debugger. If there is no debugger attached to the system, then a blue screen will appear. That’s because this macro will throw a break exception to the debugger but, as the debugger will not be there to handle this exception, figure it out. When compiled in Free, the ASSERT macro is translated into nothing: our function will handle the exception gracefully and it will return the exception code to the calling function. How sweet but, when will is the administrator going to know?

A Life Sign

The standard way to leave this signal is writing in the system event log. What is this? This link can give you detailed information about the system event logs. To write a record in this list from the kernel, we initially must allocate the buffer that will house all the information record using the IoAllocateErrorLogEntry() function.

PVOID 
  IoAllocateErrorLogEntry(
    IN PVOID  IoObject,
    IN UCHAR  EntrySize
    );

The pointer returned by this function, although being the type PVOID, points out to an IO_ERROR_LOG_PACKET structure, which has variable size depending on the information that composes it. This structure is used to package all the event data that will be logged.

typedef struct _IO_ERROR_LOG_PACKET
{
    UCHAR MajorFunctionCode;
    UCHAR RetryCount;
    USHORT DumpDataSize;
    USHORT NumberOfStrings;
    USHORT StringOffset;
    USHORT EventCategory;
    NTSTATUS ErrorCode;
    ULONG UniqueErrorValue;
    NTSTATUS FinalStatus;
    ULONG SequenceNumber;
    ULONG IoControlCode;
    LARGE_INTEGER DeviceOffset;
    ULONG DumpData[1];
 
} IO_ERROR_LOG_PACKET, *PIO_ERROR_LOG_PACKET;

Although this structure has three kilograms of members, you won’t need to fill most of them up. Then, the next step is to pass the structure to the IoWriteErrorLogEntry() function and that is it.

VOID 
  IoWriteErrorLogEntry(
    IN PVOID  ElEntry
    );

It is not that easy

So, is it just to allocate the structure, initialize it, call the function of writing and all is done? This question is simply funny. Not long ago, I was in Porto Alegre, and there someone told me that when one explain something starting with “It’s just…”, the actual work is five times bigger than the one explained. Let’s start writing that famous phrase “Hello World” to see the size of the mess!

Well… Where do I put error message? Oh yeah, that’s right, into the message file. Unlikely log files we usually do in applications, the messages are not passed directly to the writing function. Instead, the ErrorCode member of the IO_ERROR_LOG_PACKET structure receives a handle to the message which will be displayed. When the IoWriteErrorLogEntry() function is called, it puts the packet in a list in memory. Later, this package is written to the system events log file. The EventViewer, the software used to view such events, gets the message identifier contained in the package. With this handle, it finds the string that will be stored in a separated module. This module can be your own driver or a DLL. Thus, this string is read and displayed to the user’s module. Phew!

Huh? Who? When? Where? We will rely on the simplest driver example we have to add these features. Let’s start composing the module writing messages to a text file that takes the .mc extension. This text file will be compiled by the Message Compiler. This compilation will generate the resource file containing strings along with the header file that will refer to certain created symbols. See the template message file below:

MessageIdTypedef = NTSTATUS
 
SeverityNames =
(
    Success         = 0x0:STATUS_SEVERITY_SUCCESS
    Informational   = 0x1:STATUS_SEVERITY_INFORMATIONAL
    Warning         = 0x2:STATUS_SEVERITY_WARNING
    Error           = 0x3:STATUS_SEVERITY_ERROR
)
 
FacilityNames =
(
    System          = 0x0
    DriverEntryLogs = 0x2A:DRIVERENTRY_FACILITY_CODE
)
 
LanguageNames =
(
    Portuguese  = 0x0416:msg00001
    English     = 0x0409:msg00002
)
 
MessageId = 0x0001
Facility = DriverEntryLogs
Severity = Informational
SymbolicName = EVT_HELLO_MESSAGE
 
Language = Portuguese
"Ola mundo!"
.
Language = English
"Hello world!"
.

Notice that we have the same message in several languages. What a fancy thing, uh? Save this file with the name of LogMsgs.mc. You can name it of your choice, but remember that this name will be repeated at the generated files. To compile this file, just put it in the list of sources to be compiled, which is in the sources of your project file, as it is shown below. If you decide the messages should be contained within the driver itself, then you need to add the LogMsgs.rc file that will be generated by the Message Compiler.

TARGETNAME=Useless
TARGETPATH=obj
TARGETTYPE=DRIVER
 
SOURCES=LogMsgs.mc\
        Useless.c \
        LogMsgs.rc

Once compiled, the files LogMsgs.rc and LogMsgs.h will be generated. The header file defines the error codes defined at the message file and should be included in the sources files that will make calls for log creation. Watch an excerpt of this file that was generated automagically.

//
// MessageId: EVT_HELLO_MESSAGE
//
// MessageText:
//
//  "Ola mundo!"
//
#define EVT_HELLO_MESSAGE                ((NTSTATUS)0x402A0001L)

Finally we can write the code lines that will use all of this stuff we have created.

#include  
#include "LogMsgs.h"
 
/****
***     DriverEntry 
**
**      That's our driver entry point, everything starts from here,
**      and atfer gets worse and worse...
*/
 
NTSTATUS DriverEntry(IN PDRIVER_OBJECT  pDriverObject,
                     IN PUNICODE_STRING pusRegistryPath)
{
    PIO_ERROR_LOG_PACKET    pLogPacket;
 
    //-f--> Allocates an entry to the event.
    pLogPacket = IoAllocateErrorLogEntry(pDriverObject,
                                         sizeof(IO_ERROR_LOG_PACKET));
 
    //-f--> Inicializes all the structure
    RtlZeroMemory(pLogPacket, sizeof(IO_ERROR_LOG_PACKET));
 
    //-f--> Puts up the desired message
    pLogPacket->ErrorCode = EVT_HELLO_MESSAGE;
 
    //-f--> Sends the entry to the system event list
    IoWriteErrorLogEntry(pLogPacket);
 
    //-f--> Phew, we got to be here; that makes
    //      a success returning code to the system be worthy!
    return STATUS_SUCCESS;
}

Is everything done now?

From the driver viewpoint, it is all already done. The driver can now be compiled, loaded and thus able to create the entry in the log, carrying the message number to be displayed by EventViewer. After executing the driver, we can open the EventViewer; and it is able to find the entry generated by our driver, and then to see the message.

Oops! We still have to tell EventViewer what is the responsible module for storing the strings. In our case, the messages are in the driver itself. To achieve this, we must add a key with the name of our driver in the EventLog registry key. Within this key, we still have to create two values indicating the module path which contains the messages and what kinds of events our driver can generate. Look at carefully the registry path in the figure below.

Once we set the values described above in the registry, just reopen the same event so that the message is displayed as it should be. Remember that the driver does not need to send the event again for the message to be displayed correctly. The driver sends only one entry with the message identifier. This was already done regardless of the message module has been configured. In the figure below, we can see the messages in Portuguese and English, depending on the language of the operating system. What a cute!

As this post is getting bigger than I had expected (for change), I will divide it into parts. I still want to comment on how to use its input parameters to clarify the doubts that some of you may be asking now: “If the log entry is initially in a linked list, and only after a time that is actually going to the disk, can entries be lost if the machine falls in this interval? “.

Is the butler really culprit? Is the Matrix inside another Matrix? Does Tostines sell more because it’s fresh, or is it fresh because it sells more? Do not miss it!

CYA … 🙂

Did you say I don’t like Blue Screens?

September 3rd, 2007 - Fernando Roberto

I’m not saying I love them. Blue screen is a sign that something went wrong but, it is better you see it than a customer calling you saying that he saw one. So we need to do our best to make it make it appear while your driver is under test. I’ve seen programmers escaping the blue screen just trying to hide themselves behind an exception handler. I do not need to say this is not that useful. This way, you just defer to find mistakes that you or someone else will find eventually. Computers that are using your drivers may have a sudden reboot here or there. But even blue screens can happen, it’s all a matter of luck; however, your customers can realize that using your drivers makes them too unlucky. In this post, I’ll give you some tips about how we can see more blue screens in our test drivers.

Going through with F8

Do not take this as “Test Yourself,” but take it as testing all the returning codes that you can. Remember that if something gets wrong, it is not just a MessageBox that will appear, but usually, everything ends up in “blue”, sooner or later. Let’s hope for “sooner”. I often use to say that every code deserves, at least, an F8 walkthrough, which is my Step Into key. Yeah, I know it’s different of the usual one, but when I started using Visual C/C++ 1.52, its keyboard layout was it. But back to the point, once I have made a function that should simply read a file, I went through the code with F8 and received a return code that was not STATUS_SUCCESS, but passed the NT_SUCCESS() macro. The haste induced me to ignore it. It was not an error, it was just a warning. Weeks later, the test team told me that, for any reason, the driver was returning garbage when reading the file. A bit of debugging showed me that same returning code. Only after having loked at its definition in the file ntstatus.h I could understand everything.

//
// MessageId: STATUS_PENDING
//
// MessageText:
//
//  The operation that was requested is pending completion.
//
#define STATUS_PENDING                   ((NTSTATUS)0x00000103L)    // winnt

During debug sessions, the system has enough time to do the I/O that was deferred previously; but, freely running the code, the story can change.

A code that has passed on its F8 test can run in various existing and imaginable environments. Therefore, this first phase is just to remove the grosser errors. After that, it is the test team should beat the victim. There are people who really have a gift to test software. I had worked at a company where the person who used to test the products should have had a personal problem with the software that we produced (or with us, go figure!). I could test the software for days, but when I delivered it for testing, it did not take half an hour for him to call me back saying that phrase that has become his trademark: “Too bad !!!”. There was no explanation. We used to say that his PC had been formatted over an Indian burial ground. No wonder that developer’s testing is so frowned upon.

Using ASSERT

I imagine most of you have already heard from ASSERT macro. This macro is intended to ensure that a certain condition is true. If the condition is false, a blue screen just pops up. Wow, what an incredible macro that helped us very much! Yeah, I know, it is not quite that. Actually, this is the behavior we would have if the debugger was not attached to the system. Then, that macro would be perfect only in Checked. Do I must remind you that  Checked means Debug and Free means Release? Well, already done. If we take a look at its definition, we will see that the obvious thing has already been thought.

#if DBG
 
#define ASSERT( exp ) \
    ((!(exp)) ? \
        (RtlAssert( #exp, __FILE__, __LINE__, NULL ),FALSE) : \
        TRUE)
...
 
#else
 
#define ASSERT( exp )         ((void) 0)
 
...
 
#endif // DBG

But what happens if we have the debugger attached to the system? Good question, this is a really interesting question; I’m glad you have asked it. Has anyone ever told you that you have some knack for programming? Anyway, I have changed one of our examples to enforce this condition.

extern "C"
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObj,
                     IN PUNICODE_STRING pusRegistryPath)
{
    //-f--> I think I'm sure that this count is right.
    ASSERT(1 + 1 == 3);

If the condition fails and we have a kernel debugger attached to the system, it will display the condition that had failed at the output window and it will request us an answer among the  four alternatives, as it is shown below.

As you can see, the ASSERT is practical, easy and not fattening. It just requires a bit of brain, as well. I’m sying this because we’ve all had bad days, and after 11:00 pm, no programmers should answer for any produced code. Once, it took me some time to figure out why the code below was not working properly. Although everything was working perfectly fine when compiled on Checked, it seemed to me that a certain function simply was not being called in Free. If you take one more looking at the definition of this macro, you will see that the condition disappears when compiled in Free, and in this case, the call too.

    //-f--> Programming is just not enough, thinking is required.
    //      << Don't copy this >>
    ASSERT(DoSomeThing() == STATUS_SUCCESS);

A system in Checked

Wouldn’t it be nice to have an entire operating system full of ASSERTs and tests to detect the slightest sign of trouble and on finding one, the system would present us with a nice blue screen? Well, you’ve probably heard of the Checked Build versions. They are exactly what I have just written. An entire operating system built on Checked. This means that all of ASSERTs that were at the sources have been included in the final binaries and they are checking the system for you. It may seem silly to have to install your driver on one of these systems, but believe me, it is worth. I’ve had drivers that worked very well for months until my manager suggested that they should be tested on Checked versions. From the top of my arrogance, I thought to myself: “I see no reason for that”. Well, we are always learning as we are living. The test machine has not even has started with my drivers. Several blue screens were shown, one after another. Checked Build versions are still able to check for deadlocks. Could you guess what would happen if a spinlock was held for more than a certain time? I bet you do.

Since Checked Build versions are foolproof, I can use them as my default system? Actually you can, but everything is much slower, thousands of checks are being made all the time and the code has no optimization applied. In a fresh installation without installing any additional software, you can find ASSERTs at Internet Explorer or other programs. That’s right, not just the kernel is Checked, but the entire system is as well. You always have to let a kernel debugger attached to your system, because the slightest hint of smoke would be more than an enough reason for one more blue screen.

If my driver has passed by the Checked Build, then is the driver perfect? Sorry to disappoint you. Remember that the performance impact caused by so many tests can hide problems like race conditions. The ideal is to test your drivers in both versions. One setting allows us having only the system image and HAL in Checked, while the rest of the system stays in Free. This may provide you with additional tests in the kernel while the rest of the system runs the lighter version. This link explains how.

OK, is my driver beautiful now?

Actually, this still is only the minimum you should do. Another excellent tool for generating blue screens is the Driver Verifier. This application works in conjunction with the system to track actions from a list of drivers composed by you. From Windows 2000, DriverVerifier comes already installed. Try it now! It doesn’t require any skill or even practice. Type “Verifier” at the Windows “Run…” window and a simple Wizard starts. As I intend to finish this post still alive, I won’t describe how to use this tool step by step, but there are several links on the product page that can help you with this.

OK, now I know you really love Blue Screens!

To get an idea of how importnat a search for blue screens is for a software company, Microsoft promotes IFS Plug Fest in order to test the interoperability of different products that implement File Systems or filters for them. The event is for free, but each company must pay for travel expenses and lodging of its representatives. In these meetings, the professionals around the world gather together to do the tests with each other and they can get in touch and ask possible questions concerning this complex model of development. There are also sponsored seminars that discuss common problems faced by this community. I have not been at one of these, but maybe one day I will be able to.

Stay calm, there are even more

Beyond the mentioned tools, there are some others that I could not forget mentioning; however, I won’t take long on explaining how each of them works.

  • Prefast – A tool that analyses the source code to be compiled by the DDK, looking for usual programming mistakes for Kernel drivers.
  • Static Driver Verifier – That tool makes a binary analysis on the driver file already built. The test takes longer but, it detects more defects than using Prefast.
  • Hardware Compatibility Test – A set of software to apply tests to get drivers certified by Microsoft. It was discontinued a while ago.
  • Driver Test Manager – That’s the new test software used to get your drivers certified.

After so many blue screens, only drinking some coffee can help us!.
CYA!

Using FileObject and FsContext

August 29th, 2007 - Fernando Roberto

Well, with some posts and a little patience to endure my jokes, we can construct a simple driver that responds to calls from applications. A call to CreateFile function creates a connection between the application and device, returning a handle which will later be used to route reading/writting requests to the device. If there are more calls to CreateFile function, new handles will be returned. Like it happens with files, each handle has its own context. Suppose you open the same file twice so, you will get two handles with different contexts. A reading of 100 bytes from the first handle would cause the file’s current location be different from the initial position. A new reading using the same handle would result on getting the next 100 bytes ahead of the ones already read. Now if you used the second handle, you would get the first 100 bytes of the file. That’s because each handle would keep different file locations. In this post, I will talk about how to keep this context between the different opened handles for your device.

The issue in practice

Not long ago, in one of my posts, I gave a basic driver example that implements a linked list of buffers that were written to the device. The same buffers are obtained from further reads to the same device. This post will be based on this sample driver to make the tests and suggested modifications. The driver’s string list was implemented as a single global variable. If there is a single list, the various handles gotten from the applications will manipulate this same list.

Remember that the test program writes the strings, that were typed in a console at the device until an empty one is supplied. When this occurs, the application starts reading and displaying the results at the screen. Thus, the same provided string sequence should be displayed. To reproduce the problem, follow the steps below with the test driver already installed:

  1. Open up a Prompt window and execute a test program instance.
  2. Enter the string sequence that goes from “111”, “222” until “555”, but don’t enter the empty one yet.
  3. Open up a new Prompt window and execute another test program instance.
  4. On this new window, enter the string sequence that goes from “666” until “999” and then, an empty one.
  5. Go back to the previous instance and then, enter an empty string into the test program.

You should get an output as it is shown below. Note that the first test program instance has not read any string, despite having written several ones. These missing strings were got to the second test program instance.

Separating lists

Creating an algorithm able to separate these lists using the process identification could even work, but try to imagine that a single application could use two different libraries, which in turn, would use these lists. In this way, one library list would merge into the other library list, since both libraries are at the same process. To separate the context using various device openings, we use the FileObject member of the current stack location.

When an application calls the CreateFile function, this request goes to the ObjectManager which verifies the existence of the desired object, then checks whether the application has rights to obtain a handle to this object, and, if everything is agreed, the request reaches your driver in the form of an IRP with its Major Function as IRP_MJ_CREATE. To get the FileObject referring to this new connection being created, you need to do as shown at the code below.

    //-f--> Gets the FileObject referring to this list
    pStack = IoGetCurrentIrpStackLocation(pIrp);
    pFileObject = pStack->FileObject;

This or that list?

All subsequent operations that come from the same object instance will come with the same FileObject. This helps tracking the context of a certain device opening from the several ones that your driver receives.

So, all that I need to do is to create a list of FileObjects and then link each one of them to a string list? The idea of linking the FileObject to a structure that holds this context is so obvious that the system has already reserved a space into the FILE_OBJECT structure exclusively for this use.

typedef struct _FILE_OBJECT
{
    CSHORT  Type;
    CSHORT  Size;
    PDEVICE_OBJECT  DeviceObject;
    PVPB  Vpb;
    PVOID  FsContext;
    PVOID  FsContext2;
    PSECTION_OBJECT_POINTERS  SectionObjectPointer;
    PVOID  PrivateCacheMap;
    NTSTATUS  FinalStatus;
    struct _FILE_OBJECT  *RelatedFileObject;
    BOOLEAN  LockOperation;
    BOOLEAN  DeletePending;
    BOOLEAN  ReadAccess;
    BOOLEAN  WriteAccess;
    BOOLEAN  DeleteAccess;
    BOOLEAN  SharedRead;
    BOOLEAN  SharedWrite;
    BOOLEAN  SharedDelete;
    ULONG  Flags;
    UNICODE_STRING  FileName;
    LARGE_INTEGER  CurrentByteOffset;
    ULONG  Waiters;
    ULONG  Busy;
    PVOID  LastLock;
    KEVENT  Lock;
    KEVENT  Event;
    PIO_COMPLETION_CONTEXT  CompletionContext;
    KSPIN_LOCK  IrpListLock;
    LIST_ENTRY  IrpList;
    PVOID  FileObjectExtension;
 
} FILE_OBJECT, *PFILE_OBJECT;

The FsContext and FsContext2 fields are used for this purpose and unless you implement a filter, you can at ease use them. To make each device opening has its own list, it is necessary to store the head list address in one of these fields, as it is shown below at the new implementation of OnCreate. The whole source code changes are available for downloading at the end of this post.

/****
***     OnCreate
**
**      This routine is called whenever an application or driver
**      tries to get a handle to the device we have created.
*/
 
NTSTATUS
OnCreate(IN PDEVICE_OBJECT  pDeviceObj,
         IN PIRP            pIrp)
{
    NTSTATUS            nts = STATUS_SUCCESS;
    PBUFFER_LIST_HEAD   pListHead;
    PIO_STACK_LOCATION  pStack;
 
    //-f--> A hello to the debugger...
    KdPrint(("Opening EchoDevice...\n"));
 
    //-f--> Allocates the list head for this IRP_MJ_CREATE
    pListHead = (PBUFFER_LIST_HEAD) ExAllocatePoolWithTag(
        PagedPool,
        sizeof(BUFFER_LIST_HEAD),
        ECHO_TAG);
 
    if (pListHead)
    {
        //-f--> Initializing the buffer list and its Mutex
        InitializeListHead(&pListHead->BufferList);
        KeInitializeMutex(&pListHead->Mutex, 0);
 
        //-f--> We store the context to the FileObject.
        pStack = IoGetCurrentIrpStackLocation(pIrp);
        pStack->FileObject->FsContext = pListHead;
    }
    else
        nts = STATUS_INSUFFICIENT_RESOURCES;
 
    //-f--> Here we complete the IRP with success.
    pIrp->IoStatus.Status = nts;
    pIrp->IoStatus.Information = 0;
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return nts;
}

Is a FileObject equivalent to a Handle?

No. Initially we have a handle for each FileObject, but the number of handles to a FileObject increases as their handles are duplicated. When the CreateFile function is called, your driver receives an IRP_MJ_CREATE; however your driver is not alerted when someone calls the DuplicateHandle function. Thus, each handle points to an object but an object can be pointed out by several handles.

Who will clean this mess?

When all references to a given FileObject are released, your device will receive an IRP_MJ_CLOSE. Let’s use this event to clean up any remaining strings on the list.

/****
***     OnClose
**
**      I'll make it simple just saying that this routine is called, whenever
**      an application or driver closes the handle which was previously opened.
**      Actually, this does not happen exactly in this way. (postponed to a future post)
*/
 
NTSTATUS
OnClose(IN PDEVICE_OBJECT  pDeviceObj,
        IN PIRP            pIrp)
{
    PBUFFER_LIST_HEAD   pListHead;
    PIO_STACK_LOCATION  pStack;
    PLIST_ENTRY         pEntry;
    PBUFFER_ENTRY       pBufferEntry;
 
    //-f--> A hello to the debugger...
    KdPrint(("Closing EchoDevice...\n"));
 
    //-f--> Retrieves the list referring to this FileObject.
    pStack = IoGetCurrentIrpStackLocation(pIrp);
    pListHead = (PBUFFER_LIST_HEAD)pStack->FileObject->FsContext;
 
    //-f--> Here, we remove all the remaining nodes that were not read
    //      by the application. That would happen if the application had called
    //      WriteFile but not ReadFile.
    while(!IsListEmpty(&pListHead->BufferList))
    {
        //-f--> It takes the first node from the list.
 
        pEntry = RemoveHeadList(&pListHead->BufferList);
 
        //-f--> Retrieves the base address from the node
        pBufferEntry = CONTAINING_RECORD(pEntry, BUFFER_ENTRY, Entry);
 
        //-f--> Finally releases the memory used by
        //      that node.
        ExFreePool(pBufferEntry);
    }
 
    //-f--> Releases the memory used by the head list.
 
    ExFreePool(pListHead);
 
    //-f--> Here, we complete the IRP with success.
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

When an application is terminated or even when it falls unwillingly, all those application handles are closed by the system. Thus, it is guaranteed that your driver will always receive the IRP_MJ_CLOSE related to the object that was released. Actually, there is a little story about IRP_MJ_CLEANUP and IRP_MJ_CLOSE that we will let to the next time.

Once implemented the change, each opened handle will own list, unless the handle is duplicated. If we repeat the same sequence of the listed steps above to force the error, we will get the following output.

In a future post, I’ll show you how to give names to the lists in order to allow different processes begin able to open the same list from a known name.

CYA! 🙂

FileObjEcho.zip

Debug Option or Security Fault?

August 21st, 2007 - Fernando Roberto

Some time ago I have written a post that talks about replacing faulty drivers using WinDbg. This is nn excellent technique to substitute drivers which, because of some kind of problem, prevents the machine from starting. However, to be able to enjoy all of this facility, there must be a connection between the system and the Kernel Debugger. This brings us to my last post that ostensibly talks about how to enable this feature just being in front of the machine, and at worst, having a serial port. All of this is too easy, after all; you never know where an unknown problem can happen and much less, in which machine ir is going to happen. But all of this convenience has a price. The side effect is that anyone can debug the kernel of a machine without ever having any valid account on it. In fact, a laptop, a cable and a serial port are beyond than enough weapons to dismantle the system security, without a password, wireless network and even without brute-force, only doing Kernel Debugging.

Installing a Driver Through the Serial Port

Drivers are a kind of software that needs to be reliable and very well written. After all, they run at the lowest level of the system. With the drivers, it is actually very easy to cheat the system, since most of the system runs on the service offered by the kernel. The posts above mentioned have motivated me to write a small proof of concept that aims to inject a driver which installs itself on the system. This driver, in turn, extracts and installs a program into the Windows which will run every time one logs on the system. To do that, we only need a machine that can be debugged. My last post shows that this condition is not so difficult to be found. Want to try? What’s the percentage of computers around you that have serial ports?

Step by Step

This is one of those posts that come with a code available for downloading. The whole procedure shown here can be reproduced and modified by the readers of this blog. This is not a blog devoted to attack Windows; and my goal here is purely didactic. I won’t explain every line of the sample code; otherwise, this post would be very extensive. However, I am willing to answer any questions about this. The figure below outlines the steps to be taken to achieve our goal.

  1. The first step occurs when compiling the sample driver. The solution has three available projects. The first project is the application to be injected. In this case, a Win32 application that just displays a message and is ended up. We will refer to this application as MyFile.exe. The C/C++ Run Time was removed, in order the smallest possible application to be obtained. This will be worthy a lot when you have to transfer everything via a serial cable at 19200. The second project is just one tool. Using the archive MyFile.exe, which must already be compiled, this tool generates a header file that creates a variable of the type array of characters contained into its body, where the file passed as a command line. This is not the most advanced method of making a Self-Extractor, but it is enough for our purpose here. Let’s call this tool HeaderFile.exe. When the driver is compiled, this header file is included making the variable containing the application be injected. We will call our driver MapInject.sys. The driver in this example is compiled using ddkbuild.cmd with Visual Studio 2005. More details with this regard in this post.

    /****
    ***     WinMain
    **
    **      This application does almost nothing and doesn't use C/C++ Run Time
    **      so that we can avoid a bunch of codes.
    */
     
    int WINAPI WinMain(HINSTANCE   hInstance,
                       HINSTANCE   hPrevInstance,
                       LPSTR       lpCmdLine,
                       int         nShowCmd)
    {
        //-f--> That is it...
        MessageBox(NULL,
                   L"A serial port was found",
                   L"DriverEntry.com.br",
                   MB_OK | MB_ICONEXCLAMATION);
     
        return 0;
    }
  2. Using driver mapping techniques demonstrated in this post, we can make our driver be loaded in replacement of any driver in the pre-existing system. Obviously, our driver will not perform all the steps that the original driver should perform, thus, we must not choose a driver which does essential tasks for the system loading, such as disk drives, video or something like that. Particularly I have chosen using the Partport.sys parallel port driver, which is a driver that exists in any basic Windows installation and don’t not make us so missed while playing here. This driver is not also a Boot driver, because boot drivers need a special Loader on Windows XP to be mapped by the Debugger.

    map 
    \Windows\System32\drivers\parport.sys 
    Z:\MapInject\Driver\objfre_wnet_x86\i386\MapInject.sys
  3. After the driver has been loaded and mapped by the system, all that we need to do on the system is just some willingness. When our driver is first loaded, it is loaded in replacement of Parport.sys. In this condition, the driver must install itself on the system. Some things have never changed. To install a driver on Windows NT platform just create a few key values in the system registry, as described in this post. This will still work on Windows Vista. Our driver does this at this time.

  4. After making the registry modifications, now we have to rename our driver because it was mapped and, for that reason, it gets the replaced driver name. Thus, a function changes the name of the driver from Parport.sys to MapInject.sys. This driver could remain installed using the original name but, our driver would be replaced by the system restore when the driver mapping didn’t take place.

  5. As you know, our driver has a variable that contains the whole body of our application. Thus, it is very simple to extract the application and save it into the disk. The System32 directory comes in handy as a destination for our application. Since we’re running on system account at this time, we have no problem on doing it.

  6. Now that our application has been extracted, we have to make it run automagically. Some of you may know that there is no documented or reliable enough way to create a process from the system kernel however, there are several places on registry where we could write and ask an existing process in Windows to start our application. One of the most classic registry keys that allow this it is the Run of Windows Explorer; but, as shown in this application, there are many other keys to be chosen. When a user logs on the system, the Shell enumerates this key and executes each line in the user’s account that is logged on. This will make our program be executed.

Making it Work

Again, a virtual machine fits like a glove for this type of thing. If you do not know how to do Kernel Debugging using a virtual machine, this post can help you. Everything is very simple after the driver being already encoded. The only thing needed to complete the test set is a mapping that replaces the Parport.sys driver for a MapInject.sys file, as it is shown in the map file above. After that, just wait until the system loads up and everything is done. Any user who logs on to the system will see the message of our program. A point to note is that, even if the user is smart enough to find the MyFile.exe file, our change at the registry and delete them, they will be recreated when the MapInject driver starts on each system reboot. To stop this process, just remove the MapInject driver from the system.


Password? What For?

Windows protects its most important resource for most users, allowing only administrators to have access to the machine settings that can compromise the system stability. This phrase is kind of “cute” and makes several system administrators to feel comfortable. But life is a box of surprises, and in a nice sunny morning, a post is published on a RootKits website, providing tips on how to log into a system in Debug Mode without any password. Recently, another post has come out about it and I will not repeat everything here but, I can say that this same technique also works with Windows Vista. In fact, if you can log into the system without using any password, you will not need to map a driver to make it run on this machine. Just log in as an administrator, install it and go ahead but, I admit that it was at least funny to write this proof of concept, and moreover, the sources may still be an example for some tasks you want to do in Kernel Mode.

A possible solution? Maybe letting your serial port disabled in BIOS, which should be password protected, is something to be done. But there is not much you can do when someone has physical access to the machine.

Enough, I already wrote too much.
Hugs!

MapInject.zip

Emergency Debug

August 15th, 2007 - Fernando Roberto

Suddenly, a blue screen. That’s right, that driver which you had not seen its source files for so long, which was your model for stable driver, the one that was installed in the machine of the CEO where you have worked, the same one that suddenly decided to take revenge and simply got a blue screen at the system startup. That means the machine turns on and resets and resets and resets in an infinite loop that runs in parallel with your resignation letter printing which, by the way, has being printed on another PC. To debug this machine and see what is happening, and then save your project, your job, your dignity, your marriage, and why not, your life: you must configure the system to enter in Debug Mode so that our inseparable friend WinDbg can take action. But how about setting it to debug a machine that is not even starting? Today I will talk about the support that Windows offers to avoid such an embarrassing situation to connect a Debugger in a computer that has never its settings for Kernel Debugging enabled, either through BOOT.INI or BdcEdit.

This post assumes that you have already known how to do Kernel Debugging and uses some related terms with this regard. Since Windows 2000, if you press the F8 key while the machine is running the NT Loader, more precisely, 2 seconds before the system starts to loading up, the system will freeze up and displays the menu as it is shown below.

Select the “Debugging Mode” item. It will take us to the list of installed operating systems. Select the system and the rest is up to the WinDbg. Under these conditions, setting the serial connection would be applied a baud rate of 19200 on the higher serial port existing on your machine. That is, if your PC had two serial ports, then the connection would be made on COM2 at 19200. At least that’s what tells us the reference.

But in real life…

If you do some tests, will also conclude that we cannot believe everything we read. Both Windows 2000 and Windows XP always use COM2 port with 19200.

My computer is modern and it only has one serial port. Can I kill myself? That is it, even in theses cases, the system tries to use COM2, even if it does not exist. But don’t worry, life is beautiful and there is still hope. Some BIOS allow you to configure the base address and interrupt about the only existing serial port. This will help us greatly; after all, the NT Loader is not a Win32 application that requests a handle to the COM1 or COM2 device through the Object Manager. It simply follows the addresses adopted as standards.

  • COM1: Address 3F8h, IRQ 4
  • COM2: Address 2F8h, IRQ 3

In this way you can configure the only port (COM1) to answer for the addresses which are normally used for COM2. The BIOS usually has a list of features commonly used by serial ports. Thus, any autistic chimp could make this change.

But my computer is even more modern and has no serial port at all. Can I kill myself now? Hope it is the last to die. You can still install a PCI card Expander (PCMCIA in case of notebooks) that provides us with the needed serial ports. It is important to note that this board should create additional ports COM1 and COM2 at the above standard addresses.

You don’t understand, I am an unlucky bastard who bought a MacBook 13″ which, besides not having serial ports, also offers no PCMCIA or ExpressCard slot. Can I kill myself now? As I have mentioned in another post, serial ports created from USB ports are not an option for using in a TARGET machine during the Kernel Debugging. This connection is made by the NT Loader and serial ports that it knows and they are the only those ones the addresses mentioned above. OK, now you can kill yourself.

Anything that could get better?

Windows Vista ignores the documentation mercilessly. The default setting for Kernel Debugging when you press F8 during its Boot is for using  COM1 port with 115200. But if you used the Bcdedit application with the /DbgSettings to modify the connection settings for Kernel Debugging, then these same settings will be used  when F8 is pressed. That is, if you are an unlucky bastard that, like me, have configured the IEEE1397 port for debugging into your MacBook, then these are the settings used when you press F8.

Pode parecer brincadeira, mas é realmente frustrante ter que ir até à máquina do cliente onde seu driver está dando trabalho, armado dos seus vários aparatos tecnológicos, tais como notebook, porta FireWire, cabo serial e assim por diante, e não poder fazer nada simplesmente porque você não consegue conectar o depurador.

It may seem fun, but it’s really frustrating having to go to the customer machine where your driver is annoying, armed with all of your various technological devices such as laptop, FireWire port, serial cable and so on, and simply could not do anything because you cannot attach the debugger.

Once again, I hope I could help in anyway.
Cya!

My Personal GINA

August 8th, 2007 - Fernando Roberto

Due to my returning classes to the university, my time to write posts has now subsided, and once again, you will have to put up a post that says nothing about drivers. In fact, I wrote this GINA sample while I was writing a post about drivers. My friends Slug and Thiago have told me I should just leave this post and write one which could be applied to Windows Vista, since GINAs have not longer been supported on Windows Vista. Then, I have ended up forgetting this code over here. Poor thing… Anyway, as I have thought the result was at least funny, I’ll leave this Stub GINA here (sources included), that allows us to change the title of the dialogs presented.

What is a Stub GINA?

It would be even better to say what a GINA is. I have written a few things about this subject on the post that talks about how to use SoftIce, but in summary, GINA is the system component that implements the Graphical Interface for Network Authentication for users on the computer. Do you still want it more concise? It is the small screen for system logon. GINA is responsible for receiving the data that identifies a user and pass them to the components that can validate their password and generate the token with the user’s credentials. This token is used to create the session that the user is logging in and where your desktop will be created. GINA also implements the interface that makes the password changing, lockout and station shutdown. I will not detail all the steps here: everything is explained at the Platform SDK.

The original Windows GINA is implemented into a DLL named Msgina.dll that is into the System32 directory. To implement a new GINA, you must create a new DLL and “tell” the system that this DLL will be the new GINA via registry key. However, creating a GINA is not that easy as it seems to be. I have developed some of it and let’s say  documentation could be better. GINA has many responsibilities and if you just want to supplement or change any of its default behavior, you should simply create a stub GINA. Stub GINA is a DLL that exports all the functions GINA should do, but it passes the calls to the original system GINA, thus, giving us the option to only change the desired features.

This is not a Tutorial

If you want to learn how to develop a stub GINA and need a starting point, then go to the Platform SDK Samples folder and use the example that can be found at C:\MSSDK\Samples\Security\Gina\GinaStub. The project I’m leaving here performs some juggling to avoid too many repetitive codes and it also does not use the C/C++ Run Time, so that, it can be compiled using Visual Studio 2005 and still be able to run on a Windows NT 4.0.

Installing a GINA

To install a GINA, you must create a value called GinaDLL at the Winlogon registry key, as it is shown below. This value is queried by Winlogon.exe and, if this value does not exist, the default GINA is loaded, though. The GinaTitle value should contain the message that will appear at the dialog titles. Actually, this value has nothing to do with Windows: our stub GINA is the one that reads this value. From the source files, available for download at the end of this post, there is a script file that sets these registry keys to make your life easier.

Make a copy of Gina.dll file to the System32 directory. Make sure that everything is all right before rebooting the machine to make these changes take effect. If something is wrong and Winlogon.exe is not able to load the GINA, the window below appears before anything else.

This MessageBox design has improved greatly from Windows 2000. If the same problem happens on Windows NT 4.0, the following message would be displayed.

Useful tips for GINA coders

Writing GINA is the opportunity for User-Mode developers generating their own blue screens. Your DLL is loaded by Winlogon.exe, and thus, it runs on its process address space. This means that if you have an unhandled exception, this will bring this process down. Winlogon is a critical process and it cannot be overthrown. In short, the blue screen is shown up.

The next tip is kind of silly, but it’s worth being commented. During the process of developing a GINA, it is natural to have multiple builds and you will need to replace the GINA that is being used for the new one. You may have tried to override it, but as always, Winlogon.exe keeps it loaded and you cannot delete the current one. Like any DLL under these conditions, you can rename it while it is being used by a program. This lets you put a new version at the System32 directory without having to delete the one which is currently running. When the system is restarted, Winlogon will pick up the new GINA and drop the old one.

I hope you enjoyed the new toy. Now I need to continue that post.
Have fun!

TitleGina.zip

ExAllocatePool with Tagging

July 19th, 2007 - Fernando Roberto

My friend Slug was always a great reference on a different way of seeing things. He usually has a different viewpoint from mine, and I have always learned from these other viewpoints. This post will be different than you are accustomed to seeing here. Wanderley had introduced the subject “How to be a better programmer in the next six months” and brought us this new point of view (at least for me) to see blogs, known as Tagging. Here is my reply.

I cannot say that everything will be different in the next six months; I am really going to make every effort to turn a better programmer. I think my answer is similar to the response that Strauss has given us, but not equal.

As some of you know, beyond the work day at Open, I have still faced the university to complete my degree in Computer Engineering. This course has brought me new concepts about micro-processing, embedded systems, computer architecture and other subjects different from Windows programming in C/C++. I can say that it has opened up a little my range of opportunities to learn new things. Developing drivers is, in addition to programming, establishing communication between two near worlds, but virtually unknown to each other. Usually, who knows one of these sides very well, don’t do the same about the other one. It is natural. I have already known a bit of software, and every six months to go, I know some new things about hardware. Knowing better the hardware, I think I can better understand and program the operating system that interacts with it.

Speaking of new things, this month I have found myself buying a book about Device Drivers for Linux. I have been Microsoft ever since. Obviously, I am referring to the professional aspect. My days of MSX and CP200 exert the same influence that my kindergarten does about my resume. The book has not arrived yet, but certainly, it will feed my database about Software/Hardware interaction and it’ll allow me to draw a parallel between these two development platforms, Windows/Linux, and thus, I’ll be able to better understand what they have in common, both concerning Software/Hardware interaction about operating system concepts.

A friend of mine once told me that one of the best ways to learn something is teaching. I have been writing this blog for almost a year and have learned a lot from it. Writing about some things, which would be simple, made me take another view and see that there are still more and more details about it. Thus, I end up learning the details that were lost at first. Some lectures I have given the company and the device driver course work as a Boost for this learning way. I confess I’m enjoying it.

In conclusion and in summary, I will continue studying to get my degree, reading about Linux, continue blogging and being questioned about its issues. This subject is very fun for programmers, but we cannot forget that we are also husbands, sons, brothers, friends and so on. This post can offer you that courage to start studying everything and devote all your time available for the benefit of being the best developer but, cycling and going to the beach are also part of excellent programmer’s rising.

Enjoy in moderation. 😉
See you…

Bug on my Boot driver! Now what?

July 16th, 2007 - Fernando Roberto

Writing drivers is a task that must be done with little care. After all, any unresolved situation between your code and the operating system will result in a beautiful blue screen. But everything in this life has a cure and fortunately God has created the debugger to deal with these situations. After fixing the problem, it is just to change the .sys file and that’s it. Just changing the file means that the machine is expected to be in a stable condition: then, replace the file on the system and reboot the machine. But life is a surprising box and you may not believe, but most drivers are automagically started. Well, in this case, the only think we can do is to pray that the error does not happen until the driver is replaced. Otherwise, we would have to use some resources, such as putting the hard drive of victim’s machine on another computer to replace the sick driver. When there is no one else’s computer, use the Recovery Console in XP to avoid the sick driver being loaded. With some luck, this driver is not a filter attached to something important like disk, video or File System. You know, if a filter is not able to be loaded, then the main driver, either. In summary, there is a lot of juggling you can invent to replace a buggy driver that is automatically loaded. Is there anything that does not depend much on luck or even on the creativity? Today, I will talk a bit about the replacing driver system with WinDbg.

Mapping driver files

You can map the driver files so the WinDbg can replace it at the time it’d be loaded. Is not that beautiful? For this, you must use the .kdfiles command. With this command, you make a link between the driver that runs on the Target machine and the one that has been fixed on the Host side. One way to do this is to initially create a file that lists these two drivers. This file should be a simple text file where the syntax is shown below. This file can have any name and extension.

map 
\??\C:\Windows\System32\drivers\MyDriver.sys 
C:\My_Driver_Project_Folder\MyDriver.sys

The word map marks the beginning of a mapping; this does not change. Next line is the driver file path to be replaced from. This line should have the same format used in the ImagePath value under the driver key that is on registry. The last line is another driver path. This path could point at a driver in the Host machine itself or on the network.

This mapping works only in Windows XP or higher, in the Target machine, obviously. If you’re not familiar with terms like Host and Target, check this post out.

Once created the file, you will use WinDbg to launch the following command:

kd>.kdfiles C:\Any_Folder\MyFileMap.txt

Thereafter, whenever the driver is loaded by the system, the kernel checks whether this file is mapped in the debugger, and if so, WinDbg sends the new driver through the debugger connection, such as serial, USB or firewire. For large files, I recommend using firewire. You must understand that the file on Target system’s disk is replaced by the new version. This means that in the forthcoming launches of our driver, the new version will still be loaded, even if the debugger is not connected to the system.

Does this really work?

To make things a little clearer, let’s see a practical example. To do this, we need a very simple driver source, or even useless, which can be found here. Let’s change its DriverEntry function, so that it will be as shown below:

NTSTATUS DriverEntry(IN PDRIVER_OBJECT  pDriverObject,
                     IN PUNICODE_STRING pusRegistryPath)
{
    //-f--> Let's use __DATE__ and __TIME__ to change this message
    //      each time this driver is built.
    DbgPrint("This driver was built on "__DATE__" "__TIME__"\n");
 
...

So, we build an initial version of the driver and we install it on the Target machine in the way you best think, but we’ll assume that after the driver has been installed, the registry will be as shown below.

If we start the driver, we will have something like the following string in the debugger output:

This driver was built on Jul 16 2007 00:04:03

Now, we’ll create the mapping file. Here I will save it as Z:\Sources\DriverEntry\Useless\map.txt. Following the same format the driver was registered in the registry, our mapping file should have the following content:

map
\??\C:\Windows\System32\drivers\Useless.sys
Z:\Sources\DriverEntry\Useless\objchk_wxp_x86\i386\Useless.sys

Notice that the folder where I saved my file map.txt is the same where the driver sources are. This is just a matter of organization. The mapping file could be in any folder. Then, use the command .kdfiles as shown below. Notice that we list the existing maps simply using the same command without any parameters.

kd> .kdfiles Z:\Sources\DriverEntry\Useless\map.txt
KD file assocations loaded from 'Z:\Sources\DriverEntry\Useless\map.txt'
 
kd> .kdfiles
KD file assocations loaded from 'Z:\Sources\DriverEntry\Useless\map.txt'
\??\C:\Windows\System32\drivers\Useless.sys ->
Z:\Sources\DriverEntry\Useless\objchk_wxp_x86\i386\Useless.sys

Afterwards, we’ll Rebuild the driver (that should change that time stamp) and we will restart it. If everything is right there by your side, you should have an output with a time stamp different from what we had before.

kd> g
But now? I did nothing so far...
KD: Accessing 'Z:\Sources\DriverEntry\Useless\objchk_wxp_x86\i386\Useless.sys'
 (\??\C:\Windows\system32\drivers\Useless.sys)
  File size 2K.
MmLoadSystemImage: Pulled \??\C:\Windows\system32\drivers\Useless.sys from kd
This driver was built on Jul 16 2007 00:17:04

Well, if this works for a driver with manual start value, then the automatic one also should work. To see this, change your driver start value to System or Automatic, do a Rebuild again in the driver, and finally, we will restart the Target machine. When the driver is loaded, we have the automatic replacement of its image. In WinDbg, we will have the following:

Connected to Windows XP 2600 x86 compatible target, ptr64 FALSE
Kernel Debugger connection established.
Symbol search path is: SRV*c:\websymbols*http://msdl.microsoft.com/download/symbols
Executable search path is: 
Windows XP Kernel Version 2600 UP Free x86 compatible
Built by: 2600.xpsp_sp2_gdr.070227-2254
Kernel base = 0x804d7000 PsLoadedModuleList = 0x805533a0
System Uptime: not available
KD: Accessing 'Z:\Sources\DriverEntry\Useless\objchk_wxp_x86\i386\Useless.sys'
 (\??\C:\Windows\system32\drivers\Useless.sys)
  File size 2K.
MmLoadSystemImage: Pulled \??\C:\Windows\system32\drivers\Useless.sys from kd
This driver was built on Jul 16 2007 00:25:38

Yeah, it really works! But what if was my driver a Boot driver? This means that the driver image will be loaded even before the connection is established with WinDbg. Have you heard that what has no remedy, it is remedied? Fortunately, this is not applied here yet. There is a way to make this work, even with Boot drivers.

Mapping Boot drivers

To establish a connection with WinDbg, we need to replace the system loader by a special version. This version makes this connection with the Kernel Debugger, even before the Boot.ini file is read. For this reason, the connection parameters are fixed with COM1 and baud rate of 115200. This loader version is located in the directory C:\winddk\3790\debug of the DDK with its ntldr_dbg name. This file should replace the original loader system version that is at the boot drive root with the ntldr name. The debug version must stay with the same name as the original loader.

Before rebooting, we need to change the driver’s start value to Boot and remove the ImagePath value from the registry. As you may know, boot drivers do not have that luxury of using file paths that have drive letters. At the end of the changes, we should have the registry as shown below.

As I said earlier, the driver file path format should be the same as it is on registry, but knowing that now there’s no file path in the registry, so we adopt the same forms adopted by the system. Oh! okay! The same format. And what would it be? To see this format, simply restart the system with the debug loader, which should give us the following screen at boot-up.

This is the time to connect to Windbg using pre-determined parameters connection. This should result in the following output in the debugger.

Microsoft (R) Windows Debugger  Version 6.7.0005.1
Copyright (c) Microsoft Corporation. All rights reserved.
 
Opened \\.\pipe\com_1
Waiting to reconnect...
BD: Boot Debugger Initialized
BD: osloader.exe base address 00400000
Connected to Windows Boot Debugger 3790 x86 compatible target, ptr64 FALSE
Kernel Debugger connection established.
Symbol search path is: SRV*c:\websymbols*http://msdl.microsoft.com/download/symbols
Executable search path is: 
Windows Boot Debugger Kernel Version 3790 UP Checked x86 compatible
Primary image base = 0x00000000 Loaded module list = 0x00000000
System Uptime: not available
The call to LoadLibrary(bootext) failed, Win32 error 0n2
    "The system cannot find the file specified."
Please check your debugger configuration and/or network access.

Now you see the Boot.ini selection menu. Select your option and continue loading the system. A list of boot drivers should be presented, which are the ones loaded before the first breakpoint that the debugger can stop.

BD: \WINDOWS\system32\ntoskrnl.exe base address 804EA000
BD: \WINDOWS\system32\hal.dll base address 806FF000
BD: \WINDOWS\system32\KDCOM.DLL base address 80720000
BD: \WINDOWS\system32\BOOTVID.dll base address 80010000
BD: \WINDOWS\system32\DRIVERS\ACPI.sys base address 80124000
BD: \WINDOWS\system32\DRIVERS\WMILIB.SYS base address 80001000
BD: \WINDOWS\system32\DRIVERS\pci.sys base address 80062000
BD: \WINDOWS\system32\DRIVERS\isapnp.sys base address 80003000
BD: \WINDOWS\system32\DRIVERS\compbatt.sys base address 8000C000
BD: \WINDOWS\system32\DRIVERS\BATTC.SYS base address 80013000
BD: \WINDOWS\system32\DRIVERS\intelide.sys base address 80017000
BD: \WINDOWS\system32\DRIVERS\PCIIDEX.SYS base address 80019000
BD: \WINDOWS\System32\Drivers\MountMgr.sys base address 80152000
BD: \WINDOWS\system32\DRIVERS\ftdisk.sys base address 8015D000
BD: \WINDOWS\System32\drivers\dmload.sys base address 80073000
BD: \WINDOWS\System32\drivers\dmio.sys base address 8017C000
BD: \WINDOWS\System32\Drivers\PartMgr.sys base address 801A2000
BD: \WINDOWS\System32\Drivers\VolSnap.sys base address 801A7000
BD: \WINDOWS\system32\DRIVERS\atapi.sys base address 801B4000
BD: \WINDOWS\system32\DRIVERS\vmscsi.sys base address 801CC000
BD: \WINDOWS\system32\DRIVERS\SCSIPORT.SYS base address 801CF000
BD: \WINDOWS\system32\DRIVERS\disk.sys base address 801E7000
BD: \WINDOWS\system32\DRIVERS\CLASSPNP.SYS base address 801F0000
BD: \WINDOWS\system32\DRIVERS\fltMgr.sys base address 801FD000
BD: \WINDOWS\system32\DRIVERS\sr.sys base address 8021D000
BD: \WINDOWS\System32\Drivers\KSecDD.sys base address 8022F000
BD: \WINDOWS\System32\Drivers\Ntfs.sys base address 80246000
BD: \WINDOWS\System32\Drivers\NDIS.sys base address 802D3000
BD: \WINDOWS\System32\Drivers\Useless.sys base address 8000F000
BD: \WINDOWS\System32\Drivers\Mup.sys base address 80300000
BD: \WINDOWS\system32\DRIVERS\agp440.sys base address 8031B000
Shutdown occurred...unloading all symbol tables.
Waiting to reconnect...

Here the connection to the loader is closed. A new connection would be established if you had selected the Debug entry from Boot.ini, but what we have to note here, is the file path format used to load drivers during system boot. Notice that our test driver is among the drivers from the above list. Let us adopt this same file path format on our mapping file.

map
\WINDOWS\System32\Drivers\Useless.sys
Z:\Sources\DriverEntry\Useless\objchk_wxp_x86\i386\Useless.sys

After modifying the mapping file and save its contents to disk, we update the WinDbg, so it takes this change. Then, we will restart the system.

kd> .kdfiles Z:\Sources\DriverEntry\Useless\map.txt
KD file assocations loaded from 'Z:\Sources\DriverEntry\Useless\map.txt'
 
kd> .reboot
Shutdown occurred...unloading all symbol tables.
Waiting to reconnect...
BD: Boot Debugger Initialized
Connected to Windows Boot Debugger 3790 x86 compatible target, ptr64 FALSE
Kernel Debugger connection established.  (Initial Breakpoint requested)
Symbol search path is: SRV*c:\websymbols*http://msdl.microsoft.com/download/symbols
Executable search path is: 
Module List address is NULL - debugger not initialized properly.
WARNING: .reload failed, module list may be incomplete
KdDebuggerData.KernBase < SystemRangeStart
Windows Boot Debugger Kernel Version 3790 UP Checked x86 compatible
Primary image base = 0x00000000 Loaded module list = 0x00000000
System Uptime: not available
The call to LoadLibrary(bootext) failed, Win32 error 0n2
    "The system cannot find the file specified."
Please check your debugger configuration and/or network access.

The same message sequence happens, but this time, the mapping is done as it should and we have the following output when the boot drivers are loaded.

BD: osloader.exe base address 00400000
BD: \WINDOWS\system32\ntoskrnl.exe base address 804EA000
BD: \WINDOWS\system32\hal.dll base address 806FF000
BD: \WINDOWS\system32\KDCOM.DLL base address 80720000
BD: \WINDOWS\system32\BOOTVID.dll base address 80010000
BD: \WINDOWS\system32\DRIVERS\ACPI.sys base address 80124000
BD: \WINDOWS\system32\DRIVERS\WMILIB.SYS base address 80001000
BD: \WINDOWS\system32\DRIVERS\pci.sys base address 80062000
BD: \WINDOWS\system32\DRIVERS\isapnp.sys base address 80003000
BD: \WINDOWS\system32\DRIVERS\compbatt.sys base address 8000C000
BD: \WINDOWS\system32\DRIVERS\BATTC.SYS base address 80013000
BD: \WINDOWS\system32\DRIVERS\intelide.sys base address 80017000
BD: \WINDOWS\system32\DRIVERS\PCIIDEX.SYS base address 80019000
BD: \WINDOWS\System32\Drivers\MountMgr.sys base address 80152000
BD: \WINDOWS\system32\DRIVERS\ftdisk.sys base address 8015D000
BD: \WINDOWS\System32\drivers\dmload.sys base address 80073000
BD: \WINDOWS\System32\drivers\dmio.sys base address 8017C000
BD: \WINDOWS\System32\Drivers\PartMgr.sys base address 801A2000
BD: \WINDOWS\System32\Drivers\VolSnap.sys base address 801A7000
BD: \WINDOWS\system32\DRIVERS\atapi.sys base address 801B4000
BD: \WINDOWS\system32\DRIVERS\vmscsi.sys base address 801CC000
BD: \WINDOWS\system32\DRIVERS\SCSIPORT.SYS base address 801CF000
BD: \WINDOWS\system32\DRIVERS\disk.sys base address 801E7000
BD: \WINDOWS\system32\DRIVERS\CLASSPNP.SYS base address 801F0000
BD: \WINDOWS\system32\DRIVERS\fltMgr.sys base address 801FD000
BD: \WINDOWS\system32\DRIVERS\sr.sys base address 8021D000
BD: \WINDOWS\System32\Drivers\KSecDD.sys base address 8022F000
BD: \WINDOWS\System32\Drivers\Ntfs.sys base address 80246000
BD: \WINDOWS\System32\Drivers\NDIS.sys base address 802D3000
KD: Accessing 'Z:\Sources\DriverEntry\Useless\objchk_wxp_x86\i386\Useless.sys'
 (\WINDOWS\System32\Drivers\Useless.sys)
  File size 2K.BD: Loaded remote file \WINDOWS\System32\Drivers\Useless.sys
 
BlLoadImageEx: Pulled \WINDOWS\System32\Drivers\Useless.sys from Kernel Debugger
BD: \WINDOWS\System32\Drivers\Useless.sys base address 8000F000
BD: \WINDOWS\System32\Drivers\Mup.sys base address 80300000
BD: \WINDOWS\system32\DRIVERS\agp440.sys base address 8031B000
Shutdown occurred...unloading all symbol tables.
Waiting to reconnect...

And later, our proof that the driver file has been successfully replaced.

Connected to Windows XP 2600 x86 compatible target, ptr64 FALSE
Kernel Debugger connection established.  (Initial Breakpoint requested)
Symbol search path is: SRV*c:\websymbols*http://msdl.microsoft.com/download/symbols
Executable search path is: 
Windows XP Kernel Version 2600 UP Free x86 compatible
Built by: 2600.xpsp_sp2_gdr.070227-2254
Kernel base = 0x804ea000 PsLoadedModuleList = 0x8056d620
System Uptime: not available
This driver was built on Jul 16 2007 01:18:53

I assure you that this could still save your life if you had a File System filter with a bug in its DriverEntry on a customer's machine. The expression of panic when the customer sees his machine restarting in an endless loop is interesting, but keeping your job is a little more interesting.

See you!