You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2619 lines
85 KiB
2619 lines
85 KiB
/****************************** Module Header ******************************\
|
|
* Module Name: hooks.c
|
|
*
|
|
* Copyright (c) 1985 - 1999, Microsoft Corporation
|
|
*
|
|
* This module contains the user hook APIs and support routines.
|
|
*
|
|
* History:
|
|
* 01-28-91 DavidPe Created.
|
|
* 08 Feb 1992 IanJa Unicode/ANSI aware & neutral
|
|
\***************************************************************************/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
/*
|
|
* This table is used to determine whether a particular hook
|
|
* can be set for the system or a task, and other hook-ID specific things.
|
|
*/
|
|
#define HKF_SYSTEM 0x01
|
|
#define HKF_TASK 0x02
|
|
#define HKF_JOURNAL 0x04 // JOURNAL the mouse on set
|
|
#define HKF_NZRET 0x08 // Always return NZ hook for <=3.0 compatibility
|
|
#define HKF_INTERSENDABLE 0x10 // OK to call hookproc in context of hooking thread
|
|
#define HKF_LOWLEVEL 0x20 // Low level hook
|
|
|
|
CONST int ampiHookError[CWINHOOKS] = {
|
|
0, // WH_MSGFILTER (-1)
|
|
0, // WH_JOURNALRECORD 0
|
|
-1, // WH_JOURNALPLAYBACK 1
|
|
0, // WH_KEYBOARD 2
|
|
0, // WH_GETMESSAGE 3
|
|
0, // WH_CALLWNDPROC 4
|
|
0, // WH_CBT 5
|
|
0, // WH_SYSMSGFILTER 6
|
|
0, // WH_MOUSE 7
|
|
0, // WH_HARDWARE 8
|
|
0, // WH_DEBUG 9
|
|
0, // WH_SHELL 10
|
|
0, // WH_FOREGROUNDIDLE 11
|
|
0, // WH_CALLWNDPROCRET 12
|
|
0, // WH_KEYBOARD_LL 13
|
|
0 // WH_MOUSE_LL 14
|
|
#ifdef REDIRECTION
|
|
,0 // WH_HITTEST
|
|
#endif // REDIRECTION
|
|
};
|
|
|
|
CONST BYTE abHookFlags[CWINHOOKS] = {
|
|
HKF_SYSTEM | HKF_TASK | HKF_NZRET , // WH_MSGFILTER (-1)
|
|
HKF_SYSTEM | HKF_JOURNAL | HKF_INTERSENDABLE , // WH_JOURNALRECORD 0
|
|
HKF_SYSTEM | HKF_JOURNAL | HKF_INTERSENDABLE , // WH_JOURNALPLAYBACK 1
|
|
HKF_SYSTEM | HKF_TASK | HKF_NZRET | HKF_INTERSENDABLE , // WH_KEYBOARD 2
|
|
HKF_SYSTEM | HKF_TASK , // WH_GETMESSAGE 3
|
|
HKF_SYSTEM | HKF_TASK , // WH_CALLWNDPROC 4
|
|
HKF_SYSTEM | HKF_TASK , // WH_CBT 5
|
|
HKF_SYSTEM , // WH_SYSMSGFILTER 6
|
|
HKF_SYSTEM | HKF_TASK | HKF_INTERSENDABLE , // WH_MOUSE 7
|
|
HKF_SYSTEM | HKF_TASK , // WH_HARDWARE 8
|
|
HKF_SYSTEM | HKF_TASK , // WH_DEBUG 9
|
|
HKF_SYSTEM | HKF_TASK , // WH_SHELL 10
|
|
HKF_SYSTEM | HKF_TASK , // WH_FOREGROUNDIDLE 11
|
|
HKF_SYSTEM | HKF_TASK , // WH_CALLWNDPROCRET 12
|
|
HKF_SYSTEM | HKF_LOWLEVEL | HKF_INTERSENDABLE , // WH_KEYBOARD_LL 13
|
|
HKF_SYSTEM | HKF_LOWLEVEL | HKF_INTERSENDABLE // WH_MOUSE_LL 14
|
|
|
|
#ifdef REDIRECTION
|
|
,HKF_SYSTEM | HKF_LOWLEVEL | HKF_INTERSENDABLE // WH_HITTEST 15
|
|
#endif // REDIRECTION
|
|
};
|
|
|
|
|
|
/*
|
|
* HACK (hiroyama) see xxxCallJournalPlaybackHook()
|
|
* Optimization: faster determination whether the message is one of
|
|
* WM_[SYS][DEAD]CHAR.
|
|
* Argument (msg) requires to be one of keyboard messages. Range check
|
|
* should be done before calling IS_CHAR_MSG() macro.
|
|
*
|
|
* (i.e. WM_KEYFIRST <= msg < WM_KEYLAST)
|
|
*
|
|
* We expect bit 0x02 of all WM_*CHAR messages to be set.
|
|
* and bit 0x02 of all WM_*KEY* messages to be clear
|
|
*
|
|
* WM_KEYDOWN 0x100 000
|
|
* WM_KEYUP 0x101 001
|
|
* WM_CHAR 0x102 010
|
|
* WM_DEADCHAR 0x103 011
|
|
*
|
|
* WM_SYSKEYDOWN 0x104 100
|
|
* WM_SYSKEYUP 0x105 101
|
|
* WM_SYSCHAR 0x106 110
|
|
* WM_SYSDEADCHAR 0x107 111
|
|
*
|
|
*/
|
|
|
|
/*
|
|
*/
|
|
#if (WM_KEYFIRST != 0x100) || \
|
|
(WM_KEYLAST != 0x109) || \
|
|
(WM_KEYLAST != WM_UNICHAR) || \
|
|
(WM_KEYDOWN & 0x2) || \
|
|
(WM_KEYUP & 0x2) || \
|
|
(WM_SYSKEYDOWN & 0x2) || \
|
|
(WM_SYSKEYUP & 0x2) || \
|
|
!(WM_CHAR & 0x02) || \
|
|
!(WM_DEADCHAR & 0x02) || \
|
|
!(WM_SYSCHAR & 0x02) || \
|
|
!(WM_SYSDEADCHAR & 0x02)
|
|
#error "unexpected value in keyboard messages."
|
|
#endif
|
|
|
|
|
|
#if DBG
|
|
|
|
BOOL IsCharMsg(UINT msg)
|
|
{
|
|
UserAssert(msg >= WM_KEYFIRST && msg < WM_KEYLAST);
|
|
|
|
return msg & 0x02;
|
|
}
|
|
|
|
#define IS_CHAR_MSG(msg) IsCharMsg(msg)
|
|
|
|
#else
|
|
|
|
#define IS_CHAR_MSG(msg) ((msg) & 0x02)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
void UnlinkHook(PHOOK phkFree);
|
|
/***************************************************************************\
|
|
* DbgValidateThisHook
|
|
*
|
|
* Validates a hook structure and returns the start of its chain.
|
|
*
|
|
* History:
|
|
* 03-25-97 GerardoB Created
|
|
\***************************************************************************/
|
|
#if DBG
|
|
PHOOK * DbgValidateThisHook (PHOOK phk, int iType, PTHREADINFO ptiHooked)
|
|
{
|
|
CheckCritIn();
|
|
/*
|
|
* No bogus flags
|
|
*/
|
|
UserAssert(!(phk->flags & ~HF_DBGUSED));
|
|
/*
|
|
* Type
|
|
*/
|
|
UserAssert(phk->iHook == iType);
|
|
/*
|
|
* HF_GLOBAL & ptiHooked. return the start of its hook chain.
|
|
*/
|
|
if (phk->flags & HF_GLOBAL) {
|
|
UserAssert(phk->ptiHooked == NULL);
|
|
if (phk->rpdesk != NULL) {
|
|
UserAssert(GETPTI(phk) == gptiRit);
|
|
return &phk->rpdesk->pDeskInfo->aphkStart[iType + 1];
|
|
} else {
|
|
return &GETPTI(phk)->pDeskInfo->aphkStart[iType + 1];
|
|
}
|
|
} else {
|
|
UserAssert((phk->ptiHooked == ptiHooked)
|
|
|| (abHookFlags[iType + 1] & HKF_INTERSENDABLE));
|
|
|
|
return &(phk->ptiHooked->aphkStart[iType + 1]);
|
|
}
|
|
}
|
|
/***************************************************************************\
|
|
* DbgValidatefsHook
|
|
*
|
|
* Make sure that the fsHook bit masks are in sync. If the bits
|
|
* are out of sync, some hook must have the HF_INCHECKWHF flag
|
|
* (this means the bits are being adjusted right now)
|
|
*
|
|
* History:
|
|
* 05-20-97 GerardoB Extracted from PhkFirst*Valid
|
|
\***************************************************************************/
|
|
void DbgValidatefsHook(PHOOK phk, int nFilterType, PTHREADINFO pti, BOOL fGlobal)
|
|
{
|
|
CheckCritIn();
|
|
/*
|
|
* If no pti is provided, figure out what it should be.
|
|
* phk is expected to be NULL.
|
|
*/
|
|
if (pti == NULL) {
|
|
fGlobal = (phk->flags & HF_GLOBAL);
|
|
if (fGlobal) {
|
|
pti = GETPTI(phk);
|
|
} else {
|
|
pti = phk->ptiHooked;
|
|
UserAssert(pti != NULL);
|
|
}
|
|
}
|
|
|
|
if (fGlobal) {
|
|
if ((phk != NULL) ^ IsGlobalHooked(pti, WHF_FROM_WH(nFilterType))) {
|
|
PHOOK phkTemp = pti->pDeskInfo->aphkStart[nFilterType + 1];
|
|
while ((phkTemp != NULL) && !(phkTemp->flags & HF_INCHECKWHF)) {
|
|
phkTemp = phkTemp->phkNext;
|
|
}
|
|
UserAssert(phkTemp != NULL);
|
|
}
|
|
} else {
|
|
if ((phk != NULL) ^ IsHooked(pti, WHF_FROM_WH(nFilterType))) {
|
|
PHOOK phkTemp = pti->aphkStart[nFilterType + 1];
|
|
while ((phkTemp != NULL) && !(phkTemp->flags & HF_INCHECKWHF)) {
|
|
phkTemp = phkTemp->phkNext;
|
|
}
|
|
if (phkTemp == NULL) {
|
|
phkTemp = pti->pDeskInfo->aphkStart[nFilterType + 1];
|
|
while ((phkTemp != NULL) && !(phkTemp->flags & HF_INCHECKWHF)) {
|
|
phkTemp = phkTemp->phkNext;
|
|
}
|
|
}
|
|
UserAssert(phkTemp != NULL);
|
|
}
|
|
}
|
|
}
|
|
/***************************************************************************\
|
|
* DbgValidateHooks
|
|
*
|
|
* This functions expects valid (not destroyed) and properly linked.
|
|
* History:
|
|
* 03-25-97 GerardoB Created
|
|
\***************************************************************************/
|
|
void DbgValidateHooks (PHOOK phk, int iType)
|
|
{
|
|
PHOOK *pphkStart, *pphkNext;
|
|
if (phk == NULL) {
|
|
return;
|
|
}
|
|
/*
|
|
* It shouldn't be destroyed
|
|
*/
|
|
UserAssert(!(phk->flags & (HF_DESTROYED | HF_FREED)));
|
|
/*
|
|
* Validate fsHooks
|
|
*/
|
|
DbgValidatefsHook(phk, iType, NULL, FALSE);
|
|
/*
|
|
* Validate this hook and get the beginning of the hook chain
|
|
*/
|
|
pphkStart = DbgValidateThisHook(phk, iType, phk->ptiHooked);
|
|
/*
|
|
* There must be at least one hook in the chain
|
|
*/
|
|
UserAssert(*pphkStart != NULL);
|
|
/*
|
|
* Validate the link.
|
|
* And while your're at it, validate all hooks!
|
|
*/
|
|
pphkNext = pphkStart;
|
|
while ((*pphkNext != phk) && (*pphkNext != NULL)) {
|
|
UserAssert(pphkStart == DbgValidateThisHook(*pphkNext, iType, phk->ptiHooked));
|
|
pphkNext = &(*pphkNext)->phkNext;
|
|
}
|
|
/*
|
|
* Verify that we found it.
|
|
*/
|
|
UserAssert(*pphkNext == phk);
|
|
/*
|
|
* Walk until the end of the chain
|
|
*/
|
|
while (*pphkNext != NULL) {
|
|
UserAssert(pphkStart == DbgValidateThisHook(*pphkNext, iType, phk->ptiHooked));
|
|
pphkNext = &(*pphkNext)->phkNext;
|
|
}
|
|
}
|
|
#else
|
|
#define DbgValidatefsHook(phk, nFilterType, pti, fGlobal)
|
|
#endif /* DBG */
|
|
/***************************************************************************\
|
|
* zzzJournalAttach
|
|
*
|
|
* This attaches/detaches threads to one input queue so input is synchronized.
|
|
* Journalling requires this.
|
|
*
|
|
* 12-10-92 ScottLu Created.
|
|
\***************************************************************************/
|
|
|
|
BOOL zzzJournalAttach(
|
|
PTHREADINFO pti,
|
|
BOOL fAttach)
|
|
{
|
|
PTHREADINFO ptiT;
|
|
PQ pq;
|
|
PLIST_ENTRY pHead, pEntry;
|
|
|
|
/*
|
|
* If we're attaching, calculate the pqAttach for all threads journalling.
|
|
* If we're unattaching, just call zzzReattachThreads() and it will calculate
|
|
* the non-journalling queues to attach to.
|
|
*/
|
|
if (fAttach) {
|
|
if ((pq = AllocQueue(pti, NULL)) == NULL)
|
|
return FALSE;
|
|
|
|
pHead = &pti->rpdesk->PtiList;
|
|
for (pEntry = pHead->Flink; pEntry != pHead; pEntry = pEntry->Flink) {
|
|
ptiT = CONTAINING_RECORD(pEntry, THREADINFO, PtiLink);
|
|
|
|
/*
|
|
* This is the Q to attach to for all threads that will do
|
|
* journalling.
|
|
*/
|
|
if (!(ptiT->TIF_flags & (TIF_DONTJOURNALATTACH | TIF_INCLEANUP))) {
|
|
ptiT->pqAttach = pq;
|
|
ptiT->pqAttach->cThreads++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return zzzReattachThreads(fAttach);
|
|
}
|
|
/***************************************************************************\
|
|
* InterQueueMsgCleanup
|
|
*
|
|
* Walk gpsmsList looking for inter queue messages with a hung receiver;
|
|
* if one is found and it's a message that would have been an async event or
|
|
* intra queue if not journalling, then it cleans it up.
|
|
*
|
|
* While Journalling most threads are attached to the same queue. This causes
|
|
* activation and input stuff to be synchronous; if a thread hangs or dies,
|
|
* any other thread sending a message to the hung/dead thread will be
|
|
* blocked for good.
|
|
* This is critical when the blocked thread is cssr; this can happen with
|
|
* console windows or when some one requests a hard error box, specially
|
|
* during window activation.
|
|
*
|
|
* This function must be called when all queues have been detached (unless previously attached),
|
|
* so we can take care of hung/dead receivers with pending SMSs.
|
|
*
|
|
* 03-28-96 GerardoB Created
|
|
\***************************************************************************/
|
|
void InterQueueMsgCleanup (DWORD dwTimeFromLastRead)
|
|
{
|
|
PSMS *ppsms;
|
|
PSMS psmsNext;
|
|
|
|
CheckCritIn();
|
|
|
|
/*
|
|
* Walk gpsmsList
|
|
*/
|
|
for (ppsms = &gpsmsList; *ppsms; ) {
|
|
psmsNext = (*ppsms)->psmsNext;
|
|
/*
|
|
* If this is an inter queue message
|
|
*/
|
|
if (((*ppsms)->ptiSender != NULL)
|
|
&& ((*ppsms)->ptiReceiver != NULL)
|
|
&& ((*ppsms)->ptiSender->pq != (*ppsms)->ptiReceiver->pq)) {
|
|
/*
|
|
* If the receiver has been hung for a while
|
|
*/
|
|
if (FHungApp ((*ppsms)->ptiReceiver, dwTimeFromLastRead)) {
|
|
|
|
switch ((*ppsms)->message) {
|
|
/*
|
|
* Activation messages
|
|
*/
|
|
case WM_NCACTIVATE:
|
|
case WM_ACTIVATEAPP:
|
|
case WM_ACTIVATE:
|
|
case WM_SETFOCUS:
|
|
case WM_KILLFOCUS:
|
|
case WM_QUERYNEWPALETTE:
|
|
/*
|
|
* Sent to spwndFocus, which now can be in a different queue
|
|
*/
|
|
case WM_INPUTLANGCHANGE:
|
|
RIPMSG3 (RIP_WARNING, "InterQueueMsgCleanup: ptiSender:%#p ptiReceiver:%#p message:%#lx",
|
|
(*ppsms)->ptiSender, (*ppsms)->ptiReceiver, (*ppsms)->message);
|
|
ReceiverDied(*ppsms, ppsms);
|
|
break;
|
|
|
|
} /* switch */
|
|
|
|
} /* If hung receiver */
|
|
|
|
} /* If inter queue message */
|
|
|
|
/*
|
|
* If the message was not unlinked, go to the next one.
|
|
*/
|
|
if (*ppsms != psmsNext)
|
|
ppsms = &(*ppsms)->psmsNext;
|
|
|
|
} /* for */
|
|
}
|
|
/***************************************************************************\
|
|
* zzzCancelJournalling
|
|
*
|
|
* Journalling is cancelled with control-escape is pressed, or when the desktop
|
|
* is switched.
|
|
*
|
|
* 01-27-93 ScottLu Created.
|
|
\***************************************************************************/
|
|
|
|
void zzzCancelJournalling(void)
|
|
{
|
|
PTHREADINFO ptiCancelJournal;
|
|
PHOOK phook;
|
|
PHOOK phookNext;
|
|
|
|
/*
|
|
* Mouse buttons sometimes get stuck down due to hardware glitches,
|
|
* usually due to input concentrator switchboxes or faulty serial
|
|
* mouse COM ports, so clear the global button state here just in case,
|
|
* otherwise we may not be able to change focus with the mouse.
|
|
* Also do this in Alt-Tab processing.
|
|
*/
|
|
#if DBG
|
|
if (gwMouseOwnerButton)
|
|
RIPMSG1(RIP_WARNING,
|
|
"gwMouseOwnerButton=%x, being cleared forcibly\n",
|
|
gwMouseOwnerButton);
|
|
#endif
|
|
gwMouseOwnerButton = 0;
|
|
|
|
/*
|
|
* Remove journal hooks. This'll cause threads to associate with
|
|
* different queues.
|
|
* DeferWinEventNotify() so we can traverse the phook list safely
|
|
*/
|
|
DeferWinEventNotify();
|
|
UserAssert(gptiRit->pDeskInfo == grpdeskRitInput->pDeskInfo);
|
|
phook = PhkFirstGlobalValid(gptiRit, WH_JOURNALPLAYBACK);
|
|
while (phook != NULL) {
|
|
ptiCancelJournal = phook->head.pti;
|
|
|
|
if (ptiCancelJournal != NULL) {
|
|
/*
|
|
* Let the thread that set the journal hook know this is happening.
|
|
*/
|
|
_PostThreadMessage(ptiCancelJournal, WM_CANCELJOURNAL, 0, 0);
|
|
|
|
/*
|
|
* If there was an app waiting for a response back from the journal
|
|
* application, cancel that request so the app can continue running
|
|
* (for example, we don't want winlogon or console to wait for an
|
|
* app that may be hung!)
|
|
*/
|
|
SendMsgCleanup(ptiCancelJournal);
|
|
}
|
|
|
|
phookNext = PhkNextValid(phook);
|
|
zzzUnhookWindowsHookEx(phook); // May free phook memory
|
|
phook = phookNext;
|
|
}
|
|
zzzEndDeferWinEventNotify();
|
|
|
|
/*
|
|
* DeferWinEventNotify() so we can traverse the phook list safely
|
|
*/
|
|
DeferWinEventNotify();
|
|
UserAssert(gptiRit->pDeskInfo == grpdeskRitInput->pDeskInfo);
|
|
phook = PhkFirstGlobalValid(gptiRit, WH_JOURNALRECORD);
|
|
while (phook != NULL) {
|
|
ptiCancelJournal = phook->head.pti;
|
|
|
|
if (ptiCancelJournal != NULL) {
|
|
/*
|
|
* Let the thread that set the journal hook know this is happening.
|
|
*/
|
|
_PostThreadMessage(ptiCancelJournal, WM_CANCELJOURNAL, 0, 0);
|
|
|
|
/*
|
|
* If there was an app waiting for a response back from the journal
|
|
* application, cancel that request so the app can continue running
|
|
* (for example, we don't want winlogon or console to wait for an
|
|
* app that may be hung!)
|
|
*/
|
|
SendMsgCleanup(ptiCancelJournal);
|
|
}
|
|
|
|
phookNext = PhkNextValid(phook);
|
|
zzzUnhookWindowsHookEx(phook); // May free phook memory
|
|
phook = phookNext;
|
|
}
|
|
zzzEndDeferWinEventNotify();
|
|
|
|
|
|
/*
|
|
* Make sure journalling ssync mode didn't hose any one
|
|
*/
|
|
InterQueueMsgCleanup(CMSWAITTOKILLTIMEOUT);
|
|
|
|
/*
|
|
* Unlock SetForegroundWindow (if locked)
|
|
*/
|
|
gppiLockSFW = NULL;
|
|
|
|
/*
|
|
* NT5's last minute hack for evil applications, who disables the desktop window
|
|
* (perhaps by accidents though) leaving the system pretty unusable.
|
|
* See Raid #423704.
|
|
*/
|
|
if (grpdeskRitInput && grpdeskRitInput->pDeskInfo) {
|
|
PWND pwndDesktop = grpdeskRitInput->pDeskInfo->spwnd;
|
|
|
|
if (pwndDesktop && TestWF(pwndDesktop, WFDISABLED)) {
|
|
ClrWF(pwndDesktop, WFDISABLED);
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* zzzSetWindowsHookAW (API)
|
|
*
|
|
* This is the Win32 version of the SetWindowsHook() call. It has the
|
|
* same characteristics as far as return values, but only sets 'local'
|
|
* hooks. This is because we weren't provided a DLL we can load into
|
|
* other processes. Because of this WH_SYSMSGFILTER is no longer a
|
|
* valid hook. Apps will either need to call with WH_MSGFILTER or call
|
|
* the new API SetWindowsHookEx(). Essentially this API is obsolete and
|
|
* everyone should call SetWindowsHookEx().
|
|
*
|
|
* History:
|
|
* 10-Feb-1991 DavidPe Created.
|
|
* 30-Jan-1992 IanJa Added bAnsi parameter
|
|
\***************************************************************************/
|
|
|
|
PROC zzzSetWindowsHookAW(
|
|
int nFilterType,
|
|
PROC pfnFilterProc,
|
|
DWORD dwFlags)
|
|
{
|
|
PHOOK phk;
|
|
|
|
phk = zzzSetWindowsHookEx(NULL, NULL, PtiCurrent(),
|
|
nFilterType, pfnFilterProc, dwFlags);
|
|
|
|
/*
|
|
* If we get an error from zzzSetWindowsHookEx() then we return
|
|
* -1 to be compatible with older version of Windows.
|
|
*/
|
|
if (phk == NULL) {
|
|
return (PROC)-1;
|
|
}
|
|
|
|
/*
|
|
* Handle the backwards compatibility return value cases for
|
|
* SetWindowsHook. If this was the first hook in the chain,
|
|
* then return NULL, else return something non-zero. HKF_NZRET
|
|
* is a special case where SetWindowsHook would always return
|
|
* something because there was a default hook installed. Some
|
|
* apps relied on a non-zero return value in those cases.
|
|
*/
|
|
if ((phk->phkNext != NULL) || (abHookFlags[nFilterType + 1] & HKF_NZRET)) {
|
|
return (PROC)phk;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* zzzSetWindowsHookEx
|
|
*
|
|
* SetWindowsHookEx() is the updated version of SetWindowsHook(). It allows
|
|
* applications to set hooks on specific threads or throughout the entire
|
|
* system. The function returns a hook handle to the application if
|
|
* successful and NULL if a failure occured.
|
|
*
|
|
* History:
|
|
* 28-Jan-1991 DavidPe Created.
|
|
* 15-May-1991 ScottLu Changed to work client/server.
|
|
* 30-Jan-1992 IanJa Added bAnsi parameter
|
|
\***************************************************************************/
|
|
|
|
PHOOK zzzSetWindowsHookEx(
|
|
HANDLE hmod,
|
|
PUNICODE_STRING pstrLib,
|
|
PTHREADINFO ptiThread,
|
|
int nFilterType,
|
|
PROC pfnFilterProc,
|
|
DWORD dwFlags)
|
|
{
|
|
ACCESS_MASK amDesired;
|
|
PHOOK phkNew;
|
|
TL tlphkNew;
|
|
PHOOK *pphkStart;
|
|
PTHREADINFO ptiCurrent;
|
|
|
|
/*
|
|
* Check to see if filter type is valid.
|
|
*/
|
|
if (nFilterType < WH_MIN || nFilterType > WH_MAX) {
|
|
RIPERR1(ERROR_INVALID_HOOK_FILTER,
|
|
RIP_WARNING,
|
|
"Invalid hook type 0x%x",
|
|
nFilterType);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Check to see if filter proc is valid.
|
|
*/
|
|
if (pfnFilterProc == NULL) {
|
|
RIPERR0(ERROR_INVALID_FILTER_PROC,
|
|
RIP_WARNING,
|
|
"NULL hook callback");
|
|
return NULL;
|
|
}
|
|
|
|
ptiCurrent = PtiCurrent();
|
|
|
|
if (ptiThread == NULL) {
|
|
/*
|
|
* Is the app trying to set a global hook without a library?
|
|
* If so return an error.
|
|
*/
|
|
if (hmod == NULL) {
|
|
RIPERR0(ERROR_HOOK_NEEDS_HMOD,
|
|
RIP_WARNING,
|
|
"Global hook requires a non-NULL hmod");
|
|
return NULL;
|
|
}
|
|
} else {
|
|
/*
|
|
* Is the app trying to set a local hook that is global-only?
|
|
* If so return an error.
|
|
*/
|
|
if (!(abHookFlags[nFilterType + 1] & HKF_TASK)) {
|
|
RIPERR1(ERROR_GLOBAL_ONLY_HOOK,
|
|
RIP_WARNING,
|
|
"Hook type 0x%x must be global",
|
|
nFilterType);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Can't hook outside our own desktop.
|
|
*/
|
|
if (ptiThread->rpdesk != ptiCurrent->rpdesk) {
|
|
RIPERR0(ERROR_ACCESS_DENIED,
|
|
RIP_WARNING,
|
|
"Access denied to desktop in zzzSetWindowsHookEx - can't hook other desktops");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (ptiCurrent->ppi != ptiThread->ppi) {
|
|
/*
|
|
* Is the app trying to set hook in another process without a library?
|
|
* If so return an error.
|
|
*/
|
|
if (hmod == NULL) {
|
|
RIPERR0(ERROR_HOOK_NEEDS_HMOD,
|
|
RIP_WARNING,
|
|
"Cross-thread hook needs a non-NULL hmod");
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Is the app hooking another user without access?
|
|
* If so return an error. Note that this check is done
|
|
* for global hooks every time the hook is called.
|
|
*/
|
|
if ((!RtlEqualLuid(&ptiThread->ppi->luidSession,
|
|
&ptiCurrent->ppi->luidSession)) &&
|
|
!(ptiThread->TIF_flags & TIF_ALLOWOTHERACCOUNTHOOK)) {
|
|
|
|
RIPERR0(ERROR_ACCESS_DENIED,
|
|
RIP_WARNING,
|
|
"Access denied to other user in zzzSetWindowsHookEx");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if ((ptiThread->TIF_flags & (TIF_CSRSSTHREAD | TIF_SYSTEMTHREAD)) &&
|
|
!(abHookFlags[nFilterType + 1] & HKF_INTERSENDABLE)) {
|
|
|
|
/*
|
|
* Can't hook console or GUI system thread if inter-thread
|
|
* calling isn't implemented for this hook type.
|
|
*/
|
|
RIPERR1(ERROR_HOOK_TYPE_NOT_ALLOWED,
|
|
RIP_WARNING,
|
|
"nFilterType (%ld) not allowed in zzzSetWindowsHookEx",
|
|
nFilterType);
|
|
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check if this thread has access to hook its desktop.
|
|
*/
|
|
switch( nFilterType ) {
|
|
case WH_JOURNALRECORD:
|
|
amDesired = DESKTOP_JOURNALRECORD;
|
|
break;
|
|
|
|
case WH_JOURNALPLAYBACK:
|
|
amDesired = DESKTOP_JOURNALPLAYBACK;
|
|
break;
|
|
|
|
default:
|
|
amDesired = DESKTOP_HOOKCONTROL;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* The console input thread *must* be allowed to set this hook, even if
|
|
* the account it's impersonating doesn't have hook access to the desktop
|
|
* in question.
|
|
*/
|
|
if (!RtlAreAllAccessesGranted(ptiCurrent->amdesk, amDesired) &&
|
|
!(ISCSRSS() && nFilterType == WH_MSGFILTER)) {
|
|
|
|
UserAssert(!ISCSRSS() ||
|
|
(PsGetCurrentThreadId() ==
|
|
UlongToHandle(ptiCurrent->rpdesk->dwConsoleThreadId)));
|
|
|
|
RIPERR0(ERROR_ACCESS_DENIED,
|
|
RIP_WARNING,
|
|
"Access denied to desktop in zzzSetWindowsHookEx");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (amDesired != DESKTOP_HOOKCONTROL &&
|
|
(ptiCurrent->rpdesk->rpwinstaParent->dwWSF_Flags & WSF_NOIO)) {
|
|
RIPERR0(ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION,
|
|
RIP_WARNING,
|
|
"Journal hooks invalid on a desktop belonging to a non-interactive WindowStation.");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Allocate the new HOOK structure.
|
|
*/
|
|
phkNew = HMAllocObject(ptiCurrent, ptiCurrent->rpdesk, TYPE_HOOK, sizeof(HOOK));
|
|
if (phkNew == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* If a DLL is required for this hook, register the library with
|
|
* the library management routines so we can assure it's loaded
|
|
* into all the processes necessary.
|
|
*/
|
|
phkNew->ihmod = -1;
|
|
if (hmod != NULL) {
|
|
|
|
phkNew->ihmod = GetHmodTableIndex(pstrLib);
|
|
|
|
if (phkNew->ihmod == -1) {
|
|
RIPERR2(ERROR_MOD_NOT_FOUND,
|
|
RIP_WARNING,
|
|
"Couldn't load DLL %.*ws",
|
|
pstrLib->Length,
|
|
pstrLib->Buffer);
|
|
HMFreeObject((PVOID)phkNew);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Add a dependency on this module - meaning, increment a count
|
|
* that simply counts the number of hooks set into this module.
|
|
*/
|
|
if (phkNew->ihmod >= 0) {
|
|
AddHmodDependency(phkNew->ihmod);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Depending on whether we're setting a global or local hook,
|
|
* get the start of the appropriate linked-list of HOOKs. Also
|
|
* set the HF_GLOBAL flag if it's a global hook.
|
|
*/
|
|
if (ptiThread != NULL) {
|
|
pphkStart = &ptiThread->aphkStart[nFilterType + 1];
|
|
|
|
/*
|
|
* Set the WHF_* in the THREADINFO so we know it's hooked.
|
|
*/
|
|
ptiThread->fsHooks |= WHF_FROM_WH(nFilterType);
|
|
|
|
/*
|
|
* Set the flags in the thread's TEB
|
|
*/
|
|
if (ptiThread->pClientInfo) {
|
|
BOOL fAttached;
|
|
|
|
/*
|
|
* If the thread being hooked is in another process, attach
|
|
* to that process so that we can access its ClientInfo.
|
|
*/
|
|
if (ptiThread->ppi != ptiCurrent->ppi) {
|
|
KeAttachProcess(PsGetProcessPcb(ptiThread->ppi->Process));
|
|
fAttached = TRUE;
|
|
} else
|
|
fAttached = FALSE;
|
|
try {
|
|
ptiThread->pClientInfo->fsHooks = ptiThread->fsHooks;
|
|
} except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
|
|
/*
|
|
* Worst case scenario is the the cleint side will
|
|
* be out of sync of which hooks are installed
|
|
*/
|
|
}
|
|
if (fAttached) {
|
|
KeDetachProcess();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Remember which thread we're hooking.
|
|
*/
|
|
phkNew->ptiHooked = ptiThread;
|
|
|
|
} else {
|
|
pphkStart = &ptiCurrent->pDeskInfo->aphkStart[nFilterType + 1];
|
|
phkNew->flags |= HF_GLOBAL;
|
|
|
|
/*
|
|
* Set the WHF_* in the SERVERINFO so we know it's hooked.
|
|
*/
|
|
ptiCurrent->pDeskInfo->fsHooks |= WHF_FROM_WH(nFilterType);
|
|
|
|
phkNew->ptiHooked = NULL;
|
|
}
|
|
|
|
/*
|
|
* Does the hook function expect ANSI or Unicode text?
|
|
*/
|
|
phkNew->flags |= (dwFlags & HF_ANSI);
|
|
|
|
/*
|
|
* Initialize the HOOK structure. Unreferenced parameters are assumed
|
|
* to be initialized to zero by LocalAlloc().
|
|
*/
|
|
phkNew->iHook = nFilterType;
|
|
|
|
/*
|
|
* Libraries are loaded at different linear addresses in different
|
|
* process contexts. For this reason, we need to convert the filter
|
|
* proc address into an offset while setting the hook, and then convert
|
|
* it back to a real per-process function pointer when calling a
|
|
* hook. Do this by subtracting the 'hmod' (which is a pointer to the
|
|
* linear and contiguous .exe header) from the function index.
|
|
*/
|
|
phkNew->offPfn = ((ULONG_PTR)pfnFilterProc) - ((ULONG_PTR)hmod);
|
|
|
|
#ifdef HOOKBATCH
|
|
phkNew->cEventMessages = 0;
|
|
phkNew->iCurrentEvent = 0;
|
|
phkNew->CacheTimeOut = 0;
|
|
phkNew->aEventCache = NULL;
|
|
#endif //HOOKBATCH
|
|
|
|
/*
|
|
* Link this hook into the front of the hook-list.
|
|
*/
|
|
phkNew->phkNext = *pphkStart;
|
|
*pphkStart = phkNew;
|
|
|
|
/*
|
|
* If this is a journal hook, setup synchronized input processing
|
|
* AFTER we set the hook - so this synchronization can be cancelled
|
|
* with control-esc.
|
|
*/
|
|
if (abHookFlags[nFilterType + 1] & HKF_JOURNAL) {
|
|
/*
|
|
* Attach everyone to us so journal-hook processing
|
|
* will be synchronized.
|
|
* No need to DeferWinEventNotify() here, since we lock phkNew.
|
|
*/
|
|
ThreadLockAlwaysWithPti(ptiCurrent, phkNew, &tlphkNew);
|
|
if (!zzzJournalAttach(ptiCurrent, TRUE)) {
|
|
RIPMSG1(RIP_WARNING, "zzzJournalAttach failed, so abort hook %#p", phkNew);
|
|
if (ThreadUnlock(&tlphkNew) != NULL) {
|
|
zzzUnhookWindowsHookEx(phkNew);
|
|
}
|
|
return NULL;
|
|
}
|
|
if ((phkNew = ThreadUnlock(&tlphkNew)) == NULL) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
UserAssert(phkNew != NULL);
|
|
|
|
/*
|
|
* Later 5.0 GerardoB: The old code just to check this but
|
|
* I think it's some left over stuff from server side days.
|
|
.* Let's assert on it for a while
|
|
* Also, I added the assertions in the else's below because I reorganized
|
|
* the code and want to make sure we don't change behavior
|
|
*/
|
|
UserAssert(ptiCurrent->pEThread && PsGetThreadProcess(ptiCurrent->pEThread));
|
|
|
|
/*
|
|
* Can't allow a process that has set a global hook that works
|
|
* on server-side winprocs to run at background priority! Bump
|
|
* up it's dynamic priority and mark it so it doesn't get reset.
|
|
*/
|
|
if ((phkNew->flags & HF_GLOBAL) &&
|
|
(abHookFlags[nFilterType + 1] & HKF_INTERSENDABLE)) {
|
|
|
|
ptiCurrent->TIF_flags |= TIF_GLOBALHOOKER;
|
|
KeSetPriorityThread(PsGetThreadTcb(ptiCurrent->pEThread), LOW_REALTIME_PRIORITY-2);
|
|
|
|
if (abHookFlags[nFilterType + 1] & HKF_JOURNAL) {
|
|
ThreadLockAlwaysWithPti(ptiCurrent, phkNew, &tlphkNew);
|
|
/*
|
|
* If we're changing the journal hooks, jiggle the mouse.
|
|
* This way the first event will always be a mouse move, which
|
|
* will ensure that the cursor is set properly.
|
|
*/
|
|
zzzSetFMouseMoved();
|
|
phkNew = ThreadUnlock(&tlphkNew);
|
|
/*
|
|
* If setting a journal playback hook, this process is the input
|
|
* provider. This gives it the right to call SetForegroundWindow
|
|
*/
|
|
if (nFilterType == WH_JOURNALPLAYBACK) {
|
|
gppiInputProvider = ptiCurrent->ppi;
|
|
}
|
|
} else {
|
|
UserAssert(nFilterType != WH_JOURNALPLAYBACK);
|
|
}
|
|
} else {
|
|
UserAssert(!(abHookFlags[nFilterType + 1] & HKF_JOURNAL));
|
|
UserAssert(nFilterType != WH_JOURNALPLAYBACK);
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Return pointer to our internal hook structure so we know
|
|
* which hook to call next in CallNextHookEx().
|
|
*/
|
|
DbgValidateHooks(phkNew, phkNew->iHook);
|
|
return phkNew;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxCallNextHookEx
|
|
*
|
|
* In the new world DefHookProc() is a bit deceptive since SetWindowsHook()
|
|
* isn't returning the actual address of the next hook to call, but instead
|
|
* a hook handle. CallNextHookEx() is a slightly clearer picture of what's
|
|
* going on so apps don't get tempted to try and call the value we return.
|
|
*
|
|
* As a side note we don't actually use the hook handle passed in. We keep
|
|
* track of which hooks is currently being called on a thread in the Q
|
|
* structure and use that. This is because SetWindowsHook() will sometimes
|
|
* return NULL to be compatible with the way it used to work, but even though
|
|
* we may be dealing with the last 'local' hook, there may be further 'global'
|
|
* hooks we need to call. PhkNext() is smart enough to jump over to the
|
|
* 'global' hook chain if it reaches the end of the 'local' hook chain.
|
|
*
|
|
* History:
|
|
* 01-30-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
LRESULT xxxCallNextHookEx(
|
|
int nCode,
|
|
WPARAM wParam,
|
|
LPARAM lParam)
|
|
{
|
|
BOOL bAnsiHook;
|
|
|
|
if (PtiCurrent()->sphkCurrent == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return xxxCallHook2(PhkNextValid(PtiCurrent()->sphkCurrent), nCode, wParam, lParam, &bAnsiHook);
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* CheckWHFBits
|
|
*
|
|
* This routine checks to see if any hooks for nFilterType exist, and clear
|
|
* the appropriate WHF_ in the THREADINFO and SERVERINFO.
|
|
*
|
|
* History:
|
|
* 08-17-92 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
VOID CheckWHFBits(
|
|
PTHREADINFO pti,
|
|
int nFilterType)
|
|
{
|
|
BOOL fClearThreadBits;
|
|
BOOL fClearDesktopBits;
|
|
PHOOK phook;
|
|
|
|
|
|
/*
|
|
* Assume we're are going to clear local(thread) and
|
|
* global(desktop) bits.
|
|
*/
|
|
fClearThreadBits = TRUE;
|
|
fClearDesktopBits = TRUE;
|
|
/*
|
|
* Get the first valid hook for this thread
|
|
*/
|
|
phook = PhkFirstValid(pti, nFilterType);
|
|
if (phook != NULL) {
|
|
/*
|
|
* If it found a global hook, don't clear the desktop bits
|
|
* (that would mean that there are no local(thread) hooks
|
|
* so we fall through to clear the thread bits)
|
|
*/
|
|
if (phook->flags & HF_GLOBAL) {
|
|
fClearDesktopBits = FALSE;
|
|
} else {
|
|
/*
|
|
* It found a thread hook so don't clear the thread bits
|
|
*/
|
|
fClearThreadBits = FALSE;
|
|
/*
|
|
* Check for global hooks now. If there is one, don't
|
|
* clear the desktop bits
|
|
*/
|
|
phook = PhkFirstGlobalValid(pti, nFilterType);
|
|
fClearDesktopBits = (phook == NULL);
|
|
}
|
|
} /* if (phook != NULL) */
|
|
|
|
if (fClearThreadBits) {
|
|
pti->fsHooks &= ~(WHF_FROM_WH(nFilterType));
|
|
/*
|
|
* Set the flags in the thread's TEB
|
|
*/
|
|
if (pti->pClientInfo) {
|
|
BOOL fAttached;
|
|
/*
|
|
* If the hooked thread is in another process, attach
|
|
* to that process to access its address space.
|
|
*/
|
|
if (pti->ppi != PpiCurrent()) {
|
|
KeAttachProcess(PsGetProcessPcb(pti->ppi->Process));
|
|
fAttached = TRUE;
|
|
} else
|
|
fAttached = FALSE;
|
|
|
|
try {
|
|
pti->pClientInfo->fsHooks = pti->fsHooks;
|
|
} except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
|
|
/*
|
|
* Worst case scenario is the the cleint side will
|
|
* be out of sync of which hooks are installed
|
|
*/
|
|
}
|
|
|
|
if (fAttached)
|
|
KeDetachProcess();
|
|
}
|
|
}
|
|
|
|
if (fClearDesktopBits) {
|
|
pti->pDeskInfo->fsHooks &= ~(WHF_FROM_WH(nFilterType));
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* zzzUnhookWindowsHook (API)
|
|
*
|
|
* This is the old version of the Unhook API. It does the same thing as
|
|
* zzzUnhookWindowsHookEx(), but takes a filter-type and filter-proc to
|
|
* identify which hook to unhook.
|
|
*
|
|
* History:
|
|
* 01-28-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
BOOL zzzUnhookWindowsHook(
|
|
int nFilterType,
|
|
PROC pfnFilterProc)
|
|
{
|
|
PHOOK phk;
|
|
PTHREADINFO ptiCurrent;
|
|
|
|
if ((nFilterType < WH_MIN) || (nFilterType > WH_MAX)) {
|
|
RIPERR0(ERROR_INVALID_HOOK_FILTER, RIP_VERBOSE, "");
|
|
return FALSE;
|
|
}
|
|
|
|
ptiCurrent = PtiCurrent();
|
|
|
|
for (phk = PhkFirstValid(ptiCurrent, nFilterType); phk != NULL; phk = PhkNextValid(phk)) {
|
|
/*
|
|
* Is this the hook we're looking for?
|
|
*/
|
|
if (PFNHOOK(phk) == pfnFilterProc) {
|
|
|
|
/*
|
|
* Are we on the thread that set the hook?
|
|
* If not return an error.
|
|
*/
|
|
if (GETPTI(phk) != ptiCurrent) {
|
|
RIPERR0(ERROR_ACCESS_DENIED,
|
|
RIP_WARNING,
|
|
"Access denied in zzzUnhookWindowsHook: "
|
|
"this thread is not the same as that which set the hook");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return zzzUnhookWindowsHookEx( phk );
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Didn't find the hook we were looking for so return FALSE.
|
|
*/
|
|
RIPERR0(ERROR_HOOK_NOT_INSTALLED, RIP_VERBOSE, "");
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* zzzUnhookWindowsHookEx (API)
|
|
*
|
|
* Applications call this API to 'unhook' a hook. First we check if someone
|
|
* is currently calling this hook. If no one is we go ahead and free the
|
|
* HOOK structure now. If someone is then we simply clear the filter-proc
|
|
* in the HOOK structure. In xxxCallHook2() we check for this and if by
|
|
* that time no one is calling the hook in question we free it there.
|
|
*
|
|
* History:
|
|
* 01-28-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
BOOL zzzUnhookWindowsHookEx(
|
|
PHOOK phkFree)
|
|
{
|
|
PTHREADINFO pti;
|
|
|
|
pti = GETPTI(phkFree);
|
|
|
|
/*
|
|
* If this hook is already destroyed, bail
|
|
*/
|
|
if (phkFree->flags & HF_DESTROYED) {
|
|
RIPMSG1(RIP_WARNING, "_UnhookWindowsHookEx(%#p) already destroyed", phkFree);
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Clear the journaling flags in all the queues.
|
|
*/
|
|
if (abHookFlags[phkFree->iHook + 1] & HKF_JOURNAL) {
|
|
zzzJournalAttach(pti, FALSE);
|
|
/*
|
|
* If someone got stuck because of the hook, let him go
|
|
*
|
|
* I want to get some performance numbers before checking this in.
|
|
* MSTest hooks and unhooks all the time when running a script.
|
|
* This code has never been in. 5/22/96. GerardoB
|
|
*/
|
|
// InterQueueMsgCleanup(3 * CMSWAITTOKILLTIMEOUT);
|
|
}
|
|
|
|
/*
|
|
* If no one is currently calling this hook,
|
|
* go ahead and free it now.
|
|
*/
|
|
FreeHook(phkFree);
|
|
|
|
/*
|
|
* If this thread has no more global hooks that are able to hook
|
|
* server-side window procs, we must clear it's TIF_GLOBALHOOKER bit.
|
|
*/
|
|
if (pti->TIF_flags & TIF_GLOBALHOOKER) {
|
|
int iHook;
|
|
PHOOK phk;
|
|
for (iHook = WH_MIN ; iHook <= WH_MAX ; ++iHook) {
|
|
/*
|
|
* Ignore those that can't hook server-side winprocs
|
|
*/
|
|
if (!(abHookFlags[iHook + 1] & HKF_INTERSENDABLE)) {
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Scan the global hooks
|
|
*/
|
|
for (phk = PhkFirstGlobalValid(pti, iHook);
|
|
phk != NULL; phk = PhkNextValid(phk)) {
|
|
|
|
if (GETPTI(phk) == pti) {
|
|
goto StillHasGlobalHooks;
|
|
}
|
|
}
|
|
}
|
|
pti->TIF_flags &= ~TIF_GLOBALHOOKER;
|
|
}
|
|
|
|
StillHasGlobalHooks:
|
|
/*
|
|
* Success, return TRUE.
|
|
*/
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* _CallMsgFilter (API)
|
|
*
|
|
* CallMsgFilter() allows applications to call the WH_*MSGFILTER hooks.
|
|
* If there's a sysmodal window we return FALSE right away. WH_MSGFILTER
|
|
* isn't called if WH_SYSMSGFILTER returned non-zero.
|
|
*
|
|
* History:
|
|
* 01-29-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
BOOL _CallMsgFilter(
|
|
LPMSG pmsg,
|
|
int nCode)
|
|
{
|
|
PTHREADINFO pti;
|
|
|
|
pti = PtiCurrent();
|
|
|
|
/*
|
|
* First call WH_SYSMSGFILTER. If it returns non-zero, don't
|
|
* bother calling WH_MSGFILTER, just return TRUE. Otherwise
|
|
* return what WH_MSGFILTER gives us.
|
|
*/
|
|
if (IsHooked(pti, WHF_SYSMSGFILTER) && xxxCallHook(nCode, 0, (LPARAM)pmsg,
|
|
WH_SYSMSGFILTER)) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (IsHooked(pti, WHF_MSGFILTER)) {
|
|
return (BOOL)xxxCallHook(nCode, 0, (LPARAM)pmsg, WH_MSGFILTER);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxCallHook
|
|
*
|
|
* User code calls this function to call the first hook of a specific
|
|
* type.
|
|
*
|
|
* History:
|
|
* 01-29-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
int xxxCallHook(
|
|
int nCode,
|
|
WPARAM wParam,
|
|
LPARAM lParam,
|
|
int iHook)
|
|
{
|
|
BOOL bAnsiHook;
|
|
|
|
return (int)xxxCallHook2(PhkFirstValid(PtiCurrent(), iHook), nCode, wParam, lParam, &bAnsiHook);
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxCallHook2
|
|
*
|
|
* When you have an actual HOOK structure to call, you'd use this function.
|
|
* It will check to see if the hook hasn't already been unhooked, and if
|
|
* is it will free it and keep looking until it finds a hook it can call
|
|
* or hits the end of the list. We also make sure any needed DLLs are loaded
|
|
* here. We also check to see if the HOOK was unhooked inside the call
|
|
* after we return.
|
|
*
|
|
* Note: Hooking server-side window procedures (such as the desktop and console
|
|
* windows) can only be done by sending the hook message to the hooking app.
|
|
* (This is because we must not load the hookproc DLL into the server process).
|
|
* The hook types this can be done with are currently WH_JOURNALRECORD,
|
|
* WH_JOURNALPLAYBACK, WH_KEYBOARD and WH_MOUSE : these are all marked as
|
|
* HKF_INTERSENDABLE. In order to prevent a global hooker from locking up the whole
|
|
* system, the hook message is sent with a timeout. To ensure minimal
|
|
* performance degradation, the hooker process is set to foreground priority,
|
|
* and prevented from being set back to background priority with the
|
|
* TIF_GLOBALHOOKER bit in hooking thread's pti->flags.
|
|
* Hooking emulated DOS apps is prevented with the TIF_DOSEMULATOR bit in the
|
|
* console thread: this is because these apps typically hog the CPU so much that
|
|
* the hooking app does not respond rapidly enough to the hook messsages sent
|
|
* to it. IanJa Nov 1994.
|
|
*
|
|
* History:
|
|
* 02-07-91 DavidPe Created.
|
|
* 1994 Nov 02 IanJa Hooking desktop and console windows.
|
|
\***************************************************************************/
|
|
|
|
LRESULT xxxCallHook2(
|
|
PHOOK phkCall,
|
|
int nCode,
|
|
WPARAM wParam,
|
|
LPARAM lParam,
|
|
LPBOOL lpbAnsiHook)
|
|
{
|
|
UINT iHook;
|
|
PHOOK phkSave;
|
|
LONG_PTR nRet;
|
|
PTHREADINFO ptiCurrent;
|
|
BOOL fLoadSuccess;
|
|
TL tlphkCall;
|
|
TL tlphkSave;
|
|
BYTE bHookFlags;
|
|
BOOL fMustIntersend;
|
|
|
|
CheckCritIn();
|
|
|
|
if (phkCall == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
iHook = phkCall->iHook;
|
|
|
|
ptiCurrent = PtiCurrent();
|
|
/*
|
|
* Only low level hooks are allowed in the RIT context
|
|
* (This check used to be done in PhkFirstValid).
|
|
*/
|
|
if (ptiCurrent == gptiRit) {
|
|
switch (iHook) {
|
|
case WH_MOUSE_LL:
|
|
case WH_KEYBOARD_LL:
|
|
|
|
#ifdef REDIRECTION
|
|
case WH_HITTEST:
|
|
#endif // REDIRECTION
|
|
|
|
break;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If this queue is in cleanup, exit: it has no business calling back
|
|
* a hook proc. Also check if hooks are disabled for the thread.
|
|
*/
|
|
if ( ptiCurrent->TIF_flags & (TIF_INCLEANUP | TIF_DISABLEHOOKS) ||
|
|
((ptiCurrent->rpdesk == NULL) && (phkCall->iHook != WH_MOUSE_LL))) {
|
|
return ampiHookError[iHook + 1];
|
|
}
|
|
|
|
/*
|
|
* Try to call each hook in the list until one is successful or
|
|
* we reach the end of the list.
|
|
*/
|
|
do {
|
|
*lpbAnsiHook = phkCall->flags & HF_ANSI;
|
|
bHookFlags = abHookFlags[phkCall->iHook + 1];
|
|
|
|
/*
|
|
* Some WH_SHELL hook types can be called from console
|
|
* HSHELL_APPCOMMAND added for bug 346575 DefWindowProc invokes a shell hook
|
|
* for console windows if they don't handle the wm_appcommand message - we need the hook
|
|
* to go through for csrss.
|
|
*/
|
|
if ((phkCall->iHook == WH_SHELL) && (ptiCurrent->TIF_flags & TIF_CSRSSTHREAD)) {
|
|
if ((nCode == HSHELL_LANGUAGE) || (nCode == HSHELL_WINDOWACTIVATED) ||
|
|
(nCode == HSHELL_APPCOMMAND)) {
|
|
bHookFlags |= HKF_INTERSENDABLE;
|
|
}
|
|
}
|
|
|
|
if ((phkCall->iHook == WH_SHELL) && (ptiCurrent->TIF_flags & TIF_SYSTEMTHREAD)) {
|
|
if ((nCode == HSHELL_ACCESSIBILITYSTATE) ) {
|
|
bHookFlags |= HKF_INTERSENDABLE;
|
|
}
|
|
}
|
|
|
|
fMustIntersend =
|
|
(GETPTI(phkCall) != ptiCurrent) &&
|
|
(
|
|
/*
|
|
* We always want to intersend journal hooks.
|
|
* CONSIDER (adams): Why? There's a performance hit by
|
|
* doing so, so if we haven't a reason, we shouldn't
|
|
* do it.
|
|
*
|
|
* we also need to intersend low level hooks. They can be called
|
|
* from the desktop thread, the raw input thread AND also from
|
|
* any thread that calls CallNextHookEx.
|
|
*/
|
|
(bHookFlags & (HKF_JOURNAL | HKF_LOWLEVEL))
|
|
|
|
/*
|
|
* We must intersend if a 16bit app hooks a 32bit app
|
|
* because we can't load a 16bit dll into a 32bit process.
|
|
* We must also intersend if a 16bit app hooks another 16bit app
|
|
* in a different VDM, because we can't load a 16bit dll from
|
|
* one VDM into a 16bit app in another VDM (because that
|
|
* VDM is actually a 32bit process).
|
|
*/
|
|
||
|
|
( GETPTI(phkCall)->TIF_flags & TIF_16BIT &&
|
|
( !(ptiCurrent->TIF_flags & TIF_16BIT) ||
|
|
ptiCurrent->ppi != GETPTI(phkCall)->ppi))
|
|
|
|
#if defined(_WIN64)
|
|
|
|
/*
|
|
* Intersend if a 64bit app hooks a 32bit app or
|
|
* a 32bit app hooks a 64bit app.
|
|
* This is necessary since a hook DLL can not be loaded
|
|
* cross bit type.
|
|
*/
|
|
||
|
|
( (GETPTI(phkCall)->TIF_flags & TIF_WOW64) !=
|
|
(ptiCurrent->TIF_flags & TIF_WOW64)
|
|
)
|
|
|
|
#endif /* defined(_WIN64) */
|
|
|
|
/*
|
|
* We must intersend if a console or system thread is calling a hook
|
|
* that is not in the same console or the system process.
|
|
*/
|
|
||
|
|
( ptiCurrent->TIF_flags & (TIF_CSRSSTHREAD | TIF_SYSTEMTHREAD) &&
|
|
GETPTI(phkCall)->ppi != ptiCurrent->ppi)
|
|
|
|
/*
|
|
* If this is a global and non-journal hook, do a security
|
|
* check on the current desktop to see if we can call here.
|
|
* Note that we allow processes with the SYSTEM_LUID to hook
|
|
* other processes even if the other process says that it
|
|
* doesn't allow other accounts to hook them. We did this
|
|
* because there was a bug in NT 3.x that allowed it and some
|
|
* services were written to use it.
|
|
*/
|
|
||
|
|
( phkCall->flags & HF_GLOBAL &&
|
|
!RtlEqualLuid(&GETPTI(phkCall)->ppi->luidSession, &ptiCurrent->ppi->luidSession) &&
|
|
!(ptiCurrent->TIF_flags & TIF_ALLOWOTHERACCOUNTHOOK) &&
|
|
!RtlEqualLuid(&GETPTI(phkCall)->ppi->luidSession, &luidSystem))
|
|
|
|
/*
|
|
* We must intersend if the hooking thread is running in
|
|
* another process and is restricted.
|
|
*/
|
|
||
|
|
( GETPTI(phkCall)->ppi != ptiCurrent->ppi &&
|
|
IsRestricted(GETPTI(phkCall)->pEThread))
|
|
);
|
|
|
|
/*
|
|
* We're calling back... make sure the hook doesn't go away while
|
|
* we're calling back. We've thread locked here: we must unlock before
|
|
* returning or enumerating the next hook in the chain.
|
|
*/
|
|
ThreadLockAlwaysWithPti(ptiCurrent, phkCall, &tlphkCall);
|
|
|
|
if (!fMustIntersend) {
|
|
/*
|
|
* Make sure the DLL for this hook, if any, has been loaded
|
|
* for the current process.
|
|
*/
|
|
if ((phkCall->ihmod != -1) &&
|
|
(TESTHMODLOADED(ptiCurrent, phkCall->ihmod) == 0)) {
|
|
|
|
/*
|
|
* Try loading the library, since it isn't loaded in this processes
|
|
* context. First lock this hook so it doesn't go away while we're
|
|
* loading this library.
|
|
*/
|
|
fLoadSuccess = (xxxLoadHmodIndex(phkCall->ihmod) != NULL);
|
|
|
|
/*
|
|
* If the LoadLibrary() failed, skip to the next hook and try
|
|
* again.
|
|
*/
|
|
if (!fLoadSuccess) {
|
|
goto LoopAgain;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Is WH_DEBUG installed? If we're not already calling it, do so.
|
|
*/
|
|
if (IsHooked(ptiCurrent, WHF_DEBUG) && (phkCall->iHook != WH_DEBUG)) {
|
|
DEBUGHOOKINFO debug;
|
|
|
|
debug.idThread = TIDq(ptiCurrent);
|
|
debug.idThreadInstaller = 0;
|
|
debug.code = nCode;
|
|
debug.wParam = wParam;
|
|
debug.lParam = lParam;
|
|
|
|
if (xxxCallHook(HC_ACTION, phkCall->iHook, (LPARAM)&debug, WH_DEBUG)) {
|
|
/*
|
|
* If WH_DEBUG returned non-zero, skip this hook and
|
|
* try the next one.
|
|
*/
|
|
goto LoopAgain;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Make sure the hook is still around before we
|
|
* try and call it.
|
|
*/
|
|
if (HMIsMarkDestroy(phkCall)) {
|
|
goto LoopAgain;
|
|
}
|
|
|
|
/*
|
|
* Time to call the hook! Lock it first so that it doesn't go away
|
|
* while we're using it. Thread lock right away in case the lock frees
|
|
* the previous contents.
|
|
*/
|
|
|
|
#if DBG
|
|
if (phkCall->flags & HF_GLOBAL) {
|
|
UserAssert(phkCall->ptiHooked == NULL);
|
|
} else {
|
|
UserAssert(phkCall->ptiHooked == ptiCurrent);
|
|
}
|
|
#endif
|
|
phkSave = ptiCurrent->sphkCurrent;
|
|
ThreadLockWithPti(ptiCurrent, phkSave, &tlphkSave);
|
|
|
|
if (ptiCurrent->pClientInfo) {
|
|
try {
|
|
ptiCurrent->pClientInfo->phkCurrent = phkCall;
|
|
} except (W32ExceptionHandler(TRUE, RIP_WARNING)) {
|
|
nRet = 0;
|
|
goto Error;
|
|
}
|
|
}
|
|
Lock(&ptiCurrent->sphkCurrent, phkCall);
|
|
|
|
nRet = xxxHkCallHook(phkCall, nCode, wParam, lParam);
|
|
|
|
Lock(&ptiCurrent->sphkCurrent, phkSave);
|
|
if (ptiCurrent->pClientInfo) {
|
|
try {
|
|
ptiCurrent->pClientInfo->phkCurrent = phkSave;
|
|
} except (W32ExceptionHandler(TRUE, RIP_WARNING)) {
|
|
nRet = 0;
|
|
goto Error;
|
|
}
|
|
}
|
|
ThreadUnlock(&tlphkSave);
|
|
|
|
/*
|
|
* This hook proc faulted, so unhook it and try the next one.
|
|
*/
|
|
if (phkCall->flags & HF_HOOKFAULTED) {
|
|
PHOOK phkFault;
|
|
|
|
phkCall = PhkNextValid(phkCall);
|
|
phkFault = ThreadUnlock(&tlphkCall);
|
|
if (phkFault != NULL) {
|
|
FreeHook(phkFault);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Lastly, we're done with this hook so it is ok to unlock it (it may
|
|
* get freed here!
|
|
*/
|
|
ThreadUnlock(&tlphkCall);
|
|
|
|
return nRet;
|
|
|
|
} else if (bHookFlags & HKF_INTERSENDABLE) {
|
|
|
|
/*
|
|
* Receiving thread can access this structure since the
|
|
* sender thread's stack is locked down during xxxInterSendMsgEx
|
|
*/
|
|
HOOKMSGSTRUCT hkmp;
|
|
int timeout = 200; // 1/5 second !!!
|
|
|
|
hkmp.lParam = lParam;
|
|
hkmp.phk = phkCall;
|
|
hkmp.nCode = nCode;
|
|
|
|
/*
|
|
* Thread lock right away in case the lock frees the previous contents
|
|
*/
|
|
phkSave = ptiCurrent->sphkCurrent;
|
|
|
|
ThreadLockWithPti(ptiCurrent, phkSave, &tlphkSave);
|
|
|
|
Lock(&ptiCurrent->sphkCurrent, phkCall);
|
|
if (ptiCurrent->pClientInfo) {
|
|
try {
|
|
ptiCurrent->pClientInfo->phkCurrent = phkCall;
|
|
} except (W32ExceptionHandler(TRUE, RIP_WARNING)) {
|
|
nRet = 0;
|
|
goto Error;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Make sure we don't get hung!
|
|
*/
|
|
if (bHookFlags & HKF_LOWLEVEL) {
|
|
timeout = gnllHooksTimeout;
|
|
if (phkCall->fLastHookHung) {
|
|
/*
|
|
* WindowsBug: 307738
|
|
* Ever Quest hooks the Low Level hook.
|
|
* If the timeout occurred in the last hook
|
|
* callback, let's make the timeout shorter
|
|
* so that the RIT is not blocked by that.
|
|
*/
|
|
TAGMSG1(DBGTAG_KBD, "xxxCallHook2: LL Hook target pti=%p is marked as hung, adjusting timeout to 20", GETPTI(phkCall));
|
|
timeout = 20;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CONSIDER(adams): Why should a journaling hook be allowed to
|
|
* hang the console or a system thread? Will that interfere with
|
|
* the user's ability to cancel journaling through Ctrl+Esc?
|
|
*/
|
|
if (((bHookFlags & HKF_LOWLEVEL) == 0) &&
|
|
( (bHookFlags & HKF_JOURNAL) ||
|
|
!(ptiCurrent->TIF_flags & (TIF_CSRSSTHREAD | TIF_SYSTEMTHREAD)))) {
|
|
|
|
nRet = xxxInterSendMsgEx(NULL, WM_HOOKMSG, wParam,
|
|
(LPARAM)&hkmp, ptiCurrent, GETPTI(phkCall), NULL);
|
|
} else {
|
|
/*
|
|
* We are a server thread (console/desktop) and we aren't
|
|
* journalling, so we can't allow the hookproc to hang us -
|
|
* we must use a timeout.
|
|
*/
|
|
INTRSENDMSGEX ism;
|
|
|
|
ism.fuCall = ISM_TIMEOUT;
|
|
ism.fuSend = SMTO_ABORTIFHUNG | SMTO_NORMAL;
|
|
ism.uTimeout = timeout;
|
|
ism.lpdwResult = &nRet;
|
|
|
|
/*
|
|
* Don't hook DOS apps connected to the emulator - they often
|
|
* grab too much CPU for the callback to the hookproc to
|
|
* complete in a timely fashion, causing poor response.
|
|
*/
|
|
if ((ptiCurrent->TIF_flags & TIF_DOSEMULATOR) ||
|
|
FHungApp(GETPTI(phkCall), CMSHUNGAPPTIMEOUT) ||
|
|
!xxxInterSendMsgEx(NULL, WM_HOOKMSG, wParam,
|
|
(LPARAM)&hkmp, ptiCurrent, GETPTI(phkCall), &ism)) {
|
|
nRet = ampiHookError[iHook + 1];
|
|
}
|
|
|
|
/*
|
|
* If the low-level hook is eaten, the app may wake up from
|
|
* MsgWaitForMultipleObjects, clear the wake mask, but not get
|
|
* anything in GetMessage / PeekMessage and we will think it's
|
|
* hung. This causes problems in DirectInput because then the
|
|
* app may miss some hooks if FHungApp returns true, see bug
|
|
* NTBug 430342 for more details on this.
|
|
*/
|
|
if ((bHookFlags & HKF_LOWLEVEL) && nRet) {
|
|
SET_TIME_LAST_READ(GETPTI(phkCall));
|
|
}
|
|
}
|
|
|
|
Lock(&ptiCurrent->sphkCurrent, phkSave);
|
|
if (ptiCurrent->pClientInfo) {
|
|
try {
|
|
ptiCurrent->pClientInfo->phkCurrent = phkSave;
|
|
} except (W32ExceptionHandler(TRUE, RIP_WARNING)) {
|
|
nRet = 0;
|
|
}
|
|
}
|
|
Error:
|
|
ThreadUnlock(&tlphkSave);
|
|
ThreadUnlock(&tlphkCall);
|
|
return nRet;
|
|
}
|
|
// fall-through
|
|
|
|
LoopAgain:
|
|
phkCall = PhkNextValid(phkCall);
|
|
ThreadUnlock(&tlphkCall);
|
|
} while (phkCall != NULL);
|
|
|
|
return ampiHookError[iHook + 1];
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxCallMouseHook
|
|
*
|
|
* This is a helper routine that packages up a MOUSEHOOKSTRUCTEX and calls
|
|
* the WH_MOUSE hook.
|
|
*
|
|
* History:
|
|
* 02-09-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
BOOL xxxCallMouseHook(
|
|
UINT message,
|
|
PMOUSEHOOKSTRUCTEX pmhs,
|
|
BOOL fRemove)
|
|
{
|
|
BOOL bAnsiHook;
|
|
|
|
/*
|
|
* Call the mouse hook.
|
|
*/
|
|
if (xxxCallHook2(PhkFirstValid(PtiCurrent(), WH_MOUSE), fRemove ?
|
|
HC_ACTION : HC_NOREMOVE, (DWORD)message, (LPARAM)pmhs, &bAnsiHook)) {
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxCallJournalRecordHook
|
|
*
|
|
* This is a helper routine that packages up an EVENTMSG and calls
|
|
* the WH_JOURNALRECORD hook.
|
|
*
|
|
* History:
|
|
* 02-28-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
void xxxCallJournalRecordHook(
|
|
PQMSG pqmsg)
|
|
{
|
|
EVENTMSG emsg;
|
|
BOOL bAnsiHook;
|
|
|
|
/*
|
|
* Setup the EVENTMSG structure.
|
|
*/
|
|
emsg.message = pqmsg->msg.message;
|
|
emsg.time = pqmsg->msg.time;
|
|
|
|
if (RevalidateHwnd(pqmsg->msg.hwnd)) {
|
|
emsg.hwnd = pqmsg->msg.hwnd;
|
|
} else {
|
|
emsg.hwnd = NULL;
|
|
}
|
|
|
|
if ((emsg.message >= WM_MOUSEFIRST) && (emsg.message <= WM_MOUSELAST)) {
|
|
emsg.paramL = (UINT)pqmsg->msg.pt.x;
|
|
emsg.paramH = (UINT)pqmsg->msg.pt.y;
|
|
|
|
} else if ((emsg.message >= WM_KEYFIRST) && (emsg.message <= WM_KEYLAST)) {
|
|
BYTE bScanCode = LOBYTE(HIWORD(pqmsg->msg.lParam));
|
|
/*
|
|
* Build up a Win 3.1 compatible journal record key
|
|
* Win 3.1 ParamL 00 00 SC VK (SC=scan code VK=virtual key)
|
|
* Also set ParamH 00 00 00 SC to be compatible with our Playback
|
|
*
|
|
* If WM_*CHAR messages ever come this way we would have a problem
|
|
* because we would lose the top byte of the Unicode character. We'd
|
|
* We'd get ParamL 00 00 SC CH (SC=scan code, CH = low byte of WCHAR)
|
|
*
|
|
*/
|
|
if ((LOWORD(pqmsg->msg.wParam) == VK_PACKET) && (bScanCode == 0)) {
|
|
/*
|
|
* If we have an injected Unicode char (from SendInput), the
|
|
* character value was cached, let's give that to them too.
|
|
*/
|
|
emsg.paramL = (UINT)MAKELONG(pqmsg->msg.wParam, PtiCurrent()->wchInjected);
|
|
} else {
|
|
emsg.paramL = MAKELONG(MAKEWORD(pqmsg->msg.wParam, bScanCode),0);
|
|
}
|
|
emsg.paramH = bScanCode;
|
|
|
|
UserAssert((emsg.message != WM_CHAR) &&
|
|
(emsg.message != WM_DEADCHAR) &&
|
|
(emsg.message != WM_SYSCHAR) &&
|
|
(emsg.message != WM_SYSDEADCHAR));
|
|
/*
|
|
* Set extended-key bit.
|
|
*/
|
|
if (pqmsg->msg.lParam & 0x01000000) {
|
|
emsg.paramH |= 0x8000;
|
|
}
|
|
|
|
} else {
|
|
RIPMSG2(RIP_WARNING,
|
|
"Bad journal record message!\n"
|
|
" message = 0x%08lx\n"
|
|
" dwQEvent = 0x%08lx",
|
|
pqmsg->msg.message,
|
|
pqmsg->dwQEvent);
|
|
}
|
|
|
|
/*
|
|
* Call the journal recording hook.
|
|
*/
|
|
xxxCallHook2(PhkFirstGlobalValid(PtiCurrent(), WH_JOURNALRECORD), HC_ACTION, 0,
|
|
(LPARAM)&emsg, &bAnsiHook);
|
|
|
|
/*
|
|
* Write the MSG parameters back because the app may have modified it.
|
|
* AfterDark's screen saver password actually zero's out the keydown
|
|
* chars.
|
|
*
|
|
* If it was a mouse message patch up the mouse point. If it was a
|
|
* WM_KEYxxx message convert the Win 3.1 compatible journal record key
|
|
* back into a half backed WM_KEYxxx format. Only the VK and SC fields
|
|
* where initialized at this point.
|
|
*
|
|
* wParam 00 00 00 VK lParam 00 SC 00 00
|
|
*/
|
|
if ((pqmsg->msg.message >= WM_MOUSEFIRST) && (pqmsg->msg.message <= WM_MOUSELAST)) {
|
|
pqmsg->msg.pt.x = emsg.paramL;
|
|
pqmsg->msg.pt.y = emsg.paramH;
|
|
|
|
} else if ((pqmsg->msg.message >= WM_KEYFIRST) && (pqmsg->msg.message <= WM_KEYLAST)) {
|
|
(BYTE)pqmsg->msg.wParam = (BYTE)emsg.paramL;
|
|
((PBYTE)&pqmsg->msg.lParam)[2] = HIBYTE(LOWORD(emsg.paramL));
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxCallJournalPlaybackHook
|
|
*
|
|
*
|
|
* History:
|
|
* 03-01-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
DWORD xxxCallJournalPlaybackHook(
|
|
PQMSG pqmsg)
|
|
{
|
|
EVENTMSG emsg;
|
|
LONG dt;
|
|
PWND pwnd;
|
|
WPARAM wParam;
|
|
LPARAM lParam;
|
|
POINT pt;
|
|
PTHREADINFO ptiCurrent;
|
|
BOOL bAnsiHook = FALSE;
|
|
PHOOK phkCall;
|
|
TL tlphkCall;
|
|
|
|
UserAssert(IsWinEventNotifyDeferredOK());
|
|
|
|
TryNextEvent:
|
|
|
|
/*
|
|
* Initialized to the current time for compatibility with
|
|
* <= 3.0.
|
|
*/
|
|
emsg.time = NtGetTickCount();
|
|
ptiCurrent = PtiCurrent();
|
|
pwnd = NULL;
|
|
|
|
phkCall = PhkFirstGlobalValid(ptiCurrent, WH_JOURNALPLAYBACK);
|
|
ThreadLockWithPti(ptiCurrent, phkCall, &tlphkCall);
|
|
|
|
dt = (DWORD)xxxCallHook2(phkCall, HC_GETNEXT, 0, (LPARAM)&emsg, &bAnsiHook);
|
|
|
|
/*
|
|
* -1 means some error occured. Return -1 for error.
|
|
*/
|
|
if (dt == 0xFFFFFFFF) {
|
|
ThreadUnlock(&tlphkCall);
|
|
return dt;
|
|
}
|
|
|
|
/*
|
|
* Update the message id. Need this if we decide to sleep.
|
|
*/
|
|
pqmsg->msg.message = emsg.message;
|
|
|
|
if (dt > 0) {
|
|
if (ptiCurrent->TIF_flags & TIF_IGNOREPLAYBACKDELAY) {
|
|
/*
|
|
* This flag tells us to ignore the requested delay (set in mnloop)
|
|
* We clear it to indicate that we did so.
|
|
*/
|
|
RIPMSG1(RIP_WARNING, "Journal Playback delay ignored (%lx)", emsg.message);
|
|
ptiCurrent->TIF_flags &= ~TIF_IGNOREPLAYBACKDELAY;
|
|
dt = 0;
|
|
} else {
|
|
ThreadUnlock(&tlphkCall);
|
|
return dt;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The app is ready to be asked for the next event
|
|
*/
|
|
|
|
if ((emsg.message >= WM_MOUSEFIRST) && (emsg.message <= WM_MOUSELAST)) {
|
|
|
|
pt.x = (int)emsg.paramL;
|
|
pt.y = (int)emsg.paramH;
|
|
|
|
lParam = MAKELONG(LOWORD(pt.x), LOWORD(pt.y));
|
|
wParam = 0;
|
|
|
|
/*
|
|
* If the message has changed the mouse position,
|
|
* update the cursor.
|
|
*/
|
|
if (pt.x != gpsi->ptCursor.x || pt.y != gpsi->ptCursor.y) {
|
|
zzzInternalSetCursorPos(pt.x, pt.y);
|
|
}
|
|
|
|
} else if ((emsg.message >= WM_KEYFIRST) && (emsg.message < WM_KEYLAST)) {
|
|
UINT wExtraStuff = 0;
|
|
|
|
if ((emsg.message == WM_KEYUP) || (emsg.message == WM_SYSKEYUP)) {
|
|
wExtraStuff |= 0x8000;
|
|
}
|
|
|
|
if ((emsg.message == WM_SYSKEYUP) || (emsg.message == WM_SYSKEYDOWN)) {
|
|
wExtraStuff |= 0x2000;
|
|
}
|
|
|
|
if (emsg.paramH & 0x8000) {
|
|
wExtraStuff |= 0x0100;
|
|
}
|
|
|
|
if (TestKeyStateDown(ptiCurrent->pq, (BYTE)emsg.paramL)) {
|
|
wExtraStuff |= 0x4000;
|
|
}
|
|
lParam = MAKELONG(1, (UINT)((emsg.paramH & 0xFF) | wExtraStuff));
|
|
|
|
if ((LOWORD(emsg.paramL) == VK_PACKET) && (LOBYTE(emsg.paramH) == 0)) {
|
|
/*
|
|
* We are playing back an injected Unicode char (see SendInput)
|
|
* save the character for TranslateMessage to pick up.
|
|
*/
|
|
ptiCurrent->wchInjected = HIWORD(emsg.paramL);
|
|
} else {
|
|
/*
|
|
* Raid# 65331
|
|
* WM_KEY* and WM_SYSKEY* messages should only contain 8bit Virtual Keys.
|
|
* Some applications passes scan code in HIBYTE and could mess up
|
|
* the system. E.g. Tab Keydown, paramL: 0x0f09 where 0f is scan code
|
|
*/
|
|
DWORD dwMask = 0xff;
|
|
|
|
/*
|
|
* There are old ANSI apps that only fill in the byte for when
|
|
* they generate journal playback so we used to strip everything
|
|
* else off. That however breaks unicode journalling; 22645
|
|
* (Yes, some apps apparently do Playback WM_*CHAR msgs!)
|
|
*
|
|
*/
|
|
if (!bAnsiHook || IS_DBCS_ENABLED()) {
|
|
if (IS_CHAR_MSG(emsg.message)) {
|
|
RIPMSG1(RIP_VERBOSE, "Unusual char message(%x) passed through JournalPlayback.", emsg.message);
|
|
/*
|
|
* Don't mask off HIBYTE(LOWORD(paramL)) for DBCS and UNICODE.
|
|
*/
|
|
dwMask = 0xffff;
|
|
}
|
|
}
|
|
|
|
wParam = emsg.paramL & dwMask;
|
|
}
|
|
|
|
} else if (emsg.message == WM_QUEUESYNC) {
|
|
if (emsg.paramL == 0) {
|
|
pwnd = ptiCurrent->pq->spwndActive;
|
|
} else {
|
|
if ((pwnd = RevalidateHwnd((HWND)IntToPtr( emsg.paramL ))) == NULL)
|
|
pwnd = ptiCurrent->pq->spwndActive;
|
|
}
|
|
|
|
} else {
|
|
/*
|
|
* This event doesn't match up with what we're looking
|
|
* for. If the hook is still valid, then skip this message
|
|
* and try the next.
|
|
*/
|
|
if (phkCall == NULL || phkCall->offPfn == 0L) {
|
|
/* Hook is nolonger valid, return -1 */
|
|
ThreadUnlock(&tlphkCall);
|
|
return 0xFFFFFFFF;
|
|
}
|
|
|
|
RIPMSG1(RIP_WARNING,
|
|
"Bad journal playback message=0x%08lx",
|
|
emsg.message);
|
|
|
|
xxxCallHook(HC_SKIP, 0, 0, WH_JOURNALPLAYBACK);
|
|
ThreadUnlock(&tlphkCall);
|
|
goto TryNextEvent;
|
|
}
|
|
|
|
StoreQMessage(pqmsg, pwnd, emsg.message, wParam, lParam, 0, 0, 0);
|
|
|
|
ThreadUnlock(&tlphkCall);
|
|
return 0;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* FreeHook
|
|
*
|
|
* Free hook unlinks the HOOK structure from its hook-list and removes
|
|
* any hmod dependencies on this hook. It also frees the HOOK structure.
|
|
*
|
|
* History:
|
|
* 01-31-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
VOID FreeHook(
|
|
PHOOK phkFree)
|
|
{
|
|
/*
|
|
* Paranoia...
|
|
*/
|
|
UserAssert(!(phkFree->flags & HF_FREED));
|
|
|
|
/*
|
|
* If we came from zzzUnhookWindowsHookEx, journalling hooks have
|
|
* already been cleaned up. Otherwise, they'll get cleaned up in
|
|
* xxxInternalGetMessage through the gpdeskRecalcQueueAttach mechanism.
|
|
*/
|
|
|
|
/*
|
|
* Clear fsHooks bits the first time around (and mark it as destroyed).
|
|
*/
|
|
if (!(phkFree->flags & HF_DESTROYED)) {
|
|
DbgValidateHooks (phkFree, phkFree->iHook);
|
|
phkFree->flags |= HF_DESTROYED;
|
|
/*
|
|
* This hook has been marked as destroyed so CheckWHSBits
|
|
* won't take it into account when updating the fsHooks bits.
|
|
* However, this means that right at this moment fsHooks is
|
|
* out of sync. So we need a flag to make the assertion
|
|
* happy.
|
|
*/
|
|
#if DBG
|
|
phkFree->flags |= HF_INCHECKWHF;
|
|
#endif
|
|
UserAssert((phkFree->ptiHooked != NULL) || (phkFree->flags & HF_GLOBAL));
|
|
CheckWHFBits(phkFree->ptiHooked != NULL
|
|
? phkFree->ptiHooked
|
|
: GETPTI(phkFree),
|
|
phkFree->iHook);
|
|
#if DBG
|
|
phkFree->flags &= ~HF_INCHECKWHF;
|
|
#endif
|
|
}
|
|
/*
|
|
* Mark it for destruction. If it the object is locked it can't
|
|
* be freed right now.
|
|
*/
|
|
if (!HMMarkObjectDestroy((PVOID)phkFree)) {
|
|
return;
|
|
}
|
|
/*
|
|
* We're going to free this hook so get it off the list.
|
|
*/
|
|
UnlinkHook(phkFree);
|
|
/*
|
|
* Now remove the hmod dependency and free the
|
|
* HOOK structure.
|
|
*/
|
|
if (phkFree->ihmod >= 0) {
|
|
RemoveHmodDependency(phkFree->ihmod);
|
|
}
|
|
|
|
#ifdef HOOKBATCH
|
|
/*
|
|
* Free the cached Events
|
|
*/
|
|
if (phkFree->aEventCache) {
|
|
UserFreePool(phkFree->aEventCache);
|
|
phkFree->aEventCache = NULL;
|
|
}
|
|
#endif //HOOKBATCH
|
|
|
|
#if DBG
|
|
phkFree->flags |= HF_FREED;
|
|
#endif
|
|
|
|
HMFreeObject((PVOID)phkFree);
|
|
return;
|
|
}
|
|
/***************************************************************************\
|
|
* UnlinkHook
|
|
*
|
|
* Gets a hook out of its chain. Note that FreeThreadsWindowHooks unlinks
|
|
* some hooks but don't free them. So this function doesn't assume that
|
|
* the hook is going away.
|
|
*
|
|
* History:
|
|
* 04-25-97 GerardoB Added Header
|
|
\***************************************************************************/
|
|
void UnlinkHook(
|
|
PHOOK phkFree)
|
|
{
|
|
PHOOK *pphkNext;
|
|
PTHREADINFO ptiT;
|
|
|
|
CheckCritIn();
|
|
/*
|
|
* Since we have the HOOK structure, we can tell if this a global
|
|
* or local hook and start on the right list.
|
|
*/
|
|
if (phkFree->flags & HF_GLOBAL) {
|
|
pphkNext = &GETPTI(phkFree)->pDeskInfo->aphkStart[phkFree->iHook + 1];
|
|
} else {
|
|
ptiT = phkFree->ptiHooked;
|
|
if (ptiT == NULL) {
|
|
/*
|
|
* Already unlinked (by FreeThreadsWindowHooks)
|
|
*/
|
|
return;
|
|
} else {
|
|
/*
|
|
* Clear ptiHooked so we won't try to unlink it again.
|
|
*/
|
|
phkFree->ptiHooked = NULL;
|
|
}
|
|
pphkNext = &(ptiT->aphkStart[phkFree->iHook + 1]);
|
|
/*
|
|
* There must be at least one hook in the chain
|
|
*/
|
|
UserAssert(*pphkNext != NULL);
|
|
}
|
|
/*
|
|
* Find the address of the phkNext pointing to phkFree
|
|
*/
|
|
while ((*pphkNext != phkFree) && (*pphkNext != NULL)) {
|
|
pphkNext = &(*pphkNext)->phkNext;
|
|
}
|
|
/*
|
|
* If we haven't found it, it must be global hook whose owner is gone or
|
|
* has switched desktops.
|
|
*/
|
|
if (*pphkNext == NULL) {
|
|
UserAssert(phkFree->flags & HF_GLOBAL);
|
|
/*
|
|
* if we saved a pdesk, use it. Else use the one we allocated it from
|
|
*/
|
|
if (phkFree->rpdesk != NULL) {
|
|
UserAssert(GETPTI(phkFree) == gptiRit);
|
|
UserAssert(phkFree->rpdesk != NULL);
|
|
UserAssert(phkFree->rpdesk->pDeskInfo != gptiRit->pDeskInfo);
|
|
|
|
pphkNext = &phkFree->rpdesk->pDeskInfo->aphkStart[phkFree->iHook + 1];
|
|
} else {
|
|
UserAssert(GETPTI(phkFree)->pDeskInfo != phkFree->head.rpdesk->pDeskInfo);
|
|
pphkNext = &phkFree->head.rpdesk->pDeskInfo->aphkStart[phkFree->iHook + 1];
|
|
}
|
|
|
|
UserAssert(*pphkNext != NULL);
|
|
while ((*pphkNext != phkFree) && (*pphkNext != NULL)) {
|
|
pphkNext = &(*pphkNext)->phkNext;
|
|
}
|
|
}
|
|
/*
|
|
* We're supposed to find it
|
|
*/
|
|
UserAssert(*pphkNext == phkFree);
|
|
/*
|
|
* Unlink it
|
|
*/
|
|
*pphkNext = phkFree->phkNext;
|
|
phkFree->phkNext = NULL;
|
|
/*
|
|
* If we had a desktop, unlock it
|
|
*/
|
|
if (phkFree->rpdesk != NULL) {
|
|
UserAssert(phkFree->flags & HF_GLOBAL);
|
|
UserAssert(GETPTI(phkFree) == gptiRit);
|
|
UnlockDesktop(&phkFree->rpdesk, LDU_HOOK_DESK, 0);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* PhkFirstGlobalValid
|
|
*
|
|
* Returns the first not-destroyed hook on the given desktop info.
|
|
*
|
|
* History:
|
|
* 03/24/97 GerardoB Created
|
|
\***************************************************************************/
|
|
PHOOK PhkFirstGlobalValid(PTHREADINFO pti, int nFilterType)
|
|
{
|
|
PHOOK phk;
|
|
|
|
CheckCritIn();
|
|
phk = pti->pDeskInfo->aphkStart[nFilterType + 1];
|
|
/*
|
|
* Return the first hook that it's not destroyed (i.e, the
|
|
* first valid one).
|
|
*/
|
|
if ((phk != NULL) && (phk->flags & HF_DESTROYED)) {
|
|
phk = PhkNextValid(phk);
|
|
}
|
|
/*
|
|
* Good place to check fsHooks. If the bits are out of sync,
|
|
* someone must be adjusting them.
|
|
*/
|
|
DbgValidatefsHook(phk, nFilterType, pti, TRUE);
|
|
DbgValidateHooks(phk, nFilterType);
|
|
return phk;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* PhkFirstValid
|
|
*
|
|
* Given a filter-type PhkFirstValid() returns the first hook, if any, of the
|
|
* specified type.
|
|
*
|
|
* History:
|
|
* 02-10-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
PHOOK PhkFirstValid(
|
|
PTHREADINFO pti,
|
|
int nFilterType)
|
|
{
|
|
PHOOK phk;
|
|
CheckCritIn();
|
|
/*
|
|
* Grab the first hook off the local hook-list
|
|
* for the current queue.
|
|
*/
|
|
phk = pti->aphkStart[nFilterType + 1];
|
|
/*
|
|
* If there aren't any local hooks, try the global hooks.
|
|
*/
|
|
if (phk == NULL) {
|
|
phk = pti->pDeskInfo->aphkStart[nFilterType + 1];
|
|
}
|
|
/*
|
|
* Return the first hook that it's not destroyed (i.e, the
|
|
* first valid one).
|
|
*/
|
|
if ((phk != NULL) && (phk->flags & HF_DESTROYED)) {
|
|
phk = PhkNextValid(phk);
|
|
}
|
|
/*
|
|
* Good place to check fsHooks. If the bits are out of sync,
|
|
* someone must be adjusting them.
|
|
*/
|
|
|
|
DbgValidatefsHook(phk, nFilterType, pti, FALSE);
|
|
DbgValidateHooks(phk, nFilterType);
|
|
return phk;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* FreeThreadsWindowHooks
|
|
*
|
|
* During 'exit-list' processing this function is called to free any hooks
|
|
* created on, or set for the current queue.
|
|
*
|
|
* History:
|
|
* 02-10-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
VOID FreeThreadsWindowHooks(VOID)
|
|
{
|
|
int iHook;
|
|
PHOOK phk, phkNext;
|
|
PTHREADINFO ptiCurrent = PtiCurrent();
|
|
|
|
/*
|
|
* If there is not thread info, there are not hooks to worry about
|
|
*/
|
|
if (ptiCurrent == NULL || ptiCurrent->rpdesk == NULL) {
|
|
return;
|
|
}
|
|
/*
|
|
* In case we have a hook locked in as the current hook unlock it
|
|
* so it can be freed
|
|
*/
|
|
Unlock(&ptiCurrent->sphkCurrent);
|
|
|
|
UserAssert(ptiCurrent->TIF_flags & TIF_INCLEANUP);
|
|
|
|
/*
|
|
* Loop through all the hook types.
|
|
*/
|
|
for (iHook = WH_MIN ; iHook <= WH_MAX ; ++iHook) {
|
|
/*
|
|
* Loop through all the hooks of this type, including the
|
|
* ones already marked as destroyed (so don't call
|
|
* PhkFirstValid and PhkNextValid).
|
|
*/
|
|
phk = ptiCurrent->aphkStart[iHook + 1];
|
|
if (phk == NULL) {
|
|
phk = ptiCurrent->pDeskInfo->aphkStart[iHook + 1];
|
|
UserAssert((phk == NULL) || (phk->flags & HF_GLOBAL));
|
|
}
|
|
|
|
while (phk != NULL) {
|
|
/*
|
|
* We might free phk below, so grab the next now
|
|
* If at end of local chain, jump to the global chain
|
|
*/
|
|
phkNext = phk->phkNext;
|
|
if ((phkNext == NULL) && !(phk->flags & HF_GLOBAL)) {
|
|
phkNext = ptiCurrent->pDeskInfo->aphkStart[iHook + 1];
|
|
UserAssert((phkNext == NULL) || (phkNext->flags & HF_GLOBAL));
|
|
}
|
|
/*
|
|
* If this is a local(thread) hook, unlink it and mark it as
|
|
* destroyed so we won't call it anymore. We want to do
|
|
* this even if not calling FreeHook; also note that
|
|
* FreeHook won't unlink it if locked so we do it here anyway.
|
|
*/
|
|
if (!(phk->flags & HF_GLOBAL)) {
|
|
UserAssert(ptiCurrent == phk->ptiHooked);
|
|
UnlinkHook(phk);
|
|
phk->flags |= HF_DESTROYED;
|
|
phk->phkNext = NULL;
|
|
}
|
|
/*
|
|
* If this hook was created by this thread, free it
|
|
*/
|
|
if (GETPTI(phk) == ptiCurrent) {
|
|
FreeHook(phk);
|
|
}
|
|
|
|
phk = phkNext;
|
|
}
|
|
/*
|
|
* All local hooks should be unlinked
|
|
*/
|
|
UserAssert(ptiCurrent->aphkStart[iHook + 1] == NULL);
|
|
} /* for (iHook = WH_MIN....*/
|
|
|
|
/*
|
|
* Keep fsHooks in sync.
|
|
*/
|
|
ptiCurrent->fsHooks = 0;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* zzzRegisterSystemThread: Private API
|
|
*
|
|
* Used to set various attributes pertaining to a thread.
|
|
*
|
|
* History:
|
|
* 21-Jun-1994 from Chicago Created.
|
|
\***************************************************************************/
|
|
|
|
VOID zzzRegisterSystemThread (DWORD dwFlags, DWORD dwReserved)
|
|
{
|
|
PTHREADINFO ptiCurrent;
|
|
|
|
UserAssert(dwReserved == 0);
|
|
|
|
if (dwReserved != 0)
|
|
return;
|
|
|
|
ptiCurrent = PtiCurrent();
|
|
|
|
if (dwFlags & RST_DONTATTACHQUEUE)
|
|
ptiCurrent->TIF_flags |= TIF_DONTATTACHQUEUE;
|
|
|
|
if (dwFlags & RST_DONTJOURNALATTACH) {
|
|
ptiCurrent->TIF_flags |= TIF_DONTJOURNALATTACH;
|
|
|
|
/*
|
|
* If we are already journaling, then this queue was already
|
|
* journal attached. We need to unattach and reattach journaling
|
|
* so that we are removed from the journal attached queues.
|
|
*/
|
|
if (FJOURNALPLAYBACK() || FJOURNALRECORD()) {
|
|
zzzJournalAttach(ptiCurrent, FALSE);
|
|
zzzJournalAttach(ptiCurrent, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxGetCursorPos
|
|
*
|
|
\***************************************************************************/
|
|
|
|
BOOL
|
|
xxxGetCursorPos(
|
|
LPPOINT lpPt)
|
|
{
|
|
POINT pt;
|
|
PTHREADINFO ptiCurrent = PtiCurrent();
|
|
|
|
CheckCritIn();
|
|
|
|
if (ptiCurrent->rpdesk && ptiCurrent->rpdesk != grpdeskRitInput) {
|
|
return FALSE;
|
|
}
|
|
|
|
pt.x = gpsi->ptCursor.x;
|
|
pt.y = gpsi->ptCursor.y;
|
|
|
|
try {
|
|
ProbeForWrite(lpPt, sizeof(POINT), DATAALIGN);
|
|
} except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
|
|
return FALSE;
|
|
}
|
|
|
|
#ifdef REDIRECTION
|
|
/*
|
|
* If there is no CBT hook installed bail out.
|
|
*/
|
|
if (IsHooked(PtiCurrent(), WHF_CBT)) {
|
|
xxxCallHook(HCBT_GETCURSORPOS, 0, (LPARAM)&pt, WH_CBT);
|
|
}
|
|
#endif // REDIRECTION
|
|
|
|
try {
|
|
RtlCopyMemory(lpPt, &pt, sizeof(POINT));
|
|
} except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* _RegisterUserApiHook
|
|
*
|
|
* History:
|
|
* 03-Mar-2000 JerrySh Created.
|
|
\***************************************************************************/
|
|
BOOL _RegisterUserApiHook(
|
|
PUNICODE_STRING pstrLib,
|
|
ULONG_PTR offPfnInitUserApiHook)
|
|
{
|
|
/*
|
|
* If we've already registered the UserApiHook, don't do it again.
|
|
*/
|
|
if (IsInsideUserApiHook()) {
|
|
RIPERR0(ERROR_ACCESS_DENIED, RIP_WARNING, "UserApiHook already registered");
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* If this thread is restricted, don't let it install a hook.
|
|
*/
|
|
if (IsRestricted(PsGetCurrentThread())) {
|
|
RIPERR0(ERROR_ACCESS_DENIED, RIP_WARNING, "Thread is restricted");
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Register the library with the library management routines so we
|
|
* can assure it's loaded into all the processes necessary.
|
|
*/
|
|
gihmodUserApiHook = GetHmodTableIndex(pstrLib);
|
|
if (gihmodUserApiHook == -1) {
|
|
RIPERR0(ERROR_MOD_NOT_FOUND, RIP_VERBOSE, "");
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Add a dependency on this module - meaning, increment a count
|
|
* that simply counts the number of hooks set into this module.
|
|
*/
|
|
AddHmodDependency(gihmodUserApiHook);
|
|
|
|
/*
|
|
* Remember which process registered the hook.
|
|
*/
|
|
gppiUserApiHook = PpiCurrent();
|
|
|
|
/*
|
|
* Remember the offset to the DefWindowProc init routine.
|
|
*/
|
|
goffPfnInitUserApiHook = offPfnInitUserApiHook;
|
|
|
|
/*
|
|
* Set the global flag in gpsi to signal hooking ( later used
|
|
* as a check at beginning of hooked API's for loading DLL. )
|
|
*/
|
|
SET_SRVIF(SRVIF_HOOKED);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* _UnregisterUserApiHook
|
|
*
|
|
* History:
|
|
* 03-Mar-2000 JerrySh Created.
|
|
\***************************************************************************/
|
|
BOOL _UnregisterUserApiHook(VOID)
|
|
{
|
|
/*
|
|
* The window proc handler must be unregistered by the same process that
|
|
* registered it.
|
|
*/
|
|
if (PpiCurrent() != gppiUserApiHook) {
|
|
RIPERR0(ERROR_ACCESS_DENIED, RIP_WARNING, "Process hasn't registered UserApiHook");
|
|
return FALSE;
|
|
}
|
|
gppiUserApiHook = NULL;
|
|
|
|
/*
|
|
* Make sure each hooked thread will unload the hook proc DLL
|
|
*/
|
|
if (gihmodUserApiHook >= 0) {
|
|
RemoveHmodDependency(gihmodUserApiHook);
|
|
gihmodUserApiHook = -1;
|
|
}
|
|
|
|
/*
|
|
* Clear the global flag in gpsi to signal disabled hooking ( later used
|
|
* as a check at beginning of hooked API's for loading DLL. )
|
|
*/
|
|
CLEAR_SRVIF(SRVIF_HOOKED);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
#ifdef MESSAGE_PUMP_HOOK
|
|
|
|
/***************************************************************************\
|
|
* _DoInitMessagePumpHook
|
|
*
|
|
* History:
|
|
* 05-Dec-2000 JStall Created
|
|
\***************************************************************************/
|
|
BOOL _DoInitMessagePumpHook(VOID)
|
|
{
|
|
PCLIENTTHREADINFO pcti = gptiCurrent->pcti;
|
|
++pcti->cMessagePumpHooks;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* _DoUninitMessagePumpHook
|
|
*
|
|
* History:
|
|
* 05-Dec-2000 JStall Created
|
|
\***************************************************************************/
|
|
BOOL _DoUninitMessagePumpHook(VOID)
|
|
{
|
|
PCLIENTTHREADINFO pcti = gptiCurrent->pcti;
|
|
if (pcti->cMessagePumpHooks <= 0) {
|
|
return FALSE;
|
|
}
|
|
--pcti->cMessagePumpHooks;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#endif // MESSAGE_PUMP_HOOK
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxLoadUserApiHook
|
|
*
|
|
* History:
|
|
* 03-Mar-2000 JerrySh Created.
|
|
\***************************************************************************/
|
|
BOOL xxxLoadUserApiHook(VOID)
|
|
{
|
|
PTHREADINFO ptiCurrent = PtiCurrent();
|
|
|
|
/*
|
|
* A check is made here to see if hooking is still applicable after we
|
|
* dropped to kernel. There is a timing issue that could create a problem
|
|
* where we check for hooking in user mode and decide to load but as we make the
|
|
* drop to kernel, someone else is unregistering hooks and resetting all
|
|
* globals.
|
|
*/
|
|
if (!IsInsideUserApiHook()){
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* If the DLL is already loaded in this process, we're done.
|
|
*/
|
|
if (TESTHMODLOADED(ptiCurrent, gihmodUserApiHook)) {
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Don't load the DLL if:
|
|
* - it's a system or CSRSS thread, it's in cleanup, or it's disabled hooks
|
|
* - it's another user who hasn't granted access
|
|
* - it's Win64 status doesn't match
|
|
*/
|
|
if ((ptiCurrent->TIF_flags & (TIF_SYSTEMTHREAD | TIF_CSRSSTHREAD | TIF_INCLEANUP | TIF_DISABLEHOOKS)) ||
|
|
(!RtlEqualLuid(&gppiUserApiHook->luidSession, &luidSystem) &&
|
|
!RtlEqualLuid(&gppiUserApiHook->luidSession, &ptiCurrent->ppi->luidSession) &&
|
|
!(ptiCurrent->TIF_flags & TIF_ALLOWOTHERACCOUNTHOOK)) ||
|
|
((gppiUserApiHook->W32PF_Flags & W32PF_WOW64) != (ptiCurrent->ppi->W32PF_Flags & W32PF_WOW64))) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Try loading the library, since it isn't loaded in this processes
|
|
* context.
|
|
*/
|
|
return (xxxLoadHmodIndex(gihmodUserApiHook) != NULL);
|
|
}
|
|
|