I hope your daughter doesn't get nightmares from seeing the inside of Windows, many people do
You asked me for some ideas as to where to go from here. If you want to reverse drivers you'll need to understand driver structure. The best way is to create your own driver skeleton, disassemble it so as to recognize standard constructs in assembly, learn WinDbg methods of tracing it, etc.
I think one of the best summaries of driver structure is an old tutorial written by one of our members:
Kernel Mode Driver Tutorial: Part I: The Skeleton KMD by Clandestiny
http://www.reverse-engineering.info/SystemCoding/SkeletonKMD_Tutorial.htm
or
http://www.woodmann.com/forum/attachment.php?attachmentid=747&d=1062490066
Other excellent resources:
Device Driver Development for Beginners - Reloaded
http://www.kernelmode.info/forum/viewtopic.php?f=14&t=374
Making your first driver - complete walkthrough
http://articles.sysprogs.org/visualddk/firstdriver/
System Coding
http://www.reverse-engineering.info/documents/31.html
Kernel Mode Driver Development Kit for MASM32 programmers
http://www.freewebs.com/four-f/
Here's an example of what you could do. For arguments sake lets say you wanted to trace the IOCTL codes (the usermode/kernelmode communication) for beep.sys. Start up your WinDbg/VM and display the DRIVER_OBJECT details for beep.sys with extended flag information.
Code:
kd> !drvobj beep 7
Driver object (8c6299e0) is for:
\Driver\Beep
DriverEntry: 8e56e03e
DriverStartIo: 8e56b248
DriverUnload: 8e56b2c6
Dispatch routines:
[00] IRP_MJ_CREATE 8e56b1ac
..
[02] IRP_MJ_CLOSE 8e56b1fe
..
[0e] IRP_MJ_DEVICE_CONTROL 8e56b116
For IOCTL codes we are interested in the IRP_MJ_DEVICE_CONTROL Dispatch routine function address. So let's set a breakpoint on that address and trigger a beep and see what happens.
Type Ctrl-G in a command window in the VM and press Enter and it should beep. Don't ask me why, I just found that on the net. You could also use the funky NirCmd utility to create a beep and it should also work.
http://www.nirsoft.net/utils/nircmd.html
Windbg should break and you'll see this:
Code:
...
8e56b11b 8b4d0c mov ecx,dword ptr [ebp+0Ch]
8e56b11e 8b4160 mov eax,dword ptr [ecx+60h]
8e56b121 8b500c mov edx,dword ptr [eax+0Ch]
8e56b124 81ea00000100 sub edx,10000h
8e56b12a 56 push esi
8e56b12b 743c je 8e56b169
Those first 3 lines are important and you'll see them in various guises in the majority of drivers, but we need to understand them.
Disassemble beep.sys in IDA and find the function, which tells us that the first structure of interest is an _IRP
Code:
:00011116 ; int __stdcall BeepDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
:00011116 _BeepDeviceControl@8 proc near ; DATA XREF: DriverEntry(x,x)+BE
:00011116
:00011116 DeviceObject = dword ptr 8
:00011116 Irp = dword ptr 0Ch
:00011116
...
:0001111B mov ecx, [ebp+Irp] ; Irp
:0001111E mov eax, [ecx+60h]
:00011121 mov edx, [eax+0Ch]
:00011124 sub edx, 10000h
:0001112A push esi
:0001112B jz short loc_11169
Look at the definition of _IRP in the WinDDK wdm.h file, or Clandestiny's tutorial and try to figure out what is at the offset [ecx+60h] (PIRP+60h).
You could also use WinDbg. Trace past the line 8e56b11b and type
Code:
kd> dt nt!_IRP @ecx -r
+0x000 Type : 0n6
+0x002 Size : 0x94
...
+0x040 Tail : <unnamed-tag>
+0x000 Overlay : <unnamed-tag>
...
+0x020 CurrentStackLocation : 0x8ba04898 _IO_STACK_LOCATION
[ecx+60h] is therefore _IO_STACK_LOCATION.
In code this would be equivalent to
Code:
PIO_STACK_LOCATION IrpStack;
// Get pointer to IRP stack location
IrpStack = IoGetCurrentIrpStackLocation( Irp );
The next line we must parse is [eax+0Ch], or PIO_STACK_LOCATION + 0Ch. In this case the definition of the offset 0Ch is a union, the exact meaning being dependant on the service being invoked. In our case it's DeviceIoControl and we can work out from the structure definitions that offset 0Ch is IoControlCode.
Code:
ULONG ioControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode;
I know that's a lot of theory for 3 little lines, but now we've been able to interpret the code so we know that EDX contains the IOCTL code being passed from usermode through the DeviceIoControl API. From here we can just trace logically to wherever the code takes us to each of the individual functions.
btw, a handy trick for static disassembly is to add the IRP and IO_STACK_LOCATION structure definitions in IDA and use them to name the structure offsets, it would look like this.
Code:
:0001111B mov ecx, [ebp+Irp] ; Irp
:0001111E mov eax, dword ptr [ecx+IRP.Tail.Overlay.anonymous_1.anonymous_0]
:00011121 mov edx, [eax+IO_STACK_LOCATION.Parameters.DeviceIoControl.IoControlCode]
Just so you don't think it's always this straightforward, say we do the same thing with Process Explorer:
Code:
kd> !drvobj procexp100 7
Driver object (8b8e6488) is for:
\Driver\PROCEXP100
Dispatch routines:
[00] IRP_MJ_CREATE 9bbfcd46
..
[02] IRP_MJ_CLOSE 9bbfcd46
[..
[0e] IRP_MJ_DEVICE_CONTROL 9bbfcd46
Notice that all 3 IRP_MJ functions have the same address. This is a common way of writing a driver, unlike the beep.sys example. Breakpoint on that address and you'll see familiar code, plus a switch statement line pointing the way to IRP_MJ_DEVICE_CONTROL:
cmp dl,0Eh // [0e] IRP_MJ_DEVICE_CONTROL
If you wish, take a look at the driver source code in my latest blog post to see how that is constructed:
Code:
// Dispatch on MajorFunction
switch (IrpStack->MajorFunction)
{
// Default these two IRPs
case IRP_MJ_CREATE:
case IRP_MJ_CLOSE:
break;
case IRP_MJ_DEVICE_CONTROL:
// Handle the GUI IOCTL requests
Well I've beat that to death like a rented mule... Enough for now.
Kayaker