Thursday, December 19, 2013

Getting Started with Windows File System Filters - Hooking Up WinDbg to the VM

Sorry I missed last Thursday, I've been traveling and I didn't get around to post anything.
This post is focused to hooking up WinDBG to a VM. There are multiple ways to do attach debuggers to physical machines, each with it's specific pluses and minuses (please note, however, that this list changes often since Microsoft keeps improving the tools, which is awesome). Also, please note that in most cases you'll use two machines, a target (the machine you want to debug) and a host (the debugger host). If I remember correctly there used to be a way to attach WinDBG to the kernel it was running on, which was useful to inspect various kernel structures, but I don't think that's supported anymore, and anyway with VMs this is easy to do and much more flexible. Anyway, here are the main ways to attach to machines (that I'm aware of at the time of writing this):
  • serial cable connection. This is pretty universal, works on all Windows versions and it's been tested for a very long time. However, not many machine have serial ports anymore, and serial is really slow. But it's really the solution that's most likely to work for all cases.
  • USB connection. I've never actually used this, I think I tried once but gave up for some reason I can't remember (and I was left with the impression that it's quite finicky). This is also only available on Vista and newer.
  • 1394 (or firewire). I've used this a lot, it's really fast and pretty easy to set up. The downside is that it's expensive because it needs specialized hardware (like firewire cards and cables). This is available since XP and I highly recommend it if you need to work with real hardware and a VM won't work for you. 
  • network. I've not used this at all since it's pretty new (Win8+) but it should be pretty awesome since you don't have to be physically connected to the machine you're trying to debug (which limits the usefulness of the other methods, since if you work in a larger group you'll often have to debug things on machines that are not in your office so you'll have to figure out a way to connect to them). 
In general you really want a fast debugger connection because you might end up moving massive amounts of data across from the target to the host (for example, you might decide to capture a dump instead of keep using the machine, so if you'll have to capture all physical memory you'll want the 4, 8 or who knows, maybe even 128 GB to move quickly. I mean, over serial this can last weeks...
However, we're not talking about physical machines here so you should be able to just read the memory of the VM from the host, right ? Well, as it turns out, you can. There is a tool called VirtualKD, that actually does this and it has a couple of features that really help with driver development. There is a tutorial on how to install VirtualKD so please follow the steps there. Once you have everything working (the way I test that it's working is to boot up the VM and then breaking in the debugger. At that point the VM should freeze (please note that it might grab your mouse and you'll need to press some key combination (Ctrl+Alt for VMWare) to release it). Then I return to WinDBG and press F5 to resume running the VM) it's time to build and install a minifilter. This is what WinDBG should look like once it's connected properly and paused and resumed the VM:
One other thing we should set up is some method of copying files between the VM and host. I do this by setting up the Shared Folders feature to always share a certain folder between the host and the VM. Then all I need to do is copy the files I need to that folder on the host and I can access them from the VM (for this machine I used a folder D:\shared). After everything is set up, it's time to start the sequence of steps that will deploy the minifilter on the VM (which will become familiar to you pretty quickly):
  1. So now let's build a minifilter. Using the steps described in the previous post, let's try to build the passThrough minifilter. Try to build the checked version for the platform you've set up your VM to use (basically, for my Win7 x86 VM I'll start the "x86 Checked Build Environment" under the Windows 7 folder and build there). Once it's built I'd like to add a breakpoint in DriverEntry() (which is basically the main() function for drivers) to break in the debugger once the filter loads. So please just add a breakpoint (a call to DbgBreakPoint();) somewhere in DriverEntry() and rebuild the driver (bcz again)).
  2. Now we need to copy two files to the VM. Those two files are the actual driver (objchk_win7_x86\i386\passThrough.sys in my case) and the inf file (passThrough.inf). Put these two in the same directory and copy them to the VM through the shared folder.
  3. And finally we need to install the minifilter on the VM. So now we're switching to the VM. Open the shared folder, find the passThrough folder, right-click on passThrough.inf and select Install (which will require administrator privileges).
  4. Then we need to start an elevated CMD window from where we'll run "fltmc load passThrough".
  5. If everything is fine and the minifilter is loading then we'll trigger the breakpoint, which will freeze the VM. So we'll need to switch back to the debugger and unfreeze it (F5 again or use "g" command to allow execution to continue).
  6. After that we should see passthrough loaded and attached to all the volumes in your VM.
So this is it for today. In the next post I'll talk about some of my configurations and some optimizations I use to make this whole process faster, but that's not going to be until next year, so I wish you Happy Holidays! and a Happy New Year! in the meantime :).

Thursday, December 5, 2013

Getting Started with Windows File System Filters - Installing the Tools

Hello everyone,

It's been a while since I last posted anything. I took a break from file systems (and Windows, for that matter) but now I'm ready to get back. Since I'll be spending some time getting reacquainted with the whole thing I figured it's a good time to start a blog series for absolute beginners to this subject. However, you're expected to know C and understand synchronization (you should know what a mutexes and semaphores are).
So with that, the first step is to try to get the environment set up. We'll need the following tools:
  1. Windows Driver Kit - I'll be installing the 7.1.0 WDK, which should be good for Windows 7, Windows Vista, Windows XP, Windows Server 2008 R2, Windows Server 2008, and Windows Server 2003. The drivers we build should also run on Windows 8 and newer but the 7.1 WDK doesn't include the additional features (new APIs and such) that are available there. I'll cover setting up the newer WDK in a different post. For now, just get the WDK from MSDN.
  2. A Virtual Machine software - Personally I'm a big fan of VMware Workstation, but it's not free. I've used VirtualBox in the past and it's fine for what we need and it's also free so you can get it from here.
So let's get started. Install the WDK anywhere (please note that it's an ISO image so you can either mount it with any tool you like or you can just use a tool like 7zip (or most other decent archivers) to extract the files to a directory. Then just run KitSetup.exe and we're good to go.
This is what I have installed under D:\WinDDK (the red Xs are there because the kit can't find the kit I used so I can't add anything else - just ignore those):



Once the WDK installed you should be able to build a sample. So the steps are:
  1. Start a cmd prompt to build the sample: Start->Windows Driver Kits->WDK 7600.16385.1->Build Environments->Windows 7->x86 Checked Build Environment
  2. type cd src\filesys\miniFilter\nullFilter to get to the simplest minifilter sample
  3. type bcz to build it
  4. now you can go to the output directory (cd objchk_win7_x86\i386) and you should see the nullfilter.sys file, which is the null minifilter sample
At this point you should have the WDK installed properly. Please install VirtualBox on your own and create a new Win7 VM  (or, if you have VMware Workstation, you can just use that) and next week we'll configure the debugger and load the filter and look at it a bit.

Thursday, November 15, 2012

FltCreateSectionForDataScan

Thanks everyone for your suggestions. I really appreciate you took the time and sent me emails. Some of the topics you've suggested I won't be able to address for various reasons (mostly having to do with the legal implications of my doing that). However, I have a couple of topics that I might go over in the next couple of months. Incidentally, I also plan to change the frequency of my posts because of changes around my work so I'll probably update the blog whenever I get around to it. I still plan to answer questions though so feel free to leave comments and suggest topics (preferably in email). And now let's proceed to the topic at hand.

Someone suggested it would be useful to take a closer look at what FltCreateSectionForDataScan does and discuss how the same functionality could be achieved in previous OS releases. The FltCreateSectionForDataScan function is documented as being supported only in Win8+ but looking at the documentation page for the FltRegisterForDataScan function there is a mention about the FsRtlCreateSectionForDataScan function, which is documented as being available since Win 2000 SP4 and XPSP2, basically from the time FltMgr is available. A quick look in the debugger at FltCreateSectionForDataScan shows that it's really a wrapper around FsRtlCreateSectionForDataScan so I thought it might be useful to take a closer look at that:

  1. As one might expect the FsRtlCreateSectionForDataScan function begins with various parameter checks, primarily to filter out various invalid parameters. There is also a check to see if the FileObject supports sections.
  2. The next step is to set the TopLevelIrp to the value 1, which maps to FSRTL_FSP_TOP_LEVEL_IRP.
  3. Once that is done there is a call to nt!FsRtlAcquireFileExclusiveCommon. This function is not documented anywhere and I could only find some references in crash dumps. However, looking for just FsRtlAcquireFileExclusive returns more hits and we can see that FsRtlAcquireFileExclusive doesn't do much beyond calling nt!FsRtlAcquireFileExclusiveCommon. In the ntifs.h file we see a comment to the effect that FsRtlAcquireFileExclusive is called from NtCreateSection to avoid deadlocks with file systems, so we can guess that this the same thing going on here. We can also guess that whatever FsRtlCreateSectionForDataScan does is probably pretty similar to NtCreateSection.
  4. The next step is to get the file size. This is done by a call to nt!FsRtlGetFileSize, and the documentation for FsRtlGetFileSize states that "If you already own file system resources, you should call FsRtlGetFileSize instead of ZwQueryInformationFile, because attempting to acquire the file object lock would violate locking order and lead to deadlocks. The ZwQueryInformationFile function should be only if you do not already own file system resources.". This makes sense since the function has already acquired the file object lock in the previous step.
  5. There is a quick check to see if the file is empty and the function should return with STATUS_END_OF_FILE.
  6. Then, once everything is set up we have a call to nt!MmCreateSection. Calling this function directly is not supported by Microsoft and there is no documentation for how to do that. This is basically why FsRtlCreateSectionForDataScan was added. This can be inferred from the msdn page "Installing the Filter Manager rollup package for Windows XP SP2". If the function returns STATUS_FILE_LOCK_CONFLICT the call to nt!MmCreateSection is retried after delaying the thread by nt!FsRtlHalfSecond (I had no idea this existed, but I plan to use it from now on :)).
  7. Once the section is created there is a call to nt!CcZeroEndOfLastPage. I've not been able to find any documentation for this function (though it's pretty obvious what it does) except in the NT Insider article "Cache Me if You Can: Using the NT Cache Manager".
  8. Once this is done the function releases the locks so the rest of the function is done outside the critical region (that was set up around step 2).
  9. The only interesting thing happening in the rest of the function is the creation of the handle for the section in the current process handle table. The rest of it just deals with cleanup and the return path from the function.

