Windows keyboard and mouse input process

Article directory

  • Windows keyboard and mouse input process
    • 1. RawInputThread
    • 2. xxxProcessKeyEvent
    • 3.gpqForeground
    • 4. Message hook
    • 5. From keystrokes to Win32k
    • 6. Summary

Windows keyboard and mouse input process

Normally, when we use Windows, we interact with the system through the mouse and keyboard. So how do the keyboard and mouse input and convert various messages to the application? This article takes a look at one of the most basic issues, how Windows responds to keyboard and mouse input.

1. RawInputThread

Mouse and keyboard input are inseparable from an important process, which is CSRSS. When this process is started, an input thread RawInputThread will be created. This thread will continue to receive input from the keyboard and mouse. information.

In the subsystem process, the creation process of this thread function is as follows:

06 88252c04 94efd7c7 00000004 00000002 882c7300 win32k!RawInputThread + 0x486
07 88252c18 94fb2b5d 00000004 01cafbe8 88252c34 win32k!xxxCreateSystemThreads + 0x4a
08 88252c28 83e431ea 00000004 01cafc28 778d70b4 win32k!NtUserCallNoParam + 0x1b
09 88252c28 778d70b4 00000004 01cafc28 778d70b4 nt!KiFastCallEntry + 0x12a
0a 01cafbd8 75a019ec 75a0288b 00000004 00000000 ntdll!KiFastSystemCallRet
0b 01cafbdc 75a0288b 00000004 00000000 77895e7a winsrv!NtUserCallNoParam + 0xc
0c 01cafbe8 77895e7a 00000000 765c35e7 00000000 winsrv!StartCreateSystemThreads + 0x12
0d 01cafc28 778f37c8 75a02879 00000000 00000000 ntdll!__RtlUserThreadStart + 0x28
0e 01cafc40 00000000 75a02879 00000000 00000000 ntdll!_RtlUserThreadStart + 0x1b

In this function, a read request will be delivered for the mouse and keyboard. The process is as follows:

PDEVICEINFO StartDeviceRead(
    PDEVICEINFO pDeviceInfo)
{<!-- -->

    //...

    pDeviceInfo->ReadStatus = ZwReadFile(
            pDeviceInfo->handle,
            NULL, // hReadEvent
            InputApc, // InputApc()
            pDeviceInfo, //ApcContext
             & amp;pDeviceInfo->iosb,
            (PVOID)((PBYTE)pDeviceInfo + pDevTpl->offData),
            pDevTpl->cbData,
            PZERO(LARGE_INTEGER), NULL);
            
    //...

    return NULL;
}

From here we can find that this is an asynchronous read operation. If the mouse or keyboard information is read, the APC routine InputApc will be called, and then the read request will continue to be delivered in the APC routine ( CallStartDeviceRead).

2. xxxProcessKeyEvent

When RawInputThread reads keystroke information from the keyboard, it will call xxxProcessKeyEvent to handle the keyboard response, as follows:

3: kd> kb
 # ChildEBP RetAddr Args to Child
00 882528d4 95036c0a 00000020 00000039 00030195 win32k!xxxKeyEvent
01 8825290c 94fd7313 88252920 00000000 00000000 win32k!xxxProcessKeyEvent + 0x229
02 8825294c 94fd7737 ffa18900 20a18930 00000001 win32k!ProcessKeyboardInputWorker + 0x2dc
03 8825296c 94f00f58 ffa18930 a17e8ed0 882529c4 win32k!ProcessKeyboardInput + 0x68
04 8825297c 83eb67b4 ffa18930 ffa18958 00000000 win32k!InputApc + 0x4e
05 882529c4 83e83685 00000000 00000000 00000000 nt!KiDeliverApc + 0x17f
06 88252a08 83e824f7 8d35bb90 8940fc80 89410074 nt!KiSwapThread + 0x24e
07 88252a30 83e7e4a4 8940fc80 89410008 00000000 nt!KiCommitThreadWait + 0x1df
08 88252ba8 94eed276 00000004 8940c0d0 00000001 nt!KeWaitForMultipleObjects + 0x535
09 88252c04 94efd7c7 00000004 00000002 882c7300 win32k!RawInputThread + 0x486
0a 88252c18 94fb2b5d 00000004 01cafbe8 88252c34 win32k!xxxCreateSystemThreads + 0x4a
0b 88252c28 83e431ea 00000004 01cafc28 778d70b4 win32k!NtUserCallNoParam + 0x1b
0c 88252c28 778d70b4 00000004 01cafc28 778d70b4 nt!KiFastCallEntry + 0x12a
0d 01cafbd8 75a019ec 75a0288b 00000004 00000000 ntdll!KiFastSystemCallRet
0e 01cafbdc 75a0288b 00000004 00000000 77895e7a winsrv!NtUserCallNoParam + 0xc
0f 01cafbe8 77895e7a 00000000 765c35e7 00000000 winsrv!StartCreateSystemThreads + 0x12
10 01cafc28 778f37c8 75a02879 00000000 00000000 ntdll!__RtlUserThreadStart + 0x28
11 01cafc40 00000000 75a02879 00000000 00000000 ntdll!_RtlUserThreadStart + 0x1b

