Kayaker
December 6th, 2006, 21:48
Now what the heck are you up to Blabberer? That's OK, I think I know
I split this into a new thread since it's totally off topic from the original. That's a pretty cool idea, you want to morph a driver into a self-logging one?
It would probably be easier to do the detour dynamically, at least for drivers that load via ntoskrnl!IopLoadDriver. Much as I did in the Daemon Tools/SPTD thread, where you create a "wrapper" around the standard ntoskrnl DriverEntry loading routine.
http://woodmann.net/forum/showthread.php?t=9201
However this wouldn't apply to say boot loading drivers. That's an interesting idea then, to modify the file itself. One problem though would be if the driver does an internal checksum. As you say you have to update the PE.Checksum value any time you modify a driver file, but that's only to satisfy the PE loader. If the driver does its own checksum this probably wouldn't work (depends on how it's implemented). Btw, if you happen to have the SPTD driver in mind here (no, really!?), it does a full checksum hidden somewhere in its VM, so you're probably out of luck
The general idea sounds OK though, create a new section, change the Entry Point to your new section EP, do stuff, call the original DriverEntry with a CALL so it returns to your code, return control to the system. In essence create a wrapper like I did in that other thread. The only difference is that you'd need to hardcode the original DriverEntry EntryPoint address rather than getting it dynamically from the stack. Maybe something like:
Code:
__declspec(naked) DriverEntryDetour(){
///////////////////////////////////////////////
// Save the original DriverEntry parameters
// DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING RegistryPath)
_asm{
push dword ptr [esp+8]
pop pusRegistryPath_original
push dword ptr [esp+4]
pop pDriverObject_original
push dword ptr [esp]
pop ReturnAddress_DriverEntryDetour
}
///////////////////////////////////////////////
//... DO BAD STUFF...//
__asm
{
// Call orginal DriverEntry
push pusRegistryPath_original
push pDriverObject_original
call [DriverEntry_original] // hardcoded
mov DriverEntry_returnvalue, eax
}
///////////////////////////////////////////////
_asm{
mov eax, DriverEntry_returnvalue
push ReturnAddress_DriverEntryDetour
ret
}
}
As for the logging capabilities, if you have some driver code running in a new section I suppose you could run it as a debugger. You'd have to make sure your code isn't paged out as a regular INIT section would be. So what if you hooked Int3 and installed your own breakpoint handler. Then you changed the first byte of the original DriverEntry to 0xCC, saving the original byte of course. Then you call the original DriverEntry and let it run. Breakpoint hits, your Int3 ISR handles it, you check where it came from, you log 'whatever' if it's the BP you set, replace the byte, set a new 0xCC breakpoint on the very next instruction, IRETD without passing to the original Int3 handler, ad infinitum. I don't know about INT 3's purposely inserted to execute an SEH handler, as a debugger protection mechanism, you might have trouble with those or have to handle them differently.
Would it be slow? Sure, remember the BPRW Backtrace command? But that's not the point is it?
As for logging output to a text file, well this would be from within the Int3 handler so you'd have to be careful, but yeah there are file output strategies available. You could log to a circular buffer (which is what BPRW used) and occasionally dump it, that would probably be the most efficient. Or you could create a dedicated file output thread with PsCreateSystemThread and implement linked lists and spinlocks and such to output the file. This is what I did with a Softice kernel debugger extension and it worked well.
The big problem I see from all this (and I don't really know if any of it would work anyway), is the problem of writing the code. As you say you could probably fetch MmGetSystemRoutineAddress and work from there, doing it all in assembly.
The other idea I had though, instead of creating a new section and modifying the EP for the driver, you instead add a new *import module* entry to that section of the PE. This new module will be a kernel-mode dll which is automatically loaded when the driver is loaded (and unloaded in turn). As you're probably aware this is what I did as an example with my SysDasm driver, also mentioned in the above thread I linked to.
The SysDasm module loads as an export-only driver, but if you read the Intro.txt file I wrote that you can also write a kernel dll with functional entry/exit routines by adding PRIVATE exports declared as DllInitialize/DllUnload. This is all from
DLLs in Kernel Mode by Tim Roberts
http://www.wd-3.com/archive/KernelDlls.htm
Remember that such a kernel dll is loaded explicitly by the driver, just like ntoskrnl.exe / hal.dll. Now I'm not 100% sure, but *if* the DllInitialize routine is called and returns before the main DriverEntry routine, then you've got an opportunity to set up your Int3 handler/self debugger. (Does DLLMain for a usermode dll get called before the PE file first executes? should be the same principle)
Also, if the *image* of the original driver has been mapped before the dll modules are loaded, then you can also change the first byte of DriverEntry to 0xCC, priming your Int3 handler to begin its work.
If this worked you wouldn't have to worry about hardcoding anything or modifying the driver file other than inserting a new import entry, and you can write and compile a separate driver without the constraints of finding API offsets from within a code cave. I've got some thoughts about using your own Int0E and Int01 handlers instead (think ShadowWalker/OllyBone), but they're just fanciful concepts floating around at the moment
I'm sure there are plenty of holes in the logic so feel free to shoot them down.
Cheers,
Kayaker