One thing to note is that FsRtlCreateSectionForDataScan just creates the section. There are A LOT of FltMgr functions related to these data sections and based on the names most of them are dealing with conflict resolution. I'm not sure what that's about yet and I haven't actually used them anywhere but if you plan to use this API directly in downlevel OSes then you should probably expect some sort of conflicts. Good luck!

Thursday, October 4, 2012

Suggestions

Hi everyone. It seems lately I'm having a hard time coming up with good topics, so I wanted to ask you to send me emails with topics you might be interested in. These could be things you already know but you think might be beneficial to have posted somewhere to the rest of the community or things you're not sure about. However, please keep the requests specific. Things like "minifilter interaction with the memory manager" are simply too vague to be helful. You could ask about specific functions or behaviors or structure members (but again, please keep it specific). You can find my email address on my contact information on this blog.

Thanks,
Alex.

Thursday, September 27, 2012

A Helpful Debugging Technique: Debug Stream Contexts

In this post I'd like to discuss a debugging technique that I found pretty useful. I'm a big fan of keeping lots of information around in debug builds and ASSERTing all over the place. I try to be very careful about what information I keep so that I don't change the actual flow of execution for the code. For example, I tend to try to not add references when I copy some pointers because I don't want to alter when freeing the memory happens for the objects I'm tracking (but this approach requires extra care when asserting since I don't want to use pointers to free memory).
One particularly useful thing I like is to keep track of all the files I've processed in my filter and remember various decisions I made about them and keep back-pointers to things that would otherwise be impossible to find (for example when I base my decision on some parameters in the FLT_CALLBACK_DATA I might take a snapshot of those parameters; or I might keep a stack trace for certain operations and so on). The problem that arises with this approach is, of course, where to store this information. In some cases it's feasible to use some of the structures I'm already using (StreamContext or StreamHandleContext) and just add extra members in debug builds. However, there are other times where this becomes a significant pain and it would alter the filter quite a bit (for example, if the logic of the filter is to check for presence of a context to decide on what to do with a file then adding a context in which I only populate the debugging information for files I don't intend to process would change that logic). Also, certain contexts might store data that isn't fixed in size (like a file name or path) which I typically set to be the last thing in the context so adding more debugging information (which might itself be dynamic in size) would make things very complicated.
The ideal approach would be to set more than one context on an object, but FltMgr's functions don’t really allow that. Well, here is where the some FsRtl functions really come in handy. There is support built in the file system library (FsRtl code) for contexts (which I've discussed in more detail in my post on Contexts in legacy filters and FSRTL_ADVANCED_FCB_HEADER) and the library supports setting multiple contexts for the same stream, with different keys. Moreover, the contexts have two keys and there are pretty cool searching semantics that allow searching on only one key. The functions that deal with this are FsRtlInitPerStreamContext, FsRtlInsertPerStreamContext and FsRtlLookupPerStreamContext. They’re pretty easy to use and there is even a page on MSDN on Creating and Using Per-Stream Context Structures.
Here is some code that shows how to use these functions:
typedef struct _MY_DEBUG_CONTEXT {

    //
    // this is the header for the perStream context structure. 
    //

    FSRTL_PER_STREAM_CONTEXT PerStreamContext;

    // 
    // this is the information we want to track.
    //
    
    BOOLEAN Flag;

} MY_DEBUG_CONTEXT, *PMY_DEBUG_CONTEXT;