From here, our response to the keyboard is handled by xxxKeyEvent. The basic flow of this function is as follows:

  1. if ((pHook = PhkFirstValid(ptiCurrent, WH_KEYBOARD_LL)) != NULL) is true, then call xxxCallHook2(pHook, HC_ACTION, (DWORD)msg, (LPARAM) & amp;kbds, & amp;bAnsiHook)Process the WH_KEYBOARD_LL hook first, where if the key is not a hotkey (such as CTRL + ALT + DELETE) and is filtered out by WH_KEYBOARD_LL, then return directly .
  2. Call xxxDoHotKeyStuff to process the hot key. If it is a basic hot key, it will return directly after the processing is completed.
  3. If it is an ordinary keyboard message that needs to be responded to, call PostInputMessage to send the keyboard message. PostInputMessage will then call SetWakeBit to notify the relevant thread to process the message.
  4. Call PostInputMessage(gpqForeground, xxxx) to send a message to the top-level window, and then match QEVENT_APPCOMMAND to see if it is a windows enhanced message, such as pressing a keyboard key to directly open the browser and surf the Internet.
  5. In PostInputMessage, first call StoreQMessage to store it in the message queue, and then call WakeSomeone to wake up the target process for processing.
  6. In WakeSomeOne, match the message type, which is wm_keydown, wm_char, wm_mousemove, etc., and then call StoreQMessagePti Exists in the target thread, that is, the top-level window queue. When it is the target thread’s turn to execute, the wm_keydown and wm_char messages are processed.

The overall process is as follows:

3: kd> kb
 # ChildEBP RetAddr Args to Child
00 88252820 94f71eed ffb54370 00000001 0000011b win32k!SetWakeBit
01 88252840 94f6afad fe8288f0 00000100 fd4056f0 win32k!WakeSomeone + 0x189
02 88252860 9503481e fe8288f0 00000000 00000100 win32k!PostInputMessage + 0x126
03 882528d4 95036c0a 00000020 00000039 00030195 win32k!xxxKeyEvent + 0x8a6
04 8825290c 94fd7313 88252920 00000000 00000000 win32k!xxxProcessKeyEvent + 0x229
05 8825294c 94fd7737 ffa18900 20a18930 00000001 win32k!ProcessKeyboardInputWorker + 0x2dc
06 8825296c 94f00f58 ffa18930 a17e8ed0 882529c4 win32k!ProcessKeyboardInput + 0x68
07 8825297c 83eb67b4 ffa18930 ffa18958 00000000 win32k!InputApc + 0x4e
08 882529c4 83e83685 00000000 00000000 00000000 nt!KiDeliverApc + 0x17f
09 88252a08 83e824f7 8d35bb90 8940fc80 89410074 nt!KiSwapThread + 0x24e
0a 88252a30 83e7e4a4 8940fc80 89410008 00000000 nt!KiCommitThreadWait + 0x1df
0b 88252ba8 94eed276 00000004 8940c0d0 00000001 nt!KeWaitForMultipleObjects + 0x535
0c 88252c04 94efd7c7 00000004 00000002 882c7300 win32k!RawInputThread + 0x486
0d 88252c18 94fb2b5d 00000004 01cafbe8 88252c34 win32k!xxxCreateSystemThreads + 0x4a
0e 88252c28 83e431ea 00000004 01cafc28 778d70b4 win32k!NtUserCallNoParam + 0x1b
0f 88252c28 778d70b4 00000004 01cafc28 778d70b4 nt!KiFastCallEntry + 0x12a
10 01cafbd8 75a019ec 75a0288b 00000004 00000000 ntdll!KiFastSystemCallRet
11 01cafbdc 75a0288b 00000004 00000000 77895e7a winsrv!NtUserCallNoParam + 0xc
12 01cafbe8 77895e7a 00000000 765c35e7 00000000 winsrv!StartCreateSystemThreads + 0x12
13 01cafc28 778f37c8 75a02879 00000000 00000000 ntdll!__RtlUserThreadStart + 0x28
14 01cafc40 00000000 75a02879 00000000 00000000 ntdll!_RtlUserThreadStart + 0x1b

