Determining a KMDF driver's client version
This was probably the most fun I've had in a while. The initial versions of WdfVerifier simply collected the version information recorded by the KMDF coinstaller when it is used to install the framework for a driver. Unfortunately:
- not every driver needs to use the coinstaller, so the registry tracks may not even be there (for instance, any KMDF driver shipping in an OS with KMDF native to it won't have them)
- the information comes from the INF, and by now many of us know how trustworthy that information is.
- the information could be modified or even removed any number of ways, since it is just unsubstantiated registry tracks.
This was annoying (I wound up labeling all of these cases as "Inbox" in the UI), but with KMDF 1.9 it became important, as we are adding new keys that will only be used if you are actually a 1.9 driver (they won't affect a 1.7 or earlier driver when run with KMDF 1.9, in part so we can minimize regressions in behavior- a lot of attention has been paid to this sort of thing).
Since we have now revealed these at WinHEC, I'll give a short summary of those relevant to WdfVerifier:
- A new registry value for advanced verification options:
- Callback checking- where we will verify that you do not change IRQL or critical region status during your callback [yes, that has happened].
- The ability to inject faults on queues establishing guaranteed forward progress policies- i.e., we will mimic failure to allocate WDFREQUEST on incoming requests- either randomly at the Driver Verifier standard 6% rate, or else do it for ALL I/O destined for your queue
- If your driver does not have a setting for VerifierOn, we will automatically turn KMDF verifier on and off based upon whether you are using Driver Verifier on the driver.
The controls for these settings were not visible in the screen shots I posted here, because we hadn't yet said these features would be there. But in all cases, they apply only to 1.9 drivers, so as to not disrupt existing behaviors for earlier versions.
There sure is a catch here
Well, there are no easily discernible tracks pointing to your driver's version. The only certain way to tell is to load your driver- its entry code will tell the KMDF loader what its version is [which is why the !wdfldr debugger extension can tell you], but that is it. I later found out an attempt had been made to put a version string in the library code the driver must be linked with so some sort of scan could locate the version. But the linker removes unreferenced data, and in part because we didn't know it was supposed to be there, nobody had tests to verify that it was [not to imply the tests would exist if we had known- we have to prioritize test efforts, and testing something you aren't even using isn't really high on that list].
So, the real knowledge is buried in the kernel, and I want to extract it in user mode, no ways have been engineered for me to get it, and even if I get them in the new product, I personally prefer solutions that work with what is already there.
Mission Impossible- now THAT'S something I find interesting.
The Inescapable Lunacy of Bob
So fine, all I have to do is load a kernel mode binary in user mode, call its entry point, intercept the call to the loader that gives the correct version, harvest the version information, and return failing status so the rest of the initialization code won't run. Piece of cake!
Well, I actually understand the PE format pretty well- I have an internal test tool, which we mentioned briefly at DDC, where I do IAT hooking, dynamic linking [as in I have a basic GetProcAddress implementation for anything in the kernel- not just the kernel and HAL entry points MmGetSystemRoutineAddress will return, but any PE file loaded in the kernel process], and also share events and map memory between processes to implement the kinds of interfaces people get flamed for suggesting. Around the IAT hooks I built a fairly sophisticated logging, and fault injection or targeted callback mechanism- so I could do things like inject faults along one thread, while leaving others calling the same OS interface alone, etc.
So in this case, I was working in user mode, and that's a lot more forgiving than the kernel. As I said before- fun!
So, already WdfVerifier would scan binaries with no Parameters\Wdf- I would map them into memory, walk through the PE structures, and see if they imported the linking support functions from the KMDF loader. If yo understand the PE format, this means picking up the RVA (relative virtual address- how far from the start of the binary image something is) for its entry point was simple. It also meant I knew where the Import Address Table would live, and where any linkage point in that table would be.
Fine- just a little surgery on code I already have.
But I want more- the code will probably need relocation, after all, and some pieces load a bit differently in memory than they do as just a mapped data file. So let's take a quick look at the Win32 API. Ah, fine- LoadLibraryEx has several promising options. I especially like the one about loading without resolving external references...
Why? Because I am going to hook calls normally intended for other KM binaries and [since I didn't mention that before] you can't load them successfully in user mode! Why make the loader try to do something I am going to undo, and which won't succeed, anyway?
So I tried it, and it worked. So cool!
Well, now I need to think about how this has to work- The driver is going to expect a pointer to a device object, and a second to a Unicode string. So, what happens to these before the call I already know I will intercept? Well, here I cheated a bit, and actually looked at our code [but I could have done it from assembler quite easily, the path was rather short]. Hmm- nothing touched the driver object, but it did get passed by pointer into the routine I was hijacking. The Unicode string was copied with a system DDI, but not otherwise touched. So, let's intercept that string DDI and make it a no-op, and then make our "Driver object" the structure I want to return the version information in- it gets passed to the loader code, but after all, I'm spoofing like crazy.
So I tried this with some stack parameters, and it actually worked- sort of. First surprise (not a big one) was a code obfuscation technology injected some additional code into at least one driver, meaning I had an additional routine to supply and no-op.
Then I found out that not only did that option not resolve references, it didn't do any relocation! So, I had to do that myself, too...
To make a horribly long story short, I added them, it worked on all three processor platforms, secured it a bit by doing things like allocating the inputs on pages not on my stack (and well zeroed out, so they'd most likely access violate if someone ws really pulling a fast one).
Do all that the first time it is run, then set a flag in the registry that all of them have been inventoried. Allow it to be run again upon request, but most of the time, you just trust what is registered...
Now playing: Grateful Dead- untitled [usually called Skull and Roses] Not Fade Away / Going Down The Road (Feelin' Bad)- another classic jam...