VOID
FreeDebugContext(
    __in PVOID ContextToFree
    )
{

    //
    // here we would release and uninitialize any other data.. 
    //

    //
    // and then we free the actual context.
    //
    
    ExFreePoolWithTag( ContextToFree, 'CgbD' );
}


NTSTATUS
SetDebugContext(
    __in PFILE_OBJECT FileObject,
    __in PVOID Key1,
    __in_opt PVOID Key2,
    __in BOOLEAN Flag
    )
{
    PMY_DEBUG_CONTEXT context = NULL;
    NTSTATUS status = STATUS_SUCCESS;

    __try {

        //
        // allocate and initialize the context.
        //

        context = ExAllocatePoolWithTag( PagedPool, 
                                         sizeof(MY_DEBUG_CONTEXT),
                                         'CgbD' );

        if (context == NULL) {

            status = STATUS_INSUFFICIENT_RESOURCES;
            __leave;    
        }

        FsRtlInitPerStreamContext( &context->PerStreamContext,
                                   Key1,
                                   Key2,
                                   FreeDebugContext );

        //
        // initialize all the members that we want to track.
        //

        context->Flag = Flag;

        //
        // try to set the context.
        //

        status = FsRtlInsertPerStreamContext( FsRtlGetPerStreamContextPointer(FileObject),
                                              &context->PerStreamContext );

        NT_ASSERT(status == STATUS_SUCCESS);

    } __finally {

        if (!NT_SUCCESS(status)) {

            if (context != NULL) {
            
                ExFreePoolWithTag( context, 'CgbD' );
            }
        }
    }

    return status;
}