SetWakeBit is to notify the message loop to start the message response, as follows:

VOID SetWakeBit(
    PTHREADINFO pti,
    UINT wWakeBit)
{<!-- -->
    //...
    KeSetEvent(pti->pEventQueueServer, 2, FALSE);
    //...
}

There is a more important thing here. Many developers with insufficient development experience hope to intercept the CTRL + ALT + DEL keys through WH_KEYBOARD_LL. In fact, this cannot take effect because in xxxKeyEvent The process is as follows:

VOID xxxKeyEvent(
    USHORT usFlaggedVk,
    WORD wScanCode,
    DWORD time,
    ULONG_PTR ExtraInfo,
#ifdef GENERIC_INPUT
    HANDLE hDevice,
    PKEYBOARD_INPUT_DATA pkei,
#endif
    BOOL bInjected)
{<!-- -->
    //...
    if ((pHook = PhkFirstValid(ptiCurrent, WH_KEYBOARD_LL)) != NULL) {<!-- -->
        //...
        if (xxxCallHook2(pHook, HC_ACTION, (DWORD)msg, (LPARAM) & amp;kbds, & amp;bAnsiHook)) {<!-- -->
            //...
            if (IsSAS(VkHanded, & amp;fsModifiers)) {<!-- -->
                RIPMSG0(RIP_WARNING, "xxxKeyEvent: SAS ignore bad response from low level hook");
            } else {<!-- -->
                return;
            }
        }
        //...
    }
    //...
}

Here SAS is the abbreviation of Secure Attention Sequence, which means the security key. Under Windows, this key is not set to Ctrl + Alt + Del. This is why Ctrl + Alt + Del cannot be intercepted by message hooks.

PostInputMessage represents a function for message delivery. This function is implemented as follows:

BOOL PostInputMessage(
    PQ pq,
    PWND pwnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam,
    DWORD time,
    ULONG_PTR dwExtraInfo)
{<!-- -->
    //...
    pqmsgInput = AllocQEntry( & amp;pq->mlInput);
    if (pqmsgInput == NULL) {<!-- -->
        return FALSE;
    }
    StoreQMessage(pqmsgInput, pwnd, message, wParam, lParam, time, 0, dwExtraInfo);
    WakeSomeone(pq, message, pqmsgInput);

    return TRUE;
}

From here we can know that we first allocate a data structure from the pq->mlInput mouse and keyboard queue, then use StoreQMessage to fill the structure content, and finally use WakeSomeone Notify the target for processing.

3. gpqForeground

We know that SendMessage is sent to the message queue in the thread, so where do the mouse and keyboard messages go? This structure is gpqForeground, and its type is as follows:

