Author: Fernando

  • Step into Kernel (VMware 7+WinDbg)

    The first impression, which some older readers may be having now, is that I am really running out of time to write new posts, and that is why this new post would be just a repetition of the post Step into Kernel (VMware+WinDbg). Actually I am as surprised as some of you. In this post I will talk about a small detail that will make a difference when it comes to doing Kernel debugging using a virtual machine created with the new version of VMware.

    The Symptom

    This last weekend, despite the holiday, the sun and the immense desire to hit the road, I had to stay home solving some personal messes. One of them involved doing Kernel debugging on a virtual machine. Nothing unusual about that so far. After all, doing Kernel debugging on virtual machines is bread and butter for those who work developing drivers.

    I created a new VM and installed Windows on it, performed the TARGET and HOST configurations exactly as I describe in that other post, but to my surprise, for some reason WinDbg could not connect to the TARGET system, always staying with the same message “Waiting to reconnect…”. Even using Ctrl+Alt+D in WinDbg to see the debugger’s internal information, all I had in WinDbg’s output was the message shown below.

     
    Microsoft (R) Windows Debugger Version 6.12.0002.633 AMD64
    Copyright (c) Microsoft Corporation. All rights reserved.
     
    Opened \\.\pipe\com_1
    Waiting to reconnect...
    >>>> ReadAll(0x800703E3,1,0) fails.
    >>>> ReadAll(0x800703E3,1,0) fails.
    >>>> ReadAll(0x800703E3,1,0) fails.
    >>>> ReadAll(0x800703E3,1,0) fails.
    >>>> ReadAll(0x800703E3,1,0) fails.
    >>>> ReadAll(0x800703E3,1,0) fails.
    SYNCTARGET: Timeout.
    >>>> ReadAll(0x800703E3,1,0) fails.
    Throttle 0x10 write to 0x1
    >>>> ReadAll(0x800703E3,1,0) fails.
    >>>> ReadAll(0x800703E3,1,0) fails.
    >>>> ReadAll(0x800703E3,1,0) fails.

    After checking all the steps meticulously several times, I could not understand what was happening.

    The Cause

    When creating a new serial port on the virtual machine, which would be redirected to a named pipe on the real machine, I noticed that its name was out of the ordinary. Although this was supposedly the only serial port of the system, VMware’s settings-editing interface showed its name as “Serial Port 2” as shown in the figure below.

    I got suspicious about this and tried to recreate the serial port, thinking it would be a bug in VMware’s interface, but the same name appeared. So nothing better than opening the VM’s configuration file in good old Notepad. That was when I discovered that although there was no serial port besides the one I was creating in VMware’s editing interface, in the configuration file there were in fact two serial ports as I show below.

    .encoding = "windows-1252"
    config.version = "8"
    virtualHW.version = "7"
    scsi0.present = "TRUE"
    scsi0.virtualDev = "lsisas1068"
    memsize = "1024"
    mem.hotadd = "TRUE"
    scsi0:0.present = "TRUE"
    scsi0:0.fileName = "Windows 7 x64.vmdk"
    ide1:0.present = "TRUE"
    ide1:0.autodetect = "TRUE"
    ide1:0.deviceType = "cdrom-raw"
    floppy0.startConnected = "FALSE"
    floppy0.fileName = ""
    floppy0.autodetect = "TRUE"
    ethernet0.present = "TRUE"
    ethernet0.connectionType = "nat"
    ethernet0.virtualDev = "e1000"
    ethernet0.wakeOnPcktRcv = "FALSE"
    ethernet0.addressType = "generated"
    usb.present = "TRUE"
    ehci.present = "TRUE"
    sound.present = "TRUE"
    sound.fileName = "-1"
    sound.autodetect = "TRUE"
    mks.enable3d = "TRUE"
    serial0.present = "TRUE"
    serial0.fileType = "thinprint"
    pciBridge0.present = "TRUE"
    pciBridge4.present = "TRUE"
    pciBridge4.virtualDev = "pcieRootPort"
    pciBridge4.functions = "8"
    pciBridge5.present = "TRUE"
    pciBridge5.virtualDev = "pcieRootPort"
    pciBridge5.functions = "8"
    pciBridge6.present = "TRUE"
    pciBridge6.virtualDev = "pcieRootPort"
    pciBridge6.functions = "8"
    pciBridge7.present = "TRUE"
    pciBridge7.virtualDev = "pcieRootPort"
    pciBridge7.functions = "8"
    vmci0.present = "TRUE"
    roamingVM.exitBehavior = "go"
    displayName = "Windows 7 x64"
    guestOS = "windows7-64"
    nvram = "Windows 7 x64.nvram"
    virtualHW.productCompatibility = "hosted"
    printers.enabled = "TRUE"
    extendedConfigFile = "Windows 7 x64.vmxf"
    ide1:0.startConnected = "TRUE"
    ethernet0.generatedAddress = "00:0c:29:91:aa:62"
    tools.syncTime = "FALSE"
    uuid.location = "56 4d a8 8a e8 f9 f0 a8-62 de e8 25 40 91 aa 62"
    uuid.bios = "56 4d a8 8a e8 f9 f0 a8-62 de e8 25 40 91 aa 62"
    cleanShutdown = "FALSE"
    replay.supported = "FALSE"
    replay.filename = ""
    scsi0:0.redo = ""
    pciBridge0.pciSlotNumber = "17"
    pciBridge4.pciSlotNumber = "21"
    pciBridge5.pciSlotNumber = "22"
    pciBridge6.pciSlotNumber = "23"
    pciBridge7.pciSlotNumber = "24"
    scsi0.pciSlotNumber = "160"
    usb.pciSlotNumber = "32"
    ethernet0.pciSlotNumber = "33"
    sound.pciSlotNumber = "34"
    ehci.pciSlotNumber = "35"
    vmci0.pciSlotNumber = "36"
    scsi0.sasWWID = "50 05 05 6a e8 f9 f0 a0"
    vmotion.checkpointFBSize = "134217728"
    usb:0.present = "TRUE"
    usb:1.present = "TRUE"
    ethernet0.generatedAddressOffset = "0"
    vmci0.id = "1083288162"
    usb:1.deviceType = "hub"
    usb:0.deviceType = "mouse"
    ide1:0.fileName = "auto detect"
    unity.wasCapable = "FALSE"
    serial1.yieldOnMsrRead = "TRUE"
    serial1.fileName = "\\.\pipe\com_1"
    serial1.pipe.endPoint = "server"
     

    The “filetype” property with the value “thinprint” on serial port zero made me pay attention to this new hardware that was part of my virtual machine’s settings and that I hadn’t even noticed.

    The fact that this printer uses the virtual machine’s COM1 port as a hardware interface caused any new serial port to use COM2, which would be the next free port. Now it became simple to solve.

    The Solution

    There are two ways to solve this small setback. The first, which in my opinion is simpler and cruder, is to simply remove the printer before adding the serial port that will be used as the debug interface. This will make the first serial port actually be used as a serial port. If you need to use the printer, then add the printer after adding the serial port. This will make the printer use the first available serial port, which in this case is COM2. VMware’s hardware-configuration window should look like the one shown in the figure below.

    Notice that the serial port’s name no longer carries the number 2, while in the printer’s configuration it says that the port used is serial 2.

    The second solution, as an alternative to the first, is to simply configure the TARGET side so that it uses serial port 2. In the example of the figure below, I change the configuration of a Windows 7 using the BcdEdit tool. I already mentioned this tool in this post, which talks about how to do Kernel debugging through USB ports using Windows Vista.

    After everything was solved, the debugger now connects to the TARGET system and now comes the easiest part: debugging the problem. 😉

    See you!

  • What to Read to Write Windows Drivers

    A question I am always asked is about which books one needs to read in order to be able to develop drivers for Windows. This is a subject I considered too obvious to have a post about, but over time I came to realize that this subject is not so obvious for those who are starting from scratch. For some, writing drivers is something unattainable — no matter how much they read or try to learn, it will never be enough information to start getting their hands dirty. For others, writing drivers is not so complicated — that, just as they learned to write applications with Visual Basic, a few clicks would be more than enough to generate the driver they need. In an attempt to give an idea of the path and the difficulties to be faced in this new undertaking, I am going to list some books that I consider to be a good path to follow for writing drivers.

    They don’t teach this at college

    Among the many times this question was asked of me, once I was asked whether this is taught in the Computer Engineering course, which was the course I was taking at the time. I don’t know if everyone here knows, but Computer Engineering is a course that sits between Computer Science and Electronic Engineering. Trying to bring together the main subjects of both degrees, the course does not give as much emphasis to software or to electronics. Despite studying hardware, microprocessors, C language and operating systems, we don’t come anywhere near the subject of drivers. We know they exist and what their function is, but nothing that comes anywhere near the simplest example on this blog.

    Come on Fernando, since a driver is software, then the Computer Science course must teach drivers.

    Actually, the subjects seen in Computer Science deal with higher-level solutions, with more detail on specific algorithms for complex problems. The fact is that this course is more distant from basic software development. In a master’s degree this becomes even rarer in the software area.

    A driver is low-level software, but don’t overdo it

    I have a friend who always liked to study everything down to the smallest details. It’s fine if a person is passionate about a certain subject and wants to understand it completely, but if the goal is to write drivers, I believe it is better to study only what is necessary for a quick start, and then go deeper into each subject as needed. I have already seen some people say that before studying the C language, they would like to completely study the assembly language, or even start studying the Intel processor datasheet to have complete mastery of the subject. Hold on there, folks!

    I learned to program in the C language during my technical high school; I don’t know the main references on the subject, but the book I read is simple and, regarding the C language, comprehensive enough to program. That is why I recommend the book Treinamento em Linguagem C. You won’t need to buy real bibles on the subject that discuss the best way to compile a certain algorithm.

    The vast majority of the drivers I have seen in these nearly 10 years of Kernel experience are written in C, not in C++. The C language is transparent and reliable. I am not saying that you shouldn’t trust C++, but that over time you look at code in C language and manage to imagine what the assembly generated by it would be like. This helps a lot when you need to debug systems where the sources don’t match perfectly with the version you have, or even in trying to understand situations where you don’t have the source. C++ has its traps with operator overloading, templates and classes. Looking at the source it becomes hard to imagine what will really happen when that line is executed. I myself have already developed drivers in C++ and have given a course at a company where Walter Oney left his sources of a driver all in C++. I think it’s a personal choice, but the fact is that you don’t need to study C++ to write drivers.

    I have also heard people say “First I’m going to learn C++ completely and then start studying the Kernel”. I think I don’t need to say that this is completely unnecessary. Not to mention that I still don’t know whether it is humanly possible to learn C++ completely.

    Do I need to read Charles Petzold?

    In the course I offer on driver development, one of the prerequisites is basic Windows API. When I say basic, I am referring to the manipulation of system objects, such as files, events, threads, processes, virtual memory and the like. I did not want to enter the universe of message loops, MFC, WTL and countless User-Mode concepts that will not be used in Kernel.

    An excellent book about the system’s internal architecture is described in great detail in the book Windows Internals 5th Edition. The only problem is that the book has more than a thousand pages and that is a real brake pedal for someone who is all excited to go off writing drivers. A viable alternative is the temporary replacement of this book by Inside Windows NT 2nd Edition. A book that is half the size of Windows Internals and that brings the most important concepts about the system that are still used in Windows 7. When you are more comfortable, then you can read Windows Internals, and who knows, even take a certification on the subject.

    Legacy Model

    After understanding a little about the Windows architecture, you will already be able to start studying the key subject. But for that, you must choose what type of driver you intend to develop. The oldest model is Legacy, which despite having started in Windows NT 3.51, still runs on Windows 7. This model does not interact with the Plug-And-Play Manager, but it is still the model used in the development of File Systems to this day. All the rules used in the Legacy model are also used in the WDM model. This way, studying the Legacy model is a great introduction to studying WDM or File System drivers. The book I recommend for Legacy development is Windows NT Device Driver Development. Studying the Legacy model gives the reader the opportunity to go deeper into basic and essential concepts in Kernel Mode development. Because it is a simpler model, the reader’s brain manages to absorb with better quality questions such as: IRP handling, virtual memory, thread execution levels, synchronization mechanics, system objects and so on.

    The reader who studies the Legacy model will be much better prepared to deal with the WDM model, which has its own rules that will be applied using the concepts already acquired in the Legacy model. The same can be said regarding the development of File System drivers. Although File System drivers are developed in Legacy, it does not mean that by learning the Legacy model, one learns File Systems automagically. This is also a subject that requires dedicated study.

    Windows Driver Model (WDM)

    If your target model is WDM, then a good book to start with is the Windows 2000 Device Driver Book. That’s because the big problem with driver-development books is that a lot of theory is necessary to make a driver. In the WDM model things get even more loaded. This is not the best WDM book I know, and I have already seen comments on discussion lists saying that this book is known for its errors. The fact is that it gives a simplified presentation, and the best of all is that the author creates a basic driver at the beginning of the book and then goes putting more concepts into practice. With this the reader will write an example driver before dying mad with so much theory.

    After having a base in WDM, then you will already be ready to read a grown-up’s book, and I am not referring to nudity magazines. The book Programming the Windows Driver Model 2nd Edition is really very good. I myself have already read it some three times, but as I mentioned before, many pages are necessary for the reader to be able to start seeing things as a whole. All this theory tires and confuses a reader who is starting out on the subject.

    File System Drivers

    If you are one of the few living beings on this planet who need to develop or support File System drivers, then studying the Legacy model was a breeze. Some ask me: “What is so complicated about writing File System drivers?” The problem is that besides the concepts of the Legacy model, you need to deal with the very concepts used in integrating File System drivers with other system components. I am talking about their inseparable friends Memory Manager and Cache Manager. Several structures and rules are imposed by the system so that this component can fulfill its role in a stable and efficient way. The lack of books on the subject adds some adrenaline to the learning process. The book Windows NT File Systems Internals is the only book that deals with the subject for Windows. It is hoped that OSR, the company that currently holds the rights to this book, can release an updated version of it, but since the target audience is restricted, perhaps there is not enough incentive to make this happen.

    Writing File System drivers is complicated and one might even imagine that this is the most complicated type of driver there is to write, but according to OSR and other authorities on the subject, that position of most complex driver is occupied by File System filters. That is because besides having to know the rules that File System drivers need to follow, the filters still do not receive all the notifications that the driver itself receives. Writing this type of filter is being between two black boxes that talk with high intensity and intimacy, and still add value to this service. When I did my File System Drivers course at OSR, Tony Mason said that things get really interesting with the arrival of transactional NTFS.

    Faced with so much difficulty, Microsoft created the class of drivers called MiniFilters, which is a kind of Miniport driver for File System filters. Unfortunately there are still no books that deal with this subject. You will have to face this topic on the MSDN site. The detail here is that minifilter drivers are modules loaded by the Filter Manager, which from the system’s point of view is just another Legacy File System filter. This way, knowing the Legacy model is a great help when it comes to debugging all this.

    Windows Driver Foundation (WDF)

    That the WDM model is complex and laborious, I think we all agree. In order to avoid so much complexity when writing drivers, Microsoft created a new model that leans on WDM to implement all the standard behaviors of a driver by default. This means that you can write a device driver and let the framework do most of the work regarding plug-and-play, power management and countless concepts that are the same for the vast majority of drivers. If you want a special behavior in a certain situation, just register the callback routine and be happy (or not). WDF brings two frameworks, the Kernel-Mode Driver Framework (KMDF) and the User-Mode Driver Framework (UMDF). Developing drivers in KMDF brings a few restrictions compared to WDM, but it is worth writing a new driver in WDF. User-Mode drivers bring greater restrictions, mainly regarding the bus your device is on and how fast the driver needs to be. The book Developing Drivers with the Windows Driver Foundation is still the only one that deals with this subject. Once again, a lot of theory is presented until a practical example appears. Another interesting characteristic about this book is that both frameworks are presented simultaneously. Thus, you learn to develop drivers in KMDF and UMDF at the same time. Depending on the reader, this might even give a buzz.

    If what you really want is to develop a driver in User-Mode, then let’s rethink the C++ question. Since a UMDF driver must be implemented in COM, some C++ features are now basic requirements for comfortable development. Once again I am going to recommend a book by Viviane, Treinamento em Linguagem C++, which from my point of view is sufficient for writing drivers as far as the language is concerned. Later you can become a ninja master with Boost, STL and other things I can’t even imagine.

    After C++, a good dose of COM goes well. The book that my friend Strauss lent me, which went straight to the point in a clear and simple way, is Essential COM by Don Box.

    All this theoretical base will give you the background to be able to choose a type of driver and go deeper into it. Even within the Kernel subject, there are still countless driver specialties. Don’t think that you will be able to learn this subject completely before going off developing, so choose your book already and get your hands dirty.

    See you! 😉

  • Seventh C/C++ Programmers Meeting

    As some of you already know, next August 14 the seventh programmers meeting of the C/C++ Brasil group will take place. Once again, it will be a great pleasure to take part in the event as a speaker and to have the opportunity to talk a little about driver development (for a change). 😉

    My first participation in this event was in its fourth edition, where I gave an overview of Windows driver architecture and development. The slides of that talk can be downloaded from this post. Although the feedback was very good, I received some suggestions about showing more source code. Driver development requires a lot of concept, both about the operating system and about the rules imposed by the I/O Manager and its inseparable friends. So, I imagined that showing source code about structures and rules unknown to most of the talk’s participants would sound like a boring lullaby. But there is no way around it, what a programmer really likes is source code.

    It was in the sixth edition that I had my second participation. This time I focused on the characteristics of virtual-memory operation and use by drivers, giving more emphasis to the names of the routines that drivers use to obtain the Kernel’s services. Yeah, although it improved, it still was not enough. The feedback result once again revealed that practical examples and source code were missing for the folks.

    In this seventh edition I am going to practically write, compile, install, run and debug an example driver. All right there on the spot. The proposal of this talk is to demonstrate how to take these first steps to have a compiled driver right there on your own notebook.

    Although this information is also on the event page, here are the topics that will be presented by me:

    • What the Windows Driver Kit is
    • Device Drivers and Software Drivers
    • The WDK and the files of a project
    • The DriverEntry and DriverUnload routines
    • Hands on: Writing and compiling a driver
    • Testing drivers on virtual machines
    • Hands on: Installing the example driver
    • Debugging the Windows Kernel
    • Hands on: Debugging your driver
    • The Blue Screen of Death (BSOD)
    • Hands on: You never forget your first Blue Screen
    • Generating crash dumps
    • Hands on: Analyzing your crash dump
    • Questions, panic and depression

    Seems like a lot? Yeah, for this to be possible in the time I have available, it will be necessary to already have the environment prepared. You will not want to watch a one-hour talk that explains where to download the tools, how to install them and how to prepare a virtual machine to serve as a test environment for your new driver. If all this is done on the day of the event, once again I will owe you the source code and will probably be hanged in the public square. I don’t know about you, but I would not want that to happen.

    Downloading and Installing the WDK

    The first step of this preparation is to download Microsoft’s driver development kit, the Windows Driver Kit (WDK). This kit contains the headers and libraries needed so that your driver can use the routines exported by the Kernel, allowing your driver to interact with the rest of the operating system. Besides the headers and libraries, the kit also brings the compiler and the system debugger, making this kit the only tool needed to have a driver compiled and debugged.

    I haven’t yet mentioned the many, many driver examples that the kit brings, as well as all the documentation needed to use the routines exported by the Kernel. This documentation serves not only as a reference, but also as a driver-building guide. Although the documentation proposes to serve as a driver-building guide, I still strongly recommend reading books dedicated to this activity. Books manage to expose the ideas more clearly, addressing the subjects gradually.

    The fact that there is a compiler in this kit does not mean that there is a development environment specific to drivers. What I mean is that there is no version of Visual Studio specific to drivers, nor even a plug-in that could be installed in Visual Studio to add such functionality to the environment. In the most basic case, the sources could be edited in good old Notepad. This does not prevent us from using our beloved editor to write drivers, as this other post shows. In fact there are some tools for this purpose, such as VisualDDK, but it is nothing official.

    Downloading the WDK is easy and painless. And when I say painless, I am also referring to the financial sense of the idea. The first step is to download the WDK, which is available at the Microsoft Download Center. Following the instructions, you will download an ISO of approximately 620MB that will contain the entire installation.

    The ISO can be burned to a CD, or you can simply use one of those applications that creates a virtual disk drive from an image file. If you don’t have any such application on hand, you can download and install for free the OSR OsrVirtualCdAndDisk.

    After inserting the CD, run the KitSetup.exe application that will be in the CD’s root folder. Doing this you will see the Windows Driver Kit 7.1.0 splash screen as shown below.

    Then the window shown below.

    Select the items as shown above, leaving the “Device Simulation Framework” and “Windows Device Testing Framework” items unselected. Then click OK to see the next window as shown in the figure below.

    At least for the talk, simply accept the suggested installation path; this will avoid setbacks during the talk. Clicking OK in this window, just accept the installation terms and click OK in the window shown below to finish installing the WDK.

    Wait for the file copy to finish and the installation will end.

    Just a quick little compile, only to…

    After installing the WDK you can make sure everything is right simply by compiling one of the examples installed by the kit itself. To do this, open the build command prompt as demonstrated in the figure below.

    This command prompt is different from the conventional one because it is already properly configured with the environment variables needed to compile drivers for Windows XP. More details about this in the talk. With this command prompt, execute the steps indicated in the following figure.

    Make sure the example was compiled by checking the compiler’s output as highlighted in the figure above. If you have any problem following these steps, send me an e-mail and we’ll duke it out and everything’s fine.

    Downloading and Installing Virtual PC

    Having the development environment configured is essential for driver development, but it would be of no use if you don’t have a test environment. Don’t make the mistake of using your own development machine as the test environment. At least try to use an intern’s development machine or whatever. Consider that you are not going to write an application that, in the worst case, will result in the sonorous BAM. Depending on the type of driver and the size of the blunder you pull off, your machine may become unusable, no longer boot or even have corrupted data.

    Debugging a Kernel driver requires special conditions. Strictly speaking, two machines are necessary for this task. That’s right, two machines. What were you thinking? That you would be able to put a breakpoint in your driver, interrupt its execution and ALT+TAB to take a little look at the local-variables window? No way, buddy. When a breakpoint interrupts the operating system, the whole of Windows stops. The mouse doesn’t move, the window doesn’t repaint, the scheduler doesn’t work, nothing at all. That is why we need a second machine. Of your machines, one of them will be your victim, the machine on which your driver will run and which will be completely frozen when necessary. The other will be your development machine. On it will be the source codes, the symbols and the debugging environment. It is on your development machine that the little local-variables window will appear.

    Nowadays we can resort to virtualization to save time and space on our desks. Using a virtual machine we can have the environment we need to continue the talk and test the driver that will be developed. Virtual machines help in many cases, mainly in cases of developing drivers that do not deal with hardware directly. This is the case of anti-virus, firewall, file system drivers, some types of USB drivers and others. On the other hand, if you have, for example, a PCI card to do port I/O, handle interrupts or even do DMA, a VM will not help much, but that is another case.

    The Virtual PC is a free Microsoft tool and can be downloaded from this link. Running the downloaded file, we will have the following window as listed in the figure below. Just click Next for the next window.

    Accept the product terms and click Next to continue the installation.

    Just accept the suggested destination directory and click Next.

    After waiting for the file copy, finish the installation.

    Creating a new VM

    This is not a blog specialized in virtual machines, so don’t even try to send me e-mails with hairy doubts about this subject. However, I can help with creating the virtual machine we will use in the talk. After all, we will have to destroy something, otherwise it would be kind of dull.

    On this initial screen shown above, just click Next to continue with the installation.

    Here select the option to create a new virtual machine and then click Next to continue.

    In this step you give the name of your virtual machine. Feel free to put whatever name you find most appropriate. Joe, Jack and Bob are some valid options, but for the sake of clarity I’ll prefer “Windows XP” itself.

    In this step we select the operating system that will be installed on this new machine. This serves so that the VM can install the right integration tools. In short, select Windows XP and click Next to continue.

    Just accept the amount of memory recommended by the Wizard and click Next.

    Since we are going to do a new installation of Windows, we will need a new HD that will be formatted by the Windows installation. Select the option indicated in the figure above and click Next.

    Well, here you determine where the file that represents the hard disk will be created. Without much fuss just accept the suggested path and click Next to continue.

    Finish creating your new virtual machine by clicking Finish and that’s it.

    Phew! Now your virtual machine is ready to receive a new operating system.

    From this point on you can install a copy of your Windows XP. Other versions of Windows could be installed, but in order to avoid differences in the debug settings during the talk, let’s agree to install Windows XP itself.

    If you don’t have a copy of Windows XP to install, there is not much I can do to help you. The most I can do is give you the following advice: Go to the Santa Ifigênia church, which is on Santa Ifigênia avenue right in downtown São Paulo, and light a candle to the saint. While you’re there, take a stroll through the local shops. I’m sure you will receive a light on your path.

    Now I’ll stop here, otherwise I won’t have much to talk about on the day of the talk. Take this opportunity to learn the basic concepts (and I do mean basic) about how to generate your first driver, your first blue screen and finally do your first crash analysis. Think that one day you will be able to tell this to your grandchildren. Although it won’t help much, they won’t have the slightest idea what you are talking about and will end up committing you to an institution anyway.

    Hope to see you there. See ya!

    Download the slides

  • Mapping Files into Memory

    After illustrating some of the Memory Manager‘s characteristics as a service provider to the Cache Manager in the previous post, today I will demonstrate that mere User-Mode applications can also use such services. By mapping files into memory the application gains a range of virtual addresses that contains the file’s content. Access to the file’s content is done simply by dereferencing a pointer, without the need to call the ReadFile() or WriteFile() functions.

    Want a need for this? Imagine that your application needs to search for a certain string in a file, let’s say “DriverEntry”. In a “rice and beans” development, the file handle is obtained through the call to the CreateFile() function, and a buffer receives the partial content of the file, let’s suppose 200 bytes. A simple search function from the API could perform such a search in the buffer.

    This solution would be perfect if there were no possibility of the searched word falling at the edges of the buffer, as illustrated below.

    A smarter algorithm would have to be used to identify the prefix and continue the search on the next read of the file.

    This is just a simple example, but it clearly illustrates one of the advantages of mapping files. If there were a simple function that received the path of a file and returned to us a pointer to its content, the search would be quite simple.

    Files mapped into memory can also make writing to their content easier. By writing to the pointer received from such a mapping, the Memory Manager will take charge of doing the necessary I/O so that this new content reaches the disk.

    A simple mapping function

    Here I will exemplify the use of the routines that map a file into memory. The comments follow in the explanation.

    /****
    ***     MapFileToMemory
    **
    **      Routine that receives the path of a file that will be
    **      mapped into memory for reading. An address is
    **      returned to the calling routine as well as the size
    **      of the file.
    */
     
    DWORD MapFileToMemory(LPCTSTR   tzFileName,
                          LPVOID*   ppMemory,
                          LPDWORD   pdwSize)
    {
        HANDLE  hFile = NULL,
                hMapping = NULL;
        DWORD   dwError = ERROR_SUCCESS;
     
        __try
        {
            __try
            {
                //-f--> Zeroes output variables.
                *pdwSize = NULL;
                *ppMemory = NULL;
     
                //-f--> Here we open the file to be mapped
                hFile = CreateFile(tzFileName,
                                   GENERIC_READ,
                                   FILE_SHARE_READ | FILE_SHARE_DELETE,
                                   NULL,
                                   OPEN_EXISTING,
                                   FILE_ATTRIBUTE_NORMAL,
                                   NULL);
     
                //-f--> We check whether the file was opened; if not,
                //      the boogeyman comes and takes us away.
                if (hFile == INVALID_HANDLE_VALUE)
                    RaiseException(GetLastError(),
                                   0,
                                   0,
                                   NULL);
     
                //-f--> Although the file size is not necessary
                //      in this function, let's take advantage of having the file's
                //      handle in hand to obtain this information for
                //      the calling routine, which will need it.
                *pdwSize = GetFileSize(hFile,
                                       NULL);
     
                //-f--> Here we create a mapping of the file.
                //      In kernel it would be the equivalent of creating a
                //      section of the file.
                hMapping = CreateFileMapping(hFile,
                                             NULL,
                                             PAGE_READONLY,
                                             0,
                                             0,
                                             NULL);
     
                //-f--> Preventing the boogeyman.
                if (!hMapping)
                    RaiseException(GetLastError(),
                                   0,
                                   0,
                                   NULL);
     
                //-f--> Here the mapping is actually done and we get the
                //      range of addresses that will contain the content
                //      of the file.
                *ppMemory = MapViewOfFile(hMapping,
                                          FILE_MAP_READ,
                                          0,
                                          0,
                                          0);
     
                //-f--> The same boogeyman trick I already mentioned.
                if (!*ppMemory)
                    RaiseException(GetLastError(),
                                   0,
                                   0,
                                   NULL);
            }
            __finally
            {
                //-f--> Here is where we will do all the cleanup,
                //      closing the handles that were opened.
                if (hFile)
                    CloseHandle(hFile);
     
                if (hMapping)
                    CloseHandle(hMapping);
            }
        }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
            //-f--> Oops! Something did not go as rehearsed.
            //      Find someone to blame and pretend it is not your problem.
            dwError = GetExceptionCode();
        }
     
        return dwError;
    }
     

    This example is really quite simple, but feel free to add parameters that make this function more flexible and complex.

    “Fernando, even if the file was mapped successfully, you close the file handle and the mapping handle. Shouldn’t that release the references this program has to the file?”

    Actually, after we create the file mapping using the CreateFileMapping() routine, which receives the file handle, an extra reference has already been made to the file, and so we could already close its handle if we wanted to. The same happens with the call to the MapViewOfFile() routine, which receives the mapping handle, and which in turn has an indirect reference to the mapped file. In other words, once everything is mapped we can close all the handles and let the indirect references take care of it.

    In the next source code we will see a simple example of using this function.

    /****
    ***     _tmain
    **
    **      Simple use of the file-mapping function.
    **      Just so nobody says I did not do every little bit...
    */
     
    int _tmain(int argc, _TCHAR* argv[])
    {
        PBYTE   pBuffer;
        DWORD   dwError, dwSize, i;
     
        //-f--> Passes the file name and obtains the pointer
        //      with its content mapped. Just like that...
        dwError = MapFileToMemory(_T("C:\\Temp\\Test.txt"),
                                  (LPVOID*)&pBuffer,
                                  &dwSize);
     
        //-f--> Testing for errors never hurts.
        if (dwError == ERROR_SUCCESS)
        {
            //-f--> Moments of suspense before touching the address.
            printf("Hit any key to access the buffer at 0x%p...\n", pBuffer);
            _getch();
     
            //-f--> Prints each character stored in the file.
            //      "Look, Mom! No ReadFile()!"
            for (i=0; i
                printf("%c", pBuffer[i]);
     
            //-f--> Here the mapping is undone.
            UnmapViewOfFile(pBuffer);
        }
     
        return dwError;
    }
     

    At the end of this example we can observe the call to the UnmapViewOfFile() routine, which simply receives the base pointer of the file mapping. With this call, all internal references are undone and the file is finally closed.

    Testing the toy

    So that we can run a silly test, create a text file using Notepad.exe.

    Running the test application we have the output as illustrated below.

    Now in the replay slow motion

    With WinDbg we can observe the exact moment when the application accesses the range of addresses referring to the file’s content. To do this we will place a breakpoint on the file-read routine of the Ntfs.sys driver; this way we will be able to see the Memory Manager’s request being served. For this to happen, the text file cannot be in the system cache, so if you have already run the test application at least once, you will have to restart the system.

    If you have not used WinDbg yet and do not know how to connect it to the system, then read this post for a quick start. After connecting WinDbg to the system Kernel, go to the directory where the test application is and run it, but do not press any key yet, leaving it stopped as shown below:

    After that, press Ctrl+Break in WinDbg so that you gain control over the debugged system, which at this moment will remain frozen.

    To place that breakpoint on the file-read routine of Ntfs.sys, we will need to know where this routine is within the driver. We can obtain this information using WinDbg’s !drvobj extension as shown below.

    kd> !drvobj \FileSystem\Ntfs 2
    Driver object (843fd650) is for:
     \FileSystem\Ntfs
    DriverEntry:   828f5b75    Ntfs!GsDriverEntry
    DriverStartIo: 00000000    
    DriverUnload:  00000000    
    AddDevice:     00000000    
     
    Dispatch routines:
    [00] IRP_MJ_CREATE                      8289400a    Ntfs!NtfsFsdCreate
    [01] IRP_MJ_CREATE_NAMED_PIPE           8165a013    nt!IopInvalidDeviceRequest
    [02] IRP_MJ_CLOSE                       82896fcf    Ntfs!NtfsFsdClose
    [03] IRP_MJ_READ                        82818514    Ntfs!NtfsFsdRead
    [04] IRP_MJ_WRITE                       82815638    Ntfs!NtfsFsdWrite
    [05] IRP_MJ_QUERY_INFORMATION           82895a88    Ntfs!NtfsFsdDispatchWait
    [06] IRP_MJ_SET_INFORMATION             8281e950    Ntfs!NtfsFsdSetInformation
    [07] IRP_MJ_QUERY_EA                    82895a88    Ntfs!NtfsFsdDispatchWait
    [08] IRP_MJ_SET_EA                      82895a88    Ntfs!NtfsFsdDispatchWait
    [09] IRP_MJ_FLUSH_BUFFERS               82884349    Ntfs!NtfsFsdFlushBuffers
    [0a] IRP_MJ_QUERY_VOLUME_INFORMATION    828b5fc6    Ntfs!NtfsFsdDispatch
    [0b] IRP_MJ_SET_VOLUME_INFORMATION      828b5fc6    Ntfs!NtfsFsdDispatch
    [0c] IRP_MJ_DIRECTORY_CONTROL           828b5d41    Ntfs!NtfsFsdDirectoryControl
    [0d] IRP_MJ_FILE_SYSTEM_CONTROL         8289970e    Ntfs!NtfsFsdFileSystemControl
    [0e] IRP_MJ_DEVICE_CONTROL              82879466    Ntfs!NtfsFsdDeviceControl
    [0f] IRP_MJ_INTERNAL_DEVICE_CONTROL     8165a013    nt!IopInvalidDeviceRequest
    [10] IRP_MJ_SHUTDOWN                    8282b36b    Ntfs!NtfsFsdShutdown
    [11] IRP_MJ_LOCK_CONTROL                82823b7a    Ntfs!NtfsFsdLockControl
    [12] IRP_MJ_CLEANUP                     828a1d42    Ntfs!NtfsFsdCleanup
    [13] IRP_MJ_CREATE_MAILSLOT             8165a013    nt!IopInvalidDeviceRequest
    [14] IRP_MJ_QUERY_SECURITY              828b5fc6    Ntfs!NtfsFsdDispatch
    [15] IRP_MJ_SET_SECURITY                828b5fc6    Ntfs!NtfsFsdDispatch
    [16] IRP_MJ_POWER                       8165a013    nt!IopInvalidDeviceRequest
    [17] IRP_MJ_SYSTEM_CONTROL              8165a013    nt!IopInvalidDeviceRequest
    [18] IRP_MJ_DEVICE_CHANGE               8165a013    nt!IopInvalidDeviceRequest
    [19] IRP_MJ_QUERY_QUOTA                 82895a88    Ntfs!NtfsFsdDispatchWait
    [1a] IRP_MJ_SET_QUOTA                   82895a88    Ntfs!NtfsFsdDispatchWait
    [1b] IRP_MJ_PNP                         8286137b    Ntfs!NtfsFsdPnp
     
    Fast I/O routines:
    FastIoCheckIfPossible                   8288187b    Ntfs!NtfsFastIoCheckIfPossible
    FastIoRead                              82880c38    Ntfs!NtfsCopyReadA
    FastIoWrite                             82881f53    Ntfs!NtfsCopyWriteA
    FastIoQueryBasicInfo                    82888c3a    Ntfs!NtfsFastQueryBasicInfo
    FastIoQueryStandardInfo                 82888aa6    Ntfs!NtfsFastQueryStdInfo
    FastIoLock                              8287bf41    Ntfs!NtfsFastLock
    FastIoUnlockSingle                      8287bd75    Ntfs!NtfsFastUnlockSingle
    FastIoUnlockAll                         828cd7b3    Ntfs!NtfsFastUnlockAll
    FastIoUnlockAllByKey                    828cd958    Ntfs!NtfsFastUnlockAllByKey
    ReleaseFileForNtCreateSection           8281e904    Ntfs!NtfsReleaseForCreateSection
    FastIoQueryNetworkOpenInfo              8287ad84    Ntfs!NtfsFastQueryNetworkOpenInfo
    AcquireForModWrite                      8280c892    Ntfs!NtfsAcquireFileForModWrite
    MdlRead                                 828cd0d8    Ntfs!NtfsMdlReadA
    MdlReadComplete                         81650af6    nt!FsRtlMdlReadCompleteDev
    PrepareMdlWrite                         828cd31f    Ntfs!NtfsPrepareMdlWriteA
    MdlWriteComplete                        817f5a9a    nt!FsRtlMdlWriteCompleteDev
    FastIoQueryOpen                         82874d03    Ntfs!NtfsNetworkOpenCreate
    AcquireForCcFlush                       8281ab35    Ntfs!NtfsAcquireFileForCcFlush
    ReleaseForCcFlush                       8281aa9c    Ntfs!NtfsReleaseFileForCcFlush
     

    As you must be imagining, the Ntfs read routine is used very frequently, which would make this breakpoint stop many times without the slightest relation to our test. To limit the breakpoint’s scope, we will make it apply only to the thread that will make the request we are waiting for.

    As I already explained in the previous post, when the memory address is obtained, the file has not yet been read. When the application dereferences this pointer looking for the data, a page fault will be generated and the Memory Manager will take control over the thread by means of a system trap. This is the thread that will be used to perform the read of the file that will supply the memory page at the Memory Manager’s request. This is the reason why our test program waits for a key to be pressed before accessing the buffer. This gives us the opportunity to obtain the identification of the thread that is waiting for that event.

    We use the !process extension to locate our test program, and it will also list its threads, which in our case is a single one.

    kd> !process 0 2 MapFile.exe
    PROCESS 84bf6d90  SessionId: 1  Cid: 0b60    Peb: 7ffdf000  ParentCid: 0b40
        DirBase: 1f09b4c0  ObjectTable: 8e77ccd0  HandleCount:   5.
        Image: MapFile.exe
     
            THREAD 84f6cb50  Cid 0b60.0b64  Teb: 7ffde000 Win32Thread: 00000000 WAIT: (WrLpcReply) ...
                84f6cd64  Semaphore Limit 0x1
     
    kd> bp /1 /t 84f6cb50 Ntfs!NtfsFsdRead
    kd> g
     

    After placing the breakpoint, we can release the execution of the system and type something in the test application. This will make our breakpoint interrupt the system just as we intended. Looking at the call stack we have at the moment, we can evidence the execution of the trap that was generated by the test application. This trap is being served by the Memory Manager.

    Breakpoint 0 hit
    Ntfs!NtfsFsdRead:
    82818514 6a40
     
    kd> kb
    ChildEBP RetAddr  Args to Child
    8f395b6c 816f00c3 84438020 84f40290 84f40290 Ntfs!NtfsFsdRead
    8f395b84 821a3ba7 84437730 84f40290 00000000 nt!IofCallDriver+0x63
    8f395ba8 821a3d64 8f395bc8 84437730 00000000 fltmgr!FltpLegacyProcessingAfterPreCallbacksCompleted+0x251
    8f395be0 816f00c3 84437730 84f40290 00000000 fltmgr!FltpDispatch+0xc2
    8f395bf8 8167bf2e 84f6cb50 8443a18c 8443a158 nt!IofCallDriver+0x63
    8f395c14 816b8d51 00000043 84f6cb50 8443a198 nt!IoPageRead+0x172
    8f395cd0 816db03f 00020000 90825810 00000000 nt!MiDispatchFault+0xd18
    8f395d4c 8168ebf4 00000000 00020000 00000001 nt!MmAccessFault+0x1fb7
    8f395d4c 0018d972 00000000 00020000 00000001 nt!KiTrap0E+0xdc
    0015fbc0 0018ec36 00000001 00281a28 00281a78 MapFile!wmain+0x72
    0015fc0c 0018eb0f 0015fc20 77554911 7ffdf000 MapFile!__tmainCRTStartup+0x116
    0015fc14 77554911 7ffdf000 0015fc60 77ace4b6 MapFile!wmainCRTStartup+0xf
    0015fc20 77ace4b6 7ffdf000 77a26775 00000000 kernel32!BaseThreadInitThunk+0xe
    0015fc60 77ace489 0018b532 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x23
    0015fc78 00000000 0018b532 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b
     

    As we know the prototype that a dispatch routine must have, we know that the second parameter of the NtfsFsdRead() routine is the address of the IRP that the driver received. Using the !irp extension we can obtain details about the IRP. This allows us to know the FileObject to which this request is destined.

    kd> !irp 84f40290 
    Irp is active with 8 stacks 8 is current (= 0x84f403fc)
     Mdl=8443a1d8: No System Buffer: Thread 84f6cb50:  Irp stack trace.  
         cmd  flg cl Device   File     Completion-Context
     [  0, 0]   0  0 00000000 00000000 00000000-00000000    
     
                Args: 00000000 00000000 00000000 00000000
     [  0, 0]   0  0 00000000 00000000 00000000-00000000    
     
                Args: 00000000 00000000 00000000 00000000
     [  0, 0]   0  0 00000000 00000000 00000000-00000000    
     
                Args: 00000000 00000000 00000000 00000000
     [  0, 0]   0  0 00000000 00000000 00000000-00000000    
     
                Args: 00000000 00000000 00000000 00000000
     [  0, 0]   0  0 00000000 00000000 00000000-00000000    
     
                Args: 00000000 00000000 00000000 00000000
     [  0, 0]   0  0 00000000 00000000 00000000-00000000    
     
                Args: 00000000 00000000 00000000 00000000
     [  0, 0]   0  0 00000000 00000000 00000000-00000000    
     
                Args: 00000000 00000000 00000000 00000000
    >[  3, 0]   0  0 84438020 84bd0028 00000000-00000000    
               \FileSystem\Ntfs
                Args: 00001000 00000000 00000000 00000000
     

    With the FileObject address in hand, the !fileobj extension will show us more details about this object. Thus we can verify that the file to be read is indeed our text file that was mapped.

    kd> !fileobj 84bd0028 
     
    \Temp\Test.txt
     
    Device Object: 0x84439030   \Driver\volmgr
    Vpb: 0x84436e28
    Access: Read SharedRead SharedDelete 
     
    Flags:  0x44042
        Synchronous IO
        Cache Supported
        Cleanup Complete
        Handle Created
     
    FsContext: 0x92a4cd80    FsContext2: 0x92a4ced8
    CurrentByteOffset: 0
    Cache Data:
      Section Object Pointers: 84d24e74
      Shared Cache Map: 00000000
     

    Knowing that we are in the context of the thread that made the access, we can take a little peek at the accessed address before the page fault is served. This address was shown in the test application’s output before the breakpoint interrupted the execution of the system.

    kd> db 0x00020000
    00020000  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
    00020010  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
    00020020  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
    00020030  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
    00020040  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
    00020050  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
    00020060  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
    00020070  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
     

    “Fernando, why do question marks appear? It’s fine that the file’s content has not yet been copied to the application’s buffer, but shouldn’t we see garbage or even zeros?”

    Look, that question of yours was really very good; I don’t think I myself could have thought of a better question. Actually the answer to this question is linked to that crude answer from the previous post. What happens is that a range of addresses was reserved to contain the memory pages with the file’s content. Since no access has been made yet, this virtual address does not yet point to any physical memory page. Without this physical page one cannot determine its data. We can verify this using WinDbg’s !vtop extension, which translates virtual addresses to physical addresses.

    kd> !vtop 0 0x00020000
    X86VtoP: Virt 00020000, pagedir 1f09b4c0
    X86VtoP: PAE PDPE 1f09b4c0 - 000000001876d801
    X86VtoP: PAE PDE 1876d000 - 0000000018908867
    X86VtoP: PAE PTE 18908100 - ffffffff00000420
    X86VtoP: Virt ffffffff, pagedir 1f09b4c0
    X86VtoP: PAE PDPE 1f09b4d8 - 00000000187b0801
    X86VtoP: PAE PDE 187b0ff8 - 0000000000128063
    X86VtoP: PAE PTE 128ff8 - 0000000000000000
    X86VtoP: PAE zero PTE
    Virtual address 20000 translation fails, error 0x8007001E.
     
    kd> !error 0x8007001E
    Error code: (HRESULT) 0x8007001e (2147942430) - The system cannot read from the specified device.
     

    The attempt to translate this virtual address results in an error. Let’s try to do this translation again after the page fault is served. Let’s release the execution of the system up to the return address for the MmAccessFault() routine. This address was obtained in the thread’s call stack and was highlighted in the results of the kd command already illustrated above.

    kd> ga 8168ebf4 
     
    nt!KiTrap0E+0xdc:
    8168ebf4 85c0            test    eax,eax
     
    kd> !vtop 0 0x00020000
    X86VtoP: Virt 00020000, pagedir 1f09b4c0
    X86VtoP: PAE PDPE 1f09b4c0 - 000000001876d801
    X86VtoP: PAE PDE 1876d000 - 0000000018908867
    X86VtoP: PAE PTE 18908100 - 8000000018844025
    X86VtoP: PAE Mapped phys 18844000
    Virtual address 20000 translates to physical address 18844000.
     

    Here the page fault has already been served and control will be returned to the application. At this point the Memory Manager performed the necessary tasks so that this virtual address could now be translated to a physical page. Repeating the same translation attempt that failed earlier, we will have the following output.

    kd> db 0x00020000
    00020000  54 65 73 74 65 20 64 65-20 6d 61 70 65 61 6d 65  Teste de mapeame
    00020010  6e 74 6f 20 64 65 20 61-72 71 75 69 76 6f 2e 2e  nto de arquivo..
    00020020  2e 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
    00020030  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
    00020040  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
    00020050  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
    00020060  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
    00020070  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

    Releasing the execution of the system, the application will make the access to the buffer and we will have the same output shown earlier. It is worth remembering that to repeat this experiment, it is necessary to restart the system, because the text file’s content is now in the system cache. This means that the page fault will not occur again until this page is discarded by the Cache Manager. Such an event depends on many factors and may not happen until the machine shuts down.

    Anyway, this post, besides bringing a simple file-mapping function, also brings the same technical blah-blah-blah as always. I hope you liked it.
    See you! 😉

    MapFile.zip

  • Can a Lost Pointer in the Kernel Corrupt Files?

    A driver’s new version may implement that new feature you were so eagerly waiting for. After all, the driver development team is always very busy, and getting anything new is always a struggle. The only problem is that every now and then a blue screen happens. The more desperate ones may even want to use the new driver at any cost, even if a little blue screen shows up at an acceptable frequency. Then the little question comes up:

    Fernando, is there any problem if I keep using this new driver until a fix for this blue screen comes out?

    The main problem here is that we have no idea what is causing the blue screen. This kind of classification (no idea whatsoever) includes a pointer that may go writing where it should not. Besides going around running over vital operating-system structures, this bomb pointer can also corrupt files. So, one more little question:

    But Fernando, this driver doesn’t even do file handling. How can a lost pointer open a file and even generate a disk write to corrupt it?

    Actually, that is not so difficult. Have you heard of the Cache Manager?

    Who is this Cache Manager guy?

    I saw a simple definition of the Cache Manager in a Plugfest talk. Let’s see if I can reproduce it here. You developers have probably cached the content of some file in an application so as not to have to access that file every time you need the information contained in it, right? Wrong? OK, OK, from the beginning today then.

    Remember back in kindergarten when the teacher taught that if you frequently access a file, you can keep a copy of it in memory and avoid doing so much I/O to gain performance? In this case an area of memory, also known as a cache, is loaded with the file’s content. After that, the various read accesses to the file are replaced by reads from the cache. When a write happens, the cache is updated and the write also goes to the file. In cases where writing is frequent, the cache is updated on every write while the file receives several modifications at once at defined intervals.

    The cache implemented in the application loses its meaning if a given file is shared by more than one application; the content of application “A”‘s cache has to be the same as application “B”‘s cache, otherwise the changes made by application “A” would not be seen by application “B” and vice versa, not to mention that updates would be lost without the proper synchronization this would require.

    That is why there is a centralized cache in the system. A module in the Kernel that keeps memory pages containing the content of recently handled files. When a file is opened, it is registered by its respective File System driver in the Cache Manager. But the Cache Manager doesn’t do everything alone. Actually it is part of a gang in the “hood” that ensures the optimization of file accesses in the system. For that, the Cache Manager counts on the help of its faithful companions, the Virtual Memory Manager and the File System Drivers.

    Skipping a few tons of details, let’s say that when a read request reaches a file system driver, it forwards it to the Cache Manager, which will then satisfy that operation just by copying the desired content of the file that would already be in memory pages. Copying the data from memory is much faster than performing the whole ritual to obtain the same data from disk, but for that the data should already be loaded in memory.

    Fernando, does the Cache Manager put the whole file in memory?

    Want a crude answer? Yes and No ;-). The Cache Manager actually maps the open file into memory, and for that it relies on the most basic characteristics of virtual memory discussed in this other post. A range of addresses is reserved in the system, but such addresses do not yet reflect spaces in the memory chips; that is, a memory address exists, but its content is still on disk. The Memory Manager protects these addresses against accesses that may occur to them. When a read access is performed on these addresses, a page fault occurs and the Memory Manager then needs to retrieve the file data that is on disk and place it in memory. To do this, the Memory Manager will create a read request in the I/O Manager so that an IRP can be delivered to the respective file system driver of the file in question.

    Stop everything, “for the love” of God! Fernando, you said right up there that when a read request reaches the file system driver, it is forwarded to the Cache Manager, which will copy the data already contained in memory in order to serve the request. But now you are saying that to load such memory pages the Cache Manager has a chat with the Memory Manager, which in turn will create a read request for the file system drivers. Doesn’t that seem a bit recursive to you?

    I would say completely recursive, but remember that this will only happen when the file has not yet been read by any process, and therefore is not yet in the system cache. To tell one request from the other, file system drivers need to check the IRP_NOCACHE flag on the requests they receive. When requests come from an application, they do not carry the IRP_NOCACHE flag, and thus can be served by the Cache Manager; on the other hand, when the Memory Manager needs to supply the Cache Manager’s memory pages, such requests need to ignore the cache content, and that is why they carry the IRP_NOCACHE flag. To make it easier to understand this whole machine, look at the enumerated steps of a read of a file that is not yet in the cache.

    1. An application makes a read request for a file.
    2. The I/O Manager creates an IRP and forwards it to its respective file system driver.
    3. The file system driver checks the absence of the IRP_NOCACHE flag and requests the copy of this file’s data from the cache to the application’s buffer.
    4. The Cache Manager tries to make the copy by accessing the pages that were mapped from the file. With this a page fault is generated by that access and is served by the Memory Manager.
    5. The Memory Manager creates a new IRP to serve the need to supply the Cache Manager. This request is recursively forwarded to the file system driver.
    6. This time the driver checks the presence of the IRP_NOCACHE flag and then creates the requests that will be served by the disk or network drivers.
    7. The media read requests are served.
    8. The memory pages are supplied and the page fault is satisfied.
    9. The Memory Manager re-executes the Cache Manager’s read attempt that generated the page fault, but this time the read access will succeed, because the data is now at the mapped memory address.
    10. The Cache Manager completes the copy of the data to the application’s buffer.
    11. The file system driver completes the read request.
    12. The data is returned to the application that made the initial request.

    Attention now, boys and girls: the sequence described above illustrates the case where the Cache Manager still needs to load the file into memory. The next read attempts are satisfied directly by the Cache Manager, which will not generate a page fault. Don’t kill me with embarrassment by going around saying that the operating system always does the whole sequence for every file read.

    Once again the basic rules of virtual memory are applied here so that, as the memory pages stop being accessed so frequently, they lose their place in the memory chips, and thus, if they are later accessed again, a new page fault will be generated.

    Interesting to see how these components — the I/O Manager, Cache Manager, Virtual Memory Manager, File System Drivers, not to mention the filters that may still exist — all working together like black boxes, each with its role and without knowing the internal workings of the others, interacting with each other only through their public interfaces. Obviously, to the happiness of some and perhaps the sadness of others, I did not put all the details here, but they can be found in the well-known black-hen book.

    But back to the subject…

    Analogously, writes also use this same mechanism that involves file mapping. The simple fact of writing in the range of addresses kept by the Cache Manager will cause that page to be marked as modified, and later the Memory Manager will want to update that page on disk. This way we can summarize that write requests reach the File System drivers and are forwarded to the Cache Manager, which will simply write to the pages referring to the file’s content and complete the request. Page faults and system threads will take charge of updating whatever is needed at the most appropriate time. The important thing to note here is to think of the Cache Manager as a simple consumer of the Memory Manager’s services; all it needs to do is read or write to memory pages, and this is where the post’s title starts to make sense.

    Nothing stops a dumb pointer from writing to memory pages that reference the content of files. If this happens, the rest of the system will take charge of updating that pointer’s atrocities on disk, corrupting the file. It is easy to notice that it doesn’t even take that many steps for this to happen.

    1. An inexperienced driver eats that slice of pizza left forgotten in the microwave and goes totally nuts. After making a fool of itself, saying what it shouldn’t, crying and saying it thinks the world of you, the driver writes to memory pages referring to a data file. Like one of those SQL ones whose extension I can’t even imagine.
    2. The poor Memory Manager does its job to guarantee the children’s milk as if nothing wrong had happened.
    3. The file system driver goes along with it and consolidates the driver’s complete cluelessness, which by now is already hugging the toilet bowl.
    4. This step is not illustrated in the sequence above but can be explained on this site.

    At this point you will be rooting for your brand-new driver to write over some vital system structure so that a blue screen can contain this reckless one’s activity. That is why the system is full of tests and checks to ensure that the user’s data is not lost. Better to see a blue screen than to have much worse consequences. Remember the wise Morphy:

    Nothing is so bad that it can’t be made worse

    There are still many other interesting characteristics about the Cache Manager that I would like to describe here, such as the “Delayed write failure”, for example, but this post is already getting very big.


    See you… 😉

  • Preventing Process Execution

    During that long period when I was away from new posts on the blog, some things happened that deserved a little place here on my list of posts to write. One of them was the long discussion that took place on the C/C++ group list. It was about the steps to follow to write a driver that would do a bit of everything regarding security services. One of the items especially discussed was the idea of writing a driver that could prevent a certain process from being executed. Let me say right away that I am not going to get into whether this solves a security problem or not. I am not here to discuss that and, to be quite honest, I do not even want to. In this post I will demonstrate, in a very simple way, how we can prevent the execution of a process.

    Tracking the lifetime of Processes

    Before going around putting both feet on a process’s chest to make it fall, let’s first just see how to monitor its lifetime. This is easily done by calling the PsSetCreateProcessNotifyRoutine() routine, which has been available since back when the rainbow was black and white. Although the documentation says it has been available since Windows 2000, I already know this routine from other carnivals and I know it has been around at least since the late Windows NT 3.51. Wow, I am getting old. But back to the subject, this routine registers a callback function that notifies our driver about the start and the end of processes in the system. This is especially useful if a certain driver wants to keep information related to processes; thus, knowing when a process has ended is essential to free the resources used by such information.

    NTSTATUS PsSetCreateProcessNotifyRoutine(
      __in  PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine,
      __in  BOOLEAN Remove
    );

    The callback function registered by this routine has the signature shown below:

    VOID
    (*PCREATE_PROCESS_NOTIFY_ROUTINE) (
        IN HANDLE  ParentId,
        IN HANDLE  ProcessId,
        IN BOOLEAN  Create
        );

    Pretty simple, isn’t it? The first parameter is the ProcessId of the parent process in this creation. This means that, for example, if you start Notepad from “Run…” in the Windows Start menu, we will have Explorer.exe as the parent process of the new Notepad.exe process. The second parameter is the ProcessId of the process being started or ended at the time of the call. Last but not least, we have the flag that will indicate whether this is a start or an end notification for a process.

    One important thing to note here is about the parent process’s ProcessId. This parameter is reliable in process-start notifications, but not so much when it comes to termination. This happens because when a process is being started, its parent process is still there, safe and sound, but when a process ends, even though the ParentId carries the same value as the process-start notification, that data is no longer valid. Let me give an example to make it easier.

    1. Process1(32) creates Process2(57), we receive the call: CreateProcessNotifyRoutine(32, 57, TRUE);
    2. Process1(32) ends.
    3. Process3(32) is created and gets an Id equal to 32.
    4. Process2(57) ends and we receive the call: CreateProcessNotifyRoutine(32, 57, FALSE);

    In the termination notification for Process2 that occurred in step 4, the process whose Id is 32 is now Process3, which, by the way, is not really the parent process of Process2. So when you gather information about a process, do it during its initialization, keep this data in a list and then remove it when the process ends.

    Registering the callback function is very simple, but what really matters is removing this registration when the driver is unloaded. Can you imagine what would happen if one of these notifications were delivered to a driver that is no longer in memory? Well, I can.

    Getting the path of a process image

    It is likely that you want to obtain more information about the processes involved in these notifications; one such piece of information is the path of the file being executed. You can obtain this information using the Id of the processes we receive in the new-process notification. To do this, we will have to use the almost-documented ZwQueryInformationProcess() routine. This is a native API that has existed forever but was never officially documented. To use it, just declare its signature as shown below.

    NTSTATUS
    ZwQueryInformationProcess(IN HANDLE ProcessHandle,
                              IN PROCESSINFOCLASS ProcessInformationClass,
                              OUT PVOID ProcessInformation,
                              IN ULONG ProcessInformationLength,
                              OUT PULONG ReturnLength OPTIONAL);

    If you want to know more about undocumented native APIs, this link is a huge help, but nothing beats this book.

    The code below uses this API to obtain a process’s image from its Pid. Notice that ZwQueryInformationProcess() asks for a handle to the process you want to obtain information about. To obtain that handle we will first need to obtain the EPROCESS structure that represents a process in Kernel-Mode. We will do this using the PsLookupProcessByProcessId() function, which will return a pointer to that structure.

    NTSTATUS PsLookupProcessByProcessId(
      __in   HANDLE ProcessId,
      __out  PEPROCESS *Process
    );

    Although opaque, this structure will allow us to obtain the handle to the process it represents, now using the ObOpenObjectByPointer() function of the Object Manager.

    NTSTATUS ObOpenObjectByPointer(
      __in      PVOID Object,
      __in      ULONG HandleAttributes,
      __in_opt  PACCESS_STATE PassedAccessState,
      __in      ACCESS_MASK DesiredAccess,
      __in_opt  POBJECT_TYPE ObjectType,
      __in      KPROCESSOR_MODE AccessMode,
      __out     PHANDLE Handle
    );

    I think everything will be easier to understand with the source below. After all, a line of code is worth more than a thousand words. The following function obtains the EPROCESS structure of a process, then obtains the handle to it, and with that handle obtains the information we want from the process. OK, OK, OK… Here is the source, but don’t forget to read the comments.

    /****
    ***     GetProcessImageName
    **
    **      Returns a PUNICODE_STRING containing the path
    **      of the image used by the process whose Pid was
    **      provided as a parameter.
    */
    NTSTATUS
    GetProcessImageName(HANDLE           hProcessId,
                        PUNICODE_STRING* ppusImageName)
    {
        NTSTATUS        nts;
        PUNICODE_STRING pusImageName = NULL;
        ULONG           ulSize;
        HANDLE          hProcess;
        PEPROCESS       pEProcess;
     
        //-f--> First of all, zero out the output variable.
        *ppusImageName = NULL;
     
        //-f--> Here we obtain the structure that represents a process
        //     (EPROCESS) from its Pid;
        nts = PsLookupProcessByProcessId(hProcessId,
                                         &pEProcess);
        if (!NT_SUCCESS(nts))
            return nts;
     
        //-f--> Now we obtain a handle to this object.
        nts = ObOpenObjectByPointer(pEProcess,
                                    OBJ_KERNEL_HANDLE,
                                    NULL,
                                    0,
                                    *PsProcessType,
                                    KernelMode,
                                    &hProcess);
     
        //-f--> Whether or not the handle was obtained,
        //      we will have to release the reference we obtained
        //      to the EPROCESS.
        ObDereferenceObject(pEProcess);
        if (!NT_SUCCESS(nts))
            return nts;
     
        //-f--> Now that we have the handle to the process, we can
        //      obtain information about it; in this case,
        //      we will obtain the path of the process image.
        nts = ZwQueryInformationProcess(hProcess,
                                        ProcessImageFileName,
                                        NULL,
                                        0,
                                        &ulSize);
     
        //-f--> To get the right size, we pass zero on the first
        //      attempt; this will return an error and the number
        //      of bytes needed to obtain this information.
        if (nts != STATUS_INFO_LENGTH_MISMATCH)
            return nts;
     
        //-f--> The returned size includes the size of the UNICODE_STRING structure,
        //      so everything is allocated at once.
        pusImageName = (PUNICODE_STRING) ExAllocatePoolWithTag(PagedPool,
                                                               ulSize,
                                                               TRACER_TAG);
        //-f--> Oops! Close Photoshop and try again.
        if (!pusImageName)
            return STATUS_INSUFFICIENT_RESOURCES;
     
        //-f--> Now we offer the buffer allocated with the right size.
        //      What could go wrong? (EVERYTHING!)
        nts = ZwQueryInformationProcess(hProcess,
                                        ProcessImageFileName,
                                        pusImageName,
                                        ulSize,
                                        &ulSize);
        if (!NT_SUCCESS(nts))
        {
            //-f--> Oops! Something went wrong.
            ExFreePoolWithTag(pusImageName,
                              TRACER_TAG);
        }
        else
        {
            //-f--> All good so far. The caller is in charge
            //      of freeing the memory allocated here.
            *ppusImageName = pusImageName;
        }
     
        return nts;
    }
     

    With this routine it becomes easy to write the following callback function that will show us basic information about the processes started and ended.

    /****
    ***     OnCreateProcess
    **
    **      Callback function that will be registered if this
    **      driver is running on Windows Vista or earlier.
    */
    VOID
    OnCreateProcess(HANDLE  hParentId,
                    HANDLE  hProcessId,
                    BOOLEAN bCreate)
    {
        //-f--> Here we check whether the event is about a creation
        //      or termination of a process.
        if (bCreate)
        {
            NTSTATUS        nts;
            PUNICODE_STRING pusImageName = NULL;
     
            //-f--> Gets the path of the image that was used by
            //      this process.
            nts = GetProcessImageName(hProcessId,
                                      &pusImageName);
     
            if (NT_SUCCESS(nts))
            {
                //-f--> If the path was obtained successfully,
                //      it registers the process-start notification.
                DbgPrint("[Process Tracer] Action = Starting\n"
                         "                 Process Id = 0x%x\n"
                         "                 Parent Id = 0x%x\n"
                         "                 Image name = %wZ\n\n",
                         hProcessId,
                         hParentId,
                         pusImageName);
     
                //-f--> Releases the allocated resources.
                ExFreePool(pusImageName);
            }
        }
        else
        {
            //-f--> Registers the process-termination event.
            DbgPrint("[Process Tracer] Action = Finishing\n"
                     "                 Process Id = 0x%x\n"
                     "                 Parent Id = 0x%x\n\n",
                     hProcessId,
                     hParentId);
        }
    }
     

    With these functions working on a Windows XP, we will have the following output in the debugger.

    [Process Tracer] Action = Starting
                     Process Id = 0x35c
                     Parent Id = 0x694
                     Image name = \Device\HarddiskVolume1\WINDOWS\system32\notepad.exe
     
    kd> !process 0x35c 0
    Searching for Process with Cid == 35c
    Cid handle table at e1003000 with 380 entries in use
     
    PROCESS 82100020  SessionId: 0  Cid: 035c    Peb: 7ffd7000  ParentCid: 0694
        DirBase: 08840400  ObjectTable: e10d2400  HandleCount:  41.
        Image: notepad.exe
     
    kd> !process 0x694 0
    Searching for Process with Cid == 694
    Cid handle table at e1003000 with 380 entries in use
     
    PROCESS 821cc228  SessionId: 0  Cid: 0694    Peb: 7ffd6000  ParentCid: 0640
        DirBase: 08840200  ObjectTable: e18df2a0  HandleCount: 365.
        Image: explorer.exe
     
    kd> g
    [Process Tracer] Action = Finishing
                     Process Id = 0x35c
                     Parent Id = 0x694
     

    Right at the beginning I start the Notepad process from the “Run…” menu as I mentioned at the very beginning. After that I use WinDbg’s !process extension to obtain minimal information about the processes involved in this notification. Then I close Notepad, giving rise to the last message shown above.

    A new API in Windows Vista SP1

    All right, this is very cool, but we are here to talk about how to prevent a certain process from being executed. Preventing process execution in Windows Vista became child’s play with the new PsSetCreateProcessNotifyRoutineEx() routine, whose signature is listed right below:

    NTSTATUS PsSetCreateProcessNotifyRoutineEx(
      __in  PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine,
      __in  BOOLEAN Remove
    );

    This routine registers a callback function that also notifies your driver about the start and end of processes in the system. The first parameter indicates the callback routine to be registered, while the second parameter indicates whether the routine should be registered or removed. Very similar to its older sister PsSetCreateProcessNotifyRoutine(). The callback routine must have the following signature:

    VOID CreateProcessNotifyEx(
      __inout   PEPROCESS Process,
      __in      HANDLE ProcessId,
      __in_opt  PPS_CREATE_NOTIFY_INFO CreateInfo
    );

    It is easy to see that the parameters of this callback function have changed quite a bit. To know whether the event is about the creation or the termination of a process, just check the CreateInfo parameter. If it is non-null, then it is about a new process being executed; otherwise, about its termination. Let’s take a look at this structure:

    typedef struct _PS_CREATE_NOTIFY_INFO {
      SIZE_T              Size;
      union {
        ULONG  Flags;
        struct {
          ULONG FileOpenNameAvailable  :1;
          ULONG Reserved  :31;
        } ;
      } ;
      HANDLE              ParentProcessId;
      CLIENT_ID           CreatingThreadId;
      struct _FILE_OBJECT *FileObject;
      PCUNICODE_STRING    ImageFileName;
      PCUNICODE_STRING    CommandLine;
      NTSTATUS            CreationStatus;
    } PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;

    Yeah, it seems that life became much easier for those who want to seek greater detail about the processes involved in the notification. The great appeal of this new version is that we can prevent the creation of a process just by modifying the CreationStatus field. Just to exemplify this ease, I wrote the callback function below. Always read the comments.

    /****
    ***     OnCreateProcessEx
    **
    **      Callback function that will be registered if this
    **      driver is running on Windows Vista SP1 or later.
    */
    VOID
    OnCreateProcessEx(PEPROCESS                 pEProcess,
                      HANDLE                    hProcessId,
                      PPS_CREATE_NOTIFY_INFO    pCreateInfo)
    {
        //-f--> Here we check whether the event is about a creation
        //      or termination of a process.
        if (pCreateInfo)
        {
            UNICODE_STRING  usBlockingApp;
     
            //-f--> Since this is just an example, I am putting the file
            //      path hard coded here, but remember that references to
            //      file images may use HarddiskVolume1 or other
            //      variations that change depending on many things.
            RtlInitUnicodeString(&usBlockingApp,
                                 L"\\??\\C:\\Windows\\System32\\Notepad.exe");
     
            //-f--> Comparing the image of the process that was just created
            //      with the path I used above.
            if (RtlEqualUnicodeString(&usBlockingApp,
                                      pCreateInfo->ImageFileName,
                                      TRUE))
            {
                //-f--> All right, now it is time to grab the guy and show up terrifying:
                //      "Hand it over, playboy!"
                DbgPrint("[Process Tracer] Action = Blocking\n"
                         "                 Process Id = 0x%x\n"
                         "                 Parent Id = 0x%x\n"
                         "                 Image name = %wZ\n\n",
                         hProcessId,
                         pCreateInfo->ParentProcessId,
                         pCreateInfo->ImageFileName);
     
                //-f--> Changes the process-creation status so that
                //      it does not proceed.
                pCreateInfo->CreationStatus = STATUS_ACCESS_DENIED;
            }
            else
            {
                //-f--> It is not our "man"; let the process start normally.
                DbgPrint("[Process Tracer] Action = Starting\n"
                         "                 Process Id = 0x%x\n"
                         "                 Parent Id = 0x%x\n"
                         "                 Image name = %wZ\n\n",
                         hProcessId,
                         pCreateInfo->ParentProcessId,
                         pCreateInfo->ImageFileName);
            }
        }
        else
        {
            //-f--> Here we just register the notification of the termination of
            //      a process.
            DbgPrint("[Process Tracer] Action = Finishing\n"
                     "                 Process Id = 0x%x\n\n",
                     hProcessId);
        }
    }
     

    Notice that the path to the file whose creation I am blocking is hard-coded in the example source. This path may have a different syntax for the same file depending on how the process is created or on which version of Windows we are running. For this reason, if you want to run this test at home, check that the syntax is as I used here; otherwise adjust it and recompile the example.

    One driver, two options

    This new API is available only for Windows Vista SP1 and later, but it is likely that you want a driver that is still able to run on earlier versions of Windows even if the system does not support this routine. As you must already know, simply calling the PsSetCreateProcessNotifyRoutineEx() routine in your driver will create a static dependency and your driver will not be able to be loaded on older versions of Windows.

    To prevent this static dependency while having a single binary that can be loaded on both older and newer versions, using the newer version of this routine, we will use the MmGetSystemRoutineAddress() function, which is the Kernel-Mode sister of the well-known GetProcAddress() in User-Mode. The example driver available for download at the end of this post has these characteristics precisely to demonstrate how this can be done. Obviously, running the driver on Windows XP we will not have operating-system support to interrupt a process, and we will have to resort to alternative techniques to obtain the same result.

    The DriverEntry function for this driver looks like this:

    /****
    ***     DriverEntry
    **
    **      Entry point of the driver. If you are still thinking
    **      it is easy, do not worry, you will end up changing your mind.
    */
    NTSTATUS
    DriverEntry(PDRIVER_OBJECT     pDriverObj,
                PUNICODE_STRING    pusRegistryPath)
    {
        UNICODE_STRING  usSystemRoutine;
        NTSTATUS        nts;
     
        //-f--> Registers our cleanup function so that
        //      our driver can be unloaded.
        pDriverObj->DriverUnload = OnDriverUnload;
     
        //-f--> Initializes the name of the routine we will try to look up dynamically.
        RtlInitUnicodeString(&usSystemRoutine,
                             L"PsSetCreateProcessNotifyRoutineEx");
     
        //-f--> Here we check whether the system already supports PsSetCreateProcessNotifyRoutineEx
        //      Exactly as the teacher taught GetProcessAddress() back in kindergarten.
        *(PVOID*)&pfPsSetCreateProcessNotifyRoutineEx = MmGetSystemRoutineAddress(&usSystemRoutine);
     
        if (pfPsSetCreateProcessNotifyRoutineEx)
        {
            //-f--> If we are on Windows Vista SP1 or later, we will have the address
            //      of this routine, and therefore we will register with it.
            nts = pfPsSetCreateProcessNotifyRoutineEx(OnCreateProcessEx,
                                                      FALSE);
        }
        else
        {
            //-f--> Whoa! We are running on some 386. Let us register with
            //      that routine from the 90s.
            nts = PsSetCreateProcessNotifyRoutine(OnCreateProcess,
                                                  FALSE);
        }
     
        ASSERT(NT_SUCCESS(nts));
        return nts;
    }

    Testing PsSetCreateProcessNotifyRoutineEx()

    By now you must be jumping for joy imagining that your world-domination driver will finally work with great ease using this new API, but the thing is that this routine is not for just anyone. That is because only digitally signed drivers can call this new routine without receiving the STATUS_ACCESS_DENIED return.

    But Fernando, how am I going to be able to test your example driver? I don’t have a certificate or anything!

    This routine initially checks whether the module where your driver is defined has the integrity-check bit set. To set this bit just for fun, add the /INTEGRITYCHECK option to your project’s linker options. The sources file of the example project looks like this:

    TARGETNAME=ProcessTracer
    TARGETTYPE=DRIVER
     
    SOURCES=ProcessTracer.cpp
     
    LINKER_FLAGS=/INTEGRITYCHECK
     

    This will make the system check your driver’s signature, but since your driver is not signed, you will still receive the same error. To finally see this work without even having a certificate, you will have to disable code-integrity checking for drivers in Windows Vista.

    All right, no panic. Start Windows Vista and press F8 as soon as Boot begins, then select the option below in the menu that appears as shown below:

    With these two modifications it is possible to test the example driver and get an output in the debugger like the one illustrated below:

    [Process Tracer] Action = Blocking
                     Process Id = 0x3a4
                     Parent Id = 0xd98
                     Image name = \??\C:\Windows\system32\notepad.exe

    In this case, once again, I tried to start Notepad through the “Run…” menu, but this time the output I got was the one shown below:

    Phew! Another giant post for the collection. I hope I helped, and see you next time!

    ProcessTracer.zip

  • Last Post via Blogger

    Hi, gang! Man, I was already missing posting something. Too bad this is yet another one of those annoying Off-Topic posts. What can I do? It’s what we have for now.

    This morning I received yet another warning e-mail from Blogger saying that support for publishing via FTP is numbered. Since this feature is used by less than 5% of its users, they decided to put an end to it. But that was not the main reason I decided to stop posting through Blogger and use WordPress. I had already been rehearsing this transition for at least two years. College and other flimsy excuses wouldn’t let me put this idea into practice. Some readers had already been complaining about the lack of a general index and the separation of posts into categories, things that WordPress does with one hand tied behind its back.

    As a web designer, I am an excellent driver developer, and I had already heard that WordPress used PHP and MySQL, subjects I have zero familiarity with. Initially I counted on the help of my brother, who already had a blog on WordPress, but just like me, he also had college and other priorities. For a while I hired the services of a web designer who put my ideas about the new layout into practice, but in the end he got involved with something bigger and I was left hanging again.

    The idea of migrating the blog persisted, but laziness, lack of time and lack of command of HTML and CSS made me keep pushing this problem aside. I even read the official guide of the famous “I need to learn this no matter what” series, the book “WordPress for Dummies”. For someone used to reading books on somewhat hairier subjects, the book’s 380-plus pages were like a walk in the park. The most surprising thing for me was to discover that WordPress is a tool that requires neither practice nor skill, and that included the PHP and MySQL skills I don’t have. A powerful, flexible and simple tool. The unpleasant part of this story is that everything the book said about HTML and CSS is that it did not talk about it.

    So far excellent. The 68 posts could be migrated but the layout was still my karma. Other attempts to find someone willing to do the design for me showed me that I am in the wrong job. This driver-development business is nothing. To really make money, the thing to do is Web Design.

    About three months ago, when I received the first warning from Blogger about the end of FTP support, was the “Now or never” moment. My college no longer served as an excuse and little by little I got to work. After a lot of manual work migrating each post and each comment, came the layout. Thanks to the W3Schools site, to my brother’s private Photoshop lessons and to the help of some friends, this week I managed to publish the new blog. Many things are still missing. The “About Me” page still has nothing about me. The training page, which will talk about the courses I offer, still says exactly this: “This page will talk about the courses I offer”. I am still installing some plug-ins that will help with one thing here and another there. But the fact is that two days before the FTP support deadline, the new blog is already online.

    This post was the last one to be built on Blogger, with the main purpose of warning those who follow the original RSS that the address has now changed. The new RSS will be provided at a new address.

    I could not end this post without thanking the people who had the patience to put up with me and my tests and doubts about HTML, CSS and who-knows-what-else. A big thank you to my friends Lesma, Thiago Oliveira, Thiago Brito, my brother Kabloc, Willam, Francisco, to my wife Magda who migrated all the comments, and to all the others I don’t remember right now (I pestered a lot of people).

    If you didn’t know the old blog, here are the pages of the old retiree.
    Cheers!

  • Sixth C/C++ Programmers Meeting

    The start of the year is always the same rush. After we begin to get used to the idea that the long Christmas and New Year holidays are over, Carnival soon comes and throws everything off. I took advantage of this beginning of the year to take a well-deserved vacation, since my Computer Engineering course has finally ended. As a nice start for my reintegration into society, nothing better than a trip to the Brazilian northeast. Weeks before this trip I found out about the new meeting of the C/C++ programmers community. Because of the trip I would miss the event.

    A month went by and last week I saw that the event was postponed to next March sixth. Very good news, since besides being able to attend the event I will also speak a little. I already took part in another meeting of programmers and I was very happy to be able to speak to so many people about this subject that is so mysterious to many: the development of drivers for Windows.

    From my point of view the results were very good. The talk gave an introduction to the subject of driver development and obviously the 60 minutes of the talk were not enough to explain the subject in detail, but it was interesting to show the tip of the iceberg and to be able to answer some questions from those present. I wrote a post that comments on the meeting, but I recommend the post by my friend Lesma, which turned out very good.

    Dealing with Virtual Memory in Drivers

    Well, in this new meeting I am not going to give an introduction to the subject again, but I will comment on some characteristics and curiosities about Virtual Memory in driver development. Understand that I am not going to make a summary of chapter 9 of Windows Internals, “Memory Management”, which talks about Page Table Entries and Working Sets, but rather to demonstrate that, unlike a User-Mode application, drivers need to be aware of the fundamental concepts of virtual memory and paging, controlling the paging of objects and meeting the requirements of memory paging and address space.

    The topics to be discussed in the talk will be the following:

    • Overview of Virtual Memory and Paging.
    • I/O operations and Buffer handling.
    • Thread priority and memory access.
    • Allocation pools, Tags and Quotas.
    • Avoiding Fragmentation.
    • Drivers in the paging path.
    • Controlling the paging of functions.
    • Obtaining real memory addresses.
    • Direct memory access (DMA) devices.
    • Driver Verifier features.
    • Hands on: Memory operations by drivers in WinDbg.
    • Questions.

    This is the official page of the event and registration is open. See you there!

    Download the slides

  • Writing Filters

    Let’s play with something more interesting now. Obviously we will still take small steps so as not to get lost in so many details. Today I will talk about driver filters. The Windows IoManager lets us add functionality to certain drivers without having to replace them. A classic example would be a file-encryption driver. You don’t need to write a new file system driver to have such functionality. You can simply write a filter that would sit between the file system driver and the rest of the system.

    In the figure below we can observe the flow of IRPs that go from the IoManager to a certain driver; a filter is installed on top of the existing driver and starts receiving the IRPs in place of the original driver. With this, the filter has the opportunity to change the parameters of the received IRPs, or log the original driver’s activity, or duplicate system requests to that driver, or even deny the original driver’s service.

    In a write operation, a filter could encrypt the data before sending it to the original driver, and similarly, in a read operation the filter could decrypt data before delivering it to the system.

    It still won’t be this time that we build a file system encryption filter. Real-time file-encryption filters are among the most complex drivers to write. Let’s choose a simpler driver, not to say a really silly one, to apply the basic concepts demonstrated here.

    Speaking of a silly driver, let’s use the driver from this post that we already saw here. This driver simply stores a list of strings sent by an application in write operations. Such strings are returned to the application in read operations.

    Writing the DriverEntry

    As we saw in this other post, one of the things the DriverEntry() function does in an ordinary driver is to set the Dispatch Routines that the driver will service for a certain device. To do this you must fill in the Major Functions array that is in the DRIVER_OBJECT structure.

        //-f--> Sets the driver's dispatch routines.
        pDriverObj->MajorFunction[IRP_MJ_CREATE] = OnCreate;
        pDriverObj->MajorFunction[IRP_MJ_CLEANUP] = OnCleanup;
        pDriverObj->MajorFunction[IRP_MJ_CLOSE] = OnClose;
        pDriverObj->MajorFunction[IRP_MJ_READ] = OnRead;
        pDriverObj->MajorFunction[IRP_MJ_WRITE] = OnWrite;

    In the case of our example filter, we just want to monitor the activity of the driver we are attached to; this way we will always have to forward any received IRPs to the driver below. A simple and common way to do this is to set all the dispatch routines to a single function. That function is responsible for simply logging the received request and passing it along.

    If we take a look at the definition of IRP_MJ_CREATE and its friends, we will see the following excerpt from the wdm.h file.

    //
    // Define the major function codes for IRPs.
    //
     
     
    #define IRP_MJ_CREATE                   0x00
    #define IRP_MJ_CREATE_NAMED_PIPE        0x01
    #define IRP_MJ_CLOSE                    0x02
    #define IRP_MJ_READ                     0x03
    #define IRP_MJ_WRITE                    0x04
    #define IRP_MJ_QUERY_INFORMATION        0x05
    #define IRP_MJ_SET_INFORMATION          0x06
    #define IRP_MJ_QUERY_EA                 0x07
    #define IRP_MJ_SET_EA                   0x08
    #define IRP_MJ_FLUSH_BUFFERS            0x09
    #define IRP_MJ_QUERY_VOLUME_INFORMATION 0x0a
    #define IRP_MJ_SET_VOLUME_INFORMATION   0x0b
    #define IRP_MJ_DIRECTORY_CONTROL        0x0c
    #define IRP_MJ_FILE_SYSTEM_CONTROL      0x0d
    #define IRP_MJ_DEVICE_CONTROL           0x0e
    #define IRP_MJ_INTERNAL_DEVICE_CONTROL  0x0f
    #define IRP_MJ_SHUTDOWN                 0x10
    #define IRP_MJ_LOCK_CONTROL             0x11
    #define IRP_MJ_CLEANUP                  0x12
    #define IRP_MJ_CREATE_MAILSLOT          0x13
    #define IRP_MJ_QUERY_SECURITY           0x14
    #define IRP_MJ_SET_SECURITY             0x15
    #define IRP_MJ_POWER                    0x16
    #define IRP_MJ_SYSTEM_CONTROL           0x17
    #define IRP_MJ_DEVICE_CHANGE            0x18
    #define IRP_MJ_QUERY_QUOTA              0x19
    #define IRP_MJ_SET_QUOTA                0x1a
    #define IRP_MJ_PNP                      0x1b
    #define IRP_MJ_PNP_POWER                IRP_MJ_PNP      // Obsolete....
    #define IRP_MJ_MAXIMUM_FUNCTION         0x1b

    Note that there is a special definition, IRP_MJ_MAXIMUM_FUNCTION, which indicates the maximum index of the dispatch routines table. We will use a simple loop to make all the routines in our table point to a single routine that we will name OnForwardDispatch.

        //-f--> Sets all the driver's dispatch routines to
        //      one that forwards the IRP to the original driver.
        for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
            pDriverObj->MajorFunction[i] = OnForwardDispatch;

    We will see the implementation of this routine later. The next step we will take here is to locate the device we are going to attach to. To do this, we will use the IoGetDeviceObjectPointer() routine. It basically receives the device name and returns a reference to it.

    NTSTATUS 
      IoGetDeviceObjectPointer(
        IN PUNICODE_STRING  ObjectName,
        IN ACCESS_MASK  DesiredAccess,
        OUT PFILE_OBJECT  *FileObject,
        OUT PDEVICE_OBJECT  *DeviceObject
        );

    Notice that this routine has two output parameters. Besides the pointer to the device object we also receive a pointer to a file object. I have seen some people get confused about these two parameters, so I will give this some emphasis.

    The pointer to the file object represents a connection created between your driver and the device you opened. As we saw in this post, a file object is created to represent connections between user-mode applications and your driver. Applications use that file object through the handle obtained in the call to the CreateFile() routine. Here we have something similar, but only the Kernel was involved. This means that if you wanted to, you could launch IRPs to the device requesting operations as an application would, but we won’t see that today; we still have a filter to finish.

    The big confusion regarding these two parameters is about the references between the objects. In the documentation we see that the caller of this routine must release the reference it gained when the device is no longer used. We do this simply by using the ObDereferenceObject() routine.

    VOID 
      ObDereferenceObject(
        IN PVOID  Object
        );

    “I’ve got it! Since we are obtaining a reference to a device object, then I should pass the device object pointer. Right?”

    Er… Actually no. The file object is an indirect reference to the device object. According to the figure below, if we imagine that the reference counters only concern our references, when we call ObDereferenceObject() for the file object, its reference count would drop to zero and a new call to ObDeferenceObject() would be made indirectly for the device object, causing its reference count to also drop to zero, destroying the object.

    After obtaining the pointer to the target device, we will have to create our own device, which will receive the IRPs in place of the original device. To do this we will still use the IoCreateDevice() routine as we did with drivers, but with some differences.

    The first difference is that your device normally has no name. It is possible to create named filters, but that can create a security flaw. This happens because when a name is looked up in the Object Manager, its security rules are evaluated. When we create a named filter, we create the possibility of the same object being obtained by a different name that may have less restrictive security rules. But that is another subject.

    When we create a device, we need to inform the size of the device extension.

    “What is a device extension?”

    Device extension is simply a space of memory that is associated with the device object. Such space normally holds information concerning the device. The address of the device we are attached to normally sits in the device extension. This way, we can define that our device extension must contain the following structure.

    //-f--> Our device extension will contain only the address
    //      of the device we are attached to.
    typedef struct _DEVICE_EXTENSION
    {
        PDEVICE_OBJECT  pNextDeviceObj;
     
    } DEVICE_EXTENSION, *PDEVICE_EXTENSION;

    After creating our device object, we configure the I/O method by copying the DO_BUFFERED_IO and DO_DIRECT_IO bits. If you don’t remember these bits, take a look at this post. The filter must propagate the original driver’s choice, and the driver has the commitment not to change the method during its lifetime.

    Now we are ready to attach to the chosen device, and we will do this using the IoAttachDeviceToDeviceStackSafe() routine to make our device enter the device stack.

    NTSTATUS
      IoAttachDeviceToDeviceStackSafe(
        IN PDEVICE_OBJECT  SourceDevice,
        IN PDEVICE_OBJECT  TargetDevice,
        IN OUT PDEVICE_OBJECT  *AttachedToDeviceObject 
        );

    With this call we will obtain the pointer to the device we will be attached to; that device will be the next device that will receive the IRP after you pass it along.

    “But Fernando, don’t we already have the pointer to the target device?”

    Very well, when you obtain the pointer to a device, you theoretically don’t know whether there are filters already attached on top of it. The pointer you receive in this routine may not be the pointer to the target device. In the figure below we can understand how this relationship happens.

    “Fernando, is there an unsafe version of this routine?”

    There actually is, IoAttachDeviceToDeviceStack().

    PDEVICE_OBJECT 
      IoAttachDeviceToDeviceStack(
        IN PDEVICE_OBJECT  SourceDevice,
        IN PDEVICE_OBJECT  TargetDevice
        );

    It is considered unsafe because of a small time window that can cause a race condition. Notice that the difference between these routines is the way of obtaining the address of the next device. In the original version, this address is obtained by the function’s return value. If we put this function to run in slow motion we will see the following steps.


    1. Your device is attached to the device stack.
    2. The address of the next device is returned by the function.
    3. Your driver receives this address on the function’s return and updates the device extension.
    4. IRPs start arriving and your driver forwards them to the next device in the list.


    Everything looks beautiful and even gives the impression that everything will work very well in any situation, but a driver developer is a critter trained to hunt for race conditions. Take another look at the sequence, but now in super slow motion. With this super-slow-motion camera we can now observe the steps that can occur between steps 2 and 3.


    1. Your device is attached to the device list.
    2. The address of the next device is returned by the function.
      1. Your thread is interrupted and an IRP launched by an application running in parallel begins its journey through this device stack.
      2. Your device, which is already attached, receives the IRP and tries to forward it to the device below.
      3. Oops! Our device extension has not yet been updated with that address.
      4. Your driver remembers when it was a child and everything it had lived through until then.
      5. It decides to fully join that dance and send the IRP to a device whose pointer is still NULL, causing a BSOD.
    3. “Jeremias, I am a man. Something you are not, and I don’t shoot people in the back…”


    Anyway, understand that even if you use the return value of the IoAttachDeviceToDeviceStack() routine directly to update your device extension, there is still a time window in which your device will be attached but the value has not yet been updated in the device extension. This is because a routine’s return value comes through a register. Taking the value of that register and updating a variable still gives chances for bad luck.

        //-f--> ----==== DO NOT COPY THIS ====----
        //      Here we still have a time window between the device being
        //      attached and the value of pNextDeviceObj being updated.
        pDeviceExt->pNextDeviceObj = IoAttachDeviceToDeviceStack(pMyDeviceObj,
                                                                 pTargetDeviceObj);

    The IoAttachDeviceToDeviceStackSafe() routine makes the system interrupt the flow of IRPs in this stack until the variable pointed to by the output pointer is updated. For this reason, the address passed to this routine must be the final address of the variable where this value will be stored, which in our case is pDeviceExt->pNextDeviceObj without going through intermediate variables.

    These details are important and will make you understand that using the safe version of this routine is no guarantee that everything will go right. Imagine that using the safe version you receive the address of the next device in a local variable and then update your device extension. This is one of those typical cases where you need to replace that component that sits between the keyboard and the chair.

    Think it’s overkill? Try reading chapter 5 of the book “Programming the Microsoft Windows Driver Model” where Walter Oney talks about how to deal with IRP cancellation.

    After attaching our device we can already release the reference obtained by IoGetDeviceObjectPointer() using the ObDereferenceObject() routine, since the IoAttachDeviceToDeviceStackSafe() routine already guaranteed the reference until this connection is undone.

    Very well. For those who couldn’t understand almost anything I said, here is the source code of the implementation of our example DriverEntry(). You know how a programmer’s mind is, sometimes an if is worth more than a thousand pages of explanation.

    /****
    ***     DriverEntry
    **
    **      Entry point of the driver.
    **      Welcome to hell.
    */
    extern "C"
    NTSTATUS
    DriverEntry(IN PDRIVER_OBJECT  pDriverObj,
                IN PUNICODE_STRING pusRegistryPath)
    {
        NTSTATUS            nts;
        PDEVICE_OBJECT      pMyDeviceObj;
        int                 i;
        UNICODE_STRING      usDeviceName;
        PDEVICE_OBJECT      pTargetDeviceObj;
        PFILE_OBJECT        pFileObj;
        PDEVICE_EXTENSION   pDeviceExt;
     
        //-f--> Sets the driver's unload routine.
        pDriverObj->DriverUnload = OnDriverUnload;
     
        //-f--> Sets all the driver's dispatch routines to
        //      one that forwards the IRP to the original driver.
        for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
            pDriverObj->MajorFunction[i] = OnForwardDispatch;
     
        //-f--> We initialize the string with the name of the device
        //      we want to attach to.
        RtlInitUnicodeString(&usDeviceName,
                             L"\\Device\\StringList");
     
        //-f--> We get the pointer to the target device
        nts = IoGetDeviceObjectPointer(&usDeviceName,
                                       FILE_READ_DATA,
                                       &pFileObj,
                                       &pTargetDeviceObj);
        if (!NT_SUCCESS(nts))
            return nts;
     
        //-f--> We create our device object
        nts = IoCreateDevice(pDriverObj,
                             sizeof(DEVICE_EXTENSION),
                             NULL,
                             pTargetDeviceObj->DeviceType,
                             pTargetDeviceObj->Characteristics,
                             FALSE,
                             &pMyDeviceObj);
        if (!NT_SUCCESS(nts))
        {
            //-f--> Oops!
            ObDereferenceObject(pFileObj);
            return nts;
        }
     
        //-f--> Gets our DEVICE_EXTENSION
        pDeviceExt = (PDEVICE_EXTENSION)pMyDeviceObj->DeviceExtension;
     
        //-f--> Uses the same IO method as the original driver
        pMyDeviceObj->Flags |= pTargetDeviceObj->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO);
     
        //-f--> Here our driver enters the device stack
        nts = IoAttachDeviceToDeviceStackSafe(pMyDeviceObj,
                                              pTargetDeviceObj,
                                              &pDeviceExt->pNextDeviceObj);
        if (!NT_SUCCESS(nts))
        {
            //-f--> Oops!
            IoDeleteDevice(pMyDeviceObj);
            //-f--> There is no missing return here.
        }
     
        //-f--> Whether we are attached or not, we must release
        //      the reference obtained from the target device.
        ObDereferenceObject(pFileObj);
        return nts;
    }

    Writing the OnDriverUnload

    Here is where the party ends; before our driver is unloaded by the system, we will have to remove our device from the device stack and destroy it. (Evil laughter)

    The code here is simple and requires no explanation if you are able to read the comments contained in it.

    /****
    ***     OnDriverUnload
    **
    **      Driver's unload routine.
    */
    VOID
    OnDriverUnload(IN PDRIVER_OBJECT  pDriverObj)
    {
        PDEVICE_OBJECT      pMyDeviceObj;
        PDEVICE_EXTENSION   pDeviceExt;
     
        //-f--> Our device is in the list of devices created by this driver
        //      so let's get it just like this:
        pMyDeviceObj = pDriverObj->DeviceObject;
     
        //-f--> Here we get the device extension.
        pDeviceExt = (PDEVICE_EXTENSION)pMyDeviceObj->DeviceExtension;
     
        //-f--> Here we remove our device from the stack, passing the address of the
        //      next device to the routine below.
        IoDetachDevice(pDeviceExt->pNextDeviceObj);
     
        //-f--> Now we can destroy our device (evil laughter)
        IoDeleteDevice(pMyDeviceObj);
    }

    “Fernando, is our driver forced to unload when the original driver is unloaded?”

    That is the WDM philosophy: drivers are loaded automatically when their devices are detected and unloaded when their devices are disabled or removed. Filters follow the same rules and are loaded/unloaded based on these events.

    But that is not what happens here. The example drivers I use on this blog are of the Legacy model, not WDM. In the Legacy model, drivers are started following their load order in the registry, and it has nothing to do with the detection of your device. Filters need to start after the original drivers, and this is also controlled by their load order. This post talks about the load order of legacy drivers.

    “OK! You talked and talked and didn’t answer my question. What happens if I request the unload of the original driver while there is a filter attached on top of it?”

    The unloading of the drivers that form a stack must occur in reverse order to their loading. In this case the filter must be unloaded before the original driver, unstacking the devices from top to bottom. If the original driver receives an unload request while there are still references to its devices, whether by a filter or by an application, the unload is postponed until its references are undone. Until then, the attempt to obtain new references to a device that received the unload request will be denied.

    Writing Dispatch Routines

    A filter’s dispatch routines are also different from a driver’s dispatch routines. Although they can complete an IRP by calling IoCompleteRequest(), they normally pass the request along using the IoCallDriver() routine. I will talk more about the behavior of a filter’s dispatch routines in future posts. In this example filter we will just log the activity and pass the request to the next driver.

    When we talk about passing a request along we are actually talking about passing IRPs along. Reading this other post is essential for what we are going to do in the implementation of our dispatch routines.

    To finish this post while still in this life, here is the code for the implementation of our dispatch routine. After reading the post about IRPs that I just recommended, reading the comments in this code should be enough to understand everything that was done here, or not.

    /****
    ***     OnForwardDispatch
    **
    **      Our dispatch routine simply logs the received IRP
    **      and forwards the request onward.
    **
    */
    NTSTATUS
    OnForwardDispatch(IN PDEVICE_OBJECT    pDeviceObj,
                      IN PIRP              pIrp)
    {
        PDEVICE_EXTENSION   pDeviceExt;
        PIO_STACK_LOCATION  pStack;
     
        //-f--> Gets pointer to the device extension
        pDeviceExt = (PDEVICE_EXTENSION)pDeviceObj->DeviceExtension;
     
        //-f--> Gets the current stack location
        pStack = IoGetCurrentIrpStackLocation(pIrp);
     
        //-f--> Prints the name of the IRP's major function
        ASSERT(pStack->MajorFunction <= IRP_MJ_MAXIMUM_FUNCTION);
        DbgPrint("[StringFilter] : %s\n", g_szMajorNames[pStack->MajorFunction]);
     
        //-f--> Since we are not modifying anything in the stack location,
        //      let's leave it for the next device to use.
        IoSkipCurrentIrpStackLocation(pIrp);
     
        //-f--> Forwards the IRP to the next device.
        return IoCallDriver(pDeviceExt->pNextDeviceObj,
                            pIrp);
    }

    If you didn’t understand anything, don’t forget to send me an e-mail explaining your doubts (without personal offenses). This will help me understand your difficulties and improve my explanations.

    Testing the Filter

    This is the easy part of the post. To test the filter we will first have to compile, install and start the driver from this post. If you still don’t know how to do that, this other post can give you a starting point. After that, compile, install and start the filter.

    Once installed, we can use the test application to exercise the driver. We will be able to follow the filter’s activity by observing its debug messages, which can be seen in the Kernel debugger or simply by using this application, which does away with the need for a debugger to see a driver’s debug messages.

    “Fernando, I ran a test here and saw that when starting the filter it logs an IRP_MJ_CLOSE event even before starting the test application. What did I do wrong?”

    There is nothing wrong. This happens because of the sequence of steps followed in the filter’s DriverEntry() routine. Initially the driver calls the IoGetDeviceObjectPointer() routine; this makes the IoManager send an IRP_MJ_CREATE request to the original driver. After that we attach to the device stack and finally call the ObDereferenceObject() routine, which will finalize the only reference to the file object we received, sending an IRP_MJ_CLOSE request to the driver below. Since we are already attached to it, we are able to see our own activity on the original driver. This can be observed by the call stack we will have if there is a breakpoint in our dispatch routine when we release the file object reference at the end of DriverEntry().

    kd> k
    ChildEBP RetAddr  
    f8af9bcc 804ee129 StringFilter!OnForwardDispatch
    f8af9bdc 80578f6a nt!IopfCallDriver+0x31
    f8af9c14 805b0b18 nt!IopDeleteFile+0x132
    f8af9c30 80522bd1 nt!ObpRemoveObjectRoutine+0xe0
    f8af9c54 f8c80663 nt!ObfDereferenceObject+0x5f
    f8af9c7c 805767ff StringFilter!DriverEntry+0xf3
    f8af9d4c 8057690f nt!IopLoadDriver+0x66d
    f8af9d74 80534c12 nt!IopLoadUnloadDriver+0x45
    f8af9dac 805c61ee nt!ExpWorkerThread+0x100
    f8af9ddc 80541de2 nt!PspSystemThreadStartup+0x34
    00000000 00000000 nt!KiThreadStartup+0x16

    As usual, the filter source that was implemented in this post is available for download. Our filter does almost nothing, but it will already serve as a base for future posts that will give it more functionality, explaining how such functionalities are implemented.

    See you!

    StringFilter.zip

  • Boot Drivers on Windows

    I have been following the work of my friend Lesma, who on his blog has described how the boot process turns a bunch of bytes on the hard drive into a living operating system. Hitching a ride on that theme, I will take the opportunity to comment on the load order of drivers during this process. With that I can try to answer a frequent reader question: “How do I make my driver be the first one to be loaded?”. Maybe this post can clear things up a bit in that sense, or maybe not.

    Me first! Me first!

    An important point to consider in the Legacy model when we write a driver is the one regarding the moment at which your driver is loaded. This is configured in the “Start” value in the driver’s key in the registry. Four values configure the moment your driver is loaded, namely:

    • Boot (0) – Drivers are loaded during boot, even before the operating system is completely ready to run.
    • System (1) – Drivers are loaded after the boot drivers, when the Kernel is already completely functional.
    • Automatic (2) – In this group drivers are loaded when the subsystems are loaded. Basically together with the User Mode services.
    • Manual (3) – No automatic loading is performed here; the driver is loaded only when someone, or some component, requests its load.
    • Disabled (4) – Even if the driver is requested, its load is denied.


    “Well then, for my driver to be the first one loaded it just needs to be started as boot and that’s it?”

    Actually, your driver is going to compete for a spot in the queue of drivers that want to be started at boot. Several drivers are configured to be started at that moment and yours will be just one more. Even among the boot drivers, a load order needs to be followed so that certain drivers can rely on the services of other drivers. For that reason, drivers are separated into groups. One group at a time is started until all the boot drivers have gone through this process.

    Drivers identify their group by the “Group” value found in their registry key. This key must contain the name of the group the driver belongs to. The names of all the groups can be found in the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ServiceGroupOrder key. In it there is a REG_MULTI_SZ value called “List” that contains the list of all existing groups arranged in their load order.


    “OK, my driver is configured to be started at Boot and configured to start with the first group of drivers. Ready now?”

    Almost. When we talk about starting groups of drivers, it is already implied that more than one driver will be loaded. The order in which those drivers are loaded within each group can also be determined.

    The HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GroupOrderList key keeps a series of values, each with the name of a group. The value is of type REG_BINARY and its interpretation is an array of sets of four bytes. The first set indicates how many tags are contained in that binary buffer. The remaining sets are the numeric representations of each tag. Thus, the interpretation of the buffer shown in the figure below tells us that we have six tags, namely: 1, 2, 3, 4, 5 and 6.


    “But what is a tag?”

    A tag is the numeric identification of a driver within a given group. A driver identifies itself by the “Tag” value that we can find in the driver’s key in the registry.

    Even though the example shows us an increasing order of tags, the tag value does not determine the drivers’ load order. The order is determined by its position within the binary buffer.

    Squeamish about messing with binary buffers? You can use the OSR Driver Loader, which configures all of this for you when used to install a driver.


    Putting your tag first in the list of tags ranks its load order within a given group, but it is still not the determining factor for having your driver loaded before every driver in the universe. A new group can always be created and have its load order configured to come before your group.

    All these rules about groups, tags and the like make no sense for drivers managed by the Plug-And-Play (Pnp) Manager, since the loading of such drivers is requested when the device your driver is related to is detected by the bus driver.

    “Ugh! Fernando, take it easy and try to explain this again.”

    Alright, here we go. When you install a Pnp driver, you associate it with a given device. Just as an example, let’s say this device is a USB/Serial converter. Your driver will be loaded automagically when your device is detected and will be unloaded when the device is removed.

    For it to be detected, other devices need to be detected first, such as the PCI controller, the USB controller and the USB hub. This dependency list creates the USB device stack.

    The PCI controller, when detected, has its driver loaded, and this driver enumerates its child devices, since PCI is a bus. For each detected device, this driver uses the bus to detect the identity of each device and creates a Physical Device Object (PDO) for each of them. The Pnp Manager loads the driver of each device attached to this bus. That driver will create the device’s Functional Device Object (FDO), giving it functionality.

    One of these devices is the USB bus controller. Following the ritual, the USB bus driver enumerates its child devices, creating new PDOs. Thus, the USB hubs are detected and their driver will be loaded. This driver will create a new FDO for each hub. The USB hub driver will enumerate its child devices, and it is at that moment that your device is detected. The driver you wrote will be loaded and the Pnp Manager will call your AddDevice routine, which will receive the PDO that the hub driver created for your device.

    Phew! Alright, everybody calm down. The Plug-And-Play subject is not the focus of this post and is already on my list of future posts.

    All this activity, acting recursively, serves to build the system’s device tree. Knowing that this tree is formed by our drivers and their devices, it becomes explicit here that deep down “we are the trees”. The figure below gives an idea of how the device tree is organized.


    Still speaking about driver load order, it would make no sense to have your driver loaded before all the other drivers, since the basic components for communicating with your device have not been loaded yet, and therefore have no functionality at all. Moreover, having your driver loaded too early will bring you problems dealing with other system components that will not yet be ready to serve your request. More details in this post.

    Debugging at Boot

    Another curious subject that can cause some confusion is the one regarding debugging drivers that are loaded at boot. Even though the Debug connection uses the serial, firewire or even USB medium, the drivers for these media do not need to be loaded for you to be able to debug the system. In other words, the serial-port driver is not used to debug the system when the serial medium is used. This would be a problem if we consider that some drivers are loaded and started before the serial-port driver. How would these drivers be debugged?

    The fact is that the algorithm that deals with the system’s debugging media is defined in the Kernel itself (more precisely in the ntoskrnl.exe module and its siblings). This module deals directly with the hardware responsible for the medium used. This is also the explanation for another frequent question: “My computer does not have a serial port. Can I use a USB/Serial converter on the Target side to debug the system?”. As we have just seen, a USB/Serial converter depends on a whole device stack for the serial port to be available. Such functionality is not implemented in the system’s debug algorithm and, as I commented in this other post, newer systems implement new debugging features in the Kernel.

    Even if your driver is a boot driver, it can still be debugged. This other post also shows how to do the mapping of a boot driver through WinDbg. Don’t know what I’m talking about? It is about having your driver replaced by a new version automatically on the target side when it is loaded. It is worth taking a look.

    Have fun!