BOOLEAN
GetDebugContextFlag(
    __in PFILE_OBJECT FileObject,
    __in PVOID Key1,
    __in_opt PVOID Key2
    )
{
    PMY_DEBUG_CONTEXT context = NULL;
    
    context = (PMY_DEBUG_CONTEXT)FsRtlLookupPerStreamContext( FsRtlGetPerStreamContextPointer(FileObject),
                                                              Key1,
                                                              Key2 );

    if ((context != NULL) &&
        (context->Flag)) {

        return TRUE;
    }

    return FALSE;
}
Finally, one more thing to show is how easy it is to find this structure in the debugger:
0: kd> dt @@(tempFileObject) nt!_FILE_OBJECT FsContext  <- get the FsContext
   +0x00c FsContext : 0xa24d05f8 Void
0: kd> dt 0xa24d05f8 nt!_FSRTL_ADVANCED_FCB_HEADER FilterContexts. <- look at the FilterContexts  in the FsContext
   +0x02c FilterContexts  :  [ 0xa25a1108 - 0x936e93e4 ]
      +0x000 Flink           : 0xa25a1108 _LIST_ENTRY [ 0x936e93e4 - 0xa24d0624 ]
      +0x004 Blink           : 0x936e93e4 _LIST_ENTRY [ 0xa24d0624 - 0xa25a1108 ]