typedef struct tagQ {<!-- -->
    MLIST mlInput; // raw mouse and key message list.

    PTHREADINFO ptiSysLock; // Thread currently allowed to process input
    ULONG_PTR idSysLock; // Last message removed
    ULONG_PTR idSysPeek; // Last message peeked

    PTHREADINFO ptiMouse; // Last thread to get mouse msg.
    PTHREADINFO ptiKeyboard;

    PWND spwndCapture;
    PWND spwndFocus;
    PWND spwndActive;
    PWND spwndActivePrev;

    UINT codeCapture;
    UINT msgDblClk;
    WORD xbtnDblClk;
    DWORD timeDblClk;
    HWND hwndDblClk;
    POINT ptDblClk;

    BYTE afKeyRecentDown[CBKEYSTATERECENTDOWN];
    BYTE afKeyState[CBKEYSTATE];

    CARET caret;

    PCURSOR spcurCurrent;
    int iCursorLevel;

    DWORD QF_flags; // QF_ flags go here

    USHORT cThreads; // Count of threads using this queue
    USHORT cLockCount; // Count of threads that don't want this queue freed

    UINT msgJournal;
    LONG_PTR ExtraInfo;
} Q;

Judging from the name, gpqForeground is the information of the foreground window. So when was this thing set? When we call SetForegroundWindow to set the foreground window, there are the following settings:

BOOL xxxSetForegroundWindow2(
    PWND pwnd,
    PTHREADINFO pti,
    DWORD fFlags)
{<!-- -->
    //...
    if (pwnd != NULL) {<!-- -->
        //...
        gpqForeground = GETPTI(pwnd)->pq;
        //...
    }
    //...
}

We can take a look at this structural information:

3: kd> dd win32k!gpqForeground L1
950ffc00 fe959570
3: kd> dt win32k!tagQ fe959570
    + 0x000 mlInput: tagMLIST
    + 0x00c ptiSysLock: (null)
    + 0x010 idSysLock : 0
    + 0x014 idSysPeek : 0
    + 0x018 ptiMouse : 0xfd1734f8 tagTHREADINFO
    + 0x01c ptiKeyboard : 0xfd1734f8 tagTHREADINFO
    + 0x020 spwndCapture: (null)
    + 0x024 spwndFocus : 0xfea1b250 tagWND
    + 0x028 spwndActive : 0xfea1a700 tagWND
    + 0x02c spwndActivePrev: (null)
    + 0x030 codeCapture: 0
    + 0x034 msgDblClk: 0
    + 0x038 xbtnDblClk : 0
    + 0x03c timeDblClk: 0
    + 0x040 hwndDblClk: (null)
    + 0x044 ptDblClk : tagPOINT
    + 0x04c ptMouseMove: tagPOINT
    + 0x054 afKeyRecentDown : [32] ""
    + 0x074 afKeyState : [64] ""
    + 0x0b4 caret: tagCARET
    + 0x0ec spcurCurrent : 0xffb55c88 tagCURSOR
    + 0x0f0 iCursorLevel : 0n0
    + 0x0f4 QF_flags : 0x40
    + 0x0f8 cThreads : 1
    + 0x0fa cLockCount : 0
    + 0x0fc msgJournal : 0
    + 0x100 ExtraInfo : 0n0
    + 0x104ulEtwReserved1 : 0x2000

So from PostInputMessage(gpqForeground, NULL, message, wParam, lParam, time, ExtraInfo); we can know that mouse and keyboard messages are sent to the foreground window thread.

4. Message hook

In Windows, you can use SetWindowsHookExW to set mouse and keyboard hooks

HHOOK SetWindowsHookExW(
  int idHook,
  HOOKPROC lpfn,
  HINSTANCE hmod,
  DWORD dwThreadId
);

For the keyboard, there are two messages WH_KEYBOARD and WH_KEYBOARD_LL. From the above analysis, we can know that for the underlying message hook, all key information can be captured. However, for CTRL + ALT + DEL key information, the underlying message hook does not intercept it. For ordinary message hooks, it is obvious that the information about hotkey presses cannot be captured. As for hotkeys, we can still intercept them using WH_KEYBOARD_LL. To register hotkeys, use the following function:

BOOL RegisterHotKey(
  HWND hWnd,
  int id,
  UINT fsModifiers,
  UINT v
);

Regarding the specific implementation principle of message hook, we can refer to (Windows Message Hook Implementation Principle).

5. From keystrokes to Win32k