0: kd> dl 0xa25a1108 <- look at all the entries in the list
a25a1108  936e93e4 a24d0624 9441eb40 92e3cb40
936e93e4  a24d0624 a25a1108 93437438 a24d05f8
a24d0624  a25a1108 936e93e4 00000000 a24d05f4
0: kd> !pool a25a1108 2  <- check the pool type for the first one
Pool page a25a1108 region is Paged pool
*a25a1100 size:   20 previous size:  100  (Allocated) *DbgC  <- this is our structure
  Owning component : Unknown (update pooltag.txt)
0: kd> !pool 936e93e4  2   <- for fun, let's see what the other context is 
Pool page 936e93e4 region is Nonpaged pool
*936e93d8 size:   68 previous size:    8  (Allocated) *FMsl   
  Pooltag FMsl : STREAM_LIST_CTRL structure, Binary : fltmgr.sys  <- obviously FltMgr's context
0: kd> dt a25a1108 _MY_DEBUG_CONTEXT  <- display the debugging context
PassThrough!_MY_DEBUG_CONTEXT
   +0x000 PerStreamContext : _FSRTL_PER_STREAM_CONTEXT
   +0x014 Flag             : 0x1 ''

Thursday, September 20, 2012

Volume Mount Points, Directory Junctions and STATUS_ACCESS_VIOLATION

Hi everyone. I've just spent a couple of days debugging an issue that I think is pretty interesting and I hope publishing this post might save someone a lot of pain.
Before I go into the specifics, let me explain a bit about my setup. My filter opens files for its own use but on behalf of the user (in other words it opens files in response to certain user actions, but the user must have access to the files as well (which is different from certain kinds of filters where the filter uses files but there is no connection between the user and the files; in those cases the files are similar to file system metadata)). It does this by calling FltCreateFile and it uses OBJ_FORCE_ACCESS_CHECK to make sure that all the access checks are performed. The files that the filter opens depend on the file that the user opens and so it's possible that the filter ends up opening symlinks or volume mount points and such. In these cases the file system will return a STATUS_REPARSE and the create will be retried on a different path; the same can happen if a file system filter returns STATUS_REPARSE. If the new path happens to be located on a different volume (which is usually the case with volume mount points, though not always since one can in fact create a mountpoint for C:\ at C:\mnt for example…) then the FltCreateFile call will generally fail with STATUS_MOUNT_POINT_NOT_RESOLVED. I've discussed this at great length in my post on Reparsing to a Different Volume in Win7 and Win8.
The problem I was running into was that when my filter tried to open a path containing a volume mount point it would fail with STATUS_ACCESS_DENIED. This behavior was different from symlinks where the call to FltCreateFile still failed with STATUS_MOUNT_POINT_NOT_RESOLVED. This was something I wanted to investigate since the STATUS_ACCESS_VIOLATION status is one I've learned to pay attention to because it generaly indicates a bug in my code. I thought that maybe I was calling FltCreateFile with some parameter that wasn't set up correctly which led to NTFS or the IO manager or the Object manager to try an invalid access, which was probably trapped in some exception handler and returned to the caller. This was definitely worth fixing since such problems can be exploited.
So I started the looong process of debugging this issue. Being an access violation I figured it comes from some improper memory access, which is different from calling some function that returns an error, because most instructions perform memory accesses so my usual approach of stepping to the next call and walking over it to see the return status would not work. Moreover, I had no guarantee that if I set a breakpoint later on I will actually hit it since any of the memory accesses up to that breakpoint could in fact be the culprit. And, to make things worse, this was in the create path for the file system, which is heavily exercised on a running system, which means that any breakpoint that I set in the debugger that's not threaded will often trigger when another thread happens to try to do a create. Anyway, to spare you the gory details, I eventually found the problem and this is what the stack looked like when the instruction I was about to execute was the one that triggered the access violation:
0: kd> kbn L0xa
 # ChildEBP RetAddr  Args to Child              
00 b0583190 82a8f20f 92637001 92637001 b0583734 nt!ObpCaptureObjectCreateInformation+0x61
01 b05831dc 82ae2587 b0583734 924d6f78 92637001 nt!ObOpenObjectByName+0x9b
02 b0583338 82ae17f6 b0583734 00120089 92637001 nt!IopFastQueryNetworkAttributes+0x127
03 b05833a4 82a88936 92637001 dc6a18a4 b0583550 nt!IopQueryNetworkAttributes+0x40
04 b0583480 82a6926b 938e34d8 974d6f78 92637008 nt!IopParseDevice+0x115e
05 b05834fc 82a8f2d9 00000000 b0583550 00000640 nt!ObpLookupObjectName+0x4fa
06 b0583558 82a8762b b0583734 924d6f78 00010000 nt!ObOpenObjectByName+0x165
07 b05835d4 82abee29 b058371c 00120089 b0583734 nt!IopCreateFile+0x673
08 b0583630 96297b62 b058371c 00120089 b0583734 nt!IoCreateFileEx+0x9e
09 b05836bc 9631f8f1 92ed7008 938fd008 b058371c fltmgr!FltCreateFileEx2+0xba
0: kd> u
nt!ObpCaptureObjectCreateInformation+0x61:
82a78339 8a01            mov     al,byte ptr [ecx]
82a7833b 833b18          cmp     dword ptr [ebx],18h
0: kd> r
eax=7fff0000 ebx=b0583734 ecx=7fff0000 edx=00000000 esi=00000000 edi=9260dd94
eip=82a78339 esp=b0583154 ebp=b0583190 iopl=0         ov up ei pl nz na po nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000a02
nt!ObpCaptureObjectCreateInformation+0x61:
82a78339 8a01            mov     al,byte ptr [ecx]          ds:0023:7fff0000=??
So as you can see we 're trying to read one byte from ECX, which is set to 0x7fff0000. Since we're in kernel mode this will fail with access violation. Let's look a bit up the stack to see where that weird ECX value came from (and I added a couple instructions on the bottom just to get the full picture):
0: kd> ub . L0xE
nt!ObpCaptureObjectCreateInformation+0x35:
82a7830c 807d0800        cmp     byte ptr [ebp+8],0    <- compare a parameter to the function with 0. 
82a78310 7429            je      nt!ObpCaptureObjectCreateInformation+0x63 (82a7833b) <- and jump if it's 0
82a78312 64a124010000    mov     eax,dword ptr fs:[00000124h] <- look at some address 
82a78318 80b83a01000000  cmp     byte ptr [eax+13Ah],0 <- and see if another byte is 0
82a7831f 741a            je      nt!ObpCaptureObjectCreateInformation+0x63 (82a7833b) <- and if that is 0 jump to the same place as above (so we're probably exiting an IF statement)
82a78321 8bcb            mov     ecx,ebx
82a78323 f6c303          test    bl,3 <- check if any of the least significant two bits in ebx are set 
82a78326 7406            je      nt!ObpCaptureObjectCreateInformation+0x56 (82a7832e)
82a78328 e8281f0d00      call    nt!ExRaiseDatatypeMisalignment (82b4a255) <- and if they're set then raise an exception for data misalignment
82a7832d cc              int     3 <- and look, there's a breakpoint
82a7832e a11c079b82      mov     eax,dword ptr [nt!MmUserProbeAddress (829b071c)] <- load MmUserProbeAddress into EAX
82a78333 3bd8            cmp     ebx,eax <- and compare it with EBX
82a78335 7202            jb      nt!ObpCaptureObjectCreateInformation+0x61 (82a78339) <- and exit the IF statement if EBX is smaller than MmUserProbeAddress
82a78337 8bc8            mov     ecx,eax <-move MmUserProbeAddress into ECX
82a78339 8a01            mov     al,byte ptr [ecx] <- and try to read a byte from it.
82a7833b 833b18          cmp     dword ptr [ebx],18h
0: kd> dp nt!MmUserProbeAddress L1
829b071c  7fff0000
Before I explain what's going on let's see what the IF is actually checking:
0: kd> dp fs:[00000124h] L1
0030:00000124  926bfd48
0: kd> !pool 926bfd48 2
Pool page 926bfd48 region is Nonpaged pool
*926bfd18 size:  2e8 previous size:   10  (Allocated) *Thre (Protected)   <- this is a thread!
  Pooltag Thre : Thread objects, Binary : nt!ps
0: kd> dt nt!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 CycleTime        : Uint8B
...
   +0x13a PreviousMode     : Char   <-and at offset 0x13A we have the PreviousMode !
...
Let's piece it all together. This piece of code is trying to figure out if a buffer that it receives as a parameter (I didn't show that code but that's what's in EBX) is accessible. It first looks at some boolean parameter and if that's TRUE then it looks at the PreviousMode and if that's UserMode then it checks the buffer for alignment (and it raises an exception if it's not aligned) and then it checks it's in user mode memory range ( smaller than MmUserProbeAddress ). If it's not smaller than MmUserProbeAddress then it just does a probe at MmUserProbeAddress. The buffer in question is the _OBJECT_ATTRIBUTES structure, which is actually the same one as the one I pass in for the original create. This is interesting because ObOpenObjectByName can be seen twice on the stack, with the same _OBJECT_ATTRIBUTES parameter, but the first time the call to nt!ObpCaptureObjectCreateInformation works when checking the buffer and the second time it doesn't. Since the PreviousMode is UserMode in both cases, the only difference is that the boolean parameter that ObpCaptureObjectCreateInformation is called with is 1 in the first case and 0 in the second case. This parameter seems to be related to OBJ_FORCE_ACCESS_CHECK since if I don't use OBJ_FORCE_ACCESS_CHECK then the parameter is 1 in both cases and no access violation happens.
One additional thing I'd like to explain is why this check happens only for volume mount points and not symlinks. Volume mount points are different from symlinks in that there before a new IRP_MJ_CREATE is sent to the new path, for volume mount points the IO manager checks whether the user has access to the target volume. The same thing happens for directory junctions, which are similar in implementation to volume mount points. Symlinks don't perform this check and instead the new IRP_MJ_CREATE is simply issued to the new path and access checks will be performed there. Now, for volume mount points this access check takes the form of a IopQueryNetworkAttributes, which in turn is implemented as a special kind of OPEN operation (hence the call to ObOpenObjectByName). It's this second open where ObpCaptureObjectCreateInformation fails and the access violation happens.
This problem was fixed in Win8 and it was very likely a windows bug.
Before I end this post I'd like to summarize some of the things I've discussed:
  • There is a windows 7 bug where if FltCreateFile tries to open a path that traverses a volume mount point or directory junction the call will fail with STATUS_ACCESS_VIOLATION. Fixed in Win8.
  • This happens because of the extra access checking for volume mount points and directory junctions.
  • This doesn't happen for symlinks.
  • Workaround: I can't think of anything a filter could do but one posibility to address this problem is to ask customers to not use volume mount points and instead use directory symlinks.
Finally, in closing I'd like to show some code (a modification of the PassThrough sample) that calls FltCreateFile whenever the user tries to open anything that has the name "mnt" (file or folder). After the FltCreateFile call the minifilter breaks so that I get a chance to look at the status returned by the FltCreateFile. Here is the code:

    NTSTATUS status;

    UNREFERENCED_PARAMETER( FltObjects );
    UNREFERENCED_PARAMETER( CompletionContext );

    PT_DBG_PRINT( PTDBG_TRACE_ROUTINES,
                  ("PassThrough!PtPreOperationPassThrough: Entered\n") );




    if (Data->Iopb->MajorFunction == IRP_MJ_CREATE) {

        UNICODE_STRING myFile = RTL_CONSTANT_STRING( L"mnt" );
        OBJECT_ATTRIBUTES fileAttributes;
        HANDLE fileHandle = NULL;
        IO_STATUS_BLOCK ioStatus;
        PFLT_FILE_NAME_INFORMATION fileName = NULL;  

        NTSTATUS ecpFindStatus;
        ULONG targetEcpSize = 0;

        __try {

            //  
            // this is a preCreate, get the name.  
            //  

            status = FltGetFileNameInformation( Data,   
                                                FLT_FILE_NAME_OPENED | FLT_FILE_NAME_QUERY_DEFAULT,   
                                                &fileName );  
            if (!NT_SUCCESS(status)) {

                DbgBreakPoint();
                __leave;
            }
    
            //
            // we need to parse the name to get the final component
            //

            status = FltParseFileNameInformation( fileName );

            if (!NT_SUCCESS(status)) {

                DbgBreakPoint();
                __leave;
            }

            //
            //  Compare to see if this is a file we care about.
            //

            if (!RtlEqualUnicodeString( &fileName->FinalComponent,
                                        &myFile,
                                        TRUE )) {

                __leave;
            }

            //
            // intialize the attributes and issue the create.
            //

            InitializeObjectAttributes( &fileAttributes, 
                                        &fileName->Name,
                                        OBJ_KERNEL_HANDLE | OBJ_FORCE_ACCESS_CHECK,
                                        NULL,
                                        NULL );

            status = FltCreateFileEx2( FltObjects->Filter,
                                       FltObjects->Instance,
                                       &fileHandle,
                                       NULL,
                                       FILE_READ_DATA | SYNCHRONIZE | FILE_READ_ATTRIBUTES,
                                       &fileAttributes,
                                       &ioStatus,
                                       NULL,
                                       FILE_ATTRIBUTE_NORMAL,
                                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                       FILE_OPEN,
                                       FILE_SYNCHRONOUS_IO_ALERT,
                                       NULL,
                                       0,
                                       0,
                                       NULL );


            DbgBreakPoint();
        } __finally {

            if (fileName != NULL) {

                FltReleaseFileNameInformation( fileName );
            }

            if (fileHandle != NULL) {

                ZwClose(fileHandle);
            }
        }
    }



    //
    //  See if this is an operation we would like the operation status
    //  for.  If so request it.
    //
    //  NOTE: most filters do NOT need to do this.  You only need to make
    //        this call if, for example, you need to know if the oplock was
    //        actually granted.
    //

    if (PtDoRequestOperationStatus( Data )) {

        status = FltRequestOperationStatusCallback( Data,
                                                    PtOperationStatusCallback,
                                                    (PVOID)(++OperationStatusCtx) );
        if (!NT_SUCCESS(status)) {

            PT_DBG_PRINT( PTDBG_TRACE_OPERATION_STATUS,
                          ("PassThrough!PtPreOperationPassThrough: FltRequestOperationStatusCallback Failed, status=%08x\n",
                           status) );
        }
    }

Thursday, September 13, 2012

Interesting article

I'm pretty swamped at work so I won't post anything this week, but I'd like to share an article that I found interesting. It's about some malware, Backdoor.Proxybox, that hooks NTFS directly. This is the page on Symantec's blog: Backdoor.Proxybox: Kernel File System Hooking.