When we press the keyboard, the general process is as follows:

  1. The keyboard generates a hardware interrupt signal and sends it to the bus. The bus sends the signal to the ioapic register for reception. The ioapic register stores the index number of the interrupt idt of the keyboard interrupt, and which CPU it is sent to for processing (for multi-core), and then sends it to the local of the CPU. apic register.
  2. The local apic receives the signal, which stores the mapping from the interrupt idt index number to the idt keyboard interrupt processing address. Then it is sent to the keyboard interrupt processing routine, which is passed to the keyboard port driver and port class driver in turn, and the break code of the key is placed in the device extension of the port class driver. The bottom-up process ends.

For i8042, the interrupt vector is i8042prt!I8042KeyboardInterruptService. At this time we can see the entire calling relationship as follows:

0: kd> kb
 # ChildEBP RetAddr Args to Child
00 83f2d940 83e3f7ad 8d4a3a00 8d3edb10 83f2d96c i8042prt!I8042KeyboardInterruptService
01 83f2d940 84219ca6 8d4a3a00 8d3edb10 83f2d96c nt!KiInterruptDispatch + 0x6d
02 83f2d9dc 8421ca4d 8d4a3780 8d400b90 83f2dabc hal!HalpGenerateInterrupt + 0x1d2
03 83f2d9fc 8421cc7f 83f2da14 83e3f808 5eb8ae02 hal!HalpLowerIrqlHardwareInterrupts + 0xf5
04 83f2da04 83e3f808 5eb8ae02 00000061 83f2dabc hal!HalEndSystemInterrupt + 0x23
05 83f2da04 90d6c46a 5eb8ae02 00000061 83f2dabc nt!KiInterruptDispatch + 0xc8
06 83f2dabc 90d6c74a 634b0160 83f2daec 83f2dafb E1G60I32!RxPacketAssemble + 0x126
07 83f2dafc 90d6b77e 00000001 8d50a460 00000000 E1G60I32!RxProcessReceiveInterrupts + 0x5e
08 83f2db14 86ce389a 014b0160 00000000 83f2db40 E1G60I32!E1000HandleInterrupt + 0x80
09 83f2db50 86c8ea0f 8d50a474 0050a460 00000000 ndis!ndisMiniportDpc + 0xe2
0a 83f2db78 83e7d1b5 8d50a474 8d50a460 00000000 ndis!ndisInterruptDpc + 0xaf
0b 83f2dbd4 83e7d018 83f30d20 83f3a380 00000000 nt!KiExecuteAllDpcs + 0xf9
0c 83f2dc20 83e7ce38 00000000 0000000e 00000000 nt!KiRetireDpcList + 0xd5
0d 83f2dc24 00000000 0000000e 00000000 00000000 nt!KiIdleLoop + 0x38

We know that interrupt response routines generally do nothing and leave it to DPC to complete. We found this DPC to be I8042KeyboardIsrDpc, and the call is as follows:

0: kd> kb
 # ChildEBP RetAddr Args to Child
00 83f2db78 83e7d1b5 8d3edd74 8d3edb10 00000000 i8042prt!I8042KeyboardIsrDpc
01 83f2dbd4 83e7d018 83f30d20 83f3a380 00000000 nt!KiExecuteAllDpcs + 0xf9
02 83f2dc20 83e7ce38 00000000 0000000e 00000000 nt!KiRetireDpcList + 0xd5
03 83f2dc24 00000000 0000000e 00000000 00000000 nt!KiIdleLoop + 0x38

Then read the port information and pass the information to Win32k. There are examples of i8042 keyboard and mouse drivers in the DDK. If you are interested, you can look at the DDK examples for more clarity.

6. Summary

From the above analysis we can know that there are two solutions for the mouse and keyboard.

First use SetWindowsHookExW to set the mouse and keyboard hook (or low-level hook), so that the message can be captured before the mouse and keyboard respond, but if you need to process the CTRL + ALT + DELETE keys, then these two The operation does not meet the requirements.

In view of the limitations of SetWindowsHookExW, someone on the Internet has implemented a relatively unorthodox keyboard hotkey interception solution, which is the HOOK function xxxKeyEvent. From the above analysis, we can find that this The function is processed before all keyboard events, so it can definitely intercept all messages. However, I cannot guarantee the stability and compatibility of this solution, so it should be used with caution in formal projects.